Skip to content

feat: add a forcedToolUse capability and refuse .required without it - #34

Closed
sophie-ant wants to merge 1 commit into
mainfrom
sdownward/forced-tool-use
Closed

sophie-ant wants to merge 1 commit into
mainfrom
sdownward/forced-tool-use

Conversation

@sophie-ant

Copy link
Copy Markdown
Contributor

Summary

Some models reject tool_choice of type any or tool. The bridge maps the framework's .required tool-calling mode to tool_choice: any, so on such a model every .required request came back as a 400.

  • ClaudeModel.Capabilities gains forcedToolUse. When it's false, a .required request fails before anything is sent, with the new ClaudeError.forcedToolUseUnsupported. A required call is a contract, so it's refused rather than silently downgraded to optional tool use — the same way a schema on a model without structured output is. Optional and .disallowed tool use are unaffected.
  • Unlike the other capability bits, this one defaults to true: every compiled-in model accepts forced tool use, existing custom ClaudeModel(id:capabilities:) declarations keep working unchanged, and a wrong true still fails loudly (the API names tool_choice) rather than degrading the request.

This is the second half of the change whose first half landed as #29.

Test plan

  • New RequestBuilderTests: .required on a model with forcedToolUse: false throws forcedToolUseUnsupported, while optional and .disallowed requests on the same model still build; the default and the compiled-in constants report true.
  • The full test suite on this commit hasn't run yet.

Some models reject `tool_choice` of type `any` or `tool`. The bridge maps
the framework's `.required` tool-calling mode to `tool_choice: any`, so
on such a model every `.required` request came back as a 400.

`ClaudeModel.Capabilities` gains `forcedToolUse`. When it is `false`,
a `.required` request now fails before anything is sent, with the new
`ClaudeError.forcedToolUseUnsupported` — a required call is a contract,
so it is refused rather than silently downgraded to optional tool use,
the same way a schema on a model without structured output is. Optional
and `.disallowed` tool use are unaffected.

Unlike the other capability bits this one defaults to `true`: every
compiled-in model accepts forced tool use, existing custom
`ClaudeModel(id:capabilities:)` declarations keep working unchanged,
and a wrong `true` still fails loudly (the API names `tool_choice`)
rather than degrading the request.
@sophie-ant

Copy link
Copy Markdown
Contributor Author

Closing for now; not shipping this change at the moment.

@sophie-ant sophie-ant closed this Sep 9, 2026
@sophie-ant
sophie-ant deleted the sdownward/forced-tool-use branch September 9, 2026 17:52
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.

1 participant