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/.
This commit is contained in:
Mario Zechner
2026-06-10 20:08:59 +02:00
parent ee276e4db5
commit ba93da9a93
66 changed files with 283 additions and 560 deletions
+19 -23
View File
@@ -33,23 +33,23 @@ packages/ai/src/
auth/ # auth method types, helpers, login callbacks auth/ # auth method types, helpers, login callbacks
api/ # API implementations and lazy wrappers api/ # API implementations and lazy wrappers
openai-completions.ts # real implementation, imports SDKs, exports stream/streamSimple openai-completions.ts # real implementation, imports SDKs, exports stream/streamSimple
openai-completions-lazy.ts openai-completions.lazy.ts
openai-responses.ts openai-responses.ts
openai-responses-lazy.ts openai-responses.lazy.ts
openai-codex-responses.ts openai-codex-responses.ts
openai-codex-responses-lazy.ts openai-codex-responses.lazy.ts
azure-openai-responses.ts azure-openai-responses.ts
azure-openai-responses-lazy.ts azure-openai-responses.lazy.ts
anthropic-messages.ts anthropic-messages.ts
anthropic-messages-lazy.ts anthropic-messages.lazy.ts
google-generative-ai.ts google-generative-ai.ts
google-generative-ai-lazy.ts google-generative-ai.lazy.ts
google-vertex.ts google-vertex.ts
google-vertex-lazy.ts google-vertex.lazy.ts
mistral-conversations.ts mistral-conversations.ts
mistral-conversations-lazy.ts mistral-conversations.lazy.ts
bedrock-converse-stream.ts bedrock-converse-stream.ts
bedrock-converse-stream-lazy.ts bedrock-converse-stream.lazy.ts
lazy.ts # lazyStream()/lazyApi() helpers lazy.ts # lazyStream()/lazyApi() helpers
(shared helpers: openai-responses-shared, google-shared, transform-messages, ...) (shared helpers: openai-responses-shared, google-shared, transform-messages, ...)
providers/ # concrete provider factories and per-provider catalogs providers/ # concrete provider factories and per-provider catalogs
@@ -315,22 +315,18 @@ export function stream(model, context, options) { ... }
export function streamSimple(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 ```ts
export interface ProviderStreams { export interface ProviderStreams {
stream<TApi extends Api>( stream(model: Model<Api>, context: Context, options?: StreamOptions): AssistantMessageEventStream;
model: Model<TApi>,
context: Context,
options?: ApiStreamOptions<TApi>,
): AssistantMessageEventStream;
streamSimple(model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream; streamSimple(model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
} }
// src/api/lazy.ts // src/api/lazy.ts
export function lazyApi(load: () => Promise<ProviderStreams>): ProviderStreams; export function lazyApi(load: () => Promise<ProviderStreams>): ProviderStreams;
// src/api/anthropic-messages-lazy.ts // src/api/anthropic-messages.lazy.ts
export const anthropicMessagesApi = (): ProviderStreams => lazyApi(() => import("./anthropic-messages.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: Many concrete providers share an API implementation (OpenAI-completions: OpenRouter, Groq, Cerebras, xAI, ZAI, ...). They share lazy API objects by reference:
```ts ```ts
import { openAICompletionsApi } from "../api/openai-completions-lazy.ts"; import { openAICompletionsApi } from "../api/openai-completions.lazy.ts";
export function openrouterProvider(): Provider { export function openrouterProvider(): Provider {
return createProvider({ 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/` ### Phase 2 — `src/api/`
- [ ] Move stream implementations from `src/providers/` to `src/api/`, renamed by API id (`anthropic.ts` -> `api/anthropic-messages.ts`, etc.). - [x] 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`. - [x] 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/`. - [x] Move shared helpers (`openai-responses-shared`, `google-shared`, `transform-messages`, `openai-prompt-cache`, `github-copilot-headers`, `cloudflare`, `simple-options`) to `src/api/`.
- [ ] Extract `lazyStream()`/`lazyApi()` into `src/api/lazy.ts`. - [x] Extract `lazyStream()`/`lazyApi()` into `src/api/lazy.ts`.
- [ ] Add `*-lazy.ts` wrappers per API; bedrock keeps node-only import trick and `setBedrockProviderModule()`. - [x] Add `*.lazy.ts` wrappers per API; bedrock keeps node-only import trick and `setBedrockProviderModule()`.
- [ ] Delete `providers/register-builtins.ts`. - [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 ### Phase 3 — provider factories + catalogs
+16 -16
View File
@@ -11,36 +11,36 @@
"import": "./dist/index.js" "import": "./dist/index.js"
}, },
"./anthropic": { "./anthropic": {
"types": "./dist/providers/anthropic.d.ts", "types": "./dist/api/anthropic-messages.d.ts",
"import": "./dist/providers/anthropic.js" "import": "./dist/api/anthropic-messages.js"
}, },
"./azure-openai-responses": { "./azure-openai-responses": {
"types": "./dist/providers/azure-openai-responses.d.ts", "types": "./dist/api/azure-openai-responses.d.ts",
"import": "./dist/providers/azure-openai-responses.js" "import": "./dist/api/azure-openai-responses.js"
}, },
"./google": { "./google": {
"types": "./dist/providers/google.d.ts", "types": "./dist/api/google-generative-ai.d.ts",
"import": "./dist/providers/google.js" "import": "./dist/api/google-generative-ai.js"
}, },
"./google-vertex": { "./google-vertex": {
"types": "./dist/providers/google-vertex.d.ts", "types": "./dist/api/google-vertex.d.ts",
"import": "./dist/providers/google-vertex.js" "import": "./dist/api/google-vertex.js"
}, },
"./mistral": { "./mistral": {
"types": "./dist/providers/mistral.d.ts", "types": "./dist/api/mistral-conversations.d.ts",
"import": "./dist/providers/mistral.js" "import": "./dist/api/mistral-conversations.js"
}, },
"./openai-codex-responses": { "./openai-codex-responses": {
"types": "./dist/providers/openai-codex-responses.d.ts", "types": "./dist/api/openai-codex-responses.d.ts",
"import": "./dist/providers/openai-codex-responses.js" "import": "./dist/api/openai-codex-responses.js"
}, },
"./openai-completions": { "./openai-completions": {
"types": "./dist/providers/openai-completions.d.ts", "types": "./dist/api/openai-completions.d.ts",
"import": "./dist/providers/openai-completions.js" "import": "./dist/api/openai-completions.js"
}, },
"./openai-responses": { "./openai-responses": {
"types": "./dist/providers/openai-responses.d.ts", "types": "./dist/api/openai-responses.d.ts",
"import": "./dist/providers/openai-responses.js" "import": "./dist/api/openai-responses.js"
}, },
"./oauth": { "./oauth": {
"types": "./dist/oauth.d.ts", "types": "./dist/oauth.d.ts",
+1 -1
View File
@@ -8,7 +8,7 @@ import {
CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL, CLOUDFLARE_AI_GATEWAY_COMPAT_BASE_URL,
CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL, CLOUDFLARE_AI_GATEWAY_OPENAI_BASE_URL,
CLOUDFLARE_WORKERS_AI_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"; import type { AnthropicMessagesCompat, Api, KnownProvider, Model, OpenAICompletionsCompat } from "../src/types.ts";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -0,0 +1,4 @@
import type { ProviderStreams } from "../types.ts";
import { lazyApi } from "./lazy.ts";
export const anthropicMessagesApi = (): ProviderStreams => lazyApi(() => import("./anthropic-messages.ts"));
@@ -186,7 +186,7 @@ export interface AnthropicOptions extends StreamOptions {
* Enable extended thinking. * Enable extended thinking.
* For adaptive thinking models: the model decides when/how much to think. * For adaptive thinking models: the model decides when/how much to think.
* For older models: uses budget-based thinking with thinkingBudgetTokens. * 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). * a simple reasoning level to this option, or callers set it explicitly).
*/ */
thinkingEnabled?: boolean; thinkingEnabled?: boolean;
@@ -205,7 +205,7 @@ export interface AnthropicOptions extends StreamOptions {
* - "medium": Moderate thinking, may skip for simple queries * - "medium": Moderate thinking, may skip for simple queries
* - "low": Minimal thinking, skips for simple tasks * - "low": Minimal thinking, skips for simple tasks
* Ignored for older models. * Ignored for older models.
* Default: omitted unless `streamSimpleAnthropic()` maps a simple reasoning * Default: omitted unless `streamSimple()` maps a simple reasoning
* level to this option. * level to this option.
*/ */
effort?: AnthropicEffort; 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">, model: Model<"anthropic-messages">,
context: Context, context: Context,
options?: AnthropicOptions, 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">, model: Model<"anthropic-messages">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
@@ -745,14 +745,14 @@ export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleS
const base = buildBaseOptions(model, options, apiKey); const base = buildBaseOptions(model, options, apiKey);
if (!options?.reasoning) { 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 models with adaptive thinking: use an effort level.
// For older models: use budget-based thinking. // For older models: use budget-based thinking.
if (model.compat?.forceAdaptiveThinking === true) { if (model.compat?.forceAdaptiveThinking === true) {
const effort = mapThinkingLevelToEffort(model, options.reasoning); const effort = mapThinkingLevelToEffort(model, options.reasoning);
return streamAnthropic(model, context, { return stream(model, context, {
...base, ...base,
thinkingEnabled: true, thinkingEnabled: true,
effort, effort,
@@ -768,7 +768,7 @@ export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleS
options.thinkingBudgets, options.thinkingBudgets,
); );
return streamAnthropic(model, context, { return stream(model, context, {
...base, ...base,
maxTokens: adjusted.maxTokens, maxTokens: adjusted.maxTokens,
thinkingEnabled: true, thinkingEnabled: true,
@@ -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"));
@@ -69,7 +69,7 @@ export interface AzureOpenAIResponsesOptions extends StreamOptions {
/** /**
* Generate function for Azure OpenAI Responses API * 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">, model: Model<"azure-openai-responses">,
context: Context, context: Context,
options?: AzureOpenAIResponsesOptions, options?: AzureOpenAIResponsesOptions,
@@ -147,7 +147,7 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses"
return stream; return stream;
}; };
export const streamSimpleAzureOpenAIResponses: StreamFunction<"azure-openai-responses", SimpleStreamOptions> = ( export const streamSimple: StreamFunction<"azure-openai-responses", SimpleStreamOptions> = (
model: Model<"azure-openai-responses">, model: Model<"azure-openai-responses">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
@@ -161,7 +161,7 @@ export const streamSimpleAzureOpenAIResponses: StreamFunction<"azure-openai-resp
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined; const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
return streamAzureOpenAIResponses(model, context, { return stream(model, context, {
...base, ...base,
reasoningEffort, reasoningEffort,
} satisfies AzureOpenAIResponsesOptions); } satisfies AzureOpenAIResponsesOptions);
@@ -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<unknown> => {
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),
);
@@ -90,7 +90,7 @@ type Block = (TextContent | ThinkingContent | ToolCall) & { index?: number; part
const EMPTY_TEXT_PLACEHOLDER = "<empty>"; const EMPTY_TEXT_PLACEHOLDER = "<empty>";
export const streamBedrock: StreamFunction<"bedrock-converse-stream", BedrockOptions> = ( export const stream: StreamFunction<"bedrock-converse-stream", BedrockOptions> = (
model: Model<"bedrock-converse-stream">, model: Model<"bedrock-converse-stream">,
context: Context, context: Context,
options: BedrockOptions = {}, 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" }); 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">, model: Model<"bedrock-converse-stream">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
): AssistantMessageEventStream => { ): AssistantMessageEventStream => {
const base = buildBaseOptions(model, options, undefined); const base = buildBaseOptions(model, options, undefined);
if (!options?.reasoning) { 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 (isAnthropicClaudeModel(model)) {
if (supportsAdaptiveThinking(model.id, model.name)) { if (supportsAdaptiveThinking(model.id, model.name)) {
return streamBedrock(model, context, { return stream(model, context, {
...base, ...base,
reasoning: options.reasoning, reasoning: options.reasoning,
thinkingBudgets: options.thinkingBudgets, thinkingBudgets: options.thinkingBudgets,
@@ -380,7 +380,7 @@ export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", Simp
options.thinkingBudgets, options.thinkingBudgets,
); );
return streamBedrock(model, context, { return stream(model, context, {
...base, ...base,
maxTokens: adjusted.maxTokens, maxTokens: adjusted.maxTokens,
reasoning: options.reasoning, reasoning: options.reasoning,
@@ -391,7 +391,7 @@ export const streamSimpleBedrock: StreamFunction<"bedrock-converse-stream", Simp
} satisfies BedrockOptions); } satisfies BedrockOptions);
} }
return streamBedrock(model, context, { return stream(model, context, {
...base, ...base,
reasoning: options.reasoning, reasoning: options.reasoning,
thinkingBudgets: options.thinkingBudgets, thinkingBudgets: options.thinkingBudgets,
@@ -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"));
@@ -44,7 +44,7 @@ export interface GoogleOptions extends StreamOptions {
// Counter for generating unique tool call IDs // Counter for generating unique tool call IDs
let toolCallCounter = 0; let toolCallCounter = 0;
export const streamGoogle: StreamFunction<"google-generative-ai", GoogleOptions> = ( export const stream: StreamFunction<"google-generative-ai", GoogleOptions> = (
model: Model<"google-generative-ai">, model: Model<"google-generative-ai">,
context: Context, context: Context,
options?: GoogleOptions, options?: GoogleOptions,
@@ -277,7 +277,7 @@ export const streamGoogle: StreamFunction<"google-generative-ai", GoogleOptions>
return stream; return stream;
}; };
export const streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleStreamOptions> = ( export const streamSimple: StreamFunction<"google-generative-ai", SimpleStreamOptions> = (
model: Model<"google-generative-ai">, model: Model<"google-generative-ai">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
@@ -289,7 +289,7 @@ export const streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleSt
const base = buildBaseOptions(model, options, apiKey); const base = buildBaseOptions(model, options, apiKey);
if (!options?.reasoning) { 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); 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">; const googleModel = model as Model<"google-generative-ai">;
if (isGemini3ProModel(googleModel) || isGemini3FlashModel(googleModel) || isGemma4Model(googleModel)) { if (isGemini3ProModel(googleModel) || isGemini3FlashModel(googleModel) || isGemma4Model(googleModel)) {
return streamGoogle(model, context, { return stream(model, context, {
...base, ...base,
thinking: { thinking: {
enabled: true, enabled: true,
@@ -306,7 +306,7 @@ export const streamSimpleGoogle: StreamFunction<"google-generative-ai", SimpleSt
} satisfies GoogleOptions); } satisfies GoogleOptions);
} }
return streamGoogle(model, context, { return stream(model, context, {
...base, ...base,
thinking: { thinking: {
enabled: true, enabled: true,
@@ -0,0 +1,4 @@
import type { ProviderStreams } from "../types.ts";
import { lazyApi } from "./lazy.ts";
export const googleVertexApi = (): ProviderStreams => lazyApi(() => import("./google-vertex.ts"));
@@ -60,7 +60,7 @@ const THINKING_LEVEL_MAP: Record<GoogleThinkingLevel, ThinkingLevel> = {
// Counter for generating unique tool call IDs // Counter for generating unique tool call IDs
let toolCallCounter = 0; let toolCallCounter = 0;
export const streamGoogleVertex: StreamFunction<"google-vertex", GoogleVertexOptions> = ( export const stream: StreamFunction<"google-vertex", GoogleVertexOptions> = (
model: Model<"google-vertex">, model: Model<"google-vertex">,
context: Context, context: Context,
options?: GoogleVertexOptions, options?: GoogleVertexOptions,
@@ -292,14 +292,14 @@ export const streamGoogleVertex: StreamFunction<"google-vertex", GoogleVertexOpt
return stream; return stream;
}; };
export const streamSimpleGoogleVertex: StreamFunction<"google-vertex", SimpleStreamOptions> = ( export const streamSimple: StreamFunction<"google-vertex", SimpleStreamOptions> = (
model: Model<"google-vertex">, model: Model<"google-vertex">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
): AssistantMessageEventStream => { ): AssistantMessageEventStream => {
const base = buildBaseOptions(model, options, undefined); const base = buildBaseOptions(model, options, undefined);
if (!options?.reasoning) { if (!options?.reasoning) {
return streamGoogleVertex(model, context, { return stream(model, context, {
...base, ...base,
thinking: { enabled: false }, thinking: { enabled: false },
} satisfies GoogleVertexOptions); } satisfies GoogleVertexOptions);
@@ -310,7 +310,7 @@ export const streamSimpleGoogleVertex: StreamFunction<"google-vertex", SimpleStr
const geminiModel = model as unknown as Model<"google-generative-ai">; const geminiModel = model as unknown as Model<"google-generative-ai">;
if (isGemini3ProModel(geminiModel) || isGemini3FlashModel(geminiModel)) { if (isGemini3ProModel(geminiModel) || isGemini3FlashModel(geminiModel)) {
return streamGoogleVertex(model, context, { return stream(model, context, {
...base, ...base,
thinking: { thinking: {
enabled: true, enabled: true,
@@ -319,7 +319,7 @@ export const streamSimpleGoogleVertex: StreamFunction<"google-vertex", SimpleStr
} satisfies GoogleVertexOptions); } satisfies GoogleVertexOptions);
} }
return streamGoogleVertex(model, context, { return stream(model, context, {
...base, ...base,
thinking: { thinking: {
enabled: true, enabled: true,
+15 -1
View File
@@ -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"; import { AssistantMessageEventStream } from "../utils/event-stream.ts";
function createSetupErrorMessage(model: Model<Api>, error: unknown): AssistantMessage { function createSetupErrorMessage(model: Model<Api>, error: unknown): AssistantMessage {
@@ -54,3 +54,17 @@ export function lazyStream(
return outer; 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>): 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)),
};
}
@@ -0,0 +1,4 @@
import type { ProviderStreams } from "../types.ts";
import { lazyApi } from "./lazy.ts";
export const mistralConversationsApi = (): ProviderStreams => lazyApi(() => import("./mistral-conversations.ts"));
@@ -45,7 +45,7 @@ export interface MistralOptions extends StreamOptions {
/** /**
* Stream responses from Mistral using `chat.stream`. * 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">, model: Model<"mistral-conversations">,
context: Context, context: Context,
options?: MistralOptions, options?: MistralOptions,
@@ -107,7 +107,7 @@ export const streamMistral: StreamFunction<"mistral-conversations", MistralOptio
/** /**
* Maps provider-agnostic `SimpleStreamOptions` to Mistral options. * 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">, model: Model<"mistral-conversations">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
@@ -122,7 +122,7 @@ export const streamSimpleMistral: StreamFunction<"mistral-conversations", Simple
const reasoning = clampedReasoning === "off" ? undefined : clampedReasoning; const reasoning = clampedReasoning === "off" ? undefined : clampedReasoning;
const shouldUseReasoning = model.reasoning && reasoning !== undefined; const shouldUseReasoning = model.reasoning && reasoning !== undefined;
return streamMistral(model, context, { return stream(model, context, {
...base, ...base,
promptMode: shouldUseReasoning && usesPromptModeReasoning(model) ? "reasoning" : undefined, promptMode: shouldUseReasoning && usesPromptModeReasoning(model) ? "reasoning" : undefined,
reasoningEffort: reasoningEffort:
@@ -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"));
@@ -191,7 +191,7 @@ function createSSEHeaderTimeout(): { signal: AbortSignal; clear: () => void; err
// Main Stream Function // Main Stream Function
// ============================================================================ // ============================================================================
export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses", OpenAICodexResponsesOptions> = ( export const stream: StreamFunction<"openai-codex-responses", OpenAICodexResponsesOptions> = (
model: Model<"openai-codex-responses">, model: Model<"openai-codex-responses">,
context: Context, context: Context,
options?: OpenAICodexResponsesOptions, options?: OpenAICodexResponsesOptions,
@@ -404,7 +404,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
return stream; return stream;
}; };
export const streamSimpleOpenAICodexResponses: StreamFunction<"openai-codex-responses", SimpleStreamOptions> = ( export const streamSimple: StreamFunction<"openai-codex-responses", SimpleStreamOptions> = (
model: Model<"openai-codex-responses">, model: Model<"openai-codex-responses">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
@@ -418,7 +418,7 @@ export const streamSimpleOpenAICodexResponses: StreamFunction<"openai-codex-resp
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined; const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
return streamOpenAICodexResponses(model, context, { return stream(model, context, {
...base, ...base,
reasoningEffort, reasoningEffort,
} satisfies OpenAICodexResponsesOptions); } satisfies OpenAICodexResponsesOptions);
@@ -0,0 +1,4 @@
import type { ProviderStreams } from "../types.ts";
import { lazyApi } from "./lazy.ts";
export const openAICompletionsApi = (): ProviderStreams => lazyApi(() => import("./openai-completions.ts"));
@@ -108,7 +108,7 @@ function resolveCacheRetention(cacheRetention?: CacheRetention): CacheRetention
return "short"; return "short";
} }
export const streamOpenAICompletions: StreamFunction<"openai-completions", OpenAICompletionsOptions> = ( export const stream: StreamFunction<"openai-completions", OpenAICompletionsOptions> = (
model: Model<"openai-completions">, model: Model<"openai-completions">,
context: Context, context: Context,
options?: OpenAICompletionsOptions, options?: OpenAICompletionsOptions,
@@ -425,7 +425,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions", OpenA
return stream; return stream;
}; };
export const streamSimpleOpenAICompletions: StreamFunction<"openai-completions", SimpleStreamOptions> = ( export const streamSimple: StreamFunction<"openai-completions", SimpleStreamOptions> = (
model: Model<"openai-completions">, model: Model<"openai-completions">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
@@ -440,7 +440,7 @@ export const streamSimpleOpenAICompletions: StreamFunction<"openai-completions",
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
const toolChoice = (options as OpenAICompletionsOptions | undefined)?.toolChoice; const toolChoice = (options as OpenAICompletionsOptions | undefined)?.toolChoice;
return streamOpenAICompletions(model, context, { return stream(model, context, {
...base, ...base,
reasoningEffort, reasoningEffort,
toolChoice, toolChoice,
@@ -0,0 +1,4 @@
import type { ProviderStreams } from "../types.ts";
import { lazyApi } from "./lazy.ts";
export const openAIResponsesApi = (): ProviderStreams => lazyApi(() => import("./openai-responses.ts"));
@@ -78,7 +78,7 @@ export interface OpenAIResponsesOptions extends StreamOptions {
/** /**
* Generate function for OpenAI Responses API * Generate function for OpenAI Responses API
*/ */
export const streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIResponsesOptions> = ( export const stream: StreamFunction<"openai-responses", OpenAIResponsesOptions> = (
model: Model<"openai-responses">, model: Model<"openai-responses">,
context: Context, context: Context,
options?: OpenAIResponsesOptions, options?: OpenAIResponsesOptions,
@@ -159,7 +159,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses", OpenAIRes
return stream; return stream;
}; };
export const streamSimpleOpenAIResponses: StreamFunction<"openai-responses", SimpleStreamOptions> = ( export const streamSimple: StreamFunction<"openai-responses", SimpleStreamOptions> = (
model: Model<"openai-responses">, model: Model<"openai-responses">,
context: Context, context: Context,
options?: SimpleStreamOptions, options?: SimpleStreamOptions,
@@ -173,7 +173,7 @@ export const streamSimpleOpenAIResponses: StreamFunction<"openai-responses", Sim
const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined; const clampedReasoning = options?.reasoning ? clampThinkingLevel(model, options.reasoning) : undefined;
const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning; const reasoningEffort = clampedReasoning === "off" ? undefined : clampedReasoning;
return streamOpenAIResponses(model, context, { return stream(model, context, {
...base, ...base,
reasoningEffort, reasoningEffort,
} satisfies OpenAIResponsesOptions); } satisfies OpenAIResponsesOptions);
+3 -3
View File
@@ -1,6 +1,6 @@
import { streamBedrock, streamSimpleBedrock } from "./providers/amazon-bedrock.ts"; import { stream, streamSimple } from "./api/bedrock-converse-stream.ts";
export const bedrockProviderModule = { export const bedrockProviderModule = {
streamBedrock, stream,
streamSimpleBedrock, streamSimple,
}; };
+19 -14
View File
@@ -1,7 +1,26 @@
export type { Static, TSchema } from "typebox"; export type { Static, TSchema } from "typebox";
export { Type } 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/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 "./api-registry.ts";
export * from "./auth/context.ts"; export * from "./auth/context.ts";
export * from "./auth/credential-store.ts"; export * from "./auth/credential-store.ts";
@@ -11,22 +30,8 @@ export * from "./image-models.ts";
export * from "./images.ts"; export * from "./images.ts";
export * from "./images-api-registry.ts"; export * from "./images-api-registry.ts";
export * from "./models.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 * 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 * 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 "./session-resources.ts";
export * from "./stream.ts"; export * from "./stream.ts";
export * from "./types.ts"; export * from "./types.ts";
@@ -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<TApi>, context: Context, options?: TOptions) => AsyncIterable<AssistantMessageEvent>;
streamSimple: (
model: Model<TApi>,
context: Context,
options?: TSimpleOptions,
) => AsyncIterable<AssistantMessageEvent>;
}
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<AssistantMessageEvent>;
streamSimpleBedrock: (
model: Model<"bedrock-converse-stream">,
context: Context,
options?: SimpleStreamOptions,
) => AsyncIterable<AssistantMessageEvent>;
}
const importNodeOnlyProvider = (specifier: string): Promise<unknown> => {
const runtimeSpecifier = import.meta.url.endsWith(".js") ? specifier.replace(/\.ts$/, ".js") : specifier;
return import(runtimeSpecifier);
};
let anthropicProviderModulePromise:
| Promise<LazyProviderModule<"anthropic-messages", AnthropicOptions, SimpleStreamOptions>>
| undefined;
let azureOpenAIResponsesProviderModulePromise:
| Promise<LazyProviderModule<"azure-openai-responses", AzureOpenAIResponsesOptions, SimpleStreamOptions>>
| undefined;
let googleProviderModulePromise:
| Promise<LazyProviderModule<"google-generative-ai", GoogleOptions, SimpleStreamOptions>>
| undefined;
let googleVertexProviderModulePromise:
| Promise<LazyProviderModule<"google-vertex", GoogleVertexOptions, SimpleStreamOptions>>
| undefined;
let mistralProviderModulePromise:
| Promise<LazyProviderModule<"mistral-conversations", MistralOptions, SimpleStreamOptions>>
| undefined;
let openAICodexResponsesProviderModulePromise:
| Promise<LazyProviderModule<"openai-codex-responses", OpenAICodexResponsesOptions, SimpleStreamOptions>>
| undefined;
let openAICompletionsProviderModulePromise:
| Promise<LazyProviderModule<"openai-completions", OpenAICompletionsOptions, SimpleStreamOptions>>
| undefined;
let openAIResponsesProviderModulePromise:
| Promise<LazyProviderModule<"openai-responses", OpenAIResponsesOptions, SimpleStreamOptions>>
| undefined;
let bedrockProviderModuleOverride:
| LazyProviderModule<"bedrock-converse-stream", BedrockOptions, SimpleStreamOptions>
| undefined;
let bedrockProviderModulePromise:
| Promise<LazyProviderModule<"bedrock-converse-stream", BedrockOptions, SimpleStreamOptions>>
| undefined;
export function setBedrockProviderModule(module: BedrockProviderModule): void {
bedrockProviderModuleOverride = {
stream: module.streamBedrock,
streamSimple: module.streamSimpleBedrock,
};
}
function forwardStream(target: AssistantMessageEventStream, source: AsyncIterable<AssistantMessageEvent>): void {
(async () => {
for await (const event of source) {
target.push(event);
}
target.end();
})();
}
function createLazyLoadErrorMessage<TApi extends Api>(model: Model<TApi>, 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<TApi extends Api, TOptions extends StreamOptions, TSimpleOptions extends SimpleStreamOptions>(
loadModule: () => Promise<LazyProviderModule<TApi, TOptions, TSimpleOptions>>,
): StreamFunction<TApi, TOptions> {
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<LazyProviderModule<TApi, TOptions, TSimpleOptions>>): StreamFunction<TApi, TSimpleOptions> {
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();
+36 -3
View File
@@ -1,6 +1,13 @@
import "./providers/register-builtins.ts"; import { anthropicMessagesApi } from "./api/anthropic-messages.lazy.ts";
import { azureOpenAIResponsesApi } from "./api/azure-openai-responses.lazy.ts";
import { getApiProvider } from "./api-registry.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 { getEnvApiKey } from "./env-api-keys.ts";
import type { import type {
Api, Api,
@@ -9,12 +16,38 @@ import type {
Context, Context,
Model, Model,
ProviderStreamOptions, ProviderStreamOptions,
ProviderStreams,
SimpleStreamOptions, SimpleStreamOptions,
StreamOptions, StreamOptions,
} from "./types.ts"; } from "./types.ts";
export { getEnvApiKey } from "./env-api-keys.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 { function hasExplicitApiKey(apiKey: string | undefined): apiKey is string {
return typeof apiKey === "string" && apiKey.trim().length > 0; return typeof apiKey === "string" && apiKey.trim().length > 0;
} }
+22 -9
View File
@@ -1,12 +1,12 @@
import type { BedrockOptions } from "./providers/amazon-bedrock.ts"; import type { AnthropicOptions } from "./api/anthropic-messages.ts";
import type { AnthropicOptions } from "./providers/anthropic.ts"; import type { AzureOpenAIResponsesOptions } from "./api/azure-openai-responses.ts";
import type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses.ts"; import type { BedrockOptions } from "./api/bedrock-converse-stream.ts";
import type { GoogleOptions } from "./providers/google.ts"; import type { GoogleOptions } from "./api/google-generative-ai.ts";
import type { GoogleVertexOptions } from "./providers/google-vertex.ts"; import type { GoogleVertexOptions } from "./api/google-vertex.ts";
import type { MistralOptions } from "./providers/mistral.ts"; import type { MistralOptions } from "./api/mistral-conversations.ts";
import type { OpenAICodexResponsesOptions } from "./providers/openai-codex-responses.ts"; import type { OpenAICodexResponsesOptions } from "./api/openai-codex-responses.ts";
import type { OpenAICompletionsOptions } from "./providers/openai-completions.ts"; import type { OpenAICompletionsOptions } from "./api/openai-completions.ts";
import type { OpenAIResponsesOptions } from "./providers/openai-responses.ts"; import type { OpenAIResponsesOptions } from "./api/openai-responses.ts";
import type { AssistantMessageDiagnostic } from "./utils/diagnostics.ts"; import type { AssistantMessageDiagnostic } from "./utils/diagnostics.ts";
import type { AssistantMessageEventStream } from "./utils/event-stream.ts"; import type { AssistantMessageEventStream } from "./utils/event-stream.ts";
@@ -191,6 +191,19 @@ export type ApiStreamOptions<TApi extends Api> = TApi extends keyof ApiOptionsMa
? ApiOptionsMap[TApi] ? ApiOptionsMap[TApi]
: StreamOptions & Record<string, unknown>; : StreamOptions & Record<string, unknown>;
/**
* 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<Api>, context: Context, options?: StreamOptions): AssistantMessageEventStream;
streamSimple(model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
}
export interface ImagesOptions { export interface ImagesOptions {
signal?: AbortSignal; signal?: AbortSignal;
apiKey?: string; apiKey?: string;
@@ -2,7 +2,7 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht
import type { AddressInfo } from "node:net"; import type { AddressInfo } from "node:net";
import { Type } from "typebox"; import { Type } from "typebox";
import { describe, expect, it } from "vitest"; 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"; import type { Context, Model, Tool } from "../src/types.ts";
interface CapturedRequest { interface CapturedRequest {
@@ -1,8 +1,8 @@
import type Anthropic from "@anthropic-ai/sdk"; import type Anthropic from "@anthropic-ai/sdk";
import { Type } from "typebox"; import { Type } from "typebox";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
import { getModel } from "../src/models.ts"; import { getModel } from "../src/models.ts";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import type { Context, ToolCall } from "../src/types.ts"; import type { Context, ToolCall } from "../src/types.ts";
function createSseResponse(events: Array<{ event: string; data: string }>): Response { function createSseResponse(events: Array<{ event: string; data: string }>): Response {
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 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 { getModel } from "../src/models.ts";
import { streamAzureOpenAIResponses } from "../src/providers/azure-openai-responses.ts";
import type { Context } from "../src/types.ts"; import type { Context } from "../src/types.ts";
interface CapturedAzureClientOptions { interface CapturedAzureClientOptions {
@@ -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 { getModel } from "../src/models.ts";
import { streamBedrock } from "../src/providers/amazon-bedrock.ts";
import type { Context, Message } from "../src/types.ts"; import type { Context, Message } from "../src/types.ts";
const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0"); const baseModel = getModel("amazon-bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0");
@@ -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 { 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"; import type { Context, Model } from "../src/types.ts";
const context: Context = { const context: Context = {
@@ -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 { getModel } from "../src/models.ts";
import { streamBedrock } from "../src/providers/amazon-bedrock.ts";
import type { Context, Model } from "../src/types.ts"; import type { Context, Model } from "../src/types.ts";
const context: Context = { const context: Context = {
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; 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 { getModel } from "../src/models.ts";
import { type BedrockOptions, streamBedrock } from "../src/providers/amazon-bedrock.ts";
import type { Context, Model } from "../src/types.ts"; import type { Context, Model } from "../src/types.ts";
import { hasBedrockCredentials } from "./bedrock-utils.ts"; import { hasBedrockCredentials } from "./bedrock-utils.ts";
+3 -3
View File
@@ -1,8 +1,8 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest"; 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 { 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 { stream } from "../src/stream.ts";
import type { Context, Model } from "../src/types.ts"; import type { Context, Model } from "../src/types.ts";
@@ -10,13 +10,13 @@ import { tmpdir } from "node:os";
import { join, resolve } from "node:path"; import { join, resolve } from "node:path";
import { Type } from "typebox"; import { Type } from "typebox";
import { AuthStorage } from "../../coding-agent/src/core/auth-storage.ts"; import { AuthStorage } from "../../coding-agent/src/core/auth-storage.ts";
import { getModel } from "../src/models.ts";
import { import {
closeOpenAICodexWebSocketSessions, closeOpenAICodexWebSocketSessions,
getOpenAICodexWebSocketDebugStats, getOpenAICodexWebSocketDebugStats,
resetOpenAICodexWebSocketDebugStats, resetOpenAICodexWebSocketDebugStats,
streamOpenAICodexResponses, stream as streamOpenAICodexResponses,
} from "../src/providers/openai-codex-responses.ts"; } 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"; import type { AssistantMessage, Context, Message, Model, Tool, ToolResultMessage, Transport } from "../src/types.ts";
type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh"; type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh";
+1 -1
View File
@@ -2,9 +2,9 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht
import type { AddressInfo } from "node:net"; import type { AddressInfo } from "node:net";
import { Type } from "typebox"; import { Type } from "typebox";
import { afterEach, describe, expect, it } from "vitest"; 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 { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
import { getModel, getModels } from "../src/models.ts"; import { getModel, getModels } from "../src/models.ts";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import type { Context, Model, Tool } from "../src/types.ts"; import type { Context, Model, Tool } from "../src/types.ts";
const originalFireworksApiKey = process.env.FIREWORKS_API_KEY; const originalFireworksApiKey = process.env.FIREWORKS_API_KEY;
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { stream as streamAnthropic } from "../src/api/anthropic-messages.ts";
import { getModel } from "../src/models.ts"; import { getModel } from "../src/models.ts";
import { streamAnthropic } from "../src/providers/anthropic.ts";
import type { Context } from "../src/types.ts"; import type { Context } from "../src/types.ts";
const mockState = vi.hoisted(() => ({ const mockState = vi.hoisted(() => ({
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; 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"; import type { Tool } from "../src/types.ts";
function makeTool(parameters: Record<string, unknown>): Tool { function makeTool(parameters: Record<string, unknown>): Tool {
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; 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"; import type { Context, Model } from "../src/types.ts";
function makeGemini3Model<TApi extends "google-generative-ai" | "google-vertex">( function makeGemini3Model<TApi extends "google-generative-ai" | "google-vertex">(
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; 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"; import type { Context, Model } from "../src/types.ts";
function makeModel<TApi extends "google-generative-ai">( function makeModel<TApi extends "google-generative-ai">(
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; 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)", () => { describe("Google thinking detection (thoughtSignature)", () => {
it("treats part.thought === true as thinking", () => { it("treats part.thought === true as thinking", () => {
@@ -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 { getModel } from "../src/models.ts";
import { streamGoogleVertex } from "../src/providers/google-vertex.ts";
import type { Context, Model } from "../src/types.ts"; import type { Context, Model } from "../src/types.ts";
const model = getModel("google-vertex", "gemini-3-flash-preview"); const model = getModel("google-vertex", "gemini-3-flash-preview");
+2 -2
View File
@@ -66,7 +66,7 @@ describe("lazy provider module loading", () => {
expect(result.loadedSpecifiers).toEqual([]); 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 result = runProbe(`
const model = { const model = {
id: "claude-sonnet-4-6", id: "claude-sonnet-4-6",
@@ -81,7 +81,7 @@ describe("lazy provider module loading", () => {
maxTokens: 8192, maxTokens: 8192,
}; };
const context = { messages: [{ role: "user", content: "hi" }] }; 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"]); expect(result.loadedSpecifiers).toEqual(["@anthropic-ai/sdk"]);
+3 -3
View File
@@ -5,9 +5,9 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { import {
getOpenAICodexWebSocketDebugStats, getOpenAICodexWebSocketDebugStats,
resetOpenAICodexWebSocketDebugStats, resetOpenAICodexWebSocketDebugStats,
streamOpenAICodexResponses, stream as streamOpenAICodexResponses,
streamSimpleOpenAICodexResponses, streamSimple as streamSimpleOpenAICodexResponses,
} from "../src/providers/openai-codex-responses.ts"; } from "../src/api/openai-codex-responses.ts";
import type { Context, Model } from "../src/types.ts"; import type { Context, Model } from "../src/types.ts";
const originalAgentDir = process.env.PI_CODING_AGENT_DIR; const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
@@ -1,7 +1,7 @@
import { Type } from "typebox"; import { Type } from "typebox";
import { beforeEach, describe, expect, it, vi } from "vitest"; 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 { getModel } from "../src/models.ts";
import { streamOpenAICompletions } from "../src/providers/openai-completions.ts";
import type { Model } from "../src/types.ts"; import type { Model } from "../src/types.ts";
interface CacheControl { interface CacheControl {
@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; 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 { getModel } from "../src/models.ts";
import { streamOpenAICompletions } from "../src/providers/openai-completions.ts";
import type { Model } from "../src/types.ts"; import type { Model } from "../src/types.ts";
interface FakeOpenAIClientOptions { interface FakeOpenAIClientOptions {
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; 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"; import type { Context, Model } from "../src/types.ts";
const mockState = vi.hoisted(() => ({ const mockState = vi.hoisted(() => ({
@@ -2,7 +2,7 @@ import { once } from "node:events";
import http from "node:http"; import http from "node:http";
import type { AddressInfo } from "node:net"; import type { AddressInfo } from "node:net";
import { afterEach, describe, expect, it } from "vitest"; 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 { import type {
AssistantMessage, AssistantMessage,
AssistantMessageEvent, AssistantMessageEvent,
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { convertMessages } from "../src/api/openai-completions.ts";
import { getModel } from "../src/models.ts"; import { getModel } from "../src/models.ts";
import { convertMessages } from "../src/providers/openai-completions.ts";
import type { import type {
AssistantMessage, AssistantMessage,
Context, Context,
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, vi } from "vitest"; 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 { getModel } from "../src/models.ts";
import { streamOpenAIResponses } from "../src/providers/openai-responses.ts";
import type { Model } from "../src/types.ts"; import type { Model } from "../src/types.ts";
type CapturedHeaders = Headers | string[][] | Record<string, string | readonly string[]> | undefined; type CapturedHeaders = Headers | string[][] | Record<string, string | readonly string[]> | undefined;
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { convertResponsesMessages } from "../src/api/openai-responses-shared.ts";
import { getModel } from "../src/models.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 type { AssistantMessage, Context, ToolResultMessage, Usage } from "../src/types.ts";
import { shortHash } from "../src/utils/hash.ts"; import { shortHash } from "../src/utils/hash.ts";
@@ -1,7 +1,7 @@
import type { ResponseOutputMessage } from "openai/resources/responses/responses.js"; import type { ResponseOutputMessage } from "openai/resources/responses/responses.js";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { convertResponsesMessages } from "../src/api/openai-responses-shared.ts";
import { getModel } from "../src/models.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"; import type { AssistantMessage, Context, Usage } from "../src/types.ts";
const usage: Usage = { const usage: Usage = {
@@ -1,6 +1,6 @@
import type { ResponseStreamEvent } from "openai/resources/responses/responses.js"; import type { ResponseStreamEvent } from "openai/resources/responses/responses.js";
import { describe, expect, it, vi } from "vitest"; 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 type { AssistantMessage, AssistantMessageEvent, Model } from "../src/types.ts";
import { AssistantMessageEventStream } from "../src/utils/event-stream.ts"; import { AssistantMessageEventStream } from "../src/utils/event-stream.ts";
+5 -3
View File
@@ -2,10 +2,12 @@
// Run from packages/ai: node test/scratch.ts // Run from packages/ai: node test/scratch.ts
// Requires ANTHROPIC_API_KEY. // Requires ANTHROPIC_API_KEY.
import { anthropicMessagesApi } from "../src/api/anthropic-messages.lazy.ts";
import { createModels, getModels, type Provider } from "../src/models.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"; import type { Context } from "../src/types.ts";
const anthropicApi = anthropicMessagesApi();
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// 1. Define a provider. In the final design this comes from // 1. Define a provider. In the final design this comes from
// `@earendil-works/pi-ai/providers/anthropic` as `anthropicProvider()`; // `@earendil-works/pi-ai/providers/anthropic` as `anthropicProvider()`;
@@ -33,8 +35,8 @@ const anthropic: Provider<"anthropic-messages"> = {
getModels: async () => getModels("anthropic"), getModels: async () => getModels("anthropic"),
// shared lazy API implementation (loads the SDK on first request) // shared lazy API implementation (loads the SDK on first request)
stream: streamAnthropic, stream: anthropicApi.stream,
streamSimple: streamSimpleAnthropic, streamSimple: anthropicApi.streamSimple,
}; };
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; 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"; import type { AssistantMessage, Message, Model, ToolCall } from "../src/types.ts";
// Normalize function matching what anthropic.ts uses // Normalize function matching what anthropic.ts uses
@@ -153,7 +153,7 @@ async function refreshAnthropicToken(credentials: OAuthCredentials): Promise<OAu
} }
// ============================================================================= // =============================================================================
// Streaming Implementation (simplified from packages/ai/src/providers/anthropic.ts) // Streaming Implementation (simplified from packages/ai/src/api/anthropic-messages.ts)
// ============================================================================= // =============================================================================
// Claude Code tool names for OAuth stealth mode // Claude Code tool names for OAuth stealth mode
@@ -12,14 +12,14 @@
import { import {
type Api, type Api,
type AssistantMessageEventStream, type AssistantMessageEventStream,
anthropicMessagesApi,
type Context, type Context,
createAssistantMessageEventStream, createAssistantMessageEventStream,
type Model, type Model,
type OAuthCredentials, type OAuthCredentials,
type OAuthLoginCallbacks, type OAuthLoginCallbacks,
openAIResponsesApi,
type SimpleStreamOptions, type SimpleStreamOptions,
streamSimpleAnthropic,
streamSimpleOpenAIResponses,
type ThinkingLevelMap, type ThinkingLevelMap,
} from "@earendil-works/pi-ai"; } from "@earendil-works/pi-ai";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -325,7 +325,7 @@ export function streamGitLabDuo(
const innerStream = const innerStream =
cfg.backend === "anthropic" cfg.backend === "anthropic"
? streamSimpleAnthropic( ? anthropicMessagesApi().streamSimple(
{ {
...(modelWithBaseUrl as Model<"anthropic-messages">), ...(modelWithBaseUrl as Model<"anthropic-messages">),
compat: { compat: {
@@ -336,7 +336,11 @@ export function streamGitLabDuo(
context, context,
streamOptions, 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); for await (const event of innerStream) stream.push(event);
stream.end(); stream.end();
@@ -23,8 +23,8 @@ import {
} from "@earendil-works/pi-ai"; } from "@earendil-works/pi-ai";
import { import {
getOpenAICodexWebSocketDebugStats, getOpenAICodexWebSocketDebugStats,
streamSimpleOpenAICodexResponses, streamSimple as streamSimpleOpenAICodexResponses,
} from "../../ai/src/providers/openai-codex-responses.ts"; } from "../../ai/src/api/openai-codex-responses.ts";
import { AuthStorage } from "../src/core/auth-storage.ts"; import { AuthStorage } from "../src/core/auth-storage.ts";
import { createExtensionRuntime } from "../src/core/extensions/loader.ts"; import { createExtensionRuntime } from "../src/core/extensions/loader.ts";
import type { ToolDefinition } from "../src/core/extensions/types.ts"; import type { ToolDefinition } from "../src/core/extensions/types.ts";