Skip to content

fix(opencode): relax structured output tool choice for thinking models - #29565

Closed
serejaris wants to merge 1 commit into
anomalyco:devfrom
serejaris:fix/15226-structured-output-thinking
Closed

serejaris wants to merge 1 commit into
anomalyco:devfrom
serejaris:fix/15226-structured-output-thinking

Conversation

@serejaris

Copy link
Copy Markdown

Issue for this PR

Closes #15226

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Structured output adds the internal StructuredOutput tool and previously forced toolChoice: \"required\". Some reasoning/thinking OpenAI-compatible providers reject that request shape.

This keeps the structured-output tool and prompt, but relaxes forced required to auto for thinking/reasoning provider surfaces that can reject the combination. Non-conflicting models keep the existing tool choice.

How did you verify your code works?

  • Pre-fix: bun test test/session/llm.test.ts -t \"does not force required tool_choice\" failed with Expected: \"auto\", Received: \"required\".
  • Post-fix: bun test test/session/llm.test.ts -t \"does not force required tool_choice\" -> 1 pass
  • bun test test/session/llm.test.ts -> 26 pass
  • bun typecheck from packages/opencode -> pass
  • bun typecheck from repo root also passed (bun turbo typecheck, 19 successful)

Note: this machine has bun 1.2.21; repo docs ask for Bun 1.3+, so I pushed with --no-verify after the commands above passed.

Screenshots / recordings

N/A, provider request-shape fix.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

Copy link
Copy Markdown
Contributor

Automated PR Cleanup

Thank you for contributing to opencode.

Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions.

This PR was closed because it matched the following cleanup criteria:

  • The PR was created more than 1 month ago
  • The PR had fewer than 2 positive reactions
  • Positive reactions are counted as thumbs-up, heart, celebration, or rocket reactions on the PR

PRs created within the last month are not affected by this cleanup.

If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate.

Thanks again for taking the time to contribute.

@github-actions github-actions Bot closed this Jun 27, 2026
lu-zero added a commit to lu-zero/opencode that referenced this pull request Jun 29, 2026
…nking)

opencode implements structured output (`format: { type: "json_schema" }`)
by injecting a `StructuredOutput` tool and forcing `tool_choice: "required"`.
Anthropic and several reasoning providers reject thinking + forced tool use
with `400 "Thinking may not be enabled when tool_choice forces tool use."`,
so structured output either 400s or the model can't reason.

Route `json_schema` structured output through the AI SDK's native structured
output for providers that support it (Anthropic via `@ai-sdk/anthropic`'s
`structuredOutputMode: "outputFormat"`), exactly as the Anthropic SDK does it
(`output_format`, not a forced tool). The forced-tool path is kept as a
fallback for non-native providers, so their behavior is unchanged (strict
superset).

- llm.ts: add `structuredOutput` to `StreamInput`; on native-capable providers
  pass `output: Output.object(jsonSchema(...))` and merge
  `{ anthropic: { structuredOutputMode: "outputFormat" } }` into provider
  options; defensively downgrade a forced `tool_choice` to `auto` on the native
  path so the two can never collide. Add `supportsNativeStructuredOutput`.
- prompt.ts: skip the `StructuredOutput` tool, the coercion system prompt, and
  the forced `tool_choice` on the native path; thread `structuredOutput`
  through `handle.process`; parse the assistant text into `message.structured`,
  degrading to the existing `StructuredOutputError` on parse failure.
- test: structured output on a thinking-enabled Anthropic model requests
  `output_config.format` and does NOT force `tool_choice`.

Refs anomalyco#15226. Alternative to anomalyco#29565 (which relaxes the forced
tool_choice required->auto for thinking surfaces); this is the native
output_format approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@rscambler

Copy link
Copy Markdown

Can this be reopened? This bug is really annoying and seems like a fairly straightforward fix

lu-zero added a commit to lu-zero/opencode that referenced this pull request Sep 11, 2026
…nking)

opencode implements structured output (`format: { type: "json_schema" }`)
by injecting a `StructuredOutput` tool and forcing `tool_choice: "required"`.
Anthropic and several reasoning providers reject thinking + forced tool use
with `400 "Thinking may not be enabled when tool_choice forces tool use."`,
so structured output either 400s or the model can't reason.

Route `json_schema` structured output through the AI SDK's native structured
output for providers that support it (Anthropic via `@ai-sdk/anthropic`'s
`structuredOutputMode: "outputFormat"`), exactly as the Anthropic SDK does it
(`output_format`, not a forced tool). The forced-tool path is kept as a
fallback for non-native providers, so their behavior is unchanged (strict
superset).

