Skip to content

buildApiHandler:按模型選提供者

源码版本v4.0.10

職責

buildApiHandler 是 Cline 把「使用者在設定裡選了哪個提供者 (provider)」翻譯成「一個能跑的 ApiHandler 實例」的入口。Task 實例化時調它一次,拿回一個對外統一、內部卻各家的物件 (buildApiHandler call:585)。ApiHandler 這個介面定義得很窄,只有 createMessage 流式發請求、getModel 問模型資訊、可選的 getApiStreamUsageabort 四個方法 (ApiHandler interface:60),所以上層 Task 不用關心背後是 Anthropic、OpenRouter、Bedrock 還是本地 Ollama。

它的另一個職責是按「模式 (mode)」選設定。Cline 把 plan / act 兩種模式做成兩套獨立的模型設定,buildApiHandler 收到 mode 後,從 planModeApiProvideractModeApiProvider 裡挑一個,模型 id、reasoning effort、thinking budget 也都按模式各自取 (buildApiHandler:517);這樣一個 Task 可以在 plan 階段用便宜的模型,切到 act 階段再換成更強的模型。

設計動機

  • 統一介面,各家實作:ApiHandler 只暴露 createMessage 流式介面和 getModel,provider 內部用哪家 SDK 都行,上層迴圈 recursivelyMakeClineRequests 只消費 ApiStream (ApiStream:1),不感知協議差異。
  • plan/act 雙套設定:plan 模式偏資訊收集和規劃,act 模式才動檔案,兩套可以掛不同模型、不同 thinkingBudget,互不干擾 (planModeApiProvider:520).
  • 大 switch 而不是註冊表:createHandlerForProvider 是一個 30+ 分支的 switch (createHandlerForProvider:83),看起來土,但每個分支構造對應 handler 時要傳的欄位差異巨大(Anthropic 要 baseUrl、Bedrock 要一堆 AWS 設定、Vertex 要 projectId),註冊表反而要寫一堆泛型適配,不如直接列出來清楚。
  • thinkingBudget 限額兜底:進 switch 之前先建一次 handler,用拿到的 modelInfo.maxTokensthinkingBudgetTokens 卡到 maxTokens - 1(thinkingBudget clip:525),避免使用者把 thinking budget 設超過模型上限導致 API 直接報錯。
  • default 分支兜底 Anthropic:apiProvider 沒匹配上任何 case 時回落到 AnthropicHandler (default branch:504),保證舊設定或遷移中的 provider id 不會直接拋錯。

關鍵檔案

資料流

buildApiHandler 的核心邏輯是「按 mode 選 provider → 限額 thinkingBudget → 委託 switch 構造 handler」,程式碼很直白:

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

注意這裡有個「構造兩次」的小代價:為了拿到 modelInfo.maxTokens 做限額,它會先 new 一個 handler 出來(first construct:527),如果 thinkingBudget 沒超就直接復用回傳,超了就改 options 重新構造一次。AnthropicHandler 等實作裡 client 是懶載入的(ensureClient:44),構造本身不真正發請求,所以這個開銷可以忽略。

Task 在自己的建構函式裡就這樣調一次,把結果存到 this.api,後續所有 attemptApiRequest 都透過它拿流(buildApiHandler call:585)。mode 參數來自 TaskParams,Task 內部不切換 handler —— 切 mode 會重建 Task,不是在同一個 Task 裡換 handler。

邊界與失敗

  • 未知 provider id:switch 走到 default 直接 new 一個 AnthropicHandler(default:504),不拋錯,繼續往下跑直到 Anthropic API 報 401。
  • handler 構造拋例外:首次構造只為拿 modelInfo,失敗被 try/catch 吞掉(catch error:541),隨後再走 createHandlerForProvider 重試,這回拋了就拋到 Task 層。
  • thinkingBudget 等於 0:skip 整段限額邏輯,直接進 createHandlerForProvider(budget check:526),避免無謂構造兩次。
  • plan / act 共用一份 options:...options 把兩套 planMode / actMode 欄位都塞給 handler,handler 內部再按 mode 選自己那部分(destructure:518),plan handler 拿不到 act 欄位不會出錯,只是多餘欄位在 handler options 裡。
  • apiProvider 是 undefined:落到 default 分支(default:504),和未知 provider 一樣兜底 Anthropic。
  • handler 不實作 abort:介面裡 abort? 是可選的(abort optional:64),Task 取消時呼叫前要先判 this.api.abort 是否存在。

小結

buildApiHandler 本身很簡單:一個 switch 加一層 thinkingBudget 限額兜底,核心價值是把「使用者設定裡的 provider 字串」翻譯成「統一的 ApiHandler 物件」,讓上層 Task 不用關心各家 SDK 的差異。理解了這一層,接下來就可以鑽進具體 provider 看流式怎麼吐 chunk,或者看系統提示層怎麼把 mode 資訊塞進 prompt。

  • 具體一個 provider 怎麼實作 createMessage:/providers/anthropic
  • 系統提示怎麼根據 providerInfo 選變體:/prompts/prompt-builder
  • plan / act 雙模式怎麼影響 handler 選擇:/prompts/mode

對照官方資料:Cline 文件 · README