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
Unverified
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
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<TApi extends Api>(
model: Model<TApi>,
context: Context,
options?: ApiStreamOptions<TApi>,
): AssistantMessageEventStream;
stream(model: Model<Api>, context: Context, options?: StreamOptions): AssistantMessageEventStream;
streamSimple(model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
}
// src/api/lazy.ts
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"));
```
@@ -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
+16 -16
View File
@@ -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",
+1 -1
View File
@@ -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);
@@ -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.
* 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,
@@ -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
*/
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);
@@ -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>";
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,
@@ -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
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,
@@ -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
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,
+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";
function createSetupErrorMessage(model: Model<Api>, 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>): 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`.
*/
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:
@@ -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
// ============================================================================
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);
@@ -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";
}
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,
@@ -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
*/
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);
+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 = {
streamBedrock,
streamSimpleBedrock,
stream,
streamSimple,
};
+19 -14
View File
@@ -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";
@@ -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 { 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;
}
+22 -9
View File
@@ -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 Api> = TApi extends keyof ApiOptionsMa
? ApiOptionsMap[TApi]
: 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 {
signal?: AbortSignal;
apiKey?: string;
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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");
@@ -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 = {
@@ -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 = {
@@ -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";
+3 -3
View File
@@ -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";
@@ -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";
+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 } 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;
@@ -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(() => ({
@@ -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<string, unknown>): Tool {
@@ -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<TApi extends "google-generative-ai" | "google-vertex">(
@@ -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<TApi extends "google-generative-ai">(
@@ -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", () => {
@@ -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");
+2 -2
View File
@@ -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"]);
+3 -3
View File
@@ -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;
@@ -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 {
@@ -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 {
@@ -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(() => ({
@@ -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,
@@ -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,
@@ -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<string, string | readonly string[]> | undefined;
@@ -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";
@@ -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 = {
@@ -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";
+5 -3
View File
@@ -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,
};
// ---------------------------------------------------------------------------
@@ -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
@@ -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
@@ -12,14 +12,14 @@
import {
type Api,
type AssistantMessageEventStream,
anthropicMessagesApi,
type Context,
createAssistantMessageEventStream,
type Model,
type OAuthCredentials,
type OAuthLoginCallbacks,
openAIResponsesApi,
type SimpleStreamOptions,
streamSimpleAnthropic,
streamSimpleOpenAIResponses,
type ThinkingLevelMap,
} from "@earendil-works/pi-ai";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -325,7 +325,7 @@ export function streamGitLabDuo(
const innerStream =
cfg.backend === "anthropic"
? streamSimpleAnthropic(
? anthropicMessagesApi().streamSimple(
{
...(modelWithBaseUrl as Model<"anthropic-messages">),
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();
@@ -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";