Skip to content

buildApiHandler: Provider-Auswahl anhand des Modells

源码版本v4.0.10

Verantwortung

buildApiHandler ist der Einstieg, mit dem Cline „den vom Benutzer in den Einstellungen gewählten Provider" in „eine lauffähige ApiHandler-Instanz" übersetzt. Task ruft die Funktion bei der Instanziierung einmal auf und erhält ein Objekt, das nach außen einheitlich, intern aber pro Provider ist (buildApiHandler call:585). Die Schnittstelle ApiHandler ist bewusst schmal: Nur createMessage zum streamenden Senden von Anfragen, getModel zum Abfragen von Modellinformationen sowie die optionalen getApiStreamUsage und abort – vier Methoden insgesamt (ApiHandler interface:60). Die obere Task-Schicht muss daher nicht wissen, ob dahinter Anthropic, OpenRouter, Bedrock oder ein lokales Ollama steht.

Eine zweite Aufgabe besteht darin, die Konfiguration anhand des Modus (mode) zu wählen. Cline hält plan und act als zwei unabhängige Modellkonfigurationen vor; buildApiHandler nimmt den mode entgegen und wählt aus planModeApiProvider oder actModeApiProvider aus. Auch Modell-ID, reasoning effort und thinking budget werden pro Modus bezogen (buildApiHandler:517). So kann ein Task in der plan-Phase ein günstiges Modell verwenden und beim Wechsel in die act-Phase auf ein stärkeres wechseln.

Entwurfsmotivation

  • Einheitliche Schnittstelle, einzelne Implementierungen: ApiHandler exposes nur die streamende createMessage-Schnittstelle und getModel. Intern darf jeder Provider eine beliebige SDK verwenden; die obere Schleife recursivelyMakeClineRequests konsumiert ausschließlich ApiStream (ApiStream:1) und bekommt von Protokollunterschieden nichts mit.
  • plan/act als zwei Konfigurationen: plan sammelt Informationen und plant, act ändert Dateien. Beide Modi können unterschiedliche Modelle und thinkingBudget-Werte tragen, ohne einander zu beeinflussen (planModeApiProvider:520).
  • Großer switch statt Registry: createHandlerForProvider ist ein switch mit über 30 Zweigen (createHandlerForProvider:83). Das wirkt altbacken, doch die zu übergebenden Felder pro Zweig unterscheiden sich massiv (Anthropic benötigt baseUrl, Bedrock viele AWS-Felder, Vertex projectId) – eine Registry würde viele generische Adapter erfordern, die direkte Auflistung ist klarer.
  • Grenze für thinkingBudget: Vor dem switch wird einmalig ein Handler konstruiert, um an modelInfo.maxTokens thinkingBudgetTokens auf maxTokens - 1 zu begrenzen (thinkingBudget clip:525). So wird vermieden, dass ein zu hoch gesetzter thinking budget einen API-Fehler auslöst.
  • default-Fallback auf Anthropic: Trifft apiProvider auf keinen case, fällt der Code auf AnthropicHandler zurück (default branch:504), sodass alte Konfigurationen oder provider-IDs während einer Migration keinen Fehler werfen.

Schlüsseldateien

  • ApiHandler interface:60 — Vertrag über die vier Methoden createMessage / getModel / getApiStreamUsage / abort.
  • ApiHandlerModel:67 — Modellinformationen, die der Handler nach außen gibt; nur id und ModelInfo.
  • ApiProviderInfo:72 — Vollständigeres Paket für die System-Prompt-Schicht, mit providerId, mode und customPrompt.
  • createHandlerForProvider:83 — switch, der die provider-id auf den konkreten Handler abbildet.
  • anthropic case:89case "anthropic", zieht apiKey / baseUrl / thinkingBudgetTokens aus options.
  • default Anthropic fallback:504 — Fallback-Zweig; jeder nicht erkannte Provider fällt auf Anthropic zurück.
  • buildApiHandler:517 — Öffentliche Einstiegsfunktion; nach der thinkingBudget-Begrenzung Delegation an createHandlerForProvider.
  • ApiStream:1AsyncGenerator<ApiStreamChunk>; alle createMessage-Implementierungen geben diesen einheitlichen Chunk-Typ zurück.
  • ApiStreamChunk:3 — Vier Chunk-Varianten: text / reasoning / usage / tool_calls.
  • LanguageModelChatSelector:4 — Selector (vendor/family/version) für die VSCode LM API.

