Skip to content

AnthropicHandler:Anthropic Messages API のストリーミング解析

源码版本v4.0.10

役割

AnthropicHandlerApiHandler インターフェースの Anthropic Messages API 向け具象実装であり、buildApiHandlerdefault 分岐でのフォールバック handler でもある(default Anthropic:504). 上層から渡された systemPrompt / messages / tools を Anthropic SDK のリクエストに組み立て、ストリームを受け取りながら原生の BetaRawMessageStreamEvent を Cline 統一の ApiStreamChunk に翻訳する(ApiStreamChunk:3)。これにより上層の Task は Anthropic プロトコルの詳細を意識しなくて済む。

さらに Anthropic 特有の三つの仕事を担う:第一に prompt cache で、システム提示末尾に cache_control: ephemeral ブレイクポイントを打つ(cache_control:128)。第二に extended thinking で、モデルが reasoning をサポートするかどうかで thinking フィールドの有無や budget_tokens の大きさを決める(thinkingConfig:107)。第三に fast mode と 1m context という二つの beta サフィックスで、モデル id 末尾の :fast / :1m マーカーで識別し、client.beta.messages.create に beta header を付けたリクエストパスに切り替える(fast mode detection:70)。

設計動機

  • client の遅延ロード:handler のコンストラクタは Anthropic SDK クライアントを作らず、最初の createMessage で初めて ensureClient の中で作る(ensureClient:44)。こうしておけば buildApiHandler が thinkingBudget の上限クリップのために事前 new しても実際のリクエストは発火しない。
  • ストリーミング chunk の翻訳:Anthropic 原生のストリームイベントは message_start / message_delta / content_block_start / content_block_delta / content_block_stop / message_stop の六種類。Cline はそのうち text / reasoning / tool_calls / usage にマッピングできる部分だけを気にし(for await chunk:184)、それ以外(message_stop)は skip する。
  • ツール呼び出しの差分結合パラメータ:content_block_starttool_use.idname を取得し lastStartedToolCall に保存、その後の input_json_deltapartial_json を一段ずつ yield する(input_json_delta:276)。上層が完全な JSON に結合する責任を持つ。
  • thinking と temperature の排他:thinking 有効時は temperature を必ず undefined にする(temperature thinking:123)。これは Anthropic 公式の制約である。
  • prompt cache ブレイクポイントの位置:cache_control は tools 末尾ではなく system 末尾に打つ。tools は不変だが system の内容が多いため、ブレイクポイントを system 末尾に置くと命中率が最も高くなる(system cache_control:124)。
  • リトライデコレータ:createMessage@withRetry() を付け、429 / RetriableError のみリトライし、デフォルトで 3 回指数バックオフを行う(withRetry:63)。
  • adaptive thinking:Claude Opus 4.5+ はもう budgeted extended thinking を使わず、type: "adaptive" とオプションの effort フィールドに切り替える。handler はモデル id を検出して異なる分岐に進む(adaptive thinking:100).

主要ファイル

  • AnthropicHandler class:36ApiHandler を実装し、options と遅延ロードされる client のみを持つ。
  • ensureClient:44 — 最初のリクエスト時に初めて new Anthropic(...) を作り、baseURL とカスタム headers を渡す。
  • createMessage with @withRetry:63 — ストリーミングの主入口。async * ジェネレータ。
  • useFastMode detection:70 — モデル id 末尾の :fast で beta API に切り替え。
  • budget_tokens:93 — options から thinking budget を取得。0 は thinking 無効を示す。
  • thinkingConfig:107 — adaptive モデルは {type:"adaptive"}、それ以外は {type:"enabled", budget_tokens} に進む。
  • supportsPromptCache branch:114 — cache がある時は system に cache_control を打ち、無い時は else 分岐に進む。
  • stream loop:183for await chunk of stream で原生イベントを統一 chunk に翻訳する。
  • tool_use block start:227 — tool_use id/name を取得し lastStartedToolCall に保存。
  • getModel:305anthropicModels 表から ModelInfo を検索。見つからない場合は anthropicDefaultModelId にフォールバック。

