fix(client): unwrap a gateway's {data, success} envelope on non-streaming chat parses

Sub2API in front of Cline answers a stream:false Chat Completions request
with HTTP 200 and the completion nested as {"data": {"choices": ...},
"success": true}. parse_chat_message_for_route only read top-level
choices, so every non-streaming call on that route failed with "Chat API
response missing choices": compaction ("Making room failed"), the doctor
live probe, review and other create_message callers. Streaming turns were
unaffected, matching the report.

The parser now reads id, model, choices and usage from data only when
top-level choices is absent, success is the boolean true, and data.choices
is an array. A response with top-level choices takes the old path
unchanged; {success:false, ...} and {data:null, success:false, message}
envelopes still fail with the same error.

Refs #6981 (reported by @luestr, who supplied the headers and sanitized
body). The /new refusal after the failed compaction, also in #6981, is not
changed here.

Verification (source = 018cee3f3e + this diff):
- scripts/dev-test.sh tui client:: (nextest, codewhale-tui --lib):
  625 tests run: 625 passed, 0 failed (148 in client::chat::), 14726 skipped.
- Product A/B against a local mock gateway (127.0.0.1, scratch HOME,
  [providers.sub2api] kind="openai-compatible"), doctor --probe-local:
  envelope        installed 0.10.2 (91dbd4f785) FAIL, debug build PASS
  standard        installed PASS, debug PASS
  envelope success:false     installed FAIL, debug FAIL
  {data:null,success:false}  installed FAIL, debug FAIL
- TUI /compact through tmux against the envelope mock: installed 0.10.2
  shows "Making room failed: Chat API response missing choices" (the
  reporter's text); the debug build shows "Made room: 2 -> 4 messages" and
  the mock's summary text is in the saved session. With the error envelope
  the debug build still fails /compact with the same error.
- rustfmt --edition 2024 --check crates/tui/src/client/chat.rs: clean.
- ./scripts/sync-changelog.sh --check: up to date.
- ./scripts/release/check-versions.sh --require-dated-release: exit 0.
- npm test && npm run check:web: exit 0. root 101/101; runtime-sdk 19/19
  plus tsc types; extension-host 420 tests, 413 pass, 0 fail, 7 skipped;
  web node 7/7; vitest 74 files, 772/772; check:web (facts, docs, tokens,
  eslint, tsc, next build) passed.

Not verified: a live Sub2API/Cline gateway or the reporter's Windows
machine; cargo clippy; the full workspace test suite; hosted CI. No new
tests were added. Local doctor/TUI runs used CODEWHALE_TELEMETRY=0
except the first TUI launch and the doctor runs, which used a scratch
HOME with the default telemetry setting.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NBKbhx19BRbZtvWK8rVKjz
This commit is contained in:
CodeWhale BotandClaude Opus 5.5 committed 2026-10-16 03:05:08 -07:00
1 parent 018cee3f3e
commit a38062bd07
3 files changed
+26

No files matched your search

+7
View File
@@ -18,6 +18,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Cloud Model Studio (Coding Plan)" and "Alibaba Cloud Model Studio (Coding
Plan, Anthropic API)"; the default MiniMax and Model Studio rows keep their
names (CW-7408).
- Compaction no longer fails with "Chat API response missing choices" on
gateways that wrap a non-streaming Chat Completions response as
`{"data": {"choices": ...}, "success": true}`, such as Sub2API in front of
Cline. A response with top-level `choices` parses as before, and an envelope
without `success: true` still fails. Thanks @luestr for the response headers
and sanitized body in #6981; the `/new` refusal reported there is not
changed by this fix.
## [0.10.2] - 2026-10-13
+7
View File
@@ -18,6 +18,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Cloud Model Studio (Coding Plan)" and "Alibaba Cloud Model Studio (Coding
Plan, Anthropic API)"; the default MiniMax and Model Studio rows keep their
names (CW-7408).
- Compaction no longer fails with "Chat API response missing choices" on
gateways that wrap a non-streaming Chat Completions response as
`{"data": {"choices": ...}, "success": true}`, such as Sub2API in front of
Cline. A response with top-level `choices` parses as before, and an envelope
without `success: true` still fails. Thanks @luestr for the response headers
and sanitized body in #6981; the `/new` refusal reported there is not
changed by this fix.
## [0.10.2] - 2026-10-13
+12
View File
@@ -3733,6 +3733,18 @@ fn parse_chat_message_for_route(
provider: ProviderKind,
base_url: &str,
) -> Result<MessageResponse> {
let payload = match (
payload.get("choices"),
payload.get("success"),
payload.get("data"),
) {
(None, Some(Value::Bool(true)), Some(data))
if data.get("choices").is_some_and(Value::is_array) =>
{
data
}
_ => payload,
};
let id = payload
.get("id")
.and_then(Value::as_str)