AnthropicHandler:Anthropic Messages API のストリーミング解析
役割
AnthropicHandler は ApiHandler インターフェースの Anthropic Messages API 向け具象実装であり、buildApiHandler の default 分岐でのフォールバック 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_startでtool_use.idとnameを取得しlastStartedToolCallに保存、その後のinput_json_deltaでpartial_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:36—ApiHandlerを実装し、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:183—for await chunk of streamで原生イベントを統一 chunk に翻訳する。tool_use block start:227— tool_use id/name を取得しlastStartedToolCallに保存。getModel:305—anthropicModels表から ModelInfo を検索。見つからない場合はanthropicDefaultModelIdにフォールバック。
データフロー
handler は呼び出しを受けると、まず prompt-cache 分岐か通常分岐かを決める。二つのパスは異なる requestBody を構築し、その後 client.messages.create または 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 */)
}この分岐のキーは 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 の中核的な翻訳ロジック:
// 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 は input_tokens / output_tokens / cache_creation / cache_read の四つの usage フィールドを一度に持ってくる(message_start:186)。その後 message_delta が output_tokens を差分で与え、Task はこれらのフィールドでコストとコンテキスト占有を計算する。ツール呼び出しの JSON パラメータは partial_json 文字列断片として一段ずつ yield され、上層の parseAssistantMessageV2 が断片を結合してから JSON.parse する責任を持つ。
境界と失敗
- API key 欠落:
ensureClientはoptions.apiKeyが空の場合、直接"Anthropic API key is required"を投げる(apiKey required:46)。このエラーは Task 層まで伝播して表示される。 - SDK 構築失敗:
new Anthropic(...)が投げたエラーは"Error creating Anthropic client: ..."に再包装されてから投げ直される(client wrap:56). - 未知のモデル id:
getModelはmodelIdがanthropicModels表に無い時、anthropicDefaultModelIdにフォールバックする(fallback:311)。上層をクラッシュさせない。 - 429 レート制限:
@withRetry()デコレータはretry-after / x-ratelimit-resetヘッダーを読んで遅延を計算する。デフォルトで 3 回リトライし、3 回目の失敗で元のエラーを投げる(withRetry:29). - redacted thinking ブロック:
content_block_startがredacted_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-01とcontext-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