データフロー

handler は呼び出しを受けると、まず prompt-cache 分岐か通常分岐かを決める。二つのパスは異なる requestBody を構築し、その後 client.messages.create または 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 */)
}

この分岐のキーは cache_control を system 配列の最後の要素に打つこと(cache_control:128)。Anthropic はこのブレイクポイントまでのプレフィックスをキャッシュし、次に同じプレフィックスのリクエストが来たら直接キャッシュがヒットし、システム提示の入力 token コストを大幅に下げる。tool_choice は thinking が無効の時に {type:"any"} を強制し(tool_choice any:140)、「ツールがあるなら必ず使う」という意味になる。ただし thinking 有効時にこの強制をかけると Anthropic の非互換エラーを引き起こすため、その時は undefined にしてモデルに判断を任せるしかない。

ストリームを取得したら for await で chunk を翻訳する。次は 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_startinput_tokens / output_tokens / cache_creation / cache_read の四つの usage フィールドを一度に持ってくる(message_start:186)。その後 message_deltaoutput_tokens を差分で与え、Task はこれらのフィールドでコストとコンテキスト占有を計算する。ツール呼び出しの JSON パラメータは partial_json 文字列断片として一段ずつ yield され、上層の parseAssistantMessageV2 が断片を結合してから JSON.parse する責任を持つ。

境界と失敗

  • API key 欠落:ensureClientoptions.apiKey が空の場合、直接 "Anthropic API key is required" を投げる(apiKey required:46)。このエラーは Task 層まで伝播して表示される。
  • SDK 構築失敗:new Anthropic(...) が投げたエラーは "Error creating Anthropic client: ..." に再包装されてから投げ直される(client wrap:56).
  • 未知のモデル id:getModelmodelIdanthropicModels 表に無い時、anthropicDefaultModelId にフォールバックする(fallback:311)。上層をクラッシュさせない。
  • 429 レート制限:@withRetry() デコレータは retry-after / x-ratelimit-reset ヘッダーを読んで遅延を計算する。デフォルトで 3 回リトライし、3 回目の失敗で元のエラーを投げる(withRetry:29).
  • redacted thinking ブロック:content_block_startredacted_thinking を受け取った時、placeholder reasoning を一つ yield し、暗号化された data を原のまま持ち帰る(redacted_thinking:219)。後続のリクエストで Anthropic は原のままの再送を期待するためである。
  • 複数 text block 間の改行挿入:content_block_start が二個目以降の text block を受け取った時、先に \n を一つ yield する(text block newline:237)。block 同士がくっつくのを避けるためである。
  • fast mode + 1m の同時有効:モデル id が同時に :fast:1m を持つ時、beta header 配列に fast-mode-2026-02-01context-1m-2025-08-07 を同時に付ける(fastModeBetas:76).

まとめ

AnthropicHandler は Cline の中で最も「全部入り」の provider 実装である:prompt cache ブレイクポイント、extended thinking / adaptive thinking、fast mode と 1m context の二つの beta パス、そして原生ストリームイベントの統一 ApiStreamChunk への翻訳、これらすべてのマッピングを同時に管る。これを理解してから OpenAI / Gemini / Bedrock など他の handler を見ると、基本的に同じパターン —— リクエスト構築、ストリーム取得、chunk 翻訳 —— であり、ただ各社のプロトコルフィールドが異なるだけだと分かる。システム提示が cache_creation_input_tokens などのフィールドをどう消費するかは、PromptBuilder の層を見よ。

  • プロバイダ選択の入口:/providers/api-handler
  • システム提示の組み立て方:/prompts/prompt-builder
  • ツールが Anthropic Tool 定義に変換される仕組み:/prompts/toolset

公式資料:Cline 文档 · README