Skip to content

AnthropicHandler : analyser en streaming l'Anthropic Messages API ​

源码版本v4.0.10

Responsabilités ​

AnthropicHandler est l'implémentation concrète de l'interface ApiHandler pour l'Anthropic Messages API, et également le handler de repli de la branche default dans buildApiHandler (default Anthropic:504). Il assemble systemPrompt / messages / tools reçus de la couche supérieure en une requête SDK Anthropic, puis, à mesure qu'il consomme le flux, traduit les BetaRawMessageStreamEvent natifs en ApiStreamChunk unifié pour Cline (ApiStreamChunk:3), de sorte que la Task au-dessus n'a pas à connaître les détails du protocole Anthropic.

Il prend également en charge trois choses spécifiques à Anthropic : d'abord le prompt cache, en posant un point de rupture cache_control: ephemeral à la fin du system prompt (cache_control:128) ; ensuite l'extended thinking, en décidant selon que le modèle supporte le reasoning d'inclure ou non le champ thinking, et de fixer budget_tokens (thinkingConfig:107) ; enfin les deux suffixes beta fast mode et 1m context, identifiés via les marqueurs :fast / :1m en fin d'id de modèle, qui basculent vers client.beta.messages.create avec les headers beta (fast mode detection:70).

Motivation de conception ​

  • client lazy-loaded : à la construction, le handler ne crée pas le client SDK Anthropic ; il attend le premier createMessage pour le construire dans ensureClient (ensureClient:44). Ainsi, buildApiHandler peut instancier un handler en avance pour borner thinkingBudget sans déclencher de requête réelle.
  • traduction des chunks de flux : les événements natifs du flux Anthropic sont au nombre de six — message_start / message_delta / content_block_start / content_block_delta / content_block_stop / message_stop — ; Cline ne s'intéresse qu'à ceux qui se mappent en text / reasoning / tool_calls / usage (for await chunk:184), les autres (message_stop) sont simplement ignorés.
  • assemblage incrémental des paramètres d'appel d'outil : content_block_start récupère tool_use.id et name, les stocke dans lastStartedToolCall, puis les input_json_delta suivants yieldent un par un le partial_json (input_json_delta:276), la couche supérieure étant responsable de réassembler le JSON complet.
  • thinking et temperature mutuellement exclusifs : quand thinking est activé, temperature doit être mis à undefined (temperature thinking:123), selon une contrainte officielle d'Anthropic.
  • position du point de rupture du prompt cache : cache_control est posé à la fin du system plutôt qu'à la fin des tools, car tools ne change pas mais le contenu du system est volumineux — placer le point de rupture en fin de system maximise le taux de hit (system cache_control:124).
  • décorateur de retry : createMessage porte un @withRetry() ; seuls 429 et RetriableError sont retentés, par défaut trois essais avec backoff exponentiel (withRetry:63).
  • adaptive thinking : Claude Opus 4.5+ n'utilise plus l'extended thinking budgétisé, mais passe à type: "adaptive" avec un champ effort optionnel ; le handler détecte l'id de modèle et emprunte des branches différentes (adaptive thinking:100).

Fichiers clés ​

Flux de données ​

À l'appel, le handler décide d'abord entre la branche prompt-cache et la branche ordinaire, chacune construisant un requestBody différent, puis appelle client.messages.create ou client.beta.messages.create pour obtenir le flux :

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 */)
}

Le point clé de cette branche est que cache_control est placé sur le dernier élément du tableau system (cache_control:128). Anthropic met en cache tout le préfixe jusqu'à ce point de rupture ; toute requête ultérieure partageant ce préfixe pourra lire directement le cache, réduisant notablement le coût en tokens d'entrée du system prompt. tool_choice vaut {type:"any"} quand thinking est désactivé (tool_choice any:140), signifiant « si des outils sont présents, l'un doit être appelé » ; mais avec thinking activé, ce forçage déclenche une erreur d'incompatibilité côté Anthropic, donc on le laisse undefined et le modèle décide lui-même.

Une fois le flux obtenu, on entre dans la boucle for await qui traduit les chunks. Ci-dessous, le cœur de la traduction pour 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 ressort d'un coup avec les quatre champs usage input_tokens / output_tokens / cache_creation / cache_read (message_start:186) ; ensuite message_delta donne incrémentalement les output_tokens. La Task utilise ces champs pour calculer le coût et l'occupation du contexte. Les paramètres JSON d'un appel d'outil sont yieldés en fragments de chaîne partial_json, et c'est parseAssistantMessageV2 en amont qui les réassemble avant JSON.parse.

Limites et échecs ​

  • clé API absente : ensureClient lève directement "Anthropic API key is required" si options.apiKey est vide (apiKey required:46). L'erreur remonte jusqu'à la Task pour affichage.
  • échec de construction du SDK : si new Anthropic(...) lève, l'erreur est rewrappée en "Error creating Anthropic client: ..." puis propagée (client wrap:56).
  • id de modèle inconnu : getModel replie sur anthropicDefaultModelId lorsque modelId n'est pas dans la table anthropicModels (fallback:311), sans faire planter la couche supérieure.
  • 429 rate limit : le décorateur @withRetry() lit les headers retry-after / x-ratelimit-reset pour calculer le délai ; trois essais par défaut, et au troisième échec l'erreur originale est propagée (withRetry:29).
  • blocs redacted thinking : quand content_block_start reçoit redacted_thinking, le handler yield un reasoning placeholder et renvoie tel quel le data chiffré (redacted_thinking:219), car les requêtes suivantes attendent à Anthropic ce bloc renvoyé à l'identique.
  • saut de ligne entre plusieurs blocs text : content_block_start yield un \n avant tout deuxième bloc text ou ultérieur (text block newline:237), pour éviter que les blocs ne se touchent.
  • fast mode + 1m activés ensemble : si l'id de modèle porte à la fois :fast et :1m, le tableau de headers beta embarque fast-mode-2026-02-01 et context-1m-2025-08-07 simultanément (fastModeBetas:76).

Résumé ​

AnthropicHandler est l'implémentation de provider la plus « complète » de Cline : elle gère à la fois le point de rupture de prompt cache, l'extended thinking / adaptive thinking, les deux chemins beta fast mode et 1m context, ainsi que toutes les traductions des événements natifs du flux en ApiStreamChunk unifié. Une fois ce handler compris, OpenAI / Gemini / Bedrock et les autres handlers suivent tous la même recette — construire la requête, consommer le flux, traduire les chunks — seuls les champs du protocole changent. Pour la façon dont le system prompt consomme les champs comme cache_creation_input_tokens, voir la couche PromptBuilder.

  • Entrée de sélection des providers : /providers/api-handler
  • Comment le system prompt est assemblé : /prompts/prompt-builder
  • Comment les outils deviennent des Anthropic Tool : /prompts/toolset

Voir la documentation officielle : documentation Cline · README