feat(ai): support both responses and chat completions APIs
Drive both OpenAI-compatible flavours from one code path. Requests default to /v1/responses and fall back once to /v1/chat/completions when the provider reports the endpoint missing (404/405/501) or rejects the payload shape (400/422); the working flavour is cached for the rest of the run as a first-attempt hint, with the other flavour kept as a fallback so a backend change self-corrects in-request. A per-provider override is available via /ai set api-type <provider> responses|chat-completions|auto and persisted as api_type= in the provider section. One payload builder serves both flavours (messages/stream vs input/stream/store); history and custom settings are serialized once per request and the per-flavour envelope is assembled per attempt under a single lock acquisition, so the fallback retry cannot double-count the prompt. ai_parse_response_typed() dispatches on the request flavour and splits extraction per envelope: chat completions anchored on choices[].message.content, responses on the output_text part with a string-aware backward scan bounded to that part's own object so a reasoning summary or a truncated body is never returned as the reply. Endpoint detection classifies wrong-model errors from the structured error code/type/message when present, so route-missing 404s from gateways no longer suppress the fallback; it preserves and surfaces the first payload-rejection error when the fallback also fails, and re-probes on an unparseable 2xx. resolved_api_type is written only through a locked helper guarded by a URL/api-type epoch, and api_url is snapshotted under settings_lock in the request and models-fetch threads, closing use-after-free and stale-cache races against /ai set provider. The models-fetch provider ref is taken on the main thread and released on every worker exit path. Reserved custom-setting keys are extended with input (store stays writable as a legitimate chat-completions parameter).
This commit is contained in:
@@ -2002,6 +2002,30 @@ prefs_ai_get_default_model(const char* const provider)
|
||||
return _ai_get_string(provider, "default_model");
|
||||
}
|
||||
|
||||
/* --- API type --- */
|
||||
|
||||
gboolean
|
||||
prefs_ai_set_api_type(const char* const provider, const char* const api_type)
|
||||
{
|
||||
if (!provider || !api_type || !prefs)
|
||||
return FALSE;
|
||||
if (api_type[0] == '\0')
|
||||
return prefs_ai_remove_api_type(provider);
|
||||
return _ai_set_string(provider, "api_type", api_type);
|
||||
}
|
||||
|
||||
gboolean
|
||||
prefs_ai_remove_api_type(const char* const provider)
|
||||
{
|
||||
return _ai_remove_string(provider, "api_type");
|
||||
}
|
||||
|
||||
char*
|
||||
prefs_ai_get_api_type(const char* const provider)
|
||||
{
|
||||
return _ai_get_string(provider, "api_type");
|
||||
}
|
||||
|
||||
/* --- Provider URL --- */
|
||||
|
||||
gboolean
|
||||
|
||||
Reference in New Issue
Block a user