#ifndef AI_CLIENT_H #define AI_CLIENT_H #include /** * @brief AI message structure for conversation history. */ typedef struct ai_message_t { gchar* role; /* "user" or "assistant" */ gchar* content; /* Message content */ } AIMessage; /** * @brief AI provider configuration. */ typedef struct ai_provider_t { gchar* name; /* Provider name (e.g., "openai", "perplexity") */ gchar* api_url; /* API endpoint URL */ gchar* project_id; /* Optional project ID (for some providers) */ gchar* default_model; /* Default model for this provider */ pthread_mutex_t settings_lock; /* Protects settings (iterated by the request thread) */ GHashTable* settings; /* Extensible per-provider settings (e.g., temperature=0.7) */ GList* models; /* Cached models (gchar*) */ gboolean models_fresh; /* Whether models cache is current */ gint32 ref_count; /* Reference count (atomic) */ } AIProvider; /** * @brief AI chat session structure. */ typedef struct ai_session_t { pthread_mutex_t lock; /* Protects all session fields below */ gchar* provider_name; /* Provider name */ AIProvider* provider; /* Provider configuration */ gchar* model; /* Model identifier (e.g., "gpt-4", "sonar") */ gchar* api_key; /* API key for this session */ GList* history; /* Conversation history (GList of AIMessage*) */ gint32 ref_count; /* Reference count (atomic) */ } AISession; /* ======================================================================== * Provider Management * ======================================================================== */ /** * Initialize the AI client and load default providers. */ void ai_client_init(void); /** * Shutdown the AI client and free resources. */ void ai_client_shutdown(void); /** * Get a provider by name. * @param name The provider name (e.g., "openai", "perplexity") * @return AIProvider*, or NULL if not found */ AIProvider* ai_get_provider(const gchar* name); /** * Add or update a provider configuration. * @param name The provider name * @param api_url The API endpoint URL * @return New AIProvider* (caller must unref when done) */ AIProvider* ai_add_provider(const gchar* name, const gchar* api_url); /** * Remove a provider by name. * @param name The provider name * @return TRUE if provider was removed, FALSE if not found */ gboolean ai_remove_provider(const gchar* name); /** * Increment the reference count of a provider. * @param provider The provider to reference * @return The same provider pointer */ AIProvider* ai_provider_ref(AIProvider* provider); /** * Decrement the reference count of a provider. * @param provider The provider to unreference */ void ai_provider_unref(AIProvider* provider); /** * List all configured providers. * @return GList of AIProvider* (caller must not free the list or providers, * and must not unref the returned providers) */ GList* ai_list_providers(void); /** * Find a provider name for autocomplete. * @param search_str The search string * @param previous Whether to go to previous match * @param context Unused * @return Provider name, or NULL if not found */ gchar* ai_providers_find(const char* const search_str, gboolean previous, void* context); /** * Reset the provider autocomplete state. * Called from cmd_ac_reset() to clear autocomplete state. */ void ai_providers_reset_ac(void); /** * Get the API key for a provider. * @param provider_name The provider name * @return The API key, or NULL if not set (caller must free) */ gchar* ai_get_provider_key(const gchar* provider_name); /** * Set the API key for a provider. * @param provider_name The provider name * @param api_key The API key to set */ void ai_set_provider_key(const gchar* provider_name, const gchar* api_key); /** * Set the default model for a provider. * @param provider_name The provider name * @param model The default model name */ void ai_set_provider_default_model(const gchar* provider_name, const gchar* model); /** * Get the default model for a provider. * @param provider_name The provider name * @return The default model name (caller must not free), or NULL if not set */ const gchar* ai_get_provider_default_model(const gchar* provider_name); /** * Set a custom setting for a provider. * Reserved payload keys (model, messages, stream) are rejected. * @param provider_name The provider name * @param setting The setting key (e.g., "temperature") * @param value The setting value, or NULL to remove * @return TRUE on success, FALSE on unknown provider or reserved key */ gboolean ai_set_provider_setting(const gchar* provider_name, const gchar* setting, const gchar* value); /** * Get a custom setting for a provider. * @param provider_name The provider name * @param setting The setting key * @return The setting value (caller must free), or NULL if not set */ gchar* ai_get_provider_setting(const gchar* provider_name, const gchar* setting); /** * Fetch available models from a provider's API. * @param provider_name The provider name * @param user_data User data (ProfAiWin* for UI display) * @return TRUE if the request was successfully queued, FALSE otherwise */ gboolean ai_fetch_models(const gchar* provider_name, gpointer user_data); /** * Check if models cache is fresh for a provider. * @param provider_name The provider name * @return TRUE if models are fresh, FALSE otherwise */ gboolean ai_models_are_fresh(const gchar* provider_name); /* ======================================================================== * Parsing helpers (exposed for testing) * ======================================================================== */ /** * Parse model IDs from an OpenAI-compatible API response. * Expected format: {"object":"list","data":[{"id":"model1",...},...]} * @param provider The provider to add models to * @param json The JSON response from the API */ void ai_parse_models_from_json(AIProvider* provider, const gchar* json); /** * Extract assistant content from an OpenAI chat-completions response body * ({"choices":[{"message":{"content":"..."}}]}). Decodes the \" and \n * escape sequences only. * @param response_json The JSON body from the provider * @return Newly allocated content string, or NULL if not found (caller frees) */ gchar* ai_parse_response(const gchar* response_json); /** * Extract human-readable error message from an API error envelope. * Expected format: {"error":{"message":"...","type":"...","code":...}} * Decodes \" \n \\ \t escapes. * @param error_json The JSON body of the error response * @return Newly allocated error message, or NULL if not parseable (caller frees) */ gchar* ai_parse_error_message(const gchar* error_json); /* ======================================================================== * Session Management * ======================================================================== */ /** * Create a new AI session with the specified provider and model. * @param provider_name The provider name (e.g., "openai") * @param model The model identifier (e.g., "gpt-4") * @return New AISession*, or NULL on failure */ AISession* ai_session_create(const gchar* provider_name, const gchar* model); /** * Increment the reference count of an AI session. * @param session The session to reference * @return The same session pointer */ AISession* ai_session_ref(AISession* session); /** * Decrement the reference count and free the session when it reaches zero. * @param session The session to unreference */ void ai_session_unref(AISession* session); /** * Add a message to the session history. * @param session The session * @param role The message role ("user" or "assistant") * @param content The message content */ void ai_session_add_message(AISession* session, const gchar* role, const gchar* content); /** * Clear the conversation history. * @param session The session */ void ai_session_clear_history(AISession* session); /** * Get the current model for a session. * @param session The session * @return The model name (caller must not free) */ const gchar* ai_session_get_model(AISession* session); /** * Set the model for a session. * @param session The session * @param model The model name */ void ai_session_set_model(AISession* session, const gchar* model); /** * Atomically switch session provider, model, and API key. * All mutations happen under the session lock to prevent races with * _ai_request_thread() which snapshots session state before making requests. * * @param session The session * @param provider_name New provider name * @param model New model identifier * @param api_key New API key (caller must free after calling this) */ void ai_session_switch(AISession* session, const gchar* provider_name, const gchar* model, gchar* api_key); /* ======================================================================== * Request Handling * ======================================================================== */ /** * Send a prompt to the AI provider asynchronously. * @param session The AI session containing provider and model * @param prompt The prompt to send * @param user_data User data (ProfAiWin* for UI display) * @return TRUE if the request was successfully queued, FALSE otherwise */ gboolean ai_send_prompt(AISession* session, const gchar* prompt, gpointer user_data); /** * Escape a string for JSON embedding. * @param str The string to escape * @return Newly allocated escaped string (caller must free) */ gchar* ai_json_escape(const gchar* str); #endif /* AI_CLIENT_H */