Skip to content

AnthropicHandler: streaming the Anthropic Messages API

源码版本v4.0.10

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 ensureClient on the first createMessage (ensureClient:44), so that buildApiHandler instantiating 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 to text / reasoning / tool_calls / usage (for await chunk:184); others (message_stop) are skipped outright.
  • Tool-call incremental argument assembly: after content_block_start captures tool_use.id and name and stores them in lastStartedToolCall, subsequent input_json_delta events yield the partial_json fragments 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, temperature must be set to undefined (temperature thinking:123) — this is an Anthropic official constraint.
  • Prompt cache breakpoint position: cache_control is 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: createMessage is decorated with @withRetry(); only 429 / RetriableError are 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

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:

typescript
// 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:

typescript
// 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.apiKey is empty, ensureClient throws "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 modelId is not in the anthropicModels table, getModel falls back to anthropicDefaultModelId (fallback:311), so the upper layer does not crash.
  • 429 rate limit: the @withRetry() decorator reads the retry-after / x-ratelimit-reset headers 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_start receives redacted_thinking, it yields a placeholder reasoning and passes the encrypted data back 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_start receives 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 :fast and :1m, the beta header array carries both fast-mode-2026-02-01 and context-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

See official docs: Cline 文档 · README