Hmbown/CodeWhale · error
FIM response missing choices[0].text
Error message
FIM response missing choices[0].text
What it means
After a 2xx from `{base_url}/beta/completions`, `fim_completion` extracts the completion with the JSON pointer `/choices/0/text` and requires a string. This error means the endpoint returned success but a body without that field — a chat-completions-shaped reply (`choices[0].message.content`), an envelope-wrapped response, or an empty `choices` array. It is the shape-mismatch counterpart of the HTTP-status error above.
Source
Thrown at crates/tui/src/client.rs:3642
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,
);
anyhow::bail!("FIM API error: HTTP {status}: {error_text}");
}
let response_text = response
.text()
.await
.context("Failed to read FIM API response body")?;
let value: serde_json::Value =
serde_json::from_str(&response_text).context("Failed to parse FIM API response")?;
let text = value
.pointer("/choices/0/text")
.and_then(serde_json::Value::as_str)
.ok_or_else(|| anyhow::anyhow!("FIM response missing choices[0].text"))?;
Ok(text.to_string())
}
}
mod anthropic;
mod chat;
pub(crate) mod cloud_code;
mod deepseek_effort;
#[cfg(test)]
mod ds4_tests;
mod prepared;
mod provider_native_search;
mod responses;
mod stream_entry;
fn extract_sse_data_value(line: &str) -> Option<&str> {
line.strip_prefix("data:")
.map(|value| value.strip_prefix(' ').unwrap_or(value))View on GitHub (pinned to 8880682c63)
Solutions
- Inspect the raw body with curl (same request as the FIM call) and compare: FIM needs `choices[0].text`, chat gives `choices[0].message.content`.
- Route FIM to a true DeepSeek-compatible FIM endpoint rather than a chat-normalizing proxy.
- If you control the gateway, map `/beta/completions` responses to include `choices[].text`.
Defensive patterns
Strategy: try-catch
Type guard
fn has_fim_text(v: &serde_json::Value) -> bool {
v.pointer("/choices/0/text").is_some_and(serde_json::Value::is_string)
} Try / catch
let text = value.pointer("/choices/0/text").and_then(Value::as_str)
.ok_or_else(|| anyhow::anyhow!(
"FIM response missing choices[0].text (got keys: {:?}) — endpoint is not FIM-shaped",
value.as_object().map(|o| o.keys().collect::<Vec<_>>())))?; Prevention
- Prefer a gateway route that forwards DeepSeek FIM responses verbatim rather than normalizing to chat shape.
- Probe the FIM endpoint once at startup and validate the response shape, not just the status code.
- When shapes mismatch persistently, disable FIM rather than erroring per keystroke.
When it happens
Trigger: A gateway that answers `/beta/completions` with 200 but routes the request to chat-completions internally (returning `message.content` instead of `text`); providers whose FIM reply nests the completion elsewhere; 200 responses containing a JSON error object.
Common situations: LiteLLM/proxy layers normalizing everything to chat shape; custom `base_url`s where a catch-all handler returns 200 JSON for unknown paths; server versions that renamed the field.
Related errors
- FIM API error: HTTP {status}: {error_text}
- DeepSeek ${res.status}: ${text}
- DeepSeek ${res.status}: ${text}
- translate: unexpected API response shape
- Failed to call DeepSeek Chat API: HTTP {status}: {error_text
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/8156c77f24aaddf8.
Report an issue: GitHub.