From ba93da9a93c08557628d293c1601df235517314d Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 10 Jun 2026 20:08:59 +0200 Subject: [PATCH] feat(ai): move API implementations to src/api with lazy wrappers (phase 2) Stream implementations move from src/providers/ to src/api/, renamed by API id (anthropic.ts -> anthropic-messages.ts, google.ts -> google-generative-ai.ts, mistral.ts -> mistral-conversations.ts, amazon-bedrock.ts -> bedrock-converse-stream.ts). Every module now exports exactly stream/streamSimple; shared helpers move alongside. New ProviderStreams dispatch contract in types.ts, lazyApi() wrapper in api/lazy.ts, and one .lazy.ts wrapper per API. Bedrock's wrapper keeps the node-only variable-specifier import and setBedrockProviderModule() (now taking ProviderStreams). providers/register-builtins.ts deleted; interim until the compat entrypoint lands, builtin api-registry registration lives in stream.ts and lazy wrappers are exported from the root barrel. Old per-API lazy exports (streamAnthropic, ...) are gone; package.json subpaths retarget to dist/api/. --- packages/agent/docs/models.md | 42 +- packages/ai/package.json | 32 +- packages/ai/scripts/generate-models.ts | 2 +- .../ai/src/api/anthropic-messages.lazy.ts | 4 + .../anthropic-messages.ts} | 14 +- .../ai/src/api/azure-openai-responses.lazy.ts | 4 + .../azure-openai-responses.ts | 6 +- .../src/api/bedrock-converse-stream.lazy.ts | 30 ++ .../bedrock-converse-stream.ts} | 12 +- .../ai/src/{providers => api}/cloudflare.ts | 0 .../github-copilot-headers.ts | 0 .../ai/src/api/google-generative-ai.lazy.ts | 4 + .../google.ts => api/google-generative-ai.ts} | 10 +- .../src/{providers => api}/google-shared.ts | 0 packages/ai/src/api/google-vertex.lazy.ts | 4 + .../src/{providers => api}/google-vertex.ts | 10 +- packages/ai/src/api/lazy.ts | 16 +- .../ai/src/api/mistral-conversations.lazy.ts | 4 + .../mistral-conversations.ts} | 6 +- .../ai/src/api/openai-codex-responses.lazy.ts | 4 + .../openai-codex-responses.ts | 6 +- .../ai/src/api/openai-completions.lazy.ts | 4 + .../{providers => api}/openai-completions.ts | 6 +- .../{providers => api}/openai-prompt-cache.ts | 0 .../openai-responses-shared.ts | 0 packages/ai/src/api/openai-responses.lazy.ts | 4 + .../{providers => api}/openai-responses.ts | 6 +- .../src/{providers => api}/simple-options.ts | 0 .../{providers => api}/transform-messages.ts | 0 packages/ai/src/bedrock-provider.ts | 6 +- packages/ai/src/index.ts | 33 +- .../ai/src/providers/register-builtins.ts | 406 ------------------ packages/ai/src/stream.ts | 39 +- packages/ai/src/types.ts | 31 +- .../anthropic-eager-tool-input-compat.test.ts | 2 +- .../ai/test/anthropic-sse-parsing.test.ts | 2 +- .../ai/test/azure-openai-base-url.test.ts | 2 +- .../ai/test/bedrock-convert-messages.test.ts | 2 +- .../ai/test/bedrock-custom-headers.test.ts | 4 +- .../test/bedrock-endpoint-resolution.test.ts | 2 +- .../ai/test/bedrock-thinking-payload.test.ts | 2 +- packages/ai/test/cache-retention.test.ts | 6 +- .../ai/test/codex-websocket-cached-probe.ts | 6 +- packages/ai/test/fireworks-models.test.ts | 2 +- .../ai/test/github-copilot-anthropic.test.ts | 2 +- .../test/google-shared-convert-tools.test.ts | 2 +- ...-shared-gemini3-unsigned-tool-call.test.ts | 2 +- ...e-shared-image-tool-result-routing.test.ts | 2 +- .../ai/test/google-thinking-signature.test.ts | 2 +- .../google-vertex-api-key-resolution.test.ts | 2 +- packages/ai/test/lazy-module-load.test.ts | 4 +- packages/ai/test/openai-codex-stream.test.ts | 6 +- ...i-completions-cache-control-format.test.ts | 2 +- .../openai-completions-prompt-cache.test.ts | 2 +- .../ai/test/openai-completions-retry.test.ts | 2 +- ...penai-completions-thinking-as-text.test.ts | 2 +- ...nai-completions-tool-result-images.test.ts | 2 +- .../openai-responses-copilot-provider.test.ts | 2 +- ...enai-responses-foreign-toolcall-id.test.ts | 2 +- .../test/openai-responses-message-id.test.ts | 2 +- ...nai-responses-partial-json-cleanup.test.ts | 2 +- packages/ai/test/scratch.ts | 8 +- ...ssages-copilot-openai-to-anthropic.test.ts | 2 +- .../custom-provider-anthropic/index.ts | 2 +- .../custom-provider-gitlab-duo/index.ts | 12 +- .../test/sdk-codex-cache-probe-tool-loop.ts | 4 +- 66 files changed, 283 insertions(+), 560 deletions(-) create mode 100644 packages/ai/src/api/anthropic-messages.lazy.ts rename packages/ai/src/{providers/anthropic.ts => api/anthropic-messages.ts} (98%) create mode 100644 packages/ai/src/api/azure-openai-responses.lazy.ts rename packages/ai/src/{providers => api}/azure-openai-responses.ts (97%) create mode 100644 packages/ai/src/api/bedrock-converse-stream.lazy.ts rename packages/ai/src/{providers/amazon-bedrock.ts => api/bedrock-converse-stream.ts} (98%) rename packages/ai/src/{providers => api}/cloudflare.ts (100%) rename packages/ai/src/{providers => api}/github-copilot-headers.ts (100%) create mode 100644 packages/ai/src/api/google-generative-ai.lazy.ts rename packages/ai/src/{providers/google.ts => api/google-generative-ai.ts} (97%) rename packages/ai/src/{providers => api}/google-shared.ts (100%) create mode 100644 packages/ai/src/api/google-vertex.lazy.ts rename packages/ai/src/{providers => api}/google-vertex.ts (98%) create mode 100644 packages/ai/src/api/mistral-conversations.lazy.ts rename packages/ai/src/{providers/mistral.ts => api/mistral-conversations.ts} (98%) create mode 100644 packages/ai/src/api/openai-codex-responses.lazy.ts rename packages/ai/src/{providers => api}/openai-codex-responses.ts (99%) create mode 100644 packages/ai/src/api/openai-completions.lazy.ts rename packages/ai/src/{providers => api}/openai-completions.ts (99%) rename packages/ai/src/{providers => api}/openai-prompt-cache.ts (100%) rename packages/ai/src/{providers => api}/openai-responses-shared.ts (100%) create mode 100644 packages/ai/src/api/openai-responses.lazy.ts rename packages/ai/src/{providers => api}/openai-responses.ts (97%) rename packages/ai/src/{providers => api}/simple-options.ts (100%) rename packages/ai/src/{providers => api}/transform-messages.ts (100%) delete mode 100644 packages/ai/src/providers/register-builtins.ts diff --git a/packages/agent/docs/models.md b/packages/agent/docs/models.md index 89b89629a..67f875499 100644 --- a/packages/agent/docs/models.md +++ b/packages/agent/docs/models.md @@ -33,23 +33,23 @@ packages/ai/src/ auth/ # auth method types, helpers, login callbacks api/ # API implementations and lazy wrappers openai-completions.ts # real implementation, imports SDKs, exports stream/streamSimple - openai-completions-lazy.ts + openai-completions.lazy.ts openai-responses.ts - openai-responses-lazy.ts + openai-responses.lazy.ts openai-codex-responses.ts - openai-codex-responses-lazy.ts + openai-codex-responses.lazy.ts azure-openai-responses.ts - azure-openai-responses-lazy.ts + azure-openai-responses.lazy.ts anthropic-messages.ts - anthropic-messages-lazy.ts + anthropic-messages.lazy.ts google-generative-ai.ts - google-generative-ai-lazy.ts + google-generative-ai.lazy.ts google-vertex.ts - google-vertex-lazy.ts + google-vertex.lazy.ts mistral-conversations.ts - mistral-conversations-lazy.ts + mistral-conversations.lazy.ts bedrock-converse-stream.ts - bedrock-converse-stream-lazy.ts + bedrock-converse-stream.lazy.ts lazy.ts # lazyStream()/lazyApi() helpers (shared helpers: openai-responses-shared, google-shared, transform-messages, ...) providers/ # concrete provider factories and per-provider catalogs @@ -315,22 +315,18 @@ export function stream(model, context, options) { ... } export function streamSimple(model, context, options) { ... } ``` -This makes the module itself satisfy `ProviderStreams`, so the lazy wrapper is one generic helper instead of bespoke per-API plumbing: +This makes the module itself satisfy `ProviderStreams`, so the lazy wrapper is one generic helper instead of bespoke per-API plumbing. `ProviderStreams` is the untyped dispatch shape (implementation modules export concretely typed functions, which would not be assignable to a generic method); per-API option typing lives on the modules themselves and on `Provider.stream()` via `ApiStreamOptions`: ```ts export interface ProviderStreams { - stream( - model: Model, - context: Context, - options?: ApiStreamOptions, - ): AssistantMessageEventStream; + stream(model: Model, context: Context, options?: StreamOptions): AssistantMessageEventStream; streamSimple(model: Model, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream; } // src/api/lazy.ts export function lazyApi(load: () => Promise): ProviderStreams; -// src/api/anthropic-messages-lazy.ts +// src/api/anthropic-messages.lazy.ts export const anthropicMessagesApi = (): ProviderStreams => lazyApi(() => import("./anthropic-messages.ts")); ``` @@ -350,7 +346,7 @@ Notes: Many concrete providers share an API implementation (OpenAI-completions: OpenRouter, Groq, Cerebras, xAI, ZAI, ...). They share lazy API objects by reference: ```ts -import { openAICompletionsApi } from "../api/openai-completions-lazy.ts"; +import { openAICompletionsApi } from "../api/openai-completions.lazy.ts"; export function openrouterProvider(): Provider { return createProvider({ @@ -804,12 +800,12 @@ Check items off as they land. Keep this list current; it is the working state fo ### Phase 2 — `src/api/` -- [ ] Move stream implementations from `src/providers/` to `src/api/`, renamed by API id (`anthropic.ts` -> `api/anthropic-messages.ts`, etc.). -- [ ] Normalize each implementation module to export exactly `stream` and `streamSimple`. -- [ ] Move shared helpers (`openai-responses-shared`, `google-shared`, `transform-messages`, `openai-prompt-cache`, `github-copilot-headers`) to `src/api/`. -- [ ] Extract `lazyStream()`/`lazyApi()` into `src/api/lazy.ts`. -- [ ] Add `*-lazy.ts` wrappers per API; bedrock keeps node-only import trick and `setBedrockProviderModule()`. -- [ ] Delete `providers/register-builtins.ts`. +- [x] Move stream implementations from `src/providers/` to `src/api/`, renamed by API id (`anthropic.ts` -> `api/anthropic-messages.ts`, etc.). +- [x] Normalize each implementation module to export exactly `stream` and `streamSimple`. +- [x] Move shared helpers (`openai-responses-shared`, `google-shared`, `transform-messages`, `openai-prompt-cache`, `github-copilot-headers`, `cloudflare`, `simple-options`) to `src/api/`. +- [x] Extract `lazyStream()`/`lazyApi()` into `src/api/lazy.ts`. +- [x] Add `*.lazy.ts` wrappers per API; bedrock keeps node-only import trick and `setBedrockProviderModule()`. +- [x] Delete `providers/register-builtins.ts`. Interim until Phase 5 compat: builtin api-registry registration lives in `stream.ts`; lazy API wrappers are exported from the root barrel. ### Phase 3 — provider factories + catalogs diff --git a/packages/ai/package.json b/packages/ai/package.json index 9a54de2fb..7b361b9a4 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -11,36 +11,36 @@ "import": "./dist/index.js" }, "./anthropic": { - "types": "./dist/providers/anthropic.d.ts", - "import": "./dist/providers/anthropic.js" + "types": "./dist/api/anthropic-messages.d.ts", + "import": "./dist/api/anthropic-messages.js" }, "./azure-openai-responses": { - "types": "./dist/providers/azure-openai-responses.d.ts", - "import": "./dist/providers/azure-openai-responses.js" + "types": "./dist/api/azure-openai-responses.d.ts", + "import": "./dist/api/azure-openai-responses.js" }, "./google": { - "types": "./dist/providers/google.d.ts", - "import": "./dist/providers/google.js" + "types": "./dist/api/google-generative-ai.d.ts", + "import": "./dist/api/google-generative-ai.js" }, "./google-vertex": { - "types": "./dist/providers/google-vertex.d.ts", - "import": "./dist/providers/google-vertex.js" + "types": "./dist/api/google-vertex.d.ts", + "import": "./dist/api/google-vertex.js" }, "./mistral": { - "types": "./dist/providers/mistral.d.ts", - "import": "./dist/providers/mistral.js" + "types": "./dist/api/mistral-conversations.d.ts", + "import": "./dist/api/mistral-conversations.js" }, "./openai-codex-responses": { - "types": "./dist/providers/openai-codex-responses.d.ts", - "import": "./dist/providers/openai-codex-responses.js" + "types": "./dist/api/openai-codex-responses.d.ts", + "import": "./dist/api/openai-codex-responses.js" }, "./openai-completions": { - "types": "./dist/providers/openai-completions.d.ts", - "import": "./dist/providers/openai-completions.js" + "types": "./dist/api/openai-completions.d.ts", + "import": "./dist/api/openai-completions.js" }, "./openai-responses": { - "types": "./dist/providers/openai-responses.d.ts", - "import": "./dist/providers/openai-responses.js" + "types": "./dist/api/openai-responses.d.ts", + "import": "./dist/api/openai-responses.js" }, "./oauth": { "types": "./dist/oauth.d.ts", diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 5e5fff94d..9744961d6 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -8,7 +8,7 @@ import { CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL, CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL, CLOUDFLARE_WORKERS_AI_BASE_URL, -} from "../src/providers/cloudflare.ts"; +} from "../src/api/cloudflare.ts"; import type { AnthropicMessagesCompat, Api, KnownProvider, Model, OpenAICompletionsCompat } from "../src/types.ts"; const __filename = fileURLToPath(import.meta.url); diff --git a/packages/ai/src/api/anthropic-messages.lazy.ts b/packages/ai/src/api/anthropic-messages.lazy.ts new file mode 100644 index 000000000..1da2172ec --- /dev/null +++ b/packages/ai/src/api/anthropic-messages.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const anthropicMessagesApi = (): ProviderStreams => lazyApi(() => import("./anthropic-messages.ts")); diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/api/anthropic-messages.ts similarity index 98% rename from packages/ai/src/providers/anthropic.ts rename to packages/ai/src/api/anthropic-messages.ts index efe4bd757..1e3ef0beb 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/api/anthropic-messages.ts @@ -186,7 +186,7 @@ export interface AnthropicOptions extends StreamOptions { * Enable extended thinking. * For adaptive thinking models: the model decides when/how much to think. * For older models: uses budget-based thinking with thinkingBudgetTokens. - * Default: undefined (thinking is omitted unless `streamSimpleAnthropic()` maps + * Default: undefined (thinking is omitted unless `streamSimple()` maps * a simple reasoning level to this option, or callers set it explicitly). */ thinkingEnabled?: boolean; @@ -205,7 +205,7 @@ export interface AnthropicOptions extends StreamOptions { * - "medium": Moderate thinking, may skip for simple queries * - "low": Minimal thinking, skips for simple tasks * Ignored for older models. - * Default: omitted unless `streamSimpleAnthropic()` maps a simple reasoning + * Default: omitted unless `streamSimple()` maps a simple reasoning * level to this option. */ effort?: AnthropicEffort; @@ -445,7 +445,7 @@ async function* iterateAnthropicEvents( } } -export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOptions> = ( +export const stream: StreamFunction<"anthropic-messages", AnthropicOptions> = ( model: Model<"anthropic-messages">, context: Context, options?: AnthropicOptions, @@ -733,7 +733,7 @@ function mapThinkingLevelToEffort( } } -export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"anthropic-messages", SimpleStreamOptions> = ( model: Model<"anthropic-messages">, context: Context, options?: SimpleStreamOptions, @@ -745,14 +745,14 @@ export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleS const base = buildBaseOptions(model, options, apiKey); if (!options?.reasoning) { - return streamAnthropic(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions); + return stream(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions); } // For models with adaptive thinking: use an effort level. // For older models: use budget-based thinking. if (model.compat?.forceAdaptiveThinking === true) { const effort = mapThinkingLevelToEffort(model, options.reasoning); - return streamAnthropic(model, context, { + return stream(model, context, { ...base, thinkingEnabled: true, effort, @@ -768,7 +768,7 @@ export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleS options.thinkingBudgets, ); - return streamAnthropic(model, context, { + return stream(model, context, { ...base, maxTokens: adjusted.maxTokens, thinkingEnabled: true, diff --git a/packages/ai/src/api/azure-openai-responses.lazy.ts b/packages/ai/src/api/azure-openai-responses.lazy.ts new file mode 100644 index 000000000..5921e10fa --- /dev/null +++ b/packages/ai/src/api/azure-openai-responses.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const azureOpenAIResponsesApi = (): ProviderStreams => lazyApi(() => import("./azure-openai-responses.ts")); diff --git a/packages/ai/src/providers/azure-openai-responses.ts b/packages/ai/src/api/azure-openai-responses.ts similarity index 97% rename from packages/ai/src/providers/azure-openai-responses.ts rename to packages/ai/src/api/azure-openai-responses.ts index ecb4c7e6f..936603347 100644 --- a/packages/ai/src/providers/azure-openai-responses.ts +++ b/packages/ai/src/api/azure-openai-responses.ts @@ -69,7 +69,7 @@ export interface AzureOpenAIResponsesOptions extends StreamOptions { /** * Generate function for Azure OpenAI Responses API */ -export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses", AzureOpenAIResponsesOptions> = ( +export const stream: StreamFunction<"azure-openai-responses", AzureOpenAIResponsesOptions> = ( model: Model<"azure-openai-responses">, context: Context, options?: AzureOpenAIResponsesOptions, @@ -147,7 +147,7 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses" return stream; }; -export const streamSimpleAzureOpenAIResponses: StreamFunction<"azure-openai-responses", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"azure-openai-responses", SimpleStreamOptions> = ( model: Model<"azure-openai-responses">, context: Context, options?: SimpleStreamOptions, @@ -161,7 +161,7 @@ export const streamSimpleAzureOpenAIResponses: StreamFunction<"azure-openai-resp const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined; const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; - return streamAzureOpenAIResponses(model, context, { + return stream(model, context, { ...base, reasoningEffort, } satisfies AzureOpenAIResponsesOptions); diff --git a/packages/ai/src/api/bedrock-converse-stream.lazy.ts b/packages/ai/src/api/bedrock-converse-stream.lazy.ts new file mode 100644 index 000000000..f9b30b35f --- /dev/null +++ b/packages/ai/src/api/bedrock-converse-stream.lazy.ts @@ -0,0 +1,30 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +/** + * Loads the bedrock implementation through a variable specifier so bundlers + * (browser smoke, Bun compile) cannot follow the import into the Node-only + * AWS SDK. The `.ts`/`.js` rewrite keeps the trick working from both source + * and built output. + */ +const importNodeOnlyApi = (specifier: string): Promise => { + const runtimeSpecifier = import.meta.url.endsWith(".js") ? specifier.replace(/\.ts$/, ".js") : specifier; + return import(runtimeSpecifier); +}; + +let bedrockModuleOverride: ProviderStreams | undefined; + +/** + * Overrides the dynamically imported bedrock implementation. Used by the Bun + * binary build, where the variable-specifier import cannot be bundled; the + * build registers a statically imported module instead. + */ +export function setBedrockProviderModule(module: ProviderStreams): void { + bedrockModuleOverride = module; +} + +export const bedrockConverseStreamApi = (): ProviderStreams => + lazyApi( + async () => + bedrockModuleOverride ?? ((await importNodeOnlyApi("./bedrock-converse-stream.ts")) as ProviderStreams), + ); diff --git a/packages/ai/src/providers/amazon-bedrock.ts b/packages/ai/src/api/bedrock-converse-stream.ts similarity index 98% rename from packages/ai/src/providers/amazon-bedrock.ts rename to packages/ai/src/api/bedrock-converse-stream.ts index a4ace1c2f..ac6e9f293 100644 --- a/packages/ai/src/providers/amazon-bedrock.ts +++ b/packages/ai/src/api/bedrock-converse-stream.ts @@ -90,7 +90,7 @@ type Block = (TextContent | ThinkingContent | ToolCall) & { index?: number; part const EMPTY_TEXT_PLACEHOLDER = ""; -export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOptions> = ( +export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> = ( model: Model<"bedrock-converse-stream">, context: Context, options: BedrockOptions = {}, @@ -352,19 +352,19 @@ function addCustomHeadersMiddleware(client: BedrockRuntimeClient, headers: Recor client.middlewareStack.add(middleware, { step: "build", name: "pi-ai-custom-headers", priority: "low" }); } -export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"bedrock-converse-stream", SimpleStreamOptions> = ( model: Model<"bedrock-converse-stream">, context: Context, options?: SimpleStreamOptions, ): AssistantMessageEventStream => { const base = buildBaseOptions(model, options, undefined); if (!options?.reasoning) { - return streamBedrock(model, context, { ...base, reasoning: undefined } satisfies BedrockOptions); + return stream(model, context, { ...base, reasoning: undefined } satisfies BedrockOptions); } if (isAnthropicClaudeModel(model)) { if (supportsAdaptiveThinking(model.id, model.name)) { - return streamBedrock(model, context, { + return stream(model, context, { ...base, reasoning: options.reasoning, thinkingBudgets: options.thinkingBudgets, @@ -380,7 +380,7 @@ export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", Simp options.thinkingBudgets, ); - return streamBedrock(model, context, { + return stream(model, context, { ...base, maxTokens: adjusted.maxTokens, reasoning: options.reasoning, @@ -391,7 +391,7 @@ export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", Simp } satisfies BedrockOptions); } - return streamBedrock(model, context, { + return stream(model, context, { ...base, reasoning: options.reasoning, thinkingBudgets: options.thinkingBudgets, diff --git a/packages/ai/src/providers/cloudflare.ts b/packages/ai/src/api/cloudflare.ts similarity index 100% rename from packages/ai/src/providers/cloudflare.ts rename to packages/ai/src/api/cloudflare.ts diff --git a/packages/ai/src/providers/github-copilot-headers.ts b/packages/ai/src/api/github-copilot-headers.ts similarity index 100% rename from packages/ai/src/providers/github-copilot-headers.ts rename to packages/ai/src/api/github-copilot-headers.ts diff --git a/packages/ai/src/api/google-generative-ai.lazy.ts b/packages/ai/src/api/google-generative-ai.lazy.ts new file mode 100644 index 000000000..136c5043d --- /dev/null +++ b/packages/ai/src/api/google-generative-ai.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const googleGenerativeAIApi = (): ProviderStreams => lazyApi(() => import("./google-generative-ai.ts")); diff --git a/packages/ai/src/providers/google.ts b/packages/ai/src/api/google-generative-ai.ts similarity index 97% rename from packages/ai/src/providers/google.ts rename to packages/ai/src/api/google-generative-ai.ts index d3f6f6232..f959a6aa1 100644 --- a/packages/ai/src/providers/google.ts +++ b/packages/ai/src/api/google-generative-ai.ts @@ -44,7 +44,7 @@ export interface GoogleOptions extends StreamOptions { // Counter for generating unique tool call IDs let toolCallCounter = 0; -export const streamGoogle: StreamFunction<"google-generative-ai", GoogleOptions> = ( +export const stream: StreamFunction<"google-generative-ai", GoogleOptions> = ( model: Model<"google-generative-ai">, context: Context, options?: GoogleOptions, @@ -277,7 +277,7 @@ export const streamGoogle: StreamFunction<"google-generative-ai", GoogleOptions> return stream; }; -export const streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"google-generative-ai", SimpleStreamOptions> = ( model: Model<"google-generative-ai">, context: Context, options?: SimpleStreamOptions, @@ -289,7 +289,7 @@ export const streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleSt const base = buildBaseOptions(model, options, apiKey); if (!options?.reasoning) { - return streamGoogle(model, context, { ...base, thinking: { enabled: false } } satisfies GoogleOptions); + return stream(model, context, { ...base, thinking: { enabled: false } } satisfies GoogleOptions); } const clampedReasoning = clampThinkingLevel(model, options.reasoning); @@ -297,7 +297,7 @@ export const streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleSt const googleModel = model as Model<"google-generative-ai">; if (isGemini3ProModel(googleModel) || isGemini3FlashModel(googleModel) || isGemma4Model(googleModel)) { - return streamGoogle(model, context, { + return stream(model, context, { ...base, thinking: { enabled: true, @@ -306,7 +306,7 @@ export const streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleSt } satisfies GoogleOptions); } - return streamGoogle(model, context, { + return stream(model, context, { ...base, thinking: { enabled: true, diff --git a/packages/ai/src/providers/google-shared.ts b/packages/ai/src/api/google-shared.ts similarity index 100% rename from packages/ai/src/providers/google-shared.ts rename to packages/ai/src/api/google-shared.ts diff --git a/packages/ai/src/api/google-vertex.lazy.ts b/packages/ai/src/api/google-vertex.lazy.ts new file mode 100644 index 000000000..e79d4d0f7 --- /dev/null +++ b/packages/ai/src/api/google-vertex.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const googleVertexApi = (): ProviderStreams => lazyApi(() => import("./google-vertex.ts")); diff --git a/packages/ai/src/providers/google-vertex.ts b/packages/ai/src/api/google-vertex.ts similarity index 98% rename from packages/ai/src/providers/google-vertex.ts rename to packages/ai/src/api/google-vertex.ts index 6feeabb68..1e122a88d 100644 --- a/packages/ai/src/providers/google-vertex.ts +++ b/packages/ai/src/api/google-vertex.ts @@ -60,7 +60,7 @@ const THINKING_LEVEL_MAP: Record = { // Counter for generating unique tool call IDs let toolCallCounter = 0; -export const streamGoogleVertex: StreamFunction<"google-vertex", GoogleVertexOptions> = ( +export const stream: StreamFunction<"google-vertex", GoogleVertexOptions> = ( model: Model<"google-vertex">, context: Context, options?: GoogleVertexOptions, @@ -292,14 +292,14 @@ export const streamGoogleVertex: StreamFunction<"google-vertex", GoogleVertexOpt return stream; }; -export const streamSimpleGoogleVertex: StreamFunction<"google-vertex", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"google-vertex", SimpleStreamOptions> = ( model: Model<"google-vertex">, context: Context, options?: SimpleStreamOptions, ): AssistantMessageEventStream => { const base = buildBaseOptions(model, options, undefined); if (!options?.reasoning) { - return streamGoogleVertex(model, context, { + return stream(model, context, { ...base, thinking: { enabled: false }, } satisfies GoogleVertexOptions); @@ -310,7 +310,7 @@ export const streamSimpleGoogleVertex: StreamFunction<"google-vertex", SimpleStr const geminiModel = model as unknown as Model<"google-generative-ai">; if (isGemini3ProModel(geminiModel) || isGemini3FlashModel(geminiModel)) { - return streamGoogleVertex(model, context, { + return stream(model, context, { ...base, thinking: { enabled: true, @@ -319,7 +319,7 @@ export const streamSimpleGoogleVertex: StreamFunction<"google-vertex", SimpleStr } satisfies GoogleVertexOptions); } - return streamGoogleVertex(model, context, { + return stream(model, context, { ...base, thinking: { enabled: true, diff --git a/packages/ai/src/api/lazy.ts b/packages/ai/src/api/lazy.ts index 8cfd2edee..fe1836aea 100644 --- a/packages/ai/src/api/lazy.ts +++ b/packages/ai/src/api/lazy.ts @@ -1,4 +1,4 @@ -import type { Api, AssistantMessage, AssistantMessageEvent, Model } from "../types.ts"; +import type { Api, AssistantMessage, AssistantMessageEvent, Model, ProviderStreams } from "../types.ts"; import { AssistantMessageEventStream } from "../utils/event-stream.ts"; function createSetupErrorMessage(model: Model, error: unknown): AssistantMessage { @@ -54,3 +54,17 @@ export function lazyStream( return outer; } + +/** + * Wraps a dynamically imported API implementation module as `ProviderStreams`. + * The module loads on first stream call; the host's import cache deduplicates + * loads. Load failures terminate the returned stream with an error event. + */ +export function lazyApi(load: () => Promise): ProviderStreams { + return { + stream: (model, context, options) => + lazyStream(model, async () => (await load()).stream(model, context, options)), + streamSimple: (model, context, options) => + lazyStream(model, async () => (await load()).streamSimple(model, context, options)), + }; +} diff --git a/packages/ai/src/api/mistral-conversations.lazy.ts b/packages/ai/src/api/mistral-conversations.lazy.ts new file mode 100644 index 000000000..84fd03ff9 --- /dev/null +++ b/packages/ai/src/api/mistral-conversations.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const mistralConversationsApi = (): ProviderStreams => lazyApi(() => import("./mistral-conversations.ts")); diff --git a/packages/ai/src/providers/mistral.ts b/packages/ai/src/api/mistral-conversations.ts similarity index 98% rename from packages/ai/src/providers/mistral.ts rename to packages/ai/src/api/mistral-conversations.ts index 1bc7d4ce0..b4873af7a 100644 --- a/packages/ai/src/providers/mistral.ts +++ b/packages/ai/src/api/mistral-conversations.ts @@ -45,7 +45,7 @@ export interface MistralOptions extends StreamOptions { /** * Stream responses from Mistral using `chat.stream`. */ -export const streamMistral: StreamFunction<"mistral-conversations", MistralOptions> = ( +export const stream: StreamFunction<"mistral-conversations", MistralOptions> = ( model: Model<"mistral-conversations">, context: Context, options?: MistralOptions, @@ -107,7 +107,7 @@ export const streamMistral: StreamFunction<"mistral-conversations", MistralOptio /** * Maps provider-agnostic `SimpleStreamOptions` to Mistral options. */ -export const streamSimpleMistral: StreamFunction<"mistral-conversations", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"mistral-conversations", SimpleStreamOptions> = ( model: Model<"mistral-conversations">, context: Context, options?: SimpleStreamOptions, @@ -122,7 +122,7 @@ export const streamSimpleMistral: StreamFunction<"mistral-conversations", Simple const reasoning = clampedReasoning === "off" ? undefined : clampedReasoning; const shouldUseReasoning = model.reasoning && reasoning !== undefined; - return streamMistral(model, context, { + return stream(model, context, { ...base, promptMode: shouldUseReasoning && usesPromptModeReasoning(model) ? "reasoning" : undefined, reasoningEffort: diff --git a/packages/ai/src/api/openai-codex-responses.lazy.ts b/packages/ai/src/api/openai-codex-responses.lazy.ts new file mode 100644 index 000000000..a8d0907d1 --- /dev/null +++ b/packages/ai/src/api/openai-codex-responses.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const openAICodexResponsesApi = (): ProviderStreams => lazyApi(() => import("./openai-codex-responses.ts")); diff --git a/packages/ai/src/providers/openai-codex-responses.ts b/packages/ai/src/api/openai-codex-responses.ts similarity index 99% rename from packages/ai/src/providers/openai-codex-responses.ts rename to packages/ai/src/api/openai-codex-responses.ts index 5836ee1b4..fe7d8dc37 100644 --- a/packages/ai/src/providers/openai-codex-responses.ts +++ b/packages/ai/src/api/openai-codex-responses.ts @@ -191,7 +191,7 @@ function createSSEHeaderTimeout(): { signal: AbortSignal; clear: () => void; err // Main Stream Function // ============================================================================ -export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses", OpenAICodexResponsesOptions> = ( +export const stream: StreamFunction<"openai-codex-responses", OpenAICodexResponsesOptions> = ( model: Model<"openai-codex-responses">, context: Context, options?: OpenAICodexResponsesOptions, @@ -404,7 +404,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses" return stream; }; -export const streamSimpleOpenAICodexResponses: StreamFunction<"openai-codex-responses", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"openai-codex-responses", SimpleStreamOptions> = ( model: Model<"openai-codex-responses">, context: Context, options?: SimpleStreamOptions, @@ -418,7 +418,7 @@ export const streamSimpleOpenAICodexResponses: StreamFunction<"openai-codex-resp const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined; const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; - return streamOpenAICodexResponses(model, context, { + return stream(model, context, { ...base, reasoningEffort, } satisfies OpenAICodexResponsesOptions); diff --git a/packages/ai/src/api/openai-completions.lazy.ts b/packages/ai/src/api/openai-completions.lazy.ts new file mode 100644 index 000000000..6f6c6f61a --- /dev/null +++ b/packages/ai/src/api/openai-completions.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const openAICompletionsApi = (): ProviderStreams => lazyApi(() => import("./openai-completions.ts")); diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/api/openai-completions.ts similarity index 99% rename from packages/ai/src/providers/openai-completions.ts rename to packages/ai/src/api/openai-completions.ts index 0f87c8973..41f4c7a85 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/api/openai-completions.ts @@ -108,7 +108,7 @@ function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention return "short"; } -export const streamOpenAICompletions: StreamFunction<"openai-completions", OpenAICompletionsOptions> = ( +export const stream: StreamFunction<"openai-completions", OpenAICompletionsOptions> = ( model: Model<"openai-completions">, context: Context, options?: OpenAICompletionsOptions, @@ -425,7 +425,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions", OpenA return stream; }; -export const streamSimpleOpenAICompletions: StreamFunction<"openai-completions", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"openai-completions", SimpleStreamOptions> = ( model: Model<"openai-completions">, context: Context, options?: SimpleStreamOptions, @@ -440,7 +440,7 @@ export const streamSimpleOpenAICompletions: StreamFunction<"openai-completions", const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; const toolChoice = (options as OpenAICompletionsOptions | undefined)?.toolChoice; - return streamOpenAICompletions(model, context, { + return stream(model, context, { ...base, reasoningEffort, toolChoice, diff --git a/packages/ai/src/providers/openai-prompt-cache.ts b/packages/ai/src/api/openai-prompt-cache.ts similarity index 100% rename from packages/ai/src/providers/openai-prompt-cache.ts rename to packages/ai/src/api/openai-prompt-cache.ts diff --git a/packages/ai/src/providers/openai-responses-shared.ts b/packages/ai/src/api/openai-responses-shared.ts similarity index 100% rename from packages/ai/src/providers/openai-responses-shared.ts rename to packages/ai/src/api/openai-responses-shared.ts diff --git a/packages/ai/src/api/openai-responses.lazy.ts b/packages/ai/src/api/openai-responses.lazy.ts new file mode 100644 index 000000000..066ca801d --- /dev/null +++ b/packages/ai/src/api/openai-responses.lazy.ts @@ -0,0 +1,4 @@ +import type { ProviderStreams } from "../types.ts"; +import { lazyApi } from "./lazy.ts"; + +export const openAIResponsesApi = (): ProviderStreams => lazyApi(() => import("./openai-responses.ts")); diff --git a/packages/ai/src/providers/openai-responses.ts b/packages/ai/src/api/openai-responses.ts similarity index 97% rename from packages/ai/src/providers/openai-responses.ts rename to packages/ai/src/api/openai-responses.ts index 9afeff290..dfd158663 100644 --- a/packages/ai/src/providers/openai-responses.ts +++ b/packages/ai/src/api/openai-responses.ts @@ -78,7 +78,7 @@ export interface OpenAIResponsesOptions extends StreamOptions { /** * Generate function for OpenAI Responses API */ -export const streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIResponsesOptions> = ( +export const stream: StreamFunction<"openai-responses", OpenAIResponsesOptions> = ( model: Model<"openai-responses">, context: Context, options?: OpenAIResponsesOptions, @@ -159,7 +159,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIRes return stream; }; -export const streamSimpleOpenAIResponses: StreamFunction<"openai-responses", SimpleStreamOptions> = ( +export const streamSimple: StreamFunction<"openai-responses", SimpleStreamOptions> = ( model: Model<"openai-responses">, context: Context, options?: SimpleStreamOptions, @@ -173,7 +173,7 @@ export const streamSimpleOpenAIResponses: StreamFunction<"openai-responses", Sim const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined; const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; - return streamOpenAIResponses(model, context, { + return stream(model, context, { ...base, reasoningEffort, } satisfies OpenAIResponsesOptions); diff --git a/packages/ai/src/providers/simple-options.ts b/packages/ai/src/api/simple-options.ts similarity index 100% rename from packages/ai/src/providers/simple-options.ts rename to packages/ai/src/api/simple-options.ts diff --git a/packages/ai/src/providers/transform-messages.ts b/packages/ai/src/api/transform-messages.ts similarity index 100% rename from packages/ai/src/providers/transform-messages.ts rename to packages/ai/src/api/transform-messages.ts diff --git a/packages/ai/src/bedrock-provider.ts b/packages/ai/src/bedrock-provider.ts index cf08b33ef..10430092f 100644 --- a/packages/ai/src/bedrock-provider.ts +++ b/packages/ai/src/bedrock-provider.ts @@ -1,6 +1,6 @@ -import { streamBedrock, streamSimpleBedrock } from "./providers/amazon-bedrock.ts"; +import { stream, streamSimple } from "./api/bedrock-converse-stream.ts"; export const bedrockProviderModule = { - streamBedrock, - streamSimpleBedrock, + stream, + streamSimple, }; diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts index dd5842490..f98a76164 100644 --- a/packages/ai/src/index.ts +++ b/packages/ai/src/index.ts @@ -1,7 +1,26 @@ export type { Static, TSchema } from "typebox"; export { Type } from "typebox"; +export * from "./api/anthropic-messages.lazy.ts"; +export type { AnthropicEffort, AnthropicOptions, AnthropicThinkingDisplay } from "./api/anthropic-messages.ts"; +export * from "./api/azure-openai-responses.lazy.ts"; +export type { AzureOpenAIResponsesOptions } from "./api/azure-openai-responses.ts"; +export * from "./api/bedrock-converse-stream.lazy.ts"; +export type { BedrockOptions, BedrockThinkingDisplay } from "./api/bedrock-converse-stream.ts"; +export * from "./api/google-generative-ai.lazy.ts"; +export type { GoogleOptions } from "./api/google-generative-ai.ts"; +export type { GoogleThinkingLevel } from "./api/google-shared.ts"; +export * from "./api/google-vertex.lazy.ts"; +export type { GoogleVertexOptions } from "./api/google-vertex.ts"; export * from "./api/lazy.ts"; +export * from "./api/mistral-conversations.lazy.ts"; +export type { MistralOptions } from "./api/mistral-conversations.ts"; +export * from "./api/openai-codex-responses.lazy.ts"; +export type { OpenAICodexResponsesOptions, OpenAICodexWebSocketDebugStats } from "./api/openai-codex-responses.ts"; +export * from "./api/openai-completions.lazy.ts"; +export type { OpenAICompletionsOptions } from "./api/openai-completions.ts"; +export * from "./api/openai-responses.lazy.ts"; +export type { OpenAIResponsesOptions } from "./api/openai-responses.ts"; export * from "./api-registry.ts"; export * from "./auth/context.ts"; export * from "./auth/credential-store.ts"; @@ -11,22 +30,8 @@ export * from "./image-models.ts"; export * from "./images.ts"; export * from "./images-api-registry.ts"; export * from "./models.ts"; -export type { BedrockOptions, BedrockThinkingDisplay } from "./providers/amazon-bedrock.ts"; -export type { AnthropicEffort, AnthropicOptions, AnthropicThinkingDisplay } from "./providers/anthropic.ts"; -export type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses.ts"; export * from "./providers/faux.ts"; -export type { GoogleOptions } from "./providers/google.ts"; -export type { GoogleThinkingLevel } from "./providers/google-shared.ts"; -export type { GoogleVertexOptions } from "./providers/google-vertex.ts"; export * from "./providers/images/register-builtins.ts"; -export type { MistralOptions } from "./providers/mistral.ts"; -export type { - OpenAICodexResponsesOptions, - OpenAICodexWebSocketDebugStats, -} from "./providers/openai-codex-responses.ts"; -export type { OpenAICompletionsOptions } from "./providers/openai-completions.ts"; -export type { OpenAIResponsesOptions } from "./providers/openai-responses.ts"; -export * from "./providers/register-builtins.ts"; export * from "./session-resources.ts"; export * from "./stream.ts"; export * from "./types.ts"; diff --git a/packages/ai/src/providers/register-builtins.ts b/packages/ai/src/providers/register-builtins.ts deleted file mode 100644 index 8fdcaaf0f..000000000 --- a/packages/ai/src/providers/register-builtins.ts +++ /dev/null @@ -1,406 +0,0 @@ -import { clearApiProviders, registerApiProvider } from "../api-registry.ts"; -import type { - Api, - AssistantMessage, - AssistantMessageEvent, - Context, - Model, - SimpleStreamOptions, - StreamFunction, - StreamOptions, -} from "../types.ts"; -import { AssistantMessageEventStream } from "../utils/event-stream.ts"; -import type { BedrockOptions } from "./amazon-bedrock.ts"; -import type { AnthropicOptions } from "./anthropic.ts"; -import type { AzureOpenAIResponsesOptions } from "./azure-openai-responses.ts"; -import type { GoogleOptions } from "./google.ts"; -import type { GoogleVertexOptions } from "./google-vertex.ts"; -import type { MistralOptions } from "./mistral.ts"; -import type { OpenAICodexResponsesOptions } from "./openai-codex-responses.ts"; -import type { OpenAICompletionsOptions } from "./openai-completions.ts"; -import type { OpenAIResponsesOptions } from "./openai-responses.ts"; - -interface LazyProviderModule< - TApi extends Api, - TOptions extends StreamOptions, - TSimpleOptions extends SimpleStreamOptions, -> { - stream: (model: Model, context: Context, options?: TOptions) => AsyncIterable; - streamSimple: ( - model: Model, - context: Context, - options?: TSimpleOptions, - ) => AsyncIterable; -} - -interface AnthropicProviderModule { - streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOptions>; - streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleStreamOptions>; -} - -interface AzureOpenAIResponsesProviderModule { - streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses", AzureOpenAIResponsesOptions>; - streamSimpleAzureOpenAIResponses: StreamFunction<"azure-openai-responses", SimpleStreamOptions>; -} - -interface GoogleProviderModule { - streamGoogle: StreamFunction<"google-generative-ai", GoogleOptions>; - streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleStreamOptions>; -} - -interface GoogleVertexProviderModule { - streamGoogleVertex: StreamFunction<"google-vertex", GoogleVertexOptions>; - streamSimpleGoogleVertex: StreamFunction<"google-vertex", SimpleStreamOptions>; -} - -interface MistralProviderModule { - streamMistral: StreamFunction<"mistral-conversations", MistralOptions>; - streamSimpleMistral: StreamFunction<"mistral-conversations", SimpleStreamOptions>; -} - -interface OpenAICodexResponsesProviderModule { - streamOpenAICodexResponses: StreamFunction<"openai-codex-responses", OpenAICodexResponsesOptions>; - streamSimpleOpenAICodexResponses: StreamFunction<"openai-codex-responses", SimpleStreamOptions>; -} - -interface OpenAICompletionsProviderModule { - streamOpenAICompletions: StreamFunction<"openai-completions", OpenAICompletionsOptions>; - streamSimpleOpenAICompletions: StreamFunction<"openai-completions", SimpleStreamOptions>; -} - -interface OpenAIResponsesProviderModule { - streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIResponsesOptions>; - streamSimpleOpenAIResponses: StreamFunction<"openai-responses", SimpleStreamOptions>; -} - -interface BedrockProviderModule { - streamBedrock: ( - model: Model<"bedrock-converse-stream">, - context: Context, - options?: BedrockOptions, - ) => AsyncIterable; - streamSimpleBedrock: ( - model: Model<"bedrock-converse-stream">, - context: Context, - options?: SimpleStreamOptions, - ) => AsyncIterable; -} - -const importNodeOnlyProvider = (specifier: string): Promise => { - const runtimeSpecifier = import.meta.url.endsWith(".js") ? specifier.replace(/\.ts$/, ".js") : specifier; - return import(runtimeSpecifier); -}; - -let anthropicProviderModulePromise: - | Promise> - | undefined; -let azureOpenAIResponsesProviderModulePromise: - | Promise> - | undefined; -let googleProviderModulePromise: - | Promise> - | undefined; -let googleVertexProviderModulePromise: - | Promise> - | undefined; -let mistralProviderModulePromise: - | Promise> - | undefined; -let openAICodexResponsesProviderModulePromise: - | Promise> - | undefined; -let openAICompletionsProviderModulePromise: - | Promise> - | undefined; -let openAIResponsesProviderModulePromise: - | Promise> - | undefined; -let bedrockProviderModuleOverride: - | LazyProviderModule<"bedrock-converse-stream", BedrockOptions, SimpleStreamOptions> - | undefined; -let bedrockProviderModulePromise: - | Promise> - | undefined; - -export function setBedrockProviderModule(module: BedrockProviderModule): void { - bedrockProviderModuleOverride = { - stream: module.streamBedrock, - streamSimple: module.streamSimpleBedrock, - }; -} - -function forwardStream(target: AssistantMessageEventStream, source: AsyncIterable): void { - (async () => { - for await (const event of source) { - target.push(event); - } - target.end(); - })(); -} - -function createLazyLoadErrorMessage(model: Model, error: unknown): AssistantMessage { - return { - role: "assistant", - content: [], - api: model.api, - provider: model.provider, - model: model.id, - usage: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - totalTokens: 0, - cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, - }, - stopReason: "error", - errorMessage: error instanceof Error ? error.message : String(error), - timestamp: Date.now(), - }; -} - -function createLazyStream( - loadModule: () => Promise>, -): StreamFunction { - return (model, context, options) => { - const outer = new AssistantMessageEventStream(); - - loadModule() - .then((module) => { - const inner = module.stream(model, context, options); - forwardStream(outer, inner); - }) - .catch((error) => { - const message = createLazyLoadErrorMessage(model, error); - outer.push({ type: "error", reason: "error", error: message }); - outer.end(message); - }); - - return outer; - }; -} - -function createLazySimpleStream< - TApi extends Api, - TOptions extends StreamOptions, - TSimpleOptions extends SimpleStreamOptions, ->(loadModule: () => Promise>): StreamFunction { - return (model, context, options) => { - const outer = new AssistantMessageEventStream(); - - loadModule() - .then((module) => { - const inner = module.streamSimple(model, context, options); - forwardStream(outer, inner); - }) - .catch((error) => { - const message = createLazyLoadErrorMessage(model, error); - outer.push({ type: "error", reason: "error", error: message }); - outer.end(message); - }); - - return outer; - }; -} - -function loadAnthropicProviderModule(): Promise< - LazyProviderModule<"anthropic-messages", AnthropicOptions, SimpleStreamOptions> -> { - anthropicProviderModulePromise ||= import("./anthropic.ts").then((module) => { - const provider = module as AnthropicProviderModule; - return { - stream: provider.streamAnthropic, - streamSimple: provider.streamSimpleAnthropic, - }; - }); - return anthropicProviderModulePromise; -} - -function loadAzureOpenAIResponsesProviderModule(): Promise< - LazyProviderModule<"azure-openai-responses", AzureOpenAIResponsesOptions, SimpleStreamOptions> -> { - azureOpenAIResponsesProviderModulePromise ||= import("./azure-openai-responses.ts").then((module) => { - const provider = module as AzureOpenAIResponsesProviderModule; - return { - stream: provider.streamAzureOpenAIResponses, - streamSimple: provider.streamSimpleAzureOpenAIResponses, - }; - }); - return azureOpenAIResponsesProviderModulePromise; -} - -function loadGoogleProviderModule(): Promise< - LazyProviderModule<"google-generative-ai", GoogleOptions, SimpleStreamOptions> -> { - googleProviderModulePromise ||= import("./google.ts").then((module) => { - const provider = module as GoogleProviderModule; - return { - stream: provider.streamGoogle, - streamSimple: provider.streamSimpleGoogle, - }; - }); - return googleProviderModulePromise; -} - -function loadGoogleVertexProviderModule(): Promise< - LazyProviderModule<"google-vertex", GoogleVertexOptions, SimpleStreamOptions> -> { - googleVertexProviderModulePromise ||= import("./google-vertex.ts").then((module) => { - const provider = module as GoogleVertexProviderModule; - return { - stream: provider.streamGoogleVertex, - streamSimple: provider.streamSimpleGoogleVertex, - }; - }); - return googleVertexProviderModulePromise; -} - -function loadMistralProviderModule(): Promise< - LazyProviderModule<"mistral-conversations", MistralOptions, SimpleStreamOptions> -> { - mistralProviderModulePromise ||= import("./mistral.ts").then((module) => { - const provider = module as MistralProviderModule; - return { - stream: provider.streamMistral, - streamSimple: provider.streamSimpleMistral, - }; - }); - return mistralProviderModulePromise; -} - -function loadOpenAICodexResponsesProviderModule(): Promise< - LazyProviderModule<"openai-codex-responses", OpenAICodexResponsesOptions, SimpleStreamOptions> -> { - openAICodexResponsesProviderModulePromise ||= import("./openai-codex-responses.ts").then((module) => { - const provider = module as OpenAICodexResponsesProviderModule; - return { - stream: provider.streamOpenAICodexResponses, - streamSimple: provider.streamSimpleOpenAICodexResponses, - }; - }); - return openAICodexResponsesProviderModulePromise; -} - -function loadOpenAICompletionsProviderModule(): Promise< - LazyProviderModule<"openai-completions", OpenAICompletionsOptions, SimpleStreamOptions> -> { - openAICompletionsProviderModulePromise ||= import("./openai-completions.ts").then((module) => { - const provider = module as OpenAICompletionsProviderModule; - return { - stream: provider.streamOpenAICompletions, - streamSimple: provider.streamSimpleOpenAICompletions, - }; - }); - return openAICompletionsProviderModulePromise; -} - -function loadOpenAIResponsesProviderModule(): Promise< - LazyProviderModule<"openai-responses", OpenAIResponsesOptions, SimpleStreamOptions> -> { - openAIResponsesProviderModulePromise ||= import("./openai-responses.ts").then((module) => { - const provider = module as OpenAIResponsesProviderModule; - return { - stream: provider.streamOpenAIResponses, - streamSimple: provider.streamSimpleOpenAIResponses, - }; - }); - return openAIResponsesProviderModulePromise; -} - -function loadBedrockProviderModule(): Promise< - LazyProviderModule<"bedrock-converse-stream", BedrockOptions, SimpleStreamOptions> -> { - if (bedrockProviderModuleOverride) { - return Promise.resolve(bedrockProviderModuleOverride); - } - bedrockProviderModulePromise ||= importNodeOnlyProvider("./amazon-bedrock.ts").then((module) => { - const provider = module as BedrockProviderModule; - return { - stream: provider.streamBedrock, - streamSimple: provider.streamSimpleBedrock, - }; - }); - return bedrockProviderModulePromise; -} - -export const streamAnthropic = createLazyStream(loadAnthropicProviderModule); -export const streamSimpleAnthropic = createLazySimpleStream(loadAnthropicProviderModule); -export const streamAzureOpenAIResponses = createLazyStream(loadAzureOpenAIResponsesProviderModule); -export const streamSimpleAzureOpenAIResponses = createLazySimpleStream(loadAzureOpenAIResponsesProviderModule); -export const streamGoogle = createLazyStream(loadGoogleProviderModule); -export const streamSimpleGoogle = createLazySimpleStream(loadGoogleProviderModule); -export const streamGoogleVertex = createLazyStream(loadGoogleVertexProviderModule); -export const streamSimpleGoogleVertex = createLazySimpleStream(loadGoogleVertexProviderModule); -export const streamMistral = createLazyStream(loadMistralProviderModule); -export const streamSimpleMistral = createLazySimpleStream(loadMistralProviderModule); -export const streamOpenAICodexResponses = createLazyStream(loadOpenAICodexResponsesProviderModule); -export const streamSimpleOpenAICodexResponses = createLazySimpleStream(loadOpenAICodexResponsesProviderModule); -export const streamOpenAICompletions = createLazyStream(loadOpenAICompletionsProviderModule); -export const streamSimpleOpenAICompletions = createLazySimpleStream(loadOpenAICompletionsProviderModule); -export const streamOpenAIResponses = createLazyStream(loadOpenAIResponsesProviderModule); -export const streamSimpleOpenAIResponses = createLazySimpleStream(loadOpenAIResponsesProviderModule); -const streamBedrockLazy = createLazyStream(loadBedrockProviderModule); -const streamSimpleBedrockLazy = createLazySimpleStream(loadBedrockProviderModule); - -export function registerBuiltInApiProviders(): void { - registerApiProvider({ - api: "anthropic-messages", - stream: streamAnthropic, - streamSimple: streamSimpleAnthropic, - }); - - registerApiProvider({ - api: "openai-completions", - stream: streamOpenAICompletions, - streamSimple: streamSimpleOpenAICompletions, - }); - - registerApiProvider({ - api: "mistral-conversations", - stream: streamMistral, - streamSimple: streamSimpleMistral, - }); - - registerApiProvider({ - api: "openai-responses", - stream: streamOpenAIResponses, - streamSimple: streamSimpleOpenAIResponses, - }); - - registerApiProvider({ - api: "azure-openai-responses", - stream: streamAzureOpenAIResponses, - streamSimple: streamSimpleAzureOpenAIResponses, - }); - - registerApiProvider({ - api: "openai-codex-responses", - stream: streamOpenAICodexResponses, - streamSimple: streamSimpleOpenAICodexResponses, - }); - - registerApiProvider({ - api: "google-generative-ai", - stream: streamGoogle, - streamSimple: streamSimpleGoogle, - }); - - registerApiProvider({ - api: "google-vertex", - stream: streamGoogleVertex, - streamSimple: streamSimpleGoogleVertex, - }); - - registerApiProvider({ - api: "bedrock-converse-stream", - stream: streamBedrockLazy, - streamSimple: streamSimpleBedrockLazy, - }); -} - -export function resetApiProviders(): void { - clearApiProviders(); - registerBuiltInApiProviders(); -} - -registerBuiltInApiProviders(); diff --git a/packages/ai/src/stream.ts b/packages/ai/src/stream.ts index 3ae631a15..f874f03d3 100644 --- a/packages/ai/src/stream.ts +++ b/packages/ai/src/stream.ts @@ -1,6 +1,13 @@ -import "./providers/register-builtins.ts"; - -import { getApiProvider } from "./api-registry.ts"; +import { anthropicMessagesApi } from "./api/anthropic-messages.lazy.ts"; +import { azureOpenAIResponsesApi } from "./api/azure-openai-responses.lazy.ts"; +import { bedrockConverseStreamApi } from "./api/bedrock-converse-stream.lazy.ts"; +import { googleGenerativeAIApi } from "./api/google-generative-ai.lazy.ts"; +import { googleVertexApi } from "./api/google-vertex.lazy.ts"; +import { mistralConversationsApi } from "./api/mistral-conversations.lazy.ts"; +import { openAICodexResponsesApi } from "./api/openai-codex-responses.lazy.ts"; +import { openAICompletionsApi } from "./api/openai-completions.lazy.ts"; +import { openAIResponsesApi } from "./api/openai-responses.lazy.ts"; +import { clearApiProviders, getApiProvider, registerApiProvider } from "./api-registry.ts"; import { getEnvApiKey } from "./env-api-keys.ts"; import type { Api, @@ -9,12 +16,38 @@ import type { Context, Model, ProviderStreamOptions, + ProviderStreams, SimpleStreamOptions, StreamOptions, } from "./types.ts"; export { getEnvApiKey } from "./env-api-keys.ts"; +const BUILTIN_APIS: [Api, ProviderStreams][] = [ + ["anthropic-messages", anthropicMessagesApi()], + ["openai-completions", openAICompletionsApi()], + ["openai-responses", openAIResponsesApi()], + ["openai-codex-responses", openAICodexResponsesApi()], + ["azure-openai-responses", azureOpenAIResponsesApi()], + ["google-generative-ai", googleGenerativeAIApi()], + ["google-vertex", googleVertexApi()], + ["mistral-conversations", mistralConversationsApi()], + ["bedrock-converse-stream", bedrockConverseStreamApi()], +]; + +export function registerBuiltInApiProviders(): void { + for (const [api, streams] of BUILTIN_APIS) { + registerApiProvider({ api, stream: streams.stream, streamSimple: streams.streamSimple }); + } +} + +export function resetApiProviders(): void { + clearApiProviders(); + registerBuiltInApiProviders(); +} + +registerBuiltInApiProviders(); + function hasExplicitApiKey(apiKey: string | undefined): apiKey is string { return typeof apiKey === "string" && apiKey.trim().length > 0; } diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts index 4d7b4738f..2bce49bc9 100644 --- a/packages/ai/src/types.ts +++ b/packages/ai/src/types.ts @@ -1,12 +1,12 @@ -import type { BedrockOptions } from "./providers/amazon-bedrock.ts"; -import type { AnthropicOptions } from "./providers/anthropic.ts"; -import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses.ts"; -import type { GoogleOptions } from "./providers/google.ts"; -import type { GoogleVertexOptions } from "./providers/google-vertex.ts"; -import type { MistralOptions } from "./providers/mistral.ts"; -import type { OpenAICodexResponsesOptions } from "./providers/openai-codex-responses.ts"; -import type { OpenAICompletionsOptions } from "./providers/openai-completions.ts"; -import type { OpenAIResponsesOptions } from "./providers/openai-responses.ts"; +import type { AnthropicOptions } from "./api/anthropic-messages.ts"; +import type { AzureOpenAIResponsesOptions } from "./api/azure-openai-responses.ts"; +import type { BedrockOptions } from "./api/bedrock-converse-stream.ts"; +import type { GoogleOptions } from "./api/google-generative-ai.ts"; +import type { GoogleVertexOptions } from "./api/google-vertex.ts"; +import type { MistralOptions } from "./api/mistral-conversations.ts"; +import type { OpenAICodexResponsesOptions } from "./api/openai-codex-responses.ts"; +import type { OpenAICompletionsOptions } from "./api/openai-completions.ts"; +import type { OpenAIResponsesOptions } from "./api/openai-responses.ts"; import type { AssistantMessageDiagnostic } from "./utils/diagnostics.ts"; import type { AssistantMessageEventStream } from "./utils/event-stream.ts"; @@ -191,6 +191,19 @@ export type ApiStreamOptions = TApi extends keyof ApiOptionsMa ? ApiOptionsMap[TApi] : StreamOptions & Record; +/** + * The uniform stream contract of an API implementation module: every module + * under `src/api/` exports exactly `stream` and `streamSimple`, so the module + * itself satisfies this interface. Lazy wrappers (`lazyApi()`) and provider + * factories pass these around as values. This is the untyped dispatch shape; + * per-API option typing lives on the implementation modules themselves and on + * `Provider.stream()` via `ApiStreamOptions`. + */ +export interface ProviderStreams { + stream(model: Model, context: Context, options?: StreamOptions): AssistantMessageEventStream; + streamSimple(model: Model, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream; +} + export interface ImagesOptions { signal?: AbortSignal; apiKey?: string; diff --git a/packages/ai/test/anthropic-eager-tool-input-compat.test.ts b/packages/ai/test/anthropic-eager-tool-input-compat.test.ts index 22ac0e59d..c53c9d6b2 100644 --- a/packages/ai/test/anthropic-eager-tool-input-compat.test.ts +++ b/packages/ai/test/anthropic-eager-tool-input-compat.test.ts @@ -2,7 +2,7 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht import type { AddressInfo } from "node:net"; import { Type } from "typebox"; import { describe, expect, it } from "vitest"; -import { streamAnthropic } from "../src/providers/anthropic.ts"; +import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts"; import type { Context, Model, Tool } from "../src/types.ts"; interface CapturedRequest { diff --git a/packages/ai/test/anthropic-sse-parsing.test.ts b/packages/ai/test/anthropic-sse-parsing.test.ts index 030249664..889435821 100644 --- a/packages/ai/test/anthropic-sse-parsing.test.ts +++ b/packages/ai/test/anthropic-sse-parsing.test.ts @@ -1,8 +1,8 @@ import type Anthropic from "@anthropic-ai/sdk"; import { Type } from "typebox"; import { describe, expect, it } from "vitest"; +import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts"; import { getModel } from "../src/models.ts"; -import { streamAnthropic } from "../src/providers/anthropic.ts"; import type { Context, ToolCall } from "../src/types.ts"; function createSseResponse(events: Array<{ event: string; data: string }>): Response { diff --git a/packages/ai/test/azure-openai-base-url.test.ts b/packages/ai/test/azure-openai-base-url.test.ts index 15b8a5287..b80bae585 100644 --- a/packages/ai/test/azure-openai-base-url.test.ts +++ b/packages/ai/test/azure-openai-base-url.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { stream as streamAzureOpenAIResponses } from "../src/api/azure-openai-responses.ts"; import { getModel } from "../src/models.ts"; -import { streamAzureOpenAIResponses } from "../src/providers/azure-openai-responses.ts"; import type { Context } from "../src/types.ts"; interface CapturedAzureClientOptions { diff --git a/packages/ai/test/bedrock-convert-messages.test.ts b/packages/ai/test/bedrock-convert-messages.test.ts index d74dedae9..ba3ff1374 100644 --- a/packages/ai/test/bedrock-convert-messages.test.ts +++ b/packages/ai/test/bedrock-convert-messages.test.ts @@ -44,8 +44,8 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => { }; }); +import { stream as streamBedrock } from "../src/api/bedrock-converse-stream.ts"; import { getModel } from "../src/models.ts"; -import { streamBedrock } from "../src/providers/amazon-bedrock.ts"; import type { Context, Message } from "../src/types.ts"; const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0"); diff --git a/packages/ai/test/bedrock-custom-headers.test.ts b/packages/ai/test/bedrock-custom-headers.test.ts index 1017d0897..e0899068d 100644 --- a/packages/ai/test/bedrock-custom-headers.test.ts +++ b/packages/ai/test/bedrock-custom-headers.test.ts @@ -51,9 +51,9 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => { }; }); +import type { BedrockOptions } from "../src/api/bedrock-converse-stream.ts"; +import { stream as streamBedrock, streamSimple as streamSimpleBedrock } from "../src/api/bedrock-converse-stream.ts"; import { getModel } from "../src/models.ts"; -import type { BedrockOptions } from "../src/providers/amazon-bedrock.ts"; -import { streamBedrock, streamSimpleBedrock } from "../src/providers/amazon-bedrock.ts"; import type { Context, Model } from "../src/types.ts"; const context: Context = { diff --git a/packages/ai/test/bedrock-endpoint-resolution.test.ts b/packages/ai/test/bedrock-endpoint-resolution.test.ts index 412c62e01..28221d5c2 100644 --- a/packages/ai/test/bedrock-endpoint-resolution.test.ts +++ b/packages/ai/test/bedrock-endpoint-resolution.test.ts @@ -44,8 +44,8 @@ vi.mock("@aws-sdk/client-bedrock-runtime", () => { }; }); +import { stream as streamBedrock } from "../src/api/bedrock-converse-stream.ts"; import { getModel } from "../src/models.ts"; -import { streamBedrock } from "../src/providers/amazon-bedrock.ts"; import type { Context, Model } from "../src/types.ts"; const context: Context = { diff --git a/packages/ai/test/bedrock-thinking-payload.test.ts b/packages/ai/test/bedrock-thinking-payload.test.ts index 8f4e06e72..ff4509cba 100644 --- a/packages/ai/test/bedrock-thinking-payload.test.ts +++ b/packages/ai/test/bedrock-thinking-payload.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; +import { type BedrockOptions, stream as streamBedrock } from "../src/api/bedrock-converse-stream.ts"; import { getModel } from "../src/models.ts"; -import { type BedrockOptions, streamBedrock } from "../src/providers/amazon-bedrock.ts"; import type { Context, Model } from "../src/types.ts"; import { hasBedrockCredentials } from "./bedrock-utils.ts"; diff --git a/packages/ai/test/cache-retention.test.ts b/packages/ai/test/cache-retention.test.ts index 8828ba864..b37451989 100644 --- a/packages/ai/test/cache-retention.test.ts +++ b/packages/ai/test/cache-retention.test.ts @@ -1,8 +1,8 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts"; +import { stream as streamOpenAICompletions } from "../src/api/openai-completions.ts"; +import { stream as streamOpenAIResponses } from "../src/api/openai-responses.ts"; import { getModel } from "../src/models.ts"; -import { streamAnthropic } from "../src/providers/anthropic.ts"; -import { streamOpenAICompletions } from "../src/providers/openai-completions.ts"; -import { streamOpenAIResponses } from "../src/providers/openai-responses.ts"; import { stream } from "../src/stream.ts"; import type { Context, Model } from "../src/types.ts"; diff --git a/packages/ai/test/codex-websocket-cached-probe.ts b/packages/ai/test/codex-websocket-cached-probe.ts index 7317035cb..ca08c6c75 100644 --- a/packages/ai/test/codex-websocket-cached-probe.ts +++ b/packages/ai/test/codex-websocket-cached-probe.ts @@ -10,13 +10,13 @@ import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { Type } from "typebox"; import { AuthStorage } from "../../coding-agent/src/core/auth-storage.ts"; -import { getModel } from "../src/models.ts"; import { closeOpenAICodexWebSocketSessions, getOpenAICodexWebSocketDebugStats, resetOpenAICodexWebSocketDebugStats, - streamOpenAICodexResponses, -} from "../src/providers/openai-codex-responses.ts"; + stream as streamOpenAICodexResponses, +} from "../src/api/openai-codex-responses.ts"; +import { getModel } from "../src/models.ts"; import type { AssistantMessage, Context, Message, Model, Tool, ToolResultMessage, Transport } from "../src/types.ts"; type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh"; diff --git a/packages/ai/test/fireworks-models.test.ts b/packages/ai/test/fireworks-models.test.ts index 8b291b89e..0f7ac4c19 100644 --- a/packages/ai/test/fireworks-models.test.ts +++ b/packages/ai/test/fireworks-models.test.ts @@ -2,9 +2,9 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht import type { AddressInfo } from "node:net"; import { Type } from "typebox"; import { afterEach, describe, expect, it } from "vitest"; +import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts"; import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts"; import { getModel, getModels } from "../src/models.ts"; -import { streamAnthropic } from "../src/providers/anthropic.ts"; import type { Context, Model, Tool } from "../src/types.ts"; const originalFireworksApiKey = process.env.FIREWORKS_API_KEY; diff --git a/packages/ai/test/github-copilot-anthropic.test.ts b/packages/ai/test/github-copilot-anthropic.test.ts index ee3de3e85..c1c1a30f2 100644 --- a/packages/ai/test/github-copilot-anthropic.test.ts +++ b/packages/ai/test/github-copilot-anthropic.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from "vitest"; +import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts"; import { getModel } from "../src/models.ts"; -import { streamAnthropic } from "../src/providers/anthropic.ts"; import type { Context } from "../src/types.ts"; const mockState = vi.hoisted(() => ({ diff --git a/packages/ai/test/google-shared-convert-tools.test.ts b/packages/ai/test/google-shared-convert-tools.test.ts index c7b10b419..d91bcfa0f 100644 --- a/packages/ai/test/google-shared-convert-tools.test.ts +++ b/packages/ai/test/google-shared-convert-tools.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { convertTools } from "../src/providers/google-shared.ts"; +import { convertTools } from "../src/api/google-shared.ts"; import type { Tool } from "../src/types.ts"; function makeTool(parameters: Record): Tool { diff --git a/packages/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts b/packages/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts index 920c6a4e7..406c5a37a 100644 --- a/packages/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts +++ b/packages/ai/test/google-shared-gemini3-unsigned-tool-call.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { convertMessages } from "../src/providers/google-shared.ts"; +import { convertMessages } from "../src/api/google-shared.ts"; import type { Context, Model } from "../src/types.ts"; function makeGemini3Model( diff --git a/packages/ai/test/google-shared-image-tool-result-routing.test.ts b/packages/ai/test/google-shared-image-tool-result-routing.test.ts index 1430a0849..8ba5660cb 100644 --- a/packages/ai/test/google-shared-image-tool-result-routing.test.ts +++ b/packages/ai/test/google-shared-image-tool-result-routing.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { convertMessages } from "../src/providers/google-shared.ts"; +import { convertMessages } from "../src/api/google-shared.ts"; import type { Context, Model } from "../src/types.ts"; function makeModel( diff --git a/packages/ai/test/google-thinking-signature.test.ts b/packages/ai/test/google-thinking-signature.test.ts index 83b17cccd..853a6a60c 100644 --- a/packages/ai/test/google-thinking-signature.test.ts +++ b/packages/ai/test/google-thinking-signature.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { isThinkingPart, retainThoughtSignature } from "../src/providers/google-shared.ts"; +import { isThinkingPart, retainThoughtSignature } from "../src/api/google-shared.ts"; describe("Google thinking detection (thoughtSignature)", () => { it("treats part.thought === true as thinking", () => { diff --git a/packages/ai/test/google-vertex-api-key-resolution.test.ts b/packages/ai/test/google-vertex-api-key-resolution.test.ts index 5f66649c3..7fa182f9d 100644 --- a/packages/ai/test/google-vertex-api-key-resolution.test.ts +++ b/packages/ai/test/google-vertex-api-key-resolution.test.ts @@ -45,8 +45,8 @@ vi.mock("@google/genai", () => { }; }); +import { stream as streamGoogleVertex } from "../src/api/google-vertex.ts"; import { getModel } from "../src/models.ts"; -import { streamGoogleVertex } from "../src/providers/google-vertex.ts"; import type { Context, Model } from "../src/types.ts"; const model = getModel("google-vertex", "gemini-3-flash-preview"); diff --git a/packages/ai/test/lazy-module-load.test.ts b/packages/ai/test/lazy-module-load.test.ts index e21f0d12d..bfb4005e7 100644 --- a/packages/ai/test/lazy-module-load.test.ts +++ b/packages/ai/test/lazy-module-load.test.ts @@ -66,7 +66,7 @@ describe("lazy provider module loading", () => { expect(result.loadedSpecifiers).toEqual([]); }); - it("loads only the Anthropic SDK when calling the root lazy wrapper", () => { + it("loads only the Anthropic SDK when streaming through the lazy API wrapper", () => { const result = runProbe(` const model = { id: "claude-sonnet-4-6", @@ -81,7 +81,7 @@ describe("lazy provider module loading", () => { maxTokens: 8192, }; const context = { messages: [{ role: "user", content: "hi" }] }; - await mod.streamSimpleAnthropic(model, context).result(); + await mod.anthropicMessagesApi().streamSimple(model, context).result(); `); expect(result.loadedSpecifiers).toEqual(["@anthropic-ai/sdk"]); diff --git a/packages/ai/test/openai-codex-stream.test.ts b/packages/ai/test/openai-codex-stream.test.ts index 9cf0f9812..f683d678a 100644 --- a/packages/ai/test/openai-codex-stream.test.ts +++ b/packages/ai/test/openai-codex-stream.test.ts @@ -5,9 +5,9 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { getOpenAICodexWebSocketDebugStats, resetOpenAICodexWebSocketDebugStats, - streamOpenAICodexResponses, - streamSimpleOpenAICodexResponses, -} from "../src/providers/openai-codex-responses.ts"; + stream as streamOpenAICodexResponses, + streamSimple as streamSimpleOpenAICodexResponses, +} from "../src/api/openai-codex-responses.ts"; import type { Context, Model } from "../src/types.ts"; const originalAgentDir = process.env.PI_CODING_AGENT_DIR; diff --git a/packages/ai/test/openai-completions-cache-control-format.test.ts b/packages/ai/test/openai-completions-cache-control-format.test.ts index 7d8ee9c4d..04fd2361f 100644 --- a/packages/ai/test/openai-completions-cache-control-format.test.ts +++ b/packages/ai/test/openai-completions-cache-control-format.test.ts @@ -1,7 +1,7 @@ import { Type } from "typebox"; import { beforeEach, describe, expect, it, vi } from "vitest"; +import { stream as streamOpenAICompletions } from "../src/api/openai-completions.ts"; import { getModel } from "../src/models.ts"; -import { streamOpenAICompletions } from "../src/providers/openai-completions.ts"; import type { Model } from "../src/types.ts"; interface CacheControl { diff --git a/packages/ai/test/openai-completions-prompt-cache.test.ts b/packages/ai/test/openai-completions-prompt-cache.test.ts index 75098fc13..0b25e8572 100644 --- a/packages/ai/test/openai-completions-prompt-cache.test.ts +++ b/packages/ai/test/openai-completions-prompt-cache.test.ts @@ -1,6 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { stream as streamOpenAICompletions } from "../src/api/openai-completions.ts"; import { getModel } from "../src/models.ts"; -import { streamOpenAICompletions } from "../src/providers/openai-completions.ts"; import type { Model } from "../src/types.ts"; interface FakeOpenAIClientOptions { diff --git a/packages/ai/test/openai-completions-retry.test.ts b/packages/ai/test/openai-completions-retry.test.ts index cf631dd1a..f67dbbbc4 100644 --- a/packages/ai/test/openai-completions-retry.test.ts +++ b/packages/ai/test/openai-completions-retry.test.ts @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -import { streamOpenAICompletions } from "../src/providers/openai-completions.ts"; +import { stream as streamOpenAICompletions } from "../src/api/openai-completions.ts"; import type { Context, Model } from "../src/types.ts"; const mockState = vi.hoisted(() => ({ diff --git a/packages/ai/test/openai-completions-thinking-as-text.test.ts b/packages/ai/test/openai-completions-thinking-as-text.test.ts index 1c49aad3b..f8db47888 100644 --- a/packages/ai/test/openai-completions-thinking-as-text.test.ts +++ b/packages/ai/test/openai-completions-thinking-as-text.test.ts @@ -2,7 +2,7 @@ import { once } from "node:events"; import http from "node:http"; import type { AddressInfo } from "node:net"; import { afterEach, describe, expect, it } from "vitest"; -import { convertMessages, streamOpenAICompletions } from "../src/providers/openai-completions.ts"; +import { convertMessages, stream as streamOpenAICompletions } from "../src/api/openai-completions.ts"; import type { AssistantMessage, AssistantMessageEvent, diff --git a/packages/ai/test/openai-completions-tool-result-images.test.ts b/packages/ai/test/openai-completions-tool-result-images.test.ts index 3510f3967..4fa0fd681 100644 --- a/packages/ai/test/openai-completions-tool-result-images.test.ts +++ b/packages/ai/test/openai-completions-tool-result-images.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; +import { convertMessages } from "../src/api/openai-completions.ts"; import { getModel } from "../src/models.ts"; -import { convertMessages } from "../src/providers/openai-completions.ts"; import type { AssistantMessage, Context, diff --git a/packages/ai/test/openai-responses-copilot-provider.test.ts b/packages/ai/test/openai-responses-copilot-provider.test.ts index 04236feda..92b3d7409 100644 --- a/packages/ai/test/openai-responses-copilot-provider.test.ts +++ b/packages/ai/test/openai-responses-copilot-provider.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; +import { stream as streamOpenAIResponses } from "../src/api/openai-responses.ts"; import { getModel } from "../src/models.ts"; -import { streamOpenAIResponses } from "../src/providers/openai-responses.ts"; import type { Model } from "../src/types.ts"; type CapturedHeaders = Headers | string[][] | Record | undefined; diff --git a/packages/ai/test/openai-responses-foreign-toolcall-id.test.ts b/packages/ai/test/openai-responses-foreign-toolcall-id.test.ts index 10f063779..7eea6821f 100644 --- a/packages/ai/test/openai-responses-foreign-toolcall-id.test.ts +++ b/packages/ai/test/openai-responses-foreign-toolcall-id.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; +import { convertResponsesMessages } from "../src/api/openai-responses-shared.ts"; import { getModel } from "../src/models.ts"; -import { convertResponsesMessages } from "../src/providers/openai-responses-shared.ts"; import type { AssistantMessage, Context, ToolResultMessage, Usage } from "../src/types.ts"; import { shortHash } from "../src/utils/hash.ts"; diff --git a/packages/ai/test/openai-responses-message-id.test.ts b/packages/ai/test/openai-responses-message-id.test.ts index f675cc8e6..06687fa1d 100644 --- a/packages/ai/test/openai-responses-message-id.test.ts +++ b/packages/ai/test/openai-responses-message-id.test.ts @@ -1,7 +1,7 @@ import type { ResponseOutputMessage } from "openai/resources/responses/responses.js"; import { describe, expect, it } from "vitest"; +import { convertResponsesMessages } from "../src/api/openai-responses-shared.ts"; import { getModel } from "../src/models.ts"; -import { convertResponsesMessages } from "../src/providers/openai-responses-shared.ts"; import type { AssistantMessage, Context, Usage } from "../src/types.ts"; const usage: Usage = { diff --git a/packages/ai/test/openai-responses-partial-json-cleanup.test.ts b/packages/ai/test/openai-responses-partial-json-cleanup.test.ts index 76b16ad3f..26a6704aa 100644 --- a/packages/ai/test/openai-responses-partial-json-cleanup.test.ts +++ b/packages/ai/test/openai-responses-partial-json-cleanup.test.ts @@ -1,6 +1,6 @@ import type { ResponseStreamEvent } from "openai/resources/responses/responses.js"; import { describe, expect, it, vi } from "vitest"; -import { processResponsesStream } from "../src/providers/openai-responses-shared.ts"; +import { processResponsesStream } from "../src/api/openai-responses-shared.ts"; import type { AssistantMessage, AssistantMessageEvent, Model } from "../src/types.ts"; import { AssistantMessageEventStream } from "../src/utils/event-stream.ts"; diff --git a/packages/ai/test/scratch.ts b/packages/ai/test/scratch.ts index 949dccbe3..e41af1180 100644 --- a/packages/ai/test/scratch.ts +++ b/packages/ai/test/scratch.ts @@ -2,10 +2,12 @@ // Run from packages/ai: node test/scratch.ts // Requires ANTHROPIC_API_KEY. +import { anthropicMessagesApi } from "../src/api/anthropic-messages.lazy.ts"; import { createModels, getModels, type Provider } from "../src/models.ts"; -import { streamAnthropic, streamSimpleAnthropic } from "../src/providers/register-builtins.ts"; import type { Context } from "../src/types.ts"; +const anthropicApi = anthropicMessagesApi(); + // --------------------------------------------------------------------------- // 1. Define a provider. In the final design this comes from // `@earendil-works/pi-ai/providers/anthropic` as `anthropicProvider()`; @@ -33,8 +35,8 @@ const anthropic: Provider<"anthropic-messages"> = { getModels: async () => getModels("anthropic"), // shared lazy API implementation (loads the SDK on first request) - stream: streamAnthropic, - streamSimple: streamSimpleAnthropic, + stream: anthropicApi.stream, + streamSimple: anthropicApi.streamSimple, }; // --------------------------------------------------------------------------- diff --git a/packages/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts b/packages/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts index 4d858835a..24f218df7 100644 --- a/packages/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts +++ b/packages/ai/test/transform-messages-copilot-openai-to-anthropic.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { transformMessages } from "../src/providers/transform-messages.ts"; +import { transformMessages } from "../src/api/transform-messages.ts"; import type { AssistantMessage, Message, Model, ToolCall } from "../src/types.ts"; // Normalize function matching what anthropic.ts uses diff --git a/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts b/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts index 51426362e..cfa80dbe7 100644 --- a/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts +++ b/packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts @@ -153,7 +153,7 @@ async function refreshAnthropicToken(credentials: OAuthCredentials): Promise), compat: { @@ -336,7 +336,11 @@ export function streamGitLabDuo( context, streamOptions, ) - : streamSimpleOpenAIResponses(modelWithBaseUrl as Model<"openai-responses">, context, streamOptions); + : openAIResponsesApi().streamSimple( + modelWithBaseUrl as Model<"openai-responses">, + context, + streamOptions, + ); for await (const event of innerStream) stream.push(event); stream.end(); diff --git a/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts b/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts index a6338d144..6d00a1a01 100644 --- a/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts +++ b/packages/coding-agent/test/sdk-codex-cache-probe-tool-loop.ts @@ -23,8 +23,8 @@ import { } from "@earendil-works/pi-ai"; import { getOpenAICodexWebSocketDebugStats, - streamSimpleOpenAICodexResponses, -} from "../../ai/src/providers/openai-codex-responses.ts"; + streamSimple as streamSimpleOpenAICodexResponses, +} from "../../ai/src/api/openai-codex-responses.ts"; import { AuthStorage } from "../src/core/auth-storage.ts"; import { createExtensionRuntime } from "../src/core/extensions/loader.ts"; import type { ToolDefinition } from "../src/core/extensions/types.ts";