Skip to content

Cerebras (OpenAI-compatible): every multi-turn request fails with '400 status code (no body)' — reasoning_content rejected on input #11045

Description

@mdm-sfo

Qwen Code + Cerebras: every multi-turn request fails with 400 status code (no body)

Summary

When using a Cerebras-hosted model (any model) via the OpenAI-compatible provider, the first turn succeeds but every subsequent turn in the session fails with 400 status code (no body). The CLI's own background subagents (e.g. managed-auto-memory-extractor) fail the same way, since they replay session history.

Environment

  • qwen-code 0.23.0 (@qwen-code/qwen-code npm package)
  • Provider config: OpenAI-compatible, baseUrl: https://api.cerebras.ai/v1, model qwen-3.8-27b (also reproduced with gpt-oss-120b)
  • Linux x64

Root cause

The Gemini→OpenAI history converter writes the model's thinking back into request history as an assistant.reasoning_content field:

// packages/core/src/core/openaiContentGenerator/provider/... (converter)
const reasoningContent = reasoningParts.join("");
if (reasoningContent) {
  assistantMessage.reasoning_content = reasoningContent;
}

Cerebras returns thinking in a field named reasoning (and accepts reasoning back on input), but rejects reasoning_content on input with HTTP 400 — for every model, not just thinking ones:

{"message": "messages.2.assistant.reasoning_content: property 'messages.2.assistant.reasoning_content' is unsupported",
 "type": "invalid_request_error", "param": "validation_error", "code": "wrong_api_format"}

The CLI surfaces this as 400 status code (no body), which hides the actual validation message.

Minimal reproduction

# Turn 1 — succeeds
curl https://api.cerebras.ai/v1/chat/completions \
  -H "Authorization: Bearer $CEREBRAS_KEY" -H "Content-Type: application/json" \
  -d '{"model":"qwen-3.8-27b","messages":[{"role":"user","content":"test"}]}'

# Turn 2 — 400, exactly what qwen-code sends on the follow-up
curl https://api.cerebras.ai/v1/chat/completions \
  -H "Authorization: Bearer $CEREBRAS_KEY" -H "Content-Type: application/json" \
  -d '{"model":"qwen-3.8-27b","messages":[
        {"role":"user","content":"test"},
        {"role":"assistant","content":"Hey! How can I help?","reasoning_content":"The user said test."},
        {"role":"user","content":"follow-up question"}
      ]}'
# → 400 wrong_api_format: messages.2.assistant.reasoning_content unsupported

Note: substituting the field name reasoning (Cerebras's own response field) is accepted on input; only reasoning_content is rejected.

Proposed fix

The codebase already solves this exact problem for Mistral: MistralOpenAICompatibleProvider.buildRequest() maps history through stripReasoningContent(), which deletes reasoning_content from outgoing messages. Cerebras needs the identical treatment — Cerebras endpoints currently fall through to DefaultOpenAICompatibleProvider, which sends the field verbatim.

Add a Cerebras provider (or extend the strip heuristic), following the existing Mistral pattern:

// provider/cerebras.ts
const CEREBRAS_API_HOST = "api.cerebras.ai";

function isCerebrasProvider(config): boolean {
  try {
    const hostname = new URL(config?.baseUrl ?? "").hostname.toLowerCase();
    return hostname === CEREBRAS_API_HOST || hostname.endsWith(`.${CEREBRAS_API_HOST}`);
  } catch { return false; }
}

class CerebrasOpenAICompatibleProvider extends DefaultOpenAICompatibleProvider {
  buildRequest(request, userPromptId) {
    const baseRequest = super.buildRequest(request, userPromptId);
    return { ...baseRequest, messages: baseRequest.messages.map(stripReasoningContent) };
  }
}

// in determineProvider():
if (isCerebrasProvider(config)) {
  return new CerebrasOpenAICompatibleProvider(contentGeneratorConfig, cliConfig);
}

(stripReasoningContent would need to be exported from the Mistral module or moved to a shared util.)

Trade-off to note: stripping thinking from history means the model no longer sees its own prior reasoning across turns. That is already the accepted behavior for Mistral, and is strictly better than every multi-turn request failing. Cerebras accepts reasoning (no _content) on input, so renaming rather than deleting would also work — but deletion matches the existing pattern and is provider-safe.

Verified workaround

Locally patching the installed chunk (~/.local/lib/qwen-code/lib/chunks/chunk-F33GFWPR.js) with the detection + strip above fixes multi-turn sessions against Cerebras — confirmed by resuming a previously failing session and completing follow-up turns with no 400s.

Secondary issue (worth a separate ticket?)

EnhancedErrorHandler reports Cerebras 400s as 400 status code (no body) even though the response does have a JSON body with a precise validation message. Surfacing error.message from the body would have made this diagnosable in seconds instead of requiring session-telemetry archaeology.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions