Skip to content

ApiProvider 註冊表:provider 的統一接入點

源码版本v0.73.1

api-registry.ts 是 provider 註冊表的中樞——一個 Map<string, RegisteredApiProvider>,key 是 api 字串(如 "anthropic-messages""openai-responses"),value 是包了 wrapStream / wrapStreamSimple 的 provider。所有 provider——內建 9 家、擴充動態註冊的——都從同一個註冊表裡走。stream.tsresolveApiProvider 就是查這張表。

職責

  1. 存 provider:模組內常數 apiProviderRegistry = new Map<string, RegisteredApiProvider>(),以 api 字串為 key。見 packages/ai/src/api-registry.ts:35-40
  2. 包一層斷言:wrapStream / wrapStreamSimple 在調真實 stream 前斷言 model.api === api,不匹配就拋 Mismatched api。見 packages/ai/src/api-registry.ts:42-64
  3. 註冊/註銷:registerApiProvider 寫表,unregisterApiProviders(sourceId) 按 source 批量刪,clearApiProviders 清空。見 packages/ai/src/api-registry.ts:66-98
  4. 查表:getApiProvider(api) 回傳 ApiProviderInternal | undefined,stream.ts 呼叫。見 packages/ai/src/api-registry.ts:80-82

設計動機

為什麼要在 provider 外面包一層 wrapStream?因為型別系統管不住「呼叫方拿一個 Model<"openai-responses"> 卻傳給 streamAnthropic」這種錯誤——TS 的 Model<TApi> 是泛型,執行時 model.api 才是真相。wrapStreamregisterApiProvider 時把 provider 的 api 閉包進斷言,呼叫點就一句 model.api !== api 檢查,把型別錯誤轉成執行時錯誤,堆疊直接指向錯配的呼叫。

為什麼用 sourceId 而不是直接 delete?擴充註冊的 provider 可能跨進程跨重啟,只允許擴充清自己註冊的那批。sourceIdunregisterApiProviders 一個穩定的過濾鍵,內建 provider 註冊時不帶 sourceId,不會被誤刪。

關鍵檔案

wrapStream 的斷言就是一行 model.api !== api:

typescript
// packages/ai/src/api-registry.ts:42-52
function wrapStream<TApi extends Api, TOptions extends StreamOptions>(
	api: TApi,
	stream: StreamFunction<TApi, TOptions>,
): ApiStreamFunction {
	return (model, context, options) => {
		if (model.api !== api) {
			throw new Error(`Mismatched api: ${model.api} expected ${api}`);
		}
		return stream(model as Model<TApi>, context, options as TOptions);
	};
}

註冊時同時 wrap 兩個函式,store 完整的 internal provider:

typescript
// packages/ai/src/api-registry.ts:70-77
apiProviderRegistry.set(provider.api, {
	provider: {
		api: provider.api,
		stream: wrapStream(provider.api, provider.stream),
		streamSimple: wrapStreamSimple(provider.api, provider.streamSimple),
	},
	sourceId,
});

資料流

provider 註冊與查表兩段:

邊界與失敗

  • api 不匹配:wrapStream 在調真實 stream 前斷言,見 packages/ai/src/api-registry.ts:47-49。常見於呼叫方拿到錯的 Model 實例(如拿 OpenAI 模型傳給 Anthropic provider)。
  • 重複註冊:apiProviderRegistry.set 直接覆蓋,後註冊的贏。擴充可以覆蓋內建 provider。
  • 缺 sourceId:unregisterApiProviders(sourceId) 不會動到沒 sourceId 的內建 provider,見 packages/ai/src/api-registry.ts:89-92
  • getApiProviders:回傳所有 provider 陣列,UI 列「可用 provider」時用。
  • 測試 reset:clearApiProviders 後再 registerBuiltInApiProviders(),見 packages/ai/src/providers/register-builtins.ts:398-401

小結

api-registry.ts 是一張 Map + 兩個 wrap 函式,把型別不匹配從執行時謎團變成明確報錯。Provider 的 api + stream + streamSimple 三件套定義見 Provider 抽象與內建,具體某家怎麼實作 stream 看 Anthropic SSE 實作。串流門面的派發邏輯看 stream/complete 門面