WIP: feat(ai): support both responses and chat-completions APIsfeat/ai-api-type #163
Draft
jabber.developer2
wants to merge 4 commits from
feat/ai-api-type into master
pull from: feat/ai-api-type
merge into: devs:master
devs:master
devs:fix/editor-terminal-size
devs:ci/docker-hub-publishing
devs:feat/disco-ac
devs:feat/privacy-enhancements
devs:fix/clientid-regression
devs:fix/ai-chat-completions-followup
devs:feat/ai-custom
devs:fix/unencrypted-send
devs:rollback/pre-upstream-merge
devs:fix/autoping-warning-null-domain
devs:fix/pad-dead-space-reclaim
devs:feat/autoping-warning
devs:chore/untrack-gitversion
devs:fix/multiline-pad-clip
devs:fix/issue-112-followups
devs:fix/issue-128-migrate-v3-dedup
devs:fix/delay-timestamp-validation
devs:ref/light-cleanup
devs:fix/history-scroll-pad-redraw-storm
devs:fix/plugin-post-display-incoming-only
devs:merge/upstream-full
devs:merge-improve
devs:chore/remove-chatlog-stage-1
devs:fix/ai-json-encoding
devs:feat/no-db-backlog-114
devs:fix/scroll-non-chat-windows
devs:fix/paged-non-chat-windows
devs:fix/ai-leaks
devs:feat/ai
devs:fix/ai-followups
devs:test/ai-coverage-unit-only
devs:test/ai-coverage
devs:refactor/scroll-mechanism
devs:fix/verify-per-contact-context
devs:feat/no-db-mode
devs:feat/ai-json
devs:feat/pikaur-parity-arch
devs:fix-pikaur-build
devs:feat/upstream-sync
devs:feat/functest-speedup
devs:fix/cwe-134-format-string-audit
devs:ci/separate-build-step
devs:test/autoping-functional-tests
devs:test/db-functional-tests
devs:fix/arch-build
devs:fix/xep-0030-disco-items-error-handling
devs:fix/xep-0030-empty-disco-items
devs:playground/fix/src_refactoring
devs:tests/disco
devs:fix/test-CI-stability
devs:feat/parallel-tests-clean
devs:feat/parallel-functional-tests
devs:fix/functional_tests_v2
devs:fix/functional_tests
devs:fix/connect_max_args
devs:feat/extended_debug_info
devs:playground/fix/scroll-stuck
devs:build/multicore
devs:build/reenable-fedora
devs:build/autoupdate
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
37218d2e07
|
fix(ai): bound response parsing and harden AUTO fallback errors
All checks were successful
CI Code / Check spelling (pull_request) Successful in 16s
CI Code / Check coding style (pull_request) Successful in 26s
CI Code / Code Coverage (pull_request) Successful in 9m12s
CI Code / Linux (ubuntu) (pull_request) Successful in 9m18s
CI Code / Linux (debian) (pull_request) Successful in 9m29s
CI Code / Linux (arch) (pull_request) Successful in 13m13s
- bound _parse_responses scans to the output_text part's object and to
the content array, so a later sibling item's "text" (e.g. a reasoning
summary) can never be returned as the assistant reply
- do not retry the other flavour on a curl timeout: the request likely
reached the server and may still be generating, so a re-POST of the
conversation could trigger a second billed generation
- remember an unparseable 2xx from the first AUTO attempt and surface
stashed first-attempt errors in both error paths, instead of showing
only the final attempt's transport or HTTP error
- recognize Ollama's model-not-found wording in _names_model so a model
typo is not misread as a missing endpoint
- match model errors case-insensitively and recognize the
'model "x" is not supported' wording, so a wrong-model 400 (seen live
on Perplexity /v1/responses) fails fast instead of probing the other
flavour with a doomed request
- say "empty response body" instead of "Unknown error" when an HTTP
error arrives without a body (e.g. Perplexity's bare 404)
- drop the dead, racy provider-lookup fallback in the generic request
thread: a missing provider ref is a caller bug and now fails loudly
- fix ai_providers_lists_defaults to expect the header the command
actually prints ("Configured providers:"); the test was broken since
its introduction but CI never ran it
- add functional test group 5 (AI command surface) to FUNC_TEST_GROUPS
so the CI parallel target runs it; proftest.c port ranges already
account for five groups
|
|||
|
3a96d0dba1
|
fix(ai): close parser and AUTO fallback gaps
Parser:
- Never return reasoning text as the reply: the no-output_text path
anchors strictly on the message "content" array; a reasoning-only body
(truncated by max_output_tokens, carries only "summary") now fails the
parse instead of leaking the chain-of-thought. Regression test added.
- _find_json_field skips field-name occurrences that are string values
(no ':' after), so {"type":"text","text":"hi"} parses instead of
failing on the value of "type".
- _same_json_object tracks brace depth, so a nested object between the
"text" key and the "output_text" tag (e.g. "annotations") no longer
rejects the part's own text.
- Chat-completions extraction is bounded to the choices array (new
_json_array_end helper), so "message"/"content" in a sibling object
(e.g. a top-level "warning") is not returned as the reply.
AUTO fallback:
- Model errors are classified only from a structured JSON error
envelope; a route-missing 404 page merely mentioning models (e.g.
listing /v1/models) no longer suppresses the fallback. Trade-off: a
model error in a non-JSON body costs one extra fallback attempt.
- Fall back on a curl-level failure: an endpoint that accepts the
connection but never responds no longer burns the 60s timeout without
trying the flavour that works.
- Fall back on an unparseable 2xx: a gateway answering unknown paths
with a 200 error page previously reset the hint and re-probed the same
dead flavour on every request. The body is parsed inside the attempt
loop and no longer re-parsed after it.
- The second attempt's 401/403/429/5xx now wins over the stashed 400/422
payload rejection, so an invalid key or rate limit is not hidden
behind a payload-shape error; a second 404 still defers to the first.
- When the first flavour's endpoint is missing but the other answers
(even unsuccessfully, e.g. 401), the resolved hint moves to the
surviving flavour, so the dead endpoint is not probed first forever.
Settings:
- Reserved payload keys are rejected only when setting a value; removal
is allowed again, so a setting persisted before its key became
reserved (e.g. "input") is no longer stuck in the config.
|
|||
|
b3a269342d
|
fix(ai): repair custom setting name autocompletion
All checks were successful
CI Code / Check spelling (pull_request) Successful in 13s
CI Code / Check coding style (pull_request) Successful in 23s
CI Code / Code Coverage (pull_request) Successful in 3m45s
CI Code / Linux (ubuntu) (pull_request) Successful in 5m24s
CI Code / Linux (arch) (pull_request) Successful in 7m7s
CI Code / Linux (debian) (pull_request) Successful in 8m41s
The setting-name branch for /ai set custom never fired: it passed the prefix "/ai set custom " with a trailing space to autocomplete_param_with_ac(), which appends a space itself, so the matched prefix contained a double space no real input ever has. Even without that, the primitive completes the first token after the command while the setting name is the second, and the num_args guard only held before the name was started. The suggestion list (tools/search/memory/ plugins) predated the /ai set custom backend and matched no real payload parameter. Replace the branch with token-position completion (arg 5 via autocomplete_param_no_with_func) fed by a list rebuilt on demand from common payload parameters plus the keys already set on the provider, exposed through the new ai_get_provider_setting_keys() (snapshot taken under settings_lock). Drop the now-unused parse_args() call whose NULL result was fed to g_strv_length() unguarded, spamming a glib CRITICAL on TAB at /ai with zero or five-plus arguments. |
|||
|
14f76ab1db
|
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). |