Datenfluss

Die Kernlogik von buildApiHandler lautet „mode wählen → thinkingBudget begrenzen → switch konstruieren". Der Code ist direkt:

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

Achtung: Es gibt eine kleine Kostenstelle der „doppelten Konstruktion". Um an modelInfo.maxTokens zu gelangen, wird ein Handler vorab erzeugt (first construct:527). Liegt thinkingBudget innerhalb der Grenze, wird dieser Handler direkt zurückgegeben, andernfalls werden options angepasst und neu konstruiert. Da in Implementierungen wie AnthropicHandler der Client lazy geladen wird (ensureClient:44), löst die Konstruktion selbst keine Anfrage aus – der Aufwand ist vernachlässigbar.

Task ruft die Funktion in seinem Konstruktor einmal auf und speichert das Ergebnis in this.api. Alle späteren attemptApiRequest-Aufrufe holen sich über ihn den Stream (buildApiHandler call:585). Der Parameter mode stammt aus TaskParams; Task selbst tauscht den Handler nicht aus – ein Moduswechsel bedeutet, dass Task neu aufgebaut wird, kein Handler-Tausch innerhalb desselben Task.

Grenzen und Fehler

  • Unbekannte Provider-ID: Der switch landet im default-Zweig und instanziiert direkt einen AnthropicHandler (default:504); es wird kein Fehler geworfen, die Ausführung läuft weiter, bis Anthropic irgendwann mit 401 antwortet.
  • Fehler bei Handler-Konstruktion: Die erste Konstruktion dient nur dem Abruf von modelInfo; schlägt sie fehl, wird sie vom try/catch geschluckt (catch error:541). Der anschließende createHandlerForProvider-Aufruf versucht es erneut – wirft dieser, bläst der Fehler bis zur Task-Schicht durch.
  • thinkingBudget gleich 0: Die gesamte Begrenzungslogik wird übersprungen und direkt in createHandlerForProvider gegangen (budget check:526), um die doppelte Konstruktion zu vermeiden.
  • plan / act teilen sich options: ...options übergibt sowohl planMode- als auch actMode-Felder an den Handler, der intern seinen Teil auswählt (destructure:518). Ein plan-Handler erhält zwar die act-Felder nicht, was aber zu keinen Fehlern führt – lediglich redundante Felder liegen in den handler options.
  • apiProvider ist undefined: Fällt in den default-Zweig (default:504) und wird wie ein unbekannter Provider auf Anthropic zurückgeführt.
  • Handler implementiert abort nicht: In der Schnittstelle ist abort? optional (abort optional:64); Task muss vor dem Abbruch prüfen, ob this.api.abort überhaupt existiert.

Zusammenfassung

buildApiHandler selbst ist schlicht: ein switch plus eine Schicht thinkingBudget-Begrenzung. Der Kernwert liegt darin, die „Provider-Zeichenkette aus der Benutzerkonfiguration" in ein „einheitliches ApiHandler-Objekt" zu übersetzen, sodass die obere Task-Schicht die Unterschiede der einzelnen SDKs ignorieren kann. Wer diese Schicht verstanden hat, kann in einen konkreten Provider einsteigen und sehen, wie der Stream Chunks liefert, oder in der System-Prompt-Schicht nachvollziehen, wie die mode-Information in den Prompt einfließt.

  • Wie ein konkreter Provider createMessage umsetzt: /providers/anthropic
  • Wie der System-Prompt anhand von providerInfo Varianten wählt: /prompts/prompt-builder
  • Wie plan / act als zwei Modi die Handler-Wahl beeinflussen: /prompts/mode

Siehe offizielle Dokumentation: Cline-Dokumentation · README