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 门面