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:
@@ -264,6 +264,7 @@ static Autocomplete avatar_ac;
|
||||
static Autocomplete ai_subcommands_ac;
|
||||
static Autocomplete ai_set_subcommands_ac;
|
||||
static Autocomplete ai_set_custom_subcommands_ac;
|
||||
static Autocomplete ai_api_types_ac;
|
||||
static Autocomplete ai_remove_subcommands_ac;
|
||||
static Autocomplete url_ac;
|
||||
static Autocomplete executable_ac;
|
||||
@@ -453,6 +454,7 @@ static Autocomplete* all_acs[] = {
|
||||
&ai_subcommands_ac,
|
||||
&ai_set_subcommands_ac,
|
||||
&ai_set_custom_subcommands_ac,
|
||||
&ai_api_types_ac,
|
||||
&ai_remove_subcommands_ac,
|
||||
&ai_models_ac
|
||||
};
|
||||
@@ -1183,8 +1185,13 @@ cmd_ac_init(void)
|
||||
autocomplete_add_unsorted(ai_set_subcommands_ac, "provider", FALSE);
|
||||
autocomplete_add_unsorted(ai_set_subcommands_ac, "token", FALSE);
|
||||
autocomplete_add_unsorted(ai_set_subcommands_ac, "default-model", FALSE);
|
||||
autocomplete_add_unsorted(ai_set_subcommands_ac, "api-type", FALSE);
|
||||
autocomplete_add_unsorted(ai_set_subcommands_ac, "custom", FALSE);
|
||||
|
||||
autocomplete_add_unsorted(ai_api_types_ac, "chat-completions", FALSE);
|
||||
autocomplete_add_unsorted(ai_api_types_ac, "responses", FALSE);
|
||||
autocomplete_add_unsorted(ai_api_types_ac, "auto", FALSE);
|
||||
|
||||
autocomplete_add_unsorted(ai_set_custom_subcommands_ac, "tools", FALSE);
|
||||
autocomplete_add_unsorted(ai_set_custom_subcommands_ac, "search", FALSE);
|
||||
autocomplete_add_unsorted(ai_set_custom_subcommands_ac, "memory", FALSE);
|
||||
@@ -4532,6 +4539,12 @@ _force_encryption_autocomplete(ProfWin* window, const char* const input, gboolea
|
||||
static char* _ai_provider_and_model_autocomplete(ProfWin* window, const char* const input,
|
||||
gboolean previous, const char* cmd);
|
||||
|
||||
static char*
|
||||
_ai_api_type_find(const char* const search_str, gboolean previous, void* context)
|
||||
{
|
||||
return autocomplete_complete(ai_api_types_ac, search_str, FALSE, previous);
|
||||
}
|
||||
|
||||
static char*
|
||||
_ai_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
||||
{
|
||||
@@ -4560,6 +4573,18 @@ _ai_autocomplete(ProfWin* window, const char* const input, gboolean previous)
|
||||
return result;
|
||||
}
|
||||
|
||||
// /ai set api-type <provider> <type> - autocomplete the type value
|
||||
result = autocomplete_param_no_with_func(input, "/ai set api-type", 5, _ai_api_type_find, previous, NULL);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// /ai set api-type <provider> - autocomplete provider names
|
||||
result = autocomplete_param_with_func(input, "/ai set api-type", ai_providers_find, previous, NULL);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// /ai set custom <provider> <setting> <value> - autocomplete provider names
|
||||
result = autocomplete_param_with_func(input, "/ai set custom", ai_providers_find, previous, NULL);
|
||||
if (result) {
|
||||
|
||||
Reference in New Issue
Block a user