Skip to content

fix(opencode): native output_format structured output (works with thinking) - #44634

Open
lu-zero wants to merge 1 commit into
anomalyco:devfrom
lu-zero:luca/structured-output-thinking
Open

lu-zero wants to merge 1 commit into
anomalyco:devfrom
lu-zero:luca/structured-output-thinking

Conversation

@lu-zero

@lu-zero lu-zero commented Aug 24, 2026

Copy link
Copy Markdown

Issue for this PR

Fixes #15226. Reroll of #32637, closed by automated cleanup rather than review, rebased onto dev.

Type of change

  • Bug fix

What does this PR do?

format: json_schema forces tool_choice: "required", which Anthropic rejects when thinking is on (400 "Thinking may not be enabled when tool_choice forces tool use."). Still live on dev at packages/opencode/src/session/prompt.ts:1285:

toolChoice: format.type === "json_schema" ? "required" : undefined,

For Anthropic this routes through the provider's native output_format instead of the forced tool. The model returns the JSON as text, parsed into message.structured. Not a forced tool, so it coexists with thinking. Other providers keep the existing path, so their behaviour is unchanged.

File Change
session/llm.ts Add structuredOutput to StreamInput; on native-capable providers pass output: Output.object(jsonSchema(...)) and merge { anthropic: { structuredOutputMode: "outputFormat" } } into provider options; downgrade a forced tool_choice to auto on the native path so the two cannot collide. Add supportsNativeStructuredOutput.
session/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 assistant text into message.structured, degrading to the existing StructuredOutputError on parse failure.
test/session/llm.test.ts Assert a thinking-enabled Anthropic model requests output_config.format and does not force tool_choice.

#29565 and #28621 addressed this by relaxing the forced tool_choice; both closed unmerged.

How did you verify your code works?

Check Result
bun turbo typecheck 30/30 tasks pass
bun test test/session/llm.test.ts 30 pass, 0 fail
opencode serve vs live Anthropic API, thinking + format: json_schema No 400; reasoning emitted; message.structured populated

Screenshots / recordings

N/A — provider request-shape change.

Checklist

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

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@lu-zero
lu-zero force-pushed the luca/structured-output-thinking branch from cdc72c9 to 0282e79 Compare September 11, 2026 08:27
…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) <noreply@anthropic.com>
@lu-zero
lu-zero force-pushed the luca/structured-output-thinking branch from 0282e79 to f8eb445 Compare September 16, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

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

1 participant