Skip to content

buildApiHandler: selección de provider según el modelo

源码版本v4.0.10

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: ApiHandler solo expone createMessage (streaming) y getModel. Dentro de cada provider puede usarse el SDK que sea; el bucle superior recursivelyMakeClineRequests solo consume el ApiStream (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 thinkingBudget distintos, sin interferir (planModeApiProvider:520).
  • Un switch grande en vez de un registro: createHandlerForProvider es un switch con 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 requiere projectId). Un registro genérico obligaría a escribir un adaptador para cada caso; mejor listarlo en limpio.
  • Clip de thinkingBudget como salvaguarda: antes de entrar al switch, se construye una vez un handler para leer modelInfo.maxTokens y recortar thinkingBudgetTokens a maxTokens - 1 si hace falta (thinkingBudget clip:525). Así se evita que el usuario configure un thinking budget por encima del límite del modelo y la API devuelva error.
  • Rama default que cae a Anthropic: si apiProvider no encaja en ningún case, se cae a AnthropicHandler (default branch:504), de modo que las configuraciones antiguas o los ids de provider en migración no revientan directamente.

Archivos clave

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:

typescript
// 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 switch llega a default y hace un new AnthropicHandler sin 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, el try/catch la traga (catch error:541) y se reintenta con createHandlerForProvider. En esa segunda llamada, si se lanza, el error ya sube hasta Task.
  • thinkingBudget igual a 0: se omite todo el bloque de limitación y se entra directamente a createHandlerForProvider (budget check:526), evitando una doble construcción innecesaria.
  • plan / act comparten el mismo options: ...options le 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.
  • apiProvider undefined: cae al default (default:504), igual que un provider desconocido; hace fallback a Anthropic.
  • Handler sin abort: en la interfaz, abort? es opcional (abort optional:64); Task debe comprobar this.api.abort antes 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

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