Skip to content

AnthropicHandler: parseo en streaming de la Anthropic Messages API

源码版本v4.0.10

Responsabilidades

AnthropicHandler es la implementación concreta de la interfaz ApiHandler para la Anthropic Messages API, y también el handler por defecto (fallback) que buildApiHandler usa en la rama default (default Anthropic:504). Toma el systemPrompt / messages / tools que le pasa la capa superior, los empaqueta en una petición al SDK de Anthropic, y conforme recibe el flujo traduce los eventos nativos BetaRawMessageStreamEvent al tipo unificado de Cline ApiStreamChunk (ApiStreamChunk:3), de modo que el Task de arriba no tiene que conocer los detalles del protocolo de Anthropic.

Adicionalmente se encarga de tres cosas propias de Anthropic. Primera, prompt cache: pone un breakpoint cache_control: ephemeral al final del system prompt (cache_control:128). Segunda, extended thinking: según si el modelo soporta razonamiento, decide si incluir el campo thinking y cuántos budget_tokens asignar (thinkingConfig:107). Tercera, los sufijos beta fast mode y 1m context: se reconocen por los marcadores :fast / :1m al final del model id, y entonces se pasa a client.beta.messages.create con los headers beta correspondientes (fast mode detection:70).

Motivación de diseño

  • Cliente perezoso (lazy client): el constructor del handler no crea el cliente del SDK de Anthropic; espera al primer createMessage para instanciarlo dentro de ensureClient (ensureClient:44). Así, cuando buildApiHandler hace un new anticipado del handler solo para limitar el thinkingBudget, no se dispara ninguna petición real.
  • Traducción de chunks de flujo: los eventos nativos del streaming de Anthropic son seis: message_start / message_delta / content_block_start / content_block_delta / content_block_stop / message_stop. Cline solo se interesa por las partes que pueden mapearse a text / reasoning / tool_calls / usage (for await chunk:184); el resto (message_stop) se descarta.
  • Ensamblado incremental de argumentos de herramienta: cuando llega content_block_start se obtienen tool_use.id y name, se guardan en lastStartedToolCall, y los sucesivos input_json_delta van emitiendo partial_json en fragmentos (input_json_delta:276). La capa superior es la responsable de recomponer el JSON completo.
  • thinking y temperature mutuamente excluyentes: cuando thinking está activo, temperature debe quedar undefined (temperature thinking:123); es una restricción oficial de Anthropic.
  • Posición del breakpoint de prompt cache: cache_control se coloca al final del array de system, no al final de tools, porque tools no cambia pero el contenido de system es abundante; poner el breakpoint al final de system maximiza la tasa de aciertos (hit rate) (system cache_control:124).
  • Decorador de reintentos: createMessage está anotado con @withRetry(); solo reintenta en 429 / RetriableError, con backoff exponencial y un máximo por defecto de 3 intentos (withRetry:63).
  • Adaptive thinking: a partir de Claude Opus 4.5+ ya no se usa budgeted extended thinking; en su lugar se emplea type: "adaptive" con un campo opcional de effort. El handler detecta el model id y toma distintas ramas (adaptive thinking:100).

Archivos clave

Flujo de datos

Al recibir la llamada, el handler decide primero entre la rama con prompt-cache y la rama normal; ambas construyen un requestBody distinto y luego tiran del flujo vía client.messages.create o 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 */)
}

La clave de esta rama es que cache_control se coloca sobre el último elemento del array de system (cache_control:128). Anthropic cachea el prefijo hasta ese breakpoint; la próxima petición con el mismo prefijo acertará la caché y reducirá notablemente el costo en tokens de entrada del system prompt. tool_choice se fuerza a {type:"any"} cuando thinking está desactivado (tool_choice any:140), lo que significa «si hay herramientas, hay que usar alguna». Pero con thinking activado este tool_choice forzado provoca un error de incompatibilidad en Anthropic, así que en ese caso solo queda pasarlo como undefined y dejar que el modelo decida.

Una vez obtenido el flujo, se entra al for await que traduce los chunks. El siguiente fragmento es el núcleo de la traducción para 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 devuelve de una sola vez los cuatro campos de usage (input_tokens / output_tokens / cache_creation / cache_read) (message_start:186); después, message_delta irá entregando incrementos de output_tokens. Task usa estos campos para calcular costos y la ocupación del contexto. Los argumentos JSON de las llamadas a herramienta se emiten como fragmentos de cadena partial_json, uno tras otro; la capa parseAssistantMessageV2 se encarga de reconcatenarlos y hacer JSON.parse.

Límites y fallos

  • Falta API key: ensureClient lanza "Anthropic API key is required" cuando options.apiKey está vacío (apiKey required:46); el error burbujea hasta Task y se muestra al usuario.
  • Fallo al construir el SDK: si new Anthropic(...) lanza, se reenvuelve como "Error creating Anthropic client: ..." (client wrap:56).
  • Model id desconocido: en getModel, si modelId no está en la tabla anthropicModels, se cae a anthropicDefaultModelId (fallback:311), evitando que la capa superior reviente.
  • 429 rate limit: el decorador @withRetry() lee las cabeceras retry-after / x-ratelimit-reset para calcular el retardo; reintera hasta tres veces por defecto y, si la tercera falla, lanza el error original (withRetry:29).
  • Bloques thinking redacted: cuando content_block_start llega con redacted_thinking, se emite un yield con un reasoning placeholder y se devuelve intacto el data cifrado (redacted_thinking:219), porque Anthropic espera que en las peticiones siguientes se reenvíe tal cual.
  • Salto de línea entre bloques de texto múltiples: si content_block_start recibe un segundo bloque de texto (o posterior), primero hace yield de un \n (text block newline:237) para evitar que los bloques queden pegados.
  • fast mode + 1m activados a la vez: cuando el model id trae tanto :fast como :1m, el array de headers beta incluye simultáneamente fast-mode-2026-02-01 y context-1m-2025-08-07 (fastModeBetas:76).

Resumen

AnthropicHandler es la implementación de provider más «completa» de Cline: gestiona a la vez los breakpoints de prompt cache, el extended / adaptive thinking, las dos rutas beta (fast mode y 1m context) y todas las traducciones de eventos nativos a ApiStreamChunk. Una vez entendido, los demás handlers (OpenAI, Gemini, Bedrock, etc.) siguen esencialmente el mismo patrón — construir la petición, tirar del flujo, traducir los chunks — solo que con campos de protocolo distintos. Para ver cómo se consumen los cache_creation_input_tokens y demás campos del system prompt, consulta la capa PromptBuilder.

  • Entrada de selección de provider: /providers/api-handler
  • Cómo se ensambla el system prompt: /prompts/prompt-builder
  • Cómo las herramientas se convierten en definiciones Anthropic Tool: /prompts/toolset

Véase la documentación oficial: Cline 文档 · README