Hmbown/CodeWhale · error
translate: unexpected API response shape
Error message
translate: unexpected API response shape
What it means
The ChatCompletions translation path (`translate()`) sends a minimal system+user chat request and then reads the translated text from `choices[0].message.content`, requiring it to be a JSON string. This error means the endpoint returned HTTP 200 but the body does not contain that shape — the response was still consumed by `send_json_with_retry`, so this is purely a response-shape mismatch, not a transport or status failure.
Source
Thrown at crates/tui/src/client.rs:2085
}
],
"max_tokens": max_tokens,
"stream": false
});
chat::apply_route_reasoning_controls(
&mut body,
self.api_provider,
&self.base_url,
&model,
Some("off"),
);
let response = self.send_json_with_retry(&url, &body).await?;
let value: serde_json::Value = response.json().await?;
let translated = value["choices"][0]["message"]["content"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("translate: unexpected API response shape"))?
.trim()
.to_string();
Ok(translated)
}
/// List available models from the provider.
pub async fn list_models(&self) -> Result<Vec<AvailableModel>> {
let url = api_url(&self.base_url, "models");
let response = self.send_with_retry(|| self.http_client.get(&url)).await?;
let status = response.status();
if !status.is_success() {
let raw_error_text = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;
let error_text = sanitize_http_error_body(
Some(self.api_provider.display_name()),
status.as_u16(),
&raw_error_text,View on GitHub (pinned to 8880682c63)
Solutions
- Reproduce the exact request with curl against the same base_url/model and inspect the body: `curl -s $BASE_URL/chat/completions -H "Authorization: Bearer $KEY" -d '{"model":"...","messages":[{"role":"user","content":"hi"}]}'`.
- Fix the base_url so it addresses an OpenAI-compatible chat-completions endpoint (correct `/v1` prefix, no trailing path duplicates).
- Confirm the model id actually exists on that provider (wrong ids sometimes yield 200 with an error object on gateways).
- If the provider legitimately returns `content` as an array of parts, switch to a provider/model that returns plain string content for translation.
Example fix
# before base_url = "https://proxy.internal" # returns envelope-wrapped 200 # after base_url = "https://proxy.internal/v1" # real chat-completions surface
Defensive patterns
Strategy: try-catch
Type guard
fn has_chat_content_shape(v: &serde_json::Value) -> bool {
v.pointer("/choices/0/message/content").is_some_and(serde_json::Value::is_string)
} Try / catch
let value: serde_json::Value = response.json().await?;
let translated = value
.pointer("/choices/0/message/content")
.and_then(serde_json::Value::as_str)
.map(str::trim)
.filter(|s| !s.is_empty())
.ok_or_else(|| {
anyhow::anyhow!("translate: unexpected API response shape: {}",
truncate(&value.to_string(), 512)) // keep raw shape for diagnosis
})?; Prevention
- Smoke-test a provider's chat surface with a one-message curl before enabling translate on it.
- Log (at debug level) the top-level JSON keys of unexpected responses to identify envelope-wrapping proxies quickly.
- Keep translate models on providers known to return string `message.content`.
When it happens
Trigger: Pointing the client at a provider that is not OpenAI-chat-completions-shaped (Anthropic-style `content` arrays, Google-style payloads), a gateway returning a 200 JSON error envelope, a proxy returning HTML that happens to parse as JSON, an empty `choices` array (some providers when the model filters the output), or `content` being `null`/an array instead of a string.
Common situations: Custom provider base_url pointing at the wrong endpoint version (`/v1` missing or doubled); using a model alias that routes to a non-chat completion surface; middleboxes (Cloudflare, corporate proxies) rewriting responses; providers that wrap chat responses in an envelope like `{"data": ...}`.
Related errors
- FIM response missing choices[0].text
- Responses API error [{code}]: {msg}
- DeepSeek ${res.status}: ${text}
- DeepSeek ${res.status}: ${text}
- Runtime API request failed (${status}): ${message}
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/e912ee0d07a6c9ce.
Report an issue: GitHub.