feat(ai-proxy): translate tool use for the Anthropic provider - #133
Merged
Conversation
This was referenced Jul 20, 2026
The Anthropic translation path dropped tools entirely in both directions,
so Codex/Claude tool calling was non-functional (OpenAI/Ollama passthrough
was unaffected):
- Request: `tools`/`tool_choice` were never forwarded, so the model never
saw the tools and could not call them.
- Response: `tool_use` blocks were ignored and only the first text block
kept, yet `finish_reason` was set to `tool_calls` with no `tool_calls`
array — a malformed tool-calling turn.
Add a shared `protocols::tools` module and wire it into both Chat
Completions and Responses:
- map `tools` (`function.parameters` / flat `parameters` -> `input_schema`)
and `tool_choice` (`required` -> `any`, named-tool forms -> `{type:tool}`,
`parallel_tool_calls:false` -> `disable_parallel_tool_use`);
- translate assistant `tool_calls` and `role:"tool"` history to `tool_use`
/ `tool_result` blocks (consecutive results merged into one user turn);
- translate Anthropic `tool_use` responses back to OpenAI `tool_calls`
(Chat Completions) / `function_call` (Responses), with `content: null`
on a tool-only turn.
Codex freeform `custom` tools (apply_patch), `local_shell`, and hosted
server tools have no Anthropic representation. On the Responses path they
now return 400 `custom_tools_not_supported_for_provider` instead of being
dropped silently (fail visible, mirroring the dropped-reasoning handling).
Full custom/freeform mapping and true SSE tool-call streaming remain
deferred (ADR-0024 / ADR-0030 §2).
Tests: 16 new unit tests (tool schema + tool_choice mapping, history
translation, tool_calls emission, custom-tool rejection). Verified
end-to-end against a WireMock Anthropic /v1/messages upstream: outbound
request carries `tools`/`input_schema`, response comes back as `tool_calls`,
and a Responses custom tool returns 400.
- protocols/tools.rs: shared OpenAI <-> Anthropic tool mapping
- protocols/chat_completion.rs: message + tool translation both ways
- protocols/responses.rs: forward tools, reject custom tools with 400
- docs (dispatchers guide) + CHANGELOG
ndreno
force-pushed
the
feat/ai-proxy-tool-use
branch
from
July 21, 2026 06:50
e11d415 to
594bb02
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
On the Anthropic translation path (Chat Completions and Responses), tool use was dropped in both directions, making Codex/Claude tool calling non-functional. OpenAI/Ollama passthrough was unaffected.
tools/tool_choicewere never forwarded to Anthropic — the model never saw the tools, so it could never call them.tool_useblocks were ignored (only the firsttextblock was kept), yetfinish_reasonwas set to"tool_calls"with notool_callsarray — a malformed turn.apply_patch(freeformcustomtools) were silently discarded.What this does
New shared
protocols::toolsmodule, wired into both Chat Completions and Responses.Request mapping
tools: OpenAIfunction.parameters(Chat Completions) / flatparameters(Responses) → Anthropicinput_schema.tool_choice:"auto"/"none"unchanged,"required"→any,{function:{name}}/{name}→{type:"tool", name};parallel_tool_calls: false→disable_parallel_tool_use: true.tool_calls→tool_useblocks;role:"tool"messages →tool_resultblocks (consecutive results merged into one user turn).Response mapping
tool_use→ OpenAItool_calls(Chat Completions) /function_callitems (Responses);content: nullon a tool-only turn;finish_reason: "tool_calls".Unsupported tools (fail visible)
custom(apply_patch),local_shell, and hosted server tools have no Anthropic representation → Responses path returns400 custom_tools_not_supported_for_providerinstead of dropping them. Route those to OpenAI.Full custom/freeform mapping and true SSE tool-call streaming remain deferred (ADR-0024 / ADR-0030 §2) — this PR closes the non-streaming function-tool gap, which is the bulk of the value.
Testing
tool_choicemapping (both protocols), assistanttool_calls/role:"tool"history translation, consecutive tool-result merging,tool_callsemission with stringified arguments, plain-text passthrough, custom/local_shellrejection. Full suite 134 passing, clippy clean./v1/messagesupstream (patched wasm, compiled.bca, running gateway):get_weatherfunction tool → outbound request to Anthropic carriestoolswith the correctinput_schemaandtool_choice: {type:"auto"}; thetool_useresponse comes back to the client as OpenAItool_calls(arguments as a JSON string),finish_reason: "tool_calls".customapply_patchtool →400 custom_tools_not_supported_for_provider.