AnthropicHandler: Streaming-Parsing der Anthropic Messages API
Verantwortung
AnthropicHandler ist die konkrete Implementierung der ApiHandler-Schnittstelle für die Anthropic Messages API und zugleich der Fallback-Handler im default-Zweig von buildApiHandler (default Anthropic:504). Er setzt die vom Aufrufer übergebenen systemPrompt / messages / tools zu einer Anfrage an die Anthropic-SDK zusammen und übersetzt die nativen BetaRawMessageStreamEvent-Ereignisse des Streams während des Empfangs in Cline einheitliche ApiStreamChunk (ApiStreamChunk:3), sodass die obere Task-Schicht keine Anthropic-Protokolldetails kennen muss.
Zusätzlich übernimmt er drei Anthropic-spezifische Aufgaben: erstens prompt cache (Prompt-Zwischenspeicherung), indem am Ende des System-Prompts ein cache_control: ephemeral-Breakpoint gesetzt wird (cache_control:128); zweitens extended thinking, wobei je nach Modell-Unterstützung für Reasoning entschieden wird, ob das thinking-Feld gesetzt wird und wie hoch budget_tokens ausfällt (thinkingConfig:107); drittens die beiden Beta-Suffixe fast mode und 1m context, die über die Marker :fast bzw. :1m am Ende der Modell-ID erkannt und dann über client.beta.messages.create mit Beta-Header abgewickelt werden (fast mode detection:70).
Entwurfsmotivation
- Lazy-Initialisierung des Clients: Der Handler erzeugt beim Konstruieren noch keinen Anthropic-SDK-Client, sondern erst bei der ersten
createMessage-Anfrage inensureClient(ensureClient:44). So führt das vorzeitigeneweines Handlers durchbuildApiHandler– etwa um das thinkingBudget zu begrenzen – zu keiner echten Anfrage. - Chunk-Übersetzung im Stream: Native Anthropic-Stream-Ereignisse gibt es in sechs Varianten (
message_start / message_delta / content_block_start / content_block_delta / content_block_stop / message_stop), von denen Cline nur diejenigen interessieren, die sich auftext / reasoning / tool_calls / usageabbilden lassen (for await chunk:184); andere wiemessage_stopwerden direkt übersprungen. - Inkrementelles Zusammenbauen der Werkzeugargumente: Bei
content_block_startwerdentool_use.idundnameentnommen und inlastStartedToolCallabgelegt; spätereinput_json_delta-Fragmente liefern daspartial_jsonstückweise peryieldaus (input_json_delta:276). Die obere Schicht ist für das Zusammenfügen zum vollständigen JSON verantwortlich. - thinking und temperature schließen sich aus: Wenn thinking aktiviert ist, muss
temperatureaufundefinedgesetzt werden (temperature thinking:123) – eine offizielle Einschränkung von Anthropic. - Position des Prompt-Cache-Breakpoints:
cache_controlwird am Ende des system-Arrays gesetzt statt am Ende von tools, da tools unverändert bleiben, der system-Inhalt aber umfangreich ist – am Ende von system ist die Trefferquote am höchsten (system cache_control:124). - Retry-Dekorator: An
createMessageist@withRetry()angebracht; nur bei 429 oderRetriableErrorwird erneut versucht, standardmäßig dreimal mit exponentiellem Backoff (withRetry:63). - Adaptive Thinking: Claude Opus 4.5+ verwendet kein budgeted extended thinking mehr, sondern
type: "adaptive"mit optionalem effort-Feld. Der Handler erkennt die Modell-ID und wählt den passenden Zweig (adaptive thinking:100).
Schlüsseldateien
AnthropicHandler class:36— implementiertApiHandler, hält nuroptionsund einen lazy initialisiertenclient.ensureClient:44— erstellt erst bei der ersten Anfragenew Anthropic(...)mit baseURL und benutzerdefinierten Headern.createMessage with @withRetry:63— Haupteinstieg des Streams,async *-Generator.useFastMode detection:70— wechselt bei:fastam Ende der Modell-ID auf die Beta-API.budget_tokens:93— liest das thinking budget aus options; 0 deaktiviert thinking.thinkingConfig:107— adaptive Modelle verwenden{type:"adaptive"}, andere{type:"enabled", budget_tokens}.supportsPromptCache branch:114— bei vorhandenem Cache bekommt system eincache_control; ohne Cache läuft der else-Zweig.stream loop:183—for await chunk of streamübersetzt native Ereignisse in einheitliche Chunks.tool_use block start:227— übernimmt tool_use id/name inlastStartedToolCall.getModel:305— schlägt ModelInfo in deranthropicModels-Tabelle nach; bei einem Fehltreffer Rückfall aufanthropicDefaultModelId.
Datenfluss
Der Handler entscheidet nach dem Aufruf zunächst, ob der Prompt-Cache-Zweig oder der normale Zweig genommen wird. Beide Pfade bauen ein unterschiedliches requestBody auf und ziehen den Stream über client.messages.create bzw. 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 */)
}Der Schlüssel dieses Zweigs liegt darin, dass cache_control auf dem letzten Element des system-Arrays gesetzt wird (cache_control:128). Anthropic speichert den Präfix bis zu diesem Breakpoint zwischen; eine folgende Anfrage mit demselben Präfix trifft direkt den Cache und liest aus ihm, was die Input-Token-Kosten für den System-Prompt spürbar senkt. tool_choice wird bei deaktiviertem thinking auf {type:"any"} gesetzt (tool_choice any:140) – Bedeutung: „Werkzeuge vorhanden, also verwenden". Bei aktiviertem thinking löst diese Erzwingung einen Inkompatibilitätsfehler von Anthropic aus, weshalb es dann undefined bleiben muss und das Modell selbst entscheidet.
Nachdem der Stream vorliegt, erfolgt die Chunk-Übersetzung in einer for await-Schleife. Der folgende Abschnitt zeigt die Kernlogik für text und 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 liefert in einem Rutsch die vier Usage-Felder input_tokens / output_tokens / cache_creation / cache_read (message_start:186); spätere message_delta liefern inkrementell weitere output_tokens. Task nutzt diese Felder zur Kostenberechnung und zur Abschätzung der Kontextbelegung. Die JSON-Parameter eines Werkzeugaufrufs werden als partial_json-String-Fragmente stückweise herausgegeben; die obere Schicht parseAssistantMessageV2 setzt die Fragmente selbst zusammen und führt dann JSON.parse aus.
Grenzen und Fehler
- Fehlender API-Key:
ensureClientwirft direkt"Anthropic API key is required", wennoptions.apiKeyleer ist (apiKey required:46). Dieser Fehler bläst bis zur Task-Schicht durch und wird dort angezeigt. - Fehler beim SDK-Konstruktor: Ein Fehler aus
new Anthropic(...)wird als"Error creating Anthropic client: ..."neu verpackt und erneut geworfen (client wrap:56). - Unbekannte Modell-ID: Ist
modelIdnicht in deranthropicModels-Tabelle enthalten, fälltgetModelaufanthropicDefaultModelIdzurück (fallback:311), sodass die obere Schicht nicht abstürzt. - 429-Ratenlimit: Der
@withRetry()-Dekorator liest die Headerretry-after / x-ratelimit-resetzur Verzögerung; drei Versuche, danach wird der ursprüngliche Fehler geworfen (withRetry:29). - Redacted-Thinking-Blöcke: Erhält
content_block_starteinredacted_thinking, wird ein Placehalter-Reasoning per yield geliefert und die verschlüsseltedataunverändert zurückgegeben (redacted_thinking:219), da Anthropic bei Folgerequests den Wert unverändert erwartet. - Zeilenumbruch zwischen mehreren Text-Blöcken: Bei
content_block_startfür den zweiten oder einen späteren Text-Block wird zuerst ein\nper yield geliefert (text block newline:237), damit die Blöcke nicht aneinanderkleben. - fast mode + 1m gleichzeitig: Enthält die Modell-ID sowohl
:fastals auch:1m, werden im Beta-Header-Array gleichzeitigfast-mode-2026-02-01undcontext-1m-2025-08-07gesetzt (fastModeBetas:76).
Zusammenfassung
AnthropicHandler ist die am besten ausgebaute Provider-Implementierung in Cline: Sie verwaltet gleichzeitig Prompt-Cache-Breakpoints, extended thinking / adaptive thinking, die beiden Beta-Pfade fast mode und 1m context sowie die vollständige Übersetzung nativer Stream-Ereignisse in einheitliche ApiStreamChunk. Wer diese verstanden hat, findet in OpenAI / Gemini / Bedrock und den anderen Handlern stets dasselbe Muster – Anfrage aufbauen, Stream ziehen, Chunks übersetzen – lediglich mit abweichenden Protokollfeldern. Wie der System-Prompt Felder wie cache_creation_input_tokens konsumiert, ist in der PromptBuilder-Schicht beschrieben.
- Einstieg zur Provider-Auswahl:
/providers/api-handler - Aufbau des System-Prompts:
/prompts/prompt-builder - Übersetzung der Werkzeuge in Anthropic-Tool-Definitionen:
/prompts/toolset
Siehe offizielle Dokumentation: Cline-Dokumentation · README