buildApiHandler: selección de provider según el modelo
Responsabilidades
buildApiHandler es el punto de entrada en Cline que traduce «qué provider eligió el usuario en la configuración» a «una instancia de ApiHandler lista para usar». Task la llama una vez al instanciarse y obtiene un objeto con una superficie uniforme hacia fuera, pero con una implementación interna que cambia según el proveedor (buildApiHandler call:585). La interfaz ApiHandler es deliberadamente estrecha: solo expone createMessage (streaming), getModel (info del modelo), y dos métodos opcionales getApiStreamUsage y abort (ApiHandler interface:60), de modo que el Task superior no tiene que saber si detrás está Anthropic, OpenRouter, Bedrock u Ollama local.
Su otra responsabilidad es elegir la configuración según el «modo» (mode). Cline modela los modos plan y act como dos configuraciones de modelo independientes: buildApiHandler recibe el mode y, a partir de él, selecciona planModeApiProvider o actModeApiProvider; el id de modelo, el reasoning effort y el thinking budget también se leen por modo (buildApiHandler:517). Así, un mismo Task puede usar un modelo económico en la fase plan y cambiarse a uno más potente al pasar a act.
Motivación de diseño
- Interfaz única, implementaciones por proveedor:
ApiHandlersolo exponecreateMessage(streaming) ygetModel. Dentro de cada provider puede usarse el SDK que sea; el bucle superiorrecursivelyMakeClineRequestssolo consume elApiStream(ApiStream:1) y no se entera de las diferencias de protocolo. - Configuración dual plan/act: el modo plan se orienta a recolección de información y planeación, mientras que el modo act es el que realmente escribe archivos; cada uno puede colgarse de un modelo y un
thinkingBudgetdistintos, sin interferir (planModeApiProvider:520). - Un
switchgrande en vez de un registro:createHandlerForProvideres unswitchcon más de 30 ramas (createHandlerForProvider:83). Parece tosco, pero los campos que hay que pasarle a cada handler al construirlo difieren enormemente (Anthropic quiere baseUrl; Bedrock necesita un puñado de configuración AWS; Vertex requiereprojectId). Un registro genérico obligaría a escribir un adaptador para cada caso; mejor listarlo en limpio. - Clip de
thinkingBudgetcomo salvaguarda: antes de entrar al switch, se construye una vez un handler para leermodelInfo.maxTokensy recortarthinkingBudgetTokensamaxTokens - 1si hace falta (thinkingBudget clip:525). Así se evita que el usuario configure unthinking budgetpor encima del límite del modelo y la API devuelva error. - Rama
defaultque cae a Anthropic: siapiProviderno encaja en ningúncase, se cae aAnthropicHandler(default branch:504), de modo que las configuraciones antiguas o los ids de provider en migración no revientan directamente.
Archivos clave
ApiHandler interface:60— contrato con los cuatro métodoscreateMessage / getModel / getApiStreamUsage / abort.ApiHandlerModel:67— información del modelo que expone el handler; solo id y ModelInfo.ApiProviderInfo:72— paquete más completo para la capa de system prompt: lleva providerId, mode, customPrompt.createHandlerForProvider:83—switchque reparte el provider id al handler concreto.anthropic case:89— ramacase "anthropic"; lee apiKey / baseUrl / thinkingBudgetTokens de options.default Anthropic fallback:504— rama defallback; cualquier provider no reconocido cae a Anthropic.buildApiHandler:517— función de entrada pública; tras limitar elthinkingBudget, delega encreateHandlerForProvider.ApiStream:1—AsyncGenerator<ApiStreamChunk>; todos loscreateMessagede los handlers emiten este tipo unificado.ApiStreamChunk:3— cuatro tipos de chunk:text/reasoning/usage/tool_calls.LanguageModelChatSelector:4— selector vendor/family/version usado por la VSCode LM API.
Flujo de datos
El núcleo de buildApiHandler es «elegir provider según el modo → recortar thinkingBudget → delegar al switch que construye el handler». El código es directo:
// apps/vscode/src/core/api/index.ts
export function buildApiHandler(configuration: ApiConfiguration, mode: Mode): ApiHandler {
const { planModeApiProvider, actModeApiProvider, ...options } = configuration
const apiProvider = mode === "plan" ? planModeApiProvider : actModeApiProvider
// Validate thinking budget tokens against model's maxTokens to prevent API errors
try {
const thinkingBudgetTokens = mode === "plan" ? options.planModeThinkingBudgetTokens : options.actModeThinkingBudgetTokens
if (thinkingBudgetTokens && thinkingBudgetTokens > 0) {
const handler = createHandlerForProvider(apiProvider, options, mode)
const modelInfo = handler.getModel().info
if (modelInfo?.maxTokens && modelInfo.maxTokens > 0 && thinkingBudgetTokens > modelInfo.maxTokens) {
const clippedValue = modelInfo.maxTokens - 1
if (mode === "plan") {
options.planModeThinkingBudgetTokens = clippedValue
} else {
options.actModeThinkingBudgetTokens = clippedValue
}
} else {
return handler // don't rebuild unless its necessary
}
}
} catch (error) {
Logger.error("buildApiHandler error:", error)
}
return createHandlerForProvider(apiProvider, options, mode)
}Nótese el pequeño coste de «construir dos veces»: para leer modelInfo.maxTokens y acotar, se hace un primer new del handler (first construct:527); si thinkingBudget no excede el límite, se devuelve ese mismo, y si lo excede, se mutan las options y se construye una segunda vez. Implementaciones como AnthropicHandler tienen el cliente perezoso (ensureClient:44), de modo que la propia construcción no dispara peticiones y el coste es despreciable.
Task llama a esta función una vez en su constructor y guarda el resultado en this.api; todas las llamadas posteriores de attemptApiRequest obtienen el flujo a través de él (buildApiHandler call:585). El parámetro mode proviene de TaskParams; Task no cambia de handler internamente — cambiar de modo implica reconstruir el Task, no swapping del handler dentro del mismo Task.
Límites y fallos
- Provider id desconocido: el
switchllega adefaulty hace unnew AnthropicHandlersin lanzar error (default:504); la ejecución continúa hasta que la API de Anthropic devuelve 401. - Excepción en la construcción del handler: la primera construcción solo sirve para obtener
modelInfo; si falla, eltry/catchla traga (catch error:541) y se reintenta concreateHandlerForProvider. En esa segunda llamada, si se lanza, el error ya sube hasta Task. thinkingBudgetigual a 0: se omite todo el bloque de limitación y se entra directamente acreateHandlerForProvider(budget check:526), evitando una doble construcción innecesaria.- plan / act comparten el mismo
options:...optionsle entrega al handler tanto los campos planMode como actMode; el handler internamente selecciona los que le corresponden según el modo (destructure:518). Un handler de plan que reciba los campos de act no revienta, solo quedan como campos sobrantes en options. apiProviderundefined: cae aldefault(default:504), igual que un provider desconocido; hacefallbacka Anthropic.- Handler sin
abort: en la interfaz,abort?es opcional (abort optional:64); Task debe comprobarthis.api.abortantes de invocarlo durante una cancelación.
Resumen
buildApiHandler es simple por sí mismo: un switch más una capa de salvaguarda para recortar thinkingBudget. Su valor central es traducir «la cadena de provider del usuario» a «un objeto ApiHandler uniforme», liberando al Task superior de lidiar con las diferencias entre SDKs. Entendida esta capa, se puede bajar a un provider concreto para ver cómo emite chunks en streaming, o mirar la capa de system prompt para ver cómo se inyecta la información de modo en el prompt.
- Cómo implementa un provider su
createMessage:/providers/anthropic - Cómo el system prompt elige variante a partir de providerInfo:
/prompts/prompt-builder - Cómo influye el modo dual plan/act en la elección de handler:
/prompts/mode