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

  1. 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`.
  2. Route FIM to a true DeepSeek-compatible FIM endpoint rather than a chat-normalizing proxy.
  3. 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

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


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/8156c77f24aaddf8. Report an issue: GitHub.