First of three PRs for step 7. This is the route delegations will use.
Why a second renderer rather than reusing the normalized one. The vendors disagree structurally, which is exactly A13's argument for normalising in the renderer instead of on the wire:
OpenAI-compatible
Anthropic Messages
system prompt
a message with role system
a top-level system field
tool call
tool_calls array on the message
a tool_use content block
tool result
a message with role tool
a tool_result block in a user message
tool schema
function.parameters
input_schema
A middlebox flattening those would be lossy precisely where a long-running delegation lives.
render_anthropic.py builds vendor-native /v1/messages bodies from the canonical log, coalescing adjacent same-role blocks so a tool_result lands directly after its tool_use — which the API requires.
Tool manifests stay declared once in the OpenAI shape and are converted per vendor, so adding a tool does not mean editing two places and letting them drift.
AnthropicPassthroughClient implements the same LLMClient protocol, so a caller picks a route, not a different kind of client — which is what lets the delegation harness use either without knowing.
Dangling tool calls get a placeholder result here too; this API rejects them more strictly than the OpenAI shape does.
Verified live, not assumed. Three integration tests: a plain turn, a full tool-call round trip where the tool_result is fed back and accepted (this is what actually proves the placement is right), and a check that cache_read_input_tokens is present in the passthrough's usage — A5 explicitly asks that prompt caching be verified to survive the passthrough, and a proxy that flattened the request would turn a cost optimisation into a silent per-call expense.
One deliberate gap, flagged rather than hidden: vendor-opaque reasoning blocks (thinking, redacted_thinking) are dropped. A10 wants them stored, tagged with the producing model, and never replayed outside the session that produced them, and the canonical schema has no event for that yet. Nothing requests extended thinking, so none arrive today — but it needs a canonical representation before it does, and 7b is the natural place since replay-within-session is a delegation concern.
11 new unit tests, 3 live. 198 unit + 13 integration green locally.
First of three PRs for step 7. This is the route delegations will use.
**Why a second renderer rather than reusing the normalized one.** The vendors disagree *structurally*, which is exactly A13's argument for normalising in the renderer instead of on the wire:
| | OpenAI-compatible | Anthropic Messages |
|---|---|---|
| system prompt | a message with role `system` | a top-level `system` field |
| tool call | `tool_calls` array on the message | a `tool_use` content block |
| tool result | a message with role `tool` | a `tool_result` block in a **user** message |
| tool schema | `function.parameters` | `input_schema` |
A middlebox flattening those would be lossy precisely where a long-running delegation lives.
- **`render_anthropic.py`** builds vendor-native `/v1/messages` bodies from the canonical log, coalescing adjacent same-role blocks so a `tool_result` lands directly after its `tool_use` — which the API requires.
- **Tool manifests stay declared once** in the OpenAI shape and are converted per vendor, so adding a tool does not mean editing two places and letting them drift.
- **`AnthropicPassthroughClient` implements the same `LLMClient` protocol**, so a caller picks a *route*, not a different kind of client — which is what lets the delegation harness use either without knowing.
- Dangling tool calls get a placeholder result here too; this API rejects them more strictly than the OpenAI shape does.
**Verified live, not assumed.** Three integration tests: a plain turn, a full tool-call round trip where the `tool_result` is fed back and accepted (this is what actually proves the placement is right), and a check that `cache_read_input_tokens` is present in the passthrough's usage — A5 explicitly asks that prompt caching be verified to survive the passthrough, and a proxy that flattened the request would turn a cost optimisation into a silent per-call expense.
**One deliberate gap, flagged rather than hidden:** vendor-opaque reasoning blocks (`thinking`, `redacted_thinking`) are dropped. A10 wants them stored, tagged with the producing model, and never replayed outside the session that produced them, and the canonical schema has no event for that yet. Nothing requests extended thinking, so none arrive today — but it needs a canonical representation before it does, and 7b is the natural place since replay-within-session is a delegation concern.
11 new unit tests, 3 live. 198 unit + 13 integration green locally.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The second renderer adapter, and the concrete case for A13's decision
to normalise in the renderer rather than on the wire. The two vendors
disagree structurally, not cosmetically: the system prompt is a
top-level field rather than a message, a tool call is a tool_use
content block rather than an array, a tool result is a block inside a
user message rather than a "tool" role, and tool schemas use
input_schema rather than parameters. A middlebox flattening those
would be lossy exactly where a long-running delegation lives.
- render_anthropic.py builds vendor-native /v1/messages bodies from
the canonical log, coalescing adjacent same-role blocks so a
tool_result lands directly after its tool_use
- Tool manifests are declared once in OpenAI shape and converted per
vendor, so adding a tool does not mean editing two places
- AnthropicPassthroughClient implements the same LLMClient protocol,
so callers pick a route rather than a different kind of client, and
maps provider tool ids to canonical ones at the boundary
- Dangling tool calls get a placeholder result here too; this API
rejects them more strictly than the OpenAI shape
- Vendor-opaque reasoning blocks are dropped for now with the reason
recorded: A10 wants them stored and never replayed across sessions,
and the canonical schema has no event for that yet
Live tests prove the body is accepted as sent, that a tool_result
turn round-trips, and — as A5 asks — that prompt cache accounting
survives the passthrough rather than being flattened by the proxy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
First of three PRs for step 7. This is the route delegations will use.
Why a second renderer rather than reusing the normalized one. The vendors disagree structurally, which is exactly A13's argument for normalising in the renderer instead of on the wire:
systemsystemfieldtool_callsarray on the messagetool_usecontent blocktooltool_resultblock in a user messagefunction.parametersinput_schemaA middlebox flattening those would be lossy precisely where a long-running delegation lives.
render_anthropic.pybuilds vendor-native/v1/messagesbodies from the canonical log, coalescing adjacent same-role blocks so atool_resultlands directly after itstool_use— which the API requires.AnthropicPassthroughClientimplements the sameLLMClientprotocol, so a caller picks a route, not a different kind of client — which is what lets the delegation harness use either without knowing.Verified live, not assumed. Three integration tests: a plain turn, a full tool-call round trip where the
tool_resultis fed back and accepted (this is what actually proves the placement is right), and a check thatcache_read_input_tokensis present in the passthrough's usage — A5 explicitly asks that prompt caching be verified to survive the passthrough, and a proxy that flattened the request would turn a cost optimisation into a silent per-call expense.One deliberate gap, flagged rather than hidden: vendor-opaque reasoning blocks (
thinking,redacted_thinking) are dropped. A10 wants them stored, tagged with the producing model, and never replayed outside the session that produced them, and the canonical schema has no event for that yet. Nothing requests extended thinking, so none arrive today — but it needs a canonical representation before it does, and 7b is the natural place since replay-within-session is a delegation concern.11 new unit tests, 3 live. 198 unit + 13 integration green locally.
🤖 Generated with Claude Code