provider-anthropic
v1.1.6Anthropic Messages API provider worker; implements provider::anthropic::stream and provider::anthropic::refresh_models behind llm-router.
- macOS: arm64 · x64
- Linux: arm64 · armv7 · x64
- Windows: arm64 · x64 · x86
exact versions are immutable; binary and bundle artifacts are digest-pinned.
full markdown
/workers/provider-anthropic.md. paste it into an llm prompt or pipe it through curl from a worker.install
dependencies
readme
provider-anthropic
Claude models behind llm-router. Install this worker next
to the router, give it an API key, and the Anthropic catalog — streaming,
extended thinking, tool use, vision, automatic prompt caching — appears
behind the router's single front door (router::chat / router::complete).
You never call this worker directly: the router invokes it worker-to-worker,
and iii-permissions.yaml blocks agent access to provider::anthropic::*.
Install
iii worker add provider-anthropicThe provider does nothing on its own — it plugs into the router:
iii worker add llm-routeriii worker add fetches the binary, writes a config block into
~/.iii/config.yaml, and the engine starts the worker on the next
iii start.
Quickstart
Give the router a credential: paste a key into the anthropic slice of the
engine's llm-router configuration entry, or set ANTHROPIC_API_KEY in the
router's environment.
{ "providers": { "anthropic": { "api_key": "sk-ant-…" } } }The router picks up the change and kicks model discovery; Claude models land
in the catalog (router::models::list). Then make a first call — Node shown,
any SDK works:
const res = await iii.trigger('router::complete', {
model: 'claude-sonnet-4-6',
messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }], timestamp: Date.now() }],
}, { timeout_ms: 320_000 }); // outer timeout ≥ the router's 300s stream budget
// res: { message, usage, provider, model } — message is the final AssistantMessageFor token-by-token streaming, call router::chat with an iii channel — the
walkthrough lives in llm-router's Quickstart.
Request extended thinking with thinking_level; the worker maps the level
to an Anthropic thinking budget from the model's catalog record. xhigh
degrades to high on models that don't support it, and the level is
dropped on models that don't support thinking at all.
Configuration
All operator configuration lives in the router's llm-router entry — this
worker keeps no config of its own:
"anthropic": {
"api_key": "sk-ant-…", // or ANTHROPIC_API_KEY in the router's env
"api_url": "https://api.anthropic.com/v1/messages", // override for proxies / gateways
"max_tokens": 8192 // output ceiling when a request sets none
}Worker-side environment variables:
| Variable | Default | Meaning |
|---|---|---|
PROVIDER_ANTHROPIC_CACHE |
enabled | 0/false disables automatic prompt-cache markers |
III_WS_URL |
ws://127.0.0.1:49134 |
engine WebSocket to attach to when --url is not set |
The binary also takes the standard worker CLI flags: --url (engine
WebSocket), --manifest (print the registry manifest and exit), and
--config (accepted but ignored with a warning — provider config comes
from the llm-router configuration entry).
Prompt caching needs no setup: markers go on the system prompt, the tools tail, and the last stable assistant turn whenever the prefix is big enough to be worth a cache write.
Models
The catalog slice is live GET /v1/models merged with a curated capability
snapshot — context windows, output ceilings, thinking budgets, pricing
(USD per MTok). Live ids the snapshot doesn't know get conservative
defaults; curated aliases the API doesn't enumerate are kept, so the catalog
has no cold hole before first discovery. The snapshot lives in
src/curated.rs — update it against models.dev when
Anthropic ships new models; discovery only supplies bare ids.
Notes
- Structured output: the Messages API has no native JSON mode; every
catalog record declares
supports_structured_output: false, and a forwardedresponse_formatis reported inwarningsand ignored. - Errors: 401/403 →
auth_expired, 429 →rate_limited, 413/context →context_overflow, 5xx/network →transient, other 4xx →permanent. The worker never retries — the router owns retry policy. - Credentials: resolved per request via
router::provider::resolve(config slice →ANTHROPIC_API_KEYon the router → none). Bothapi_key(x-api-key) andoauth(Bearer) shapes work; v1 performs no OAuth refresh. - Identity binding: the router issues a
registration_tokenon first registration, persisted in iii-state (scopeprovider-anthropic). If that state is lost the router rejects re-registration — clear the binding on the router side to re-pair.
Tests
cargo test # unit (pure modules + TCP stubs)
III_ENGINE_BIN=$(which iii) cargo test --test integration -- --test-threads=1The integration suite spawns a real engine, the real router (path dep), this provider, and a local stub upstream — no external API calls anywhere.
api reference (json)
{
"functions": [
{
"description": "Cancel the in-flight upstream stream for a request_id (router::abort fan-out), stopping billed generation immediately.",
"metadata": {
"internal": true
},
"name": "provider::anthropic::abort",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Input of a provider's `provider::<id>::abort`: actively cancel the in-flight upstream stream for `request_id` (the router's `request_id`, delivered to the provider as `resolution_key`) so billed generation stops immediately instead of waiting for the provider to notice the closed channel on its next write.",
"properties": {
"request_id": {
"type": "string"
}
},
"required": [
"request_id"
],
"title": "ProviderAbortRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Output of `provider::<id>::abort`. `aborted: false` means the request was unknown — already finished, never started, or aborted before (idempotent).",
"properties": {
"aborted": {
"type": "boolean"
}
},
"required": [
"aborted"
],
"title": "ProviderAbortResponse",
"type": "object"
}
},
{
"description": "Internal: router::ready subscriber that re-declares this provider and refreshes its catalog.",
"metadata": {
"internal": true
},
"name": "provider::anthropic::on_router_ready",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Event delivered to a provider's `provider::<id>::on_router_ready` (the `router::ready` trigger payload, currently `{}`). Unknown fields are ignored.",
"title": "RouterReadyEvent",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Ack returned by a provider's `provider::<id>::on_router_ready`.",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "ProviderReadyAck",
"type": "object"
}
},
{
"description": "Refresh the Anthropic catalog slice from the upstream models endpoint and reconcile it through the router; returns the model count written.",
"metadata": {
"internal": true
},
"name": "provider::anthropic::refresh_models",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Input of a provider's `provider::<id>::refresh_models` — takes no arguments. A struct (not `Value`) keeps the request schema concrete; unknown fields (e.g. the engine-injected `_caller_worker_id`) are ignored.",
"title": "RefreshModelsRequest",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Output of `provider::<id>::refresh_models`.",
"properties": {
"count": {
"format": "uint",
"minimum": 0,
"type": "integer"
},
"ok": {
"type": "boolean"
}
},
"required": [
"count",
"ok"
],
"title": "RefreshModelsResponse",
"type": "object"
}
},
{
"description": "Stream an Anthropic chat completion: resolve credentials, call the upstream Messages API, and relay AssistantMessageEvent frames to writer_ref.",
"metadata": {
"internal": true
},
"name": "provider::anthropic::stream",
"request_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AgentFunction": {
"description": "Function invocation schema — what a provider sees as a `tools` array entry (README § Function invocation schema; adapter boundary). These describe iii functions exposed to the model, not provider-native tools.",
"properties": {
"description": {
"type": "string"
},
"execution_mode": {
"type": [
"string",
"null"
]
},
"label": {
"type": [
"string",
"null"
]
},
"name": {
"type": "string"
},
"parameters": true
},
"required": [
"description",
"name",
"parameters"
],
"type": "object"
},
"AgentMessage": {
"anyOf": [
{
"$ref": "#/definitions/AssistantMessage"
},
{
"$ref": "#/definitions/FunctionResultMessage"
},
{
"$ref": "#/definitions/CustomMessage"
},
{
"$ref": "#/definitions/UserMessage"
}
],
"description": "The canonical transcript message union. Untagged: the single-variant role tags disambiguate deserialization."
},
"AssistantMessage": {
"properties": {
"content": {
"items": {
"$ref": "#/definitions/ContentBlock"
},
"type": "array"
},
"error_kind": {
"anyOf": [
{
"$ref": "#/definitions/ErrorKind"
},
{
"type": "null"
}
]
},
"error_message": {
"type": [
"string",
"null"
]
},
"model": {
"type": "string"
},
"native_stop_reason": {
"type": [
"string",
"null"
]
},
"provider": {
"type": "string"
},
"role": {
"$ref": "#/definitions/AssistantRoleTag"
},
"stop_reason": {
"$ref": "#/definitions/StopReason"
},
"timestamp": {
"format": "int64",
"type": "integer"
},
"usage": {
"anyOf": [
{
"$ref": "#/definitions/Usage"
},
{
"type": "null"
}
]
},
"warnings": {
"items": {
"type": "string"
},
"type": [
"array",
"null"
]
}
},
"required": [
"content",
"model",
"provider",
"role",
"stop_reason",
"timestamp"
],
"type": "object"
},
"AssistantRoleTag": {
"enum": [
"assistant"
],
"type": "string"
},
"ChannelDirection": {
"enum": [
"read",
"write"
],
"type": "string"
},
"ContentBlock": {
"description": "Content blocks — the atomic units of message content (README § Content blocks).",
"oneOf": [
{
"properties": {
"text": {
"type": "string"
},
"type": {
"enum": [
"text"
],
"type": "string"
}
},
"required": [
"text",
"type"
],
"type": "object"
},
{
"properties": {
"data": {
"type": "string"
},
"mime": {
"type": "string"
},
"type": {
"enum": [
"image"
],
"type": "string"
}
},
"required": [
"data",
"mime",
"type"
],
"type": "object"
},
{
"properties": {
"signature": {
"type": [
"string",
"null"
]
},
"text": {
"type": "string"
},
"type": {
"enum": [
"thinking"
],
"type": "string"
}
},
"required": [
"text",
"type"
],
"type": "object"
},
{
"description": "Opaque redacted thinking payload — replayed verbatim on the Anthropic wire.",
"properties": {
"data": {
"type": "string"
},
"type": {
"enum": [
"redacted_thinking"
],
"type": "string"
}
},
"required": [
"data",
"type"
],
"type": "object"
},
{
"properties": {
"arguments": true,
"function_id": {
"type": "string"
},
"id": {
"type": "string"
},
"type": {
"enum": [
"function_call"
],
"type": "string"
}
},
"required": [
"arguments",
"function_id",
"id",
"type"
],
"type": "object"
},
{
"properties": {
"content": {
"items": {
"$ref": "#/definitions/ContentBlock"
},
"type": "array"
},
"function_call_id": {
"type": "string"
},
"is_error": {
"type": [
"boolean",
"null"
]
},
"type": {
"enum": [
"function_result"
],
"type": "string"
}
},
"required": [
"content",
"function_call_id",
"type"
],
"type": "object"
}
]
},
"CustomMessage": {
"properties": {
"content": {
"items": {
"$ref": "#/definitions/ContentBlock"
},
"type": "array"
},
"custom_type": {
"type": "string"
},
"details": true,
"display": {
"type": [
"string",
"null"
]
},
"role": {
"$ref": "#/definitions/CustomRoleTag"
},
"timestamp": {
"format": "int64",
"type": "integer"
}
},
"required": [
"content",
"custom_type",
"role",
"timestamp"
],
"type": "object"
},
"CustomRoleTag": {
"enum": [
"custom"
],
"type": "string"
},
"ErrorKind": {
"enum": [
"auth_expired",
"rate_limited",
"context_overflow",
"transient",
"permanent"
],
"type": "string"
},
"FunctionResultMessage": {
"properties": {
"content": {
"items": {
"$ref": "#/definitions/ContentBlock"
},
"type": "array"
},
"details": true,
"function_call_id": {
"type": "string"
},
"function_id": {
"type": "string"
},
"is_error": {
"type": "boolean"
},
"role": {
"$ref": "#/definitions/FunctionResultRoleTag"
},
"timestamp": {
"format": "int64",
"type": "integer"
}
},
"required": [
"content",
"details",
"function_call_id",
"function_id",
"is_error",
"role",
"timestamp"
],
"type": "object"
},
"FunctionResultRoleTag": {
"enum": [
"function_result"
],
"type": "string"
},
"Model": {
"description": "The capability record (README § Model descriptor).",
"properties": {
"context_window": {
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"display_name": {
"type": [
"string",
"null"
]
},
"id": {
"type": "string"
},
"input_limit": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"max_output_tokens": {
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"pricing": {
"anyOf": [
{
"$ref": "#/definitions/Pricing"
},
{
"type": "null"
}
]
},
"provider": {
"type": "string"
},
"reasoning_efforts": {
"items": {
"$ref": "#/definitions/ReasoningEffort"
},
"type": [
"array",
"null"
]
},
"supports_cache": {
"type": [
"boolean",
"null"
]
},
"supports_structured_output": {
"type": [
"boolean",
"null"
]
},
"supports_thinking": {
"type": [
"boolean",
"null"
]
},
"supports_tools": {
"type": [
"boolean",
"null"
]
},
"supports_vision": {
"type": [
"boolean",
"null"
]
},
"supports_xhigh": {
"type": [
"boolean",
"null"
]
},
"thinking_budgets": {
"additionalProperties": {
"format": "uint64",
"minimum": 0,
"type": "integer"
},
"type": [
"object",
"null"
]
}
},
"required": [
"context_window",
"id",
"max_output_tokens",
"provider"
],
"type": "object"
},
"Pricing": {
"properties": {
"cache_read": {
"format": "double",
"type": [
"number",
"null"
]
},
"cache_write": {
"format": "double",
"type": [
"number",
"null"
]
},
"input": {
"format": "double",
"type": [
"number",
"null"
]
},
"output": {
"format": "double",
"type": [
"number",
"null"
]
}
},
"type": "object"
},
"ReasoningEffort": {
"description": "One provider-native reasoning effort advertised for a specific model.\n\nValues intentionally remain strings: provider catalogs can add efforts without requiring a router-wide enum release first.",
"properties": {
"description": {
"type": [
"string",
"null"
]
},
"effort": {
"type": "string"
}
},
"required": [
"effort"
],
"type": "object"
},
"ResponseFormat": {
"properties": {
"schema": true,
"type": {
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
},
"StopReason": {
"enum": [
"end",
"length",
"function_call",
"aborted",
"error"
],
"type": "string"
},
"StreamChannelRef": {
"properties": {
"access_key": {
"type": "string"
},
"channel_id": {
"type": "string"
},
"direction": {
"$ref": "#/definitions/ChannelDirection"
}
},
"required": [
"access_key",
"channel_id",
"direction"
],
"type": "object"
},
"ThinkingLevel": {
"description": "\"minimal\" requests the lowest reasoning effort and needs only `thinking` support; levels map to provider-native knobs via `Model::thinking_budgets`.",
"enum": [
"minimal",
"low",
"medium",
"high",
"xhigh"
],
"type": "string"
},
"Usage": {
"properties": {
"cache_read": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"cache_write": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"cost_usd": {
"format": "double",
"type": [
"number",
"null"
]
},
"input": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"output": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"reasoning": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
}
},
"type": "object"
},
"UserMessage": {
"properties": {
"content": {
"items": {
"$ref": "#/definitions/ContentBlock"
},
"type": "array"
},
"role": {
"$ref": "#/definitions/UserRoleTag"
},
"timestamp": {
"format": "int64",
"type": "integer"
}
},
"required": [
"content",
"role",
"timestamp"
],
"type": "object"
},
"UserRoleTag": {
"description": "Single-variant role tags: exact-match on deserialize, correct wire string on serialize, and they let `AgentMessage` be an untagged union.",
"enum": [
"user"
],
"type": "string"
}
},
"description": "Input of a provider worker's `provider::<id>::stream` iii function — what the router forwards per attempt. (No `PartialEq`: `iii_sdk::StreamChannelRef` doesn't implement it.)",
"properties": {
"max_output_tokens": {
"format": "uint64",
"minimum": 0,
"type": [
"integer",
"null"
]
},
"messages": {
"items": {
"$ref": "#/definitions/AgentMessage"
},
"type": "array"
},
"model": {
"type": "string"
},
"model_meta": {
"anyOf": [
{
"$ref": "#/definitions/Model"
},
{
"type": "null"
}
]
},
"provider_options": true,
"resolution_key": {
"type": [
"string",
"null"
]
},
"response_format": {
"anyOf": [
{
"$ref": "#/definitions/ResponseFormat"
},
{
"type": "null"
}
]
},
"system_prompt": {
"type": [
"string",
"null"
]
},
"thinking_level": {
"anyOf": [
{
"$ref": "#/definitions/ThinkingLevel"
},
{
"type": "null"
}
]
},
"tools": {
"items": {
"$ref": "#/definitions/AgentFunction"
},
"type": [
"array",
"null"
]
},
"writer_ref": {
"$ref": "#/definitions/StreamChannelRef"
}
},
"required": [
"messages",
"model",
"writer_ref"
],
"title": "ProviderStreamInput",
"type": "object"
},
"response_schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Output of a provider's `provider::<id>::stream` (spec § stream contract): the function streams frames to `writer_ref` and returns this ack.",
"properties": {
"ok": {
"type": "boolean"
}
},
"required": [
"ok"
],
"title": "ProviderStreamOutput",
"type": "object"
}
}
],
"triggers": []
}