AnthropicHandler: parseo en streaming de la Anthropic Messages API
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
createMessagepara instanciarlo dentro deensureClient(ensureClient:44). Así, cuandobuildApiHandlerhace unnewanticipado del handler solo para limitar elthinkingBudget, 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 atext / reasoning / tool_calls / usage(for await chunk:184); el resto (message_stop) se descarta. - Ensamblado incremental de argumentos de herramienta: cuando llega
content_block_startse obtienentool_use.idyname, se guardan enlastStartedToolCall, y los sucesivosinput_json_deltavan emitiendopartial_jsonen 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,
temperaturedebe quedarundefined(temperature thinking:123); es una restricción oficial de Anthropic. - Posición del breakpoint de prompt cache:
cache_controlse 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:
createMessageestá 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 deeffort. El handler detecta el model id y toma distintas ramas (adaptive thinking:100).
Archivos clave
AnthropicHandler class:36— implementaApiHandler, solo guardaoptionsy unclientperezoso.ensureClient:44— construyenew Anthropic(...)recién en la primera petición, con baseURL y headers personalizados.createMessage with @withRetry:63— entrada principal del flujo, generadorasync *.useFastMode detection:70— detecta el sufijo:fasten el model id y conmuta a la API beta.budget_tokens:93— lee el thinking budget de options; 0 desactiva thinking.thinkingConfig:107— los modelos adaptive usan{type:"adaptive"}, el resto{type:"enabled", budget_tokens}.supportsPromptCache branch:114— con cache, ponecache_controlal system; sin cache, toma la rama else.stream loop:183— elfor await chunk of streamtraduce eventos nativos a chunks unificados.tool_use block start:227— obtiene tool_use id/name y los guarda enlastStartedToolCall.getModel:305— busca ModelInfo en la tablaanthropicModels; si no lo encuentra, cae aanthropicDefaultModelId.
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:
// 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:
// 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:
ensureClientlanza"Anthropic API key is required"cuandooptions.apiKeyestá 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, simodelIdno está en la tablaanthropicModels, se cae aanthropicDefaultModelId(fallback:311), evitando que la capa superior reviente. - 429 rate limit: el decorador
@withRetry()lee las cabecerasretry-after / x-ratelimit-resetpara 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_startllega conredacted_thinking, se emite unyieldcon un reasoningplaceholdery se devuelve intacto eldatacifrado (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_startrecibe un segundo bloque de texto (o posterior), primero haceyieldde 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
:fastcomo:1m, el array de headers beta incluye simultáneamentefast-mode-2026-02-01ycontext-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