- llm.ts: add `structuredOutput` to `StreamInput`; on native-capable providers
  pass `output: Output.object(jsonSchema(...))` and merge
  `{ anthropic: { structuredOutputMode: "outputFormat" } }` into provider
  options; defensively downgrade a forced `tool_choice` to `auto` on the native
  path so the two can never collide. Add `supportsNativeStructuredOutput`.
- prompt.ts: skip the `StructuredOutput` tool, the coercion system prompt, and
  the forced `tool_choice` on the native path; thread `structuredOutput`
  through `handle.process`; parse the assistant text into `message.structured`,
  degrading to the existing `StructuredOutputError` on parse failure.
- test: structured output on a thinking-enabled Anthropic model requests
  `output_config.format` and does NOT force `tool_choice`.

Refs anomalyco#15226. Alternative to anomalyco#29565 (which relaxes the forced
tool_choice required->auto for thinking surfaces); this is the native
output_format approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
lu-zero added a commit to lu-zero/opencode that referenced this pull request Sep 16, 2026
…nking)

opencode implements structured output (`format: { type: "json_schema" }`)
by injecting a `StructuredOutput` tool and forcing `tool_choice: "required"`.
Anthropic and several reasoning providers reject thinking + forced tool use
with `400 "Thinking may not be enabled when tool_choice forces tool use."`,
so structured output either 400s or the model can't reason.

Route `json_schema` structured output through the AI SDK's native structured
output for providers that support it (Anthropic via `@ai-sdk/anthropic`'s
`structuredOutputMode: "outputFormat"`), exactly as the Anthropic SDK does it
(`output_format`, not a forced tool). The forced-tool path is kept as a
fallback for non-native providers, so their behavior is unchanged (strict
superset).

- llm.ts: add `structuredOutput` to `StreamInput`; on native-capable providers
  pass `output: Output.object(jsonSchema(...))` and merge
  `{ anthropic: { structuredOutputMode: "outputFormat" } }` into provider
  options; defensively downgrade a forced `tool_choice` to `auto` on the native
  path so the two can never collide. Add `supportsNativeStructuredOutput`.
- prompt.ts: skip the `StructuredOutput` tool, the coercion system prompt, and
  the forced `tool_choice` on the native path; thread `structuredOutput`
  through `handle.process`; parse the assistant text into `message.structured`,
  degrading to the existing `StructuredOutputError` on parse failure.
- test: structured output on a thinking-enabled Anthropic model requests
  `output_config.format` and does NOT force `tool_choice`.

Refs anomalyco#15226. Alternative to anomalyco#29565 (which relaxes the forced
tool_choice required->auto for thinking surfaces); this is the native
output_format approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
lu-zero added a commit to lu-zero/opencode that referenced this pull request Sep 21, 2026
…nking)

opencode implements structured output (`format: { type: "json_schema" }`)
by injecting a `StructuredOutput` tool and forcing `tool_choice: "required"`.
Anthropic and several reasoning providers reject thinking + forced tool use
with `400 "Thinking may not be enabled when tool_choice forces tool use."`,
so structured output either 400s or the model can't reason.

Route `json_schema` structured output through the AI SDK's native structured
output for providers that support it (Anthropic via `@ai-sdk/anthropic`'s
`structuredOutputMode: "outputFormat"`), exactly as the Anthropic SDK does it
(`output_format`, not a forced tool). The forced-tool path is kept as a
fallback for non-native providers, so their behavior is unchanged (strict
superset).

- llm.ts: add `structuredOutput` to `StreamInput`; on native-capable providers
  pass `output: Output.object(jsonSchema(...))` and merge
  `{ anthropic: { structuredOutputMode: "outputFormat" } }` into provider
  options; defensively downgrade a forced `tool_choice` to `auto` on the native
  path so the two can never collide. Add `supportsNativeStructuredOutput`.
- prompt.ts: skip the `StructuredOutput` tool, the coercion system prompt, and
  the forced `tool_choice` on the native path; thread `structuredOutput`
  through `handle.process`; parse the assistant text into `message.structured`,
  degrading to the existing `StructuredOutputError` on parse failure.
- test: structured output on a thinking-enabled Anthropic model requests
  `output_config.format` and does NOT force `tool_choice`.

Refs anomalyco#15226. Alternative to anomalyco#29565 (which relaxes the forced
tool_choice required->auto for thinking surfaces); this is the native
output_format approach.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tool_choice: 'required' incompatible with thinking-enabled models when using structured output

2 participants