Skip to content

Add anthropic-messages protocol for configured providers - #1176

Open
espetro wants to merge 3 commits into
vercel-labs:mainfrom
espetro:anthropic-messages-protocol
Open

espetro wants to merge 3 commits into
vercel-labs:mainfrom
espetro:anthropic-messages-protocol

Conversation

@espetro

@espetro espetro commented Oct 6, 2026

Copy link
Copy Markdown

Summary

Adds an anthropic-messages Protocol variant to the configured-provider architecture so ~/.fx/settings.json connections can talk directly to the Anthropic Messages API — the direct-vendor-key gap tracked in #150.

{ "providers": {
  "claude": {
    "protocol": "anthropic-messages",
    "base_url": "https://api.anthropic.com",
    "auth": { "type": "x-api-key", "env": "ANTHROPIC_API_KEY" },
    "model_metadata": { "claude-sonnet-4-5": { "context_window": 200000, "max_output_tokens": 8192 } }
  }
} }

configured_provider.Protocol gains @"anthropic-messages" and Auth gains x_api_key; builtins/providers.zig dispatches to a new anthropic_messages transport that mirrors chat_completions (SSE stream, prepared-body reuse, deadline enforcement), sending x-api-key + anthropic-version: 2023-06-01 instead of bearer auth.

The codec (anthropic_messages_protocol.zig) handles the wire differences: hoists instructions into the system string, emits content-part arrays, projects dotted tool names onto Anthropic's [A-Za-z0-9_-] namespace via wire_name (functions.read → functions_read), reduces message_start/content_block_*/message_delta SSE events into stream_provider.Result, and binds thinking blocks to tool calls in provider_state_json so signed thinking replays verbatim (Anthropic rejects mismatched signatures when tool use continues). budget_tokens is clamped below max_tokens, thinking is skipped on tool_choice:any calls (rejected together), parallel_tool_calls:false serializes as disable_parallel_tool_use, and model_context_window_exceeded maps to .length.

E2E coverage in configured-providers.test.ts drives the built binary against a local SSE fixture: request shape + x-api-key headers on /v1/messages, and a tool_use→tool_result round trip asserting signed thinking replay on the continuation.

Adds a second configured-provider protocol so fx can talk directly to
the Anthropic Messages API (or any compatible endpoint) from
~/.fx/settings.json:

    "providers": {
      "claude": {
        "protocol": "anthropic-messages",
        "base_url": "https://api.anthropic.com/v1",
        "auth": { "type": "x-api-key", "env": "ANTHROPIC_API_KEY" },
        "model_metadata": { "claude-sonnet-4-5": { ... } }
      }
    }

- configured_provider: protocol gains the anthropic-messages variant;
  auth gains x-api-key; endpoint_url routes /messages vs /chat/completions
- anthropic_messages_protocol: request serialization (system hoist,
  tool_use/tool_result blocks, inline images, thinking budgets from
  reasoning effort) and SSE reduction (message_start/content_block_*/
  message_delta), with verbatim thinking-block replay bound to the
  tool calls it was minted beside
- anthropic_messages: transport bundle mirroring chat_completions
  (endpoint, headers, catalog projection, reviewer plumbing)
- wire-name projection: Anthropic tool names forbid dots, so dotted fx
  tool identities map to [A-Za-z0-9_-] wire names inbound and outbound
- credentials: x-api-key env credentials resolve like bearer
Adds an Anthropic Messages SSE fixture and two cases to
configured-providers.test.ts: x-api-key auth + /v1/messages request
shape (system hoist, stream flag, content parts), and a tool_use /
tool_result round trip asserting signed thinking blocks replay
verbatim on the continuation request.
Addresses codec review findings that would 400 or misbehave on the wire:

- Skip thinking on required tool calls (tool_choice:any is rejected
  alongside thinking); required calls keep their contract.
- Omit thinking when max_tokens cannot exceed the budget floor.
- Serialize parallel_tool_calls=false as disable_parallel_tool_use,
  forcing an explicit tool_choice when the mode would omit it.
- Map model_context_window_exceeded to a length finish reason.
- Bound signature deltas by the replay budget instead of identity_bytes.
- Free owned tool calls on the OOM path in Reducer.finish.

This branch has not been deployed

No deployments
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