AnthropicHandler: streaming the Anthropic Messages API
Responsibilities
AnthropicHandler is the concrete implementation of the ApiHandler interface for the Anthropic Messages API, and also the fallback handler in the default branch of buildApiHandler (default Anthropic:504). It assembles the systemPrompt / messages / tools passed from above into an Anthropic SDK request, then while receiving the stream it translates the native BetaRawMessageStreamEvent into Cline's unified ApiStreamChunk (ApiStreamChunk:3), so the upper Task does not need to be aware of Anthropic protocol details.
It additionally handles three Anthropic-specific things. First, prompt cache: it places a cache_control: ephemeral breakpoint at the end of the system prompt (cache_control:128). Second, extended thinking: depending on whether the model supports reasoning, it decides whether to include the thinking field and how many budget_tokens to allocate (thinkingConfig:107). Third, the two beta suffixes fast mode and 1m context are recognized via the :fast / :1m markers at the end of the model id, and then routed to client.beta.messages.create with beta headers (fast mode detection:70).
Design motivation
- Lazy-loaded client: the handler does not create an Anthropic SDK client in its constructor; it only creates one inside
ensureClienton the firstcreateMessage(ensureClient:44), so thatbuildApiHandlerinstantiating a handler ahead of time to clip thinkingBudget does not actually fire a request. - Stream chunk translation: Anthropic's native stream events come in six kinds —
message_start / message_delta / content_block_start / content_block_delta / content_block_stop / message_stop— and Cline only cares about the parts that can map totext / reasoning / tool_calls / usage(for await chunk:184); others (message_stop) are skipped outright. - Tool-call incremental argument assembly: after
content_block_startcapturestool_use.idandnameand stores them inlastStartedToolCall, subsequentinput_json_deltaeventsyieldthepartial_jsonfragments one by one (input_json_delta:276); the upper layer is responsible for assembling them into complete JSON. - thinking and temperature are mutually exclusive: when thinking is on,
temperaturemust be set toundefined(temperature thinking:123) — this is an Anthropic official constraint. - Prompt cache breakpoint position:
cache_controlis placed at the end of system rather than at the end of tools, because tools are invariant but system content is large; placing the breakpoint at the end of system yields the highest hit rate (system cache_control:124). - Retry decorator:
createMessageis decorated with@withRetry(); only 429 /RetriableErrorare retried, with 3 retries by default and exponential backoff (withRetry:63). - adaptive thinking: Claude Opus 4.5+ no longer uses budgeted extended thinking; it switches to
type: "adaptive"with an optional effort field, and the handler detects the model id and takes a different branch (adaptive thinking:100).
Key files
AnthropicHandler class:36— implementsApiHandler; holds onlyoptionsand a lazily-loadedclient.ensureClient:44— only constructsnew Anthropic(...)on the first request, with baseURL and custom headers.createMessage with @withRetry:63— main streaming entry, anasync *generator.useFastMode detection:70— switches to the beta API via the trailing:faston the model id.budget_tokens:93— pulls the thinking budget from options; 0 disables thinking.thinkingConfig:107— adaptive models go through{type:"adaptive"}; others go through{type:"enabled", budget_tokens}.supportsPromptCache branch:114— when caching is supported, placescache_controlon system; otherwise takes the else branch.stream loop:183—for await chunk of streamtranslates native events into unified chunks.tool_use block start:227— captures tool_use id/name and stores it inlastStartedToolCall.getModel:305— looks up ModelInfo from theanthropicModelstable; falls back toanthropicDefaultModelIdif not found.
Data flow
After receiving a call, the handler first decides whether to take the prompt-cache branch or the plain branch. The two paths construct different requestBodys, then pull the stream via client.messages.create or client.beta.messages.create:
// apps/vscode/src/core/api/providers/anthropic.ts
if (model.info.supportsPromptCache) {
const anthropicMessages = sanitizeAnthropicMessages(messages, true)
const requestBody: AnthropicMessageCreateParamsStreaming & Record<string, unknown> = {
model: modelId,
thinking: thinkingConfig,
max_tokens: model.info.maxTokens || 8192,
temperature: isAdaptiveThinkingModel ? undefined : reasoningOn ? undefined : 0,
system: [
{
text: systemPrompt,
type: "text",
cache_control: { type: "ephemeral" },
},
], // setting cache breakpoint for system prompt so new tasks can reuse it
messages: anthropicMessages,
stream: true,
tools: nativeToolsOn ? tools : undefined,
tool_choice: nativeToolsOn && !thinkingEnabled ? { type: "any" } : undefined,
}
// ...
stream = useFastMode
? await createFastModeMessage(requestBody)
: await client.messages.create(requestBody, /* options */)
}The key to this branch is that cache_control is placed on the last item of the system array (cache_control:128). Anthropic caches the prefix up to that breakpoint, so the next request with the same prefix can hit the read cache directly, significantly reducing the input token cost of the system prompt. tool_choice is forced to {type:"any"} when thinking is off (tool_choice any:140), meaning "if there are tools, they must be used"; but when thinking is on, this constraint would trigger an Anthropic incompatibility error, so at that point it can only be undefined and the model decides for itself.
After the stream is obtained it enters for await to translate chunks. The following snippet is the core translation logic for text / reasoning:
// apps/vscode/src/core/api/providers/anthropic.ts
for await (const chunk of stream) {
switch (chunk?.type) {
case "message_start":
{
const usage = chunk.message.usage
yield {
type: "usage",
inputTokens: usage.input_tokens || 0,
outputTokens: usage.output_tokens || 0,
cacheWriteTokens: usage.cache_creation_input_tokens || undefined,
cacheReadTokens: usage.cache_read_input_tokens || undefined,
}
}
break
// ...
case "content_block_delta":
switch (chunk.delta.type) {
case "text_delta":
yield { type: "text", text: chunk.delta.text }
break
case "input_json_delta":
if (lastStartedToolCall.id && lastStartedToolCall.name && chunk.delta.partial_json) {
yield {
type: "tool_calls",
tool_call: {
...lastStartedToolCall,
function: { ...lastStartedToolCall, id: lastStartedToolCall.id, name: lastStartedToolCall.name, arguments: chunk.delta.partial_json },
},
}
}
break
}
break
}
}message_start carries the four usage fields input_tokens / output_tokens / cache_creation / cache_read at once (message_start:186); subsequent message_delta events incrementally supply output_tokens. Task uses these fields to compute cost and context occupation. The JSON arguments of a tool call are yielded piece by piece as partial_json string fragments, and the upper layer parseAssistantMessageV2 is responsible for assembling the fragments and then calling JSON.parse.
Boundaries and failure modes
- Missing API key: when
options.apiKeyis empty,ensureClientthrows"Anthropic API key is required"directly (apiKey required:46); this error bubbles up to the Task layer for display. - SDK construction failure: errors thrown by
new Anthropic(...)are rewrapped as"Error creating Anthropic client: ..."and rethrown (client wrap:56). - Unknown model id: when
modelIdis not in theanthropicModelstable,getModelfalls back toanthropicDefaultModelId(fallback:311), so the upper layer does not crash. - 429 rate limit: the
@withRetry()decorator reads theretry-after / x-ratelimit-resetheaders to compute the delay, retries 3 times by default, and on the third failure rethrows the original error (withRetry:29). - redacted thinking block: when
content_block_startreceivesredacted_thinking, it yields a placeholder reasoning and passes the encrypteddataback as-is (redacted_thinking:219), because subsequent requests expect Anthropic to receive it back as-is. - Insert newline between multiple text blocks: when
content_block_startreceives the second or later text block, it first yields a\n(text block newline:237), to avoid blocks being glued together. - fast mode + 1m combined: when the model id carries both
:fastand:1m, the beta header array carries bothfast-mode-2026-02-01andcontext-1m-2025-08-07(fastModeBetas:76).
Summary
AnthropicHandler is the most "full-featured" provider implementation in Cline: it manages prompt cache breakpoints, extended thinking / adaptive thinking, the two beta paths fast mode and 1m context, and all the mappings that translate native stream events into unified ApiStreamChunk. Once you understand it, the other handlers — OpenAI / Gemini / Bedrock and so on — follow the same pattern: build the request, pull the stream, translate chunks; only the protocol field names differ across vendors. For how the system prompt consumes fields like cache_creation_input_tokens, see the PromptBuilder layer.
- Provider selection entry:
/providers/api-handler - How the system prompt is assembled:
/prompts/prompt-builder - How tools become Anthropic Tool definitions:
/prompts/toolset