Anthropic
Claude models on the /v1/messages wire — vision, extended thinking, prompt caching, and tools.
文档正文为英文。导航、搜索和界面会跟随你选择的语言。
The Anthropic provider speaks Claude's native /v1/messages API. Use it for Claude Opus/Sonnet/Haiku with vision, extended thinking (reasoning), prompt caching, and tool use. The same provider also drives Claude-on-Vertex — see Vertex.
Setup
The factory lives at the @deuz-sdk/core/anthropic subpath. It returns a Provider: call it with a model id to get a LanguageModel descriptor.
import { createAnthropic } from '@deuz-sdk/core/anthropic';
// Read the key at the app layer — the SDK core never touches process.env.
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const model = anthropic('claude-opus-4-8');A pre-built default provider is also exported for when the key is supplied at the call layer (via deps.keyProvider or a createClient):
import { anthropic } from '@deuz-sdk/core/anthropic';
const model = anthropic('claude-opus-4-8');Factory options
createAnthropic(settings) accepts:
| Option | Type | Description |
|---|---|---|
apiKey | string | Sent as the x-api-key header. Optional here if resolved at the call layer. |
baseURL | string | Overrides the API base (proxy/gateway). The adapter appends /v1/messages. |
fetch | typeof fetch | Custom fetch implementation. Wins over deps.fetch. |
headers | Record<string, string> | Extra headers merged into every request. |
Settings are stashed on the descriptor via a private Symbol, so they never widen the public LanguageModel type or leak through JSON.stringify/Object.keys.
Models and capabilities
The descriptor { provider: 'anthropic', modelId, surface: 'anthropic' } is resolved against the registry, which is the single source of truth for per-model behavior. Pinned Claude slugs:
| Model | Vision | Tools | Thinking | Caching | Context | Max output |
|---|---|---|---|---|---|---|
claude-fable-5 | yes | yes | yes (adaptive) | yes | 1,000,000 | 128,000 |
claude-sonnet-5 | yes | yes | yes (adaptive) | yes | 1,000,000 | 128,000 |
claude-opus-4-8 | yes | yes | yes | yes | 1,000,000 | 128,000 |
claude-opus-4-7 | yes | yes | yes | yes | 1,000,000 | 128,000 |
claude-opus-4-6 | yes | yes | yes | yes | 1,000,000 | 128,000 |
claude-sonnet-4-6 | yes | yes | yes | yes | 1,000,000 | 64,000 |
claude-haiku-4-5 | yes | yes | yes | yes | 200,000 | 64,000 |
On Opus 4.7+, Sonnet 5 and Fable 5 the registry also flags samplingRestrictions — non-default temperature/top_p/top_k return HTTP 400 on those models, so the adapter never sends them.
Unknown slugs do not throw. A future claude-opus-4-9 falls back to conservative anthropic-surface defaults (tools/reasoning/structured-output off, max_tokens 4,096) and logs a warning via deps.logger, so new releases work without an SDK upgrade — pin a known slug to keep the full capability matrix.
Basic streaming
streamChat returns synchronously and never throws — failures surface as an error part on fullStream and reject the usage/finishReason promises. See streamChat.
import { streamChat } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const result = streamChat({
model: anthropic('claude-opus-4-8'),
messages: [{ role: 'user', content: 'Write a haiku about TypeScript.' }],
});
for await (const chunk of result.textStream) {
process.stdout.write(chunk);
}
const usage = await result.usage;
console.log('\ntokens:', usage.totalTokens);For a one-shot, buffered call use generateText instead.
Extended thinking (reasoning)
Set effort to control Claude's thinking depth. The wire depends on the model generation (effortWire in the registry):
- Opus 4.7+, Sonnet 5, Fable 5 (
effortWire: 'output_config'): the adapter sendsoutput_config.effortwith your level verbatim ('low' | 'medium' | 'high' | 'xhigh' | 'max'). Manualthinking.budget_tokensreturns HTTP 400 on these models, so the adapter never sends it; athinkingfield appears only as{ type: 'adaptive' }carrying the block binding when a request replays thinking to a Claude 5.x model. Adaptive thinking is always available; omittingeffortleaves the model default. - Opus 4.6 and older (
effortWire: 'budget_tokens'): the canonical level maps to athinking.budget_tokensvalue:
effort | budget_tokens |
|---|---|
'none' (or omitted) | thinking disabled |
'low' | 4,000 |
'medium' | 10,000 |
'high' | 24,000 |
'xhigh' / 'max' | 48,000 |
On the legacy wire, max_tokens is automatically raised to at least budget_tokens + 1024, and temperature/topP are not sent (Anthropic requires them unset with thinking enabled). Thinking text streams as reasoning-delta parts on fullStream, each carrying its block's id and source: 'anthropic'; the block's last reasoning-delta carries its signature. Thinking tokens bill inside outputTokens; since May 2026 the API also breaks them out, so usage.reasoningTokens reports the output_tokens_details.thinking_tokens count (0 on older models).
import { streamChat } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const result = streamChat({
model: anthropic('claude-opus-4-8'),
messages: [{ role: 'user', content: 'Is 9007199254740993 prime? Reason it through.' }],
effort: 'high',
});
for await (const part of result.fullStream) {
if (part.type === 'reasoning-delta') {
if (part.text) process.stdout.write(`[think] ${part.text}`);
if (part.signature) console.log('\n[signature attached]');
} else if (part.type === 'text-delta') {
process.stdout.write(part.text);
}
}The signature round-trips automatically inside the agentic loop — preserve any reasoning part you persist, or follow-up tool turns will be rejected.
Thinking blocks and block binding
Since 2.3 every block Claude sends is its own reasoning part, replayed unchanged and in its original place on the next request, in generateText, streamChat, native runs and useChat:
- A thinking block with empty text and a
signatureis kept. Claude 5.x models default todisplay: 'omitted', which streams only the signature. - Consecutive thinking blocks stay separate parts, each with its own signature, and a
redacted_thinkingblock is a part withredacted: truewhosetextis the opaquedata. - Thinking between two tool calls stays between them; the adapter no longer moves thinking to the front of the message.
- Only Claude's own blocks are sent. Reasoning from another provider (an OpenAI encrypted item, a Gemini thought) and a thinking block without a signature are left out of the request. See Reasoning parts.
Claude Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5 bind each thinking block's signature to the conversation before it. On accounts opened from 2026-08-31 a replayed block whose prefix changed (an edited earlier message, a rewritten system prompt) fails the whole request with HTTP 400. The agentic loops keep that prefix stable (Preserved thinking); as the safety net for a history edited elsewhere, a request that sends thinking back to one of these models, or to an unknown claude-{fable,opus,sonnet,haiku}-5… id, carries:
{
"thinking": {
"type": "adaptive",
"block_binding": { "prefix_mismatch_behavior": "drop_block" }
}
}with the header anthropic-beta: thinking-binding-controls-2026-08-01 (added to any anthropic-beta list you pass in headers). A mismatched block is then dropped and the request proceeds. A request without replayed thinking, an older model, or effort: 'none' (thinking disabled or between_tools, which carry no binding) is sent as before.
providerOptions.anthropic.thinking.block_binding overrides the default: an object is sent as given, null sends none. The rest of your providerOptions.anthropic.thinking (for example display) is kept next to the binding.
import { streamChat } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const result = streamChat({
model: anthropic('claude-opus-5-5'),
messages, // an earlier assistant turn carries thinking blocks
// Fail with 400 on a changed prefix instead of dropping the block:
providerOptions: {
anthropic: {
thinking: { type: 'adaptive', block_binding: { prefix_mismatch_behavior: 'error' } },
},
},
});
for await (const part of result.fullStream) {
if (part.type === 'warning' && part.warning.setting === 'thinking.block_binding') {
console.warn(part.warning.message); // what the API changed in the request
}
}The response's input_transformations report (blocks the API removed, and blocks that failed the check but were shown anyway) rides the finish part as providerMetadata.anthropic.input_transformations and surfaces as one warning with type: 'other' and setting: 'thinking.block_binding', on the stream and on the result's warnings.
The binding controls follow the beta types of @anthropic-ai/sdk 0.133.0 and Anthropic's API reference (cached 2026-10-06); they are not live-verified.
Vision
Pass an image part. The value may be a base64 string, a data URL, an http(s) URL, or raw Uint8Array bytes. For base64/data-URL/Uint8Array images mediaType is forwarded as the source media_type; for an http(s) URL the media type is inferred by Anthropic from the URL (the url source carries no media_type).
import { generateText } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const res = await generateText({
model: anthropic('claude-opus-4-8'),
messages: [
{
role: 'user',
content: [
{ type: 'text', text: 'What is in this image?' },
{
type: 'image',
image: 'https://example.com/photo.jpg',
mediaType: 'image/jpeg',
},
],
},
],
});
console.log(res.text);Tools
Tools are plain objects keyed by name in a ToolSet. Each has a parameters schema (a Standard Schema like Zod, or a raw JSON Schema) and an optional execute. Provide maxSteps to let the agentic loop run tools and feed results back. See Tools.
import { generateText } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';
import type { JSONSchema } from '@deuz-sdk/core';
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const citySchema: JSONSchema = {
type: 'object',
properties: { city: { type: 'string' } },
required: ['city'],
additionalProperties: false,
};
const res = await generateText({
model: anthropic('claude-opus-4-8'),
messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
tools: {
getWeather: {
description: 'Look up the current weather for a city.',
parameters: citySchema,
execute: async ({ city }: { city: string }) => ({ city, tempC: 22 }),
},
},
maxSteps: 5,
});
console.log(res.text);
console.log(res.steps?.length, 'steps');toolChoice accepts 'auto', 'required', 'none', or { type: 'tool', toolName }. Note that forced tool choice is illegal alongside extended thinking — when effort is set, the adapter downgrades a forced choice to auto.
Prompt caching
All pinned Claude slugs have caching: true. The adapter reports cache token breakdowns on usage:
| Field | Meaning |
|---|---|
cachedReadTokens | Tokens served from a cache hit (cache_read_input_tokens). |
cacheWriteTokens | Standard (5-minute) cache-creation tokens. |
cacheWrite1hTokens | 1-hour cache-creation tokens (ephemeral_1h_input_tokens). |
totalTokens includes input + cache reads + cache writes + output. Feed this breakdown into the pricing helper for correct cost — cache reads are billed at a fraction of input price.
Since 1.2.0 you can also write to the cache with one flag: promptCaching: 'auto' sends Anthropic's top-level automatic cache_control field — the API places the breakpoint on the last cacheable block and moves it forward as the conversation grows. 'auto-1h' uses the 1-hour TTL. Providers that cache implicitly (OpenAI, Gemini) ignore the flag.
const result = streamChat({ model, messages, promptCaching: 'auto' });Edge cases (per Anthropic docs): if the last block already carries an explicit cache_control with the same TTL, automatic caching is a no-op; with a different TTL the API returns 400 — don't combine this flag with hand-written breakpoints via providerOptions. The flag is currently effective only on Anthropic; other providers cache implicitly and ignore it.
Server-side web search
anthropicWebSearch() (root export) adds Anthropic's provider-executed web search — the model decides to search, Anthropic runs it during the turn, and results stream back as canonical source parts. Searches are counted in usage.serverToolUses ($10 / 1,000 searches).
When a response reports usage.iterations (server-side fallback, compaction, server tool loops), the token counters are the sum of the iterations. Since 2.3 the search count and the reasoningTokens share are kept as well: the iteration entries carry token counters only, so both come from the top-level usage (shapes from @anthropic-ai/sdk 0.133.0; not live-verified).
import { generateText, anthropicWebSearch } from '@deuz-sdk/core';
const res = await generateText({
model: anthropic('claude-fable-5'),
messages: [{ role: 'user', content: 'What shipped in AI this week?' }],
tools: { web_search: anthropicWebSearch({ max_uses: 5 }) },
});Defaults to web_search_20260318. On 20260209+ versions allowed_callers defaults to code-execution (dynamic filtering); models without programmatic tool calling need anthropicWebSearch({ allowed_callers: ['direct'] }) — the API 400s otherwise. Provider tools never run locally and never break the agentic loop as client tools.
providerOptions escape hatch
Request-body fields the SDK does not model ride providerOptions.anthropic (top-level, shallow; canonical fields always win) — e.g. the server-side fallbacks beta:
streamChat({
model, messages,
headers: { 'anthropic-beta': 'server-side-fallback-2026-06-01' },
providerOptions: { anthropic: { fallbacks: [{ model: 'claude-opus-4-8' }] } },
});Structured output
generateObject selects a strategy from capabilities. On Anthropic it uses the native output_config JSON-schema mode by default. With extended thinking enabled it is forced into json mode, because Anthropic rejects a forced tool-choice when thinking is on.
import { generateObject } from '@deuz-sdk/core';
import { createAnthropic } from '@deuz-sdk/core/anthropic';
import { z } from 'zod';
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const { object } = await generateObject({
model: anthropic('claude-opus-4-8'),
messages: [{ role: 'user', content: 'Capital of France?' }],
schema: z.object({ city: z.string() }),
});
console.log(object.city); // "Paris"The /v1/messages wire
Requests go to ${baseURL}/v1/messages with x-api-key, anthropic-version: 2023-06-01, and content-type: application/json. The adapter normalizes everything to the canonical delta stream — it never proxies raw provider bytes.
Notable mappings:
- The leading system messages become the top-level
systemfield; on Claude 5.x a later system message rides in place as arole: 'system'message (older models fold it in), and a tool-set change astool_removal/tool_additionblocks (betasmid-conversation-tool-changes-2026-07-01,inline-tools-2026-09-15). toolrole messages are sent asuserturns containingtool_resultblocks.reasoningparts keep their place in each content array and are serialized asthinking/redacted_thinkingblocks; only Claude's own signed blocks are sent (since 2.3).
Streaming tool-call accumulation
Claude emits a content_block_start for each tool_use block (carrying the tool id and name), then a sequence of input_json_delta events whose partial_json fragments are the argument JSON. The adapter slots each fragment by block index, emits canonical tool-call-delta parts as strings, and parses the accumulated JSON once at block end. Text streams via text_delta, thinking via thinking_delta, and the block signature via signature_delta; a redacted_thinking block arrives complete in its content_block_start. Each reasoning delta carries its block's index as its id, so consecutive blocks stay apart.
Claude on Vertex AI
The same Messages body works against Vertex AI: the model id moves into the URL, anthropic_version: vertex-2023-10-16 goes in the body, and auth becomes an OAuth Bearer token. This is wired through the Vertex provider — you do not configure it on createAnthropic directly.
Error mapping
Upstream errors are normalized to the typed error taxonomy from @deuz-sdk/core:
Anthropic error.type | Thrown error |
|---|---|
authentication_error | AuthenticationError (401) |
permission_error | AuthenticationError (403) |
not_found_error | ModelNotFoundError (404) |
rate_limit_error | RateLimitError (429) |
overloaded_error | OverloadedError (529) |
request_too_large | InvalidRequestError (413) |
invalid_request_error | InvalidRequestError |
api_error | APICallError (retryable) |
Retry-After is honored for pre-first-byte retries, and the upstream request-id is preserved on the error. Secrets are redacted from all logs and error payloads.