Skip to content

buildApiHandler : choisir un provider selon le modèle

源码版本v4.0.10

Responsabilités

buildApiHandler est le point d'entrée par lequel Cline traduit « le provider choisi par l'utilisateur dans les réglages » en « une instance d'ApiHandler prête à l'emploi ». La Task l'appelle une fois à l'instanciation et récupère un objet au interface unifiée mais à l'implémentation interne variable selon le provider (buildApiHandler call:585). L'interface ApiHandler est volontairement étroite : createMessage pour lancer une requête en streaming, getModel pour interroger les infos du modèle, plus les deux méthodes optionnelles getApiStreamUsage et abort — seulement quatre méthodes (ApiHandler interface:60). La Task au-dessus n'a donc pas à se soucier de savoir si derrière se trouve Anthropic, OpenRouter, Bedrock ou un Ollama local.

Son autre responsabilité est de sélectionner la configuration selon le mode. Cline modélise les deux modes plan / act (modes) comme deux configurations de modèle distinctes ; buildApiHandler reçoit le mode, puis choisit entre planModeApiProvider et actModeApiProvider, l'id de modèle, le reasoning effort et le thinking budget étant également sélectionnés par mode (buildApiHandler:517). Ainsi, une Task peut utiliser un modèle économique pendant la phase plan, puis basculer sur un modèle plus puissant lors du passage en act.

Motivation de conception

  • Interface unifiée, implémentations distinctes : ApiHandler n'expose que l'interface de streaming createMessage et getModel. À l'intérieur du provider, libre d'utiliser le SDK que l'on veut ; la boucle supérieure recursivelyMakeClineRequests ne consomme que ApiStream (ApiStream:1), indépendamment des différences de protocole.
  • Deux configurations plan/act : le mode plan privilégie la collecte d'informations et la planification, le mode act agit sur les fichiers ; chacun peut embarquer modèles et thinkingBudget distincts, sans interférence (planModeApiProvider:520).
  • Un grand switch plutôt qu'un registre : createHandlerForProvider est un switch de plus de 30 branches (createHandlerForProvider:83). Cela peut sembler rustique, mais comme les champs à passer à chaque handler diffèrent fortement (Anthropic attend baseUrl, Bedrock toute une configuration AWS, Vertex un projectId), un registre générique imposerait d'écrire beaucoup d'adaptateurs — autant tout lister explicitement.
  • Plafond de thinkingBudget en filet de sécurité : avant d'entrer dans le switch, on instancie une fois un handler pour récupérer modelInfo.maxTokens et borner thinkingBudgetTokens à maxTokens - 1 (thinkingBudget clip:525), évitant qu'un thinking budget trop élevé fasse rejeter la requête par l'API.
  • Branche default repli sur Anthropic : si apiProvider ne correspond à aucun case, on retombe sur AnthropicHandler (default branch:504), garantissant qu'un id de provider ancien ou en cours de migration ne déclenche pas une erreur immédiate.

Fichiers clés

  • ApiHandler interface:60 — contrat des quatre méthodes createMessage / getModel / getApiStreamUsage / abort.
  • ApiHandlerModel:67 — infos modèle exposées par le handler : seulement id et ModelInfo.
  • ApiProviderInfo:72 — paquet plus complet pour la couche de system prompt, avec providerId, mode et customPrompt.
  • createHandlerForProvider:83 — switch de dispatch entre id de provider et handler concret.
  • anthropic case:89 — branche case "anthropic", récupère apiKey / baseUrl / thinkingBudgetTokens depuis options.
  • default Anthropic fallback:504 — branche de repli, tout provider non reconnu retombe sur Anthropic.
  • buildApiHandler:517 — fonction d'entrée publique, applique le plafond thinkingBudget puis délègue à createHandlerForProvider.
  • ApiStream:1AsyncGenerator<ApiStreamChunk>, type de chunk unifié produit par tous les handlers via createMessage.
  • ApiStreamChunk:3 — quatre variétés de chunk : text / reasoning / usage / tool_calls.
  • LanguageModelChatSelector:4 — sélecteur vendor/family/version pour le VSCode LM API.

Flux de données

La logique centrale de buildApiHandler est « choisir le provider par mode → borner thinkingBudget → déléguer au switch pour construire le handler ». Le code est direct :

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

À noter, ce « coût de construction double » : pour obtenir modelInfo.maxTokens et borner la valeur, on instancie d'abord un handler (first construct:527). Si thinkingBudget n'excède pas le plafond, on le réutilise tel quel ; sinon, on modifie options et on reconstruit. Comme AnthropicHandler et consorts initialisent leur client en lazy (ensureClient:44), la construction ne déclenche pas de requête réelle — ce surcoût est donc négligeable.

La Task appelle cette fonction une fois dans son constructeur et stocke le résultat dans this.api ; tous les attemptApiRequest ultérieurs récupèrent le flux via lui (buildApiHandler call:585). Le paramètre mode provient de TaskParams ; la Task ne change pas de handler en interne — un changement de mode reconstruit la Task plutôt que de changer de handler au sein d'une même Task.

Limites et échecs

  • id de provider inconnu : le switch tombe dans default et instancie un AnthropicHandler (default:504), sans lever d'erreur — le code continue jusqu'à ce que l'API Anthropic renvoie un 401.
  • handler qui lève à la construction : la première construction ne sert qu'à obtenir modelInfo ; en cas d'échec, le try/catch l'avale (catch error:541), puis on rappelle createHandlerForProvider. Cette seconde fois, si une erreur est levée, elle remonte jusqu'à la Task.
  • thinkingBudget égal à 0 : on skippe toute la logique de plafonnement et on entre directement dans createHandlerForProvider (budget check:526), évitant une construction double inutile.
  • plan / act partagent un même options : ...options transmet au handler les deux jeux de champs planMode / actMode ; le handler choisit ensuite en interne la part qui le concerne (destructure:518). Le handler de plan ne dispose pas des champs act — pas d'erreur, simplement des champs superflus dans ses options.
  • apiProvider undefined : tombe dans la branche default (default:504), comme un provider inconnu, et replie sur Anthropic.
  • handler n'implémentant pas abort : dans l'interface, abort? est optionnel (abort optional:64). À l'annulation, la Task doit vérifier l'existence de this.api.abort avant de l'appeler.

Résumé

buildApiHandler reste simple : un switch plus une couche de plafonnement thinkingBudget. Sa valeur essentielle est de traduire « la chaîne de provider issue de la configuration utilisateur » en « un objet ApiHandler unifié », rendant la Task supérieure indépendante des différences entre SDK. Une fois cette couche comprise, on peut plonger dans un provider concret pour voir comment le streaming yield des chunks, ou examiner comment la couche de system prompt injecte les informations de mode dans le prompt.

  • Comment un provider concret implémente createMessage : /providers/anthropic
  • Comment le system prompt choisit une variante selon providerInfo : /prompts/prompt-builder
  • Comment le double mode plan / act influe sur le choix du handler : /prompts/mode

Voir la documentation officielle : documentation Cline · README