Skip to content

[bug] Anthropic thinking block is forwarded to openai_responses clients as reasoning.type (OpenAI 400) #1012

Open

Description

@shakhal

Summary

When an Anthropic Messages request that carries a thinking block is routed to an openai_responses client, the translator copies the Anthropic thinking object verbatim into the Responses reasoning field. OpenAI rejects the request:

400 {"error":{"message":"Unknown parameter: 'reasoning.type'.","type":"invalid_request_error","param":"reasoning.type","code":"unknown_parameter"}}

This breaks every Claude Agent SDK / Claude Code session pointed at an OpenAI reasoning model through Switchyard 0.3.0, because the SDK sends thinking ({"type":"adaptive"} on current models, {"type":"enabled","budget_tokens":N} on older ones) on every turn.

Where

  • switchyard-translation/src/codecs/anthropic/buffered.rs:80 — ReasoningParams.raw is set to body.get("thinking").cloned() (the Anthropic-shaped object).
  • switchyard-translation/src/codecs/responses/buffered.rs:260-270 — the encoder starts from request.reasoning.raw, inserts effort, and emits the result as reasoning. The Anthropic keys (type, budget_tokens) survive.
  • The openai_chat encoder only emits reasoning_effort from request.reasoning.effort, so chat clients are unaffected.

Reproduce (0.3.0, switchyard-server, route → openai_responses client, model gpt-5.5)

curl -s http://127.0.0.1:4000/v1/messages -H 'content-type: application/json' -H 'anthropic-version: 2023-06-01' -d '{
  "model": "<route>", "max_tokens": 4000,
  "thinking": {"type": "adaptive"},
  "messages": [{"role": "user", "content": "Call the noop tool with kind=a, then say ok."}],
  "tools": [{"name": "noop", "description": "Probe tool.", "input_schema": {"type": "object", "properties": {"kind": {"type": "string"}}}}]
}'

→ 400 Unknown parameter: 'reasoning.type'. The same request with "output_config": {"effort": "medium"} instead of thinking succeeds (the effort is mapped to reasoning.effort correctly). thinking: {"type":"enabled","budget_tokens":2000} fails the same way.

Expected

raw from a different wire format should not be replayed into the Responses reasoning object. Either carry raw only when the source format was already openai_responses, or build the Responses reasoning from the normalized fields (effort; optionally summary) and drop unknown keys. Mapping Anthropic budget_tokens to a Responses effort tier would be a bonus but is not needed to unbreak clients.

Workaround we use

Set MAX_THINKING_TOKENS=0 on the Claude Agent SDK subprocess for routes that terminate on openai_responses, so the SDK omits the thinking block; the model still reasons at the effort passed through output_config.effort.

Version: switchyard-server 0.3.0 (cargo install --locked switchyard-server --version 0.3.0), static musl build.

Activity

  1. joshuacox commented on Oct 16, 2026

    @joshuacox

    This looks fixed on current main, so it may be closable.

    The Responses encoder no longer replays an Anthropic thinking object into reasoning. In crates/switchyard-translation/src/codecs/responses/buffered.rs, the Anthropic branch now builds reasoning from the normalized fields only: effort from output_config, and effort: "none" when thinking is disabled. The Anthropic keys (type, budget_tokens) are dropped rather than forwarded, so there is no reasoning.type for OpenAI to reject.

    That landed in #840 (merged 2026-09-23), which also added the test anthropic_thinking_to_responses_uses_normalized_effort covering adaptive, enabled with a budget_tokens, and disabled thinking. I re-ran that test against current main (4cbb2d1) and it passes.

    This report was against the 0.3.0 release build, which predates the fix. One residual from the same area is tracked separately: open PR #856 reports the dropped budget_tokens as a lossy conversion.

    Happy to be corrected if a route still reproduces the 400 on current main.

  2. shakhal commented on Oct 16, 2026

    @shakhal
    Author

    Thanks — confirmed against main: the Anthropic branch in codecs/responses/buffered.rs now keeps only the disabled → effort: "none" mapping, so the reasoning.type leak is gone. Our report was indeed against the v0.3.0 release build (cargo install --locked switchyard-server --version 0.3.0), and #840 landed the day after that tag.

    Since v0.3.0 is still the latest release on crates.io, every Claude Agent SDK / Claude Code user who installs the released server hits this on their first turn against an OpenAI reasoning model. Is a 0.3.1 patch release (or a 0.4.0) planned? Until one exists we're keeping a client-side workaround (MAX_THINKING_TOKENS=0 on the SDK subprocess for Responses routes). Fine to close from our side once the release question is answered.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions