zidane/ providers/ anthropic
The Anthropic provider for the Messages API, with API keys, Claude OAuth credentials or Amazon Bedrock.
import { … } from 'zidane/providers/anthropic'src/providers/anthropic.ts
Use anthropic() when your agent talks to Claude. With no key passed, it reads ANTHROPIC_API_KEY, then the stored OAuth credentials. It sets prompt-cache breakpoints and maps thinking levels to Anthropic's effort and adaptive thinking. System notes (systemNotes on a run) go out as native role: "system" messages on the models that accept them, and as <system-reminder> text elsewhere. An effort note becomes a per-message effort setting, so changing effort mid-conversation keeps the prompt cache. The other exports are the pieces it is built from, for custom gateways and tests.
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers/anthropic'
const agent = createAgent({ provider: anthropic({ defaultModel: 'claude-fable-5-1' }) })
const stats = await agent.run({ prompt: 'Summarize README.md in three bullets.' })
console.log(stats.turns)Functions#
anthropicFunction#
Creates the Anthropic provider for the Messages API.
Authenticates with an API key or Claude OAuth credentials, and can route through Amazon Bedrock via bedrock. Without apiKey, reads ANTHROPIC_API_KEY or stored OAuth credentials: a snapshot at construction, then again on every call.
Outside Bedrock mode, throws at construction when no credential is found.
function anthropic(anthropicParams?: AnthropicParams): ProviderParameters
| Name | Type | Description |
|---|---|---|
| anthropicParams? | AnthropicParams | API key or OAuth credentials, default model, Bedrock mode, base URL, headers and beta flags. |
Returns
Example
import { createAgent } from 'zidane'
import { anthropic } from 'zidane/providers/anthropic'
const agent = createAgent({
provider: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
})
await agent.run({ prompt: 'Say hello.' })applyAnthropicCacheBreakpointsFunction#
Mutate an Anthropic request in place to add cache breakpoints on the three stable prefixes:
- System prompt —
cache_controlon the static prefix only (see below). - Tool definitions — last tool.
- Conversation — last content block of the last message with content (an effort-only system message has none).
A fourth, lookback breakpoint goes on the message right before the latest assistant turn, whatever its role, when the request carries fewer than 4 markers (see the comment in the body).
Each breakpoint tells Anthropic to cache the prefix ending at that block; subsequent turns reuse the cached prefix and pay only for the delta. Safe no-op when any prefix is empty (no tools, empty system, etc.).
System-prompt boundary handling (SYSTEM_PROMPT_BOUNDARY):
- When
originalSystemis provided AND carries the marker, the system block is rewritten as a 2-block array: static (cached) then dynamic (uncached). The caller is expected to have already stripped the marker fromparams.systemviarenderSystemForWire— this helper only re-splits to applycache_controlcleanly. - When
originalSystemis omitted or marker-free, the existing single- block treatment applies:cache_controlon the whole system string. - Array system prompts: existing semantics —
cache_controllands on the last block. Callers building their own multi-block layout own the breakpoint placement.
function applyAnthropicCacheBreakpoints(
params: MessageCreateParamsStreaming,
originalSystem?: string,
ttl?: CacheTtl,
): voidParameters
| Name | Type | Description |
|---|---|---|
| params | MessageCreateParamsStreaming | Streaming request to mutate. |
| originalSystem? | string | System prompt before boundary rendering, used to split the static and dynamic parts. |
| ttl? | CacheTtl | Cache lifetime for the breakpoints. Uses the API default when omitted. |
buildAnthropicClientOptionsFunction#
SDK constructor options for a resolved credential — the single place that decides which auth header goes on the wire.
Exported (and pure) so the header shape is test-pinned without a network
round-trip: authToken → Authorization: Bearer …, apiKey → x-api-key,
and apiKey: null alongside authToken is what suppresses x-api-key so a
gateway sees exactly one credential.
function buildAnthropicClientOptions(
apiKey: string,
auth: { bearer: boolean; claudeCode: boolean },
baseURL?: string,
extraBetas?: readonly string[],
extraHeaders?: Record<string, string>,
): Record<string, unknown>Parameters
| Name | Type | Description |
|---|---|---|
| apiKey | string | Resolved API key or bearer token. |
| auth | { bearer: boolean; claudeCode: boolean } | Wire auth shape from |
| baseURL? | string | Optional SDK base URL override. |
| extraBetas? | readonly string[] | Extra |
| extraHeaders? | Record<string, string> | Extra request headers. Reserved header names are dropped. |
Returns
Record<string, unknown>cacheWriteTtlMultiplierFunction#
Returns the factor to apply to the cache write cost of a turn that wrote under the 1h TTL, or 1 when no 1h writes are reported.
function cacheWriteTtlMultiplier(
usage: { cache_creation?: { ephemeral_1h_input_tokens?: number; ephemeral_5m_input_tokens?: number } | null; cache_creation_input_tokens?: number | null },
): numberParameters
| Name | Type | Description |
|---|---|---|
| usage | { cache_creation?: { ephemeral_1h_input_tokens?: number; ephemeral_5m_input_tokens?: number } | null; cache_creation_input_tokens?: number | null } | Usage block of the response, with the optional per-TTL cache write split. |
Returns
numberclassifyAnthropicErrorFunction#
Classify an Anthropic SDK / HTTP error for typed-error wrapping.
prompt is too long(400 invalid_request_error) →context_exceeded.'tool_use' ids were found without 'tool_result' blocks/'tool_result must be preceded by a tool_call'(400) →tool_pairing_corruption. The local repair pass should have caught this — the typed error tells consumers their pre-send pipeline is shipping orphaned blocks somehow (customcontext:transformhook, bypassed loop, mid-stream session edit) so they can patch the root cause instead of chasing 400s.- A rate limit, spent credit or overload (429, 529,
billing_error, also mid-stream) →provider_errorworded bydescribeProviderLimit, with the server's reset (seeclassifyProviderLimitfor when it stops retrying). - Any other Anthropic
APIError-shaped value →provider_errorwith the native status/type code. - Unknown errors →
null(loop wraps inAgentProviderErrorgenerically).
Anthropic's wire error shape is { type: 'error', error: { type, message } } — the
SDK stores the parsed body on err.error. We walk both levels so callers see the
most specific providerCode we can find.
function classifyAnthropicError(err: unknown, label?: string): ClassifiedError | nullParameters
| Name | Type | Default | Description |
|---|---|---|---|
| err | unknown | Error thrown by the SDK or the HTTP layer. | |
| label? | string | 'Claude' | Product a limit's message names; providers built on this wire (Bolt) pass their own. |
Returns
ClassifiedError | nullcredentialIdentityFunction#
Credential-resolution identity for resolveOAuthApiKey. Anthropic's
own unless the caller pointed the provider at a gateway with its own
credentials via AnthropicParams.credentialSource.
function credentialIdentity(
anthropicParams?: AnthropicParams,
): { envKey?: string; missingError: string; providerId: string; refreshError: (reason: string) => string }Parameters
| Name | Type | Description |
|---|---|---|
| anthropicParams? | AnthropicParams | Provider params, read for |
Returns
{ envKey?: string; missingError: string; providerId: string; refreshError: (reason: string) => string }defaultAnthropicMaxTokensFunction#
Conservative per-family output ceiling, used ONLY as a fallback when pi-ai's
model registry doesn't know the model (or pi-ai is externalized) — see
resolveAnthropicMaxOutput, which is the primary path. These are
non-beta floors we can always send without a 400; the registry reports the
real (often much higher, e.g. Opus 4.x = 128K native) max when available.
function defaultAnthropicMaxTokens(modelId: string): numberParameters
| Name | Type | Description |
|---|---|---|
| modelId | string | Model id. Matched by family name ( |
Returns
numberplanAnthropicThinkingFunction#
Decide how to translate a ThinkingLevel into Anthropic's request shape.
Pure / synchronous — exported so tests can assert routing without standing
up the SDK.
Routing rules:
'off'→null(no thinking field, no effort hint).'adaptive'→ adaptive thinking with no effort hint (model decides everything). WhentaskBudgetis set, it is carried astaskBudgetTokensand emitted asoutput_config.task_budget— the native whole-task thinking cap. LEGACY: whencustomBudgetis set, it is carried asmaxTokensCapso the request builder capsmax_tokensaccordingly — this clamps the whole response envelope (text and tool-call JSON included), which is the wrong knob for bounding thinking now thattaskBudgetexists; kept only for callers who already depend on it. PrefertaskBudget.- Budgeted levels → adaptive thinking with an
efforthint (plustaskBudgetTokenswhentaskBudgetis set), unlesscustomBudgetis provided. - Any level +
customBudget→ explicit-budgetenabledpath, carrying the level'sefforthint alongside the budget. The caller has opted into precise budget control and accepts the Anthropic deprecation warning that comes with it on opus 4.6+.'adaptive'is the sole exception: it never falls back to enabled, since adaptive is the current Anthropic API surface for self-budgeted thinking.taskBudgetis adaptive-only and ignored on this path. effortHint: false(defaulttrue) strips theefforthint from every shape — budgeted levels plan plain adaptive and thecustomBudgetpath plansenabledwith nooutput_config.effort— for Anthropic models that reject the effort field.
function planAnthropicThinking(
level: 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'adaptive',
customBudget?: number,
taskBudget?: number,
effortHint?: boolean,
): AnthropicThinkingPlan | nullParameters
| Name | Type | Default | Description |
|---|---|---|---|
| level | 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'adaptive' | Thinking level. | |
| customBudget? | number | Explicit thinking token budget. | |
| taskBudget? | number | Whole-task thinking budget, for adaptive thinking. | |
| effortHint? | boolean | true | Set |
Returns
AnthropicThinkingPlan | nullresolveAnthropicAuthShapeFunction#
Whether the wire auth is a bearer token, and whether the Claude Code
persona (betas + x-app headers + system-prompt folding) applies. Only the
default 'auto' mode infers both from the token shape — see
AnthropicParams.authMode.
function resolveAnthropicAuthShape(
apiKey: string,
authMode: 'auto' | 'bearer' | 'api-key' | undefined,
): { bearer: boolean; claudeCode: boolean }Parameters
| Name | Type | Description |
|---|---|---|
| apiKey | string | Resolved credential. Tokens containing |
| authMode | 'auto' | 'bearer' | 'api-key' | undefined | Auth mode from the provider params. Defaults to |
Returns
{ bearer: boolean; claudeCode: boolean }resolveAnthropicBetasFunction#
Build the anthropic-beta header value — OAuth defaults plus caller-supplied
extras, de-duped while preserving order. Returns undefined when no betas
apply (non-OAuth, no extras).
function resolveAnthropicBetas(
isOAuth: boolean,
extraBetas?: readonly string[],
): string | undefinedParameters
| Name | Type | Description |
|---|---|---|
| isOAuth | boolean | Whether the Claude Code persona applies, which adds the OAuth default betas. |
| extraBetas? | readonly string[] | Extra beta flags to append. |
Returns
string | undefinedresolveAnthropicMaxOutputFunction#
Resolve a model's max output tokens. Prefers pi-ai's bundled registry — the
authoritative source the SDK itself uses to build requests, so e.g. Opus 4.x
reports its full native 128K (no extended-output beta required) — and falls
back to defaultAnthropicMaxTokens only when the registry lacks the
model. Used as BOTH the default (when the caller leaves maxTokens unset)
and a hard clamp, since a max_tokens above the model's max would 400.
The clamp is only as good as the ceiling's provenance, so an explicit caller
maxTokens can RAISE the fallback: models the registry doesn't know are
mostly ids that never appear in pi-ai's anthropic registry — Bedrock ids
(global.anthropic.claude-*) and non-Anthropic models served over the
Anthropic wire (openai.gpt-* on Bedrock, gateway aliases). For the
latter the fallback degrades to the 32000 catch-all; for the former the
family match yields a deliberately conservative non-beta floor. Clamping a
caller who knows the real ceiling down to either silently truncates long
generations mid-write (observed as turns pinned at exactly 32000 tokens).
It can only raise, never lower: the caller's maxTokens is a cap on the
VISIBLE response, already enforced as max_tokens itself, whereas this
value is the model's physical ceiling. Letting it lower the ceiling would
break the enabled thinking path's invariant that the budget sits ON TOP
of the response cap — the budget would be shrunk to share the caller's cap,
or dropped entirely once no satisfiable budget fits.
A registry-backed max always wins over the caller — above it the provider
400s. A CATALOG entry (behavior.modelCatalog / setModelCatalog) outranks
even that: it's an explicit claim about a model whose registry entry may be
absent, stale, or an alias for something else entirely.
function resolveAnthropicMaxOutput(
modelId: string,
explicitMaxTokens?: number,
catalog?: ModelCatalog,
): Promise<number>Parameters
| Name | Type | Description |
|---|---|---|
| modelId | string | Model id to resolve. |
| explicitMaxTokens? | number | Caller token cap. Can raise the fallback ceiling, never lower it. |
| catalog? | ModelCatalog | Per-run model catalog that outranks the registry. |
Returns
Promise<number>resolveThinkingBindingFunction#
Effective AnthropicParams.thinkingBinding: the explicit param wins,
then the shell's ZIDANE_THINKING_BINDING, then the chat host's setting
channel (THINKING_BINDING_SETTING_ENV), then 'drop_block'. Read
per request so a flip lands on the next call without rebuilding the
provider (the factory is no-arg in the chat host). Unrecognized values are
ignored at each level.
function resolveThinkingBinding(
param: ThinkingBindingBehavior | undefined,
env?: ProcessEnv,
): ThinkingBindingBehaviorParameters
| Name | Type | Default | Description |
|---|---|---|---|
| param | ThinkingBindingBehavior | undefined | Explicit | |
| env? | ProcessEnv | process.env | Environment to read the fallbacks from. |
Returns
Constants#
EXTENDED_CACHE_TTL_BETAConstant#
Beta flag Anthropic requires before it honors ttl: '1h' on cache_control.
const EXTENDED_CACHE_TTL_BETA = 'extended-cache-ttl-2025-04-11'THINKING_BINDING_SETTING_ENVConstant#
Env channel the chat host writes the user's thinkingBinding SETTING to.
Distinct from ZIDANE_THINKING_BINDING (the shell override) so the two
never read each other back: a host recreated in-process must not mistake
an earlier host's setting write for a shell value.
const THINKING_BINDING_SETTING_ENV = 'ZIDANE_THINKING_BINDING_SETTING'Interfaces#
AnthropicBedrockConfigInterface#
Amazon Bedrock mode for the anthropic provider.
Routes the identical Messages wire shape through @anthropic-ai/bedrock-sdk
(AnthropicBedrock), which signs requests with SigV4 instead of an
Anthropic API key. Credentials resolve through the standard AWS chain —
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (+ AWS_SESSION_TOKEN) env
vars, shared config, instance roles — so no ANTHROPIC_API_KEY is needed
or consulted.
Model ids are Bedrock-shaped (e.g.
global.anthropic.claude-haiku-4-5-20251001-v1:0); pass one as
defaultModel or per run.
interface AnthropicBedrockConfigProperties
| Name | Type | Description |
|---|---|---|
| region? | string | AWS region for the Bedrock runtime endpoint. Resolution order:
this field → |
| sdk? | (options: Record<string, unknown>) => unknown | Bring-your-own client constructor. The default lazily imports
Receives the resolved client options ( |
AnthropicContextManagementInterface#
Server-side context-management config — the body of context_management on
the Messages API. Typed loosely (Record-of-unknown) so we don't pin a specific
SDK schema version: the SDK's non-beta Messages types do not include this
field, but the wire format is stable behind the context-management-2025-06-27 beta.
See: https://docs.anthropic.com/en/docs/build-with-claude/context-management
interface AnthropicContextManagementProperties
| Name | Type | Description |
|---|---|---|
| edits? | Record<string, unknown>[] | Context-editing strategies the API applies server-side, such as clearing old tool results. |
AnthropicParamsInterface#
Options of the anthropic provider: Anthropic API, Claude subscription OAuth, gateway or Bedrock.
interface AnthropicParams extends OAuthParamsProperties
| Name | Type | Default | Description |
|---|---|---|---|
access?from OAuthParams | string | OAuth access token supplied at runtime. Used together with | |
account?from OAuthParams | string | Run on ONE stored account of the provider instead of its active one: the
credential's stable id ( | |
apiKey?from OAuthParams | string | Static API key or OAuth access token sent as is, bypassing credential lookup and refresh. | |
| authMode? | 'auto' | 'bearer' | 'api-key' | How the resolved credential reaches the wire.
Only | |
| baseURL? | string | Optional override for the Anthropic SDK base URL. Honored end-to-end — headers and routing pass through to the forwarded host. Useful for proxies (e.g. corporate gateways, internal router). | |
| bedrock? | boolean | AnthropicBedrockConfig | Run against Amazon Bedrock instead of the Anthropic API. Bedrock has no Caveats: | |
| capabilities? | Partial<ProviderCapabilities> | Override advertised capabilities (merged over the Anthropic defaults).
For gateways behind | |
| contextManagement? | AnthropicContextManagement | Server-side context-management directive. Sent on the request body as
Typed loosely so future Anthropic schema additions land without an SDK bump. A typical compaction edit: | |
| credentialSource? | { envKey?: string; missingError?: string; providerId: string; refreshError?: (reason: string) => string } | Override the credential identity used to resolve (and lazily refresh) the
token on every call. Defaults to Anthropic's own
( Set this when
| |
| defaultModel? | string | DEFAULT_MODEL_PICK.modelId | Model used when a request does not set one. |
expires?from OAuthParams | number | Expiry of | |
| extraBetas? | readonly string[] | Additional
Honored on both the OAuth and API-key paths. | |
| extraBodyParams? | Record<string, unknown> | Generic pass-through for fields on the Messages API request body that the
SDK does not yet type. Spread into the request before the typed fields,
so explicit options ( Forward-compat escape hatch for new Anthropic betas — when a future flag
ships before zidane has a dedicated typed knob, set it here without
waiting on a release. Most fields will still need the matching beta in
| |
| extraHeaders? | Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>) | Extra HTTP headers sent on every request, merged into the SDK's
Use for routing/attribution headers a proxy or gateway in front of the
May be a function resolved per request (awaited) instead of a static
map — the seam for gateways minting short-lived/signed auth tokens that a
long, suspended, or replayed durable run outlives. The resolver runs on
every | |
| label? | string | 'Claude' | Product a rate-limit, quota or overload message names, for services
that speak this wire ( |
| promptCacheKey? | PromptCacheKeyOption | Prompt-cache affinity key — an opt-in routing hint carried on the request as
On 1P Anthropic this is effectively a no-op: cache entries are scoped per
organization/workspace and matched purely by prefix, with no client-side
routing knob, and The derived default is a hash, which also satisfies Anthropic's guidance that this field not carry PII. Default: unset — no | |
| promptCacheKeyHeader? | string | Additionally send the affinity key as this HTTP header, for gateways that
route on a header rather than a body field. Defers to any same-named entry
in Default: unset — no header is added. | |
refresh?from OAuthParams | string | OAuth refresh token supplied at runtime. | |
| thinkingBinding? | ThinkingBindingBehavior | Preserved-thinking prefix binding (
Unset falls back to the Rides with the |
Types#
AnthropicThinkingPlanType#
Description of the Anthropic-side thinking configuration derived from a
ThinkingLevel. null means no thinking-related field should be set on the
request.
Two shapes:
adaptive—thinking.type='adaptive'. The model self-budgets per turn. Wheneffortis set, also emitsoutput_config.effortto hint at the level of reasoning the caller wants. This is the post-Claude-4.6 default for level-based control and avoids thethinking.type='enabled'deprecation warning. WhenmaxTokensCapis set, the request builder reducesmax_tokenstomin(max_tokens, maxTokensCap)— adaptive thinks and replies inside one envelope, so capping the envelope soft-bounds the thinking too.enabled— explicit-budget path:thinking.type='enabled'with an explicitbudget_tokens.maxTokensBumpis added to the request'smax_tokensso the budget sits on top of the response cap. When the caller's level maps to an effort tier,effortis carried too and emitted asoutput_config.effortalongside the explicit budget — both fields are valid together on 4.6+ models. The only way to pin a precise token budget; Anthropic emits a deprecation warning for this shape on opus 4.6+, so we only route here when the caller explicitly setsbehavior.thinkingBudget.
In both shapes Anthropic requires temperature to be 1.
type AnthropicThinkingPlan =
| { budgetTokens: number; effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max'; kind: 'enabled'; maxTokensBump: number }
| { effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max'; kind: 'adaptive'; maxTokensCap?: number; taskBudgetTokens?: number }ThinkingBindingBehaviorType#
What the API does with replayed thinking blocks whose prefix changed: drop_block, error or off. See AnthropicParams.thinkingBinding.
type ThinkingBindingBehavior = 'drop_block' | 'error' | 'off'