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:
@@ -44,6 +44,8 @@ void test_ai_set_provider_default_model(void** state);
|
||||
void test_ai_get_provider_default_model(void** state);
|
||||
void test_ai_set_provider_setting(void** state);
|
||||
void test_ai_set_provider_setting_reserved_key(void** state);
|
||||
void test_ai_set_provider_api_type(void** state);
|
||||
void test_ai_api_type_string_round_trip(void** state);
|
||||
void test_ai_get_provider_setting(void** state);
|
||||
/* Model caching tests */
|
||||
void test_ai_models_are_fresh_initial(void** state);
|
||||
@@ -65,8 +67,17 @@ int ai_client_teardown_with_prefs(void** state);
|
||||
|
||||
/* Chat response parser tests (ai_parse_response) */
|
||||
void test_ai_parse_response_openai_content(void** state);
|
||||
void test_ai_parse_response_legacy_text_format_unsupported(void** state);
|
||||
void test_ai_parse_response_responses_output_text(void** state);
|
||||
void test_ai_parse_response_responses_full_envelope(void** state);
|
||||
void test_ai_parse_response_responses_reasoning_summary_skipped(void** state);
|
||||
void test_ai_parse_response_choices_win_over_output(void** state);
|
||||
void test_ai_parse_response_choices_in_string_value(void** state);
|
||||
void test_ai_parse_response_content_before_choices(void** state);
|
||||
void test_ai_parse_response_content_preferred_over_text(void** state);
|
||||
void test_ai_parse_response_responses_brace_in_text(void** state);
|
||||
void test_ai_parse_response_choices_logprobs_before_content(void** state);
|
||||
void test_ai_parse_response_reasoning_not_leaked_without_text(void** state);
|
||||
void test_ai_parse_response_responses_text_value_text(void** state);
|
||||
void test_ai_parse_response_escaped_quote(void** state);
|
||||
void test_ai_parse_response_newline_escape(void** state);
|
||||
void test_ai_parse_response_empty_content(void** state);
|
||||
@@ -129,6 +140,7 @@ void test_ai_parse_models_multiple_models(void** state);
|
||||
/* Prefs round-trip tests */
|
||||
void test_ai_prefs_round_trip_api_key(void** state);
|
||||
void test_ai_prefs_round_trip_remove_key(void** state);
|
||||
void test_ai_prefs_round_trip_api_type(void** state);
|
||||
void test_ai_prefs_multiple_providers_persist(void** state);
|
||||
|
||||
/* Autocomplete deeper coverage */
|
||||
|
||||
Reference in New Issue
Block a user