Sign In

@opencode-ai/ai

Package Overview
Dependencies
Maintainers
2
Versions
607
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@opencode-ai/ai - npm Package Compare versions

Comparing version
0.0.0-bootstrap.0
to
0.0.0-dev-17471
+2
dist/cache-policy.d.ts
import { LLMRequest } from "./schema/messages.js";
export declare const applyCachePolicy: (request: LLMRequest) => LLMRequest;
// Apply an `LLMRequest.cache` policy by injecting `CacheHint`s onto the parts
// the policy designates. Runs once at compile time, before the per-protocol
// body builder, so the existing inline-hint lowering path handles the rest.
//
// The default `"auto"` shape places breakpoints at the last tool definition,
// the first and last distinct system parts, and the conversation tail. This
// exposes reusable tool, base-agent, project, and session prefixes while
// advancing the tail after each tool result keeps the previous cache entry
// within Anthropic's 20-block lookback during long agent turns.
//
// Manual `cache: CacheHint` placements on individual parts are preserved and
// count against the four-breakpoint budget; auto only fills remaining slots.
import { CacheHint } from "./schema/options.js";
import { LLMRequest, Message, ToolDefinition } from "./schema/messages.js";
const AUTO = {
tools: true,
system: true,
messages: { tail: 1 },
};
const NONE = {};
const BREAKPOINT_CAP = 4;
// Resolution rules:
// - undefined → "auto" — caching is on by default. The math favors it:
// Anthropic 5m-cache write is 1.25x base, read is 0.1x,
// so a single reuse within 5 minutes already wins.
// - "auto" → tools + first/last system + final message boundary.
// - "none" → no auto placement; manual `CacheHint`s still flow.
// - object form → exactly what the caller asked for.
const resolve = (policy) => {
if (policy === undefined || policy === "auto")
return AUTO;
if (policy === "none")
return NONE;
return policy;
};
// Protocols whose wire format ignores inline cache markers (OpenAI's implicit
// prefix caching, Gemini's implicit + out-of-band CachedContent). Skip the
// whole policy pass for these — emitting hints would be harmless but pointless.
const RESPECTS_INLINE_HINTS = new Set(["anthropic-messages", "bedrock-converse", "openrouter"]);
const makeHint = (ttlSeconds) => ttlSeconds !== undefined ? new CacheHint({ type: "ephemeral", ttlSeconds }) : new CacheHint({ type: "ephemeral" });
const markLastTool = (tools, hint, budget) => {
if (tools.length === 0)
return tools;
const last = tools.length - 1;
if (tools[last].cache || budget.remaining === 0)
return tools;
budget.remaining -= 1;
return tools.map((tool, i) => (i === last ? new ToolDefinition({ ...tool, cache: hint }) : tool));
};
const markSystemBoundaries = (system, hint, budget) => {
if (system.length === 0)
return system;
let changed = false;
const next = system.map((part, index) => {
if ((index !== 0 && index !== system.length - 1) || part.cache || budget.remaining === 0)
return part;
budget.remaining -= 1;
changed = true;
return { ...part, cache: hint };
});
return changed ? next : system;
};
const lastIndexOfRole = (messages, role) => messages.findLastIndex((m) => m.role === role);
// Mark the last text part of `messages[index]`. If no text part exists, mark
// the last content part regardless of type — that's the breakpoint position
// in tool-result-only messages too.
const markMessageAt = (messages, index, hint, budget) => {
if (index < 0 || index >= messages.length)
return messages;
const target = messages[index];
if (target.content.length === 0)
return messages;
const lastTextIndex = target.content.findLastIndex((part) => part.type === "text");
const markAt = lastTextIndex >= 0 ? lastTextIndex : target.content.length - 1;
const existing = target.content[markAt];
if (("cache" in existing && existing.cache) || budget.remaining === 0)
return messages;
budget.remaining -= 1;
const nextContent = target.content.map((part, i) => (i === markAt ? { ...part, cache: hint } : part));
const next = new Message({ ...target, content: nextContent });
// Single pass over `messages`, substituting the one updated entry. Long
// conversations call this on every request, so avoid `.map()` here — its
// closure dispatch and identity copies show up in profiling.
const result = messages.slice();
result[index] = next;
return result;
};
const markMessages = (messages, strategy, hint, budget) => {
if (messages.length === 0)
return messages;
if (strategy === "latest-user-message")
return markMessageAt(messages, lastIndexOfRole(messages, "user"), hint, budget);
if (strategy === "latest-assistant")
return markMessageAt(messages, lastIndexOfRole(messages, "assistant"), hint, budget);
const start = Math.max(0, messages.length - strategy.tail);
let next = messages;
for (let i = start; i < messages.length; i++)
next = markMessageAt(next, i, hint, budget);
return next;
};
const countHints = (request) => request.tools.reduce((count, tool) => count + (tool.cache === undefined ? 0 : 1), 0) +
request.system.reduce((count, part) => count + (part.cache === undefined ? 0 : 1), 0) +
request.messages.reduce((count, message) => count +
message.content.reduce((contentCount, part) => contentCount + ("cache" in part && part.cache !== undefined ? 1 : 0), 0), 0);
export const applyCachePolicy = (request) => {
if (!RESPECTS_INLINE_HINTS.has(request.model.route.id))
return request;
if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto"))
return request;
const policy = resolve(request.cache);
if (!policy.tools && !policy.system && !policy.messages)
return request;
const hint = makeHint(policy.ttlSeconds);
const budget = { remaining: Math.max(0, BREAKPOINT_CAP - countHints(request)) };
const tools = policy.tools ? markLastTool(request.tools, hint, budget) : request.tools;
const system = policy.system ? markSystemBoundaries(request.system, hint, budget) : request.system;
const messages = policy.messages ? markMessages(request.messages, policy.messages, hint, budget) : request.messages;
if (tools === request.tools && system === request.system && messages === request.messages)
return request;
return LLMRequest.update(request, { tools, system, messages });
};
import { Context, Effect, Layer } from "effect";
import { RequestExecutor } from "./route/executor.js";
import type { ImageOptions, ImageRequestFor, ImageResponse } from "./image.js";
import type { AIError } from "./schema/index.js";
export type Execute = RequestExecutor.Interface["execute"];
export interface Interface {
readonly generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<ImageResponse, AIError>;
}
declare const Service_base: Context.ServiceClass<Service, "@opencode/ImageClient", Interface>;
export declare class Service extends Service_base {
}
export declare const generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<ImageResponse, AIError, Service>;
export declare const layer: Layer.Layer<Service, never, RequestExecutor.Service>;
export declare const ImageClient: {
readonly Service: typeof Service;
readonly layer: Layer.Layer<Service, never, RequestExecutor.Service>;
readonly generate: <Options extends ImageOptions>(request: ImageRequestFor<Options>) => Effect.Effect<ImageResponse, AIError, Service>;
};
export {};
import { Context, Effect, Layer } from "effect";
import { RequestExecutor } from "./route/executor.js";
export class Service extends Context.Service()("@opencode/ImageClient") {
}
export const generate = (request) => Effect.gen(function* () {
const client = yield* Service;
return yield* client.generate(request);
});
export const layer = Layer.effect(Service, Effect.gen(function* () {
const executor = yield* RequestExecutor.Service;
return Service.of({
generate: (request) => request.model.route.generate(request, executor.execute),
});
}));
export const ImageClient = {
Service,
layer,
generate,
};
import { Effect, Schema } from "effect";
import { HttpOptions, AIError, ModelID, ProviderID, Usage } from "./schema/index.js";
import { Service, type Execute as ImageExecute } from "./image-client.js";
export interface ImageRoute<Options extends ImageOptions = ImageOptions> {
readonly id: string;
readonly generate: (request: ImageRequestFor<Options>, execute: ImageExecute) => Effect.Effect<ImageResponse, AIError>;
}
export type ImageOptions = Record<string, unknown>;
export declare class ImageModel<Options extends ImageOptions = ImageOptions> {
protected readonly _Options: (options: Options) => Options;
readonly id: ModelID;
readonly provider: ProviderID;
readonly route: ImageRoute<Options>;
readonly http?: HttpOptions;
constructor(input: ImageModel.Input<Options>);
static make<Options extends ImageOptions = ImageOptions>(input: ImageModel.MakeInput<Options>): ImageModel<Options>;
}
export declare namespace ImageModel {
interface Input<Options extends ImageOptions = ImageOptions> {
readonly id: ModelID;
readonly provider: ProviderID;
readonly route: ImageRoute<Options>;
readonly http?: HttpOptions;
}
interface MakeInput<Options extends ImageOptions = ImageOptions> extends Omit<Input<Options>, "id" | "provider"> {
readonly id: string | ModelID;
readonly provider: string | ProviderID;
}
}
export declare const ImageModelSchema: Schema.declare<ImageModel<ImageOptions>, ImageModel<ImageOptions>>;
export declare const ImageInputSchema: Schema.toTaggedUnion<"type", readonly [Schema.Struct<{
readonly type: Schema.Literal<"bytes">;
readonly data: Schema.Uint8Array;
readonly mediaType: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"url">;
readonly url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file-id">;
readonly id: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file-uri">;
readonly uri: Schema.String;
readonly mediaType: Schema.String;
}>]>;
export type ImageInput = Schema.Schema.Type<typeof ImageInputSchema>;
export declare const ImageInput: {
readonly bytes: (data: Uint8Array, mediaType: string) => ImageInput;
readonly url: (url: string) => ImageInput;
readonly file: (id: string) => ImageInput;
readonly fileUri: (uri: string, mediaType: string) => ImageInput;
};
declare const ImageRequest_base: Schema.Class<ImageRequest, Schema.Struct<{
readonly model: Schema.declare<ImageModel<ImageOptions>, ImageModel<ImageOptions>>;
readonly prompt: Schema.String;
readonly images: Schema.optional<Schema.$Array<Schema.toTaggedUnion<"type", readonly [Schema.Struct<{
readonly type: Schema.Literal<"bytes">;
readonly data: Schema.Uint8Array;
readonly mediaType: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"url">;
readonly url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file-id">;
readonly id: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file-uri">;
readonly uri: Schema.String;
readonly mediaType: Schema.String;
}>]>>>;
readonly options: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly http: Schema.optional<typeof HttpOptions>;
}>, {}>;
export declare class ImageRequest extends ImageRequest_base {
protected readonly _ImageRequest: void;
}
export type ImageRequestFor<Options extends ImageOptions = ImageOptions> = Omit<ImageRequest, "model" | "options"> & {
readonly model: ImageModel<Options>;
readonly options?: Options;
};
export type ImageModelOptions<Model> = Model extends ImageModel<infer Options> ? Options : never;
export type ImageRequestInput<Model extends object = ImageModel> = Omit<ConstructorParameters<typeof ImageRequest>[0], "model" | "options" | "http"> & {
readonly model: Model;
readonly options?: NoInfer<ImageModelOptions<Model>>;
readonly http?: HttpOptions.Input;
} & (Model extends ImageModel<ImageModelOptions<Model>> ? unknown : never);
declare const GeneratedImage_base: Schema.Class<GeneratedImage, Schema.Struct<{
readonly mediaType: Schema.String;
readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>, {}>;
export declare class GeneratedImage extends GeneratedImage_base {
}
declare const ImageResponse_base: Schema.Class<ImageResponse, Schema.Struct<{
readonly images: Schema.$Array<typeof GeneratedImage>;
readonly usage: Schema.optional<typeof Usage>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>, {}>;
export declare class ImageResponse extends ImageResponse_base {
get image(): GeneratedImage;
}
export declare function request<const Model extends object>(input: ImageRequestInput<Model>): ImageRequestFor<ImageModelOptions<Model>>;
export declare function request(input: ImageRequest): ImageRequest;
export declare function generate<const Model extends object>(input: ImageRequestInput<Model>): Effect.Effect<ImageResponse, AIError, Service>;
export declare function generate(input: ImageRequest): Effect.Effect<ImageResponse, AIError, Service>;
export declare const Image: {
readonly request: typeof request;
readonly generate: typeof generate;
};
export {};
import { Effect, Schema } from "effect";
import { HttpOptions, InvalidRequestReason, AIError, ModelID, ProviderID, ProviderMetadata, Usage, } from "./schema/index.js";
import { ImageClient, Service } from "./image-client.js";
export class ImageModel {
id;
provider;
route;
http;
constructor(input) {
this.id = input.id;
this.provider = input.provider;
this.route = input.route;
this.http = input.http;
}
static make(input) {
return new ImageModel({
id: ModelID.make(input.id),
provider: ProviderID.make(input.provider),
route: input.route,
http: input.http,
});
}
}
export const ImageModelSchema = Schema.declare((value) => value instanceof ImageModel, {
expected: "Image.Model",
});
const ImageBytesInput = Schema.Struct({
type: Schema.Literal("bytes"),
data: Schema.Uint8Array,
mediaType: Schema.String,
});
const ImageUrlInput = Schema.Struct({
type: Schema.Literal("url"),
url: Schema.String,
});
const ImageFileIDInput = Schema.Struct({
type: Schema.Literal("file-id"),
id: Schema.String,
});
const ImageFileURIInput = Schema.Struct({
type: Schema.Literal("file-uri"),
uri: Schema.String,
mediaType: Schema.String,
});
export const ImageInputSchema = Schema.Union([
ImageBytesInput,
ImageUrlInput,
ImageFileIDInput,
ImageFileURIInput,
]).pipe(Schema.toTaggedUnion("type"));
export const ImageInput = {
bytes: (data, mediaType) => ({ type: "bytes", data, mediaType }),
url: (url) => ({ type: "url", url }),
file: (id) => ({ type: "file-id", id }),
fileUri: (uri, mediaType) => ({ type: "file-uri", uri, mediaType }),
};
export class ImageRequest extends Schema.Class("Image.Request")({
model: ImageModelSchema,
prompt: Schema.String,
images: Schema.optional(Schema.Array(ImageInputSchema)),
options: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
http: Schema.optional(HttpOptions),
}) {
}
export class GeneratedImage extends Schema.Class("Image.Generated")({
mediaType: Schema.String,
data: Schema.Union([Schema.String, Schema.Uint8Array]),
providerMetadata: Schema.optional(ProviderMetadata),
}) {
}
export class ImageResponse extends Schema.Class("Image.Response")({
images: Schema.Array(GeneratedImage),
usage: Schema.optional(Usage),
providerMetadata: Schema.optional(ProviderMetadata),
}) {
get image() {
return this.images[0];
}
}
export function request(input) {
if (input instanceof ImageRequest)
return input;
return new ImageRequest({
...input,
model: input.model,
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
});
}
export function generate(input) {
return Effect.try({
try: () => (input instanceof ImageRequest ? input : request(input)),
catch: (error) => new AIError({
module: "Image",
method: "generate",
reason: new InvalidRequestReason({ message: error instanceof Error ? error.message : String(error) }),
}),
}).pipe(Effect.flatMap((request) => ImageClient.generate(request)));
}
export const Image = {
request,
generate,
};
export { LLMClient } from "./route/client.js";
export { ImageClient } from "./image-client.js";
export { Auth } from "./route/auth.js";
export { Provider } from "./provider.js";
export { ProviderPackage } from "./provider-package.js";
export { isContextOverflow, isContextOverflowFailure } from "./provider-error.js";
export type { RouteLanguageModelInput, RouteRoutedLanguageModelInput, Interface as LLMClientShape, Service as LLMClientService, } from "./route/client.js";
export * from "./schema/index.js";
export { GeneratedImage, ImageInput, ImageInputSchema, ImageModel, ImageRequest, ImageResponse } from "./image.js";
export type { ImageModelOptions, ImageOptions, ImageRequestFor, ImageRequestInput, ImageRoute } from "./image.js";
export { Image } from "./image.js";
export { Tool, ToolFailure, toDefinitions } from "./tool.js";
export { ToolRuntime } from "./tool-runtime.js";
export type { DispatchResult as ToolDispatchResult, ToolSettlement } from "./tool-runtime.js";
export type { AnyExecutableTool, AnyTool, ExecutableTool, ExecutableTools, Definition as ToolShape, ToolExecute, ToolExecuteContext, ToolModelOutputInput, Tools, ToolSchema, ToolToModelOutput, } from "./tool.js";
export * as LLM from "./llm.js";
export type { Definition as ProviderDefinition, LanguageModelFactory as ProviderLanguageModelFactory, LanguageModelOptions as ProviderLanguageModelOptions, } from "./provider.js";
export type { Definition as ProviderPackageDefinition, Settings as ProviderPackageSettings, } from "./provider-package.js";
export { LLMClient } from "./route/client.js";
export { ImageClient } from "./image-client.js";
export { Auth } from "./route/auth.js";
export { Provider } from "./provider.js";
export { ProviderPackage } from "./provider-package.js";
export { isContextOverflow, isContextOverflowFailure } from "./provider-error.js";
export * from "./schema/index.js";
export { GeneratedImage, ImageInput, ImageInputSchema, ImageModel, ImageRequest, ImageResponse } from "./image.js";
export { Image } from "./image.js";
export { Tool, ToolFailure, toDefinitions } from "./tool.js";
export { ToolRuntime } from "./tool-runtime.js";
export * as LLM from "./llm.js";
import { Effect, JsonSchema, Schema } from "effect";
import { Service } from "./route/client.js";
import { GenerationOptions, HttpOptions, AIError, LLMRequest, LLMResponse, Message, LanguageModel, SystemPart, ToolChoice, ToolDefinition, type ContentPart, type LanguageModelProviderOptions } from "./schema/index.js";
import { type ToolSchema } from "./tool.js";
/** Input accepted by `LLM.request`, normalized into the canonical `LLMRequest` class. */
export type RequestInput<SelectedLanguageModel extends LanguageModel = LanguageModel> = Omit<ConstructorParameters<typeof LLMRequest>[0], "model" | "system" | "messages" | "tools" | "toolChoice" | "generation" | "http" | "providerOptions"> & {
readonly model: SelectedLanguageModel;
readonly system?: string | SystemPart | ReadonlyArray<SystemPart>;
readonly prompt?: string | ContentPart | ReadonlyArray<ContentPart>;
readonly messages?: ReadonlyArray<Message | Message.Input>;
readonly tools?: ReadonlyArray<ToolDefinition.Input>;
readonly toolChoice?: ToolChoice.Input;
readonly generation?: GenerationOptions.Input;
readonly providerOptions?: NoInfer<LanguageModelProviderOptions<SelectedLanguageModel>>;
readonly http?: HttpOptions.Input;
};
export declare const generate: typeof import("./route/client.js").generate;
export declare const stream: typeof import("./route/client.js").stream;
export declare const request: <const SelectedLanguageModel extends LanguageModel>(input: RequestInput<SelectedLanguageModel>) => LLMRequest;
type GenerateObjectBase<SelectedLanguageModel extends LanguageModel = LanguageModel> = Omit<RequestInput<SelectedLanguageModel>, "tools" | "toolChoice">;
export declare class GenerateObjectResponse<T> {
readonly object: T;
readonly response: LLMResponse;
constructor(object: T, response: LLMResponse);
get events(): readonly ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
get usage(): import("./schema/events.js").Usage | undefined;
}
export interface GenerateObjectOptions<S extends ToolSchema<any>, SelectedLanguageModel extends LanguageModel = LanguageModel> extends GenerateObjectBase<SelectedLanguageModel> {
readonly schema: S;
}
export interface GenerateObjectDynamicOptions<SelectedLanguageModel extends LanguageModel = LanguageModel> extends GenerateObjectBase<SelectedLanguageModel> {
/** Raw JSON Schema object describing the expected output shape. */
readonly jsonSchema: JsonSchema.JsonSchema;
}
/**
* Run a model and decode its output against `schema`. Works on every protocol
* because it forces a synthetic tool call internally — provider-native JSON
* modes are intentionally avoided so behaviour is uniform.
*
* Two input modes:
*
* 1. `schema: EffectSchema<T>` — `.object` is decoded and typed as `T`.
* Decode failures surface as `AIError`.
* 2. `jsonSchema: JsonSchema.JsonSchema` — `.object` is `unknown`. Use when
* the schema is only available at runtime (MCP, plugin manifests). Caller validates.
*/
export declare function generateObject<const SelectedLanguageModel extends LanguageModel, S extends ToolSchema<any>>(options: GenerateObjectOptions<S, SelectedLanguageModel>): Effect.Effect<GenerateObjectResponse<Schema.Schema.Type<S>>, AIError, Service>;
export declare function generateObject<const SelectedLanguageModel extends LanguageModel>(options: GenerateObjectDynamicOptions<SelectedLanguageModel>): Effect.Effect<GenerateObjectResponse<unknown>, AIError, Service>;
export {};
import { Effect, JsonSchema, Schema } from "effect";
import { LLMClient, Service } from "./route/client.js";
import { GenerationOptions, HttpOptions, InvalidProviderOutputReason, AIError, LLMEvent, LLMRequest, LLMResponse, Message, LanguageModel, SystemPart, ToolChoice, ToolDefinition, } from "./schema/index.js";
import { make as makeTool, toDefinitions } from "./tool.js";
export const generate = LLMClient.generate;
export const stream = LLMClient.stream;
export const request = (input) => {
const { system: requestSystem, prompt, messages, tools, toolChoice: requestToolChoice, generation: requestGeneration, providerOptions: requestProviderOptions, http: requestHttp, ...rest } = input;
return new LLMRequest({
...rest,
system: SystemPart.content(requestSystem),
messages: [...(messages?.map(Message.make) ?? []), ...(prompt === undefined ? [] : [Message.user(prompt)])],
tools: tools?.map(ToolDefinition.make) ?? [],
toolChoice: requestToolChoice ? ToolChoice.make(requestToolChoice) : undefined,
generation: requestGeneration === undefined ? undefined : GenerationOptions.make(requestGeneration),
providerOptions: requestProviderOptions,
http: requestHttp === undefined ? undefined : HttpOptions.make(requestHttp),
});
};
const GENERATE_OBJECT_TOOL_NAME = "generate_object";
const GENERATE_OBJECT_TOOL_DESCRIPTION = "Return the structured result by calling this tool.";
export class GenerateObjectResponse {
object;
response;
constructor(object, response) {
this.object = object;
this.response = response;
}
get events() {
return this.response.events;
}
get usage() {
return this.response.usage;
}
}
const runGenerateObject = Effect.fn("LLM.generateObject")(function* (options, tool) {
const baseRequest = request(options);
const generateRequest = LLMRequest.update(baseRequest, {
tools: toDefinitions({ [GENERATE_OBJECT_TOOL_NAME]: tool }),
toolChoice: ToolChoice.named(GENERATE_OBJECT_TOOL_NAME),
});
const response = yield* LLMClient.generate(generateRequest);
const call = response.toolCalls.find((event) => LLMEvent.is.toolCall(event) && event.name === GENERATE_OBJECT_TOOL_NAME);
if (!call || !LLMEvent.is.toolCall(call))
return yield* new AIError({
module: "LLM",
method: "generateObject",
reason: new InvalidProviderOutputReason({
message: `generateObject: model did not call the forced \`${GENERATE_OBJECT_TOOL_NAME}\` tool`,
}),
});
const object = yield* tool._decode(call.input).pipe(Effect.mapError((error) => new AIError({
module: "LLM",
method: "generateObject",
reason: new InvalidProviderOutputReason({
message: `generateObject: tool input failed schema decode: ${error.message}`,
}),
})));
return new GenerateObjectResponse(object, response);
});
export function generateObject(options) {
if ("schema" in options) {
const { schema, ...rest } = options;
return runGenerateObject(rest, makeTool({
description: GENERATE_OBJECT_TOOL_DESCRIPTION,
parameters: schema,
success: Schema.Unknown,
execute: () => Effect.void,
}));
}
const { jsonSchema, ...rest } = options;
return runGenerateObject(rest, makeTool({
description: GENERATE_OBJECT_TOOL_DESCRIPTION,
jsonSchema,
execute: () => Effect.void,
}));
}
export * from "./protocols/index.js";
export * from "./protocols/index.js";
import { Schema } from "effect";
import { Route } from "../route/client.js";
import { Protocol } from "../route/protocol.js";
import { type ProviderOptions } from "../schema/index.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolStream } from "./utils/tool-stream.js";
export declare const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
export declare const PATH = "/messages";
export type ThinkingInput = {
readonly type: "adaptive";
readonly display?: "summarized" | "omitted";
} | {
readonly type: "disabled";
} | ({
readonly type: "enabled";
} & ({
readonly budgetTokens: number;
readonly budget_tokens?: number;
} | {
readonly budgetTokens?: number;
readonly budget_tokens: number;
}));
export interface OptionsInput {
readonly [key: string]: unknown;
readonly thinking?: ThinkingInput;
readonly effort?: string;
}
export type ProviderOptionsInput = ProviderOptions & {
readonly anthropic?: OptionsInput;
};
export declare const AnthropicMessagesBody: Schema.Struct<{
model: Schema.String;
system: Schema.optional<Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>>>;
messages: Schema.$Array<Schema.toTaggedUnion<"role", readonly [Schema.Struct<{
readonly role: Schema.Literal<"user">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"image">;
readonly source: Schema.Struct<{
readonly type: Schema.tag<"base64">;
readonly media_type: Schema.String;
readonly data: Schema.String;
}>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"document">;
readonly source: Schema.Struct<{
readonly type: Schema.tag<"base64">;
readonly media_type: Schema.Literal<"application/pdf">;
readonly data: Schema.String;
}>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"tool_result">;
readonly tool_use_id: Schema.String;
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"image">;
readonly source: Schema.Struct<{
readonly type: Schema.tag<"base64">;
readonly media_type: Schema.String;
readonly data: Schema.String;
}>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"document">;
readonly source: Schema.Struct<{
readonly type: Schema.tag<"base64">;
readonly media_type: Schema.Literal<"application/pdf">;
readonly data: Schema.String;
}>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>]>>]>;
readonly is_error: Schema.optional<Schema.Boolean>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>]>>;
}>, Schema.Struct<{
readonly role: Schema.Literal<"assistant">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"thinking">;
readonly thinking: Schema.String;
readonly signature: Schema.optional<Schema.String>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"redacted_thinking">;
readonly data: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"tool_use">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly input: Schema.Unknown;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"server_tool_use">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly input: Schema.Unknown;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.Literals<readonly ["web_search_tool_result", "code_execution_tool_result", "web_fetch_tool_result"]>;
readonly tool_use_id: Schema.String;
readonly content: Schema.Unknown;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>]>>;
}>, Schema.Struct<{
readonly role: Schema.Literal<"system">;
readonly content: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>>;
}>]>>;
tools: Schema.optional<Schema.$Array<Schema.Struct<{
readonly name: Schema.String;
readonly description: Schema.String;
readonly input_schema: Schema.$Record<Schema.String, Schema.Unknown>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.tag<"ephemeral">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>>;
}>>>;
tool_choice: Schema.optional<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literals<readonly ["auto", "any", "none"]>;
}>, Schema.Struct<{
readonly type: Schema.tag<"tool">;
readonly name: Schema.String;
}>]>>;
stream: Schema.Literal<true>;
max_tokens: Schema.Number;
temperature: Schema.optional<Schema.Number>;
top_p: Schema.optional<Schema.Number>;
top_k: Schema.optional<Schema.Number>;
stop_sequences: Schema.optional<Schema.$Array<Schema.String>>;
thinking: Schema.optional<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"enabled">;
readonly budget_tokens: Schema.Number;
}>, Schema.Struct<{
readonly type: Schema.tag<"adaptive">;
readonly display: Schema.optional<Schema.Literals<readonly ["summarized", "omitted"]>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"disabled">;
}>]>>;
output_config: Schema.optional<Schema.Struct<{
readonly effort: Schema.optional<Schema.String>;
}>>;
}>;
export type AnthropicMessagesBody = Schema.Schema.Type<typeof AnthropicMessagesBody>;
/**
* The Anthropic Messages protocol — request body construction, body schema,
* and the streaming-event state machine. Used by native Anthropic Cloud and
* (once registered) Vertex Anthropic / Bedrock-hosted Anthropic passthrough.
*/
export declare const protocol: Protocol<{
readonly max_tokens: number;
readonly model: string;
readonly messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "tool_result";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly is_error?: boolean | undefined;
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "server_tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "web_search_tool_result" | "code_execution_tool_result" | "web_fetch_tool_result";
readonly content: unknown;
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "thinking";
readonly thinking: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly signature?: string | undefined;
} | {
readonly data: string;
readonly type: "redacted_thinking";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
} | {
readonly role: "system";
readonly content: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[];
})[];
readonly stream: true;
readonly system?: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly tools?: readonly {
readonly description: string;
readonly name: string;
readonly input_schema: {
readonly [x: string]: unknown;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly thinking?: {
readonly type: "enabled";
readonly budget_tokens: number;
} | {
readonly type: "adaptive";
readonly display?: "summarized" | "omitted" | undefined;
} | {
readonly type: "disabled";
} | undefined;
readonly tool_choice?: {
readonly type: "none" | "auto" | "any";
} | {
readonly type: "tool";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly top_k?: number | undefined;
readonly stop_sequences?: readonly string[] | undefined;
readonly output_config?: {
readonly effort?: string | undefined;
} | undefined;
}, string, {
readonly type: string;
readonly error?: {
readonly type?: string | undefined;
readonly message?: string | undefined;
} | undefined;
readonly message?: {
readonly usage?: {
readonly [x: string]: unknown;
readonly server_tool_use?: {
readonly [x: string]: unknown;
readonly web_search_requests?: number | undefined;
} | null | undefined;
readonly input_tokens?: number | undefined;
readonly output_tokens?: number | undefined;
readonly cache_creation_input_tokens?: number | null | undefined;
readonly cache_read_input_tokens?: number | null | undefined;
readonly output_tokens_details?: {
readonly [x: string]: unknown;
readonly thinking_tokens?: number | undefined;
} | null | undefined;
} | undefined;
} | undefined;
readonly delta?: {
readonly type?: string | undefined;
readonly text?: string | undefined;
readonly thinking?: string | undefined;
readonly signature?: string | undefined;
readonly partial_json?: string | undefined;
readonly stop_reason?: string | null | undefined;
readonly stop_sequence?: string | null | undefined;
} | undefined;
readonly index?: number | undefined;
readonly usage?: {
readonly [x: string]: unknown;
readonly server_tool_use?: {
readonly [x: string]: unknown;
readonly web_search_requests?: number | undefined;
} | null | undefined;
readonly input_tokens?: number | undefined;
readonly output_tokens?: number | undefined;
readonly cache_creation_input_tokens?: number | null | undefined;
readonly cache_read_input_tokens?: number | null | undefined;
readonly output_tokens_details?: {
readonly [x: string]: unknown;
readonly thinking_tokens?: number | undefined;
} | null | undefined;
} | undefined;
readonly content_block?: {
readonly type: string;
readonly id?: string | undefined;
readonly data?: string | undefined;
readonly name?: string | undefined;
readonly input?: unknown;
readonly text?: string | undefined;
readonly content?: unknown;
readonly thinking?: string | undefined;
readonly signature?: string | undefined;
readonly tool_use_id?: string | undefined;
} | undefined;
}, {
tools: Partial<Record<number, ToolStream.PendingTool>>;
reasoningSignatures: {};
lifecycle: Lifecycle.State;
}>;
export declare const route: Route<{
readonly max_tokens: number;
readonly model: string;
readonly messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "tool_result";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly is_error?: boolean | undefined;
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "server_tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "web_search_tool_result" | "code_execution_tool_result" | "web_fetch_tool_result";
readonly content: unknown;
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "thinking";
readonly thinking: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly signature?: string | undefined;
} | {
readonly data: string;
readonly type: "redacted_thinking";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
} | {
readonly role: "system";
readonly content: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[];
})[];
readonly stream: true;
readonly system?: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly tools?: readonly {
readonly description: string;
readonly name: string;
readonly input_schema: {
readonly [x: string]: unknown;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly thinking?: {
readonly type: "enabled";
readonly budget_tokens: number;
} | {
readonly type: "adaptive";
readonly display?: "summarized" | "omitted" | undefined;
} | {
readonly type: "disabled";
} | undefined;
readonly tool_choice?: {
readonly type: "none" | "auto" | "any";
} | {
readonly type: "tool";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly top_k?: number | undefined;
readonly stop_sequences?: readonly string[] | undefined;
readonly output_config?: {
readonly effort?: string | undefined;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>;
export * as AnthropicMessages from "./anthropic-messages.js";
import { Effect, Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { Route } from "../route/client.js";
import { Auth } from "../route/auth.js";
import { Endpoint } from "../route/endpoint.js";
import { Framing } from "../route/framing.js";
import { Protocol } from "../route/protocol.js";
import { AIError, LLMEvent, mergeJsonRecords, Usage, } from "../schema/index.js";
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
import { classifyProviderFailure } from "../provider-error.js";
import * as Cache from "./utils/cache.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolSchemaProjection } from "./utils/tool-schema.js";
import { ToolStream } from "./utils/tool-stream.js";
const ADAPTER = "anthropic-messages";
const MEDIA_MIMES = new Set([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES]);
export const DEFAULT_BASE_URL = "https://api.anthropic.com/v1";
export const PATH = "/messages";
// =============================================================================
// Request Body Schema
// =============================================================================
const AnthropicCacheControl = Schema.Struct({
type: Schema.tag("ephemeral"),
ttl: Schema.optional(Schema.Literals(["5m", "1h"])),
});
const AnthropicTextBlock = Schema.Struct({
type: Schema.tag("text"),
text: Schema.String,
cache_control: Schema.optional(AnthropicCacheControl),
});
const AnthropicImageBlock = Schema.Struct({
type: Schema.tag("image"),
source: Schema.Struct({
type: Schema.tag("base64"),
media_type: Schema.String,
data: Schema.String,
}),
cache_control: Schema.optional(AnthropicCacheControl),
});
const AnthropicDocumentBlock = Schema.Struct({
type: Schema.tag("document"),
source: Schema.Struct({
type: Schema.tag("base64"),
media_type: Schema.Literal("application/pdf"),
data: Schema.String,
}),
cache_control: Schema.optional(AnthropicCacheControl),
});
const AnthropicThinkingBlock = Schema.Struct({
type: Schema.tag("thinking"),
thinking: Schema.String,
signature: Schema.optional(Schema.String),
cache_control: Schema.optional(AnthropicCacheControl),
});
// Safety-filtered thinking arrives as an opaque encrypted `data` payload with
// no visible text. It must round-trip verbatim so multi-turn thinking + tool
// use conversations keep their reasoning continuity.
const AnthropicRedactedThinkingBlock = Schema.Struct({
type: Schema.tag("redacted_thinking"),
data: Schema.String,
cache_control: Schema.optional(AnthropicCacheControl),
});
const AnthropicToolUseBlock = Schema.Struct({
type: Schema.tag("tool_use"),
id: Schema.String,
name: Schema.String,
input: Schema.Unknown,
cache_control: Schema.optional(AnthropicCacheControl),
});
const AnthropicServerToolUseBlock = Schema.Struct({
type: Schema.tag("server_tool_use"),
id: Schema.String,
name: Schema.String,
input: Schema.Unknown,
cache_control: Schema.optional(AnthropicCacheControl),
});
// Server tool result blocks: web_search_tool_result, code_execution_tool_result,
// and web_fetch_tool_result. The provider executes the tool and inlines the
// structured result into the assistant turn — there is no client tool_result
// round-trip. We round-trip the structured `content` payload as opaque JSON so
// the next request can echo it back when continuing the conversation.
const AnthropicServerToolResultType = Schema.Literals([
"web_search_tool_result",
"code_execution_tool_result",
"web_fetch_tool_result",
]);
const AnthropicServerToolResultBlock = Schema.Struct({
type: AnthropicServerToolResultType,
tool_use_id: Schema.String,
content: Schema.Unknown,
cache_control: Schema.optional(AnthropicCacheControl),
});
// Anthropic accepts either a plain string or an ordered array of text, image, and
// document blocks inside `tool_result.content`. The array form keeps media as native
// model input instead of JSON-stringifying base64 into prompt text.
const AnthropicToolResultContent = Schema.Union([AnthropicTextBlock, AnthropicImageBlock, AnthropicDocumentBlock]);
const AnthropicToolResultBlock = Schema.Struct({
type: Schema.tag("tool_result"),
tool_use_id: Schema.String,
content: Schema.Union([Schema.String, Schema.Array(AnthropicToolResultContent)]),
is_error: Schema.optional(Schema.Boolean),
cache_control: Schema.optional(AnthropicCacheControl),
});
const AnthropicUserBlock = Schema.Union([
AnthropicTextBlock,
AnthropicImageBlock,
AnthropicDocumentBlock,
AnthropicToolResultBlock,
]);
const AnthropicAssistantBlock = Schema.Union([
AnthropicTextBlock,
AnthropicThinkingBlock,
AnthropicRedactedThinkingBlock,
AnthropicToolUseBlock,
AnthropicServerToolUseBlock,
AnthropicServerToolResultBlock,
]);
const AnthropicMessage = Schema.Union([
Schema.Struct({ role: Schema.Literal("user"), content: Schema.Array(AnthropicUserBlock) }),
Schema.Struct({ role: Schema.Literal("assistant"), content: Schema.Array(AnthropicAssistantBlock) }),
Schema.Struct({ role: Schema.Literal("system"), content: Schema.Array(AnthropicTextBlock) }),
]).pipe(Schema.toTaggedUnion("role"));
const AnthropicTool = Schema.Struct({
name: Schema.String,
description: Schema.String,
input_schema: JsonObject,
cache_control: Schema.optional(AnthropicCacheControl),
});
const AnthropicToolChoice = Schema.Union([
Schema.Struct({ type: Schema.Literals(["auto", "any", "none"]) }),
Schema.Struct({ type: Schema.tag("tool"), name: Schema.String }),
]);
const AnthropicThinking = Schema.Union([
Schema.Struct({
type: Schema.tag("enabled"),
budget_tokens: Schema.Number,
}),
Schema.Struct({
type: Schema.tag("adaptive"),
display: Schema.optional(Schema.Literals(["summarized", "omitted"])),
}),
Schema.Struct({
type: Schema.tag("disabled"),
}),
]);
const AnthropicOutputConfig = Schema.Struct({
effort: Schema.optional(Schema.String),
});
const AnthropicBodyFields = {
model: Schema.String,
system: optionalArray(AnthropicTextBlock),
messages: Schema.Array(AnthropicMessage),
tools: optionalArray(AnthropicTool),
tool_choice: Schema.optional(AnthropicToolChoice),
stream: Schema.Literal(true),
max_tokens: Schema.Number,
temperature: Schema.optional(Schema.Number),
top_p: Schema.optional(Schema.Number),
top_k: Schema.optional(Schema.Number),
stop_sequences: optionalArray(Schema.String),
thinking: Schema.optional(AnthropicThinking),
output_config: Schema.optional(AnthropicOutputConfig),
};
export const AnthropicMessagesBody = Schema.Struct(AnthropicBodyFields);
const AnthropicUsage = Schema.StructWithRest(Schema.Struct({
input_tokens: Schema.optional(Schema.Number),
output_tokens: Schema.optional(Schema.Number),
cache_creation_input_tokens: optionalNull(Schema.Number),
cache_read_input_tokens: optionalNull(Schema.Number),
server_tool_use: optionalNull(Schema.StructWithRest(Schema.Struct({ web_search_requests: Schema.optional(Schema.Number) }), [
Schema.Record(Schema.String, Schema.Unknown),
])),
output_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({ thinking_tokens: Schema.optional(Schema.Number) }), [
Schema.Record(Schema.String, Schema.Unknown),
])),
}), [Schema.Record(Schema.String, Schema.Unknown)]);
const AnthropicStreamBlock = Schema.Struct({
type: Schema.String,
id: Schema.optional(Schema.String),
name: Schema.optional(Schema.String),
text: Schema.optional(Schema.String),
thinking: Schema.optional(Schema.String),
signature: Schema.optional(Schema.String),
// redacted_thinking blocks arrive whole in content_block_start with the
// encrypted payload in `data`; there is no streaming delta sequence.
data: Schema.optional(Schema.String),
input: Schema.optional(Schema.Unknown),
// *_tool_result blocks arrive whole as content_block_start (no streaming
// delta) with the structured payload in `content` and the originating
// server_tool_use id in `tool_use_id`.
tool_use_id: Schema.optional(Schema.String),
content: Schema.optional(Schema.Unknown),
});
const AnthropicStreamDelta = Schema.Struct({
type: Schema.optional(Schema.String),
text: Schema.optional(Schema.String),
thinking: Schema.optional(Schema.String),
partial_json: Schema.optional(Schema.String),
signature: Schema.optional(Schema.String),
stop_reason: optionalNull(Schema.String),
stop_sequence: optionalNull(Schema.String),
});
const AnthropicEvent = Schema.Struct({
type: Schema.String,
index: Schema.optional(Schema.Number),
message: Schema.optional(Schema.Struct({ usage: Schema.optional(AnthropicUsage) })),
content_block: Schema.optional(AnthropicStreamBlock),
delta: Schema.optional(AnthropicStreamDelta),
usage: Schema.optional(AnthropicUsage),
// `type` and `message` are both required per Anthropic's spec, but
// OpenAI-compatible proxies and gateway translations occasionally drop one
// or the other; mark them optional so a partial payload still parses and
// the parser can fall back to whichever field is populated.
error: Schema.optional(Schema.Struct({ type: Schema.optional(Schema.String), message: Schema.optional(Schema.String) })),
});
const invalid = ProviderShared.invalidRequest;
// =============================================================================
// Request Lowering
// =============================================================================
// Anthropic accepts at most 4 explicit cache_control breakpoints per request,
// across `tools`, `system`, and `messages`. Beyond the cap the API returns a
// 400 — so the lowering layer counts emitted markers and silently drops any
// that exceed it.
const ANTHROPIC_BREAKPOINT_CAP = 4;
const EPHEMERAL_5M = { type: "ephemeral" };
const EPHEMERAL_1H = { type: "ephemeral", ttl: "1h" };
const cacheControl = (breakpoints, cache) => {
if (cache?.type !== "ephemeral" && cache?.type !== "persistent")
return undefined;
if (breakpoints.remaining <= 0) {
breakpoints.dropped += 1;
return undefined;
}
breakpoints.remaining -= 1;
return Cache.ttlBucket(cache.ttlSeconds) === "1h" ? EPHEMERAL_1H : EPHEMERAL_5M;
};
const anthropicMetadata = (metadata) => ({ anthropic: metadata });
const signatureFromMetadata = (metadata) => {
const anthropic = metadata?.anthropic;
if (!ProviderShared.isRecord(anthropic))
return undefined;
return typeof anthropic.signature === "string" ? anthropic.signature : undefined;
};
const redactedDataFromMetadata = (metadata) => {
const anthropic = metadata?.anthropic;
if (!ProviderShared.isRecord(anthropic))
return undefined;
return typeof anthropic.redactedData === "string" ? anthropic.redactedData : undefined;
};
const lowerTool = (breakpoints, tool, inputSchema) => ({
name: tool.name,
description: tool.description,
input_schema: inputSchema,
cache_control: cacheControl(breakpoints, tool.cache),
});
const lowerToolChoice = (toolChoice) => ProviderShared.matchToolChoice("Anthropic Messages", toolChoice, {
auto: () => ({ type: "auto" }),
none: () => ({ type: "none" }),
required: () => ({ type: "any" }),
tool: (name) => ({ type: "tool", name }),
});
const lowerToolCall = (part) => ({
type: "tool_use",
id: part.id,
name: part.name,
input: part.input,
});
const lowerServerToolCall = (part) => ({
type: "server_tool_use",
id: part.id,
name: part.name,
input: part.input,
});
// Server tool result blocks are typed by name. Anthropic ships three today;
// extend this list when new server tools land. The block content is the
// structured payload returned by the provider, which we round-trip as-is.
const serverToolResultType = (name) => {
if (name === "web_search")
return "web_search_tool_result";
if (name === "code_execution")
return "code_execution_tool_result";
if (name === "web_fetch")
return "web_fetch_tool_result";
return undefined;
};
const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part) {
const wireType = serverToolResultType(part.name);
if (!wireType)
return yield* invalid(`Anthropic Messages does not know how to round-trip server tool result for ${part.name}`);
// Prefer the provider-owned replay payload; fall back to the result value for
// histories constructed directly from provider events.
const payload = part.providerMetadata?.anthropic?.["result"] ?? part.result.value;
return { type: wireType, tool_use_id: part.id, content: payload };
});
const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part) {
const media = yield* ProviderShared.validateMedia("Anthropic Messages", part, MEDIA_MIMES);
if (media.mime === "application/pdf")
return {
type: "document",
source: {
type: "base64",
media_type: "application/pdf",
data: media.base64,
},
};
return {
type: "image",
source: {
type: "base64",
media_type: media.mime,
data: media.base64,
},
};
});
// Tool results may carry structured text, images, and documents. Keep media as provider-native
// content instead of JSON-stringifying base64 into a prompt string.
const lowerToolResultContentItem = Effect.fnUntraced(function* (item) {
if (item.type === "text")
return { type: "text", text: item.text };
return yield* lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name });
});
const lowerToolResultContent = Effect.fnUntraced(function* (part) {
// Text / json / error results stay as a string for backward compatibility
// with existing cassettes and provider expectations.
if (part.result.type !== "content")
return ProviderShared.toolResultText(part);
// Preserve the narrowed array element type when compiled through a consumer package.
const content = part.result.value;
return yield* Effect.forEach(content, lowerToolResultContentItem);
});
// Mid-conversation system messages are a native Claude API feature only for
// Opus 4.8. Other Anthropic models intentionally use the same visible wrapped-
// user fallback as non-Anthropic routes rather than sending a role they reject.
const supportsNativeSystemUpdates = (request) => String(request.model.id) === "claude-opus-4-8";
const endsInServerToolUse = (message) => {
const last = message.content.at(-1);
return message.role === "assistant" && last?.type === "tool-call" && last.providerExecuted === true;
};
const canUseNativeSystemUpdate = (messages, index) => {
const previous = messages[index - 1];
const next = messages[index + 1];
return (previous !== undefined &&
previous.role !== "system" &&
(previous.role === "user" || previous.role === "tool" || endsInServerToolUse(previous)) &&
next?.role !== "system" &&
(next === undefined || next.role === "assistant"));
};
const splitsLocalToolResults = (messages, index) => {
const pending = new Set();
for (const message of messages.slice(0, index)) {
for (const part of message.content) {
if (message.role === "assistant" && part.type === "tool-call" && part.providerExecuted !== true)
pending.add(part.id);
if (message.role === "tool" && part.type === "tool-result")
pending.delete(part.id);
}
}
return pending.size > 0;
};
const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUpdate")(function* (message, breakpoints) {
const content = yield* ProviderShared.systemUpdateText("Anthropic Messages", message);
return {
role: "system",
content: content.map((part) => ({
type: "text",
text: part.text,
cache_control: cacheControl(breakpoints, part.cache),
})),
};
});
const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (request, breakpoints) {
const messages = [];
for (const [index, message] of request.messages.entries()) {
if (message.role === "system") {
if (splitsLocalToolResults(request.messages, index))
return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result");
if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request.messages, index)) {
messages.push(yield* lowerNativeSystemUpdate(message, breakpoints));
continue;
}
const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message);
const block = { type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) };
const previous = messages.at(-1);
if (previous?.role === "user")
messages[messages.length - 1] = { role: "user", content: [...previous.content, block] };
else
messages.push({ role: "user", content: [block] });
continue;
}
if (message.role === "user") {
const content = [];
for (const part of message.content) {
if (part.type === "text") {
content.push({ type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) });
continue;
}
if (part.type === "media") {
content.push(yield* lowerMedia(part));
continue;
}
return yield* ProviderShared.unsupportedContent("Anthropic Messages", "user", ["text", "media"]);
}
messages.push({ role: "user", content });
continue;
}
if (message.role === "assistant") {
const content = [];
for (const part of message.content) {
if (part.type === "text") {
content.push({ type: "text", text: part.text, cache_control: cacheControl(breakpoints, part.cache) });
continue;
}
if (part.type === "reasoning") {
// Mirrors Vercel's @ai-sdk/anthropic: a signature marks visible
// thinking; only signature-less parts carrying redactedData
// round-trip as opaque redacted_thinking blocks.
const signature = part.encrypted ?? signatureFromMetadata(part.providerMetadata);
const redactedData = redactedDataFromMetadata(part.providerMetadata);
if (signature === undefined && redactedData !== undefined) {
content.push({ type: "redacted_thinking", data: redactedData });
continue;
}
content.push({ type: "thinking", thinking: part.text, signature });
continue;
}
if (part.type === "tool-call") {
content.push(part.providerExecuted ? lowerServerToolCall(part) : lowerToolCall(part));
continue;
}
if (part.type === "tool-result" && part.providerExecuted) {
content.push(yield* lowerServerToolResult(part));
continue;
}
return yield* invalid(`Anthropic Messages assistant messages only support text, reasoning, and tool-call content for now`);
}
messages.push({ role: "assistant", content });
continue;
}
const content = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["tool-result"]))
return yield* ProviderShared.unsupportedContent("Anthropic Messages", "tool", ["tool-result"]);
content.push({
type: "tool_result",
tool_use_id: part.id,
content: yield* lowerToolResultContent(part),
is_error: part.result.type === "error" ? true : undefined,
cache_control: cacheControl(breakpoints, part.cache),
});
}
const previous = messages.at(-1);
if (previous?.role === "user" && previous.content.every((block) => block.type === "tool_result"))
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] };
else
messages.push({ role: "user", content });
}
return messages;
});
const resolveOptions = Effect.fn("AnthropicMessages.resolveOptions")(function* (request) {
const input = request.providerOptions?.anthropic;
return {
thinking: yield* resolveThinking(input?.thinking),
effort: typeof input?.effort === "string" ? input.effort : undefined,
};
});
const resolveThinking = Effect.fn("AnthropicMessages.resolveThinking")(function* (input) {
if (!ProviderShared.isRecord(input))
return undefined;
if (input.type === "adaptive") {
const display = input.display === "summarized"
? "summarized"
: input.display === "omitted"
? "omitted"
: undefined;
return { type: "adaptive", ...(display === undefined ? {} : { display }) };
}
if (input.type === "disabled")
return { type: "disabled" };
if (input.type !== "enabled")
return undefined;
const budget = typeof input.budgetTokens === "number"
? input.budgetTokens
: typeof input.budget_tokens === "number"
? input.budget_tokens
: undefined;
if (budget === undefined)
return yield* ProviderShared.invalidRequest("Anthropic thinking provider option requires budgetTokens");
return { type: "enabled", budget_tokens: budget };
});
const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request) {
const generation = request.generation;
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
const outputLimit = request.model.defaults?.limits?.output ?? request.model.route.defaults.limits?.output ?? 4096;
// Allocate the 4-breakpoint budget in invalidation order: tools → system →
// messages. Tools live highest in the cache hierarchy, so when callers
// over-mark we keep their tool hints and shed the message-tail ones first.
const breakpoints = Cache.newBreakpoints(ANTHROPIC_BREAKPOINT_CAP);
const tools = request.tools.length === 0
? undefined
: request.tools.map((tool) => lowerTool(breakpoints, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility)));
// Anthropic rejects tool_choice when tools are absent; "none" is only meaningful with tools present.
const toolChoice = tools === undefined || !request.toolChoice ? undefined : yield* lowerToolChoice(request.toolChoice);
const system = request.system.length === 0
? undefined
: request.system.map((part) => ({
type: "text",
text: part.text,
cache_control: cacheControl(breakpoints, part.cache),
}));
const messages = yield* lowerMessages(request, breakpoints);
if (breakpoints.dropped > 0) {
yield* Effect.logWarning(`Anthropic Messages: dropped ${breakpoints.dropped} cache breakpoint(s); the API allows at most ${ANTHROPIC_BREAKPOINT_CAP} per request.`);
}
const options = yield* resolveOptions(request);
return {
model: request.model.id,
system,
messages,
tools,
tool_choice: toolChoice,
stream: true,
max_tokens: generation?.maxTokens ?? outputLimit,
temperature: generation?.temperature,
top_p: generation?.topP,
top_k: generation?.topK,
stop_sequences: generation?.stop,
thinking: options.thinking,
output_config: options.effort === undefined ? undefined : { effort: options.effort },
};
});
// =============================================================================
// Stream Parsing
// =============================================================================
const mapFinishReason = (reason) => {
if (reason === "end_turn" || reason === "stop_sequence" || reason === "pause_turn")
return "stop";
if (reason === "max_tokens" || reason === "model_context_window_exceeded")
return "length";
if (reason === "tool_use")
return "tool-calls";
if (reason === "refusal")
return "content-filter";
return "unknown";
};
// Anthropic reports the non-overlapping breakdown natively — its
// `input_tokens` is the *non-cached* count per the Messages API docs, with
// cache reads and writes as separate fields. We sum them to derive the
// inclusive `inputTokens` the rest of the contract expects. Extended
// thinking tokens are included in `output_tokens`; newer responses also
// expose that subset through `output_tokens_details.thinking_tokens`.
const mapUsage = (usage) => {
if (!usage)
return undefined;
const nonCached = usage.input_tokens;
const cacheRead = usage.cache_read_input_tokens ?? undefined;
const cacheWrite = usage.cache_creation_input_tokens ?? undefined;
const inputTokens = ProviderShared.sumTokens(nonCached, cacheRead, cacheWrite);
return new Usage({
inputTokens,
outputTokens: usage.output_tokens,
nonCachedInputTokens: nonCached,
cacheReadInputTokens: cacheRead,
cacheWriteInputTokens: cacheWrite,
reasoningTokens: usage.output_tokens_details?.thinking_tokens,
totalTokens: ProviderShared.totalTokens(inputTokens, usage.output_tokens, undefined),
providerMetadata: { anthropic: usage },
});
};
// Anthropic emits usage on `message_start` and again on `message_delta` — the
// final delta carries the authoritative totals. Right-biased merge: each
// field prefers `right` when defined, falls back to `left`. `inputTokens` is
// recomputed from the merged breakdown so the inclusive total stays
// consistent with `nonCached + cacheRead + cacheWrite`.
const mergeUsage = (left, right) => {
if (!left)
return right;
if (!right)
return left;
const nonCachedInputTokens = right.nonCachedInputTokens ?? left.nonCachedInputTokens;
const cacheReadInputTokens = right.cacheReadInputTokens ?? left.cacheReadInputTokens;
const cacheWriteInputTokens = right.cacheWriteInputTokens ?? left.cacheWriteInputTokens;
const inputTokens = ProviderShared.sumTokens(nonCachedInputTokens, cacheReadInputTokens, cacheWriteInputTokens);
const outputTokens = right.outputTokens ?? left.outputTokens;
const reasoningTokens = right.reasoningTokens ?? left.reasoningTokens;
return new Usage({
inputTokens,
outputTokens,
nonCachedInputTokens,
cacheReadInputTokens,
cacheWriteInputTokens,
reasoningTokens,
totalTokens: ProviderShared.totalTokens(inputTokens, outputTokens, undefined),
providerMetadata: {
anthropic: mergeJsonRecords(left.providerMetadata?.["anthropic"], right.providerMetadata?.["anthropic"]) ?? {},
},
});
};
// Server tool result blocks come whole in `content_block_start` (no streaming
// delta sequence). We convert the payload to a `tool-result` event with
// `providerExecuted: true`. The runtime appends it to the assistant message
// for round-trip; downstream consumers can inspect `result.value` for the
// structured payload.
const SERVER_TOOL_RESULT_NAMES = {
web_search_tool_result: "web_search",
code_execution_tool_result: "code_execution",
web_fetch_tool_result: "web_fetch",
};
const isServerToolResultType = (type) => type in SERVER_TOOL_RESULT_NAMES;
const serverToolResultEvent = (block) => {
if (!block.type || !isServerToolResultType(block.type))
return undefined;
const errorPayload = typeof block.content === "object" && block.content !== null && "type" in block.content
? String(block.content.type)
: "";
const isError = errorPayload.endsWith("_tool_result_error");
return LLMEvent.toolResult({
id: block.tool_use_id ?? "",
name: SERVER_TOOL_RESULT_NAMES[block.type],
result: isError ? { type: "error", value: block.content } : { type: "json", value: block.content },
providerExecuted: true,
// The complete payload is irreducible provider replay state: subsequent
// stateless requests must round-trip the typed result block verbatim.
providerMetadata: anthropicMetadata({ blockType: block.type, result: block.content }),
});
};
const NO_EVENTS = [];
const onMessageStart = (state, event) => {
const usage = mapUsage(event.message?.usage);
return [usage ? { ...state, usage: mergeUsage(state.usage, usage) } : state, NO_EVENTS];
};
const onContentBlockStart = (state, event) => {
const block = event.content_block;
if (!block)
return [state, NO_EVENTS];
if ((block.type === "tool_use" || block.type === "server_tool_use") && event.index !== undefined) {
const events = [];
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
return [
{
...state,
lifecycle,
tools: ToolStream.start(state.tools, event.index, {
id: block.id ?? String(event.index),
name: block.name ?? "",
input: block.input !== undefined && (!ProviderShared.isRecord(block.input) || Object.keys(block.input).length > 0)
? ProviderShared.encodeJson(block.input)
: undefined,
providerExecuted: block.type === "server_tool_use",
}),
},
[
...events,
LLMEvent.toolInputStart({
id: block.id ?? String(event.index),
name: block.name ?? "",
providerExecuted: block.type === "server_tool_use" ? true : undefined,
}),
],
];
}
if (block.type === "text" && block.text !== undefined) {
const events = [];
const id = `text-${event.index ?? 0}`;
const lifecycle = Lifecycle.textStart(state.lifecycle, events, id);
return [
{ ...state, lifecycle: block.text ? Lifecycle.textDelta(lifecycle, events, id, block.text) : lifecycle },
events,
];
}
if (block.type === "thinking" && block.thinking !== undefined) {
const events = [];
const id = `reasoning-${event.index ?? 0}`;
const providerMetadata = block.signature === undefined ? undefined : anthropicMetadata({ signature: block.signature });
const lifecycle = Lifecycle.reasoningStart(state.lifecycle, events, id, providerMetadata);
return [
{
...state,
lifecycle: block.thinking
? Lifecycle.reasoningDelta(lifecycle, events, id, block.thinking, providerMetadata)
: lifecycle,
reasoningSignatures: event.index === undefined || block.signature === undefined
? state.reasoningSignatures
: { ...state.reasoningSignatures, [event.index]: block.signature },
},
events,
];
}
// Redacted thinking surfaces as an empty reasoning part carrying the opaque
// payload as `redactedData` metadata (same model as Vercel's
// @ai-sdk/anthropic). The existing content_block_stop closes the part.
if (block.type === "redacted_thinking" && block.data !== undefined) {
const events = [];
return [
{
...state,
lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `reasoning-${event.index ?? 0}`, anthropicMetadata({ redactedData: block.data })),
},
events,
];
}
const result = serverToolResultEvent(block);
if (!result)
return [state, NO_EVENTS];
const events = [];
return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, [...events, result]];
};
const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* (state, event) {
const delta = event.delta;
if (delta?.type === "text_delta" && delta.text) {
const events = [];
return [
{ ...state, lifecycle: Lifecycle.textDelta(state.lifecycle, events, `text-${event.index ?? 0}`, delta.text) },
events,
];
}
if (delta?.type === "thinking_delta" && delta.thinking) {
const events = [];
return [
{
...state,
lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${event.index ?? 0}`, delta.thinking),
},
events,
];
}
if (delta?.type === "signature_delta" && delta.signature) {
const index = event.index ?? 0;
return [
{
...state,
reasoningSignatures: { ...state.reasoningSignatures, [index]: delta.signature },
},
NO_EVENTS,
];
}
if (delta?.type === "input_json_delta" && event.index !== undefined) {
if (!delta.partial_json)
return [state, NO_EVENTS];
const result = ToolStream.appendExisting(ADAPTER, state.tools, event.index, delta.partial_json, "Anthropic Messages tool argument delta is missing its tool call");
if (ToolStream.isError(result))
return yield* result;
const events = [];
const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
events.push(...result.events);
return [{ ...state, lifecycle, tools: result.tools }, events];
}
return [state, NO_EVENTS];
});
const onContentBlockStop = Effect.fn("AnthropicMessages.onContentBlockStop")(function* (state, event) {
if (event.index === undefined)
return [state, NO_EVENTS];
const result = yield* ToolStream.finish(ADAPTER, state.tools, event.index);
const events = [];
const resultEvents = result.events ?? [];
const signature = state.reasoningSignatures[event.index];
const lifecycle = resultEvents.length
? Lifecycle.stepStart(state.lifecycle, events)
: Lifecycle.reasoningEnd(Lifecycle.textEnd(state.lifecycle, events, `text-${event.index}`), events, `reasoning-${event.index}`, signature === undefined ? undefined : anthropicMetadata({ signature }));
events.push(...resultEvents);
const reasoningSignatures = { ...state.reasoningSignatures };
delete reasoningSignatures[event.index];
return [{ ...state, lifecycle, tools: result.tools, reasoningSignatures }, events];
});
const onMessageDelta = (state, event) => {
const usage = mergeUsage(state.usage, mapUsage(event.usage));
return [
{
...state,
usage,
pendingFinish: {
reason: {
normalized: mapFinishReason(event.delta?.stop_reason),
raw: event.delta?.stop_reason ?? undefined,
},
providerMetadata: event.delta?.stop_sequence === null || event.delta?.stop_sequence === undefined
? undefined
: anthropicMetadata({ stopSequence: event.delta.stop_sequence }),
},
},
NO_EVENTS,
];
};
const onMessageStop = (state) => {
const events = [];
const lifecycle = Lifecycle.finish(state.lifecycle, events, {
reason: state.pendingFinish?.reason ?? {
normalized: "unknown",
raw: undefined,
},
usage: state.usage,
providerMetadata: state.pendingFinish?.providerMetadata,
});
return [{ ...state, lifecycle }, events];
};
// Prefix `error.type` so overloads, rate limits, and quota errors are visible
// even when the provider message is generic or empty.
const providerErrorMessage = (event) => {
const type = event.error?.type;
const message = event.error?.message;
if (type && message)
return `${type}: ${message}`;
return message || type || "Anthropic Messages stream error";
};
const onError = (event) => new AIError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({ message: providerErrorMessage(event), code: event.error?.type }),
});
const step = (state, event) => {
if (event.type === "message_start")
return Effect.succeed(onMessageStart(state, event));
if (event.type === "content_block_start")
return Effect.succeed(onContentBlockStart(state, event));
if (event.type === "content_block_delta")
return onContentBlockDelta(state, event);
if (event.type === "content_block_stop")
return onContentBlockStop(state, event);
if (event.type === "message_delta")
return Effect.succeed(onMessageDelta(state, event));
if (event.type === "message_stop")
return Effect.succeed(onMessageStop(state));
if (event.type === "error")
return onError(event);
return Effect.succeed([state, NO_EVENTS]);
};
// =============================================================================
// Protocol And Anthropic Route
// =============================================================================
/**
* The Anthropic Messages protocol — request body construction, body schema,
* and the streaming-event state machine. Used by native Anthropic Cloud and
* (once registered) Vertex Anthropic / Bedrock-hosted Anthropic passthrough.
*/
export const protocol = Protocol.make({
id: ADAPTER,
body: {
schema: AnthropicMessagesBody,
from: fromRequest,
},
stream: {
event: Protocol.jsonEvent(AnthropicEvent),
initial: () => ({
tools: ToolStream.empty(),
reasoningSignatures: {},
lifecycle: Lifecycle.initial(),
}),
step,
},
});
export const route = Route.make({
id: ADAPTER,
provider: "anthropic",
providerMetadataKey: "anthropic",
protocol,
endpoint: Endpoint.path(PATH, { baseURL: DEFAULT_BASE_URL }),
auth: Auth.none,
framing: Framing.sse,
headers: () => ({ "anthropic-version": "2023-06-01" }),
});
export * as AnthropicMessages from "./anthropic-messages.js";
import { Schema } from "effect";
import { Route } from "../route/client.js";
import { Protocol } from "../route/protocol.js";
import { Usage, type FinishReasonDetails } from "../schema/index.js";
import { BedrockAuth } from "./utils/bedrock-auth.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolStream } from "./utils/tool-stream.js";
export type { Credentials as BedrockCredentials } from "./utils/bedrock-auth.js";
declare const BedrockConverseBody: Schema.Struct<{
modelId: Schema.String;
messages: Schema.$Array<Schema.toTaggedUnion<"role", readonly [Schema.Struct<{
readonly role: Schema.Literal<"user">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly text: Schema.String;
}>, Schema.Struct<{
readonly image: Schema.Struct<{
readonly format: Schema.Literals<readonly ["png", "jpeg", "gif", "webp"]>;
readonly source: Schema.Struct<{
readonly bytes: Schema.String;
}>;
}>;
}>, Schema.Struct<{
readonly document: Schema.Struct<{
readonly format: Schema.Literals<readonly ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]>;
readonly name: Schema.String;
readonly source: Schema.Struct<{
readonly bytes: Schema.String;
}>;
}>;
}>, Schema.Struct<{
readonly toolResult: Schema.Struct<{
readonly toolUseId: Schema.String;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly text: Schema.String;
}>, Schema.Struct<{
readonly json: Schema.Unknown;
}>, Schema.Struct<{
readonly image: Schema.Struct<{
readonly format: Schema.Literals<readonly ["png", "jpeg", "gif", "webp"]>;
readonly source: Schema.Struct<{
readonly bytes: Schema.String;
}>;
}>;
}>, Schema.Struct<{
readonly document: Schema.Struct<{
readonly format: Schema.Literals<readonly ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]>;
readonly name: Schema.String;
readonly source: Schema.Struct<{
readonly bytes: Schema.String;
}>;
}>;
}>]>>;
readonly status: Schema.optional<Schema.Literals<readonly ["success", "error"]>>;
}>;
}>, Schema.Struct<{
readonly cachePoint: Schema.Struct<{
readonly type: Schema.tag<"default">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>;
}>]>>;
}>, Schema.Struct<{
readonly role: Schema.Literal<"assistant">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly text: Schema.String;
}>, Schema.Struct<{
readonly reasoningContent: Schema.Union<readonly [Schema.Struct<{
readonly reasoningText: Schema.Struct<{
readonly text: Schema.String;
readonly signature: Schema.optional<Schema.String>;
}>;
}>, Schema.Struct<{
readonly redactedContent: Schema.String;
}>]>;
}>, Schema.Struct<{
readonly toolUse: Schema.Struct<{
readonly toolUseId: Schema.String;
readonly name: Schema.String;
readonly input: Schema.Unknown;
}>;
}>, Schema.Struct<{
readonly cachePoint: Schema.Struct<{
readonly type: Schema.tag<"default">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>;
}>]>>;
}>]>>;
system: Schema.optional<Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly text: Schema.String;
}>, Schema.Struct<{
readonly cachePoint: Schema.Struct<{
readonly type: Schema.tag<"default">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>;
}>]>>>;
inferenceConfig: Schema.optional<Schema.Struct<{
readonly maxTokens: Schema.optional<Schema.Number>;
readonly temperature: Schema.optional<Schema.Number>;
readonly topP: Schema.optional<Schema.Number>;
readonly stopSequences: Schema.optional<Schema.$Array<Schema.String>>;
}>>;
toolConfig: Schema.optional<Schema.Struct<{
readonly tools: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly toolSpec: Schema.Struct<{
readonly name: Schema.String;
readonly description: Schema.String;
readonly inputSchema: Schema.Struct<{
readonly json: Schema.$Record<Schema.String, Schema.Unknown>;
}>;
}>;
}>, Schema.Struct<{
readonly cachePoint: Schema.Struct<{
readonly type: Schema.tag<"default">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>;
}>]>>;
readonly toolChoice: Schema.optional<Schema.Union<readonly [Schema.Struct<{
readonly auto: Schema.Struct<{}>;
}>, Schema.Struct<{
readonly any: Schema.Struct<{}>;
}>, Schema.Struct<{
readonly tool: Schema.Struct<{
readonly name: Schema.String;
}>;
}>]>>;
}>>;
additionalModelRequestFields: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>;
export type BedrockConverseBody = Schema.Schema.Type<typeof BedrockConverseBody>;
interface ParserState {
readonly tools: ToolStream.State<number>;
readonly pendingFinish: {
readonly reason: FinishReasonDetails;
readonly usage?: Usage;
} | undefined;
readonly hasToolCalls: boolean;
readonly lifecycle: Lifecycle.State;
readonly reasoningSignatures: Readonly<Record<number, string>>;
}
/**
* The Bedrock Converse protocol — request body construction, body schema, and
* the streaming-event state machine.
*/
export declare const protocol: Protocol<{
readonly messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly image: {
readonly format: "png" | "jpeg" | "gif" | "webp";
readonly source: {
readonly bytes: string;
};
};
} | {
readonly document: {
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
readonly name: string;
readonly source: {
readonly bytes: string;
};
};
} | {
readonly text: string;
} | {
readonly toolResult: {
readonly content: readonly ({
readonly image: {
readonly format: "png" | "jpeg" | "gif" | "webp";
readonly source: {
readonly bytes: string;
};
};
} | {
readonly document: {
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
readonly name: string;
readonly source: {
readonly bytes: string;
};
};
} | {
readonly text: string;
} | {
readonly json: unknown;
})[];
readonly toolUseId: string;
readonly status?: "error" | "success" | undefined;
};
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly text: string;
} | {
readonly toolUse: {
readonly toolUseId: string;
readonly name: string;
readonly input: unknown;
};
} | {
readonly reasoningContent: {
readonly reasoningText: {
readonly text: string;
readonly signature?: string | undefined;
};
} | {
readonly redactedContent: string;
};
})[];
})[];
readonly modelId: string;
readonly system?: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly text: string;
})[] | undefined;
readonly inferenceConfig?: {
readonly maxTokens?: number | undefined;
readonly temperature?: number | undefined;
readonly topP?: number | undefined;
readonly stopSequences?: readonly string[] | undefined;
} | undefined;
readonly toolConfig?: {
readonly tools: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly toolSpec: {
readonly name: string;
readonly description: string;
readonly inputSchema: {
readonly json: {
readonly [x: string]: unknown;
};
};
};
})[];
readonly toolChoice?: {
readonly auto: {};
} | {
readonly any: {};
} | {
readonly tool: {
readonly name: string;
};
} | undefined;
} | undefined;
readonly additionalModelRequestFields?: {
readonly [x: string]: unknown;
} | undefined;
}, {
readonly metadata?: {
readonly usage?: {
readonly inputTokens?: number | undefined;
readonly outputTokens?: number | undefined;
readonly cacheReadInputTokens?: number | undefined;
readonly cacheWriteInputTokens?: number | undefined;
readonly totalTokens?: number | undefined;
} | undefined;
readonly metrics?: unknown;
} | undefined;
readonly messageStart?: {
readonly role: string;
} | undefined;
readonly contentBlockStart?: {
readonly contentBlockIndex: number;
readonly start?: {
readonly toolUse?: {
readonly toolUseId: string;
readonly name: string;
} | undefined;
} | undefined;
} | undefined;
readonly contentBlockDelta?: {
readonly contentBlockIndex: number;
readonly delta?: {
readonly text?: string | undefined;
readonly toolUse?: {
readonly input: string;
} | undefined;
readonly reasoningContent?: {
readonly data?: string | undefined;
readonly text?: string | undefined;
readonly signature?: string | undefined;
readonly redactedContent?: string | undefined;
} | undefined;
} | undefined;
} | undefined;
readonly contentBlockStop?: {
readonly contentBlockIndex: number;
} | undefined;
readonly messageStop?: {
readonly stopReason: string;
readonly additionalModelResponseFields?: unknown;
} | undefined;
readonly internalServerException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly modelStreamErrorException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly validationException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly throttlingException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly serviceUnavailableException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
}, {
readonly metadata?: {
readonly usage?: {
readonly inputTokens?: number | undefined;
readonly outputTokens?: number | undefined;
readonly cacheReadInputTokens?: number | undefined;
readonly cacheWriteInputTokens?: number | undefined;
readonly totalTokens?: number | undefined;
} | undefined;
readonly metrics?: unknown;
} | undefined;
readonly messageStart?: {
readonly role: string;
} | undefined;
readonly contentBlockStart?: {
readonly contentBlockIndex: number;
readonly start?: {
readonly toolUse?: {
readonly toolUseId: string;
readonly name: string;
} | undefined;
} | undefined;
} | undefined;
readonly contentBlockDelta?: {
readonly contentBlockIndex: number;
readonly delta?: {
readonly text?: string | undefined;
readonly toolUse?: {
readonly input: string;
} | undefined;
readonly reasoningContent?: {
readonly data?: string | undefined;
readonly text?: string | undefined;
readonly signature?: string | undefined;
readonly redactedContent?: string | undefined;
} | undefined;
} | undefined;
} | undefined;
readonly contentBlockStop?: {
readonly contentBlockIndex: number;
} | undefined;
readonly messageStop?: {
readonly stopReason: string;
readonly additionalModelResponseFields?: unknown;
} | undefined;
readonly internalServerException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly modelStreamErrorException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly validationException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly throttlingException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
readonly serviceUnavailableException?: {
readonly message?: string | undefined;
readonly originalMessage?: string | undefined;
readonly originalStatusCode?: number | undefined;
} | undefined;
}, ParserState>;
export declare const route: Route<{
readonly messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly image: {
readonly format: "png" | "jpeg" | "gif" | "webp";
readonly source: {
readonly bytes: string;
};
};
} | {
readonly document: {
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
readonly name: string;
readonly source: {
readonly bytes: string;
};
};
} | {
readonly text: string;
} | {
readonly toolResult: {
readonly content: readonly ({
readonly image: {
readonly format: "png" | "jpeg" | "gif" | "webp";
readonly source: {
readonly bytes: string;
};
};
} | {
readonly document: {
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
readonly name: string;
readonly source: {
readonly bytes: string;
};
};
} | {
readonly text: string;
} | {
readonly json: unknown;
})[];
readonly toolUseId: string;
readonly status?: "error" | "success" | undefined;
};
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly text: string;
} | {
readonly toolUse: {
readonly toolUseId: string;
readonly name: string;
readonly input: unknown;
};
} | {
readonly reasoningContent: {
readonly reasoningText: {
readonly text: string;
readonly signature?: string | undefined;
};
} | {
readonly redactedContent: string;
};
})[];
})[];
readonly modelId: string;
readonly system?: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly text: string;
})[] | undefined;
readonly inferenceConfig?: {
readonly maxTokens?: number | undefined;
readonly temperature?: number | undefined;
readonly topP?: number | undefined;
readonly stopSequences?: readonly string[] | undefined;
} | undefined;
readonly toolConfig?: {
readonly tools: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly toolSpec: {
readonly name: string;
readonly description: string;
readonly inputSchema: {
readonly json: {
readonly [x: string]: unknown;
};
};
};
})[];
readonly toolChoice?: {
readonly auto: {};
} | {
readonly any: {};
} | {
readonly tool: {
readonly name: string;
};
} | undefined;
} | undefined;
readonly additionalModelRequestFields?: {
readonly [x: string]: unknown;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<object>>;
export declare const sigV4Auth: (credentials: BedrockAuth.Credentials | undefined, options?: {
readonly service?: string;
readonly name?: string;
}) => import("../route.js").AuthShape;
export * as BedrockConverse from "./bedrock-converse.js";
import { Effect, Schema } from "effect";
import { Route } from "../route/client.js";
import { Endpoint } from "../route/endpoint.js";
import { Protocol } from "../route/protocol.js";
import { AIError, LLMEvent, Usage, } from "../schema/index.js";
import { BedrockEventStream } from "./bedrock-event-stream.js";
import { classifyProviderFailure } from "../provider-error.js";
import { JsonObject, optionalArray, ProviderShared } from "./shared.js";
import { BedrockAuth } from "./utils/bedrock-auth.js";
import { BedrockCache } from "./utils/bedrock-cache.js";
import { BedrockMedia } from "./utils/bedrock-media.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolSchemaProjection } from "./utils/tool-schema.js";
import { ToolStream } from "./utils/tool-stream.js";
const ADAPTER = "bedrock-converse";
// =============================================================================
// Request Body Schema
// =============================================================================
const BedrockTextBlock = Schema.Struct({
text: Schema.String,
});
const BedrockToolUseBlock = Schema.Struct({
toolUse: Schema.Struct({
toolUseId: Schema.String,
name: Schema.String,
input: Schema.Unknown,
}),
});
const BedrockToolResultContentItem = Schema.Union([
Schema.Struct({ text: Schema.String }),
Schema.Struct({ json: Schema.Unknown }),
BedrockMedia.ImageBlock,
BedrockMedia.DocumentBlock,
]);
const BedrockToolResultBlock = Schema.Struct({
toolResult: Schema.Struct({
toolUseId: Schema.String,
content: Schema.Array(BedrockToolResultContentItem),
status: Schema.optional(Schema.Literals(["success", "error"])),
}),
});
const BedrockReasoningBlock = Schema.Struct({
reasoningContent: Schema.Union([
Schema.Struct({
reasoningText: Schema.Struct({
text: Schema.String,
signature: Schema.optional(Schema.String),
}),
}),
Schema.Struct({ redactedContent: Schema.String }),
]),
});
const BedrockUserBlock = Schema.Union([
BedrockTextBlock,
BedrockMedia.ImageBlock,
BedrockMedia.DocumentBlock,
BedrockToolResultBlock,
BedrockCache.CachePointBlock,
]);
const BedrockAssistantBlock = Schema.Union([
BedrockTextBlock,
BedrockReasoningBlock,
BedrockToolUseBlock,
BedrockCache.CachePointBlock,
]);
const BedrockMessage = Schema.Union([
Schema.Struct({ role: Schema.Literal("user"), content: Schema.Array(BedrockUserBlock) }),
Schema.Struct({ role: Schema.Literal("assistant"), content: Schema.Array(BedrockAssistantBlock) }),
]).pipe(Schema.toTaggedUnion("role"));
const BedrockSystemBlock = Schema.Union([BedrockTextBlock, BedrockCache.CachePointBlock]);
const BedrockToolSpec = Schema.Struct({
toolSpec: Schema.Struct({
name: Schema.String,
description: Schema.String,
inputSchema: Schema.Struct({
json: JsonObject,
}),
}),
});
const BedrockTool = Schema.Union([BedrockToolSpec, BedrockCache.CachePointBlock]);
const BedrockToolChoice = Schema.Union([
Schema.Struct({ auto: Schema.Struct({}) }),
Schema.Struct({ any: Schema.Struct({}) }),
Schema.Struct({ tool: Schema.Struct({ name: Schema.String }) }),
]);
const BedrockBodyFields = {
modelId: Schema.String,
messages: Schema.Array(BedrockMessage),
system: optionalArray(BedrockSystemBlock),
inferenceConfig: Schema.optional(Schema.Struct({
maxTokens: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
topP: Schema.optional(Schema.Number),
stopSequences: optionalArray(Schema.String),
})),
toolConfig: Schema.optional(Schema.Struct({
tools: Schema.Array(BedrockTool),
toolChoice: Schema.optional(BedrockToolChoice),
})),
additionalModelRequestFields: Schema.optional(JsonObject),
};
const BedrockConverseBody = Schema.Struct(BedrockBodyFields);
const BedrockUsageSchema = Schema.Struct({
inputTokens: Schema.optional(Schema.Number),
outputTokens: Schema.optional(Schema.Number),
totalTokens: Schema.optional(Schema.Number),
cacheReadInputTokens: Schema.optional(Schema.Number),
cacheWriteInputTokens: Schema.optional(Schema.Number),
});
const BedrockStreamException = Schema.Struct({
message: Schema.optional(Schema.String),
originalMessage: Schema.optional(Schema.String),
originalStatusCode: Schema.optional(Schema.Number),
});
// Streaming event shape — the AWS event stream wraps each JSON payload by its
// `:event-type` header (e.g. `messageStart`, `contentBlockDelta`). We
// reconstruct that wrapping in `decodeFrames` below so the event schema can
// stay a plain discriminated record.
const BedrockEvent = Schema.Struct({
messageStart: Schema.optional(Schema.Struct({ role: Schema.String })),
contentBlockStart: Schema.optional(Schema.Struct({
contentBlockIndex: Schema.Number,
start: Schema.optional(Schema.Struct({
toolUse: Schema.optional(Schema.Struct({ toolUseId: Schema.String, name: Schema.String })),
})),
})),
contentBlockDelta: Schema.optional(Schema.Struct({
contentBlockIndex: Schema.Number,
delta: Schema.optional(Schema.Struct({
text: Schema.optional(Schema.String),
toolUse: Schema.optional(Schema.Struct({ input: Schema.String })),
reasoningContent: Schema.optional(Schema.Struct({
text: Schema.optional(Schema.String),
signature: Schema.optional(Schema.String),
// Blob fields in Bedrock's JSON event stream are base64 strings.
redactedContent: Schema.optional(Schema.String),
// Vercel's Bedrock provider exposes the same delta under
// Anthropic's shorter `data` spelling.
data: Schema.optional(Schema.String),
})),
})),
})),
contentBlockStop: Schema.optional(Schema.Struct({ contentBlockIndex: Schema.Number })),
messageStop: Schema.optional(Schema.Struct({
stopReason: Schema.String,
additionalModelResponseFields: Schema.optional(Schema.Unknown),
})),
metadata: Schema.optional(Schema.Struct({
usage: Schema.optional(BedrockUsageSchema),
metrics: Schema.optional(Schema.Unknown),
})),
internalServerException: Schema.optional(BedrockStreamException),
modelStreamErrorException: Schema.optional(BedrockStreamException),
validationException: Schema.optional(BedrockStreamException),
throttlingException: Schema.optional(BedrockStreamException),
serviceUnavailableException: Schema.optional(BedrockStreamException),
});
// =============================================================================
// Request Lowering
// =============================================================================
const lowerToolSpec = (tool, inputSchema) => ({
toolSpec: {
name: tool.name,
description: tool.description,
inputSchema: { json: inputSchema },
},
});
const lowerTools = (compatibility, breakpoints, tools) => {
const result = [];
for (const tool of tools) {
result.push(lowerToolSpec(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, compatibility)));
const cachePoint = BedrockCache.block(breakpoints, tool.cache);
if (cachePoint)
result.push(cachePoint);
}
return result;
};
const textWithCache = (breakpoints, text, cache) => {
const cachePoint = BedrockCache.block(breakpoints, cache);
return cachePoint ? [{ text }, cachePoint] : [{ text }];
};
const lowerToolChoice = (toolChoice) => ProviderShared.matchToolChoice("Bedrock Converse", toolChoice, {
auto: () => ({ auto: {} }),
none: () => undefined,
required: () => ({ any: {} }),
tool: (name) => ({ tool: { name } }),
});
const bedrockMetadata = (metadata) => ({ bedrock: metadata });
const reasoningSignature = (part) => {
const bedrock = part.providerMetadata?.bedrock;
return (part.encrypted ??
(ProviderShared.isRecord(bedrock) && typeof bedrock.signature === "string" ? bedrock.signature : undefined));
};
const reasoningRedactedData = (part) => {
const bedrock = part.providerMetadata?.bedrock;
return ProviderShared.isRecord(bedrock) && typeof bedrock.redactedData === "string" ? bedrock.redactedData : undefined;
};
const lowerToolCall = (part) => ({
toolUse: {
toolUseId: part.id,
name: part.name,
input: part.input,
},
});
const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (part) {
if (part.result.type === "text" || part.result.type === "error")
return [{ text: ProviderShared.toolResultText(part) }];
if (part.result.type === "json")
return [{ json: part.result.value }];
const content = [];
for (const item of part.result.value) {
if (item.type === "text") {
content.push({ text: item.text });
continue;
}
const media = yield* BedrockMedia.lower({
type: "media",
mediaType: item.mime,
data: item.uri,
filename: item.name,
});
content.push(media);
}
return content;
});
const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (part) {
return {
toolResult: {
toolUseId: part.id,
content: yield* lowerToolResultContent(part),
status: part.result.type === "error" ? "error" : "success",
},
};
});
const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (request, breakpoints) {
const messages = [];
for (const message of request.messages) {
if (message.role === "system") {
const part = yield* ProviderShared.wrappedSystemUpdate("Bedrock Converse", message);
const content = textWithCache(breakpoints, part.text, part.cache);
const previous = messages.at(-1);
if (previous?.role === "user")
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] };
else
messages.push({ role: "user", content });
continue;
}
if (message.role === "user") {
const content = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["text", "media"]))
return yield* ProviderShared.unsupportedContent("Bedrock Converse", "user", ["text", "media"]);
if (part.type === "text") {
content.push(...textWithCache(breakpoints, part.text, part.cache));
continue;
}
if (part.type === "media") {
content.push(yield* BedrockMedia.lower(part));
continue;
}
}
const previous = messages.at(-1);
if (previous?.role === "user")
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] };
else
messages.push({ role: "user", content });
continue;
}
if (message.role === "assistant") {
const content = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
return yield* ProviderShared.unsupportedContent("Bedrock Converse", "assistant", [
"text",
"reasoning",
"tool-call",
]);
if (part.type === "text") {
content.push(...textWithCache(breakpoints, part.text, part.cache));
continue;
}
if (part.type === "reasoning") {
const signature = reasoningSignature(part);
const redactedData = reasoningRedactedData(part);
if (signature === undefined && redactedData !== undefined) {
content.push({ reasoningContent: { redactedContent: redactedData } });
continue;
}
content.push({ reasoningContent: { reasoningText: { text: part.text, signature } } });
continue;
}
if (part.type === "tool-call") {
content.push(lowerToolCall(part));
continue;
}
}
messages.push({ role: "assistant", content });
continue;
}
const content = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["tool-result"]))
return yield* ProviderShared.unsupportedContent("Bedrock Converse", "tool", ["tool-result"]);
content.push(yield* lowerToolResult(part));
const cachePoint = BedrockCache.block(breakpoints, part.cache);
if (cachePoint)
content.push(cachePoint);
}
const previous = messages.at(-1);
if (previous?.role === "user")
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] };
else
messages.push({ role: "user", content });
}
return messages;
});
// System prompts share the cache-point convention: emit the text block, then
// optionally a positional `cachePoint` marker.
const lowerSystem = (breakpoints, system) => system.flatMap((part) => textWithCache(breakpoints, part.text, part.cache));
const fromRequest = Effect.fn("BedrockConverse.fromRequest")(function* (request) {
const toolChoice = request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined;
const generation = request.generation;
// Bedrock-Claude shares Anthropic's 4-breakpoint cap. Spend the budget in
// tools → system → messages order to favour the highest-impact prefixes.
const breakpoints = BedrockCache.breakpoints();
const toolConfig = request.tools.length > 0
? {
tools: lowerTools(request.model.compatibility?.toolSchema, breakpoints, request.tools),
// Converse has no native "none". Keep definitions stable for prompt
// caching and omit only the unsupported choice.
toolChoice,
}
: undefined;
const system = request.system.length === 0 ? undefined : lowerSystem(breakpoints, request.system);
const messages = yield* lowerMessages(request, breakpoints);
if (breakpoints.dropped > 0) {
yield* Effect.logWarning(`Bedrock Converse: dropped ${breakpoints.dropped} cache breakpoint(s); the API allows at most ${BedrockCache.BEDROCK_BREAKPOINT_CAP} per request.`);
}
return {
modelId: request.model.id,
messages,
system,
inferenceConfig: generation?.maxTokens === undefined &&
generation?.temperature === undefined &&
generation?.topP === undefined &&
(generation?.stop === undefined || generation.stop.length === 0)
? undefined
: {
maxTokens: generation?.maxTokens,
temperature: generation?.temperature,
topP: generation?.topP,
stopSequences: generation?.stop,
},
toolConfig,
// Converse's base inferenceConfig has no topK; Anthropic/Nova accept it
// as a model-specific field, so it goes through additionalModelRequestFields.
additionalModelRequestFields: generation?.topK === undefined ? undefined : { top_k: generation.topK },
};
});
// =============================================================================
// Stream Parsing
// =============================================================================
const mapFinishReason = (reason) => {
if (reason === "end_turn" || reason === "stop_sequence")
return "stop";
if (reason === "max_tokens" || reason === "model_context_window_exceeded")
return "length";
if (reason === "tool_use")
return "tool-calls";
if (reason === "content_filtered" || reason === "guardrail_intervened")
return "content-filter";
if (reason === "malformed_model_output" || reason === "malformed_tool_use")
return "error";
return "unknown";
};
// AWS reports inputTokens separately from cache reads and writes.
// Bedrock does not break reasoning out of outputTokens for current models.
const mapUsage = (usage) => {
if (!usage)
return undefined;
const inputTokens = ProviderShared.sumTokens(usage.inputTokens, usage.cacheReadInputTokens, usage.cacheWriteInputTokens);
return new Usage({
inputTokens,
outputTokens: usage.outputTokens,
nonCachedInputTokens: usage.inputTokens,
cacheReadInputTokens: usage.cacheReadInputTokens,
cacheWriteInputTokens: usage.cacheWriteInputTokens,
totalTokens: ProviderShared.totalTokens(inputTokens, usage.outputTokens, usage.totalTokens),
providerMetadata: { bedrock: usage },
});
};
const step = (state, event) => Effect.gen(function* () {
if (event.contentBlockStart?.start?.toolUse) {
const index = event.contentBlockStart.contentBlockIndex;
const events = [];
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
return [
{
...state,
lifecycle,
tools: ToolStream.start(state.tools, index, {
id: event.contentBlockStart.start.toolUse.toolUseId,
name: event.contentBlockStart.start.toolUse.name,
}),
},
[
...events,
LLMEvent.toolInputStart({
id: event.contentBlockStart.start.toolUse.toolUseId,
name: event.contentBlockStart.start.toolUse.name,
}),
],
];
}
if (event.contentBlockDelta?.delta?.text) {
const events = [];
return [
{
...state,
lifecycle: Lifecycle.textDelta(state.lifecycle, events, `text-${event.contentBlockDelta.contentBlockIndex}`, event.contentBlockDelta.delta.text),
},
events,
];
}
if (event.contentBlockDelta?.delta?.reasoningContent) {
const index = event.contentBlockDelta.contentBlockIndex;
const reasoning = event.contentBlockDelta.delta.reasoningContent;
const events = [];
const redactedData = reasoning.redactedContent ?? reasoning.data;
const providerMetadata = reasoning.signature
? bedrockMetadata({ signature: reasoning.signature })
: redactedData !== undefined
? bedrockMetadata({ redactedData })
: undefined;
const lifecycle = reasoning.text !== undefined || providerMetadata !== undefined
? Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${index}`, reasoning.text ?? "", providerMetadata)
: state.lifecycle;
return [
{
...state,
lifecycle,
reasoningSignatures: reasoning.signature
? { ...state.reasoningSignatures, [index]: reasoning.signature }
: state.reasoningSignatures,
},
events,
];
}
if (event.contentBlockDelta?.delta?.toolUse) {
const index = event.contentBlockDelta.contentBlockIndex;
const result = ToolStream.appendExisting(ADAPTER, state.tools, index, event.contentBlockDelta.delta.toolUse.input, "Bedrock Converse tool delta is missing its tool call");
if (ToolStream.isError(result))
return yield* result;
const events = [];
const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
events.push(...result.events);
return [{ ...state, lifecycle, tools: result.tools }, events];
}
if (event.contentBlockStop) {
const index = event.contentBlockStop.contentBlockIndex;
const result = yield* ToolStream.finish(ADAPTER, state.tools, index);
const events = [];
const resultEvents = result.events ?? [];
const lifecycle = resultEvents.length
? Lifecycle.stepStart(state.lifecycle, events)
: Lifecycle.reasoningEnd(Lifecycle.textEnd(state.lifecycle, events, `text-${index}`), events, `reasoning-${index}`, state.reasoningSignatures[index]
? bedrockMetadata({ signature: state.reasoningSignatures[index] })
: undefined);
events.push(...resultEvents);
return [
{
...state,
hasToolCalls: resultEvents.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
state.hasToolCalls,
lifecycle,
tools: result.tools,
reasoningSignatures: Object.fromEntries(Object.entries(state.reasoningSignatures).filter(([key]) => key !== String(index))),
},
events,
];
}
if (event.messageStop) {
return [
{
...state,
pendingFinish: {
reason: {
normalized: mapFinishReason(event.messageStop.stopReason),
raw: event.messageStop.stopReason,
},
usage: state.pendingFinish?.usage,
},
},
[],
];
}
if (event.metadata) {
const usage = mapUsage(event.metadata.usage) ?? state.pendingFinish?.usage;
return [
{
...state,
pendingFinish: {
reason: state.pendingFinish?.reason ?? { normalized: "stop" },
usage,
},
},
[],
];
}
const exception = [
["internalServerException", event.internalServerException],
["modelStreamErrorException", event.modelStreamErrorException],
["serviceUnavailableException", event.serviceUnavailableException],
["throttlingException", event.throttlingException],
["validationException", event.validationException],
].find((entry) => entry[1] !== undefined);
if (exception) {
return yield* new AIError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({
message: exception[1]?.message ?? exception[1]?.originalMessage ?? "Bedrock Converse stream error",
code: exception[0],
}),
});
}
return [state, []];
});
const framing = BedrockEventStream.framing(ADAPTER);
const onHalt = (state) => state.pendingFinish
? (() => {
const events = [];
Lifecycle.finish(state.lifecycle, events, {
reason: {
...state.pendingFinish.reason,
normalized: state.pendingFinish.reason.normalized === "stop" && state.hasToolCalls
? "tool-calls"
: state.pendingFinish.reason.normalized,
},
usage: state.pendingFinish.usage,
});
return events;
})()
: [];
// =============================================================================
// Protocol And Bedrock Route
// =============================================================================
/**
* The Bedrock Converse protocol — request body construction, body schema, and
* the streaming-event state machine.
*/
export const protocol = Protocol.make({
id: ADAPTER,
body: {
schema: BedrockConverseBody,
from: fromRequest,
},
stream: {
event: BedrockEvent,
initial: () => ({
tools: ToolStream.empty(),
pendingFinish: undefined,
hasToolCalls: false,
lifecycle: Lifecycle.initial(),
reasoningSignatures: {},
}),
step,
onHalt,
},
});
export const route = Route.make({
id: ADAPTER,
provider: "bedrock",
providerMetadataKey: "bedrock",
protocol,
// Bedrock's URL embeds the region in the route endpoint host and the
// validated modelId in the path. We read the validated body so the URL
// matches the body that gets signed.
endpoint: Endpoint.path(({ body }) => `/model/${encodeURIComponent(body.modelId)}/converse-stream`),
auth: BedrockAuth.auth,
framing,
});
export const sigV4Auth = BedrockAuth.sigV4;
export * as BedrockConverse from "./bedrock-converse.js";
import { Framing } from "../route/framing.js";
/**
* AWS event-stream framing for Bedrock Converse. Each frame is decoded by
* `@smithy/eventstream-codec` (length + header + payload + CRC) and rewrapped
* under its `:event-type` header so the chunk schema can match the JSON
* payload directly.
*/
export declare const framing: (route: string) => Framing.Definition<object>;
export * as BedrockEventStream from "./bedrock-event-stream.js";
import { EventStreamCodec } from "@smithy/eventstream-codec";
import { fromUtf8, toUtf8 } from "@smithy/util-utf8";
import { Effect, Stream } from "effect";
import { Framing } from "../route/framing.js";
import { ProviderShared } from "./shared.js";
// Bedrock streams responses using the AWS event stream binary protocol — each
// frame is `[length:4][headers-length:4][prelude-crc:4][headers][payload][crc:4]`.
// We use `@smithy/eventstream-codec` to validate framing and CRCs, then
// reconstruct the JSON wrapping by `:event-type` so the chunk schema can match.
const eventCodec = new EventStreamCodec(toUtf8, fromUtf8);
const utf8 = new TextDecoder();
const initialFrameBuffer = { buffer: new Uint8Array(0), offset: 0 };
const appendChunk = (state, chunk) => {
const remaining = state.buffer.length - state.offset;
// Compact: drop the consumed prefix and append the new chunk in one alloc.
// This bounds buffer growth to at most one network chunk past the live
// window, regardless of stream length.
const next = new Uint8Array(remaining + chunk.length);
next.set(state.buffer.subarray(state.offset), 0);
next.set(chunk, remaining);
return { buffer: next, offset: 0 };
};
const consumeFrames = (route) => (state, chunk) => Effect.gen(function* () {
let cursor = appendChunk(state, chunk);
const out = [];
while (cursor.buffer.length - cursor.offset >= 4) {
const view = cursor.buffer.subarray(cursor.offset);
const totalLength = new DataView(view.buffer, view.byteOffset, view.byteLength).getUint32(0, false);
if (view.length < totalLength)
break;
const decoded = yield* Effect.try({
try: () => eventCodec.decode(view.subarray(0, totalLength)),
catch: (error) => ProviderShared.eventError(route, `Failed to decode Bedrock Converse event-stream frame: ${error instanceof Error ? error.message : String(error)}`),
});
cursor = { buffer: cursor.buffer, offset: cursor.offset + totalLength };
const messageType = decoded.headers[":message-type"]?.value;
if (messageType === "error") {
const code = decoded.headers[":error-code"]?.value;
const message = decoded.headers[":error-message"]?.value;
return yield* ProviderShared.eventError(route, [code, message].filter((value) => typeof value === "string").join(": ") ||
"Bedrock Converse event-stream error");
}
const eventType = messageType === "event"
? decoded.headers[":event-type"]?.value
: messageType === "exception"
? decoded.headers[":exception-type"]?.value
: undefined;
if (typeof eventType !== "string")
continue;
const payload = utf8.decode(decoded.body);
if (!payload)
continue;
// The AWS event stream pads short payloads with a `p` field. Drop it
// before handing the object to the chunk schema. JSON decode goes
// through the shared Schema-driven codec to satisfy the package rule
// against ad-hoc `JSON.parse` calls.
const parsed = (yield* ProviderShared.parseJson(route, payload, "Failed to parse Bedrock Converse event-stream payload"));
delete parsed.p;
out.push({ [eventType]: parsed });
}
return [cursor, out];
});
/**
* AWS event-stream framing for Bedrock Converse. Each frame is decoded by
* `@smithy/eventstream-codec` (length + header + payload + CRC) and rewrapped
* under its `:event-type` header so the chunk schema can match the JSON
* payload directly.
*/
export const framing = (route) => ({
id: "aws-event-stream",
frame: (bytes) => bytes.pipe(Stream.mapAccumEffect(() => initialFrameBuffer, consumeFrames(route))),
});
export * as BedrockEventStream from "./bedrock-event-stream.js";
import { Schema } from "effect";
import { Route } from "../route/client.js";
import { Protocol } from "../route/protocol.js";
import { type ProviderOptions } from "../schema/index.js";
import { Lifecycle } from "./utils/lifecycle.js";
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
export interface OptionsInput {
readonly [key: string]: unknown;
readonly cachedContent?: string;
readonly safetySettings?: ReadonlyArray<{
readonly category: "HARM_CATEGORY_UNSPECIFIED" | "HARM_CATEGORY_HATE_SPEECH" | "HARM_CATEGORY_DANGEROUS_CONTENT" | "HARM_CATEGORY_HARASSMENT" | "HARM_CATEGORY_SEXUALLY_EXPLICIT" | "HARM_CATEGORY_CIVIC_INTEGRITY" | (string & {});
readonly threshold: "HARM_BLOCK_THRESHOLD_UNSPECIFIED" | "BLOCK_LOW_AND_ABOVE" | "BLOCK_MEDIUM_AND_ABOVE" | "BLOCK_ONLY_HIGH" | "BLOCK_NONE" | "OFF" | (string & {});
}>;
readonly serviceTier?: "standard" | "flex" | "priority" | (string & {});
readonly thinkingConfig?: {
readonly thinkingBudget?: number;
readonly includeThoughts?: boolean;
readonly thinkingLevel?: "minimal" | "low" | "medium" | "high" | (string & {});
};
}
export type ProviderOptionsInput = ProviderOptions & {
readonly gemini?: OptionsInput;
};
declare const GeminiBody: Schema.Struct<{
cachedContent: Schema.optional<Schema.String>;
contents: Schema.$Array<Schema.Struct<{
readonly role: Schema.Literals<readonly ["user", "model"]>;
readonly parts: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly text: Schema.String;
readonly thought: Schema.optional<Schema.Boolean>;
readonly thoughtSignature: Schema.optional<Schema.String>;
}>, Schema.Struct<{
readonly inlineData: Schema.Struct<{
readonly mimeType: Schema.String;
readonly data: Schema.String;
}>;
}>, Schema.Struct<{
readonly functionCall: Schema.Struct<{
readonly id: Schema.optional<Schema.String>;
readonly name: Schema.String;
readonly args: Schema.Unknown;
}>;
readonly thoughtSignature: Schema.optional<Schema.String>;
}>, Schema.Struct<{
readonly functionResponse: Schema.Struct<{
readonly id: Schema.optional<Schema.String>;
readonly name: Schema.String;
readonly response: Schema.Unknown;
readonly parts: Schema.optional<Schema.$Array<Schema.Struct<{
readonly inlineData: Schema.Struct<{
readonly mimeType: Schema.String;
readonly data: Schema.String;
}>;
}>>>;
}>;
}>]>>;
}>>;
safetySettings: Schema.optional<Schema.$Array<Schema.Struct<{
readonly category: Schema.String;
readonly threshold: Schema.String;
}>>>;
serviceTier: Schema.optional<Schema.String>;
systemInstruction: Schema.optional<Schema.Struct<{
readonly parts: Schema.$Array<Schema.Struct<{
readonly text: Schema.String;
}>>;
}>>;
tools: Schema.optional<Schema.$Array<Schema.Struct<{
readonly functionDeclarations: Schema.$Array<Schema.Struct<{
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>>;
}>>>;
toolConfig: Schema.optional<Schema.Struct<{
readonly functionCallingConfig: Schema.Struct<{
readonly mode: Schema.Literals<readonly ["AUTO", "NONE", "ANY"]>;
readonly allowedFunctionNames: Schema.optional<Schema.$Array<Schema.String>>;
}>;
}>>;
generationConfig: Schema.optional<Schema.Struct<{
readonly maxOutputTokens: Schema.optional<Schema.Number>;
readonly temperature: Schema.optional<Schema.Number>;
readonly topP: Schema.optional<Schema.Number>;
readonly topK: Schema.optional<Schema.Number>;
readonly frequencyPenalty: Schema.optional<Schema.Number>;
readonly presencePenalty: Schema.optional<Schema.Number>;
readonly seed: Schema.optional<Schema.Number>;
readonly stopSequences: Schema.optional<Schema.$Array<Schema.String>>;
readonly thinkingConfig: Schema.optional<Schema.Struct<{
readonly thinkingBudget: Schema.optional<Schema.Number>;
readonly includeThoughts: Schema.optional<Schema.Boolean>;
readonly thinkingLevel: Schema.optional<Schema.String>;
}>>;
}>>;
}>;
export type GeminiBody = Schema.Schema.Type<typeof GeminiBody>;
/**
* The Gemini protocol — request body construction, body schema, and the
* streaming-event state machine. Used by Google AI Studio Gemini and (once
* registered) Vertex Gemini.
*/
export declare const protocol: Protocol<{
readonly contents: readonly {
readonly role: "user" | "model";
readonly parts: readonly ({
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
} | {
readonly text: string;
readonly thought?: boolean | undefined;
readonly thoughtSignature?: string | undefined;
} | {
readonly functionCall: {
readonly name: string;
readonly args: unknown;
readonly id?: string | undefined;
};
readonly thoughtSignature?: string | undefined;
} | {
readonly functionResponse: {
readonly name: string;
readonly response: unknown;
readonly id?: string | undefined;
readonly parts?: readonly {
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
}[] | undefined;
};
})[];
}[];
readonly tools?: readonly {
readonly functionDeclarations: readonly {
readonly description: string;
readonly name: string;
readonly parameters?: {
readonly [x: string]: unknown;
} | undefined;
}[];
}[] | undefined;
readonly toolConfig?: {
readonly functionCallingConfig: {
readonly mode: "AUTO" | "NONE" | "ANY";
readonly allowedFunctionNames?: readonly string[] | undefined;
};
} | undefined;
readonly cachedContent?: string | undefined;
readonly safetySettings?: readonly {
readonly category: string;
readonly threshold: string;
}[] | undefined;
readonly serviceTier?: string | undefined;
readonly systemInstruction?: {
readonly parts: readonly {
readonly text: string;
}[];
} | undefined;
readonly generationConfig?: {
readonly temperature?: number | undefined;
readonly topP?: number | undefined;
readonly topK?: number | undefined;
readonly frequencyPenalty?: number | undefined;
readonly presencePenalty?: number | undefined;
readonly seed?: number | undefined;
readonly stopSequences?: readonly string[] | undefined;
readonly thinkingConfig?: {
readonly thinkingBudget?: number | undefined;
readonly includeThoughts?: boolean | undefined;
readonly thinkingLevel?: string | undefined;
} | undefined;
readonly maxOutputTokens?: number | undefined;
} | undefined;
}, string, {
readonly candidates?: readonly {
readonly content?: {
readonly role: "user" | "model";
readonly parts: readonly ({
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
} | {
readonly text: string;
readonly thought?: boolean | undefined;
readonly thoughtSignature?: string | undefined;
} | {
readonly functionCall: {
readonly name: string;
readonly args: unknown;
readonly id?: string | undefined;
};
readonly thoughtSignature?: string | undefined;
} | {
readonly functionResponse: {
readonly name: string;
readonly response: unknown;
readonly id?: string | undefined;
readonly parts?: readonly {
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
}[] | undefined;
};
})[];
} | undefined;
readonly finishReason?: string | undefined;
}[] | undefined;
readonly usageMetadata?: {
readonly cachedContentTokenCount?: number | undefined;
readonly thoughtsTokenCount?: number | undefined;
readonly promptTokenCount?: number | undefined;
readonly candidatesTokenCount?: number | undefined;
readonly totalTokenCount?: number | undefined;
} | undefined;
}, {
hasToolCalls: boolean;
nextToolCallId: number;
lifecycle: Lifecycle.State;
}>;
export declare const route: Route<{
readonly contents: readonly {
readonly role: "user" | "model";
readonly parts: readonly ({
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
} | {
readonly text: string;
readonly thought?: boolean | undefined;
readonly thoughtSignature?: string | undefined;
} | {
readonly functionCall: {
readonly name: string;
readonly args: unknown;
readonly id?: string | undefined;
};
readonly thoughtSignature?: string | undefined;
} | {
readonly functionResponse: {
readonly name: string;
readonly response: unknown;
readonly id?: string | undefined;
readonly parts?: readonly {
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
}[] | undefined;
};
})[];
}[];
readonly tools?: readonly {
readonly functionDeclarations: readonly {
readonly description: string;
readonly name: string;
readonly parameters?: {
readonly [x: string]: unknown;
} | undefined;
}[];
}[] | undefined;
readonly toolConfig?: {
readonly functionCallingConfig: {
readonly mode: "AUTO" | "NONE" | "ANY";
readonly allowedFunctionNames?: readonly string[] | undefined;
};
} | undefined;
readonly cachedContent?: string | undefined;
readonly safetySettings?: readonly {
readonly category: string;
readonly threshold: string;
}[] | undefined;
readonly serviceTier?: string | undefined;
readonly systemInstruction?: {
readonly parts: readonly {
readonly text: string;
}[];
} | undefined;
readonly generationConfig?: {
readonly temperature?: number | undefined;
readonly topP?: number | undefined;
readonly topK?: number | undefined;
readonly frequencyPenalty?: number | undefined;
readonly presencePenalty?: number | undefined;
readonly seed?: number | undefined;
readonly stopSequences?: readonly string[] | undefined;
readonly thinkingConfig?: {
readonly thinkingBudget?: number | undefined;
readonly includeThoughts?: boolean | undefined;
readonly thinkingLevel?: string | undefined;
} | undefined;
readonly maxOutputTokens?: number | undefined;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>;
export * as Gemini from "./gemini.js";
import { Effect, Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { Route } from "../route/client.js";
import { Auth } from "../route/auth.js";
import { Endpoint } from "../route/endpoint.js";
import { Framing } from "../route/framing.js";
import { Protocol } from "../route/protocol.js";
import { LLMEvent, Usage, } from "../schema/index.js";
import { JsonObject, optionalArray, ProviderShared } from "./shared.js";
import { GeminiToolSchema } from "./utils/gemini-tool-schema.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolSchemaProjection } from "./utils/tool-schema.js";
const ADAPTER = "gemini";
const MEDIA_MIMES = new Set(ProviderShared.MEDIA_MIMES);
// Google documents this sentinel for replaying Gemini 3 function calls after their original signature was lost.
const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
// Gemini 3 rejects replayed function calls without a thought signature. Google's SDKs avoid that in normal chats by
// retaining complete model responses, but OpenCode reconstructs durable history and may encounter an unsigned call
// from an older or external session. Model IDs are open-ended, so unknown Gemini aliases inherit current behavior.
const requiresThoughtSignatureFallback = (modelID) => {
if (!/(^|\/)gemini-/i.test(modelID))
return false;
if (/(^|\/)gemini-(?:1|2)(?:[.-]|$)/i.test(modelID))
return false;
if (/(^|\/)gemini-pro(?:-vision)?$/i.test(modelID))
return false;
return !/(^|\/)gemini-robotics-er-1\.5(?:[.-]|$)/i.test(modelID);
};
// =============================================================================
// Request Body Schema
// =============================================================================
const GeminiTextPart = Schema.Struct({
text: Schema.String,
thought: Schema.optional(Schema.Boolean),
thoughtSignature: Schema.optional(Schema.String),
});
const GeminiInlineDataPart = Schema.Struct({
inlineData: Schema.Struct({
mimeType: Schema.String,
data: Schema.String,
}),
});
const GeminiFunctionCallPart = Schema.Struct({
functionCall: Schema.Struct({
id: Schema.optional(Schema.String),
name: Schema.String,
args: Schema.Unknown,
}),
thoughtSignature: Schema.optional(Schema.String),
});
const GeminiFunctionResponsePart = Schema.Struct({
functionResponse: Schema.Struct({
id: Schema.optional(Schema.String),
name: Schema.String,
response: Schema.Unknown,
parts: Schema.optional(Schema.Array(GeminiInlineDataPart)),
}),
});
const GeminiContentPart = Schema.Union([
GeminiTextPart,
GeminiInlineDataPart,
GeminiFunctionCallPart,
GeminiFunctionResponsePart,
]);
const GeminiContent = Schema.Struct({
role: Schema.Literals(["user", "model"]),
parts: Schema.Array(GeminiContentPart),
});
const GeminiSystemInstruction = Schema.Struct({
parts: Schema.Array(Schema.Struct({ text: Schema.String })),
});
const GeminiFunctionDeclaration = Schema.Struct({
name: Schema.String,
description: Schema.String,
parameters: Schema.optional(JsonObject),
});
const GeminiTool = Schema.Struct({
functionDeclarations: Schema.Array(GeminiFunctionDeclaration),
});
const GeminiToolConfig = Schema.Struct({
functionCallingConfig: Schema.Struct({
mode: Schema.Literals(["AUTO", "NONE", "ANY"]),
allowedFunctionNames: optionalArray(Schema.String),
}),
});
const GeminiThinkingConfig = Schema.Struct({
thinkingBudget: Schema.optional(Schema.Number),
includeThoughts: Schema.optional(Schema.Boolean),
thinkingLevel: Schema.optional(Schema.String),
});
const GeminiSafetySetting = Schema.Struct({
category: Schema.String,
threshold: Schema.String,
});
const GeminiGenerationConfig = Schema.Struct({
maxOutputTokens: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
topP: Schema.optional(Schema.Number),
topK: Schema.optional(Schema.Number),
frequencyPenalty: Schema.optional(Schema.Number),
presencePenalty: Schema.optional(Schema.Number),
seed: Schema.optional(Schema.Number),
stopSequences: optionalArray(Schema.String),
thinkingConfig: Schema.optional(GeminiThinkingConfig),
});
const GeminiBodyFields = {
cachedContent: Schema.optional(Schema.String),
contents: Schema.Array(GeminiContent),
safetySettings: optionalArray(GeminiSafetySetting),
serviceTier: Schema.optional(Schema.String),
systemInstruction: Schema.optional(GeminiSystemInstruction),
tools: optionalArray(GeminiTool),
toolConfig: Schema.optional(GeminiToolConfig),
generationConfig: Schema.optional(GeminiGenerationConfig),
};
const GeminiBody = Schema.Struct(GeminiBodyFields);
const GeminiUsage = Schema.Struct({
cachedContentTokenCount: Schema.optional(Schema.Number),
thoughtsTokenCount: Schema.optional(Schema.Number),
promptTokenCount: Schema.optional(Schema.Number),
candidatesTokenCount: Schema.optional(Schema.Number),
totalTokenCount: Schema.optional(Schema.Number),
});
const GeminiCandidate = Schema.Struct({
content: Schema.optional(GeminiContent),
finishReason: Schema.optional(Schema.String),
});
const GeminiEvent = Schema.Struct({
candidates: optionalArray(GeminiCandidate),
usageMetadata: Schema.optional(GeminiUsage),
});
// =============================================================================
// Tool Schema Conversion
// =============================================================================
// Tool-schema conversion has two distinct concerns:
//
// 1. Sanitize — fix common authoring mistakes Gemini rejects: integer/number
// enums (must be strings), `required` entries that don't match a property,
// untyped arrays (`items` must be present), and `properties`/`required`
// keys on non-object scalars. Mirrors OpenCode's historical Gemini rules.
//
// 2. Project — lossy mapping from JSON Schema to Gemini's schema dialect:
// drop empty root parameter schemas while preserving nested empty objects,
// expand type arrays into `anyOf`, derive `nullable: true` from null members,
// coerce `const` to `[const]` enum, recurse properties/items, and propagate
// only an allowlisted set of keys (description, required, format, type,
// nullable, enum, properties, items, allOf, anyOf, oneOf, minLength).
// Anything outside the allowlist (e.g. `additionalProperties`, `$ref`) is
// silently dropped.
//
// Sanitize runs first, then project. The implementation lives in
// `utils/gemini-tool-schema` so this protocol keeps the same shape as the other
// provider protocols.
// =============================================================================
// Request Lowering
// =============================================================================
const lowerTool = (tool, inputSchema) => ({
name: tool.name,
description: tool.description,
parameters: GeminiToolSchema.convert(inputSchema),
});
const lowerToolConfig = (toolChoice) => ProviderShared.matchToolChoice("Gemini", toolChoice, {
auto: () => ({ functionCallingConfig: { mode: "AUTO" } }),
none: () => ({ functionCallingConfig: { mode: "NONE" } }),
required: () => ({ functionCallingConfig: { mode: "ANY" } }),
tool: (name) => ({ functionCallingConfig: { mode: "ANY", allowedFunctionNames: [name] } }),
});
const lowerUserPart = Effect.fn("Gemini.lowerUserPart")(function* (part) {
if (part.type === "text")
return { text: part.text };
const media = yield* ProviderShared.validateMedia("Gemini", part, MEDIA_MIMES);
return { inlineData: { mimeType: media.mime, data: media.base64 } };
});
const googleMetadata = (metadata) => ({ google: metadata });
const thoughtSignature = (providerMetadata) => {
const google = providerMetadata?.google;
return ProviderShared.isRecord(google) && typeof google.thoughtSignature === "string"
? google.thoughtSignature
: undefined;
};
const functionCallId = (providerMetadata) => {
const google = providerMetadata?.google;
return ProviderShared.isRecord(google) && typeof google.functionCallId === "string"
? google.functionCallId
: undefined;
};
const lowerToolCall = (part) => ({
functionCall: { id: functionCallId(part.providerMetadata), name: part.name, args: part.input },
thoughtSignature: thoughtSignature(part.providerMetadata),
});
const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
const contents = [];
for (const message of request.messages) {
if (message.role === "system") {
const part = yield* ProviderShared.wrappedSystemUpdate("Gemini", message);
const previous = contents.at(-1);
if (previous?.role === "user")
contents[contents.length - 1] = { role: "user", parts: [...previous.parts, { text: part.text }] };
else
contents.push({ role: "user", parts: [{ text: part.text }] });
continue;
}
if (message.role === "user") {
const parts = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["text", "media"]))
return yield* ProviderShared.unsupportedContent("Gemini", "user", ["text", "media"]);
parts.push(yield* lowerUserPart(part));
}
contents.push({ role: "user", parts });
continue;
}
if (message.role === "assistant") {
const parts = [];
// Parallel Gemini 3 calls may carry one signature on the first call; unsigned sibling calls are valid.
let hasSignedToolCall = false;
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
return yield* ProviderShared.unsupportedContent("Gemini", "assistant", ["text", "reasoning", "tool-call"]);
if (part.type === "text") {
parts.push({ text: part.text });
continue;
}
if (part.type === "reasoning") {
parts.push({ text: part.text, thought: true, thoughtSignature: thoughtSignature(part.providerMetadata) });
continue;
}
if (part.type === "tool-call") {
const lowered = lowerToolCall(part);
const signature = lowered.thoughtSignature;
parts.push({
...lowered,
thoughtSignature: signature ??
(requiresThoughtSignatureFallback(request.model.id) && !hasSignedToolCall
? SKIP_THOUGHT_SIGNATURE_VALIDATOR
: undefined),
});
if (signature !== undefined)
hasSignedToolCall = true;
continue;
}
}
contents.push({ role: "model", parts });
continue;
}
const parts = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["tool-result"]))
return yield* ProviderShared.unsupportedContent("Gemini", "tool", ["tool-result"]);
if (part.result.type !== "content") {
parts.push({
functionResponse: {
id: functionCallId(part.providerMetadata),
name: part.name,
response: {
name: part.name,
content: ProviderShared.toolResultText(part),
},
},
});
continue;
}
const content = part.result.value;
const text = content.filter((item) => item.type === "text").map((item) => item.text);
const media = [];
for (const item of content) {
if (item.type === "text")
continue;
const value = yield* ProviderShared.validateToolFile("Gemini", item, MEDIA_MIMES);
media.push({ inlineData: { mimeType: value.mime, data: value.base64 } });
}
parts.push({
functionResponse: {
id: functionCallId(part.providerMetadata),
name: part.name,
response: {
name: part.name,
content: text.join("\n"),
},
parts: media.length > 0 ? media : undefined,
},
});
}
contents.push({ role: "user", parts });
}
return contents;
});
const resolveOptions = (request) => {
const input = request.providerOptions?.gemini;
const value = input?.thinkingConfig;
const thinkingConfig = {
thinkingBudget: ProviderShared.isRecord(value) && typeof value.thinkingBudget === "number" ? value.thinkingBudget : undefined,
includeThoughts: ProviderShared.isRecord(value) && typeof value.includeThoughts === "boolean"
? value.includeThoughts
: ProviderShared.isRecord(value)
? true
: undefined,
thinkingLevel: ProviderShared.isRecord(value) && typeof value.thinkingLevel === "string" ? value.thinkingLevel : undefined,
};
return {
cachedContent: typeof input?.cachedContent === "string" ? input.cachedContent : undefined,
safetySettings: mapSafetySettings(input?.safetySettings),
serviceTier: typeof input?.serviceTier === "string" ? input.serviceTier : undefined,
thinkingConfig: Object.values(thinkingConfig).some((item) => item !== undefined) ? thinkingConfig : undefined,
};
};
function mapSafetySettings(value) {
if (!Array.isArray(value))
return undefined;
const settings = value.flatMap((item) => ProviderShared.isRecord(item) && typeof item.category === "string" && typeof item.threshold === "string"
? [{ category: item.category, threshold: item.threshold }]
: []);
return settings;
}
const fromRequest = Effect.fn("Gemini.fromRequest")(function* (request) {
const hasTools = request.tools.length > 0;
const generation = request.generation;
const options = resolveOptions(request);
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
const generationConfig = {
maxOutputTokens: generation?.maxTokens,
temperature: generation?.temperature,
topP: generation?.topP,
topK: generation?.topK,
frequencyPenalty: generation?.frequencyPenalty,
presencePenalty: generation?.presencePenalty,
seed: generation?.seed,
stopSequences: generation?.stop,
thinkingConfig: options.thinkingConfig,
};
return {
cachedContent: options.cachedContent,
contents: yield* lowerMessages(request),
safetySettings: options.safetySettings,
serviceTier: options.serviceTier,
systemInstruction: request.system.length === 0 ? undefined : { parts: [{ text: ProviderShared.joinText(request.system) }] },
tools: hasTools
? [
{
functionDeclarations: request.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility))),
},
]
: undefined,
toolConfig: hasTools && request.toolChoice ? yield* lowerToolConfig(request.toolChoice) : undefined,
generationConfig: Object.values(generationConfig).some((value) => value !== undefined)
? generationConfig
: undefined,
};
});
// =============================================================================
// Stream Parsing
// =============================================================================
// Gemini reports `promptTokenCount` (inclusive total) with a
// `cachedContentTokenCount` subset. `candidatesTokenCount` is *exclusive*
// of `thoughtsTokenCount` — visible-only, not a total — so we sum the two
// to produce the inclusive `outputTokens` the rest of the contract expects.
const mapUsage = (usage) => {
if (!usage)
return undefined;
const cached = usage.cachedContentTokenCount;
const nonCached = ProviderShared.subtractTokens(usage.promptTokenCount, cached);
// `candidatesTokenCount` is visible-only; sum with thoughts to produce the
// inclusive `outputTokens` the contract expects. Only compute the total
// when the visible component is reported — otherwise we'd fabricate an
// inclusive number from a partial breakdown.
const outputTokens = usage.candidatesTokenCount !== undefined ? usage.candidatesTokenCount + (usage.thoughtsTokenCount ?? 0) : undefined;
return new Usage({
inputTokens: usage.promptTokenCount,
outputTokens,
nonCachedInputTokens: nonCached,
cacheReadInputTokens: cached,
reasoningTokens: usage.thoughtsTokenCount,
totalTokens: ProviderShared.totalTokens(usage.promptTokenCount, outputTokens, usage.totalTokenCount),
providerMetadata: { google: usage },
});
};
const mapFinishReason = (finishReason, hasToolCalls) => {
if (finishReason === undefined)
return hasToolCalls ? "tool-calls" : "unknown";
if (finishReason === "STOP")
return hasToolCalls ? "tool-calls" : "stop";
if (finishReason === "MAX_TOKENS")
return "length";
if (finishReason === "IMAGE_SAFETY" ||
finishReason === "RECITATION" ||
finishReason === "SAFETY" ||
finishReason === "BLOCKLIST" ||
finishReason === "PROHIBITED_CONTENT" ||
finishReason === "SPII" ||
finishReason === "MODEL_ARMOR" ||
finishReason === "IMAGE_PROHIBITED_CONTENT" ||
finishReason === "IMAGE_RECITATION" ||
finishReason === "LANGUAGE")
return "content-filter";
if (finishReason === "MALFORMED_FUNCTION_CALL" ||
finishReason === "UNEXPECTED_TOOL_CALL" ||
finishReason === "NO_IMAGE" ||
finishReason === "TOO_MANY_TOOL_CALLS" ||
finishReason === "MISSING_THOUGHT_SIGNATURE" ||
finishReason === "MALFORMED_RESPONSE")
return "error";
return "unknown";
};
const finish = (state) => state.finishReason || state.usage
? (() => {
const events = [];
const lifecycle = state.reasoningSignature
? Lifecycle.reasoningEnd(state.lifecycle, events, "reasoning-0", googleMetadata({ thoughtSignature: state.reasoningSignature }))
: state.lifecycle;
Lifecycle.finish(lifecycle, events, {
reason: {
normalized: mapFinishReason(state.finishReason, state.hasToolCalls),
raw: state.finishReason,
},
usage: state.usage,
});
return events;
})()
: [];
const step = (state, event) => {
const nextState = {
...state,
usage: event.usageMetadata ? (mapUsage(event.usageMetadata) ?? state.usage) : state.usage,
};
const candidate = event.candidates?.[0];
if (!candidate?.content)
return Effect.succeed([
{ ...nextState, finishReason: candidate?.finishReason ?? nextState.finishReason },
[],
]);
const events = [];
let hasToolCalls = nextState.hasToolCalls;
let lifecycle = nextState.lifecycle;
let nextToolCallId = nextState.nextToolCallId;
let reasoningSignature = nextState.reasoningSignature;
for (const part of candidate.content.parts) {
if ("thoughtSignature" in part && part.thoughtSignature && "thought" in part && part.thought)
reasoningSignature = part.thoughtSignature;
if ("text" in part && part.text.length > 0) {
if (part.thought) {
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", part.text, part.thoughtSignature ? googleMetadata({ thoughtSignature: part.thoughtSignature }) : undefined);
continue;
}
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined);
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", part.text);
continue;
}
if ("functionCall" in part) {
const input = part.functionCall.args;
const id = `tool_${nextToolCallId++}`;
const metadata = {
...(part.functionCall.id === undefined ? {} : { functionCallId: part.functionCall.id }),
...(part.thoughtSignature === undefined ? {} : { thoughtSignature: part.thoughtSignature }),
};
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningSignature ? googleMetadata({ thoughtSignature: reasoningSignature }) : undefined);
lifecycle = Lifecycle.stepStart(lifecycle, events);
events.push(LLMEvent.toolCall({
id,
name: part.functionCall.name,
input,
providerMetadata: Object.keys(metadata).length > 0 ? googleMetadata(metadata) : undefined,
}));
hasToolCalls = true;
}
}
return Effect.succeed([
{
...nextState,
hasToolCalls,
lifecycle,
nextToolCallId,
reasoningSignature,
finishReason: candidate.finishReason ?? nextState.finishReason,
},
events,
]);
};
// =============================================================================
// Protocol And Gemini Route
// =============================================================================
/**
* The Gemini protocol — request body construction, body schema, and the
* streaming-event state machine. Used by Google AI Studio Gemini and (once
* registered) Vertex Gemini.
*/
export const protocol = Protocol.make({
id: ADAPTER,
body: {
schema: GeminiBody,
from: fromRequest,
},
stream: {
event: Protocol.jsonEvent(GeminiEvent),
initial: () => ({ hasToolCalls: false, nextToolCallId: 0, lifecycle: Lifecycle.initial() }),
step,
onHalt: finish,
},
});
export const route = Route.make({
id: ADAPTER,
provider: "google",
providerMetadataKey: "google",
protocol,
// Gemini's path embeds the model id and pins SSE framing at the URL level.
endpoint: Endpoint.path(({ request }) => `/models/${request.model.id}:streamGenerateContent?alt=sse`, {
baseURL: DEFAULT_BASE_URL,
}),
auth: Auth.none,
framing: Framing.sse,
});
export * as Gemini from "./gemini.js";
import { ImageModel } from "../image.js";
import { type Definition as AuthDefinition } from "../route/auth.js";
import { type HttpOptions } from "../schema/index.js";
export declare const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
export type GoogleImageString<Known extends string> = Known | (string & {});
export type GoogleImageOptions = {
readonly aspectRatio?: GoogleImageString<"1:1" | "2:3" | "3:2" | "3:4" | "4:3" | "4:5" | "5:4" | "9:16" | "16:9" | "21:9">;
readonly imageSize?: GoogleImageString<"1K" | "2K" | "4K">;
readonly seed?: number;
readonly thinkingLevel?: GoogleImageString<"MINIMAL" | "LOW" | "MEDIUM" | "HIGH">;
readonly includeThoughts?: boolean;
} & Record<string, unknown>;
export type GoogleImageBody = Record<string, unknown> & {
readonly contents: ReadonlyArray<{
readonly role: "user";
readonly parts: ReadonlyArray<Record<string, unknown>>;
}>;
readonly generationConfig: Record<string, unknown>;
};
export interface ModelInput {
readonly id: string;
readonly auth: AuthDefinition;
readonly baseURL?: string;
readonly headers?: Record<string, string>;
readonly http?: HttpOptions;
}
export declare const model: (input: ModelInput) => ImageModel<GoogleImageOptions>;
export declare const GoogleImages: {
readonly model: (input: ModelInput) => ImageModel<GoogleImageOptions>;
};
import { Effect, Encoding, Schema } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { GeneratedImage, ImageModel, ImageResponse, } from "../image.js";
import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, Usage, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { ProviderShared } from "./shared.js";
import { ImageInputs } from "./utils/image-input.js";
const ADAPTER = "google-images";
export const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
const GoogleUsage = Schema.StructWithRest(Schema.Struct({
cachedContentTokenCount: Schema.optional(Schema.Number),
thoughtsTokenCount: Schema.optional(Schema.Number),
promptTokenCount: Schema.optional(Schema.Number),
candidatesTokenCount: Schema.optional(Schema.Number),
totalTokenCount: Schema.optional(Schema.Number),
promptTokensDetails: Schema.optional(Schema.Unknown),
candidatesTokensDetails: Schema.optional(Schema.Unknown),
}), [Schema.Record(Schema.String, Schema.Unknown)]);
const GoogleImageResponse = Schema.Struct({
candidates: Schema.optional(Schema.Array(Schema.Struct({
index: Schema.optional(Schema.Number),
content: Schema.optional(Schema.Struct({
parts: Schema.Array(Schema.Struct({
text: Schema.optional(Schema.String),
thought: Schema.optional(Schema.Boolean),
thoughtSignature: Schema.optional(Schema.String),
inlineData: Schema.optional(Schema.Struct({
mimeType: Schema.String,
data: Schema.String,
})),
})),
})),
finishReason: Schema.optional(Schema.String),
finishMessage: Schema.optional(Schema.String),
safetyRatings: Schema.optional(Schema.Unknown),
citationMetadata: Schema.optional(Schema.Unknown),
groundingMetadata: Schema.optional(Schema.Unknown),
}))),
usageMetadata: Schema.optional(GoogleUsage),
modelVersion: Schema.optional(Schema.String),
responseId: Schema.optional(Schema.String),
promptFeedback: Schema.optional(Schema.Unknown),
});
const nativeOptions = (options) => {
const { aspectRatio, imageSize, seed, thinkingLevel, includeThoughts, ...native } = options ?? {};
const image = {
aspectRatio,
imageSize,
};
const thinkingConfig = {
thinkingLevel,
includeThoughts,
};
return (mergeJsonRecords({
responseModalities: ["IMAGE"],
imageConfig: Object.values(image).some((value) => value !== undefined) ? image : undefined,
seed,
thinkingConfig: Object.values(thinkingConfig).some((value) => value !== undefined) ? thinkingConfig : undefined,
}, native) ?? { responseModalities: ["IMAGE"] });
};
const invalidOutput = (message, providerMetadata) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER, providerMetadata }),
});
const applyQuery = (url, query) => {
if (!query)
return url;
const next = new URL(url);
Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
return next.toString();
};
export const model = (input) => {
const route = {
id: ADAPTER,
generate: Effect.fn("GoogleImages.generate")(function* (request, execute) {
const imageParts = yield* Effect.forEach(request.images ?? [], googleImagePart);
const http = mergeHttpOptions(request.model.http, request.http);
const requestBody = mergeJsonRecords({
contents: [{ role: "user", parts: [{ text: request.prompt }, ...imageParts] }],
generationConfig: nativeOptions(request.options),
}, http?.body);
const text = ProviderShared.encodeJson(requestBody);
const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}/models/${request.model.id}:generateContent`, http?.query);
const headers = yield* Auth.toEffect(input.auth)({
request,
method: "POST",
url,
body: text,
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
});
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the Google Images response")));
const decoded = yield* Schema.decodeUnknownEffect(GoogleImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("Google Images returned an invalid response")));
const candidates = decoded.candidates ?? [];
const candidateMetadata = candidates.map((candidate, candidateIndex) => ({
index: candidate.index ?? candidateIndex,
finishReason: candidate.finishReason,
finishMessage: candidate.finishMessage,
safetyRatings: candidate.safetyRatings,
citationMetadata: candidate.citationMetadata,
groundingMetadata: candidate.groundingMetadata,
parts: (candidate.content?.parts ?? []).map((part) => part.inlineData === undefined
? {
type: "text",
text: part.text,
thought: part.thought,
thoughtSignature: part.thoughtSignature,
}
: {
type: "inlineData",
mediaType: part.inlineData.mimeType,
thought: part.thought,
thoughtSignature: part.thoughtSignature,
}),
}));
const encoded = candidates.flatMap((candidate, candidateIndex) => (candidate.content?.parts ?? []).flatMap((part, partIndex) => part.inlineData === undefined || part.thought === true
? []
: [{ candidate, candidateIndex, partIndex, inlineData: part.inlineData }]));
const images = yield* Effect.forEach(encoded, (item) => Effect.fromResult(Encoding.decodeBase64(item.inlineData.data)).pipe(Effect.mapError(() => invalidOutput(`Google Images candidate ${item.candidateIndex} part ${item.partIndex} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
mediaType: item.inlineData.mimeType,
data,
providerMetadata: {
google: {
candidateIndex: item.candidate.index ?? item.candidateIndex,
partIndex: item.partIndex,
finishReason: item.candidate.finishReason,
safetyRatings: item.candidate.safetyRatings,
citationMetadata: item.candidate.citationMetadata,
groundingMetadata: item.candidate.groundingMetadata,
thoughtSignature: item.candidate.content?.parts[item.partIndex]?.thoughtSignature,
},
},
}))));
if (images.length === 0) {
const finishReasons = candidates.flatMap((candidate) => candidate.finishReason === undefined ? [] : [candidate.finishReason]);
return yield* invalidOutput(`Google Images returned no final images${finishReasons.length === 0 ? "" : ` (finish reasons: ${finishReasons.join(", ")})`}; inspect reason.providerMetadata.google for prompt feedback and candidate details`, {
google: {
promptFeedback: decoded.promptFeedback,
candidates: candidateMetadata,
},
});
}
const usage = decoded.usageMetadata;
const outputTokens = usage?.candidatesTokenCount === undefined
? undefined
: usage.candidatesTokenCount + (usage.thoughtsTokenCount ?? 0);
return new ImageResponse({
images,
usage: usage === undefined
? undefined
: new Usage({
inputTokens: usage.promptTokenCount,
outputTokens,
nonCachedInputTokens: ProviderShared.subtractTokens(usage.promptTokenCount, usage.cachedContentTokenCount),
cacheReadInputTokens: usage.cachedContentTokenCount,
reasoningTokens: usage.thoughtsTokenCount,
totalTokens: ProviderShared.totalTokens(usage.promptTokenCount, outputTokens, usage.totalTokenCount),
providerMetadata: { google: usage },
}),
providerMetadata: {
google: {
modelVersion: decoded.modelVersion,
responseId: decoded.responseId,
promptFeedback: decoded.promptFeedback,
candidates: candidateMetadata,
},
},
});
}),
};
return ImageModel.make({ id: input.id, provider: "google", route, http: input.http });
};
const googleImagePart = (image) => {
if (image.type === "bytes")
return Effect.succeed({ inlineData: { mimeType: image.mediaType, data: Encoding.encodeBase64(image.data) } });
if (image.type === "file-uri")
return Effect.succeed({ fileData: { mimeType: image.mediaType, fileUri: image.uri } });
if (image.type === "url")
return ImageInputs.decodeDataUrl(image.url, ADAPTER).pipe(Effect.flatMap((decoded) => {
if (decoded === undefined)
return Effect.fail(ImageInputs.invalid(ADAPTER, "Google generateContent does not fetch public image URLs; use bytes, a data URL, or a Gemini file URI"));
return Effect.succeed({
inlineData: { mimeType: decoded.mediaType, data: Encoding.encodeBase64(decoded.data) },
});
}));
return Effect.fail(ImageInputs.invalid(ADAPTER, "Google generateContent requires Gemini file URIs rather than provider file IDs"));
};
export const GoogleImages = {
model,
};
export * as AnthropicMessages from "./anthropic-messages.js";
export * as BedrockConverse from "./bedrock-converse.js";
export * as Gemini from "./gemini.js";
export * as OpenAIChat from "./openai-chat.js";
export * as OpenAIImages from "./openai-images.js";
export * as OpenAICompatibleChat from "./openai-compatible-chat.js";
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
export * as OpenAIResponses from "./openai-responses.js";
export * as OpenResponses from "./open-responses.js";
export * as OpenResponsesChannel from "./open-responses-channel.js";
export * as AnthropicMessages from "./anthropic-messages.js";
export * as BedrockConverse from "./bedrock-converse.js";
export * as Gemini from "./gemini.js";
export * as OpenAIChat from "./openai-chat.js";
export * as OpenAIImages from "./openai-images.js";
export * as OpenAICompatibleChat from "./openai-compatible-chat.js";
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
export * as OpenAIResponses from "./openai-responses.js";
export * as OpenResponses from "./open-responses.js";
export * as OpenResponsesChannel from "./open-responses-channel.js";
import { Headers } from "effect/unstable/http";
import { HttpTransport, type Transport, type WebSocketChannelDriver } from "../route/transport/index.js";
export interface Options {
readonly id: string;
readonly name: string;
readonly rotateAfterMs?: number;
readonly headers?: (headers: Headers.Headers) => Headers.Headers;
readonly driver?: (input: {
readonly request: Readonly<Record<string, unknown>>;
readonly message: string;
readonly base: WebSocketChannelDriver;
}) => WebSocketChannelDriver;
}
export interface Prepared {
readonly http: HttpTransport.HttpPrepared<string>;
readonly channel?: {
readonly url: string;
readonly headers: Headers.Headers;
readonly rotateAfterMs?: number;
readonly driver: WebSocketChannelDriver;
};
}
export declare const transport: <Body>(options: Options) => Transport<Body, Prepared, string>;
export declare const OpenResponsesChannel: {
readonly transport: <Body>(options: Options) => Transport<Body, Prepared, string>;
};
import { Effect, Schema, Stream } from "effect";
import { Headers } from "effect/unstable/http";
import { Framing } from "../route/framing.js";
import { HttpTransport, WebSocketTransport, } from "../route/transport/index.js";
import * as ProviderShared from "./shared.js";
import { OpenResponses } from "./open-responses.js";
const WebSocketResponseCreate = Schema.StructWithRest(Schema.Struct({ type: Schema.tag("response.create") }), [
Schema.Record(Schema.String, Schema.Unknown),
]);
const decodeMessage = ProviderShared.validateWith(Schema.decodeUnknownEffect(WebSocketResponseCreate));
const encodeMessage = Schema.encodeSync(Schema.fromJsonString(WebSocketResponseCreate));
const decodeEvent = Schema.decodeUnknownEffect(OpenResponses.protocol.stream.event);
const message = (body) => Effect.gen(function* () {
if (!ProviderShared.isRecord(body))
return yield* ProviderShared.invalidRequest("Open Responses WebSocket body must be a JSON object");
const { stream: _stream, stream_options: _streamOptions, background: _background, ...request } = body;
const decoded = yield* decodeMessage({ ...request, type: "response.create" });
return { request: decoded, message: encodeMessage(decoded) };
});
const driver = (options, body) => {
let responseID;
let terminal = false;
return {
create: () => Effect.sync(() => {
responseID = undefined;
terminal = false;
return { message: body, mode: "full" };
}),
observe: (_create, frame) => Effect.gen(function* () {
const event = yield* decodeEvent(frame).pipe(Effect.mapError(() => ProviderShared.eventError(options.id, `Invalid ${options.name} WebSocket event`, frame)));
if (terminal)
return yield* ProviderShared.eventError(options.id, `${options.name} emitted ${event.type} after a terminal event`, frame);
if (event.type === "error") {
terminal = true;
yield* OpenResponses.decodeKnownErrorEvent(event).pipe(Effect.mapError(() => ProviderShared.eventError(options.id, `${options.name} returned a malformed error event`, frame)));
return {
type: "provider-failure",
error: OpenResponses.providerFailure(options.id, event, `${options.name} stream error`),
};
}
if (event.type === "response.failed") {
terminal = true;
if (responseID && event.response?.id && event.response.id !== responseID)
return yield* ProviderShared.eventError(options.id, `${options.name} response ID changed during execution`, frame);
return {
type: "provider-failure",
error: OpenResponses.providerFailure(options.id, event, `${options.name} response failed`),
};
}
if (event.type === "response.created") {
const created = event.response?.id;
if (responseID)
return yield* ProviderShared.eventError(options.id, `${options.name} emitted duplicate response.created`, frame);
if (!created)
return yield* ProviderShared.eventError(options.id, `${options.name} response.created is missing response.id`, frame);
responseID = created;
return { type: "frame", frame };
}
if (!responseID)
return yield* ProviderShared.eventError(options.id, `${options.name} emitted ${event.type} before response.created`, frame);
if (event.response?.id && event.response.id !== responseID)
return yield* ProviderShared.eventError(options.id, `${options.name} response ID changed during execution`, frame);
if (event.type === "response.completed") {
terminal = true;
return { type: "completed", frame };
}
if (event.type === "response.incomplete") {
terminal = true;
return { type: "incomplete", frame };
}
return { type: "frame", frame };
}),
};
};
export const transport = (options) => {
const http = HttpTransport.sseJson.with();
return {
id: http.id,
prepare: (input) => Effect.gen(function* () {
const parts = yield* HttpTransport.jsonRequestParts(input);
const headers = Headers.remove(options.headers?.(parts.headers) ?? parts.headers, "content-length");
const channel = input.webSocket
? yield* Effect.gen(function* () {
const create = yield* message(parts.jsonBody);
const base = driver(options, create.message);
return {
url: yield* WebSocketTransport.toWebSocketUrl(parts.url),
headers,
rotateAfterMs: options.rotateAfterMs,
driver: options.driver?.({ request: create.request, message: create.message, base }) ?? base,
};
})
: undefined;
return {
http: {
request: ProviderShared.jsonPost({ url: parts.url, body: parts.bodyText, headers: parts.headers }),
framing: Framing.sse,
middleware: input.middleware,
},
channel,
};
}),
execute: (prepared, request, runtime, executeOptions) => {
if (!executeOptions?.webSocket || !prepared.channel)
return http.execute(prepared.http, request, runtime);
const exchange = {
id: request.id ?? "request",
connect: {
url: prepared.channel.url,
headers: prepared.channel.headers,
rotateAfterMs: prepared.channel.rotateAfterMs,
},
fallback: () => Stream.unwrap(http.execute(prepared.http, request, runtime).pipe(Effect.map((execution) => execution.frames))),
driver: prepared.channel.driver,
};
return executeOptions.webSocket.execute(exchange);
},
};
};
export const OpenResponsesChannel = { transport };
import { Effect, Schema } from "effect";
import { HttpTransport } from "../route/transport/index.js";
import { Protocol } from "../route/protocol.js";
import { AIError, LLMEvent, Usage, type LLMRequest, type MediaPart, type ProviderMetadata, type ToolDefinition } from "../schema/index.js";
import { ProviderShared } from "./shared.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolStream } from "./utils/tool-stream.js";
export declare const PATH = "/responses";
declare const MediaInput: Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>;
export type MediaInput = Schema.Schema.Type<typeof MediaInput>;
export declare const MessagePhase: Schema.Literals<readonly ["commentary", "final_answer"]>;
type MessagePhase = Schema.Schema.Type<typeof MessagePhase>;
export declare const InputItem: Schema.Union<readonly [Schema.Struct<{
readonly role: Schema.tag<"system">;
readonly content: Schema.String;
}>, Schema.Struct<{
readonly role: Schema.tag<"user">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>]>>;
}>, Schema.Struct<{
readonly role: Schema.tag<"assistant">;
readonly content: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"output_text">;
readonly text: Schema.String;
}>>;
readonly phase: Schema.optionalKey<Schema.Literals<readonly ["commentary", "final_answer"]>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"reasoning">;
readonly id: Schema.optionalKey<Schema.String>;
readonly summary: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"summary_text">;
readonly text: Schema.String;
}>>;
readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"item_reference">;
readonly id: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call">;
readonly call_id: Schema.String;
readonly name: Schema.String;
readonly arguments: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call_output">;
readonly call_id: Schema.String;
readonly output: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>>]>;
}>]>;
type OpenResponsesInputItem = Schema.Schema.Type<typeof InputItem>;
type LoweredInputItem = OpenResponsesInputItem | {
readonly role: "assistant";
readonly content: ReadonlyArray<{
readonly type: "output_text";
readonly text: string;
}>;
readonly phase?: MessagePhase | null;
};
export declare const Tool: Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.$Record<Schema.String, Schema.Unknown>;
readonly strict: Schema.optional<Schema.Boolean>;
}>;
export declare const ToolChoice: Schema.Union<readonly [Schema.Literals<readonly ["auto", "none", "required"]>, Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
}>]>;
export declare const coreFields: {
model: Schema.String;
input: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly role: Schema.tag<"system">;
readonly content: Schema.String;
}>, Schema.Struct<{
readonly role: Schema.tag<"user">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>]>>;
}>, Schema.Struct<{
readonly role: Schema.tag<"assistant">;
readonly content: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"output_text">;
readonly text: Schema.String;
}>>;
readonly phase: Schema.optionalKey<Schema.Literals<readonly ["commentary", "final_answer"]>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"reasoning">;
readonly id: Schema.optionalKey<Schema.String>;
readonly summary: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"summary_text">;
readonly text: Schema.String;
}>>;
readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"item_reference">;
readonly id: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call">;
readonly call_id: Schema.String;
readonly name: Schema.String;
readonly arguments: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call_output">;
readonly call_id: Schema.String;
readonly output: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>>]>;
}>]>>;
instructions: Schema.optional<Schema.String>;
tools: Schema.optional<Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.$Record<Schema.String, Schema.Unknown>;
readonly strict: Schema.optional<Schema.Boolean>;
}>>>;
tool_choice: Schema.optional<Schema.Union<readonly [Schema.Literals<readonly ["auto", "none", "required"]>, Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
}>]>>;
store: Schema.optional<Schema.Boolean>;
service_tier: Schema.optional<Schema.Literals<readonly ["auto", "default", "flex", "priority"]>>;
prompt_cache_key: Schema.optional<Schema.String>;
include: Schema.optional<Schema.$Array<Schema.Literals<readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"]>>>;
reasoning: Schema.optional<Schema.Struct<{
readonly effort: Schema.optional<Schema.String>;
readonly summary: Schema.optional<Schema.Literals<readonly ["auto", "concise", "detailed"]>>;
}>>;
text: Schema.optional<Schema.Struct<{
readonly verbosity: Schema.optional<Schema.Literals<readonly ["low", "medium", "high"]>>;
}>>;
max_output_tokens: Schema.optional<Schema.Number>;
temperature: Schema.optional<Schema.Number>;
top_p: Schema.optional<Schema.Number>;
};
declare const OpenResponsesBody: Schema.Struct<{
readonly stream: Schema.Literal<true>;
readonly model: Schema.String;
readonly input: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly role: Schema.tag<"system">;
readonly content: Schema.String;
}>, Schema.Struct<{
readonly role: Schema.tag<"user">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>]>>;
}>, Schema.Struct<{
readonly role: Schema.tag<"assistant">;
readonly content: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"output_text">;
readonly text: Schema.String;
}>>;
readonly phase: Schema.optionalKey<Schema.Literals<readonly ["commentary", "final_answer"]>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"reasoning">;
readonly id: Schema.optionalKey<Schema.String>;
readonly summary: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"summary_text">;
readonly text: Schema.String;
}>>;
readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"item_reference">;
readonly id: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call">;
readonly call_id: Schema.String;
readonly name: Schema.String;
readonly arguments: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call_output">;
readonly call_id: Schema.String;
readonly output: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>>]>;
}>]>>;
readonly instructions: Schema.optional<Schema.String>;
readonly tools: Schema.optional<Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.$Record<Schema.String, Schema.Unknown>;
readonly strict: Schema.optional<Schema.Boolean>;
}>>>;
readonly tool_choice: Schema.optional<Schema.Union<readonly [Schema.Literals<readonly ["auto", "none", "required"]>, Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
}>]>>;
readonly store: Schema.optional<Schema.Boolean>;
readonly service_tier: Schema.optional<Schema.Literals<readonly ["auto", "default", "flex", "priority"]>>;
readonly prompt_cache_key: Schema.optional<Schema.String>;
readonly include: Schema.optional<Schema.$Array<Schema.Literals<readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"]>>>;
readonly reasoning: Schema.optional<Schema.Struct<{
readonly effort: Schema.optional<Schema.String>;
readonly summary: Schema.optional<Schema.Literals<readonly ["auto", "concise", "detailed"]>>;
}>>;
readonly text: Schema.optional<Schema.Struct<{
readonly verbosity: Schema.optional<Schema.Literals<readonly ["low", "medium", "high"]>>;
}>>;
readonly max_output_tokens: Schema.optional<Schema.Number>;
readonly temperature: Schema.optional<Schema.Number>;
readonly top_p: Schema.optional<Schema.Number>;
}>;
export type OpenResponsesBody = Schema.Schema.Type<typeof OpenResponsesBody>;
export declare const StreamItem: Schema.StructWithRest<Schema.Struct<{
readonly type: Schema.String;
readonly id: Schema.optional<Schema.String>;
readonly call_id: Schema.optional<Schema.String>;
readonly name: Schema.optional<Schema.String>;
readonly arguments: Schema.optional<Schema.String>;
readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
export type StreamItem = Schema.Schema.Type<typeof StreamItem>;
export declare const WebSocketErrorEvent: Schema.StructWithRest<Schema.Struct<{
readonly type: Schema.tag<"error">;
readonly status: Schema.optional<Schema.Number>;
readonly status_code: Schema.optional<Schema.Number>;
readonly code: Schema.optional<Schema.NullOr<Schema.String>>;
readonly message: Schema.optional<Schema.String>;
readonly param: Schema.optional<Schema.NullOr<Schema.String>>;
readonly error: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly type: Schema.optional<Schema.NullOr<Schema.String>>;
readonly code: Schema.optional<Schema.NullOr<Schema.String>>;
readonly message: Schema.optional<Schema.NullOr<Schema.String>>;
readonly param: Schema.optional<Schema.NullOr<Schema.String>>;
}>>>;
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean]>>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
export declare const decodeKnownErrorEvent: (event: Event) => Effect.Effect<{
readonly [x: string]: unknown;
readonly type: "error";
readonly error?: {
readonly type?: string | null | undefined;
readonly code?: string | null | undefined;
readonly message?: string | null | undefined;
readonly param?: string | null | undefined;
} | null | undefined;
readonly status?: number | undefined;
readonly code?: string | null | undefined;
readonly message?: string | undefined;
readonly headers?: {
readonly [x: string]: string | number | boolean;
} | undefined;
readonly param?: string | null | undefined;
readonly status_code?: number | undefined;
}, Schema.SchemaError, never>;
export declare const Event: Schema.StructWithRest<Schema.Struct<{
readonly type: Schema.String;
readonly delta: Schema.optional<Schema.String>;
readonly text: Schema.optional<Schema.String>;
readonly item_id: Schema.optional<Schema.String>;
readonly summary_index: Schema.optional<Schema.Number>;
readonly item: Schema.optional<Schema.StructWithRest<Schema.Struct<{
readonly type: Schema.String;
readonly id: Schema.optional<Schema.String>;
readonly call_id: Schema.optional<Schema.String>;
readonly name: Schema.optional<Schema.String>;
readonly arguments: Schema.optional<Schema.String>;
readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>;
readonly response: Schema.optional<Schema.StructWithRest<Schema.Struct<{
readonly id: Schema.optional<Schema.String>;
readonly service_tier: Schema.optional<Schema.NullOr<Schema.String>>;
readonly incomplete_details: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly reason: Schema.optional<Schema.String>;
}>>>;
readonly usage: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly input_tokens: Schema.optional<Schema.Number>;
readonly input_tokens_details: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly cached_tokens: Schema.optional<Schema.Number>;
readonly cache_write_tokens: Schema.optional<Schema.Number>;
}>>>;
readonly output_tokens: Schema.optional<Schema.Number>;
readonly output_tokens_details: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly reasoning_tokens: Schema.optional<Schema.Number>;
}>>>;
readonly total_tokens: Schema.optional<Schema.Number>;
}>>>;
readonly error: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly type: Schema.optional<Schema.NullOr<Schema.String>>;
readonly code: Schema.optional<Schema.NullOr<Schema.String>>;
readonly message: Schema.optional<Schema.NullOr<Schema.String>>;
readonly param: Schema.optional<Schema.NullOr<Schema.String>>;
}>>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>;
readonly code: Schema.optional<Schema.NullOr<Schema.String>>;
readonly message: Schema.optional<Schema.String>;
readonly param: Schema.optional<Schema.NullOr<Schema.String>>;
readonly error: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly type: Schema.optional<Schema.NullOr<Schema.String>>;
readonly code: Schema.optional<Schema.NullOr<Schema.String>>;
readonly message: Schema.optional<Schema.NullOr<Schema.String>>;
readonly param: Schema.optional<Schema.NullOr<Schema.String>>;
}>>>;
readonly status: Schema.optional<Schema.Unknown>;
readonly status_code: Schema.optional<Schema.Unknown>;
readonly headers: Schema.optional<Schema.Unknown>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
export type Event = Schema.Schema.Type<typeof Event>;
export interface Extension {
readonly id: string;
readonly name: string;
readonly lowerMedia?: (input: {
readonly part: MediaPart;
readonly media: ProviderShared.ValidatedMedia;
readonly request: LLMRequest;
}) => MediaInput | undefined;
readonly messagePhase?: (value: unknown) => MessagePhase | null | undefined;
}
export interface ParserState {
readonly id: string;
readonly name: string;
readonly providerMetadataKey: string;
readonly tools: ToolStream.State<string>;
readonly hasFunctionCall: boolean;
readonly lifecycle: Lifecycle.State;
readonly messageItems: ReadonlySet<string>;
readonly messagePhase: (value: unknown) => MessagePhase | null | undefined;
readonly messagePhases: Readonly<Record<string, MessagePhase | null>>;
readonly reasoningItems: Readonly<Record<string, ReasoningStreamItem>>;
readonly store: boolean | undefined;
}
type ReasoningSummaryStatus = "active" | "can-conclude" | "concluded";
interface ReasoningStreamItem {
readonly encryptedContent: string | null | undefined;
readonly summaryParts: Readonly<Record<number, ReasoningSummaryStatus>>;
}
export declare const lowerTool: (protocolName: string, tool: ToolDefinition, inputSchema: {
readonly [x: string]: unknown;
}) => Effect.Effect<{
type: "function";
name: string;
description: string;
parameters: {
readonly [x: string]: unknown;
};
strict: boolean;
}, AIError, never>;
export declare const lowerToolChoice: (protocolName: string, toolChoice: NonNullable<LLMRequest["toolChoice"]>) => Effect.Effect<"required" | "none" | "auto" | {
type: "function";
name: string;
}, AIError, never>;
export declare const fromRequestWithExtension: (request: LLMRequest, extension: Extension) => Effect.Effect<{
service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
text?: {
verbosity: "low" | "medium" | "high";
} | undefined;
reasoning?: {
effort: string | undefined;
summary: "auto" | "concise" | "detailed" | undefined;
} | undefined;
include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
prompt_cache_key?: string | undefined;
store?: boolean | undefined;
instructions?: string | undefined;
model: string & import("effect/Brand").Brand<"AI.ModelID">;
input: LoweredInputItem[];
tools: {
type: "function";
name: string;
description: string;
parameters: {
readonly [x: string]: unknown;
};
strict: boolean;
}[] | undefined;
tool_choice: "required" | "none" | "auto" | {
type: "function";
name: string;
} | undefined;
stream: true;
max_output_tokens: number | undefined;
temperature: number | undefined;
top_p: number | undefined;
}, AIError, never>;
export declare const fromRequest: (request: LLMRequest) => Effect.Effect<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly {
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
}[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, AIError, never>;
export declare const providerMetadata: (state: ParserState, metadata: Record<string, unknown>) => ProviderMetadata;
export type StepResult = readonly [ParserState, ReadonlyArray<LLMEvent>];
export declare const terminal: (event: Event) => boolean;
export declare const onReasoningDelta: (state: ParserState, event: Event, itemID: string) => StepResult;
export declare const onReasoningDone: (state: ParserState, _event: Event) => StepResult;
export declare const providerFailure: (id: string, event: Event, fallback: string) => AIError;
export declare const step: (state: ParserState, event: Event) => AIError | Effect.Effect<StepResult, never, never> | Effect.Effect<[ParserState, readonly ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[]], AIError, never>;
/**
* The provider-neutral Open Responses protocol. Provider-specific Responses
* implementations compose this baseline with their own tools and event variants.
*/
export declare const initial: (request: LLMRequest, extension?: Extension) => ParserState;
export declare const protocol: Protocol<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly {
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
}[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, string, {
readonly [x: string]: unknown;
readonly type: string;
readonly error?: {
readonly type?: string | null | undefined;
readonly code?: string | null | undefined;
readonly message?: string | null | undefined;
readonly param?: string | null | undefined;
} | null | undefined;
readonly status?: unknown;
readonly code?: string | null | undefined;
readonly message?: string | undefined;
readonly headers?: unknown;
readonly text?: string | undefined;
readonly item?: {
readonly [x: string]: unknown;
readonly type: string;
readonly id?: string | undefined;
readonly name?: string | undefined;
readonly arguments?: string | undefined;
readonly encrypted_content?: string | null | undefined;
readonly call_id?: string | undefined;
} | undefined;
readonly delta?: string | undefined;
readonly response?: {
readonly [x: string]: unknown;
readonly error?: {
readonly type?: string | null | undefined;
readonly code?: string | null | undefined;
readonly message?: string | null | undefined;
readonly param?: string | null | undefined;
} | null | undefined;
readonly id?: string | undefined;
readonly usage?: {
readonly input_tokens?: number | undefined;
readonly output_tokens?: number | undefined;
readonly output_tokens_details?: {
readonly reasoning_tokens?: number | undefined;
} | null | undefined;
readonly total_tokens?: number | undefined;
readonly input_tokens_details?: {
readonly cached_tokens?: number | undefined;
readonly cache_write_tokens?: number | undefined;
} | null | undefined;
} | null | undefined;
readonly service_tier?: string | null | undefined;
readonly incomplete_details?: {
readonly reason?: string | undefined;
} | null | undefined;
} | undefined;
readonly param?: string | null | undefined;
readonly status_code?: unknown;
readonly item_id?: string | undefined;
readonly summary_index?: number | undefined;
}, ParserState>;
export declare const httpTransport: HttpTransport.HttpJsonTransport<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly {
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
}[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, string>;
export * as OpenResponses from "./open-responses.js";
import { Effect, Schema } from "effect";
import { HttpTransport } from "../route/transport/index.js";
import { Protocol } from "../route/protocol.js";
import { AIError, LLMEvent, Usage, } from "../schema/index.js";
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
import { classifyProviderFailure } from "../provider-error.js";
import { OpenResponsesOptions } from "./utils/open-responses-options.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolSchemaProjection } from "./utils/tool-schema.js";
import { ToolStream } from "./utils/tool-stream.js";
const ADAPTER = "open-responses";
const NAME = "Open Responses";
const MEDIA_MIMES = new Set([...ProviderShared.IMAGE_MIMES, ...ProviderShared.PDF_MIMES]);
export const PATH = "/responses";
// =============================================================================
// Request Body Schema
// =============================================================================
const OpenResponsesInputText = Schema.Struct({
type: Schema.tag("input_text"),
text: Schema.String,
});
const OpenResponsesInputImage = Schema.Struct({
type: Schema.tag("input_image"),
image_url: Schema.String,
});
const OpenResponsesInputFile = Schema.Struct({
type: Schema.tag("input_file"),
filename: Schema.String,
file_data: Schema.String,
mime_type: Schema.optional(Schema.String),
});
const MediaInput = Schema.Union([OpenResponsesInputImage, OpenResponsesInputFile]);
const OpenResponsesInputContent = Schema.Union([OpenResponsesInputText, MediaInput]);
const OpenResponsesOutputText = Schema.Struct({
type: Schema.tag("output_text"),
text: Schema.String,
});
export const MessagePhase = Schema.Literals(["commentary", "final_answer"]);
const OpenResponsesReasoningSummaryText = Schema.Struct({
type: Schema.tag("summary_text"),
text: Schema.String,
});
const OpenResponsesReasoningItem = Schema.Struct({
type: Schema.tag("reasoning"),
id: Schema.optionalKey(Schema.String),
summary: Schema.Array(OpenResponsesReasoningSummaryText),
encrypted_content: optionalNull(Schema.String),
});
const OpenResponsesItemReference = Schema.Struct({
type: Schema.tag("item_reference"),
id: Schema.String,
});
// `function_call_output.output` accepts either a plain string or an ordered
// array of content items so tools can return images and files in addition to text.
// https://www.openresponses.org/reference
const OpenResponsesFunctionCallOutputContent = Schema.Union([
OpenResponsesInputText,
OpenResponsesInputImage,
OpenResponsesInputFile,
]);
const OpenResponsesFunctionCallOutput = Schema.Union([
Schema.String,
Schema.Array(OpenResponsesFunctionCallOutputContent),
]);
export const InputItem = Schema.Union([
Schema.Struct({ role: Schema.tag("system"), content: Schema.String }),
Schema.Struct({ role: Schema.tag("user"), content: Schema.Array(OpenResponsesInputContent) }),
Schema.Struct({
role: Schema.tag("assistant"),
content: Schema.Array(OpenResponsesOutputText),
phase: Schema.optionalKey(MessagePhase),
}),
OpenResponsesReasoningItem,
OpenResponsesItemReference,
Schema.Struct({
type: Schema.tag("function_call"),
call_id: Schema.String,
name: Schema.String,
arguments: Schema.String,
}),
Schema.Struct({
type: Schema.tag("function_call_output"),
call_id: Schema.String,
output: OpenResponsesFunctionCallOutput,
}),
]);
export const Tool = Schema.Struct({
type: Schema.tag("function"),
name: Schema.String,
description: Schema.String,
parameters: JsonObject,
strict: Schema.optional(Schema.Boolean),
});
export const ToolChoice = Schema.Union([
Schema.Literals(["auto", "none", "required"]),
Schema.Struct({ type: Schema.tag("function"), name: Schema.String }),
]);
// Fields shared between the HTTP body and the WebSocket `response.create`
// message. The HTTP body adds `stream: true`; the WebSocket message adds
// `type: "response.create"`. Defining the shared shape once keeps the two
// transports in sync without a destructure-and-strip dance.
export const coreFields = {
model: Schema.String,
input: Schema.Array(InputItem),
instructions: Schema.optional(Schema.String),
tools: optionalArray(Tool),
tool_choice: Schema.optional(ToolChoice),
store: Schema.optional(Schema.Boolean),
service_tier: Schema.optional(OpenResponsesOptions.ServiceTierSchema),
prompt_cache_key: Schema.optional(Schema.String),
include: optionalArray(OpenResponsesOptions.ResponseIncludableSchema),
reasoning: Schema.optional(Schema.Struct({
effort: Schema.optional(OpenResponsesOptions.ReasoningEffort),
summary: Schema.optional(Schema.Literals(["auto", "concise", "detailed"])),
})),
text: Schema.optional(Schema.Struct({
verbosity: Schema.optional(OpenResponsesOptions.TextVerbositySchema),
})),
max_output_tokens: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
top_p: Schema.optional(Schema.Number),
};
const OpenResponsesBody = Schema.Struct({
...coreFields,
stream: Schema.Literal(true),
});
const OpenResponsesUsage = Schema.Struct({
input_tokens: Schema.optional(Schema.Number),
input_tokens_details: optionalNull(Schema.Struct({
cached_tokens: Schema.optional(Schema.Number),
cache_write_tokens: Schema.optional(Schema.Number),
})),
output_tokens: Schema.optional(Schema.Number),
output_tokens_details: optionalNull(Schema.Struct({ reasoning_tokens: Schema.optional(Schema.Number) })),
total_tokens: Schema.optional(Schema.Number),
});
export const StreamItem = Schema.StructWithRest(Schema.Struct({
type: Schema.String,
id: Schema.optional(Schema.String),
call_id: Schema.optional(Schema.String),
name: Schema.optional(Schema.String),
arguments: Schema.optional(Schema.String),
encrypted_content: optionalNull(Schema.String),
}), [Schema.Record(Schema.String, Schema.Unknown)]);
// The Responses schema puts streaming error details at the top level and
// response failures under `response.error`. WebSocket failures use an
// event-level `error` envelope, so accept all three shapes here.
// https://www.openresponses.org/specification
const OpenResponsesErrorPayload = Schema.Struct({
type: optionalNull(Schema.String),
code: optionalNull(Schema.String),
message: optionalNull(Schema.String),
param: optionalNull(Schema.String),
});
const WebSocketErrorHeader = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]);
export const WebSocketErrorEvent = Schema.StructWithRest(Schema.Struct({
type: Schema.tag("error"),
status: Schema.optional(Schema.Number),
status_code: Schema.optional(Schema.Number),
code: optionalNull(Schema.String),
message: Schema.optional(Schema.String),
param: optionalNull(Schema.String),
error: optionalNull(OpenResponsesErrorPayload),
headers: Schema.optional(Schema.Record(Schema.String, WebSocketErrorHeader)),
}), [Schema.Record(Schema.String, Schema.Unknown)]);
const decodeWebSocketErrorEvent = Schema.decodeUnknownEffect(WebSocketErrorEvent);
export const decodeKnownErrorEvent = (event) => decodeWebSocketErrorEvent({
...event,
status: typeof event.status === "number" ? event.status : undefined,
status_code: typeof event.status_code === "number" ? event.status_code : undefined,
headers: ProviderShared.isRecord(event.headers)
? Object.fromEntries(Object.entries(event.headers).filter((entry) => typeof entry[1] === "string" || typeof entry[1] === "number" || typeof entry[1] === "boolean"))
: undefined,
});
export const Event = Schema.StructWithRest(Schema.Struct({
type: Schema.String,
delta: Schema.optional(Schema.String),
text: Schema.optional(Schema.String),
item_id: Schema.optional(Schema.String),
summary_index: Schema.optional(Schema.Number),
item: Schema.optional(StreamItem),
response: Schema.optional(Schema.StructWithRest(Schema.Struct({
id: Schema.optional(Schema.String),
service_tier: optionalNull(Schema.String),
incomplete_details: optionalNull(Schema.Struct({ reason: Schema.optional(Schema.String) })),
usage: optionalNull(OpenResponsesUsage),
error: optionalNull(OpenResponsesErrorPayload),
}), [Schema.Record(Schema.String, Schema.Unknown)])),
code: optionalNull(Schema.String),
message: Schema.optional(Schema.String),
param: optionalNull(Schema.String),
error: optionalNull(OpenResponsesErrorPayload),
status: Schema.optional(Schema.Unknown),
status_code: Schema.optional(Schema.Unknown),
headers: Schema.optional(Schema.Unknown),
}), [Schema.Record(Schema.String, Schema.Unknown)]);
const BASE = { id: ADAPTER, name: NAME };
// =============================================================================
// Request Lowering
// =============================================================================
export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool, inputSchema) {
if (tool.native !== undefined)
return yield* ProviderShared.invalidRequest(`${protocolName} does not support provider-native tool ${tool.name}`);
return {
type: "function",
name: tool.name,
description: tool.description,
parameters: ToolSchemaProjection.responses(inputSchema),
// TODO: Read this from Responses tool options so direct LLM callers can opt into strict schemas.
strict: false,
};
});
export const lowerToolChoice = (protocolName, toolChoice) => ProviderShared.matchToolChoice(protocolName, toolChoice, {
auto: () => "auto",
none: () => "none",
required: () => "required",
tool: (toolName) => ({ type: "function", name: toolName }),
});
const lowerToolCall = (part) => ({
type: "function_call",
call_id: part.id,
name: part.name,
arguments: ProviderShared.encodeJson(part.input),
});
const lowerReasoning = (part, providerMetadataKey) => {
const metadata = part.providerMetadata?.[providerMetadataKey];
if (!ProviderShared.isRecord(metadata) || typeof metadata.itemId !== "string" || metadata.itemId.length === 0)
return undefined;
const encryptedContent = typeof metadata.reasoningEncryptedContent === "string" || metadata.reasoningEncryptedContent === null
? metadata.reasoningEncryptedContent
: undefined;
return {
type: "reasoning",
id: metadata.itemId,
summary: part.text.length > 0 ? [{ type: "summary_text", text: part.text }] : [],
encrypted_content: encryptedContent,
};
};
const hostedToolItemID = (part, providerMetadataKey) => {
const metadata = part.providerMetadata?.[providerMetadataKey];
return ProviderShared.isRecord(metadata) && typeof metadata.itemId === "string" && metadata.itemId.length > 0
? metadata.itemId
: undefined;
};
const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (part, request, extension) {
const media = yield* ProviderShared.validateMedia(extension.name, part, MEDIA_MIMES);
const extended = extension.lowerMedia?.({ part, media, request });
if (extended)
return extended;
if (media.mime === "application/pdf") {
return {
type: "input_file",
filename: part.filename ?? "document.pdf",
file_data: media.dataUrl,
};
}
return { type: "input_image", image_url: media.dataUrl };
});
const lowerUserContent = Effect.fnUntraced(function* (part, request, extension) {
if (part.type === "text")
return { type: "input_text", text: part.text };
if (part.type === "media")
return yield* lowerMedia(part, request, extension);
return yield* ProviderShared.unsupportedContent(extension.name, "user", ["text", "media"]);
});
// Tool results may carry structured text, images, and files. Keep media as provider-native
// content instead of JSON-stringifying base64 into a prompt string.
const lowerToolResultContentItem = Effect.fnUntraced(function* (item, request, extension) {
if (item.type === "text")
return { type: "input_text", text: item.text };
return yield* lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name }, request, extension);
});
const lowerToolResultOutput = Effect.fnUntraced(function* (part, request, extension) {
// Text/json/error results are encoded as a plain string for backward
// compatibility with existing cassettes and provider expectations.
if (part.result.type !== "content")
return ProviderShared.toolResultText(part);
// Preserve the narrowed array element type when compiled through a consumer package.
const content = part.result.value;
return yield* Effect.forEach(content, (item) => lowerToolResultContentItem(item, request, extension));
});
const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (request, extension) {
const system = request.system.length === 0 ? [] : [{ role: "system", content: ProviderShared.joinText(request.system) }];
const input = [...system];
const store = OpenResponsesOptions.resolve(request).store;
const providerMetadataKey = request.model.route.providerMetadataKey ?? "openresponses";
for (const message of request.messages) {
if (message.role === "system") {
const part = yield* ProviderShared.wrappedSystemUpdate(extension.name, message);
const previous = input.at(-1);
if (previous && "role" in previous && previous.role === "user")
input[input.length - 1] = {
role: "user",
content: [...previous.content, { type: "input_text", text: part.text }],
};
else
input.push({ role: "user", content: [{ type: "input_text", text: part.text }] });
continue;
}
if (message.role === "user") {
input.push({
role: "user",
content: yield* Effect.forEach(message.content, (part) => lowerUserContent(part, request, extension)),
});
continue;
}
if (message.role === "assistant") {
const content = [];
const reasoningItems = {};
const reasoningReferences = new Set();
const hostedToolReferences = new Set();
const flushText = () => {
if (content.length === 0)
return;
const groups = content.reduce((groups, part) => {
const metadata = part.providerMetadata?.[providerMetadataKey];
const phase = ProviderShared.isRecord(metadata) ? messagePhase(metadata.phase, extension) : undefined;
const group = groups.at(-1);
if (group && group.phase === phase)
group.parts.push(part);
else
groups.push({ phase, parts: [part] });
return groups;
}, []);
input.push(...groups.map((group) => ({
role: "assistant",
content: group.parts.map((part) => ({ type: "output_text", text: part.text })),
...(group.phase === undefined ? {} : { phase: group.phase }),
})));
content.splice(0, content.length);
};
for (const part of message.content) {
if (part.type === "text") {
content.push(part);
continue;
}
if (part.type === "reasoning") {
flushText();
const reasoning = lowerReasoning(part, providerMetadataKey);
if (!reasoning)
continue;
if (store !== false) {
if (!reasoningReferences.has(reasoning.id))
input.push({ type: "item_reference", id: reasoning.id });
reasoningReferences.add(reasoning.id);
continue;
}
const existing = reasoningItems[reasoning.id];
if (existing) {
existing.summary.push(...reasoning.summary);
if (typeof reasoning.encrypted_content === "string")
existing.encrypted_content = reasoning.encrypted_content;
continue;
}
const replay = {
type: reasoning.type,
summary: reasoning.summary,
encrypted_content: reasoning.encrypted_content,
};
reasoningItems[reasoning.id] = replay;
input.push(replay);
continue;
}
if (part.type === "tool-call") {
flushText();
if (part.providerExecuted === true)
continue;
input.push(lowerToolCall(part));
continue;
}
if (part.type === "tool-result" && part.providerExecuted === true) {
flushText();
const itemID = hostedToolItemID(part, providerMetadataKey);
if (store !== false && itemID && !hostedToolReferences.has(itemID))
input.push({ type: "item_reference", id: itemID });
if (store === false && part.result.type === "content") {
const content = part.result.value;
input.push({
role: "user",
content: yield* Effect.forEach(content, (item) => lowerToolResultContentItem(item, request, extension)),
});
}
if (itemID)
hostedToolReferences.add(itemID);
continue;
}
return yield* ProviderShared.unsupportedContent(extension.name, "assistant", [
"text",
"reasoning",
"tool-call",
"tool-result",
]);
}
flushText();
continue;
}
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["tool-result"]))
return yield* ProviderShared.unsupportedContent(extension.name, "tool", ["tool-result"]);
input.push({
type: "function_call_output",
call_id: part.id,
output: yield* lowerToolResultOutput(part, request, extension),
});
}
}
// With store:false, Responses APIs only accept previous reasoning items when the
// complete item has encrypted state. Summary blocks for one item may carry
// that state only on the last block, so filter after they have been joined.
return store === false
? input.filter((item) => !("type" in item) || item.type !== "reasoning" || typeof item.encrypted_content === "string")
: input;
});
const lowerOptions = (request) => {
const options = OpenResponsesOptions.resolve(request);
return {
...(options.instructions ? { instructions: options.instructions } : {}),
...(options.store !== undefined ? { store: options.store } : {}),
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
...(options.include ? { include: options.include } : {}),
...(options.reasoningEffort || options.reasoningSummary
? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } }
: {}),
...(options.textVerbosity ? { text: { verbosity: options.textVerbosity } } : {}),
...(options.serviceTier ? { service_tier: options.serviceTier } : {}),
};
};
export const fromRequestWithExtension = Effect.fn("OpenResponses.fromRequestWithExtension")(function* (request, extension) {
const generation = request.generation;
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
return {
model: request.model.id,
input: yield* lowerMessages(request, extension),
tools: request.tools.length === 0
? undefined
: yield* Effect.forEach(request.tools, (tool) => lowerTool(extension.name, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility))),
tool_choice: request.toolChoice ? yield* lowerToolChoice(extension.name, request.toolChoice) : undefined,
stream: true,
max_output_tokens: generation?.maxTokens,
temperature: generation?.temperature,
top_p: generation?.topP,
...lowerOptions(request),
};
});
const decodeBody = ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenResponsesBody));
export const fromRequest = Effect.fn("OpenResponses.fromRequest")(function* (request) {
return yield* decodeBody(yield* fromRequestWithExtension(request, BASE));
});
// =============================================================================
// Stream Parsing
// =============================================================================
// Responses APIs report `input_tokens` (inclusive total) with a
// cached-read and cache-write subsets, and `output_tokens` (inclusive total)
// with a `reasoning_tokens` subset. Pass the totals through and derive the
// non-cached breakdown.
const mapUsage = (usage, providerMetadataKey) => {
if (!usage)
return undefined;
const cached = usage.input_tokens_details?.cached_tokens;
const cacheWrite = usage.input_tokens_details?.cache_write_tokens;
const reasoning = usage.output_tokens_details?.reasoning_tokens;
const nonCached = ProviderShared.subtractTokens(usage.input_tokens, ProviderShared.sumTokens(cached, cacheWrite));
return new Usage({
inputTokens: usage.input_tokens,
outputTokens: usage.output_tokens,
nonCachedInputTokens: nonCached,
cacheReadInputTokens: cached,
cacheWriteInputTokens: cacheWrite,
reasoningTokens: reasoning,
totalTokens: ProviderShared.totalTokens(usage.input_tokens, usage.output_tokens, usage.total_tokens),
providerMetadata: { [providerMetadataKey]: usage },
});
};
const mapFinishReason = (event, hasFunctionCall) => {
const reason = event.response?.incomplete_details?.reason;
if (reason === undefined || reason === null) {
if (hasFunctionCall)
return "tool-calls";
if (event.type === "response.incomplete")
return "unknown";
return "stop";
}
if (reason === "max_output_tokens")
return "length";
if (reason === "content_filter")
return "content-filter";
return hasFunctionCall ? "tool-calls" : "unknown";
};
export const providerMetadata = (state, metadata) => ({
[state.providerMetadataKey]: metadata,
});
const isReasoningItem = (item) => item.type === "reasoning" && typeof item.id === "string" && item.id.length > 0;
const NO_EVENTS = [];
// `response.completed` / `response.incomplete` are clean finishes that emit a
// `finish` event; `response.failed` and `error` are hard failures. All four end
// the stream, so keep this set aligned with `step` and the protocol's terminal predicate.
const TERMINAL_TYPES = new Set(["error", "response.completed", "response.incomplete", "response.failed"]);
export const terminal = (event) => TERMINAL_TYPES.has(event.type);
const onOutputTextDelta = (state, event, id) => {
if (!event.delta)
return [state, NO_EVENTS];
const events = [];
const phase = state.messagePhases[id];
const metadata = phase === undefined ? undefined : providerMetadata(state, { phase });
const lifecycle = Lifecycle.textStart(state.lifecycle, events, id, metadata);
return [{ ...state, lifecycle: Lifecycle.textDelta(lifecycle, events, id, event.delta) }, events];
};
const onOutputTextDone = (state, event, id) => {
if (state.messageItems.has(id)) {
if (state.lifecycle.text.has(id) || event.text === undefined)
return [state, NO_EVENTS];
return onOutputTextDelta(state, { ...event, delta: event.text }, id);
}
const events = [];
return [{ ...state, lifecycle: Lifecycle.textEnd(state.lifecycle, events, id) }, events];
};
export const onReasoningDelta = (state, event, itemID) => {
if (!event.delta)
return [state, NO_EVENTS];
const events = [];
const id = event.summary_index !== undefined || state.reasoningItems[itemID] ? `${itemID}:${event.summary_index ?? 0}` : itemID;
return [
{
...state,
lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, id, event.delta),
},
events,
];
};
export const onReasoningDone = (state, _event) => [state, NO_EVENTS];
const reasoningMetadata = (state, item) => providerMetadata(state, { itemId: item.id, reasoningEncryptedContent: item.encrypted_content ?? null });
// Responses APIs stream reasoning items in a stable order:
// `output_item.added` (reasoning) →
// `reasoning_summary_part.added` (index=0) →
// `reasoning_summary_text.delta` →
// `reasoning_summary_part.done` (index=0) →
// (repeat for index>0) →
// `output_item.done` (reasoning).
// The handlers below rely on this ordering: `onOutputItemAdded` seeds the
// per-item entry, `onReasoningSummaryPartAdded` for `summary_index === 0`
// short-circuits when the entry already exists, and higher-index handlers
// fold against the same entry. Behaviour for out-of-order events is
// best-effort, not guaranteed.
const onOutputItemAdded = (state, event) => {
const item = event.item;
if (item?.type === "message" && item.id)
return [
{
...state,
messageItems: new Set([...state.messageItems, item.id]),
messagePhases: (() => {
const phase = state.messagePhase(item.phase);
return phase === undefined ? state.messagePhases : { ...state.messagePhases, [item.id]: phase };
})(),
},
NO_EVENTS,
];
if (item && isReasoningItem(item)) {
const events = [];
return [
{
...state,
lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `${item.id}:0`, reasoningMetadata(state, item)),
reasoningItems: {
...state.reasoningItems,
[item.id]: { encryptedContent: item.encrypted_content, summaryParts: { 0: "active" } },
},
},
events,
];
}
if (item?.type !== "function_call" || !item.id)
return [state, NO_EVENTS];
const metadata = providerMetadata(state, { itemId: item.id });
const events = [];
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
return [
{
...state,
lifecycle,
tools: ToolStream.start(state.tools, item.id, {
id: item.call_id ?? item.id,
name: item.name ?? "",
input: item.arguments ?? "",
providerMetadata: metadata,
}),
},
[
...events,
LLMEvent.toolInputStart({ id: item.call_id ?? item.id, name: item.name ?? "", providerMetadata: metadata }),
],
];
};
const onReasoningSummaryPartAdded = (state, event) => {
if (!event.item_id || event.summary_index === undefined)
return [state, NO_EVENTS];
const item = state.reasoningItems[event.item_id] ?? { encryptedContent: undefined, summaryParts: {} };
if (event.summary_index === 0) {
if (state.reasoningItems[event.item_id])
return [state, NO_EVENTS];
const events = [];
return [
{
...state,
lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `${event.item_id}:0`, providerMetadata(state, { itemId: event.item_id, reasoningEncryptedContent: null })),
reasoningItems: {
...state.reasoningItems,
[event.item_id]: { ...item, summaryParts: { 0: "active" } },
},
},
events,
];
}
const events = [];
const closed = Object.entries(item.summaryParts)
.filter((entry) => entry[1] === "can-conclude")
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${event.item_id}:${entry[0]}`, providerMetadata(state, { itemId: event.item_id })), state.lifecycle);
return [
{
...state,
lifecycle: Lifecycle.reasoningStart(closed, events, `${event.item_id}:${event.summary_index}`, providerMetadata(state, { itemId: event.item_id, reasoningEncryptedContent: item.encryptedContent ?? null })),
reasoningItems: {
...state.reasoningItems,
[event.item_id]: {
...item,
summaryParts: {
...Object.fromEntries(Object.entries(item.summaryParts).map((entry) => entry[1] === "can-conclude" ? [entry[0], "concluded"] : entry)),
[event.summary_index]: "active",
},
},
},
},
events,
];
};
const onReasoningSummaryPartDone = (state, event) => {
if (!event.item_id || event.summary_index === undefined)
return [state, NO_EVENTS];
const item = state.reasoningItems[event.item_id];
if (!item)
return [state, NO_EVENTS];
const events = [];
return [
{
...state,
lifecycle: state.store !== false
? Lifecycle.reasoningEnd(state.lifecycle, events, `${event.item_id}:${event.summary_index}`, providerMetadata(state, { itemId: event.item_id }))
: state.lifecycle,
reasoningItems: {
...state.reasoningItems,
[event.item_id]: {
...item,
summaryParts: {
...item.summaryParts,
[event.summary_index]: state.store !== false ? "concluded" : "can-conclude",
},
},
},
},
events,
];
};
const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgumentsDelta")(function* (state, event) {
if (!event.item_id || !event.delta)
return [state, NO_EVENTS];
const result = ToolStream.appendExisting(state.id, state.tools, event.item_id, event.delta, `${state.name} tool argument delta is missing its tool call`);
if (ToolStream.isError(result))
return yield* result;
const events = [];
const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
events.push(...result.events);
return [{ ...state, lifecycle, tools: result.tools }, events];
});
const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (state, event) {
const item = event.item;
if (!item)
return [state, NO_EVENTS];
if (item.type === "message" && item.id) {
const itemPhase = state.messagePhase(item.phase);
const phase = itemPhase === undefined ? state.messagePhases[item.id] : itemPhase;
const events = [];
const messageItems = new Set(state.messageItems);
messageItems.delete(item.id);
const { [item.id]: _phase, ...messagePhases } = state.messagePhases;
return [
{
...state,
lifecycle: Lifecycle.textEnd(state.lifecycle, events, item.id, phase === undefined ? undefined : providerMetadata(state, { phase })),
messageItems,
messagePhases,
},
events,
];
}
if (item.type === "function_call") {
if (!item.id || !item.call_id || !item.name)
return [state, NO_EVENTS];
const tools = state.tools[item.id]
? state.tools
: ToolStream.start(state.tools, item.id, { id: item.call_id, name: item.name });
const result = item.arguments === undefined
? yield* ToolStream.finish(state.id, tools, item.id)
: yield* ToolStream.finishWithInput(state.id, tools, item.id, item.arguments);
const events = [];
const resultEvents = result.events ?? [];
const lifecycle = resultEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
events.push(...resultEvents);
return [
{
...state,
lifecycle,
hasFunctionCall: resultEvents.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
state.hasFunctionCall,
tools: result.tools,
},
events,
];
}
if (isReasoningItem(item)) {
const events = [];
const metadata = reasoningMetadata(state, item);
const reasoningItem = state.reasoningItems[item.id];
if (reasoningItem) {
const lifecycle = Object.entries(reasoningItem.summaryParts)
.filter((entry) => entry[1] === "active" || entry[1] === "can-conclude")
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${entry[0]}`, metadata), state.lifecycle);
const { [item.id]: _removed, ...reasoningItems } = state.reasoningItems;
return [{ ...state, lifecycle, reasoningItems }, events];
}
if (!state.lifecycle.reasoning.has(item.id)) {
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata: metadata }));
events.push(LLMEvent.reasoningEnd({ id: item.id, providerMetadata: metadata }));
return [{ ...state, lifecycle }, events];
}
return [
{ ...state, lifecycle: Lifecycle.reasoningEnd(state.lifecycle, events, item.id, metadata) },
events,
];
}
return [state, NO_EVENTS];
});
const onResponseFinish = (state, event) => {
const events = [];
const lifecycle = Lifecycle.finish(state.lifecycle, events, {
reason: {
normalized: mapFinishReason(event, state.hasFunctionCall),
raw: event.response?.incomplete_details?.reason,
},
usage: mapUsage(event.response?.usage, state.providerMetadataKey),
providerMetadata: event.response?.id || event.response?.service_tier
? providerMetadata(state, {
responseId: event.response.id,
serviceTier: event.response.service_tier,
})
: undefined,
});
return [{ ...state, lifecycle }, events];
};
// Build a single human-readable message from whatever the provider supplied.
// When both code and message are present, prefix the code so consumers see
// the failure mode (e.g. `rate_limit_exceeded: Slow down`) instead of just
// the bare message — production rate limits and context-length failures used
// to be indistinguishable from generic stream drops.
const providerErrorMessage = (event, fallback) => {
const nested = event.error ?? event.response?.error ?? undefined;
const message = event.message || nested?.message || undefined;
const code = event.code || nested?.code || undefined;
if (message && code)
return `${code}: ${message}`;
return message || code || fallback;
};
export const providerFailure = (id, event, fallback) => {
const code = event.code || event.error?.code || event.response?.error?.code || undefined;
const message = providerErrorMessage(event, fallback);
const status = typeof event.status === "number"
? event.status
: typeof event.status_code === "number"
? event.status_code
: undefined;
return new AIError({
module: id,
method: "stream",
reason: classifyProviderFailure({ message, code, status }),
});
};
const providerError = (state, event, fallback) => providerFailure(state.id, event, fallback);
export const step = (state, event) => {
if (event.type === "response.output_text.delta" || event.type === "response.output_text.done") {
if (!event.item_id)
return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
return Effect.succeed(event.type === "response.output_text.delta"
? onOutputTextDelta(state, event, event.item_id)
: onOutputTextDone(state, event, event.item_id));
}
if (event.type === "response.reasoning.delta" || event.type === "response.reasoning_summary_text.delta") {
if (!event.item_id)
return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
return Effect.succeed(onReasoningDelta(state, event, event.item_id));
}
if (event.type === "response.reasoning.done" || event.type === "response.reasoning_summary_text.done") {
if (!event.item_id)
return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
return Effect.succeed(onReasoningDone(state, event));
}
if (event.type === "response.reasoning_summary_part.added")
return event.item_id
? Effect.succeed(onReasoningSummaryPartAdded(state, event))
: ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
if (event.type === "response.reasoning_summary_part.done")
return event.item_id
? Effect.succeed(onReasoningSummaryPartDone(state, event))
: ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
if (event.type === "response.output_item.added") {
if (event.item?.type === "message" && !event.item.id)
return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
return Effect.succeed(onOutputItemAdded(state, event));
}
if (event.type === "response.function_call_arguments.delta")
return onFunctionCallArgumentsDelta(state, event);
if (event.type === "response.output_item.done") {
if (event.item?.type === "message" && !event.item.id)
return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
return onOutputItemDone(state, event);
}
if (event.type === "response.completed" || event.type === "response.incomplete")
return Effect.succeed(onResponseFinish(state, event));
if (event.type === "response.failed")
return providerError(state, event, `${state.name} response failed`);
if (event.type === "error")
return decodeKnownErrorEvent(event).pipe(Effect.mapError(() => ProviderShared.eventError(state.id, `${state.name} returned a malformed error event`)), Effect.flatMap(() => providerError(state, event, `${state.name} stream error`)));
return Effect.succeed([state, NO_EVENTS]);
};
// =============================================================================
// Protocol
// =============================================================================
/**
* The provider-neutral Open Responses protocol. Provider-specific Responses
* implementations compose this baseline with their own tools and event variants.
*/
export const initial = (request, extension = BASE) => ({
id: extension.id,
name: extension.name,
providerMetadataKey: request.model.route.providerMetadataKey ?? "openresponses",
hasFunctionCall: false,
tools: ToolStream.empty(),
lifecycle: Lifecycle.initial(),
messageItems: new Set(),
messagePhase: (value) => messagePhase(value, extension),
messagePhases: {},
reasoningItems: {},
store: OpenResponsesOptions.resolve(request).store,
});
const messagePhase = (value, extension) => {
if (value === "commentary" || value === "final_answer")
return value;
return extension.messagePhase?.(value);
};
export const protocol = Protocol.make({
id: ADAPTER,
body: {
schema: OpenResponsesBody,
from: fromRequest,
},
stream: {
event: Protocol.jsonEvent(Event),
initial,
step,
terminal,
},
});
export const httpTransport = HttpTransport.sseJson.with();
export * as OpenResponses from "./open-responses.js";
import { Effect, Schema } from "effect";
import { Route } from "../route/client.js";
import { HttpTransport } from "../route/transport/index.js";
import { Protocol } from "../route/protocol.js";
import { AIError, LLMEvent, Usage, type FinishReasonDetails, type CacheHint, type LLMRequest } from "../schema/index.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolStream } from "./utils/tool-stream.js";
export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
export declare const PATH = "/chat/completions";
declare const OpenAIChatCacheControl: Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>;
export declare const bodyFields: {
model: Schema.String;
messages: Schema.$Array<Schema.toTaggedUnion<"role", readonly [Schema.Struct<{
readonly role: Schema.Literal<"system">;
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"image_url">;
readonly image_url: Schema.Struct<{
readonly url: Schema.String;
}>;
}>]>>]>;
}>, Schema.Struct<{
readonly role: Schema.Literal<"user">;
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"image_url">;
readonly image_url: Schema.Struct<{
readonly url: Schema.String;
}>;
}>]>>]>;
}>, Schema.StructWithRest<Schema.Struct<{
readonly role: Schema.Literal<"assistant">;
readonly content: Schema.NullOr<Schema.String>;
readonly tool_calls: Schema.optional<Schema.$Array<Schema.Struct<{
readonly id: Schema.String;
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
readonly arguments: Schema.String;
}>;
}>>>;
readonly reasoning_content: Schema.optional<Schema.String>;
readonly reasoning: Schema.optional<Schema.String>;
readonly reasoning_text: Schema.optional<Schema.String>;
readonly reasoning_details: Schema.optional<Schema.Unknown>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.Struct<{
readonly role: Schema.Literal<"tool">;
readonly tool_call_id: Schema.String;
readonly content: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>]>>;
tools: Schema.optional<Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.$Record<Schema.String, Schema.Unknown>;
}>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>>>;
tool_choice: Schema.optional<Schema.Union<readonly [Schema.Literals<readonly ["auto", "none", "required"]>, Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
}>;
}>]>>;
stream: Schema.Literal<true>;
stream_options: Schema.optional<Schema.Struct<{
readonly include_usage: Schema.Boolean;
}>>;
store: Schema.optional<Schema.Boolean>;
prompt_cache_key: Schema.optional<Schema.String>;
reasoning_effort: Schema.optional<Schema.String>;
max_completion_tokens: Schema.optional<Schema.Number>;
max_tokens: Schema.optional<Schema.Number>;
temperature: Schema.optional<Schema.Number>;
top_p: Schema.optional<Schema.Number>;
frequency_penalty: Schema.optional<Schema.Number>;
presence_penalty: Schema.optional<Schema.Number>;
seed: Schema.optional<Schema.Number>;
stop: Schema.optional<Schema.$Array<Schema.String>>;
};
declare const OpenAIChatBody: Schema.Struct<{
model: Schema.String;
messages: Schema.$Array<Schema.toTaggedUnion<"role", readonly [Schema.Struct<{
readonly role: Schema.Literal<"system">;
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"image_url">;
readonly image_url: Schema.Struct<{
readonly url: Schema.String;
}>;
}>]>>]>;
}>, Schema.Struct<{
readonly role: Schema.Literal<"user">;
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"image_url">;
readonly image_url: Schema.Struct<{
readonly url: Schema.String;
}>;
}>]>>]>;
}>, Schema.StructWithRest<Schema.Struct<{
readonly role: Schema.Literal<"assistant">;
readonly content: Schema.NullOr<Schema.String>;
readonly tool_calls: Schema.optional<Schema.$Array<Schema.Struct<{
readonly id: Schema.String;
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
readonly arguments: Schema.String;
}>;
}>>>;
readonly reasoning_content: Schema.optional<Schema.String>;
readonly reasoning: Schema.optional<Schema.String>;
readonly reasoning_text: Schema.optional<Schema.String>;
readonly reasoning_details: Schema.optional<Schema.Unknown>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.Struct<{
readonly role: Schema.Literal<"tool">;
readonly tool_call_id: Schema.String;
readonly content: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>]>>;
tools: Schema.optional<Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.$Record<Schema.String, Schema.Unknown>;
}>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>>>;
tool_choice: Schema.optional<Schema.Union<readonly [Schema.Literals<readonly ["auto", "none", "required"]>, Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
}>;
}>]>>;
stream: Schema.Literal<true>;
stream_options: Schema.optional<Schema.Struct<{
readonly include_usage: Schema.Boolean;
}>>;
store: Schema.optional<Schema.Boolean>;
prompt_cache_key: Schema.optional<Schema.String>;
reasoning_effort: Schema.optional<Schema.String>;
max_completion_tokens: Schema.optional<Schema.Number>;
max_tokens: Schema.optional<Schema.Number>;
temperature: Schema.optional<Schema.Number>;
top_p: Schema.optional<Schema.Number>;
frequency_penalty: Schema.optional<Schema.Number>;
presence_penalty: Schema.optional<Schema.Number>;
seed: Schema.optional<Schema.Number>;
stop: Schema.optional<Schema.$Array<Schema.String>>;
}>;
export type OpenAIChatBody = Schema.Schema.Type<typeof OpenAIChatBody>;
export declare const OpenAIChatEvent: Schema.Struct<{
readonly choices: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
readonly delta: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
readonly content: Schema.optional<Schema.NullOr<Schema.String>>;
readonly reasoning_content: Schema.optional<Schema.NullOr<Schema.String>>;
readonly reasoning: Schema.optional<Schema.NullOr<Schema.String>>;
readonly reasoning_text: Schema.optional<Schema.NullOr<Schema.String>>;
readonly reasoning_details: Schema.optional<Schema.NullOr<Schema.Unknown>>;
readonly tool_calls: Schema.optional<Schema.NullOr<Schema.$Array<Schema.Struct<{
readonly index: Schema.optional<Schema.NullOr<Schema.Number>>;
readonly id: Schema.optional<Schema.NullOr<Schema.String>>;
readonly function: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly name: Schema.optional<Schema.NullOr<Schema.String>>;
readonly arguments: Schema.optional<Schema.NullOr<Schema.String>>;
}>>>;
}>>>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
readonly finish_reason: Schema.optional<Schema.NullOr<Schema.String>>;
readonly native_finish_reason: Schema.optional<Schema.NullOr<Schema.String>>;
}>>>>;
readonly usage: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
readonly prompt_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
readonly completion_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
readonly total_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
readonly prompt_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
readonly cached_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
readonly cache_write_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
readonly completion_tokens_details: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{
readonly reasoning_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
readonly accepted_prediction_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
readonly rejected_prediction_tokens: Schema.optional<Schema.NullOr<Schema.Number>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>;
readonly error: Schema.optional<Schema.NullOr<Schema.Struct<{
readonly code: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Number]>>>;
readonly message: Schema.String;
}>>>;
}>;
export type OpenAIChatEvent = Schema.Schema.Type<typeof OpenAIChatEvent>;
interface PendingToolDelta {
readonly id?: string;
readonly name?: string;
readonly input: string;
}
export interface ParserState {
readonly tools: ToolStream.State<number>;
readonly pendingTools: Partial<Record<number, PendingToolDelta>>;
readonly toolCallEvents: ReadonlyArray<LLMEvent>;
readonly usage?: Usage;
readonly finishReason?: FinishReasonDetails;
readonly lifecycle: Lifecycle.State;
readonly reasoningField?: string;
readonly reasoningDetails: Array<unknown>;
readonly reasoningDetailsObserved: boolean;
readonly reasoningEmitted: boolean;
readonly latestToolIndex?: number;
readonly nextToolIndex: number;
}
interface LoweringOptions {
readonly cacheControl?: (cache: CacheHint | undefined) => Schema.Schema.Type<typeof OpenAIChatCacheControl> | undefined;
}
export declare const fromRequest: (request: LLMRequest, options?: LoweringOptions | undefined) => Effect.Effect<{
reasoning_effort?: string | undefined;
prompt_cache_key?: string | undefined;
store?: boolean | undefined;
temperature: number | undefined;
top_p: number | undefined;
frequency_penalty: number | undefined;
presence_penalty: number | undefined;
seed: number | undefined;
stop: readonly string[] | undefined;
max_completion_tokens: number | undefined;
model: string & import("effect/Brand").Brand<"AI.ModelID">;
messages: ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
tools: {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
tool_choice: "required" | "none" | "auto" | {
type: "function";
function: {
name: string;
};
} | undefined;
stream: true;
stream_options: {
include_usage: boolean;
};
} | {
reasoning_effort?: string | undefined;
prompt_cache_key?: string | undefined;
store?: boolean | undefined;
temperature: number | undefined;
top_p: number | undefined;
frequency_penalty: number | undefined;
presence_penalty: number | undefined;
seed: number | undefined;
stop: readonly string[] | undefined;
max_tokens: number | undefined;
model: string & import("effect/Brand").Brand<"AI.ModelID">;
messages: ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
tools: {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
tool_choice: "required" | "none" | "auto" | {
type: "function";
function: {
name: string;
};
} | undefined;
stream: true;
stream_options: {
include_usage: boolean;
};
}, AIError, never>;
/**
* The OpenAI Chat protocol — request body construction, body schema, and the
* streaming-event state machine. Reused by every route that speaks OpenAI Chat
* over HTTP+SSE: native OpenAI, DeepSeek, TogetherAI, Cerebras, Baseten,
* Fireworks, DeepInfra, and (once added) Azure OpenAI Chat.
*/
export declare const protocol: Protocol<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, string, {
readonly error?: {
readonly message: string;
readonly code?: string | number | null | undefined;
} | null | undefined;
readonly usage?: {
readonly [x: string]: unknown;
readonly prompt_tokens?: number | null | undefined;
readonly completion_tokens?: number | null | undefined;
readonly total_tokens?: number | null | undefined;
readonly prompt_tokens_details?: {
readonly [x: string]: unknown;
readonly cached_tokens?: number | null | undefined;
readonly cache_write_tokens?: number | null | undefined;
} | null | undefined;
readonly completion_tokens_details?: {
readonly [x: string]: unknown;
readonly reasoning_tokens?: number | null | undefined;
readonly accepted_prediction_tokens?: number | null | undefined;
readonly rejected_prediction_tokens?: number | null | undefined;
} | null | undefined;
} | null | undefined;
readonly choices?: readonly {
readonly delta?: {
readonly [x: string]: unknown;
readonly reasoning?: string | null | undefined;
readonly reasoning_content?: string | null | undefined;
readonly reasoning_text?: string | null | undefined;
readonly content?: string | null | undefined;
readonly tool_calls?: readonly {
readonly function?: {
readonly name?: string | null | undefined;
readonly arguments?: string | null | undefined;
} | null | undefined;
readonly id?: string | null | undefined;
readonly index?: number | null | undefined;
}[] | null | undefined;
readonly reasoning_details?: unknown;
} | null | undefined;
readonly finish_reason?: string | null | undefined;
readonly native_finish_reason?: string | null | undefined;
}[] | null | undefined;
}, ParserState>;
export declare const httpTransport: HttpTransport.HttpJsonTransport<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, string>;
export declare const route: Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, HttpTransport.HttpPrepared<string>>;
export * as OpenAIChat from "./openai-chat.js";
import { Effect, Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { Route } from "../route/client.js";
import { Auth } from "../route/auth.js";
import { Endpoint } from "../route/endpoint.js";
import { HttpTransport } from "../route/transport/index.js";
import { Protocol } from "../route/protocol.js";
import { AIError, LLMEvent, Usage, } from "../schema/index.js";
import { classifyProviderFailure } from "../provider-error.js";
import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
import { OpenAIOptions } from "./utils/openai-options.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { ToolSchemaProjection } from "./utils/tool-schema.js";
import { ToolStream } from "./utils/tool-stream.js";
const ADAPTER = "openai-chat";
const IMAGE_MIMES = new Set(ProviderShared.IMAGE_MIMES);
const RESERVED_REASONING_FIELDS = new Set(["role", "content", "tool_calls"]);
export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
export const PATH = "/chat/completions";
// =============================================================================
// Request Body Schema
// =============================================================================
// The body schema is the provider-native JSON body. `fromRequest` below builds
// this shape from the common `LLMRequest`, then `Route.make` validates and
// JSON-encodes it before transport.
const OpenAIChatCacheControl = Schema.Struct({
type: Schema.Literal("ephemeral"),
ttl: Schema.optional(Schema.String),
});
const OpenAIChatFunction = Schema.Struct({
name: Schema.String,
description: Schema.String,
parameters: JsonObject,
});
const OpenAIChatTool = Schema.Struct({
type: Schema.tag("function"),
function: OpenAIChatFunction,
cache_control: Schema.optional(OpenAIChatCacheControl),
});
const OpenAIChatAssistantToolCall = Schema.Struct({
id: Schema.String,
type: Schema.tag("function"),
function: Schema.Struct({
name: Schema.String,
arguments: Schema.String,
}),
});
// Intentionally omit Gemini's provider-specific `extra_content.google.thought_signature`
// extension until direct Google OpenAI-compatible routing is supported here:
// https://github.com/vercel/ai/issues/11590
// https://github.com/vercel/ai/pull/11745
// https://ai.google.dev/gemini-api/docs/thought-signatures#openai
const OpenAIChatUserContent = Schema.Union([
Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
cache_control: Schema.optional(OpenAIChatCacheControl),
}),
Schema.Struct({
type: Schema.Literal("image_url"),
image_url: Schema.Struct({ url: Schema.String }),
}),
]);
const OpenAIChatMessage = Schema.Union([
Schema.Struct({
role: Schema.Literal("system"),
content: Schema.Union([Schema.String, Schema.Array(OpenAIChatUserContent)]),
}),
Schema.Struct({
role: Schema.Literal("user"),
content: Schema.Union([Schema.String, Schema.Array(OpenAIChatUserContent)]),
}),
Schema.StructWithRest(Schema.Struct({
role: Schema.Literal("assistant"),
content: Schema.NullOr(Schema.String),
tool_calls: optionalArray(OpenAIChatAssistantToolCall),
reasoning_content: Schema.optional(Schema.String),
reasoning: Schema.optional(Schema.String),
reasoning_text: Schema.optional(Schema.String),
reasoning_details: Schema.optional(Schema.Unknown),
cache_control: Schema.optional(OpenAIChatCacheControl),
}), [Schema.Record(Schema.String, Schema.Unknown)]),
Schema.Struct({
role: Schema.Literal("tool"),
tool_call_id: Schema.String,
content: Schema.String,
cache_control: Schema.optional(OpenAIChatCacheControl),
}),
]).pipe(Schema.toTaggedUnion("role"));
const OpenAIChatToolChoice = Schema.Union([
Schema.Literals(["auto", "none", "required"]),
Schema.Struct({
type: Schema.tag("function"),
function: Schema.Struct({ name: Schema.String }),
}),
]);
export const bodyFields = {
model: Schema.String,
messages: Schema.Array(OpenAIChatMessage),
tools: optionalArray(OpenAIChatTool),
tool_choice: Schema.optional(OpenAIChatToolChoice),
stream: Schema.Literal(true),
stream_options: Schema.optional(Schema.Struct({ include_usage: Schema.Boolean })),
store: Schema.optional(Schema.Boolean),
prompt_cache_key: Schema.optional(Schema.String),
reasoning_effort: Schema.optional(OpenAIOptions.OpenAIReasoningEffort),
max_completion_tokens: Schema.optional(Schema.Number),
max_tokens: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
top_p: Schema.optional(Schema.Number),
frequency_penalty: Schema.optional(Schema.Number),
presence_penalty: Schema.optional(Schema.Number),
seed: Schema.optional(Schema.Number),
stop: optionalArray(Schema.String),
};
const OpenAIChatBody = Schema.Struct(bodyFields);
// =============================================================================
// Streaming Event Schema
// =============================================================================
// The event schema is one decoded SSE `data:` payload. `Framing.sse` splits the
// byte stream into strings, then `Protocol.jsonEvent` decodes each string into
// this provider-native event shape.
const OpenAIChatUsage = Schema.StructWithRest(Schema.Struct({
prompt_tokens: optionalNull(Schema.Number),
completion_tokens: optionalNull(Schema.Number),
total_tokens: optionalNull(Schema.Number),
prompt_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({
cached_tokens: optionalNull(Schema.Number),
cache_write_tokens: optionalNull(Schema.Number),
}), [Schema.Record(Schema.String, Schema.Unknown)])),
completion_tokens_details: optionalNull(Schema.StructWithRest(Schema.Struct({
reasoning_tokens: optionalNull(Schema.Number),
accepted_prediction_tokens: optionalNull(Schema.Number),
rejected_prediction_tokens: optionalNull(Schema.Number),
}), [Schema.Record(Schema.String, Schema.Unknown)])),
}), [Schema.Record(Schema.String, Schema.Unknown)]);
const OpenAIChatToolCallDeltaFunction = Schema.Struct({
name: optionalNull(Schema.String),
arguments: optionalNull(Schema.String),
});
const OpenAIChatToolCallDelta = Schema.Struct({
index: optionalNull(Schema.Number),
id: optionalNull(Schema.String),
function: optionalNull(OpenAIChatToolCallDeltaFunction),
});
const OpenAIChatDelta = Schema.StructWithRest(Schema.Struct({
content: optionalNull(Schema.String),
reasoning_content: optionalNull(Schema.String),
reasoning: optionalNull(Schema.String),
reasoning_text: optionalNull(Schema.String),
reasoning_details: optionalNull(Schema.Unknown),
tool_calls: optionalNull(Schema.Array(OpenAIChatToolCallDelta)),
}), [Schema.Record(Schema.String, Schema.Unknown)]);
const OpenAIChatChoice = Schema.Struct({
delta: optionalNull(OpenAIChatDelta),
finish_reason: optionalNull(Schema.String),
native_finish_reason: optionalNull(Schema.String),
});
const OpenAIChatError = Schema.Struct({
code: optionalNull(Schema.Union([Schema.String, Schema.Number])),
message: Schema.String,
});
export const OpenAIChatEvent = Schema.Struct({
choices: optionalNull(Schema.Array(OpenAIChatChoice)),
usage: optionalNull(OpenAIChatUsage),
error: optionalNull(OpenAIChatError),
});
const lowerTool = (tool, inputSchema, options) => ({
type: "function",
function: {
name: tool.name,
description: tool.description,
parameters: ToolSchemaProjection.openAI(inputSchema),
},
cache_control: options.cacheControl?.(tool.cache),
});
const lowerToolChoice = (toolChoice) => ProviderShared.matchToolChoice("OpenAI Chat", toolChoice, {
auto: () => "auto",
none: () => "none",
required: () => "required",
tool: (name) => ({ type: "function", function: { name } }),
});
const lowerToolCall = (part) => ({
id: part.id,
type: "function",
function: {
name: part.name,
arguments: ProviderShared.encodeJson(part.input),
},
});
const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part) {
const media = yield* ProviderShared.validateMedia("OpenAI Chat", part, IMAGE_MIMES);
return { type: "image_url", image_url: { url: media.dataUrl } };
});
const openAICompatibleReasoningContent = (native) => isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined;
const reasoningField = (part) => {
const field = part.providerMetadata?.openai?.reasoningField;
return typeof field === "string" ? field : undefined;
};
const reasoningDetails = (parts, native) => {
const observed = parts.flatMap((part) => {
const details = part.providerMetadata?.openai?.reasoningDetails;
return Array.isArray(details) ? details : [];
});
if (parts.some((part) => Array.isArray(part.providerMetadata?.openai?.reasoningDetails)))
return observed;
if (isRecord(native) && Array.isArray(native.reasoning_details))
return native.reasoning_details;
};
const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message, options) {
const content = [];
for (const part of message.content) {
if (part.type === "text") {
content.push({ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) });
continue;
}
if (part.type === "media") {
content.push(yield* lowerMedia(part));
continue;
}
return yield* ProviderShared.unsupportedContent("OpenAI Chat", "user", ["text", "media"]);
}
if (content.every((part) => part.type === "text" && part.cache_control === undefined))
return {
role: "user",
content: content.map((part) => (part.type === "text" ? part.text : "")).join(""),
};
return { role: "user", content };
});
const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(function* (message, configuredField, options = {}) {
const content = [];
const reasoning = [];
const toolCalls = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
return yield* ProviderShared.unsupportedContent("OpenAI Chat", "assistant", ["text", "reasoning", "tool-call"]);
if (part.type === "text") {
content.push(part);
continue;
}
if (part.type === "reasoning") {
reasoning.push(part);
continue;
}
if (part.type === "tool-call") {
toolCalls.push(lowerToolCall(part));
continue;
}
}
const text = reasoning.map((part) => part.text).join("");
const details = reasoningDetails(reasoning, message.native?.openaiCompatible);
const observedField = reasoning.map(reasoningField).find((value) => value !== undefined);
const nativeReasoning = openAICompatibleReasoningContent(message.native?.openaiCompatible);
const fullyStructured = reasoning.every((part) => Array.isArray(part.providerMetadata?.openai?.reasoningDetails));
const field = (() => {
if (configuredField !== undefined)
return configuredField;
if (reasoning.length === 0)
return undefined;
if (observedField !== undefined)
return observedField;
if (nativeReasoning !== undefined)
return "reasoning_content";
if (!fullyStructured)
return "reasoning_content";
})();
const reasoningText = (() => {
if (configuredField !== undefined)
return reasoning.length === 0 ? (nativeReasoning ?? "") : text;
if (reasoning.length === 0)
return nativeReasoning;
return text;
})();
const cached = message.content.findLast((part) => "cache" in part && part.cache !== undefined);
const cacheControl = options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined);
const result = {
role: "assistant",
content: content.length > 0 ? content.map((part) => part.text).join("") : toolCalls.length > 0 ? null : "",
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
...(details !== undefined ? { reasoning_details: details } : {}),
...(cacheControl !== undefined ? { cache_control: cacheControl } : {}),
};
if (field === undefined || reasoningText === undefined)
return result;
return { ...result, [field]: reasoningText };
});
const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (message, options) {
const messages = [];
const images = [];
for (const part of message.content) {
if (!ProviderShared.supportsContent(part, ["tool-result"]))
return yield* ProviderShared.unsupportedContent("OpenAI Chat", "tool", ["tool-result"]);
if (part.result.type !== "content") {
messages.push({
role: "tool",
tool_call_id: part.id,
content: ProviderShared.toolResultText(part),
cache_control: options.cacheControl?.(part.cache),
});
continue;
}
const content = part.result.value;
const text = content.filter((item) => item.type === "text").map((item) => item.text);
messages.push({
role: "tool",
tool_call_id: part.id,
content: text.join("\n"),
cache_control: options.cacheControl?.(part.cache),
});
const files = content.filter((item) => item.type === "file");
images.push(...(yield* Effect.forEach(files, (item) => lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name }))));
}
return { messages, images };
});
const lowerMessage = Effect.fn("OpenAIChat.lowerMessage")(function* (message, reasoningField, options = {}) {
if (message.role === "user")
return [yield* lowerUserMessage(message, options)];
if (message.role === "assistant")
return [yield* lowerAssistantMessage(message, reasoningField, options)];
return (yield* lowerToolMessages(message, options)).messages;
});
const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request, options) {
const system = request.system.length === 0
? []
: request.system.some((part) => part.cache !== undefined) && options.cacheControl !== undefined
? [
{
role: "system",
content: request.system.map((part) => ({
type: "text",
text: part.text,
cache_control: options.cacheControl?.(part.cache),
})),
},
]
: [{ role: "system", content: ProviderShared.joinText(request.system) }];
const messages = [...system];
const pendingImages = [];
const flushImages = () => {
if (pendingImages.length === 0)
return;
messages.push({ role: "user", content: pendingImages.splice(0) });
};
for (const message of request.messages) {
if (message.role === "system") {
const part = yield* ProviderShared.wrappedSystemUpdate("OpenAI Chat", message);
if (pendingImages.length > 0) {
messages.push({
role: "user",
content: [
...pendingImages.splice(0),
{ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) },
],
});
continue;
}
const previous = messages.at(-1);
if (previous?.role === "user" && typeof previous.content === "string")
messages[messages.length - 1] = options.cacheControl?.(part.cache)
? {
role: "user",
content: [
{ type: "text", text: previous.content },
{ type: "text", text: part.text, cache_control: options.cacheControl(part.cache) },
],
}
: { role: "user", content: `${previous.content}\n${part.text}` };
else if (previous?.role === "user" && Array.isArray(previous.content))
messages[messages.length - 1] = {
role: "user",
content: [
...previous.content,
{ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) },
],
};
else
messages.push(options.cacheControl?.(part.cache)
? {
role: "user",
content: [{ type: "text", text: part.text, cache_control: options.cacheControl(part.cache) }],
}
: { role: "user", content: part.text });
continue;
}
if (message.role === "tool") {
const lowered = yield* lowerToolMessages(message, options);
messages.push(...lowered.messages);
pendingImages.push(...lowered.images);
continue;
}
flushImages();
messages.push(...(yield* lowerMessage(message, request.model.compatibility?.reasoningField, options)));
}
flushImages();
return messages;
});
const lowerOptions = (request) => {
const options = OpenAIOptions.resolve(request);
return {
...(options.store !== undefined ? { store: options.store } : {}),
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
...(options.reasoningEffort ? { reasoning_effort: options.reasoningEffort } : {}),
};
};
export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (request, options = {}) {
// `fromRequest` returns the provider body only. Endpoint, auth, framing,
// validation, and HTTP execution are composed by `Route.make`.
const reasoningField = request.model.compatibility?.reasoningField;
if (reasoningField && RESERVED_REASONING_FIELDS.has(reasoningField))
return yield* ProviderShared.invalidRequest(`OpenAI Chat reasoning field conflicts with reserved field ${reasoningField}`);
const generation = request.generation;
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
const maxTokensField = request.model.compatibility?.maxTokensField ?? "max_tokens";
return {
model: request.model.id,
messages: yield* lowerMessages(request, options),
tools: request.tools.length === 0
? undefined
: request.tools.map((tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility), options)),
tool_choice: request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined,
stream: true,
stream_options: { include_usage: true },
...(maxTokensField === "max_completion_tokens"
? { max_completion_tokens: generation?.maxTokens }
: { max_tokens: generation?.maxTokens }),
temperature: generation?.temperature,
top_p: generation?.topP,
frequency_penalty: generation?.frequencyPenalty,
presence_penalty: generation?.presencePenalty,
seed: generation?.seed,
stop: generation?.stop,
...lowerOptions(request),
};
});
// =============================================================================
// Stream Parsing
// =============================================================================
// Streaming parsers are small state machines: every event returns a new state
// plus the common `LLMEvent`s produced by that event. Tool calls are accumulated
// because OpenAI streams JSON arguments across multiple deltas.
const mapFinishReason = (reason) => {
if (reason === "stop")
return "stop";
if (reason === "length")
return "length";
if (reason === "content_filter")
return "content-filter";
if (reason === "function_call" || reason === "tool_calls")
return "tool-calls";
if (reason === "error")
return "error";
return "unknown";
};
// OpenAI Chat reports `prompt_tokens` (inclusive total) with a
// cached-read and cache-write subsets, and `completion_tokens` (inclusive
// total) with a `reasoning_tokens` subset. We pass the inclusive totals
// through and derive the non-cached breakdown so the `AI.Usage` contract is
// satisfied on both sides.
const mapUsage = (usage) => {
if (!usage)
return undefined;
const input = usage.prompt_tokens ?? undefined;
const output = usage.completion_tokens ?? undefined;
const cached = usage.prompt_tokens_details?.cached_tokens ?? undefined;
const cacheWrite = usage.prompt_tokens_details?.cache_write_tokens ?? undefined;
const reasoning = usage.completion_tokens_details?.reasoning_tokens ?? undefined;
const nonCached = ProviderShared.subtractTokens(input, ProviderShared.sumTokens(cached, cacheWrite));
return new Usage({
inputTokens: input,
outputTokens: output,
nonCachedInputTokens: nonCached,
cacheReadInputTokens: cached,
cacheWriteInputTokens: cacheWrite,
reasoningTokens: reasoning,
totalTokens: ProviderShared.totalTokens(input, output, usage.total_tokens ?? undefined),
providerMetadata: { openai: usage },
});
};
const toolIndexByID = (tools, pendingTools, id) => {
if (!id)
return undefined;
const entry = Object.entries({ ...pendingTools, ...tools }).find(([, tool]) => tool?.id === id);
return entry ? Number(entry[0]) : undefined;
};
const reasoningDelta = (delta, configuredField) => {
if (!delta)
return undefined;
const fields = new Set([configuredField, "reasoning_content", "reasoning", "reasoning_text"]);
for (const field of fields) {
if (field === undefined)
continue;
const text = delta[field];
if (typeof text === "string" && text.length > 0)
return { field, text };
}
return undefined;
};
const detailText = (details) => {
const text = details.flatMap((detail) => {
if (!isRecord(detail))
return [];
if (detail.type === "reasoning.text" && typeof detail.text === "string" && detail.text)
return [detail.text];
if (detail.type === "reasoning.summary" && typeof detail.summary === "string" && detail.summary)
return [detail.summary];
return [];
});
if (text.length > 0)
return text.join("");
};
const appendReasoningDetails = (result, details) => {
for (const detail of details) {
const previous = result.at(-1);
if (!isRecord(previous) ||
previous.type !== "reasoning.text" ||
!isRecord(detail) ||
detail.type !== "reasoning.text" ||
conflictingReasoningTextDetails(previous, detail)) {
result.push(detail);
continue;
}
result[result.length - 1] = {
...previous,
...Object.fromEntries(Object.entries(detail).filter((entry) => entry[1] !== undefined)),
text: `${typeof previous.text === "string" ? previous.text : ""}${typeof detail.text === "string" ? detail.text : ""}`,
signature: mergeDetailValue(previous.signature, detail.signature),
format: mergeDetailValue(previous.format, detail.format),
};
}
};
const mergeDetailValue = (previous, current) => previous || current || (previous !== undefined ? previous : current);
const conflictingReasoningTextDetails = (previous, current) => conflictingDetailValue(previous.id, current.id) ||
conflictingDetailValue(previous.index, current.index) ||
conflictingDetailValue(previous.format, current.format) ||
(Boolean(previous.signature) && Boolean(current.signature) && previous.signature !== current.signature);
const conflictingDetailValue = (previous, current) => previous !== undefined && previous !== null && current !== undefined && current !== null && previous !== current;
const reasoningMetadata = (field, details) => ({
openai: {
...(field ? { reasoningField: field } : {}),
...(details ? { reasoningDetails: details } : {}),
},
});
const step = (state, event) => Effect.gen(function* () {
if (event.error)
return yield* new AIError({
module: ADAPTER,
method: "stream",
reason: classifyProviderFailure({
message: event.error.message,
code: event.error.code === undefined || event.error.code === null ? undefined : String(event.error.code),
status: typeof event.error.code === "number" ? event.error.code : undefined,
}),
});
const events = [];
const usage = mapUsage(event.usage) ?? state.usage;
const choice = event.choices?.[0];
const rawFinishReason = choice?.finish_reason;
const finishReason = rawFinishReason !== undefined && rawFinishReason !== null
? { normalized: mapFinishReason(rawFinishReason), raw: choice?.native_finish_reason ?? rawFinishReason }
: state.finishReason;
const delta = choice?.delta;
const toolDeltas = delta?.tool_calls ?? [];
let tools = state.tools;
let pendingTools = state.pendingTools;
let latestToolIndex = state.latestToolIndex;
let nextToolIndex = state.nextToolIndex;
let lifecycle = state.lifecycle;
const reasoning = reasoningDelta(delta, state.reasoningField);
const hasLateContent = Boolean(delta?.content) ||
reasoning !== undefined ||
(Array.isArray(delta?.reasoning_details) && delta.reasoning_details.length > 0) ||
toolDeltas.some((tool) => Boolean(tool.id) || Boolean(tool.function?.name) || Boolean(tool.function?.arguments));
if (state.finishReason !== undefined) {
if (hasLateContent)
return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason");
return [{ ...state, usage }, events];
}
const reasoningField = state.reasoningField ?? reasoning?.field;
const detailDelta = Array.isArray(delta?.reasoning_details) ? delta.reasoning_details : undefined;
if (detailDelta !== undefined)
appendReasoningDetails(state.reasoningDetails, detailDelta);
const reasoningDetailsObserved = state.reasoningDetailsObserved || detailDelta !== undefined;
const deltaMetadata = reasoningMetadata(reasoningField);
const text = detailDelta?.length ? (detailText(detailDelta) ?? reasoning?.text) : reasoning?.text;
if (text !== undefined)
lifecycle = Lifecycle.reasoningDelta(lifecycle, events, "reasoning-0", text, deltaMetadata);
else if (reasoningDetailsObserved &&
!lifecycle.reasoning.has("reasoning-0") &&
(Boolean(delta?.content) || toolDeltas.length > 0))
lifecycle = Lifecycle.reasoningStart(lifecycle, events, "reasoning-0", deltaMetadata);
const reasoningEmitted = state.reasoningEmitted || lifecycle.reasoning.has("reasoning-0");
if (delta?.content) {
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, "reasoning-0", reasoningMetadata(reasoningField, reasoningDetailsObserved ? state.reasoningDetails : undefined));
lifecycle = Lifecycle.textDelta(lifecycle, events, "text-0", delta.content);
}
// Compatible providers may omit indexes. Prefer durable identity, then use
// batch position for parallel deltas or the latest call for sparse chunks.
for (const [position, tool] of toolDeltas.entries()) {
const matched = toolIndexByID(tools, pendingTools, tool.id || undefined);
const fallback = toolDeltas.length > 1 ? position : (latestToolIndex ?? position);
const fallbackTool = tools[fallback] ?? pendingTools[fallback];
const index = tool.index ?? matched ?? (tool.id && fallbackTool?.id && fallbackTool.id !== tool.id ? nextToolIndex : fallback);
const current = tools[index];
const pending = pendingTools[index];
const id = current?.id ?? pending?.id ?? (tool.id || undefined);
const name = current?.name ?? pending?.name ?? (tool.function?.name || undefined);
const text = `${pending?.input ?? ""}${tool.function?.arguments ?? ""}`;
latestToolIndex = index;
nextToolIndex = Math.max(nextToolIndex, index + 1);
if (!current && (!id || !name)) {
pendingTools = {
...pendingTools,
[index]: { id: id || undefined, name: name || undefined, input: text },
};
continue;
}
if (pending) {
pendingTools = { ...pendingTools };
delete pendingTools[index];
}
const result = ToolStream.appendOrStart(ADAPTER, tools, index, { id: id || undefined, name: name || undefined, text }, "OpenAI Chat tool call delta is missing id or name");
if (ToolStream.isError(result))
return yield* result;
tools = result.tools;
if (result.events.length)
lifecycle = Lifecycle.stepStart(lifecycle, events);
events.push(...result.events);
}
if (finishReason !== undefined && state.finishReason === undefined && Object.keys(pendingTools).length > 0)
return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat tool call delta is missing id or name");
// Finalize accumulated tool inputs eagerly when finish_reason arrives so
// valid calls and malformed local calls settle independently.
const finished = finishReason !== undefined && state.finishReason === undefined && Object.keys(tools).length > 0
? yield* ToolStream.finishAll(ADAPTER, tools)
: undefined;
return [
{
tools: finished?.tools ?? tools,
pendingTools,
toolCallEvents: finished?.events ?? state.toolCallEvents,
usage,
finishReason,
lifecycle,
reasoningField,
reasoningDetails: state.reasoningDetails,
reasoningDetailsObserved,
reasoningEmitted,
latestToolIndex,
nextToolIndex,
},
events,
];
});
const finishEvents = (state) => {
const events = [];
const toolCallEvents = state.finishReason === undefined && Object.keys(state.tools).length > 0
? Effect.runSync(ToolStream.finishAll(ADAPTER, state.tools)).events
: state.toolCallEvents;
const hasToolCalls = toolCallEvents.length > 0;
const reason = state.finishReason
? {
...state.finishReason,
normalized: state.finishReason.normalized === "stop" && hasToolCalls ? "tool-calls" : state.finishReason.normalized,
}
: { normalized: hasToolCalls ? "tool-calls" : "unknown" };
const metadata = reasoningMetadata(state.reasoningField, state.reasoningDetailsObserved ? state.reasoningDetails : undefined);
const started = state.reasoningDetailsObserved && !state.reasoningEmitted
? Lifecycle.reasoningStart(state.lifecycle, events, "reasoning-0", reasoningMetadata(state.reasoningField))
: state.lifecycle;
const ended = Lifecycle.reasoningEnd(started, events, "reasoning-0", metadata);
const lifecycle = toolCallEvents.length ? Lifecycle.stepStart(ended, events) : ended;
events.push(...toolCallEvents);
Lifecycle.finish(lifecycle, events, { reason, usage: state.usage });
return events;
};
// =============================================================================
// Protocol And OpenAI Route
// =============================================================================
/**
* The OpenAI Chat protocol — request body construction, body schema, and the
* streaming-event state machine. Reused by every route that speaks OpenAI Chat
* over HTTP+SSE: native OpenAI, DeepSeek, TogetherAI, Cerebras, Baseten,
* Fireworks, DeepInfra, and (once added) Azure OpenAI Chat.
*/
export const protocol = Protocol.make({
id: ADAPTER,
body: {
schema: OpenAIChatBody,
from: fromRequest,
},
stream: {
event: Protocol.jsonEvent(OpenAIChatEvent),
initial: (request) => ({
tools: ToolStream.empty(),
pendingTools: {},
toolCallEvents: [],
lifecycle: Lifecycle.initial(),
reasoningField: request.model.compatibility?.reasoningField,
reasoningDetails: [],
reasoningDetailsObserved: false,
reasoningEmitted: false,
nextToolIndex: 0,
}),
step,
onHalt: finishEvents,
},
});
export const httpTransport = HttpTransport.sseJson.with();
export const route = Route.make({
id: ADAPTER,
provider: "openai",
providerMetadataKey: "openai",
protocol,
endpoint: Endpoint.path(PATH, { baseURL: DEFAULT_BASE_URL }),
auth: Auth.none,
transport: httpTransport,
});
export * as OpenAIChat from "./openai-chat.js";
import { Route, type RouteRoutedLanguageModelInput } from "../route/client.js";
export type OpenAICompatibleChatLanguageModelInput = RouteRoutedLanguageModelInput;
/**
* Route for non-OpenAI providers that expose an OpenAI Chat-compatible
* `/chat/completions` endpoint. Reuses `OpenAIChat.protocol` end-to-end and
* overrides only the route id so providers can be resolved per-family without
* colliding with native OpenAI. Provider helpers configure the route endpoint
* before model selection.
*/
export declare const route: Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>;
export * as OpenAICompatibleChat from "./openai-compatible-chat.js";
import { Route } from "../route/client.js";
import { Endpoint } from "../route/endpoint.js";
import { Framing } from "../route/framing.js";
import * as OpenAIChat from "./openai-chat.js";
const ADAPTER = "openai-compatible-chat";
/**
* Route for non-OpenAI providers that expose an OpenAI Chat-compatible
* `/chat/completions` endpoint. Reuses `OpenAIChat.protocol` end-to-end and
* overrides only the route id so providers can be resolved per-family without
* colliding with native OpenAI. Provider helpers configure the route endpoint
* before model selection.
*/
export const route = Route.make({
id: ADAPTER,
providerMetadataKey: "openai",
protocol: OpenAIChat.protocol,
endpoint: Endpoint.path("/chat/completions"),
framing: Framing.sse,
});
export * as OpenAICompatibleChat from "./openai-compatible-chat.js";
import { Route, type RouteRoutedLanguageModelInput } from "../route/client.js";
export type OpenAICompatibleResponsesLanguageModelInput = RouteRoutedLanguageModelInput;
/**
* Deployment adapter for providers that expose an Open Responses-compatible
* `/responses` endpoint. Provider helpers configure identity, endpoint, and
* auth while the semantic protocol remains provider-neutral.
*/
export declare const route: Route<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly {
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
}[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>;
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
import { Route } from "../route/client.js";
import { Endpoint } from "../route/endpoint.js";
import { OpenResponses } from "./open-responses.js";
const ADAPTER = "openai-compatible-responses";
/**
* Deployment adapter for providers that expose an Open Responses-compatible
* `/responses` endpoint. Provider helpers configure identity, endpoint, and
* auth while the semantic protocol remains provider-neutral.
*/
export const route = Route.make({
id: ADAPTER,
providerMetadataKey: "openresponses",
protocol: OpenResponses.protocol,
endpoint: Endpoint.path(OpenResponses.PATH),
transport: OpenResponses.httpTransport,
});
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
import { ImageModel, type ImageInput } from "../image.js";
import { type Definition as AuthDefinition } from "../route/auth.js";
import { type HttpOptions } from "../schema/index.js";
export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
export declare const PATH = "/images/generations";
export declare const EDIT_PATH = "/images/edits";
export type OpenAIImageString<Known extends string> = Known | (string & {});
export type OpenAIImageOptions = {
readonly mask?: ImageInput;
readonly n?: number;
readonly size?: OpenAIImageString<"auto" | "256x256" | "512x512" | "1024x1024" | "1536x1024" | "1024x1536" | "1792x1024" | "1024x1792">;
readonly quality?: OpenAIImageString<"auto" | "low" | "medium" | "high" | "standard" | "hd">;
readonly background?: OpenAIImageString<"auto" | "opaque" | "transparent">;
readonly moderation?: OpenAIImageString<"auto" | "low">;
readonly outputFormat?: OpenAIImageString<"png" | "jpeg" | "webp">;
readonly outputCompression?: number;
} & Record<string, unknown>;
export type OpenAIImageBody = Record<string, unknown> & {
readonly model: string;
readonly prompt: string;
};
export interface ModelInput {
readonly id: string;
readonly auth: AuthDefinition;
readonly baseURL?: string;
readonly headers?: Record<string, string>;
readonly http?: HttpOptions;
}
export declare const model: (input: ModelInput) => ImageModel<OpenAIImageOptions>;
export declare const OpenAIImages: {
readonly model: (input: ModelInput) => ImageModel<OpenAIImageOptions>;
};
import { Effect, Encoding, Schema } from "effect";
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
import { ImageModel, GeneratedImage, ImageResponse, } from "../image.js";
import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, Usage, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { ProviderShared } from "./shared.js";
import { ImageInputs } from "./utils/image-input.js";
import { OpenAIImage } from "./utils/openai-image.js";
const ADAPTER = "openai-images";
export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
export const PATH = "/images/generations";
export const EDIT_PATH = "/images/edits";
const OpenAIImageResponse = Schema.Struct({
data: Schema.Array(Schema.Struct({
b64_json: Schema.optional(Schema.String),
url: Schema.optional(Schema.String),
revised_prompt: Schema.optional(Schema.String),
})),
output_format: Schema.optional(Schema.String),
usage: Schema.optional(Schema.Struct({
input_tokens: Schema.optional(Schema.Number),
output_tokens: Schema.optional(Schema.Number),
total_tokens: Schema.optional(Schema.Number),
input_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
output_tokens_details: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
})),
});
const nativeOptions = (options) => {
if (!options)
return undefined;
const { mask: _, outputFormat, outputCompression, ...native } = options;
return {
output_format: outputFormat,
output_compression: outputCompression,
...native,
};
};
const invalidOutput = (message) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
});
const applyQuery = (url, query) => {
if (!query)
return url;
const next = new URL(url);
Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
return next.toString();
};
export const model = (input) => {
const route = {
id: ADAPTER,
generate: Effect.fn("OpenAIImages.generate")(function* (request, execute) {
const mask = request.options?.mask;
if (mask !== undefined && (request.images?.length ?? 0) === 0)
return yield* ImageInputs.invalid(ADAPTER, "An OpenAI image mask requires at least one input image");
const http = mergeHttpOptions(request.model.http, request.http);
const sourceImages = request.images ?? [];
const multipartImages = yield* Effect.forEach(sourceImages, (image) => {
if (image.type === "bytes")
return Effect.succeed({ data: image.data, mediaType: image.mediaType });
if (image.type === "url")
return ImageInputs.decodeDataUrl(image.url, ADAPTER);
return Effect.succeed(undefined);
});
const multipartMask = mask === undefined
? undefined
: mask.type === "bytes"
? { data: mask.data, mediaType: mask.mediaType }
: mask.type === "url"
? yield* ImageInputs.decodeDataUrl(mask.url, ADAPTER)
: undefined;
const useMultipart = sourceImages.length > 0 &&
multipartImages.every((image) => image !== undefined) &&
(mask === undefined || multipartMask !== undefined);
const path = sourceImages.length === 0 ? PATH : EDIT_PATH;
const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${path}`, http?.query);
if (useMultipart) {
const form = new FormData();
form.append("model", request.model.id);
form.append("prompt", request.prompt);
Object.entries(mergeJsonRecords(nativeOptions(request.options), http?.body) ?? {}).forEach(([key, value]) => {
if (["model", "prompt", "image", "image[]", "images", "mask"].includes(key))
return;
form.append(key, typeof value === "string" ? value : ProviderShared.encodeJson(value));
});
multipartImages.forEach((image, index) => {
if (image === undefined)
return;
form.append("image[]", imageBlob(image.data, image.mediaType), `image-${index}`);
});
if (multipartMask !== undefined)
form.append("mask", imageBlob(multipartMask.data, multipartMask.mediaType), "mask");
const headers = yield* Auth.toEffect(input.auth)({
request,
method: "POST",
url,
body: "[multipart/form-data]",
headers: Headers.remove(Headers.fromInput({ ...input.headers, ...http?.headers }), "content-type"),
});
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyFormData(form)));
return yield* parseResponse(response, request.options, http?.body);
}
const references = sourceImages.map((image) => {
if (image.type === "bytes")
return { image_url: ImageInputs.dataUrl(image) };
if (image.type === "url")
return { image_url: image.url };
if (image.type === "file-id")
return { file_id: image.id };
return undefined;
});
if (references.some((image) => image === undefined))
return yield* ImageInputs.invalid(ADAPTER, "OpenAI Images accepts image URLs, data URLs, bytes, and file IDs");
const maskReference = mask === undefined
? undefined
: mask.type === "bytes"
? { image_url: ImageInputs.dataUrl(mask) }
: mask.type === "url"
? { image_url: mask.url }
: mask.type === "file-id"
? { file_id: mask.id }
: undefined;
if (mask !== undefined && maskReference === undefined)
return yield* ImageInputs.invalid(ADAPTER, "OpenAI Images accepts masks as URLs, data URLs, bytes, or file IDs");
const requestBody = mergeJsonRecords({
model: request.model.id,
prompt: request.prompt,
images: references.length === 0 ? undefined : references,
mask: maskReference,
}, nativeOptions(request.options), http?.body);
const text = ProviderShared.encodeJson(requestBody);
const headers = yield* Auth.toEffect(input.auth)({
request,
method: "POST",
url,
body: text,
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
});
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
return yield* parseResponse(response, request.options, http?.body);
}),
};
return ImageModel.make({ id: input.id, provider: "openai", route, http: input.http });
};
const parseResponse = Effect.fn("OpenAIImages.parseResponse")(function* (response, options, overlay) {
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the OpenAI Images response")));
const decoded = yield* Schema.decodeUnknownEffect(OpenAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("OpenAI Images returned an invalid response")));
const requestBody = mergeJsonRecords(nativeOptions(options), overlay);
const format = decoded.output_format ?? (typeof requestBody?.output_format === "string" ? requestBody.output_format : "png");
const images = yield* Effect.forEach(decoded.data, (item, index) => {
if (item.b64_json)
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() => invalidOutput(`OpenAI Images result ${index} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
mediaType: `image/${format}`,
data,
providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
})));
if (item.url)
return Effect.succeed(new GeneratedImage({
mediaType: `image/${format}`,
data: item.url,
providerMetadata: item.revised_prompt === undefined ? undefined : { openai: { revisedPrompt: item.revised_prompt } },
}));
return Effect.fail(invalidOutput(`OpenAI Images result ${index} has neither image data nor a URL`));
});
if (images.length === 0)
return yield* invalidOutput("OpenAI Images returned no images");
return new ImageResponse({
images,
usage: decoded.usage === undefined
? undefined
: new Usage({
inputTokens: decoded.usage.input_tokens,
outputTokens: decoded.usage.output_tokens,
totalTokens: decoded.usage.total_tokens,
providerMetadata: { openai: decoded.usage },
}),
providerMetadata: { openai: { outputFormat: format } },
});
});
const imageBlob = (data, mediaType) => {
const buffer = new ArrayBuffer(data.byteLength);
new Uint8Array(buffer).set(data);
return new Blob([buffer], { type: mediaType });
};
export const OpenAIImages = {
model,
};
import type { WebSocketChannelDriver } from "../route/transport/index.js";
export interface DriverInput {
readonly id: string;
readonly name: string;
readonly request: Readonly<Record<string, unknown>>;
readonly message: string;
readonly base: WebSocketChannelDriver;
}
export declare const driver: (input: DriverInput) => WebSocketChannelDriver;
export declare const OpenAIResponsesChannel: {
readonly driver: (input: DriverInput) => WebSocketChannelDriver;
};
import { AIError, TransportReason } from "../schema/index.js";
import { Effect, Option, Schema } from "effect";
import * as ProviderShared from "./shared.js";
import { OpenResponses } from "./open-responses.js";
const PROTOCOL = "openai-responses.websocket.v1";
const VERSION = 1;
const decodeEvent = Schema.decodeUnknownEffect(OpenResponses.protocol.stream.event);
const checkpointValue = (checkpoint) => {
if (checkpoint?.protocol !== PROTOCOL || !ProviderShared.isRecord(checkpoint.value))
return undefined;
if (checkpoint.value.version !== VERSION)
return undefined;
if (typeof checkpoint.value.responseID !== "string" || checkpoint.value.responseID.trim().length === 0)
return undefined;
if (!ProviderShared.isRecord(checkpoint.value.request) || !Array.isArray(checkpoint.value.output))
return undefined;
return {
version: VERSION,
responseID: checkpoint.value.responseID,
request: checkpoint.value.request,
output: checkpoint.value.output,
};
};
const canonical = (value) => {
if (value === undefined)
return "undefined";
if (Array.isArray(value))
return `[${value.map(canonical).join(",")}]`;
if (!ProviderShared.isRecord(value))
return ProviderShared.encodeJson(value);
return `{${Object.keys(value)
.sort()
.map((key) => `${ProviderShared.encodeJson(key)}:${canonical(value[key])}`)
.join(",")}}`;
};
const json = (value) => {
if (typeof value !== "string")
return value;
return Option.getOrElse(Schema.decodeUnknownOption(ProviderShared.Json)(value), () => value);
};
const comparable = (value) => {
if (!ProviderShared.isRecord(value))
return value;
if (value.type === "message" && value.role === "assistant")
return {
role: "assistant",
content: value.content,
...(value.phase === undefined ? {} : { phase: value.phase }),
};
if (value.type === "function_call")
return {
type: value.type,
call_id: value.call_id,
name: value.name,
arguments: json(value.arguments),
};
if (value.type === "reasoning")
return {
type: value.type,
summary: value.summary,
encrypted_content: value.encrypted_content,
};
return value;
};
const invariant = (request) => {
const { type: _type, input: _input, previous_response_id: _previousResponseID, ...rest } = request;
return rest;
};
const incremental = (request, checkpoint) => {
const input = request.input;
const previousInput = checkpoint.request.input;
if (!Array.isArray(input) || !Array.isArray(previousInput))
return undefined;
if (canonical(invariant(request)) !== canonical(invariant(checkpoint.request)))
return undefined;
const baseline = [...previousInput, ...checkpoint.output];
if (input.length <= baseline.length)
return undefined;
if (!baseline.every((item, index) => canonical(comparable(item)) === canonical(comparable(input[index]))))
return undefined;
return input.slice(baseline.length);
};
const code = (event) => event.code || event.error?.code || event.response?.error?.code || undefined;
const rejected = (input, observation, recovery) => ({
type: "rejected",
recovery,
error: new AIError({
module: input.id,
method: "stream",
reason: new TransportReason({
message: observation.error.message,
transport: "websocket",
operation: "read",
phase: "receive",
delivery: "rejected",
recovery,
}),
}),
});
export const driver = (input) => {
const { previous_response_id: _previousResponseID, ...request } = input.request;
let output = [];
return {
create: (checkpoint) => Effect.sync(() => {
output = [];
const previous = checkpointValue(checkpoint);
const delta = previous ? incremental(request, previous) : undefined;
if (!previous || !delta)
return { message: ProviderShared.encodeJson(request), mode: "full" };
return {
message: ProviderShared.encodeJson({ ...request, input: delta, previous_response_id: previous.responseID }),
mode: "incremental",
};
}),
observe: (create, frame) => Effect.gen(function* () {
const event = yield* decodeEvent(frame).pipe(Effect.mapError(() => ProviderShared.eventError(input.id, `Invalid ${input.name} WebSocket event`, frame)));
const observation = yield* input.base.observe(create, frame);
if (event.type === "response.output_item.done" && event.item)
output.push(event.item);
if (observation.type === "provider-failure") {
const rejection = code(event);
if (rejection === "previous_response_not_found")
return rejected(input, observation, "retry-full");
if (rejection === "websocket_connection_limit_reached")
return rejected(input, observation, "rotate-and-retry-full");
}
if (observation.type !== "completed")
return observation;
const responseID = event.response?.id;
if (!responseID || responseID.trim().length === 0)
return observation;
return {
...observation,
checkpoint: {
protocol: PROTOCOL,
value: { version: VERSION, responseID, request, output: output.slice() },
},
};
}),
};
};
export const OpenAIResponsesChannel = { driver };
import { Schema } from "effect";
import { Route } from "../route/client.js";
import { Protocol } from "../route/protocol.js";
import { HttpTransport } from "../route/transport/index.js";
import { OpenResponses } from "./open-responses.js";
export declare const DEFAULT_BASE_URL = "https://api.openai.com/v1";
export declare const PATH = "/responses";
declare const OpenAIResponsesBody: Schema.Struct<{
readonly stream: Schema.Literal<true>;
readonly input: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly role: Schema.tag<"assistant">;
readonly content: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"output_text">;
readonly text: Schema.String;
}>>;
readonly phase: Schema.optionalKey<Schema.NullOr<Schema.Literals<readonly ["commentary", "final_answer"]>>>;
}>, Schema.Union<readonly [Schema.Struct<{
readonly role: Schema.tag<"system">;
readonly content: Schema.String;
}>, Schema.Struct<{
readonly role: Schema.tag<"user">;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>]>>;
}>, Schema.Struct<{
readonly role: Schema.tag<"assistant">;
readonly content: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"output_text">;
readonly text: Schema.String;
}>>;
readonly phase: Schema.optionalKey<Schema.Literals<readonly ["commentary", "final_answer"]>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"reasoning">;
readonly id: Schema.optionalKey<Schema.String>;
readonly summary: Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"summary_text">;
readonly text: Schema.String;
}>>;
readonly encrypted_content: Schema.optional<Schema.NullOr<Schema.String>>;
}>, Schema.Struct<{
readonly type: Schema.tag<"item_reference">;
readonly id: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call">;
readonly call_id: Schema.String;
readonly name: Schema.String;
readonly arguments: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"function_call_output">;
readonly call_id: Schema.String;
readonly output: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"input_text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_image">;
readonly image_url: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.tag<"input_file">;
readonly filename: Schema.String;
readonly file_data: Schema.String;
readonly mime_type: Schema.optional<Schema.String>;
}>]>>]>;
}>]>]>>;
readonly tools: Schema.optional<Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.$Record<Schema.String, Schema.Unknown>;
readonly strict: Schema.optional<Schema.Boolean>;
}>, Schema.Struct<{
readonly type: Schema.tag<"image_generation">;
readonly action: Schema.optional<Schema.Literals<readonly ["auto", "generate", "edit"]>>;
readonly background: Schema.optional<Schema.Literals<readonly ["auto", "opaque", "transparent"]>>;
readonly input_fidelity: Schema.optional<Schema.Literals<readonly ["low", "high"]>>;
readonly output_compression: Schema.optional<Schema.Int>;
readonly output_format: Schema.optional<Schema.Literals<readonly ["png", "jpeg", "webp"]>>;
readonly partial_images: Schema.optional<Schema.Int>;
readonly quality: Schema.optional<Schema.Literals<readonly ["auto", "low", "medium", "high"]>>;
readonly size: Schema.optional<Schema.String>;
}>]>>>;
readonly tool_choice: Schema.optional<Schema.Union<readonly [Schema.Union<readonly [Schema.Literals<readonly ["auto", "none", "required"]>, Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly name: Schema.String;
}>]>, Schema.Struct<{
readonly type: Schema.tag<"image_generation">;
}>]>>;
readonly model: Schema.String;
readonly instructions: Schema.optional<Schema.String>;
readonly store: Schema.optional<Schema.Boolean>;
readonly service_tier: Schema.optional<Schema.Literals<readonly ["auto", "default", "flex", "priority"]>>;
readonly prompt_cache_key: Schema.optional<Schema.String>;
readonly include: Schema.optional<Schema.$Array<Schema.Literals<readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"]>>>;
readonly reasoning: Schema.optional<Schema.Struct<{
readonly effort: Schema.optional<Schema.String>;
readonly summary: Schema.optional<Schema.Literals<readonly ["auto", "concise", "detailed"]>>;
}>>;
readonly text: Schema.optional<Schema.Struct<{
readonly verbosity: Schema.optional<Schema.Literals<readonly ["low", "medium", "high"]>>;
}>>;
readonly max_output_tokens: Schema.optional<Schema.Number>;
readonly temperature: Schema.optional<Schema.Number>;
readonly top_p: Schema.optional<Schema.Number>;
}>;
export type OpenAIResponsesBody = Schema.Schema.Type<typeof OpenAIResponsesBody>;
export declare const protocol: Protocol<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, string, {
readonly [x: string]: unknown;
readonly type: string;
readonly error?: {
readonly type?: string | null | undefined;
readonly code?: string | null | undefined;
readonly message?: string | null | undefined;
readonly param?: string | null | undefined;
} | null | undefined;
readonly status?: unknown;
readonly code?: string | null | undefined;
readonly message?: string | undefined;
readonly headers?: unknown;
readonly text?: string | undefined;
readonly item?: {
readonly [x: string]: unknown;
readonly type: string;
readonly id?: string | undefined;
readonly name?: string | undefined;
readonly arguments?: string | undefined;
readonly encrypted_content?: string | null | undefined;
readonly call_id?: string | undefined;
} | undefined;
readonly delta?: string | undefined;
readonly response?: {
readonly [x: string]: unknown;
readonly error?: {
readonly type?: string | null | undefined;
readonly code?: string | null | undefined;
readonly message?: string | null | undefined;
readonly param?: string | null | undefined;
} | null | undefined;
readonly id?: string | undefined;
readonly usage?: {
readonly input_tokens?: number | undefined;
readonly output_tokens?: number | undefined;
readonly output_tokens_details?: {
readonly reasoning_tokens?: number | undefined;
} | null | undefined;
readonly total_tokens?: number | undefined;
readonly input_tokens_details?: {
readonly cached_tokens?: number | undefined;
readonly cache_write_tokens?: number | undefined;
} | null | undefined;
} | null | undefined;
readonly service_tier?: string | null | undefined;
readonly incomplete_details?: {
readonly reason?: string | undefined;
} | null | undefined;
} | undefined;
readonly param?: string | null | undefined;
readonly status_code?: unknown;
readonly item_id?: string | undefined;
readonly summary_index?: number | undefined;
}, OpenResponses.ParserState>;
export declare const httpTransport: HttpTransport.HttpJsonTransport<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, string>;
export declare const transport: import("../route/transport/index.js").Transport<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("./open-responses-channel.js").Prepared, string>;
export declare const route: Route<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("./open-responses-channel.js").Prepared>;
export * as OpenAIResponses from "./openai-responses.js";
import { Effect, Encoding, Schema } from "effect";
import { Headers } from "effect/unstable/http";
import { Route } from "../route/client.js";
import { Auth } from "../route/auth.js";
import { Endpoint } from "../route/endpoint.js";
import { Protocol } from "../route/protocol.js";
import { HttpTransport } from "../route/transport/index.js";
import { LLMEvent, LLMRequest } from "../schema/index.js";
import { OpenResponses } from "./open-responses.js";
import { optionalArray, ProviderShared } from "./shared.js";
import { Lifecycle } from "./utils/lifecycle.js";
import { OpenAIImage } from "./utils/openai-image.js";
import { ToolSchemaProjection } from "./utils/tool-schema.js";
import { OpenResponsesChannel } from "./open-responses-channel.js";
import { OpenAIResponsesChannel } from "./openai-responses-channel.js";
const ADAPTER = "openai-responses";
const NAME = "OpenAI Responses";
const WEBSOCKET_PROTOCOL_HEADER = "responses_websockets=2026-02-06";
const WEBSOCKET_ROTATE_AFTER_MS = 55 * 60 * 1000;
export const DEFAULT_BASE_URL = "https://api.openai.com/v1";
export const PATH = OpenResponses.PATH;
const OpenAIResponsesImageGenerationTool = Schema.Struct({
type: Schema.tag("image_generation"),
action: Schema.optional(Schema.Literals(["auto", "generate", "edit"])),
background: Schema.optional(Schema.Literals(["auto", "opaque", "transparent"])),
input_fidelity: Schema.optional(Schema.Literals(["low", "high"])),
output_compression: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 }))),
output_format: Schema.optional(Schema.Literals(["png", "jpeg", "webp"])),
partial_images: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
quality: Schema.optional(Schema.Literals(["auto", "low", "medium", "high"])),
size: Schema.optional(OpenAIImage.Size),
});
const OpenAIResponsesTools = Schema.Union([OpenResponses.Tool, OpenAIResponsesImageGenerationTool]);
const OpenAIResponsesToolChoice = Schema.Union([
OpenResponses.ToolChoice,
Schema.Struct({ type: Schema.tag("image_generation") }),
]);
const OpenAIResponsesInputItem = Schema.Union([
Schema.Struct({
role: Schema.tag("assistant"),
content: Schema.Array(Schema.Struct({ type: Schema.tag("output_text"), text: Schema.String })),
phase: Schema.optionalKey(Schema.NullOr(OpenResponses.MessagePhase)),
}),
OpenResponses.InputItem,
]);
const OpenAIResponsesCoreFields = {
...OpenResponses.coreFields,
input: Schema.Array(OpenAIResponsesInputItem),
tools: optionalArray(OpenAIResponsesTools),
tool_choice: Schema.optional(OpenAIResponsesToolChoice),
};
const OpenAIResponsesBody = Schema.Struct({
...OpenAIResponsesCoreFields,
stream: Schema.Literal(true),
});
const extension = {
id: ADAPTER,
name: NAME,
messagePhase: (value) => (value === null ? null : undefined),
lowerMedia: ({ part, media, request }) => {
if (request.model.provider !== "xai" || media.mime !== "application/pdf")
return undefined;
return {
type: "input_file",
filename: part.filename ?? "document.pdf",
file_data: media.base64,
mime_type: media.mime,
};
},
};
const nativeImageToolInput = (tool) => {
const native = tool.native?.openai;
return ProviderShared.isRecord(native) && native.type === "image_generation" ? native : undefined;
};
const nativeImageTool = (tool) => {
const native = nativeImageToolInput(tool);
return Schema.is(OpenAIResponsesImageGenerationTool)(native) ? native : undefined;
};
const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool, inputSchema) {
const native = nativeImageToolInput(tool);
if (native !== undefined) {
if (Schema.is(OpenAIResponsesImageGenerationTool)(native))
return native;
return yield* ProviderShared.invalidRequest("OpenAI Responses image generation tool options are invalid");
}
return yield* OpenResponses.lowerTool(NAME, tool, inputSchema);
});
const lowerToolChoice = (toolChoice, tools) => ProviderShared.matchToolChoice(NAME, toolChoice, {
auto: () => "auto",
none: () => "none",
required: () => "required",
tool: (name) => tools.some((tool) => tool.name === name && nativeImageTool(tool) !== undefined)
? { type: "image_generation" }
: { type: "function", name },
});
const fromRequest = Effect.fn("OpenAIResponses.fromRequest")(function* (request) {
const body = yield* OpenResponses.fromRequestWithExtension(LLMRequest.update(request, { tools: [], toolChoice: undefined }), extension);
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
return {
...body,
tools: request.tools.length === 0
? undefined
: yield* Effect.forEach(request.tools, (tool) => lowerTool(tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility))),
tool_choice: request.toolChoice ? yield* lowerToolChoice(request.toolChoice, request.tools) : undefined,
};
});
const HOSTED_TOOLS = {
web_search_call: { name: "web_search", input: (item) => item.action ?? {} },
web_search_preview_call: { name: "web_search_preview", input: (item) => item.action ?? {} },
file_search_call: { name: "file_search", input: (item) => ({ queries: item.queries ?? [] }) },
code_interpreter_call: {
name: "code_interpreter",
input: (item) => ({ code: item.code, container_id: item.container_id }),
},
computer_use_call: { name: "computer_use", input: (item) => item.action ?? {} },
image_generation_call: { name: "image_generation", input: () => ({}) },
mcp_call: {
name: "mcp",
input: (item) => ({ server_label: item.server_label, name: item.name, arguments: item.arguments }),
},
local_shell_call: { name: "local_shell", input: (item) => item.action ?? {} },
};
const isHostedToolItem = (item) => item.type in HOSTED_TOOLS && typeof item.id === "string" && item.id.length > 0;
const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item) {
const isError = item.error !== undefined && item.error !== null;
if (item.type === "image_generation_call" && item.result) {
yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError(() => ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64")));
const format = item.output_format ?? "png";
return {
type: "content",
value: [
{
type: "file",
uri: `data:image/${format};base64,${item.result}`,
mime: `image/${format}`,
},
],
};
}
return isError ? { type: "error", value: item.error } : { type: "json", value: item };
});
const onHostedToolDone = Effect.fn("OpenAIResponses.onHostedToolDone")(function* (state, item) {
const tool = HOSTED_TOOLS[item.type];
const providerMetadata = OpenResponses.providerMetadata(state, { itemId: item.id });
const events = [];
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
events.push(LLMEvent.toolCall({
id: item.id,
name: tool.name,
input: tool.input(item),
providerExecuted: true,
providerMetadata,
}), LLMEvent.toolResult({
id: item.id,
name: tool.name,
result: yield* hostedToolResult(item),
providerExecuted: true,
providerMetadata,
}));
return [{ ...state, lifecycle }, events];
});
const step = (state, event) => {
if (event.type === "response.reasoning_text.delta" || event.type === "response.reasoning_summary.delta")
return event.item_id
? Effect.succeed(OpenResponses.onReasoningDelta(state, event, event.item_id))
: ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`);
if (event.type === "response.reasoning_text.done" || event.type === "response.reasoning_summary.done")
return event.item_id
? Effect.succeed(OpenResponses.onReasoningDone(state, event))
: ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`);
if (event.type === "response.output_item.done" && event.item && isHostedToolItem(event.item))
return onHostedToolDone(state, event.item);
return OpenResponses.step(state, event);
};
export const protocol = Protocol.make({
id: ADAPTER,
body: {
schema: OpenAIResponsesBody,
from: fromRequest,
},
stream: {
event: OpenResponses.protocol.stream.event,
initial: (request) => OpenResponses.initial(request, extension),
step,
terminal: OpenResponses.terminal,
},
});
const endpoint = Endpoint.path(PATH, { baseURL: DEFAULT_BASE_URL });
const auth = Auth.none;
export const httpTransport = HttpTransport.sseJson.with();
export const transport = OpenResponsesChannel.transport({
id: ADAPTER,
name: NAME,
rotateAfterMs: WEBSOCKET_ROTATE_AFTER_MS,
headers: (headers) => Headers.set(headers, "openai-beta", headers["openai-beta"] ?? WEBSOCKET_PROTOCOL_HEADER),
driver: (input) => OpenAIResponsesChannel.driver({ id: ADAPTER, name: NAME, ...input }),
});
export const route = Route.make({
id: ADAPTER,
provider: "openai",
providerMetadataKey: "openai",
protocol,
endpoint,
auth,
transport,
defaults: { providerOptions: { openai: { store: false } } },
});
export * as OpenAIResponses from "./openai-responses.js";
import { Buffer } from "node:buffer";
import { Tool } from "@opencode-ai/schema/tool";
import { Effect, Schema, Stream } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { AIError, type ContentPart, type LLMRequest, type ToolResultPart } from "../schema/index.js";
import { isRecord } from "../utils/record.js";
export { isRecord };
export declare const Json: Schema.fromJsonString<Schema.Unknown>;
export declare const decodeJson: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => unknown;
export declare const encodeJson: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => string;
export declare const JsonObject: Schema.$Record<Schema.String, Schema.Unknown>;
export declare const optionalArray: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.$Array<S>>;
export declare const optionalNull: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.NullOr<S>>;
/**
* Streaming tool-call accumulator. Adapters that build a tool call across
* multiple `tool-input-delta` chunks store the partial JSON input string here
* and finalize it with `parseToolInput` once the call completes.
*/
export interface ToolAccumulator {
readonly id: string;
readonly name: string;
readonly input: string;
}
/**
* `Usage.totalTokens` policy shared by every route. Honors a provider-
* supplied total; otherwise falls back to `inputTokens + outputTokens` only
* when at least one is defined. Returns `undefined` when neither input nor
* output is known so routes don't publish a misleading `0`.
*
* Under the additive `AI.Usage` contract, `inputTokens` and `outputTokens`
* are the non-cached input and visible output only. The provider-supplied
* `total` is the source of truth when present; the computed fallback
* under-counts cache and reasoning by design and exists mainly so
* Anthropic-style providers (which don't surface a total) still get a
* sensible aggregate on the input + output axes.
*/
export declare const totalTokens: (inputTokens: number | undefined, outputTokens: number | undefined, total: number | undefined) => number | undefined;
/**
* Subtract `subtrahend` from `total`, clamping to zero if the provider
* reports a non-sensical breakdown (e.g. `cached_tokens > prompt_tokens`).
* Used by protocol mappers when deriving a non-overlapping breakdown field
* from a provider's inclusive total — `nonCachedInputTokens` from
* `inputTokens - cacheReadInputTokens - cacheWriteInputTokens`.
*
* If `total` is `undefined`, returns `undefined` (we don't fabricate
* counts). If `subtrahend` is `undefined`, returns `total` unchanged. The
* provider-native breakdown stays available on `Usage.native` for debugging.
*/
export declare const subtractTokens: (total: number | undefined, subtrahend: number | undefined) => number | undefined;
/**
* Sum a list of optional token counts, returning `undefined` only when
* every value is `undefined` (so we don't fabricate a `0`). Used by
* protocol mappers to derive the inclusive `inputTokens` total from a
* provider that natively reports a non-overlapping breakdown
* (e.g. Anthropic, whose `input_tokens` is already non-cached only).
*/
export declare const sumTokens: (...values: ReadonlyArray<number | undefined>) => number | undefined;
export declare const eventError: (route: string, message: string, raw?: string) => AIError;
export declare const parseJson: (route: string, input: string, message: string) => Effect.Effect<unknown, AIError, never>;
/**
* Join the `text` field of a list of parts with newlines. Used by routes
* that flatten system / message content arrays into a single provider string
* (OpenAI Chat `system` content, OpenAI Responses `system` content, Gemini
* `systemInstruction.parts[].text`).
*/
export declare const joinText: (parts: ReadonlyArray<{
readonly text: string;
}>) => string;
/**
* Stable fallback representation for chronological `Message.system(...)`
* updates on routes that do not support that privileged role natively. The
* wrapper remains visibly lower-authority user text, preserves the original
* temporal position, and XML-escapes content so it cannot close the wrapper.
*/
export declare const wrapSystemUpdate: (parts: ReadonlyArray<{
readonly text: string;
}>) => string;
/**
* Chronological system updates deliberately accept text only. Do not insert
* raw retrieved, tool, or web content into privileged updates: keep untrusted
* data in ordinary user/tool messages instead.
*/
export declare const systemUpdateText: (route: string, message: import("../schema/messages.js").Message) => Effect.Effect<{
readonly type: "text";
readonly text: string;
readonly metadata?: {
readonly [x: string]: unknown;
} | undefined;
readonly cache?: import("../schema/options.js").CacheHint | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
}[], AIError, never>;
/** Lower an unsupported privileged update into visible, in-order user text. */
export declare const wrappedSystemUpdate: (route: string, message: import("../schema/messages.js").Message) => Effect.Effect<{
type: "text";
text: string;
cache: import("../schema/options.js").CacheHint | undefined;
}, AIError, never>;
/**
* Parse the streamed JSON input of a tool call. Treats an empty string as
* `"{}"` — providers occasionally finish a tool call without ever emitting
* input deltas (e.g. zero-arg tools). The error message is uniform across
* routes: `Invalid JSON input for <route> tool call <name>`.
*/
export declare const parseToolInput: (route: string, name: string, raw: string) => Effect.Effect<unknown, AIError, never>;
export declare const IMAGE_MIMES: readonly ["image/png", "image/jpeg", "image/gif", "image/webp"];
export declare const VIDEO_MIMES: readonly ["video/mp4", "video/webm", "video/quicktime"];
export declare const AUDIO_MIMES: readonly ["audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac"];
export declare const PDF_MIMES: readonly ["application/pdf"];
export declare const MEDIA_MIMES: readonly ["image/png", "image/jpeg", "image/gif", "image/webp", "video/mp4", "video/webm", "video/quicktime", "audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac", "application/pdf"];
export declare const MAX_MEDIA_ENCODED_BYTES: number;
export declare const MAX_MEDIA_DECODED_BYTES: number;
export interface ValidatedMedia {
readonly mime: string;
readonly base64: string;
readonly dataUrl: string;
readonly bytes: Uint8Array;
}
export declare const validateMedia: (route: string, part: {
readonly data: string | Uint8Array<ArrayBufferLike>;
readonly type: "media";
readonly mediaType: string;
readonly metadata?: {
readonly [x: string]: unknown;
} | undefined;
readonly filename?: string | undefined;
}, supportedMimes: ReadonlySet<string>) => Effect.Effect<{
mime: string;
base64: string;
dataUrl: string;
bytes: Buffer<ArrayBuffer>;
}, AIError, never>;
export declare const validateToolFile: (route: string, part: Tool.FileContent, supportedMimes: ReadonlySet<string>) => Effect.Effect<{
mime: string;
base64: string;
dataUrl: string;
bytes: Buffer<ArrayBuffer>;
}, AIError, never>;
export declare const trimBaseUrl: (value: string) => string;
export declare const toolResultText: (part: ToolResultPart) => string;
export declare const errorText: (error: unknown) => string;
/**
* `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
* decoder, and drops empty / `[DONE]` keep-alive events so the downstream
* `decodeChunk` sees one JSON string per element. The SSE channel emits a
* `Retry` control event on its error channel; we drop it here (we don't
* implement client-driven retries) so the public error channel stays
* `AIError`.
*/
export declare const sseFraming: (bytes: Stream.Stream<Uint8Array, AIError>) => Stream.Stream<string, AIError>;
/**
* Canonical invalid-request constructor. Lift one-line `const invalid =
* (message) => invalidRequest(message)` aliases out of every
* route so the error constructor lives in one place. If we ever extend
* `InvalidRequestReason` with route context or trace metadata, the change
* lands here.
*/
export declare const invalidRequest: (message: string) => AIError;
export declare const matchToolChoice: <Auto, None, Required, Tool>(route: string, toolChoice: NonNullable<LLMRequest["toolChoice"]>, cases: {
readonly auto: () => Auto;
readonly none: () => None;
readonly required: () => Required;
readonly tool: (name: string) => Tool;
}) => Effect.Effect<Auto | None | Required | Tool, AIError, never>;
type ContentType = ContentPart["type"];
export declare const supportsContent: <const Type extends ContentType>(part: ContentPart, types: ReadonlyArray<Type>) => part is Extract<ContentPart, {
readonly type: Type;
}>;
export declare const unsupportedContent: (route: string, role: LLMRequest["messages"][number]["role"], types: ReadonlyArray<ContentType>) => AIError;
/**
* Build a `validate` step from a Schema decoder. Replaces the per-route
* lambda body `(payload) => decode(payload).pipe(Effect.mapError((e) =>
* invalid(e.message)))`. Any decode error is translated into
* `AIError` carrying the original parse-error message.
*/
export declare const validateWith: <A, I, E extends {
readonly message: string;
}>(decode: (input: I) => Effect.Effect<A, E>) => (payload: I) => Effect.Effect<A, AIError, never>;
/**
* Build an HTTP POST with a JSON body. Sets `content-type: application/json`
* automatically after caller-supplied headers so routes cannot accidentally
* send JSON with a stale content type. The body is passed pre-encoded so
* routes can choose between
* `Schema.encodeSync(payload)` and `ProviderShared.encodeJson(payload)`.
*/
export declare const jsonPost: (input: {
readonly url: string;
readonly body: string;
readonly headers?: Headers.Input;
}) => HttpClientRequest.HttpClientRequest;
export * as ProviderShared from "./shared.js";
import { Buffer } from "node:buffer";
import { Tool } from "@opencode-ai/schema/tool";
import { Effect, Schema, Stream } from "effect";
import * as Sse from "effect/unstable/encoding/Sse";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { InvalidProviderOutputReason, InvalidRequestReason, AIError, } from "../schema/index.js";
import { isRecord } from "../utils/record.js";
export { isRecord };
export const Json = Schema.fromJsonString(Schema.Unknown);
export const decodeJson = Schema.decodeUnknownSync(Json);
export const encodeJson = Schema.encodeSync(Json);
const isJson = Schema.is(Schema.Json);
export const JsonObject = Schema.Record(Schema.String, Schema.Unknown);
export const optionalArray = (schema) => Schema.optional(Schema.Array(schema));
export const optionalNull = (schema) => Schema.optional(Schema.NullOr(schema));
/**
* `Usage.totalTokens` policy shared by every route. Honors a provider-
* supplied total; otherwise falls back to `inputTokens + outputTokens` only
* when at least one is defined. Returns `undefined` when neither input nor
* output is known so routes don't publish a misleading `0`.
*
* Under the additive `AI.Usage` contract, `inputTokens` and `outputTokens`
* are the non-cached input and visible output only. The provider-supplied
* `total` is the source of truth when present; the computed fallback
* under-counts cache and reasoning by design and exists mainly so
* Anthropic-style providers (which don't surface a total) still get a
* sensible aggregate on the input + output axes.
*/
export const totalTokens = (inputTokens, outputTokens, total) => {
if (total !== undefined)
return total;
if (inputTokens === undefined && outputTokens === undefined)
return undefined;
return (inputTokens ?? 0) + (outputTokens ?? 0);
};
/**
* Subtract `subtrahend` from `total`, clamping to zero if the provider
* reports a non-sensical breakdown (e.g. `cached_tokens > prompt_tokens`).
* Used by protocol mappers when deriving a non-overlapping breakdown field
* from a provider's inclusive total — `nonCachedInputTokens` from
* `inputTokens - cacheReadInputTokens - cacheWriteInputTokens`.
*
* If `total` is `undefined`, returns `undefined` (we don't fabricate
* counts). If `subtrahend` is `undefined`, returns `total` unchanged. The
* provider-native breakdown stays available on `Usage.native` for debugging.
*/
export const subtractTokens = (total, subtrahend) => {
if (total === undefined)
return undefined;
if (subtrahend === undefined)
return total;
return Math.max(0, total - subtrahend);
};
/**
* Sum a list of optional token counts, returning `undefined` only when
* every value is `undefined` (so we don't fabricate a `0`). Used by
* protocol mappers to derive the inclusive `inputTokens` total from a
* provider that natively reports a non-overlapping breakdown
* (e.g. Anthropic, whose `input_tokens` is already non-cached only).
*/
export const sumTokens = (...values) => {
if (values.every((value) => value === undefined))
return undefined;
return values.reduce((acc, value) => acc + (value ?? 0), 0);
};
export const eventError = (route, message, raw) => new AIError({
module: "ProviderShared",
method: "stream",
reason: new InvalidProviderOutputReason({ route, message, raw }),
});
export const parseJson = (route, input, message) => Effect.try({
try: () => decodeJson(input),
catch: () => eventError(route, message, input),
});
/**
* Join the `text` field of a list of parts with newlines. Used by routes
* that flatten system / message content arrays into a single provider string
* (OpenAI Chat `system` content, OpenAI Responses `system` content, Gemini
* `systemInstruction.parts[].text`).
*/
export const joinText = (parts) => parts.map((part) => part.text).join("\n");
const escapeSystemUpdateText = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
/**
* Stable fallback representation for chronological `Message.system(...)`
* updates on routes that do not support that privileged role natively. The
* wrapper remains visibly lower-authority user text, preserves the original
* temporal position, and XML-escapes content so it cannot close the wrapper.
*/
export const wrapSystemUpdate = (parts) => `<system-update>\n${escapeSystemUpdateText(joinText(parts))}\n</system-update>`;
/**
* Chronological system updates deliberately accept text only. Do not insert
* raw retrieved, tool, or web content into privileged updates: keep untrusted
* data in ordinary user/tool messages instead.
*/
export const systemUpdateText = Effect.fn("ProviderShared.systemUpdateText")(function* (route, message) {
const content = [];
for (const part of message.content) {
if (!supportsContent(part, ["text"]))
return yield* unsupportedContent(route, "system", ["text"]);
content.push(part);
}
return content;
});
/** Lower an unsupported privileged update into visible, in-order user text. */
export const wrappedSystemUpdate = Effect.fn("ProviderShared.wrappedSystemUpdate")(function* (route, message) {
const content = yield* systemUpdateText(route, message);
return { type: "text", text: wrapSystemUpdate(content), cache: content.at(-1)?.cache };
});
/**
* Parse the streamed JSON input of a tool call. Treats an empty string as
* `"{}"` — providers occasionally finish a tool call without ever emitting
* input deltas (e.g. zero-arg tools). The error message is uniform across
* routes: `Invalid JSON input for <route> tool call <name>`.
*/
export const parseToolInput = (route, name, raw) => parseJson(route, raw || "{}", `Invalid JSON input for ${route} tool call ${name}`);
export const IMAGE_MIMES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
export const VIDEO_MIMES = ["video/mp4", "video/webm", "video/quicktime"];
export const AUDIO_MIMES = ["audio/wav", "audio/mp3", "audio/aiff", "audio/aac", "audio/ogg", "audio/flac"];
export const PDF_MIMES = ["application/pdf"];
export const MEDIA_MIMES = [...IMAGE_MIMES, ...VIDEO_MIMES, ...AUDIO_MIMES, ...PDF_MIMES];
export const MAX_MEDIA_ENCODED_BYTES = 28 * 1024 * 1024;
export const MAX_MEDIA_DECODED_BYTES = 20 * 1024 * 1024;
const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
export const validateMedia = Effect.fn("ProviderShared.validateMedia")(function* (route, part, supportedMimes) {
const mime = part.mediaType.toLowerCase();
if (!supportedMimes.has(mime))
return yield* invalidRequest(`${route} does not support media type ${part.mediaType}`);
let base64;
if (typeof part.data !== "string") {
if (part.data.byteLength > MAX_MEDIA_DECODED_BYTES)
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_DECODED_BYTES} byte decoded limit`);
base64 = Buffer.from(part.data).toString("base64");
}
else if (part.data.startsWith("data:")) {
const match = /^data:([^;,]+);base64,([A-Za-z0-9+/]*={0,2})$/s.exec(part.data);
if (!match)
return yield* invalidRequest(`${route} media data URL must contain valid base64`);
if (match[1].toLowerCase() !== mime)
return yield* invalidRequest(`${route} media type ${part.mediaType} does not match data URL type ${match[1]}`);
base64 = match[2];
}
else {
base64 = part.data;
}
if (Buffer.byteLength(base64, "utf8") > MAX_MEDIA_ENCODED_BYTES)
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_ENCODED_BYTES} byte encoded limit`);
if (!base64 || base64.length % 4 !== 0 || !base64Pattern.test(base64))
return yield* invalidRequest(`${route} media must contain valid base64`);
const bytes = Buffer.from(base64, "base64");
if (bytes.byteLength > MAX_MEDIA_DECODED_BYTES)
return yield* invalidRequest(`${route} media exceeds the ${MAX_MEDIA_DECODED_BYTES} byte decoded limit`);
if (bytes.toString("base64") !== base64)
return yield* invalidRequest(`${route} media must contain canonical base64`);
return { mime, base64, dataUrl: `data:${mime};base64,${base64}`, bytes };
});
export const validateToolFile = (route, part, supportedMimes) => validateMedia(route, { type: "media", mediaType: part.mime, data: part.uri, filename: part.name }, supportedMimes);
export const trimBaseUrl = (value) => value.replace(/\/+$/, "");
export const toolResultText = (part) => {
if (part.result.type === "text")
return String(part.result.value);
if (part.result.type === "error") {
const value = part.result.value;
const prototype = typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value);
const structured = Array.isArray(value) || prototype === Object.prototype || prototype === null;
return structured && isJson(value) ? encodeJson(value) : String(value);
}
return encodeJson(part.result.value);
};
export const errorText = (error) => {
if (error instanceof Error)
return error.message;
if (typeof error === "string")
return error;
if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint")
return String(error);
if (error === null)
return "null";
if (error === undefined)
return "undefined";
return "Unknown stream error";
};
/**
* `framing` step for Server-Sent Events. Decodes UTF-8, runs the SSE channel
* decoder, and drops empty / `[DONE]` keep-alive events so the downstream
* `decodeChunk` sees one JSON string per element. The SSE channel emits a
* `Retry` control event on its error channel; we drop it here (we don't
* implement client-driven retries) so the public error channel stays
* `AIError`.
*/
export const sseFraming = (bytes) => bytes.pipe(Stream.decodeText(), Stream.pipeThroughChannel(Sse.decode()), Stream.catchTag("Retry", () => Stream.empty), Stream.filter((event) => event.data.length > 0 && event.data !== "[DONE]"), Stream.map((event) => event.data));
/**
* Canonical invalid-request constructor. Lift one-line `const invalid =
* (message) => invalidRequest(message)` aliases out of every
* route so the error constructor lives in one place. If we ever extend
* `InvalidRequestReason` with route context or trace metadata, the change
* lands here.
*/
export const invalidRequest = (message) => new AIError({
module: "ProviderShared",
method: "request",
reason: new InvalidRequestReason({ message }),
});
export const matchToolChoice = (route, toolChoice, cases) => Effect.gen(function* () {
if (toolChoice.type === "auto")
return cases.auto();
if (toolChoice.type === "none")
return cases.none();
if (toolChoice.type === "required")
return cases.required();
if (!toolChoice.name)
return yield* invalidRequest(`${route} tool choice requires a tool name`);
return cases.tool(toolChoice.name);
});
const formatContentTypes = (types) => {
if (types.length <= 1)
return types[0] ?? "";
if (types.length === 2)
return `${types[0]} and ${types[1]}`;
return `${types.slice(0, -1).join(", ")}, and ${types.at(-1)}`;
};
export const supportsContent = (part, types) => types.includes(part.type);
export const unsupportedContent = (route, role, types) => invalidRequest(`${route} ${role} messages only support ${formatContentTypes(types)} content for now`);
/**
* Build a `validate` step from a Schema decoder. Replaces the per-route
* lambda body `(payload) => decode(payload).pipe(Effect.mapError((e) =>
* invalid(e.message)))`. Any decode error is translated into
* `AIError` carrying the original parse-error message.
*/
export const validateWith = (decode) => (payload) => decode(payload).pipe(Effect.mapError((error) => invalidRequest(error.message)));
/**
* Build an HTTP POST with a JSON body. Sets `content-type: application/json`
* automatically after caller-supplied headers so routes cannot accidentally
* send JSON with a stale content type. The body is passed pre-encoded so
* routes can choose between
* `Schema.encodeSync(payload)` and `ProviderShared.encodeJson(payload)`.
*/
export const jsonPost = (input) => HttpClientRequest.post(input.url).pipe(HttpClientRequest.setHeaders(Headers.set(Headers.fromInput(input.headers), "content-type", "application/json")), HttpClientRequest.bodyText(input.body, "application/json"));
export * as ProviderShared from "./shared.js";
import { Auth } from "../../route/auth.js";
/**
* AWS credentials for SigV4 signing. Bedrock also supports Bearer API key auth,
* which provider facades configure as route auth instead of SigV4. STS-vended
* credentials should be refreshed by the consumer (rebuild the model) before
* they expire; the route does not refresh.
*/
export interface Credentials {
readonly region: string;
readonly accessKeyId: string;
readonly secretAccessKey: string;
readonly sessionToken?: string;
}
/** Sign the exact JSON bytes with SigV4 using credentials configured on the route. */
export declare const sigV4: (credentials: Credentials | undefined, options?: {
readonly service?: string;
readonly name?: string;
}) => Auth.Definition;
/** Bedrock route auth defaults to SigV4 and expects credentials from route configuration. */
export declare const auth: Auth.Definition;
export * as BedrockAuth from "./bedrock-auth.js";
import { AwsV4Signer } from "aws4fetch";
import { Effect } from "effect";
import { Headers } from "effect/unstable/http";
import { Auth } from "../../route/auth.js";
import { ProviderShared } from "../shared.js";
const signRequest = (input) => Effect.tryPromise({
try: async () => {
const signed = await new AwsV4Signer({
url: input.url,
method: "POST",
headers: Object.entries(input.headers),
body: input.body,
region: input.credentials.region,
accessKeyId: input.credentials.accessKeyId,
secretAccessKey: input.credentials.secretAccessKey,
sessionToken: input.credentials.sessionToken,
service: input.service,
}).sign();
return Object.fromEntries(signed.headers.entries());
},
catch: (error) => ProviderShared.invalidRequest(`${input.name} SigV4 signing failed: ${error instanceof Error ? error.message : String(error)}`),
});
/** Sign the exact JSON bytes with SigV4 using credentials configured on the route. */
export const sigV4 = (credentials, options = {}) => Auth.custom((input) => {
return Effect.gen(function* () {
if (!credentials) {
return yield* ProviderShared.invalidRequest(`${options.name ?? "Bedrock Converse"} requires either route bearer auth or AWS credentials configured on the route`);
}
const headersForSigning = Headers.set(input.headers, "content-type", "application/json");
const signed = yield* signRequest({
url: input.url,
body: input.body,
headers: headersForSigning,
credentials,
service: options.service ?? "bedrock",
name: options.name ?? "Bedrock Converse",
});
return Headers.setAll(headersForSigning, signed);
});
});
/** Bedrock route auth defaults to SigV4 and expects credentials from route configuration. */
export const auth = sigV4(undefined);
export * as BedrockAuth from "./bedrock-auth.js";
import { Schema } from "effect";
import type { CacheHint } from "../../schema/index.js";
import { type Breakpoints } from "./cache.js";
export declare const CachePointBlock: Schema.Struct<{
readonly cachePoint: Schema.Struct<{
readonly type: Schema.tag<"default">;
readonly ttl: Schema.optional<Schema.Literals<readonly ["5m", "1h"]>>;
}>;
}>;
export type CachePointBlock = Schema.Schema.Type<typeof CachePointBlock>;
export declare const BEDROCK_BREAKPOINT_CAP = 4;
export type { Breakpoints } from "./cache.js";
export declare const breakpoints: () => Breakpoints;
export declare const block: (breakpoints: Breakpoints, cache: CacheHint | undefined) => CachePointBlock | undefined;
export * as BedrockCache from "./bedrock-cache.js";
import { Schema } from "effect";
import { newBreakpoints, ttlBucket } from "./cache.js";
// Bedrock cache markers are positional: emit a `cachePoint` block immediately
// after the content the caller wants treated as a cacheable prefix. Bedrock
// accepts optional `ttl: "5m" | "1h"` on cachePoint, mirroring Anthropic.
export const CachePointBlock = Schema.Struct({
cachePoint: Schema.Struct({
type: Schema.tag("default"),
ttl: Schema.optional(Schema.Literals(["5m", "1h"])),
}),
});
// Bedrock-Claude enforces the same 4-breakpoint cap as the Anthropic Messages
// API. Callers pass a shared counter through every `block()` call site so the
// budget is respected across `system`, `messages`, and `tools`.
export const BEDROCK_BREAKPOINT_CAP = 4;
export const breakpoints = () => newBreakpoints(BEDROCK_BREAKPOINT_CAP);
const DEFAULT_5M = { cachePoint: { type: "default" } };
const DEFAULT_1H = { cachePoint: { type: "default", ttl: "1h" } };
export const block = (breakpoints, cache) => {
if (cache?.type !== "ephemeral" && cache?.type !== "persistent")
return undefined;
if (breakpoints.remaining <= 0) {
breakpoints.dropped += 1;
return undefined;
}
breakpoints.remaining -= 1;
return ttlBucket(cache.ttlSeconds) === "1h" ? DEFAULT_1H : DEFAULT_5M;
};
export * as BedrockCache from "./bedrock-cache.js";
import { Effect, Schema } from "effect";
export declare const ImageFormat: Schema.Literals<readonly ["png", "jpeg", "gif", "webp"]>;
export type ImageFormat = Schema.Schema.Type<typeof ImageFormat>;
export declare const ImageBlock: Schema.Struct<{
readonly image: Schema.Struct<{
readonly format: Schema.Literals<readonly ["png", "jpeg", "gif", "webp"]>;
readonly source: Schema.Struct<{
readonly bytes: Schema.String;
}>;
}>;
}>;
export type ImageBlock = Schema.Schema.Type<typeof ImageBlock>;
export declare const DocumentFormat: Schema.Literals<readonly ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]>;
export type DocumentFormat = Schema.Schema.Type<typeof DocumentFormat>;
export declare const DocumentBlock: Schema.Struct<{
readonly document: Schema.Struct<{
readonly format: Schema.Literals<readonly ["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]>;
readonly name: Schema.String;
readonly source: Schema.Struct<{
readonly bytes: Schema.String;
}>;
}>;
}>;
export type DocumentBlock = Schema.Schema.Type<typeof DocumentBlock>;
export declare const lower: (part: {
readonly data: string | Uint8Array<ArrayBufferLike>;
readonly type: "media";
readonly mediaType: string;
readonly metadata?: {
readonly [x: string]: unknown;
} | undefined;
readonly filename?: string | undefined;
}) => Effect.Effect<{
readonly document: {
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
readonly name: string;
readonly source: {
readonly bytes: string;
};
};
} | {
image: {
format: "png" | "jpeg" | "gif" | "webp";
source: {
bytes: string;
};
};
}, import("../../schema/errors.js").AIError, never>;
export * as BedrockMedia from "./bedrock-media.js";
import { Effect, Schema } from "effect";
import { ProviderShared } from "../shared.js";
// Bedrock Converse accepts image `format` as the file extension and
// `source.bytes` as base64 in the JSON wire format.
export const ImageFormat = Schema.Literals(["png", "jpeg", "gif", "webp"]);
export const ImageBlock = Schema.Struct({
image: Schema.Struct({
format: ImageFormat,
source: Schema.Struct({ bytes: Schema.String }),
}),
});
// Bedrock document blocks require a user-facing name so the model can refer to
// the uploaded document.
export const DocumentFormat = Schema.Literals(["pdf", "csv", "doc", "docx", "xls", "xlsx", "html", "txt", "md"]);
export const DocumentBlock = Schema.Struct({
document: Schema.Struct({
format: DocumentFormat,
name: Schema.String,
source: Schema.Struct({ bytes: Schema.String }),
}),
});
const IMAGE_FORMATS = {
"image/png": "png",
"image/jpeg": "jpeg",
"image/jpg": "jpeg",
"image/gif": "gif",
"image/webp": "webp",
};
const DOCUMENT_FORMATS = {
"application/pdf": "pdf",
"text/csv": "csv",
"application/msword": "doc",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
"application/vnd.ms-excel": "xls",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
"text/html": "html",
"text/plain": "txt",
"text/markdown": "md",
};
const documentBlock = (name, format, bytes) => ({
document: {
format,
name,
source: { bytes },
},
});
// Route by MIME. Known image/document formats lower into a typed block; anything
// else fails with a clear error instead of silently degrading to a malformed
// document block. Image MIME types not in `IMAGE_FORMATS` (e.g. `image/svg+xml`)
// get an image-specific error so the caller knows it's a format-support issue,
// not a kind-detection issue.
export const lower = Effect.fn("BedrockMedia.lower")(function* (part) {
const mime = part.mediaType.toLowerCase();
const imageFormat = IMAGE_FORMATS[mime];
if (imageFormat) {
const media = yield* ProviderShared.validateMedia("Bedrock Converse", part, new Set(Object.keys(IMAGE_FORMATS)));
return { image: { format: imageFormat, source: { bytes: media.base64 } } };
}
if (mime.startsWith("image/"))
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support image media type ${part.mediaType}`);
const documentFormat = DOCUMENT_FORMATS[mime];
if (documentFormat) {
if (!part.filename)
return yield* ProviderShared.invalidRequest("Bedrock Converse document media requires a filename");
const media = yield* ProviderShared.validateMedia("Bedrock Converse", part, new Set(Object.keys(DOCUMENT_FORMATS)));
return documentBlock(part.filename, documentFormat, media.base64);
}
return yield* ProviderShared.invalidRequest(`Bedrock Converse does not support media type ${part.mediaType}`);
});
export * as BedrockMedia from "./bedrock-media.js";
export interface Breakpoints {
remaining: number;
dropped: number;
}
export declare const newBreakpoints: (cap: number) => Breakpoints;
export declare const ttlBucket: (ttlSeconds: number | undefined) => "1h" | undefined;
// Shared helpers for provider cache-marker lowering. Anthropic and Bedrock
// both enforce a 4-breakpoint cap per request and accept the same `5m`/`1h`
// TTL buckets, so the counter and TTL mapping live here.
export const newBreakpoints = (cap) => ({ remaining: cap, dropped: 0 });
// Returns `"1h"` for any `ttlSeconds >= 3600`, otherwise `undefined` (the
// provider default 5m). Anthropic & Bedrock both treat anything shorter than
// an hour as 5m.
export const ttlBucket = (ttlSeconds) => ttlSeconds !== undefined && ttlSeconds >= 3600 ? "1h" : undefined;
export declare const convert: (schema: unknown) => Record<string, unknown> | undefined;
export * as GeminiToolSchema from "./gemini-tool-schema.js";
import { isRecord } from "../../utils/record.js";
// Gemini accepts a JSON Schema-like dialect for tool parameters, but rejects a
// handful of common JSON Schema shapes. Keep this projection isolated so the
// Gemini protocol file still reads like the other protocol modules.
const SCHEMA_INTENT_KEYS = [
"type",
"properties",
"items",
"prefixItems",
"enum",
"const",
"$ref",
"additionalProperties",
"patternProperties",
"required",
"not",
"if",
"then",
"else",
];
const hasCombiner = (schema) => isRecord(schema) && (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf) || Array.isArray(schema.allOf));
const hasSchemaIntent = (schema) => isRecord(schema) && (hasCombiner(schema) || SCHEMA_INTENT_KEYS.some((key) => key in schema));
const sanitizeNode = (schema) => {
if (!isRecord(schema))
return Array.isArray(schema) ? schema.map(sanitizeNode) : schema;
const result = Object.fromEntries(Object.entries(schema).map(([key, value]) => [
key,
key === "enum" && Array.isArray(value) ? value.map(String) : sanitizeNode(value),
]));
if (Array.isArray(result.enum) && (result.type === "integer" || result.type === "number"))
result.type = "string";
const properties = result.properties;
if (result.type === "object" && isRecord(properties) && Array.isArray(result.required)) {
result.required = result.required.filter((field) => typeof field === "string" && field in properties);
}
if (result.type === "array" && !hasCombiner(result)) {
result.items = result.items ?? {};
if (isRecord(result.items) && !hasSchemaIntent(result.items))
result.items = { ...result.items, type: "string" };
}
if (typeof result.type === "string" && result.type !== "object" && !hasCombiner(result)) {
delete result.properties;
delete result.required;
}
return result;
};
const emptyObjectSchema = (schema) => schema.type === "object" &&
(!isRecord(schema.properties) || Object.keys(schema.properties).length === 0) &&
!schema.additionalProperties;
const projectNode = (schema, nested = false) => {
if (!isRecord(schema))
return undefined;
if (!nested && emptyObjectSchema(schema))
return undefined;
const types = Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null") : undefined;
const anyOf = Array.isArray(schema.anyOf) ? schema.anyOf : undefined;
const hasNullAnyOf = anyOf?.some((item) => isRecord(item) && item.type === "null") ?? false;
const anyOfTypes = hasNullAnyOf ? anyOf?.filter((item) => !isRecord(item) || item.type !== "null") : anyOf;
const flattenedAnyOf = hasNullAnyOf && anyOfTypes?.length === 1 ? projectNode(anyOfTypes[0], true) : undefined;
const result = Object.fromEntries([
["description", schema.description],
["required", schema.required],
["format", schema.format],
["type", types ? (types.length === 0 ? "null" : undefined) : schema.type],
[
"nullable",
(Array.isArray(schema.type) && schema.type.includes("null") && types && types.length > 0) || hasNullAnyOf
? true
: undefined,
],
["enum", schema.const !== undefined ? [schema.const] : schema.enum],
[
"properties",
isRecord(schema.properties)
? Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [key, projectNode(value, true)]))
: undefined,
],
[
"items",
Array.isArray(schema.items)
? schema.items.map((item) => projectNode(item, true))
: schema.items === undefined
? undefined
: projectNode(schema.items, true),
],
["allOf", Array.isArray(schema.allOf) ? schema.allOf.map((item) => projectNode(item, true)) : undefined],
[
"anyOf",
anyOfTypes
? hasNullAnyOf && anyOfTypes.length === 1
? undefined
: anyOfTypes.map((item) => projectNode(item, true))
: types && types.length > 0
? types.map((type) => ({ type }))
: undefined,
],
["oneOf", Array.isArray(schema.oneOf) ? schema.oneOf.map((item) => projectNode(item, true)) : undefined],
["minLength", schema.minLength],
].filter((entry) => entry[1] !== undefined));
return flattenedAnyOf ? { ...result, ...flattenedAnyOf } : result;
};
export const convert = (schema) => projectNode(sanitizeNode(schema));
export * as GeminiToolSchema from "./gemini-tool-schema.js";
import { Effect } from "effect";
import type { ImageInput } from "../../image.js";
import { AIError } from "../../schema/index.js";
export declare const dataUrl: (input: Extract<ImageInput, {
readonly type: "bytes";
}>) => string;
export declare const decodeDataUrl: (url: string, module: string) => Effect.Effect<{
readonly mediaType: string;
readonly data: Uint8Array;
} | undefined, AIError>;
export declare const invalidImageInput: (module: string, message: string) => AIError;
export declare const ImageInputs: {
readonly dataUrl: (input: Extract<ImageInput, {
readonly type: "bytes";
}>) => string;
readonly decodeDataUrl: (url: string, module: string) => Effect.Effect<{
readonly mediaType: string;
readonly data: Uint8Array;
} | undefined, AIError>;
readonly invalid: (module: string, message: string) => AIError;
};
import { Effect, Encoding } from "effect";
import { InvalidRequestReason, AIError } from "../../schema/index.js";
const invalid = (module, message) => new AIError({
module,
method: "generate",
reason: new InvalidRequestReason({ message }),
});
export const dataUrl = (input) => `data:${input.mediaType};base64,${Encoding.encodeBase64(input.data)}`;
export const decodeDataUrl = (url, module) => {
if (!url.startsWith("data:"))
return Effect.succeed(undefined);
const match = /^data:([^;,]+);base64,(.*)$/s.exec(url);
if (!match)
return Effect.fail(invalid(module, "Image data URLs must contain a MIME type and base64 data"));
return Effect.fromResult(Encoding.decodeBase64(match[2])).pipe(Effect.mapError(() => invalid(module, "Image data URL contains invalid base64 data")), Effect.map((data) => ({ mediaType: match[1], data })));
};
export const invalidImageInput = invalid;
export const ImageInputs = {
dataUrl,
decodeDataUrl,
invalid: invalidImageInput,
};
import { LLMEvent, type FinishReasonDetails, type ProviderMetadata, type Usage } from "../../schema/index.js";
export interface State {
readonly stepStarted: boolean;
readonly text: ReadonlySet<string>;
readonly reasoning: ReadonlySet<string>;
}
export declare const initial: () => State;
export declare const stepStart: (state: State, events: LLMEvent[]) => State;
export declare const textStart: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
export declare const textDelta: (state: State, events: LLMEvent[], id: string, text: string) => State;
export declare const reasoningStart: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
export declare const reasoningDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
export declare const reasoningEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
export declare const textEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
export declare const finish: (state: State, events: LLMEvent[], input: {
readonly reason: FinishReasonDetails;
readonly usage?: Usage;
readonly providerMetadata?: ProviderMetadata;
}) => State;
export * as Lifecycle from "./lifecycle.js";
import { LLMEvent } from "../../schema/index.js";
export const initial = () => ({ stepStarted: false, text: new Set(), reasoning: new Set() });
export const stepStart = (state, events) => {
if (state.stepStarted)
return state;
events.push(LLMEvent.stepStart({ index: 0 }));
return { ...state, stepStarted: true };
};
export const textStart = (state, events, id, providerMetadata) => {
if (state.text.has(id))
return state;
const stepped = stepStart(state, events);
events.push(LLMEvent.textStart({ id, providerMetadata }));
return { ...stepped, text: new Set([...stepped.text, id]) };
};
export const textDelta = (state, events, id, text) => {
const started = textStart(state, events, id);
events.push(LLMEvent.textDelta({ id, text }));
return started;
};
export const reasoningStart = (state, events, id, providerMetadata) => {
if (state.reasoning.has(id))
return state;
const stepped = stepStart(state, events);
events.push(LLMEvent.reasoningStart({ id, providerMetadata }));
return { ...stepped, reasoning: new Set([...stepped.reasoning, id]) };
};
export const reasoningDelta = (state, events, id, text, providerMetadata) => {
const started = reasoningStart(state, events, id, providerMetadata);
events.push(LLMEvent.reasoningDelta({ id, text, providerMetadata }));
return started;
};
export const reasoningEnd = (state, events, id, providerMetadata) => {
if (!state.reasoning.has(id))
return state;
const stepped = stepStart(state, events);
events.push(LLMEvent.reasoningEnd({ id, providerMetadata }));
const reasoning = new Set(stepped.reasoning);
reasoning.delete(id);
return { ...stepped, reasoning };
};
export const textEnd = (state, events, id, providerMetadata) => {
if (!state.text.has(id))
return state;
const stepped = stepStart(state, events);
events.push(LLMEvent.textEnd({ id, providerMetadata }));
const text = new Set(stepped.text);
text.delete(id);
return { ...stepped, text };
};
const closeOpenBlocks = (state, events) => {
for (const id of state.reasoning)
events.push(LLMEvent.reasoningEnd({ id }));
for (const id of state.text)
events.push(LLMEvent.textEnd({ id }));
return { ...state, text: new Set(), reasoning: new Set() };
};
export const finish = (state, events, input) => {
const stepped = closeOpenBlocks(stepStart(state, events), events);
events.push(LLMEvent.stepFinish({
index: 0,
reason: input.reason,
usage: input.usage,
providerMetadata: input.providerMetadata,
}), LLMEvent.finish(input));
return { ...stepped, stepStarted: false };
};
export * as Lifecycle from "./lifecycle.js";
import { Schema } from "effect";
import { TextVerbosity, type LLMRequest } from "../../schema/index.js";
export declare const ResponseIncludables: readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"];
export type ResponseIncludable = (typeof ResponseIncludables)[number];
export declare const ServiceTiers: readonly ["auto", "default", "flex", "priority"];
export type ServiceTier = (typeof ServiceTiers)[number];
export declare const ReasoningEffort: Schema.String;
export declare const TextVerbositySchema: Schema.Literals<readonly ["low", "medium", "high"]>;
export declare const ResponseIncludableSchema: Schema.Literals<readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"]>;
export declare const ServiceTierSchema: Schema.Literals<readonly ["auto", "default", "flex", "priority"]>;
export interface Resolved {
readonly instructions?: string;
readonly store?: boolean;
readonly reasoningEffort?: string;
readonly reasoningSummary?: "auto" | "concise" | "detailed";
readonly include?: ReadonlyArray<ResponseIncludable>;
readonly textVerbosity?: Schema.Schema.Type<typeof TextVerbosity>;
readonly serviceTier?: ServiceTier;
}
export declare const resolve: (request: LLMRequest) => Resolved;
export * as OpenResponsesOptions from "./open-responses-options.js";
import { Schema } from "effect";
import { TextVerbosity } from "../../schema/index.js";
export const ResponseIncludables = [
"file_search_call.results",
"web_search_call.results",
"web_search_call.action.sources",
"message.input_image.image_url",
"computer_call_output.output.image_url",
"code_interpreter_call.outputs",
"reasoning.encrypted_content",
"message.output_text.logprobs",
];
export const ServiceTiers = ["auto", "default", "flex", "priority"];
const TEXT_VERBOSITY = new Set(["low", "medium", "high"]);
const INCLUDABLES = new Set(ResponseIncludables);
const SERVICE_TIERS = new Set(ServiceTiers);
const isTextVerbosity = (value) => typeof value === "string" && TEXT_VERBOSITY.has(value);
const isServiceTier = (value) => typeof value === "string" && SERVICE_TIERS.has(value);
export const ReasoningEffort = Schema.String;
export const TextVerbositySchema = TextVerbosity;
export const ResponseIncludableSchema = Schema.Literals(ResponseIncludables);
export const ServiceTierSchema = Schema.Literals(ServiceTiers);
export const resolve = (request) => {
const input = request.providerOptions?.[request.model.route.providerMetadataKey ?? "openresponses"];
const include = Array.isArray(input?.include)
? input.include.filter((entry) => INCLUDABLES.has(entry))
: [];
const reasoningSummary = input?.reasoningSummary;
return {
instructions: typeof input?.instructions === "string" ? input.instructions : undefined,
store: typeof input?.store === "boolean" ? input.store : undefined,
reasoningEffort: typeof input?.reasoningEffort === "string" ? input.reasoningEffort : undefined,
reasoningSummary: reasoningSummary === "auto" || reasoningSummary === "concise" || reasoningSummary === "detailed"
? reasoningSummary
: undefined,
include: include.length > 0 ? include : undefined,
textVerbosity: isTextVerbosity(input?.textVerbosity) ? input.textVerbosity : undefined,
serviceTier: isServiceTier(input?.serviceTier) ? input.serviceTier : undefined,
};
};
export * as OpenResponsesOptions from "./open-responses-options.js";
import { Schema } from "effect";
export declare const Size: Schema.String;
export declare const OpenAIImage: {
readonly Size: Schema.String;
};
import { Schema } from "effect";
const dimensions = (value) => {
const match = /^(\d+)x(\d+)$/.exec(value);
if (!match)
return undefined;
return { width: Number(match[1]), height: Number(match[2]) };
};
export const Size = Schema.String.check(Schema.makeFilter((value) => {
if (value === "auto")
return undefined;
const parsed = dimensions(value);
if (!parsed)
return "image size must be `auto` or `{width}x{height}`";
return parsed.width > 0 && parsed.height > 0 ? undefined : "image dimensions must be positive integers";
}));
export const OpenAIImage = {
Size,
};
import { OpenResponsesOptions } from "./open-responses-options.js";
export declare const OpenAIReasoningEfforts: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
export type OpenAIReasoningEffort = string;
export declare const OpenAIResponseIncludables: readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"];
export type OpenAIResponseIncludable = OpenResponsesOptions.ResponseIncludable;
export declare const OpenAIServiceTiers: readonly ["auto", "default", "flex", "priority"];
export type OpenAIServiceTier = OpenResponsesOptions.ServiceTier;
export declare const OpenAIReasoningEffort: import("effect/Schema").String;
export declare const OpenAITextVerbosity: import("effect/Schema").Literals<readonly ["low", "medium", "high"]>;
export declare const OpenAIResponseIncludable: import("effect/Schema").Literals<readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"]>;
export declare const OpenAIServiceTier: import("effect/Schema").Literals<readonly ["auto", "default", "flex", "priority"]>;
export declare const isReasoningEffort: (effort: unknown) => effort is OpenAIReasoningEffort;
export declare const resolve: (request: import("../../schema/messages.js").LLMRequest) => OpenResponsesOptions.Resolved;
export * as OpenAIOptions from "./openai-options.js";
import { ReasoningEfforts } from "../../schema/index.js";
import { OpenResponsesOptions } from "./open-responses-options.js";
export const OpenAIReasoningEfforts = ReasoningEfforts;
// Mirrors OpenAI's `ResponseIncludable` union from the official SDK. Keep this
// in lockstep with `openai-node/src/resources/responses/responses.ts`.
export const OpenAIResponseIncludables = OpenResponsesOptions.ResponseIncludables;
export const OpenAIServiceTiers = OpenResponsesOptions.ServiceTiers;
export const OpenAIReasoningEffort = OpenResponsesOptions.ReasoningEffort;
export const OpenAITextVerbosity = OpenResponsesOptions.TextVerbositySchema;
export const OpenAIResponseIncludable = OpenResponsesOptions.ResponseIncludableSchema;
export const OpenAIServiceTier = OpenResponsesOptions.ServiceTierSchema;
export const isReasoningEffort = (effort) => typeof effort === "string";
export const resolve = OpenResponsesOptions.resolve;
export * as OpenAIOptions from "./openai-options.js";
import type { JsonSchema, LanguageModelToolSchemaCompatibility } from "../../schema/index.js";
export declare const ToolSchemaProjection: {
readonly gemini: (schema: JsonSchema) => JsonSchema;
readonly modelCompatibility: (schema: JsonSchema, compatibility: LanguageModelToolSchemaCompatibility | undefined) => JsonSchema;
readonly moonshot: (schema: JsonSchema) => JsonSchema;
readonly openAI: (schema: JsonSchema) => JsonSchema;
readonly responses: (schema: JsonSchema) => JsonSchema;
};
import { isRecord } from "../../utils/record.js";
import { GeminiToolSchema } from "./gemini-tool-schema.js";
const removeNullSchemas = (value) => {
if (Array.isArray(value))
return value.map(removeNullSchemas);
if (!isRecord(value))
return value;
const fields = Object.fromEntries(Object.entries(value)
.filter(([key]) => key !== "anyOf")
.map(([key, field]) => [key, removeNullSchemas(field)]));
if (!Array.isArray(value.anyOf))
return fields;
const variants = value.anyOf.filter((variant) => !isRecord(variant) || variant.type !== "null").map(removeNullSchemas);
if (variants.length === 1 && isRecord(variants[0]))
return { ...fields, ...variants[0] };
return { ...fields, anyOf: variants };
};
const tupleItemsSchema = (items) => {
const projected = items.map(moonshotNode);
if (projected.length === 0)
return {};
if (projected.length === 1)
return projected[0];
return { anyOf: projected };
};
const moonshotNode = (schema) => {
if (Array.isArray(schema))
return schema.map(moonshotNode);
if (!isRecord(schema))
return schema;
if (typeof schema.$ref === "string")
return { $ref: schema.$ref };
return Object.fromEntries(Object.entries(schema).flatMap(([key, value]) => {
if (key === "items" && Array.isArray(value))
return [[key, tupleItemsSchema(value)]];
if (key === "prefixItems") {
if ("items" in schema)
return [];
return [["items", tupleItemsSchema(Array.isArray(value) ? value : [])]];
}
if (key === "unevaluatedItems")
return [];
return [[key, moonshotNode(value)]];
}));
};
const moonshot = (schema) => {
const projected = moonshotNode(schema);
return isRecord(projected) ? projected : {};
};
const openAI = (schema) => {
const variants = Array.isArray(schema.anyOf) ? schema.anyOf.filter(isRecord) : [];
const flattened = variants.length === 0
? { ...schema, type: "object" }
: {
...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "anyOf")),
type: "object",
properties: variants.reduce((properties, variant) => ({ ...(isRecord(variant.properties) ? variant.properties : {}), ...properties }), {}),
additionalProperties: false,
};
const normalized = removeNullSchemas(flattened);
return isRecord(normalized) ? normalized : { type: "object" };
};
const responses = openAI;
const gemini = (schema) => GeminiToolSchema.convert(schema) ?? {};
const modelCompatibility = (schema, compatibility) => {
if (compatibility === undefined)
return schema;
switch (compatibility) {
case "gemini":
return gemini(schema);
case "moonshot":
return moonshot(schema);
}
};
export const ToolSchemaProjection = {
gemini,
modelCompatibility,
moonshot,
openAI,
responses,
};
import { Effect } from "effect";
import { AIError, LLMEvent, type ProviderMetadata } from "../../schema/index.js";
import { type ToolAccumulator } from "../shared.js";
type StreamKey = string | number;
/**
* One pending streamed tool call. Providers emit the tool identity and JSON
* argument text across separate chunks; `input` is the raw JSON string collected
* so far, not the parsed object.
*/
export interface PendingTool extends ToolAccumulator {
readonly providerExecuted?: boolean;
readonly providerMetadata?: ProviderMetadata;
}
/**
* Sparse parser state keyed by the provider's stream-local tool identifier.
*
* This key is not the final tool-call id (`call_...`). It is the id/index the
* provider uses while streaming a partial call: OpenAI Chat / Anthropic /
* Bedrock use numeric content indexes, while OpenAI Responses uses string
* `item_id`s. The generic keeps each protocol internally consistent.
*/
export type State<K extends StreamKey> = Partial<Record<K, PendingTool>>;
/**
* Result of adding argument text to one pending tool call. It returns both the
* next `tools` state and the updated `tool` because parsers often need the
* current id/name immediately. `events` contains lifecycle and delta events
* produced by the append; metadata-only deltas update identity without output.
*/
export interface AppendOutcome<K extends StreamKey> {
readonly tools: State<K>;
readonly tool: PendingTool;
readonly events: ReadonlyArray<LLMEvent>;
}
/** Create empty accumulator state for one provider stream. */
export declare const empty: <K extends StreamKey>() => State<K>;
export declare const isError: <K extends StreamKey>(result: AppendOutcome<K> | AIError) => result is AIError;
/**
* Register a tool call whose start event arrived before any argument deltas.
* Used by Anthropic `content_block_start`, Bedrock `contentBlockStart`, and
* OpenAI Responses `response.output_item.added`.
*/
export declare const start: <K extends StreamKey>(tools: State<K>, key: K, tool: Omit<PendingTool, "input"> & {
readonly input?: string;
}) => Partial<Record<K, PendingTool>>;
/**
* Append a streamed argument delta, starting the tool if this provider encodes
* identity on the first delta instead of a separate start event. OpenAI Chat has
* this shape: `tool_calls[].index` is the stream key, and `id` / `name` may only
* appear on the first delta for that index.
*/
export declare const appendOrStart: <K extends StreamKey>(route: string, tools: State<K>, key: K, delta: {
readonly id?: string;
readonly name?: string;
readonly text: string;
}, missingToolMessage: string) => AppendOutcome<K> | AIError;
/**
* Append argument text to a tool that must already have been started. This keeps
* protocols honest when their stream grammar promises a start event before any
* argument delta.
*/
export declare const appendExisting: <K extends StreamKey>(route: string, tools: State<K>, key: K, text: string, missingToolMessage: string) => AppendOutcome<K> | AIError;
/**
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
* from state, and return either a call or a non-executable local input error.
* Missing keys are a no-op because some providers emit stop events for
* non-tool content blocks.
*/
export declare const finish: <K extends StreamKey>(route: string, tools: State<K>, key: K) => Effect.Effect<{
tools: Partial<Record<K, PendingTool>>;
events?: undefined;
} | {
tools: Partial<Record<K, PendingTool>>;
events: readonly ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../../schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../../schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
}, AIError, never>;
/**
* Finalize one pending tool call with an authoritative final input string.
* OpenAI Responses can send accumulated deltas and then repeat the completed
* arguments on `response.output_item.done`; the final value wins.
*/
export declare const finishWithInput: <K extends StreamKey>(route: string, tools: State<K>, key: K, input: string) => Effect.Effect<{
tools: Partial<Record<K, PendingTool>>;
events?: undefined;
} | {
tools: Partial<Record<K, PendingTool>>;
events: readonly ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../../schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../../schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
}, AIError, never>;
/**
* Finalize every pending tool call at once. OpenAI Chat has this shape: it does
* not emit per-tool stop events, so all accumulated calls finish independently
* when the choice receives a terminal `finish_reason`.
*/
export declare const finishAll: <K extends StreamKey>(route: string, tools: State<K>) => Effect.Effect<{
tools: Partial<Record<K, PendingTool>>;
events: ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../../schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../../schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
}, AIError, never>;
export * as ToolStream from "./tool-stream.js";
import { Effect } from "effect";
import { AIError, LLMEvent } from "../../schema/index.js";
import { eventError, parseToolInput } from "../shared.js";
/** Create empty accumulator state for one provider stream. */
export const empty = () => ({});
const withTool = (tools, key, tool) => {
return { ...tools, [key]: tool };
};
const withoutTool = (tools, key) => {
const next = { ...tools };
delete next[key];
return next;
};
const inputStart = (tool) => LLMEvent.toolInputStart({
id: tool.id,
name: tool.name,
providerExecuted: tool.providerExecuted ? true : undefined,
providerMetadata: tool.providerMetadata,
});
const inputDelta = (tool, text) => LLMEvent.toolInputDelta({
id: tool.id,
name: tool.name,
text,
});
const toolCall = (route, tool, inputOverride) => {
const raw = inputOverride ?? tool.input;
return parseToolInput(route, tool.name, raw).pipe(Effect.map((input) => LLMEvent.toolCall({
id: tool.id,
name: tool.name,
input,
providerExecuted: tool.providerExecuted ? true : undefined,
providerMetadata: tool.providerMetadata,
})), Effect.catch((error) => tool.providerExecuted
? Effect.fail(error)
: Effect.succeed(LLMEvent.toolInputError({
id: tool.id,
name: tool.name,
raw,
}))));
};
const finishEvents = (tool, event) => event.type === "tool-input-error"
? [event]
: [LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }), event];
/** Store the updated tool and produce the optional public delta event. */
const appendTool = (tools, key, tool, text) => {
const events = [];
if (!tools[key])
events.push(inputStart(tool));
if (text.length > 0)
events.push(inputDelta(tool, text));
return {
tools: withTool(tools, key, tool),
tool,
events,
};
};
export const isError = (result) => result instanceof AIError;
/**
* Register a tool call whose start event arrived before any argument deltas.
* Used by Anthropic `content_block_start`, Bedrock `contentBlockStart`, and
* OpenAI Responses `response.output_item.added`.
*/
export const start = (tools, key, tool) => withTool(tools, key, { ...tool, input: tool.input ?? "" });
/**
* Append a streamed argument delta, starting the tool if this provider encodes
* identity on the first delta instead of a separate start event. OpenAI Chat has
* this shape: `tool_calls[].index` is the stream key, and `id` / `name` may only
* appear on the first delta for that index.
*/
export const appendOrStart = (route, tools, key, delta, missingToolMessage) => {
const current = tools[key];
const id = current?.id ?? delta.id;
const name = current?.name ?? delta.name;
if (!id || !name)
return eventError(route, missingToolMessage);
const tool = {
id,
name,
input: `${current?.input ?? ""}${delta.text}`,
providerExecuted: current?.providerExecuted,
providerMetadata: current?.providerMetadata,
};
if (current && delta.text.length === 0 && current.id === id && current.name === name)
return { tools, tool: current, events: [] };
return appendTool(tools, key, tool, delta.text);
};
/**
* Append argument text to a tool that must already have been started. This keeps
* protocols honest when their stream grammar promises a start event before any
* argument delta.
*/
export const appendExisting = (route, tools, key, text, missingToolMessage) => {
const current = tools[key];
if (!current)
return eventError(route, missingToolMessage);
if (text.length === 0)
return { tools, tool: current, events: [] };
return appendTool(tools, key, { ...current, input: `${current.input}${text}` }, text);
};
/**
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
* from state, and return either a call or a non-executable local input error.
* Missing keys are a no-op because some providers emit stop events for
* non-tool content blocks.
*/
export const finish = (route, tools, key) => Effect.gen(function* () {
const tool = tools[key];
if (!tool)
return { tools };
return {
tools: withoutTool(tools, key),
events: finishEvents(tool, yield* toolCall(route, tool)),
};
});
/**
* Finalize one pending tool call with an authoritative final input string.
* OpenAI Responses can send accumulated deltas and then repeat the completed
* arguments on `response.output_item.done`; the final value wins.
*/
export const finishWithInput = (route, tools, key, input) => Effect.gen(function* () {
const tool = tools[key];
if (!tool)
return { tools };
return {
tools: withoutTool(tools, key),
events: finishEvents(tool, yield* toolCall(route, tool, input)),
};
});
/**
* Finalize every pending tool call at once. OpenAI Chat has this shape: it does
* not emit per-tool stop events, so all accumulated calls finish independently
* when the choice receives a terminal `finish_reason`.
*/
export const finishAll = (route, tools) => Effect.gen(function* () {
const pending = Object.values(tools).filter((tool) => tool !== undefined);
return {
tools: empty(),
events: yield* Effect.forEach(pending, (tool) => toolCall(route, tool).pipe(Effect.map((event) => finishEvents(tool, event)))).pipe(Effect.map((events) => events.flat())),
};
});
export * as ToolStream from "./tool-stream.js";
import { ImageModel } from "../image.js";
import { type Definition as AuthDefinition } from "../route/auth.js";
import { type HttpOptions } from "../schema/index.js";
export declare const DEFAULT_BASE_URL = "https://api.x.ai/v1";
export declare const PATH = "/images/generations";
export declare const EDIT_PATH = "/images/edits";
export type XAIImageString<Known extends string> = Known | (string & {});
export type XAIImageOptions = {
readonly n?: number;
readonly aspectRatio?: XAIImageString<"1:1" | "3:4" | "4:3" | "9:16" | "16:9" | "2:3" | "3:2" | "9:19.5" | "19.5:9" | "9:20" | "20:9" | "1:2" | "2:1" | "auto">;
readonly aspect_ratio?: XAIImageString<"1:1" | "3:4" | "4:3" | "9:16" | "16:9" | "2:3" | "3:2" | "9:19.5" | "19.5:9" | "9:20" | "20:9" | "1:2" | "2:1" | "auto">;
readonly resolution?: XAIImageString<"1k" | "2k">;
readonly responseFormat?: XAIImageString<"url" | "b64_json">;
readonly response_format?: XAIImageString<"url" | "b64_json">;
} & Record<string, unknown>;
export interface ModelInput {
readonly id: string;
readonly auth: AuthDefinition;
readonly baseURL?: string;
readonly headers?: Record<string, string>;
readonly http?: HttpOptions;
}
export declare const model: (input: ModelInput) => ImageModel<XAIImageOptions>;
export declare const XAIImages: {
readonly model: (input: ModelInput) => ImageModel<XAIImageOptions>;
};
import { Effect, Encoding, Schema } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { GeneratedImage, ImageModel, ImageResponse } from "../image.js";
import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, Usage, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { ProviderShared, optionalNull } from "./shared.js";
import { ImageInputs } from "./utils/image-input.js";
const ADAPTER = "xai-images";
export const DEFAULT_BASE_URL = "https://api.x.ai/v1";
export const PATH = "/images/generations";
export const EDIT_PATH = "/images/edits";
const XAIImageResponse = Schema.Struct({
data: Schema.Array(Schema.Struct({
b64_json: optionalNull(Schema.String),
url: optionalNull(Schema.String),
revised_prompt: optionalNull(Schema.String),
mime_type: optionalNull(Schema.String),
})),
usage: Schema.optional(Schema.Unknown),
});
const nativeOptions = (options) => {
if (!options)
return undefined;
const { aspectRatio, responseFormat, ...native } = options;
return {
aspect_ratio: aspectRatio,
response_format: responseFormat,
...native,
};
};
const invalidOutput = (message) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
});
const applyQuery = (url, query) => {
if (!query)
return url;
const next = new URL(url);
Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
return next.toString();
};
export const model = (input) => {
const route = {
id: ADAPTER,
generate: Effect.fn("XAIImages.generate")(function* (request, execute) {
const http = mergeHttpOptions(request.model.http, request.http);
const imageReferences = (request.images ?? []).map((image) => {
if (image.type === "bytes")
return { url: ImageInputs.dataUrl(image), type: "image_url" };
if (image.type === "url")
return { url: image.url, type: "image_url" };
if (image.type === "file-id")
return { file_id: image.id };
return undefined;
});
if (imageReferences.some((image) => image === undefined))
return yield* ImageInputs.invalid(ADAPTER, "xAI Images accepts image URLs, data URLs, bytes, and file IDs");
const requestBody = mergeJsonRecords({
model: request.model.id,
prompt: request.prompt,
image: imageReferences.length === 1 ? imageReferences[0] : undefined,
images: imageReferences.length > 1 ? imageReferences : undefined,
}, nativeOptions(request.options), http?.body);
const text = ProviderShared.encodeJson(requestBody);
const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${imageReferences.length === 0 ? PATH : EDIT_PATH}`, http?.query);
const headers = yield* Auth.toEffect(input.auth)({
request,
method: "POST",
url,
body: text,
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
});
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the xAI Images response")));
const decoded = yield* Schema.decodeUnknownEffect(XAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("xAI Images returned an invalid response")));
const images = yield* Effect.forEach(decoded.data, (item, index) => {
const mediaType = item.mime_type ?? "application/octet-stream";
if (item.b64_json)
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(Effect.mapError(() => invalidOutput(`xAI Images result ${index} contains invalid base64 data`)), Effect.map((data) => new GeneratedImage({
mediaType,
data,
providerMetadata: item.revised_prompt === undefined || item.revised_prompt === null
? undefined
: { xai: { revisedPrompt: item.revised_prompt } },
})));
if (item.url)
return Effect.succeed(new GeneratedImage({
mediaType,
data: item.url,
providerMetadata: item.revised_prompt === undefined || item.revised_prompt === null
? undefined
: { xai: { revisedPrompt: item.revised_prompt } },
}));
return Effect.fail(invalidOutput(`xAI Images result ${index} has neither image data nor a URL`));
});
if (images.length === 0)
return yield* invalidOutput("xAI Images returned no images");
const usage = ProviderShared.isRecord(decoded.usage) ? decoded.usage : undefined;
return new ImageResponse({
images,
usage: usage === undefined ? undefined : new Usage({ providerMetadata: { xai: usage } }),
providerMetadata: usage === undefined ? undefined : { xai: { usage } },
});
}),
};
return ImageModel.make({ id: input.id, provider: "xai", route, http: input.http });
};
export const XAIImages = {
model,
};
import { ImageModel } from "../image.js";
import { type Definition as AuthDefinition } from "../route/auth.js";
import { type HttpOptions } from "../schema/index.js";
export declare const DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
export declare const PATH = "/images/generations";
export type ZAIImageString<Known extends string> = Known | (string & {});
export type ZAIImageOptions = {
readonly size?: ZAIImageString<"1024x1024" | "768x1344" | "864x1152" | "1344x768" | "1152x864" | "1440x720" | "720x1440">;
readonly quality?: ZAIImageString<"hd" | "standard">;
readonly userID?: string;
} & Record<string, unknown>;
export interface ModelInput {
readonly id: string;
readonly auth: AuthDefinition;
readonly baseURL?: string;
readonly headers?: Record<string, string>;
readonly http?: HttpOptions;
}
export declare const model: (input: ModelInput) => ImageModel<ZAIImageOptions>;
export declare const ZAIImages: {
readonly model: (input: ModelInput) => ImageModel<ZAIImageOptions>;
};
import { Effect, Schema } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { GeneratedImage, ImageModel, ImageResponse } from "../image.js";
import { Auth } from "../route/auth.js";
import { InvalidProviderOutputReason, AIError, mergeHttpOptions, mergeJsonRecords, } from "../schema/index.js";
import { ProviderShared } from "./shared.js";
import { ImageInputs } from "./utils/image-input.js";
const ADAPTER = "zai-images";
export const DEFAULT_BASE_URL = "https://api.z.ai/api/paas/v4";
export const PATH = "/images/generations";
const ZAIImageResponse = Schema.Struct({
created: Schema.optional(Schema.Int),
id: Schema.optional(Schema.String),
request_id: Schema.optional(Schema.String),
data: Schema.Array(Schema.Struct({ url: Schema.String })),
content_filter: Schema.optional(Schema.Array(Schema.Struct({
role: Schema.optional(Schema.String),
level: Schema.optional(Schema.Number),
}))),
});
const nativeOptions = (options) => {
if (!options)
return undefined;
const { userID, ...native } = options;
return {
user_id: userID,
...native,
};
};
const invalidOutput = (message) => new AIError({
module: ADAPTER,
method: "generate",
reason: new InvalidProviderOutputReason({ message, route: ADAPTER }),
});
const applyQuery = (url, query) => {
if (!query)
return url;
const next = new URL(url);
Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
return next.toString();
};
export const model = (input) => {
const route = {
id: ADAPTER,
generate: Effect.fn("ZAIImages.generate")(function* (request, execute) {
if ((request.images?.length ?? 0) > 0)
return yield* ImageInputs.invalid(ADAPTER, "Z.ai hosted image generation does not support image inputs");
const http = mergeHttpOptions(request.model.http, request.http);
const requestBody = mergeJsonRecords({ model: request.model.id, prompt: request.prompt }, nativeOptions(request.options), http?.body);
const text = ProviderShared.encodeJson(requestBody);
const url = applyQuery(`${(input.baseURL ?? DEFAULT_BASE_URL).replace(/\/$/, "")}${PATH}`, http?.query);
const headers = yield* Auth.toEffect(input.auth)({
request,
method: "POST",
url,
body: text,
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
});
const response = yield* execute(HttpClientRequest.post(url).pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(text, "application/json")));
const payload = yield* response.json.pipe(Effect.mapError(() => invalidOutput("Failed to read the Z.ai Images response")));
const decoded = yield* Schema.decodeUnknownEffect(ZAIImageResponse)(payload).pipe(Effect.mapError(() => invalidOutput("Z.ai Images returned an invalid response")));
if (decoded.data.length === 0)
return yield* invalidOutput("Z.ai Images returned no images");
return new ImageResponse({
images: decoded.data.map((item) => new GeneratedImage({
mediaType: "application/octet-stream",
data: item.url,
})),
providerMetadata: {
zai: {
created: decoded.created,
id: decoded.id,
requestID: decoded.request_id,
contentFilter: decoded.content_filter,
},
},
});
}),
};
return ImageModel.make({ id: input.id, provider: "zai", route, http: input.http });
};
export const ZAIImages = {
model,
};
import { AIError, type HttpContext, type HttpRateLimitDetails, type ProviderMetadata } from "./schema/index.js";
export declare const isContextOverflow: (message: string) => boolean;
export declare const isPayloadTooLarge: (message: string) => boolean;
export declare const isContextOverflowFailure: (failure: unknown) => boolean;
export interface ProviderFailure {
readonly message: string;
readonly status?: number | undefined;
readonly code?: string | undefined;
readonly retryAfterMs?: number | undefined;
readonly rateLimit?: HttpRateLimitDetails | undefined;
readonly http?: HttpContext | undefined;
readonly providerMetadata?: ProviderMetadata | undefined;
}
export declare function classifyProviderFailure(input: ProviderFailure): AIError["reason"];
import { Option, Schema } from "effect";
import { AuthenticationReason, ContentPolicyReason, InvalidRequestReason, AIError, ProviderErrorEvent, ProviderInternalReason, QuotaExceededReason, RateLimitReason, UnknownProviderReason, } from "./schema/index.js";
const patterns = [
/prompt is too long/i,
/input is too long for requested model/i,
/exceeds the context window/i,
/exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i,
/input token count.*exceeds the maximum/i,
/tokens in request more than max tokens allowed/i,
/maximum prompt length is \d+/i,
/reduce the length of the messages/i,
/maximum context length is \d+ tokens/i,
/exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i,
/input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i,
/exceeds the limit of \d+/i,
/exceeds the available context size/i,
/greater than the context length/i,
/context window exceeds limit/i,
/exceeded model token limit/i,
/context[_ ]length[_ ]exceeded/i,
/context length is only \d+ tokens/i,
/input length.*exceeds.*context length/i,
/prompt too long; exceeded (?:max )?context length/i,
/too large for model with \d+ maximum context length/i,
/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,
/model_context_window_exceeded/i,
/too many tokens/i,
/token limit exceeded/i,
];
const payloadPatterns = [/request_too_large/i, /request entity too large/i, /payload too large/i, /request too large/i];
const exclusions = [/^(throttling error|service unavailable):/i, /rate limit/i, /too many requests/i];
export const isContextOverflow = (message) => !exclusions.some((pattern) => pattern.test(message)) &&
(patterns.some((pattern) => pattern.test(message)) || /^400\s*(status code)?\s*\(no body\)/i.test(message));
export const isPayloadTooLarge = (message) => payloadPatterns.some((pattern) => pattern.test(message));
export const isContextOverflowFailure = (failure) => failure instanceof AIError
? failure.reason._tag === "InvalidRequest" && failure.reason.classification === "context-overflow"
: Schema.is(ProviderErrorEvent)(failure) && failure.classification === "context-overflow";
const decodeJson = Schema.decodeUnknownOption(Schema.UnknownFromJsonString);
const QUOTA_CODES = new Set(["insufficient_quota", "usage_not_included", "billing_error"]);
const SERVER_CODES = new Set([
"api_error",
"internal_error",
"internalserverexception",
"modelstreamerrorexception",
"overloaded_error",
"server_error",
"server_is_overloaded",
"slow_down",
"serviceunavailableexception",
]);
const INVALID_REQUEST_CODES = new Set(["invalid_prompt", "invalid_request_error", "validationexception"]);
const RATE_LIMIT_TEXT = /rate increased too quickly|rate[-_\s]?limit|too[_\s]?many[_\s]?requests/i;
const QUOTA_TEXT = /insufficient[-_\s]?quota|quota[-_\s]?exceeded/i;
const CONTENT_POLICY_TEXT = /content[-_\s]?policy|content_filter|safety/i;
// Keep HTTP failures and provider-reported stream failures on one typed path so
// session retry policy never needs provider-specific string matching.
export function classifyProviderFailure(input) {
const body = input.http?.body ?? "";
const codes = [input.code, ...providerCodes(body), ...providerCodes(input.message)]
.filter((code) => code !== undefined)
.map((code) => code.toLowerCase());
const text = body || input.message;
const common = { message: input.message, providerMetadata: input.providerMetadata, http: input.http };
const clientScoped = input.status === undefined || (input.status >= 400 && input.status < 500);
if (clientScoped &&
(codes.includes("context_length_exceeded") ||
codes.includes("model_context_window_exceeded") ||
isContextOverflow(text)))
return new InvalidRequestReason({ ...common, classification: "context-overflow" });
if (input.status === 413 || isPayloadTooLarge(text))
return new InvalidRequestReason({ ...common, classification: "payload-too-large" });
if (CONTENT_POLICY_TEXT.test(text))
return new ContentPolicyReason(common);
if (codes.some((code) => QUOTA_CODES.has(code)) || (input.status === 429 && QUOTA_TEXT.test(text)))
return new QuotaExceededReason(common);
if (input.status === 401)
return new AuthenticationReason({ ...common, kind: "invalid" });
if (input.status === 403)
return new AuthenticationReason({ ...common, kind: "insufficient-permissions" });
if (codes.includes("authentication_error"))
return new AuthenticationReason({ ...common, kind: "invalid" });
if (codes.includes("permission_error"))
return new AuthenticationReason({ ...common, kind: "insufficient-permissions" });
if (codes.some((code) => code.includes("rate_limit") || code === "too_many_requests" || code === "throttlingexception"))
return new RateLimitReason({
...common,
retryAfterMs: input.retryAfterMs,
rateLimit: input.rateLimit,
});
if (RATE_LIMIT_TEXT.test(text))
return new RateLimitReason({
...common,
retryAfterMs: input.retryAfterMs,
rateLimit: input.rateLimit,
});
if (codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable")))
return new ProviderInternalReason({
...common,
status: input.status,
retryAfterMs: input.retryAfterMs,
});
if (input.status === 429) {
return new RateLimitReason({
...common,
retryAfterMs: input.retryAfterMs,
rateLimit: input.rateLimit,
});
}
if (input.status === 408 || input.status === 409 || (input.status !== undefined && input.status >= 500))
return new ProviderInternalReason({
...common,
status: input.status,
retryAfterMs: input.retryAfterMs,
});
if (codes.some((code) => INVALID_REQUEST_CODES.has(code)))
return new InvalidRequestReason(common);
if (input.status === 400 || input.status === 404 || input.status === 413 || input.status === 422)
return new InvalidRequestReason(common);
return new UnknownProviderReason({ ...common, status: input.status });
}
function providerCodes(value) {
const decoded = Option.getOrUndefined(decodeJson(value));
if (!isRecord(decoded))
return [];
const error = isRecord(decoded.error) ? decoded.error : undefined;
return [decoded.code, error?.code, error?.type].filter((value) => typeof value === "string");
}
function isRecord(value) {
return typeof value === "object" && value !== null;
}
import type { LanguageModel, ProviderOptions } from "./schema/index.js";
export interface Settings extends Readonly<Record<string, unknown>> {
readonly baseURL?: string;
readonly headers?: Readonly<Record<string, string>>;
readonly body?: Readonly<Record<string, unknown>>;
readonly limits?: {
readonly context: number;
readonly input?: number;
readonly output: number;
};
}
export interface Definition<ProviderSettings extends Settings = Settings, Options extends ProviderOptions = ProviderOptions> {
readonly model: (modelID: string, settings: ProviderSettings) => LanguageModel<Options>;
}
export * as ProviderPackage from "./provider-package.js";
export * as ProviderPackage from "./provider-package.js";
import type { LanguageModel, ModelID, ProviderID } from "./schema/index.js";
export type LanguageModelOptions = Pick<LanguageModel.Input, "defaults" | "compatibility">;
/**
* Advanced structural provider definition helper. Built-in providers should
* prefer explicit `configure(options).model(id)` facades so deployment config is
* chosen before model selection. The optional `apis` map remains for external
* structural providers that expose multiple route selectors behind one provider.
*/
export type LanguageModelFactory<Options extends LanguageModelOptions = LanguageModelOptions> = (id: string | ModelID, options?: Options) => LanguageModel;
type AnyLanguageModelFactory = (...args: never[]) => LanguageModel;
export interface Definition<Factory extends AnyLanguageModelFactory = LanguageModelFactory> {
readonly id: ProviderID;
readonly model: Factory;
readonly apis?: Record<string, AnyLanguageModelFactory>;
}
type DefinitionShape = {
readonly id: ProviderID;
readonly model: (...args: never[]) => LanguageModel;
readonly apis?: Record<string, (...args: never[]) => LanguageModel>;
};
type NoExtraFields<Input, Shape> = Input & Record<Exclude<keyof Input, keyof Shape>, never>;
export declare const make: <DefinitionType extends DefinitionShape>(definition: NoExtraFields<DefinitionType, DefinitionShape>) => NoExtraFields<DefinitionType, DefinitionShape>;
export * as Provider from "./provider.js";
export const make = (definition) => definition;
export * as Provider from "./provider.js";
export * from "./providers/index.js";
export * from "./providers/index.js";
import type { Route as RouteDef, RouteDefaultsInput } from "../route/client.js";
import type { ProviderPackage } from "../provider-package.js";
import { type Credentials } from "../protocols/utils/bedrock-auth.js";
import { type ModelID } from "../schema/index.js";
import { type OpenAIProviderOptionsInput } from "./openai-options.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & {
readonly apiKey?: string;
readonly baseURL?: string;
readonly credentials?: Credentials;
readonly region?: string;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly auth?: "bearer" | "sigv4";
readonly baseURL?: string;
readonly credentials?: Credentials;
readonly region?: string;
readonly providerOptions?: OpenAIProviderOptionsInput;
}
export declare const routes: (RouteDef<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>> | RouteDef<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("../protocols/open-responses-channel.js").Prepared>)[];
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const chatModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
export declare const responsesModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
export declare const model: (modelID: string, settings: Settings) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
import { Auth } from "../route/auth.js";
import { OpenAIChat } from "../protocols/openai-chat.js";
import { OpenAIResponses } from "../protocols/openai-responses.js";
import { BedrockAuth } from "../protocols/utils/bedrock-auth.js";
import { ProviderID } from "../schema/index.js";
import { withOpenAIOptions } from "./openai-options.js";
export const id = ProviderID.make("amazon-bedrock");
const responsesRoute = OpenAIResponses.route.with({
id: "bedrock-mantle-responses",
provider: id,
});
const chatRoute = OpenAIChat.route.with({
id: "bedrock-mantle-chat",
provider: id,
});
export const routes = [responsesRoute, chatRoute];
const configuredRoute = (route, input) => {
const region = input.region ?? input.credentials?.region ?? "us-east-1";
const credentials = input.credentials === undefined ? undefined : { ...input.credentials, region };
return route.with({
endpoint: { baseURL: input.baseURL ?? `https://bedrock-mantle.${region}.api.aws/v1` },
auth: input.apiKey === undefined
? BedrockAuth.sigV4(credentials, { service: "bedrock-mantle", name: "Bedrock Mantle" })
: Auth.bearer(input.apiKey),
});
};
const defaults = (input) => {
const { apiKey: _, baseURL: _baseURL, credentials: _credentials, region: _region, ...rest } = input;
return rest;
};
export const configure = (input = {}) => {
const configuredResponsesRoute = configuredRoute(responsesRoute, input);
const configuredChatRoute = configuredRoute(chatRoute, input);
const modelDefaults = defaults(input);
const responses = (modelID) => configuredResponsesRoute
.with(withOpenAIOptions(modelID, modelDefaults))
.model({ id: modelID });
const chat = (modelID) => configuredChatRoute
.with(withOpenAIOptions(modelID, modelDefaults))
.model({ id: modelID });
return {
id,
model: chat,
chat,
responses,
configure,
};
};
export const provider = configure();
const config = (settings) => {
if (settings.auth === "bearer" && settings.apiKey === undefined)
throw new Error("Amazon Bedrock Mantle bearer auth requires apiKey");
if (settings.auth === "sigv4" && settings.apiKey !== undefined)
throw new Error("Amazon Bedrock Mantle SigV4 auth does not accept apiKey");
return {
apiKey: settings.auth === "sigv4" ? undefined : settings.apiKey,
baseURL: settings.baseURL,
credentials: settings.credentials,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
region: settings.region,
};
};
export const chatModel = (modelID, settings) => configure(config(settings)).chat(modelID);
export const responsesModel = (modelID, settings) => configure(config(settings)).responses(modelID);
export const model = chatModel;
import type { RouteDefaultsInput } from "../route/client.js";
import type { ProviderPackage } from "../provider-package.js";
import { type ModelID } from "../schema/index.js";
import type { BedrockCredentials } from "../protocols/bedrock-converse.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & {
readonly apiKey?: string;
readonly headers?: Record<string, string>;
readonly credentials?: BedrockCredentials;
/** AWS region. Defaults to `us-east-1` when neither this nor `credentials.region` is set. */
readonly region?: string;
/** Override the computed `https://bedrock-runtime.<region>.amazonaws.com` URL. */
readonly baseURL?: string;
};
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly auth?: "bearer" | "sigv4";
readonly baseURL?: string;
readonly credentials?: BedrockCredentials;
readonly region?: string;
readonly topP?: number;
}
export declare const routes: import("../route/client.js").Route<{
readonly messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly image: {
readonly format: "png" | "jpeg" | "gif" | "webp";
readonly source: {
readonly bytes: string;
};
};
} | {
readonly document: {
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
readonly name: string;
readonly source: {
readonly bytes: string;
};
};
} | {
readonly text: string;
} | {
readonly toolResult: {
readonly content: readonly ({
readonly image: {
readonly format: "png" | "jpeg" | "gif" | "webp";
readonly source: {
readonly bytes: string;
};
};
} | {
readonly document: {
readonly format: "pdf" | "csv" | "doc" | "docx" | "xls" | "xlsx" | "html" | "txt" | "md";
readonly name: string;
readonly source: {
readonly bytes: string;
};
};
} | {
readonly text: string;
} | {
readonly json: unknown;
})[];
readonly toolUseId: string;
readonly status?: "error" | "success" | undefined;
};
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly text: string;
} | {
readonly toolUse: {
readonly toolUseId: string;
readonly name: string;
readonly input: unknown;
};
} | {
readonly reasoningContent: {
readonly reasoningText: {
readonly text: string;
readonly signature?: string | undefined;
};
} | {
readonly redactedContent: string;
};
})[];
})[];
readonly modelId: string;
readonly system?: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly text: string;
})[] | undefined;
readonly inferenceConfig?: {
readonly maxTokens?: number | undefined;
readonly temperature?: number | undefined;
readonly topP?: number | undefined;
readonly stopSequences?: readonly string[] | undefined;
} | undefined;
readonly toolConfig?: {
readonly tools: readonly ({
readonly cachePoint: {
readonly type: "default";
readonly ttl?: "1h" | "5m" | undefined;
};
} | {
readonly toolSpec: {
readonly name: string;
readonly description: string;
readonly inputSchema: {
readonly json: {
readonly [x: string]: unknown;
};
};
};
})[];
readonly toolChoice?: {
readonly auto: {};
} | {
readonly any: {};
} | {
readonly tool: {
readonly name: string;
};
} | undefined;
} | undefined;
readonly additionalModelRequestFields?: {
readonly [x: string]: unknown;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<object>>[];
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings>["model"];
import { Auth } from "../route/auth.js";
import { ProviderID } from "../schema/index.js";
import * as BedrockConverse from "../protocols/bedrock-converse.js";
export const id = ProviderID.make("amazon-bedrock");
export const routes = [BedrockConverse.route];
const bedrockBaseURL = (region) => `https://bedrock-runtime.${region}.amazonaws.com`;
const configuredRoute = (input) => {
const { apiKey, credentials, region, baseURL, ...rest } = input;
const resolvedRegion = region ?? credentials?.region ?? "us-east-1";
return BedrockConverse.route.with({
...rest,
provider: id,
endpoint: { baseURL: baseURL ?? bedrockBaseURL(resolvedRegion) },
auth: apiKey === undefined ? BedrockConverse.sigV4Auth(credentials) : Auth.bearer(apiKey),
});
};
export const configure = (input = {}) => {
const route = configuredRoute(input);
return {
id,
model: (modelID) => route.model({ id: modelID }),
configure,
};
};
export const provider = configure();
export const model = (modelID, settings) => {
if (settings.auth === "bearer" && settings.apiKey === undefined)
throw new Error("Amazon Bedrock bearer auth requires apiKey");
if (settings.auth === "sigv4" && settings.apiKey !== undefined)
throw new Error("Amazon Bedrock SigV4 auth does not accept apiKey");
return configure({
apiKey: settings.auth === "sigv4" ? undefined : settings.apiKey,
baseURL: settings.baseURL,
credentials: settings.credentials,
generation: settings.topP === undefined ? undefined : { topP: settings.topP },
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
region: settings.region,
}).model(modelID);
};
export { chatModel as model } from "../amazon-bedrock-mantle.js";
export type { Settings } from "../amazon-bedrock-mantle.js";
export { chatModel as model } from "../amazon-bedrock-mantle.js";
export { chatModel as model } from "../../amazon-bedrock-mantle.js";
export type { Settings } from "../../amazon-bedrock-mantle.js";
export { chatModel as model } from "../../amazon-bedrock-mantle.js";
export { responsesModel as model } from "../../amazon-bedrock-mantle.js";
export type { Settings } from "../../amazon-bedrock-mantle.js";
export { responsesModel as model } from "../../amazon-bedrock-mantle.js";
import type { ProviderPackage } from "../provider-package.js";
import { AnthropicMessages } from "../protocols/anthropic-messages.js";
import type { ProviderAuthOption } from "../route/auth-options.js";
import type { RouteDefaultsInput } from "../route/client.js";
import { type ModelID } from "../schema/index.js";
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput;
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput;
export type AnthropicThinkingInput = AnthropicMessages.ThinkingInput;
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & ProviderAuthOption<"optional"> & {
readonly provider?: string;
readonly baseURL: string;
readonly providerOptions?: AnthropicMessages.ProviderOptionsInput;
};
export type Settings = ProviderPackage.Settings & ({
readonly apiKey?: string;
readonly authToken?: never;
} | {
readonly apiKey?: never;
readonly authToken?: string;
}) & {
readonly baseURL: string;
readonly provider?: string;
readonly providerOptions?: AnthropicMessages.ProviderOptionsInput;
};
export declare const routes: import("../route/client.js").Route<{
readonly max_tokens: number;
readonly model: string;
readonly messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "tool_result";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly is_error?: boolean | undefined;
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "server_tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "web_search_tool_result" | "code_execution_tool_result" | "web_fetch_tool_result";
readonly content: unknown;
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "thinking";
readonly thinking: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly signature?: string | undefined;
} | {
readonly data: string;
readonly type: "redacted_thinking";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
} | {
readonly role: "system";
readonly content: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[];
})[];
readonly stream: true;
readonly system?: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly tools?: readonly {
readonly description: string;
readonly name: string;
readonly input_schema: {
readonly [x: string]: unknown;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly thinking?: {
readonly type: "enabled";
readonly budget_tokens: number;
} | {
readonly type: "adaptive";
readonly display?: "summarized" | "omitted" | undefined;
} | {
readonly type: "disabled";
} | undefined;
readonly tool_choice?: {
readonly type: "none" | "auto" | "any";
} | {
readonly type: "tool";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly top_k?: number | undefined;
readonly stop_sequences?: readonly string[] | undefined;
readonly output_config?: {
readonly effort?: string | undefined;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<AnthropicMessages.ProviderOptionsInput>;
configure: (input: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<AnthropicMessages.ProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, AnthropicMessages.ProviderOptionsInput>["model"];
export * as AnthropicCompatible from "./anthropic-compatible.js";
import { AnthropicMessages } from "../protocols/anthropic-messages.js";
import { Auth } from "../route/auth.js";
import { ProviderID } from "../schema/index.js";
export const id = ProviderID.make("anthropic-compatible");
export const routes = [AnthropicMessages.route];
const auth = (input) => {
if ("auth" in input && input.auth)
return input.auth;
return Auth.optional("apiKey" in input ? input.apiKey : undefined, "apiKey").pipe(Auth.header("x-api-key"));
};
export const configure = (input) => {
if (!input.baseURL)
throw new Error("Anthropic-compatible providers require a baseURL");
const provider = input.provider ?? "anthropic-compatible";
const { provider: _, baseURL, apiKey: _apiKey, auth: _auth, ...rest } = input;
const route = AnthropicMessages.route.with({
...rest,
provider,
endpoint: { baseURL },
auth: auth(input),
});
return {
id: ProviderID.make(provider),
model: (modelID) => route.model({ id: modelID }),
configure,
};
};
export const provider = {
id,
configure,
};
export const model = (modelID, settings) => {
if (settings.apiKey !== undefined && settings.authToken !== undefined)
throw new Error("Anthropic-compatible apiKey cannot be combined with authToken");
return configure({
...(settings.authToken === undefined ? { apiKey: settings.apiKey } : { auth: Auth.bearer(settings.authToken) }),
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
provider: settings.provider,
providerOptions: settings.providerOptions,
}).model(modelID);
};
export * as AnthropicCompatible from "./anthropic-compatible.js";
import type { RouteDefaultsInput } from "../route/client.js";
import type { ProviderAuthOption } from "../route/auth-options.js";
import type { ProviderPackage } from "../provider-package.js";
import { type ModelID } from "../schema/index.js";
import { AnthropicMessages } from "../protocols/anthropic-messages.js";
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput;
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput;
export type AnthropicThinkingInput = AnthropicMessages.ThinkingInput;
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export declare const routes: import("../route/client.js").Route<{
readonly max_tokens: number;
readonly model: string;
readonly messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "tool_result";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly is_error?: boolean | undefined;
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "server_tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "web_search_tool_result" | "code_execution_tool_result" | "web_fetch_tool_result";
readonly content: unknown;
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "thinking";
readonly thinking: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly signature?: string | undefined;
} | {
readonly data: string;
readonly type: "redacted_thinking";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
} | {
readonly role: "system";
readonly content: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[];
})[];
readonly stream: true;
readonly system?: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly tools?: readonly {
readonly description: string;
readonly name: string;
readonly input_schema: {
readonly [x: string]: unknown;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly thinking?: {
readonly type: "enabled";
readonly budget_tokens: number;
} | {
readonly type: "adaptive";
readonly display?: "summarized" | "omitted" | undefined;
} | {
readonly type: "disabled";
} | undefined;
readonly tool_choice?: {
readonly type: "none" | "auto" | "any";
} | {
readonly type: "tool";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly top_k?: number | undefined;
readonly stop_sequences?: readonly string[] | undefined;
readonly output_config?: {
readonly effort?: string | undefined;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export type Config = RouteDefaultsInput & ProviderAuthOption<"optional"> & {
readonly baseURL?: string;
readonly providerOptions?: AnthropicMessages.ProviderOptionsInput;
};
export type Settings = ProviderPackage.Settings & ({
readonly apiKey?: string;
readonly authToken?: never;
} | {
readonly apiKey?: never;
readonly authToken?: string;
}) & {
readonly baseURL?: string;
readonly providerOptions?: AnthropicMessages.ProviderOptionsInput;
};
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<AnthropicMessages.ProviderOptionsInput>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<AnthropicMessages.ProviderOptionsInput>;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<AnthropicMessages.ProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, AnthropicMessages.ProviderOptionsInput>["model"];
import { Auth } from "../route/auth.js";
import { ProviderID } from "../schema/index.js";
import { AnthropicMessages } from "../protocols/anthropic-messages.js";
import { AnthropicCompatible } from "./anthropic-compatible.js";
export const id = ProviderID.make("anthropic");
export const routes = [AnthropicMessages.route];
const auth = (options) => {
if ("auth" in options && options.auth)
return options.auth;
return Auth.optional("apiKey" in options ? options.apiKey : undefined, "apiKey")
.orElse(Auth.config("ANTHROPIC_API_KEY"))
.pipe(Auth.header("x-api-key"));
};
export const configure = (input = {}) => {
const { apiKey: _, auth: _auth, baseURL, ...rest } = input;
const compatible = AnthropicCompatible.configure({
...rest,
auth: auth(input),
baseURL: baseURL ?? AnthropicMessages.DEFAULT_BASE_URL,
provider: id,
});
return {
id,
model: (modelID) => compatible.model(modelID),
configure,
};
};
export const provider = configure();
export const model = (modelID, settings) => {
if (settings.apiKey !== undefined && settings.authToken !== undefined)
throw new Error("Anthropic apiKey cannot be combined with authToken");
return configure({
...(settings.authToken === undefined ? { apiKey: settings.apiKey } : { auth: Auth.bearer(settings.authToken) }),
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
}).model(modelID);
};
import { type AtLeastOne, type ProviderAuthOption } from "../route/auth-options.js";
import type { Route as RouteDef, RouteDefaultsInput } from "../route/client.js";
import type { ProviderPackage } from "../provider-package.js";
import { type ModelID } from "../schema/index.js";
import { type OpenAIProviderOptionsInput } from "./openai-options.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
type AzureURL = AtLeastOne<{
readonly resourceName: string;
readonly baseURL: string;
}>;
export type LanguageModelOptions = AzureURL & RouteDefaultsInput & ProviderAuthOption<"optional"> & {
readonly apiVersion?: string;
readonly queryParams?: Record<string, string>;
readonly useDeploymentBasedUrls?: boolean;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export type Config = LanguageModelOptions;
export type Settings = ProviderPackage.Settings & AzureURL & {
readonly apiKey?: string;
readonly apiVersion?: string;
readonly queryParams?: Readonly<Record<string, string>>;
readonly useDeploymentBasedUrls?: boolean;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export declare const routes: (RouteDef<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>> | RouteDef<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("../protocols/open-responses-channel.js").Prepared>)[];
export declare const configure: (input: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const responsesModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
export declare const chatModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
export declare const model: (modelID: string, settings: Settings) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
export {};
import { Auth } from "../route/auth.js";
import {} from "../route/auth-options.js";
import { ProviderID } from "../schema/index.js";
import * as OpenAIChat from "../protocols/openai-chat.js";
import * as OpenAIResponses from "../protocols/openai-responses.js";
import { ProviderShared } from "../protocols/shared.js";
import { withOpenAIOptions } from "./openai-options.js";
export const id = ProviderID.make("azure");
const routeAuth = Auth.remove("authorization");
const resourceBaseURL = (resourceName) => `https://${resourceName.trim()}.openai.azure.com/openai`;
const responsesRoute = OpenAIResponses.route.with({
id: "azure-openai-responses",
provider: id,
auth: routeAuth,
});
const chatRoute = OpenAIChat.route.with({
id: "azure-openai-chat",
provider: id,
auth: routeAuth,
});
export const routes = [responsesRoute, chatRoute];
const defaults = (input) => {
const { apiKey: _, apiVersion: _apiVersion, resourceName: _resourceName, useDeploymentBasedUrls: _useDeploymentBasedUrls, baseURL: _baseURL, queryParams: _queryParams, ...rest } = input;
if ("auth" in rest) {
const { auth: _, ...withoutAuth } = rest;
return withoutAuth;
}
return rest;
};
const auth = (input) => {
if ("auth" in input && input.auth)
return input.auth;
return Auth.remove("authorization").andThen(Auth.optional("apiKey" in input ? input.apiKey : undefined, "apiKey")
.orElse(Auth.config("AZURE_OPENAI_API_KEY"))
.pipe(Auth.header("api-key")));
};
const configuredRoute = (route, input, modelID) => route.with({
auth: auth(input),
endpoint: endpoint(input, modelID),
});
function endpoint(input, modelID) {
const baseURL = ProviderShared.trimBaseUrl(input.baseURL ?? resourceBaseURL(input.resourceName));
const query = { "api-version": input.apiVersion ?? "v1", ...input.queryParams };
if (input.useDeploymentBasedUrls)
return { baseURL: `${baseURL}/deployments/${modelID}`, query };
if (input.baseURL !== undefined && !new URL(input.baseURL).hostname.endsWith(".openai.azure.com")) {
return { baseURL, query: input.queryParams };
}
return { baseURL: `${baseURL}/v1`, query };
}
export const configure = (input) => {
const modelDefaults = defaults(input);
const responses = (modelID) => configuredRoute(responsesRoute, input, modelID)
.with(withOpenAIOptions(modelID, modelDefaults))
.model({ id: modelID });
const chat = (modelID) => configuredRoute(chatRoute, input, modelID)
.with(withOpenAIOptions(modelID, modelDefaults))
.model({ id: modelID });
return {
id,
model: responses,
responses,
chat,
configure,
};
};
export const provider = {
id,
configure,
};
const config = (settings) => {
const common = {
apiKey: settings.apiKey,
apiVersion: settings.apiVersion,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
queryParams: settings.queryParams === undefined ? undefined : { ...settings.queryParams },
useDeploymentBasedUrls: settings.useDeploymentBasedUrls,
};
if (settings.baseURL !== undefined)
return { ...common, baseURL: settings.baseURL };
if (settings.resourceName !== undefined)
return { ...common, resourceName: settings.resourceName };
throw new Error("Azure requires resourceName or baseURL");
};
export const responsesModel = (modelID, settings) => configure(config(settings)).responses(modelID);
export const chatModel = (modelID, settings) => configure(config(settings)).chat(modelID);
export const model = responsesModel;
export { chatModel as model } from "../azure.js";
export type { Settings } from "../azure.js";
export { chatModel as model } from "../azure.js";
export { responsesModel as model } from "../azure.js";
export type { Settings } from "../azure.js";
export { responsesModel as model } from "../azure.js";
import type { Config, Redacted } from "effect";
import { type AtLeastOne, type ProviderAuthOption } from "../route/auth-options.js";
import type { RouteDefaultsInput } from "../route/client.js";
import { type ModelID } from "../schema/index.js";
import type { OpenAIProviderOptionsInput } from "./openai-options.js";
export declare const aiGatewayID: string & import("effect/Brand").Brand<"AI.ProviderID">;
export declare const workersAIID: string & import("effect/Brand").Brand<"AI.ProviderID">;
export declare const aiGatewayAuthEnvVars: readonly ["CLOUDFLARE_API_TOKEN", "CF_AIG_TOKEN"];
export declare const workersAIAuthEnvVars: readonly ["CLOUDFLARE_API_KEY", "CLOUDFLARE_WORKERS_AI_TOKEN"];
type CloudflareSecret = string | Redacted.Redacted | Config.Config<string | Redacted.Redacted>;
type GatewayURL = AtLeastOne<{
readonly accountId: string;
readonly baseURL: string;
}> & {
readonly gatewayId?: string;
};
export type AIGatewayOptions = GatewayURL & Omit<RouteDefaultsInput, "providerOptions"> & ProviderAuthOption<"optional"> & {
/** Cloudflare AI Gateway authentication token. Sent as `cf-aig-authorization`. */
readonly gatewayApiKey?: CloudflareSecret;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
type WorkersAIURL = AtLeastOne<{
readonly accountId: string;
readonly baseURL: string;
}>;
export type WorkersAIOptions = WorkersAIURL & Omit<RouteDefaultsInput, "providerOptions"> & ProviderAuthOption<"optional"> & {
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export declare const aiGatewayBaseURL: (input: GatewayURL) => string;
export declare const workersAIBaseURL: (input: WorkersAIURL) => string;
export declare const aiGatewayRoute: import("../route/client.js").Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>;
export declare const workersAIRoute: import("../route/client.js").Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>;
export declare const routes: import("../route/client.js").Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const CloudflareAIGateway: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (options: AIGatewayOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const CloudflareWorkersAI: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (options: WorkersAIOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export {};
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js";
import { Auth } from "../route/auth.js";
import { AuthOptions } from "../route/auth-options.js";
import { ProviderID } from "../schema/index.js";
export const aiGatewayID = ProviderID.make("cloudflare-ai-gateway");
export const workersAIID = ProviderID.make("cloudflare-workers-ai");
export const aiGatewayAuthEnvVars = ["CLOUDFLARE_API_TOKEN", "CF_AIG_TOKEN"];
export const workersAIAuthEnvVars = ["CLOUDFLARE_API_KEY", "CLOUDFLARE_WORKERS_AI_TOKEN"];
export const aiGatewayBaseURL = (input) => {
if (input.baseURL)
return input.baseURL;
if (!input.accountId)
throw new Error("CloudflareAIGateway.configure requires accountId unless baseURL is supplied");
return `https://gateway.ai.cloudflare.com/v1/${encodeURIComponent(input.accountId)}/${encodeURIComponent(input.gatewayId?.trim() || "default")}/compat`;
};
const aiGatewayAuth = (input) => {
if ("auth" in input && input.auth)
return input.auth;
const gateway = Auth.optional(input.gatewayApiKey, "gatewayApiKey")
.orElse(Auth.config("CLOUDFLARE_API_TOKEN"))
.orElse(Auth.config("CF_AIG_TOKEN"))
.pipe(Auth.bearerHeader("cf-aig-authorization"));
if (!("apiKey" in input) || input.apiKey === undefined)
return gateway;
if (input.gatewayApiKey === undefined)
return Auth.bearer(input.apiKey);
return Auth.bearerHeader("cf-aig-authorization", input.gatewayApiKey).andThen(Auth.bearer(input.apiKey));
};
export const workersAIBaseURL = (input) => {
if (input.baseURL)
return input.baseURL;
if (!input.accountId)
throw new Error("CloudflareWorkersAI.configure requires accountId unless baseURL is supplied");
return `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(input.accountId)}/ai/v1`;
};
const workersAIAuth = (input) => {
return AuthOptions.bearer(input, workersAIAuthEnvVars);
};
export const aiGatewayRoute = OpenAICompatibleChat.route.with({
id: "cloudflare-ai-gateway",
provider: aiGatewayID,
});
export const workersAIRoute = OpenAICompatibleChat.route.with({
id: "cloudflare-workers-ai",
provider: workersAIID,
});
export const routes = [aiGatewayRoute, workersAIRoute];
const aiGatewayDefaults = (options) => {
const { accountId: _accountId, gatewayId: _gatewayId, apiKey: _apiKey, gatewayApiKey: _gatewayApiKey, baseURL: _baseURL, auth: _auth, ...rest } = options;
return rest;
};
const workersAIDefaults = (options) => {
const { accountId: _accountId, apiKey: _apiKey, auth: _auth, baseURL: _baseURL, ...rest } = options;
return rest;
};
const configureAIGateway = (options) => {
const route = aiGatewayRoute.with({
...aiGatewayDefaults(options),
endpoint: { baseURL: aiGatewayBaseURL(options) },
auth: aiGatewayAuth(options),
});
return {
id: aiGatewayID,
model: (modelID) => route.model({ id: modelID }),
configure: configureAIGateway,
};
};
const configureWorkersAI = (options) => {
const route = workersAIRoute.with({
...workersAIDefaults(options),
endpoint: { baseURL: workersAIBaseURL(options) },
auth: workersAIAuth(options),
});
return {
id: workersAIID,
model: (modelID) => route.model({ id: modelID }),
configure: configureWorkersAI,
};
};
export const CloudflareAIGateway = {
id: aiGatewayID,
configure: configureAIGateway,
};
export const CloudflareWorkersAI = {
id: workersAIID,
configure: configureWorkersAI,
};
import type { ProviderPackage } from "../provider-package.js";
import type { RouteDefaultsInput } from "../route/client.js";
import { type ModelID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
import type { OpenAIProviderOptionsInput } from "./openai-options.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & GoogleVertexShared.OAuthOptions & {
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly accessToken?: string;
readonly apiKey?: never;
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: OpenAIProviderOptionsInput;
}
export declare const routes: import("../route/client.js").Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
import { OpenAICompatibleChat } from "../protocols/openai-compatible-chat.js";
import { ProviderID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
export const id = ProviderID.make("google-vertex");
const route = OpenAICompatibleChat.route.with({
id: "google-vertex-chat",
provider: id,
});
export const routes = [route];
const configuredRoute = (input) => {
if ("apiKey" in input && input.apiKey !== undefined)
throw new Error("Google Vertex Chat does not support API keys");
const { accessToken: _accessToken, auth: _auth, baseURL, location: inputLocation, project: inputProject, ...rest } = input;
const location = GoogleVertexShared.location(inputLocation, "global");
const project = GoogleVertexShared.project(inputProject);
return route.with({
...rest,
endpoint: {
baseURL: baseURL ??
`https://aiplatform.googleapis.com/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/endpoints/openapi`,
},
auth: GoogleVertexShared.oauth(input, project),
});
};
export const configure = (input = {}) => {
const route = configuredRoute(input);
return {
id,
model: (modelID) => route.model({ id: modelID }),
configure,
};
};
export const provider = {
id,
configure,
};
export const model = (modelID, settings) => {
if (settings.apiKey !== undefined)
throw new Error("Google Vertex Chat does not support API keys");
return configure({
accessToken: settings.accessToken,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
location: settings.location,
project: settings.project,
providerOptions: settings.providerOptions,
}).model(modelID);
};
import type { ProviderPackage } from "../provider-package.js";
import { AnthropicMessages } from "../protocols/anthropic-messages.js";
import { Route, type RouteDefaultsInput } from "../route/client.js";
import { type ModelID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
export type AnthropicOptionsInput = AnthropicMessages.OptionsInput;
export type AnthropicProviderOptionsInput = AnthropicMessages.ProviderOptionsInput;
export type AnthropicThinkingInput = AnthropicMessages.ThinkingInput;
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & GoogleVertexShared.OAuthOptions & {
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: AnthropicMessages.ProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly accessToken?: string;
readonly apiKey?: never;
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: AnthropicMessages.ProviderOptionsInput;
}
export declare const routes: Route<{
anthropic_version: "vertex-2023-10-16";
system?: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
max_tokens: number;
tools?: readonly {
readonly description: string;
readonly name: string;
readonly input_schema: {
readonly [x: string]: unknown;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[] | undefined;
messages: readonly ({
readonly role: "user";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "tool_result";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "image";
readonly source: {
readonly type: "base64";
readonly media_type: string;
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "document";
readonly source: {
readonly type: "base64";
readonly media_type: "application/pdf";
readonly data: string;
};
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly is_error?: boolean | undefined;
})[];
} | {
readonly role: "assistant";
readonly content: readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly id: string;
readonly type: "server_tool_use";
readonly name: string;
readonly input: unknown;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "web_search_tool_result" | "code_execution_tool_result" | "web_fetch_tool_result";
readonly content: unknown;
readonly tool_use_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
} | {
readonly type: "thinking";
readonly thinking: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
readonly signature?: string | undefined;
} | {
readonly data: string;
readonly type: "redacted_thinking";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
})[];
} | {
readonly role: "system";
readonly content: readonly {
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: "1h" | "5m" | undefined;
} | undefined;
}[];
})[];
temperature?: number | undefined;
stream: true;
thinking?: {
readonly type: "enabled";
readonly budget_tokens: number;
} | {
readonly type: "adaptive";
readonly display?: "summarized" | "omitted" | undefined;
} | {
readonly type: "disabled";
} | undefined;
tool_choice?: {
readonly type: "none" | "auto" | "any";
} | {
readonly type: "tool";
readonly name: string;
} | undefined;
top_p?: number | undefined;
top_k?: number | undefined;
stop_sequences?: readonly string[] | undefined;
output_config?: {
readonly effort?: string | undefined;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<AnthropicMessages.ProviderOptionsInput>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<AnthropicMessages.ProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, AnthropicMessages.ProviderOptionsInput>["model"];
import { Effect, Schema, Struct } from "effect";
import { AnthropicMessages } from "../protocols/anthropic-messages.js";
import { Auth } from "../route/auth.js";
import { Route } from "../route/client.js";
import { Endpoint } from "../route/endpoint.js";
import { Framing } from "../route/framing.js";
import { Protocol } from "../route/protocol.js";
import { ProviderID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
const VERSION = "vertex-2023-10-16";
export const id = ProviderID.make("google-vertex");
const route = Route.make({
id: "google-vertex-messages",
provider: id,
providerMetadataKey: "anthropic",
protocol: Protocol.make({
id: AnthropicMessages.protocol.id,
body: {
schema: Schema.Struct({
...Struct.omit(AnthropicMessages.AnthropicMessagesBody.fields, ["model"]),
anthropic_version: Schema.Literal(VERSION),
}),
from: (request) => AnthropicMessages.protocol.body.from(request).pipe(Effect.map((body) => ({
...Struct.omit(body, ["model"]),
anthropic_version: VERSION,
}))),
},
stream: AnthropicMessages.protocol.stream,
}),
endpoint: Endpoint.path(({ request }) => `/${request.model.id}:streamRawPredict`),
auth: Auth.none,
framing: Framing.sse,
});
export const routes = [route];
const configuredRoute = (input) => {
if ("apiKey" in input && input.apiKey !== undefined)
throw new Error("Google Vertex Messages does not support API keys");
const { accessToken: _accessToken, auth: _auth, baseURL, location: inputLocation, project: inputProject, ...rest } = input;
const location = GoogleVertexShared.location(inputLocation, "global");
const project = GoogleVertexShared.project(inputProject);
return route.with({
...rest,
endpoint: {
baseURL: baseURL ??
`https://${GoogleVertexShared.host(location)}/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/publishers/anthropic/models`,
},
auth: GoogleVertexShared.oauth(input, project),
});
};
export const configure = (input = {}) => {
const route = configuredRoute(input);
return {
id,
model: (modelID) => route.model({ id: modelID }),
configure,
};
};
export const provider = {
id,
configure,
};
export const model = (modelID, settings) => {
if (settings.apiKey !== undefined)
throw new Error("Google Vertex Messages does not support API keys");
return configure({
accessToken: settings.accessToken,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
location: settings.location,
project: settings.project,
providerOptions: settings.providerOptions,
}).model(modelID);
};
import type { ProviderPackage } from "../provider-package.js";
import type { RouteDefaultsInput } from "../route/client.js";
import { type ModelID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
import type { OpenResponsesProviderOptionsInput } from "./open-responses-options.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & GoogleVertexShared.OAuthOptions & {
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: OpenResponsesProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly accessToken?: string;
readonly apiKey?: never;
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: OpenResponsesProviderOptionsInput;
}
export declare const routes: import("../route/client.js").Route<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly {
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
}[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenResponsesProviderOptionsInput>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenResponsesProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, OpenResponsesProviderOptionsInput>["model"];
import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses.js";
import { ProviderID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
export const id = ProviderID.make("google-vertex");
const route = OpenAICompatibleResponses.route.with({
id: "google-vertex-responses",
provider: id,
providerOptions: { openresponses: { store: false } },
});
export const routes = [route];
const configuredRoute = (input) => {
if ("apiKey" in input && input.apiKey !== undefined)
throw new Error("Google Vertex Responses does not support API keys");
const { accessToken: _accessToken, auth: _auth, baseURL, location: inputLocation, project: inputProject, ...rest } = input;
const location = GoogleVertexShared.location(inputLocation, "global");
const project = GoogleVertexShared.project(inputProject);
return route.with({
...rest,
endpoint: {
baseURL: baseURL ??
`https://aiplatform.googleapis.com/v1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}/endpoints/openapi`,
},
auth: GoogleVertexShared.oauth(input, project),
});
};
export const configure = (input = {}) => {
const route = configuredRoute(input);
return {
id,
model: (modelID) => route.model({ id: modelID }),
configure,
};
};
export const provider = {
id,
configure,
};
export const model = (modelID, settings) => {
if (settings.apiKey !== undefined)
throw new Error("Google Vertex Responses does not support API keys");
return configure({
accessToken: settings.accessToken,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
location: settings.location,
project: settings.project,
providerOptions: settings.providerOptions,
}).model(modelID);
};
import { Auth } from "../route/auth.js";
export type OAuthOptions = {
readonly accessToken?: string;
readonly auth?: never;
} | {
readonly accessToken?: never;
readonly auth?: Auth.Definition;
};
export type ApiKeyOptions = (OAuthOptions & {
readonly apiKey?: never;
}) | {
readonly accessToken?: never;
readonly apiKey?: string;
readonly auth?: never;
};
export declare const project: (value?: string) => string | undefined;
export declare const location: (value: string | undefined, fallback: string) => string;
export declare const host: (location: string) => string;
export declare const requireProject: (value: string | undefined) => string;
export declare const apiKey: (input: ApiKeyOptions) => string | undefined;
export declare const oauth: (input: OAuthOptions, project?: string) => Auth.Definition;
export * as GoogleVertexShared from "./google-vertex-shared.js";
import { Effect, Redacted } from "effect";
import { Auth, MissingCredentialError } from "../route/auth.js";
const SCOPE = "https://www.googleapis.com/auth/cloud-platform";
export const project = (value) => value ??
process.env.GOOGLE_VERTEX_PROJECT ??
process.env.GOOGLE_CLOUD_PROJECT ??
process.env.GCP_PROJECT ??
process.env.GCLOUD_PROJECT;
export const location = (value, fallback) => value ??
process.env.GOOGLE_VERTEX_LOCATION ??
process.env.GOOGLE_CLOUD_LOCATION ??
process.env.VERTEX_LOCATION ??
fallback;
export const host = (location) => {
if (location === "global")
return "aiplatform.googleapis.com";
// Jurisdictional multi-regions use Regional Endpoint Platform domains.
if (location === "eu" || location === "us")
return `aiplatform.${location}.rep.googleapis.com`;
return `${location}-aiplatform.googleapis.com`;
};
export const requireProject = (value) => {
if (value)
return value;
throw new Error("Google Vertex requires a project when baseURL is not configured");
};
export const apiKey = (input) => {
if (input.apiKey !== undefined && (input.accessToken !== undefined || input.auth !== undefined))
throw new Error("Google Vertex apiKey cannot be combined with accessToken or auth");
if (input.accessToken !== undefined || input.auth !== undefined)
return undefined;
return input.apiKey ?? process.env.GOOGLE_VERTEX_API_KEY;
};
const adc = (project) => {
let client;
const loadClient = () => {
if (client)
return client;
client = import("google-auth-library").then(({ GoogleAuth }) => new GoogleAuth({ projectId: project, scopes: [SCOPE] }).getClient());
return client;
};
return Auth.effect(Effect.tryPromise({
try: async () => {
const token = await (await loadClient()).getAccessToken();
if (!token.token)
throw new Error("Google ADC returned an empty access token");
return Redacted.make(token.token);
},
catch: () => new MissingCredentialError("Google Application Default Credentials"),
})).bearer();
};
export const oauth = (input, project) => {
if (input.accessToken !== undefined && input.auth !== undefined)
throw new Error("Google Vertex accessToken cannot be combined with auth");
if (input.auth)
return input.auth;
if (input.accessToken !== undefined)
return Auth.bearer(input.accessToken);
return adc(project);
};
export * as GoogleVertexShared from "./google-vertex-shared.js";
import type { ProviderPackage } from "../provider-package.js";
import { Gemini } from "../protocols/gemini.js";
import { Route, type RouteDefaultsInput } from "../route/client.js";
import { type ModelID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
export type GeminiOptionsInput = Gemini.OptionsInput;
export type GeminiProviderOptionsInput = Gemini.ProviderOptionsInput;
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & GoogleVertexShared.ApiKeyOptions & {
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: Gemini.ProviderOptionsInput;
};
export type Settings = ProviderPackage.Settings & ({
readonly accessToken?: string;
readonly apiKey?: never;
} | {
readonly accessToken?: never;
readonly apiKey?: string;
}) & {
readonly baseURL?: string;
readonly location?: string;
readonly project?: string;
readonly providerOptions?: Gemini.ProviderOptionsInput;
};
export declare const routes: Route<{
readonly contents: readonly {
readonly role: "user" | "model";
readonly parts: readonly ({
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
} | {
readonly text: string;
readonly thought?: boolean | undefined;
readonly thoughtSignature?: string | undefined;
} | {
readonly functionCall: {
readonly name: string;
readonly args: unknown;
readonly id?: string | undefined;
};
readonly thoughtSignature?: string | undefined;
} | {
readonly functionResponse: {
readonly name: string;
readonly response: unknown;
readonly id?: string | undefined;
readonly parts?: readonly {
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
}[] | undefined;
};
})[];
}[];
readonly tools?: readonly {
readonly functionDeclarations: readonly {
readonly description: string;
readonly name: string;
readonly parameters?: {
readonly [x: string]: unknown;
} | undefined;
}[];
}[] | undefined;
readonly toolConfig?: {
readonly functionCallingConfig: {
readonly mode: "AUTO" | "NONE" | "ANY";
readonly allowedFunctionNames?: readonly string[] | undefined;
};
} | undefined;
readonly cachedContent?: string | undefined;
readonly safetySettings?: readonly {
readonly category: string;
readonly threshold: string;
}[] | undefined;
readonly serviceTier?: string | undefined;
readonly systemInstruction?: {
readonly parts: readonly {
readonly text: string;
}[];
} | undefined;
readonly generationConfig?: {
readonly temperature?: number | undefined;
readonly topP?: number | undefined;
readonly topK?: number | undefined;
readonly frequencyPenalty?: number | undefined;
readonly presencePenalty?: number | undefined;
readonly seed?: number | undefined;
readonly stopSequences?: readonly string[] | undefined;
readonly thinkingConfig?: {
readonly thinkingBudget?: number | undefined;
readonly includeThoughts?: boolean | undefined;
readonly thinkingLevel?: string | undefined;
} | undefined;
readonly maxOutputTokens?: number | undefined;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.ProviderOptionsInput>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.ProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, Gemini.ProviderOptionsInput>["model"];
import { Gemini } from "../protocols/gemini.js";
import { Auth } from "../route/auth.js";
import { Route } from "../route/client.js";
import { Endpoint } from "../route/endpoint.js";
import { Framing } from "../route/framing.js";
import { ProviderID } from "../schema/index.js";
import { GoogleVertexShared } from "./google-vertex-shared.js";
export const id = ProviderID.make("google-vertex");
const route = Route.make({
id: "google-vertex-gemini",
provider: id,
providerMetadataKey: "google",
protocol: Gemini.protocol,
endpoint: Endpoint.path(({ request }) => {
const model = String(request.model.id);
return `/${model.startsWith("endpoints/") ? model : `models/${model}`}:streamGenerateContent?alt=sse`;
}),
auth: Auth.none,
framing: Framing.sse,
});
export const routes = [route];
const configuredRoute = (input, modelID) => {
const { accessToken: _accessToken, apiKey: _apiKey, auth: _auth, baseURL, location: inputLocation, project: inputProject, ...rest } = input;
const apiKey = GoogleVertexShared.apiKey(input);
const endpointModel = String(modelID).startsWith("endpoints/");
if (apiKey !== undefined && endpointModel)
throw new Error("Google Vertex tuned models do not support Express Mode API keys");
const location = GoogleVertexShared.location(inputLocation, "us-central1");
const project = GoogleVertexShared.project(inputProject);
const endpoint = baseURL ??
(apiKey
? "https://aiplatform.googleapis.com/v1/publishers/google"
: `https://${GoogleVertexShared.host(location)}/v1beta1/projects/${GoogleVertexShared.requireProject(project)}/locations/${location}${endpointModel ? "" : "/publishers/google"}`);
return route.with({
...rest,
endpoint: { baseURL: endpoint },
auth: apiKey === undefined ? GoogleVertexShared.oauth(input, project) : Auth.header("x-goog-api-key", apiKey),
});
};
export const configure = (input = {}) => {
return {
id,
model: (modelID) => configuredRoute(input, modelID).model({ id: modelID }),
configure,
};
};
export const provider = {
id,
configure,
};
export const model = (modelID, settings) => {
if (settings.apiKey !== undefined && settings.accessToken !== undefined)
throw new Error("Google Vertex apiKey cannot be combined with accessToken or auth");
return configure({
...(settings.apiKey === undefined ? { accessToken: settings.accessToken } : { apiKey: settings.apiKey }),
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
location: settings.location,
project: settings.project,
providerOptions: settings.providerOptions,
}).model(modelID);
};
export { model } from "../google-vertex-chat.js";
export type { Settings } from "../google-vertex-chat.js";
export { model } from "../google-vertex-chat.js";
export { model } from "../google-vertex.js";
export type { Settings } from "../google-vertex.js";
export { model } from "../google-vertex.js";
export { model } from "../google-vertex-messages.js";
export type { Settings } from "../google-vertex-messages.js";
export { model } from "../google-vertex-messages.js";
export { model } from "../google-vertex-responses.js";
export type { Settings } from "../google-vertex-responses.js";
export { model } from "../google-vertex-responses.js";
import type { RouteDefaultsInput } from "../route/client.js";
import type { ProviderAuthOption } from "../route/auth-options.js";
import type { ProviderPackage } from "../provider-package.js";
import { type ModelID } from "../schema/index.js";
import { Gemini } from "../protocols/gemini.js";
export type { GoogleImageOptions } from "../protocols/google-images.js";
export type GeminiOptionsInput = Gemini.OptionsInput;
export type GeminiProviderOptionsInput = Gemini.ProviderOptionsInput;
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export declare const routes: import("../route/client.js").Route<{
readonly contents: readonly {
readonly role: "user" | "model";
readonly parts: readonly ({
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
} | {
readonly text: string;
readonly thought?: boolean | undefined;
readonly thoughtSignature?: string | undefined;
} | {
readonly functionCall: {
readonly name: string;
readonly args: unknown;
readonly id?: string | undefined;
};
readonly thoughtSignature?: string | undefined;
} | {
readonly functionResponse: {
readonly name: string;
readonly response: unknown;
readonly id?: string | undefined;
readonly parts?: readonly {
readonly inlineData: {
readonly mimeType: string;
readonly data: string;
};
}[] | undefined;
};
})[];
}[];
readonly tools?: readonly {
readonly functionDeclarations: readonly {
readonly description: string;
readonly name: string;
readonly parameters?: {
readonly [x: string]: unknown;
} | undefined;
}[];
}[] | undefined;
readonly toolConfig?: {
readonly functionCallingConfig: {
readonly mode: "AUTO" | "NONE" | "ANY";
readonly allowedFunctionNames?: readonly string[] | undefined;
};
} | undefined;
readonly cachedContent?: string | undefined;
readonly safetySettings?: readonly {
readonly category: string;
readonly threshold: string;
}[] | undefined;
readonly serviceTier?: string | undefined;
readonly systemInstruction?: {
readonly parts: readonly {
readonly text: string;
}[];
} | undefined;
readonly generationConfig?: {
readonly temperature?: number | undefined;
readonly topP?: number | undefined;
readonly topK?: number | undefined;
readonly frequencyPenalty?: number | undefined;
readonly presencePenalty?: number | undefined;
readonly seed?: number | undefined;
readonly stopSequences?: readonly string[] | undefined;
readonly thinkingConfig?: {
readonly thinkingBudget?: number | undefined;
readonly includeThoughts?: boolean | undefined;
readonly thinkingLevel?: string | undefined;
} | undefined;
readonly maxOutputTokens?: number | undefined;
} | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export type Config = RouteDefaultsInput & ProviderAuthOption<"optional"> & {
readonly baseURL?: string;
readonly providerOptions?: Gemini.ProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly baseURL?: string;
readonly providerOptions?: Gemini.ProviderOptionsInput;
}
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.ProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.ProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<Gemini.ProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, Gemini.ProviderOptionsInput>["model"];
export declare const image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/google-images.js").GoogleImageOptions>;
import { Auth } from "../route/auth.js";
import { HttpOptions, ProviderID, mergeHttpOptions } from "../schema/index.js";
import { Gemini } from "../protocols/gemini.js";
import { GoogleImages } from "../protocols/google-images.js";
export const id = ProviderID.make("google");
export const routes = [Gemini.route];
const auth = (options) => {
if ("auth" in options && options.auth)
return options.auth;
return Auth.optional("apiKey" in options ? options.apiKey : undefined, "apiKey")
.orElse(Auth.config("GOOGLE_GENERATIVE_AI_API_KEY"))
.pipe(Auth.header("x-goog-api-key"));
};
const configuredRoute = (input) => {
const { apiKey: _, auth: _auth, baseURL, ...rest } = input;
return Gemini.route.with({ ...rest, endpoint: { baseURL }, auth: auth(input) });
};
export const configure = (input = {}) => {
const route = configuredRoute(input);
const image = (modelID) => GoogleImages.model({
id: modelID,
auth: auth(input),
baseURL: input.baseURL,
headers: input.headers,
http: mergeHttpOptions(input.http === undefined ? undefined : HttpOptions.make(input.http)),
});
return {
id,
model: (modelID) => route.model({ id: modelID }),
image,
configure,
};
};
export const provider = configure();
export const model = (modelID, settings) => configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
}).model(modelID);
export const image = provider.image;
export * as Anthropic from "./anthropic.js";
export * as AnthropicCompatible from "./anthropic-compatible.js";
export * as AmazonBedrock from "./amazon-bedrock.js";
export * as AmazonBedrockMantle from "./amazon-bedrock-mantle.js";
export * as Azure from "./azure.js";
export * as Cloudflare from "./cloudflare.js";
export { CloudflareAIGateway, CloudflareWorkersAI } from "./cloudflare.js";
export * as Google from "./google.js";
export * as GoogleVertex from "./google-vertex.js";
export * as GoogleVertexChat from "./google-vertex-chat.js";
export * as GoogleVertexMessages from "./google-vertex-messages.js";
export * as GoogleVertexResponses from "./google-vertex-responses.js";
export * as OpenAI from "./openai.js";
export * as OpenAICompatible from "./openai-compatible.js";
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
export * as OpenRouter from "./openrouter.js";
export * as XAI from "./xai.js";
export * as ZAI from "./zai.js";
export * as Anthropic from "./anthropic.js";
export * as AnthropicCompatible from "./anthropic-compatible.js";
export * as AmazonBedrock from "./amazon-bedrock.js";
export * as AmazonBedrockMantle from "./amazon-bedrock-mantle.js";
export * as Azure from "./azure.js";
export * as Cloudflare from "./cloudflare.js";
export { CloudflareAIGateway, CloudflareWorkersAI } from "./cloudflare.js";
export * as Google from "./google.js";
export * as GoogleVertex from "./google-vertex.js";
export * as GoogleVertexChat from "./google-vertex-chat.js";
export * as GoogleVertexMessages from "./google-vertex-messages.js";
export * as GoogleVertexResponses from "./google-vertex-responses.js";
export * as OpenAI from "./openai.js";
export * as OpenAICompatible from "./openai-compatible.js";
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js";
export * as OpenRouter from "./openrouter.js";
export * as XAI from "./xai.js";
export * as ZAI from "./zai.js";
import type { ResponseIncludable, ServiceTier } from "../protocols/utils/open-responses-options.js";
import type { ProviderOptions, ReasoningEffort, TextVerbosity } from "../schema/index.js";
export interface OpenResponsesOptionsInput {
readonly [key: string]: unknown;
readonly instructions?: string;
readonly store?: boolean;
readonly reasoningEffort?: ReasoningEffort;
readonly reasoningSummary?: "auto" | "concise" | "detailed";
readonly include?: ReadonlyArray<ResponseIncludable>;
readonly textVerbosity?: TextVerbosity;
readonly serviceTier?: ServiceTier;
}
export type OpenResponsesProviderOptionsInput = ProviderOptions & {
readonly openresponses?: OpenResponsesOptionsInput;
};
export * as OpenResponsesProviderOptions from "./open-responses-options.js";
export * as OpenResponsesProviderOptions from "./open-responses-options.js";
export interface OpenAICompatibleProfile {
readonly provider: string;
readonly baseURL: string;
}
export declare const profiles: {
readonly baseten: {
readonly provider: "baseten";
readonly baseURL: "https://inference.baseten.co/v1";
};
readonly cerebras: {
readonly provider: "cerebras";
readonly baseURL: "https://api.cerebras.ai/v1";
};
readonly deepinfra: {
readonly provider: "deepinfra";
readonly baseURL: "https://api.deepinfra.com/v1/openai";
};
readonly deepseek: {
readonly provider: "deepseek";
readonly baseURL: "https://api.deepseek.com/v1";
};
readonly fireworks: {
readonly provider: "fireworks";
readonly baseURL: "https://api.fireworks.ai/inference/v1";
};
readonly groq: {
readonly provider: "groq";
readonly baseURL: "https://api.groq.com/openai/v1";
};
readonly openrouter: {
readonly provider: "openrouter";
readonly baseURL: "https://openrouter.ai/api/v1";
};
readonly togetherai: {
readonly provider: "togetherai";
readonly baseURL: "https://api.together.xyz/v1";
};
readonly xai: {
readonly provider: "xai";
readonly baseURL: "https://api.x.ai/v1";
};
};
export declare const byProvider: Record<string, OpenAICompatibleProfile>;
export const profiles = {
baseten: { provider: "baseten", baseURL: "https://inference.baseten.co/v1" },
cerebras: { provider: "cerebras", baseURL: "https://api.cerebras.ai/v1" },
deepinfra: { provider: "deepinfra", baseURL: "https://api.deepinfra.com/v1/openai" },
deepseek: { provider: "deepseek", baseURL: "https://api.deepseek.com/v1" },
fireworks: { provider: "fireworks", baseURL: "https://api.fireworks.ai/inference/v1" },
groq: { provider: "groq", baseURL: "https://api.groq.com/openai/v1" },
openrouter: { provider: "openrouter", baseURL: "https://openrouter.ai/api/v1" },
togetherai: { provider: "togetherai", baseURL: "https://api.together.xyz/v1" },
xai: { provider: "xai", baseURL: "https://api.x.ai/v1" },
};
export const byProvider = Object.fromEntries(Object.values(profiles).map((profile) => [profile.provider, profile]));
import type { ProviderPackage } from "../provider-package.js";
import { type ProviderAuthOption } from "../route/auth-options.js";
import type { RouteDefaultsInput } from "../route/client.js";
import { type ModelID } from "../schema/index.js";
import type { OpenResponsesProviderOptionsInput } from "./open-responses-options.js";
export type { OpenResponsesOptionsInput, OpenResponsesProviderOptionsInput } from "./open-responses-options.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = RouteDefaultsInput & ProviderAuthOption<"optional"> & {
readonly provider?: string;
readonly baseURL: string;
readonly providerOptions?: OpenResponsesProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly baseURL: string;
readonly provider?: string;
readonly providerOptions?: OpenResponsesProviderOptionsInput;
}
export declare const routes: import("../route/client.js").Route<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly {
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
}[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenResponsesProviderOptionsInput>;
configure: (input: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenResponsesProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, OpenResponsesProviderOptionsInput>["model"];
import { OpenAICompatibleResponses } from "../protocols/openai-compatible-responses.js";
import { AuthOptions } from "../route/auth-options.js";
import { ProviderID } from "../schema/index.js";
export const id = ProviderID.make("openai-compatible");
export const routes = [OpenAICompatibleResponses.route];
export const configure = (input) => {
const provider = input.provider ?? "openai-compatible";
const { provider: _, baseURL, apiKey: _apiKey, auth: _auth, ...rest } = input;
const route = OpenAICompatibleResponses.route.with({
...rest,
provider,
endpoint: { baseURL },
auth: AuthOptions.bearer(input, []),
});
return {
id: ProviderID.make(provider),
model: (modelID) => route.model({ id: modelID }),
configure,
};
};
export const provider = {
id,
configure,
};
export const model = (modelID, settings) => configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
provider: settings.provider,
providerOptions: settings.providerOptions,
}).model(modelID);
import { type ModelID } from "../schema/index.js";
import type { RouteDefaultsInput } from "../route/client.js";
import { type ProviderAuthOption } from "../route/auth-options.js";
import type { ProviderPackage } from "../provider-package.js";
import type { OpenAIProviderOptionsInput } from "./openai-options.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
type GenericModelOptions = Omit<RouteDefaultsInput, "providerOptions"> & ProviderAuthOption<"optional"> & {
readonly provider?: string;
readonly baseURL: string;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly baseURL: string;
readonly provider?: string;
}
export type FamilyModelOptions = Omit<RouteDefaultsInput, "providerOptions"> & ProviderAuthOption<"optional"> & {
readonly baseURL?: string;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export declare const routes: import("../route/client.js").Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input: GenericModelOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input: GenericModelOptions) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
configure: (input: GenericModelOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
export declare const baseten: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: FamilyModelOptions) => /*elided*/ any;
};
export declare const cerebras: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: FamilyModelOptions) => /*elided*/ any;
};
export declare const deepinfra: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: FamilyModelOptions) => /*elided*/ any;
};
export declare const deepseek: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: FamilyModelOptions) => /*elided*/ any;
};
export declare const fireworks: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: FamilyModelOptions) => /*elided*/ any;
};
export declare const groq: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: FamilyModelOptions) => /*elided*/ any;
};
export declare const togetherai: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
configure: (input?: FamilyModelOptions) => /*elided*/ any;
};
export {};
import { ProviderID } from "../schema/index.js";
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js";
import { AuthOptions } from "../route/auth-options.js";
import { profiles } from "./openai-compatible-profile.js";
export const id = ProviderID.make("openai-compatible");
export const routes = [OpenAICompatibleChat.route];
export const configure = (input) => {
const provider = input.provider ?? "openai-compatible";
const { provider: _, baseURL, apiKey: _apiKey, auth: _auth, ...rest } = input;
const route = OpenAICompatibleChat.route.with({
...rest,
provider,
endpoint: { baseURL },
auth: AuthOptions.bearer(input, []),
});
return {
id: ProviderID.make(provider),
model: (modelID) => route.model({ id: modelID, provider: ProviderID.make(provider) }),
configure,
};
};
const define = (profile) => {
const configureProfile = (input = {}) => {
const facade = configure({
...input,
baseURL: input.baseURL ?? profile.baseURL,
provider: profile.provider,
});
return {
id: ProviderID.make(profile.provider),
model: facade.model,
configure: configureProfile,
};
};
return configureProfile();
};
export const provider = {
id,
configure,
};
export const model = (modelID, settings) => configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers === undefined ? undefined : { ...settings.headers },
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
provider: settings.provider,
}).model(modelID);
export const baseten = define(profiles.baseten);
export const cerebras = define(profiles.cerebras);
export const deepinfra = define(profiles.deepinfra);
export const deepseek = define(profiles.deepseek);
export const fireworks = define(profiles.fireworks);
export const groq = define(profiles.groq);
export const togetherai = define(profiles.togetherai);
export * from "../openai-compatible-responses.js";
export * from "../openai-compatible-responses.js";
import type { ProviderOptions } from "../schema/index.js";
import type { OpenResponsesOptionsInput } from "./open-responses-options.js";
export type { OpenAIResponseIncludable, OpenAIServiceTier } from "../protocols/utils/openai-options.js";
export type OpenAIOptionsInput = OpenResponsesOptionsInput;
export type OpenAIProviderOptionsInput = ProviderOptions & {
readonly openai?: OpenAIOptionsInput;
};
export declare const gpt5DefaultOptions: (modelID: string, options?: {
readonly textVerbosity?: boolean;
}) => ProviderOptions | undefined;
export declare const openAIDefaultOptions: (modelID: string, options?: {
readonly textVerbosity?: boolean;
}) => ProviderOptions | undefined;
export declare const withOpenAIOptions: <Options extends {
readonly providerOptions?: OpenAIProviderOptionsInput;
}>(modelID: string, options: Options, defaults?: {
readonly textVerbosity?: boolean;
}) => Omit<Options, "providerOptions"> & {
readonly providerOptions?: ProviderOptions;
};
export * as OpenAIProviderOptions from "./openai-options.js";
import { mergeProviderOptions } from "../schema/index.js";
const definedEntries = (input) => Object.entries(input).filter((entry) => entry[1] !== undefined);
const openAIProviderOptions = (options) => {
const openai = Object.fromEntries(definedEntries({
store: options?.store,
reasoningEffort: options?.reasoningEffort,
reasoningSummary: options?.reasoningSummary,
include: options?.include,
textVerbosity: options?.textVerbosity,
serviceTier: options?.serviceTier,
}));
if (Object.keys(openai).length === 0)
return undefined;
return { openai };
};
export const gpt5DefaultOptions = (modelID, options = {}) => {
const id = modelID.toLowerCase();
if (!id.includes("gpt-5") || id.includes("gpt-5-chat") || id.includes("gpt-5-pro"))
return undefined;
return openAIProviderOptions({
reasoningEffort: "medium",
reasoningSummary: "auto",
// GPT-5 reasoning models are configured stateless (`store: false`) by
// `openAIDefaultOptions` below, so the only way a follow-up turn can
// carry reasoning state is via the encrypted reasoning include. Without
// this, callers using the default model facade get reasoning summaries
// they cannot replay statelessly.
include: ["reasoning.encrypted_content"],
textVerbosity: options.textVerbosity === true && id.includes("gpt-5.") && !id.includes("codex") && !id.includes("-chat")
? "low"
: undefined,
});
};
export const openAIDefaultOptions = (modelID, options = {}) => mergeProviderOptions(openAIProviderOptions({ store: false }), gpt5DefaultOptions(modelID, options));
export const withOpenAIOptions = (modelID, options, defaults = {}) => {
return {
...options,
providerOptions: mergeProviderOptions(openAIDefaultOptions(modelID, defaults), options.providerOptions),
};
};
export * as OpenAIProviderOptions from "./openai-options.js";
import { type ProviderAuthOption } from "../route/auth-options.js";
import type { Route, RouteDefaultsInput } from "../route/client.js";
import type { ProviderPackage } from "../provider-package.js";
import { ToolDefinition, type ModelID } from "../schema/index.js";
import { type OpenAIProviderOptionsInput } from "./openai-options.js";
import { type OpenAIImageString } from "../protocols/openai-images.js";
export type { OpenAIOptionsInput, OpenAIResponseIncludable } from "./openai-options.js";
export type { OpenAIImageOptions } from "../protocols/openai-images.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export declare const routes: (Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>> | Route<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("../protocols/open-responses-channel.js").Prepared>)[];
export type Config = RouteDefaultsInput & ProviderAuthOption<"optional"> & {
readonly baseURL?: string;
readonly queryParams?: Record<string, string>;
readonly providerOptions?: OpenAIProviderOptionsInput;
};
export interface ImageGenerationOptions {
readonly action?: OpenAIImageString<"auto" | "generate" | "edit">;
readonly background?: OpenAIImageString<"auto" | "opaque" | "transparent">;
readonly inputFidelity?: OpenAIImageString<"low" | "high">;
readonly outputCompression?: number;
readonly outputFormat?: OpenAIImageString<"png" | "jpeg" | "webp">;
readonly partialImages?: number;
readonly quality?: OpenAIImageString<"auto" | "low" | "medium" | "high" | "standard" | "hd">;
readonly size?: OpenAIImageString<"auto" | "256x256" | "512x512" | "1024x1024" | "1536x1024" | "1024x1536" | "1792x1024" | "1024x1792">;
}
export declare const imageGeneration: (options?: ImageGenerationOptions) => ToolDefinition;
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly baseURL?: string;
readonly organization?: string;
readonly project?: string;
readonly queryParams?: Readonly<Record<string, string>>;
readonly providerOptions?: OpenAIProviderOptionsInput;
}
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/openai-images.js").OpenAIImageOptions>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/openai-images.js").OpenAIImageOptions>;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
responses: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
chat: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/openai-images.js").OpenAIImageOptions>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
export declare const chatModel: ProviderPackage.Definition<Settings, OpenAIProviderOptionsInput>["model"];
export declare const responses: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
export declare const chat: (id: string | ModelID) => import("../schema/options.js").LanguageModel<OpenAIProviderOptionsInput>;
export declare const image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/openai-images.js").OpenAIImageOptions>;
import { AuthOptions } from "../route/auth-options.js";
import { HttpOptions, ProviderID, ToolDefinition, mergeHttpOptions } from "../schema/index.js";
import * as OpenAIChat from "../protocols/openai-chat.js";
import * as OpenAIResponses from "../protocols/openai-responses.js";
import { withOpenAIOptions } from "./openai-options.js";
import { OpenAIImages } from "../protocols/openai-images.js";
export const id = ProviderID.make("openai");
export const routes = [OpenAIResponses.route, OpenAIChat.route];
export const imageGeneration = (options = {}) => ToolDefinition.make({
name: "image_generation",
description: "Generate or edit an image using OpenAI's hosted image generation tool.",
inputSchema: { type: "object", properties: {}, additionalProperties: false },
native: {
openai: {
type: "image_generation",
action: options.action,
background: options.background,
input_fidelity: options.inputFidelity,
output_compression: options.outputCompression,
output_format: options.outputFormat,
partial_images: options.partialImages,
quality: options.quality,
size: options.size,
},
},
});
const auth = (options) => AuthOptions.bearer(options, "OPENAI_API_KEY");
const defaults = (input) => {
const { apiKey: _, auth: _auth, baseURL: _baseURL, queryParams: _queryParams, ...rest } = input;
return rest;
};
const configuredRoute = (route, input) => route.with({
auth: auth(input),
endpoint: { baseURL: input.baseURL, query: input.queryParams },
});
export const configure = (input = {}) => {
const responsesRoute = configuredRoute(OpenAIResponses.route, input);
const chatRoute = configuredRoute(OpenAIChat.route, input);
const modelDefaults = defaults(input);
const responses = (id) => responsesRoute
.with(withOpenAIOptions(id, modelDefaults, { textVerbosity: true }))
.model({ id });
const chat = (id) => chatRoute.with(withOpenAIOptions(id, modelDefaults)).model({ id });
const image = (modelID) => OpenAIImages.model({
id: modelID,
auth: auth(input),
baseURL: input.baseURL,
headers: input.headers,
http: mergeHttpOptions(input.http === undefined ? undefined : HttpOptions.make(input.http), input.queryParams === undefined ? undefined : new HttpOptions({ query: input.queryParams })),
});
return {
id,
model: responses,
responses,
chat,
image,
configure,
};
};
export const provider = configure();
const config = (settings) => {
const headers = {
...(settings.organization === undefined ? {} : { "OpenAI-Organization": settings.organization }),
...(settings.project === undefined ? {} : { "OpenAI-Project": settings.project }),
...settings.headers,
};
return {
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: Object.keys(headers).length === 0 ? undefined : headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
queryParams: settings.queryParams === undefined ? undefined : { ...settings.queryParams },
};
};
export const model = (modelID, settings) => {
return configure(config(settings)).responses(modelID);
};
export const chatModel = (modelID, settings) => configure(config(settings)).chat(modelID);
export const responses = provider.responses;
export const chat = provider.chat;
export const image = provider.image;
export { chatModel as model } from "../openai.js";
export type { Settings } from "../openai.js";
export { chatModel as model } from "../openai.js";
export { model } from "../openai.js";
export type { Settings } from "../openai.js";
export { model } from "../openai.js";
import { Schema } from "effect";
import { Route, type RouteDefaultsInput } from "../route/client.js";
import { Protocol } from "../route/protocol.js";
import { type ProviderAuthOption } from "../route/auth-options.js";
import { type ModelID, type ProviderOptions } from "../schema/index.js";
import type { ProviderPackage } from "../provider-package.js";
import * as OpenAIChat from "../protocols/openai-chat.js";
export declare const profile: {
readonly provider: "openrouter";
readonly baseURL: "https://openrouter.ai/api/v1";
};
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
type OpenRouterString<Known extends string> = Known | (string & {});
export interface OpenRouterProviderRouting {
readonly [key: string]: unknown;
readonly order?: ReadonlyArray<string>;
readonly allow_fallbacks?: boolean;
readonly require_parameters?: boolean;
readonly data_collection?: OpenRouterString<"allow" | "deny">;
readonly only?: ReadonlyArray<string>;
readonly ignore?: ReadonlyArray<string>;
readonly quantizations?: ReadonlyArray<string>;
readonly sort?: OpenRouterString<"price" | "throughput" | "latency">;
readonly max_price?: Readonly<{
prompt?: number | string;
completion?: number | string;
image?: number | string;
audio?: number | string;
request?: number | string;
}>;
readonly zdr?: boolean;
}
export type OpenRouterPlugin = Readonly<{
id: "web";
max_results?: number;
search_prompt?: string;
engine?: OpenRouterString<"native" | "exa">;
}> | Readonly<{
id: "file-parser";
max_files?: number;
pdf?: {
engine?: string;
};
}> | Readonly<{
id: "moderation";
}> | Readonly<{
id: "response-healing";
}> | Readonly<{
id: "auto-router";
allowed_models?: ReadonlyArray<string>;
}> | Readonly<{
id: string & {};
[key: string]: unknown;
}>;
export interface OpenRouterOptions {
readonly [key: string]: unknown;
readonly debug?: Readonly<{
echo_upstream_body?: boolean;
}>;
readonly models?: ReadonlyArray<string>;
readonly plugins?: ReadonlyArray<OpenRouterPlugin>;
readonly provider?: OpenRouterProviderRouting;
readonly reasoning?: Readonly<{
enabled?: boolean;
exclude?: boolean;
effort?: OpenRouterString<"none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max">;
max_tokens?: number;
}>;
readonly usage?: boolean | Readonly<{
include: boolean;
}>;
readonly user?: string;
readonly web_search_options?: Readonly<{
max_results?: number;
search_prompt?: string;
engine?: OpenRouterString<"native" | "exa">;
}>;
}
export type OpenRouterProviderOptionsInput = ProviderOptions & {
readonly openrouter?: OpenRouterOptions;
};
export type LanguageModelOptions = Omit<RouteDefaultsInput, "providerOptions"> & ProviderAuthOption<"optional"> & {
readonly baseURL?: string;
readonly providerOptions?: OpenRouterProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly baseURL?: string;
readonly providerOptions?: OpenRouterProviderOptionsInput;
}
declare const OpenRouterBody: Schema.StructWithRest<Schema.Struct<{
model: Schema.String;
messages: Schema.$Array<Schema.toTaggedUnion<"role", readonly [Schema.Struct<{
readonly role: Schema.Literal<"system">;
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"image_url">;
readonly image_url: Schema.Struct<{
readonly url: Schema.String;
}>;
}>]>>]>;
}>, Schema.Struct<{
readonly role: Schema.Literal<"user">;
readonly content: Schema.Union<readonly [Schema.String, Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"image_url">;
readonly image_url: Schema.Struct<{
readonly url: Schema.String;
}>;
}>]>>]>;
}>, Schema.StructWithRest<Schema.Struct<{
readonly role: Schema.Literal<"assistant">;
readonly content: Schema.NullOr<Schema.String>;
readonly tool_calls: Schema.optional<Schema.$Array<Schema.Struct<{
readonly id: Schema.String;
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
readonly arguments: Schema.String;
}>;
}>>>;
readonly reasoning_content: Schema.optional<Schema.String>;
readonly reasoning: Schema.optional<Schema.String>;
readonly reasoning_text: Schema.optional<Schema.String>;
readonly reasoning_details: Schema.optional<Schema.Unknown>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>, Schema.Struct<{
readonly role: Schema.Literal<"tool">;
readonly tool_call_id: Schema.String;
readonly content: Schema.String;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>]>>;
tools: Schema.optional<Schema.$Array<Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
readonly description: Schema.String;
readonly parameters: Schema.$Record<Schema.String, Schema.Unknown>;
}>;
readonly cache_control: Schema.optional<Schema.Struct<{
readonly type: Schema.Literal<"ephemeral">;
readonly ttl: Schema.optional<Schema.String>;
}>>;
}>>>;
tool_choice: Schema.optional<Schema.Union<readonly [Schema.Literals<readonly ["auto", "none", "required"]>, Schema.Struct<{
readonly type: Schema.tag<"function">;
readonly function: Schema.Struct<{
readonly name: Schema.String;
}>;
}>]>>;
stream: Schema.Literal<true>;
stream_options: Schema.optional<Schema.Struct<{
readonly include_usage: Schema.Boolean;
}>>;
store: Schema.optional<Schema.Boolean>;
prompt_cache_key: Schema.optional<Schema.String>;
reasoning_effort: Schema.optional<Schema.String>;
max_completion_tokens: Schema.optional<Schema.Number>;
max_tokens: Schema.optional<Schema.Number>;
temperature: Schema.optional<Schema.Number>;
top_p: Schema.optional<Schema.Number>;
frequency_penalty: Schema.optional<Schema.Number>;
presence_penalty: Schema.optional<Schema.Number>;
seed: Schema.optional<Schema.Number>;
stop: Schema.optional<Schema.$Array<Schema.String>>;
}>, readonly [Schema.$Record<Schema.String, Schema.Any>]>;
export type OpenRouterBody = Schema.Schema.Type<typeof OpenRouterBody>;
export declare const protocol: Protocol<{
readonly [x: string]: any;
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, string, {
readonly error?: {
readonly message: string;
readonly code?: string | number | null | undefined;
} | null | undefined;
readonly usage?: {
readonly [x: string]: unknown;
readonly prompt_tokens?: number | null | undefined;
readonly completion_tokens?: number | null | undefined;
readonly total_tokens?: number | null | undefined;
readonly prompt_tokens_details?: {
readonly [x: string]: unknown;
readonly cached_tokens?: number | null | undefined;
readonly cache_write_tokens?: number | null | undefined;
} | null | undefined;
readonly completion_tokens_details?: {
readonly [x: string]: unknown;
readonly reasoning_tokens?: number | null | undefined;
readonly accepted_prediction_tokens?: number | null | undefined;
readonly rejected_prediction_tokens?: number | null | undefined;
} | null | undefined;
} | null | undefined;
readonly choices?: readonly {
readonly delta?: {
readonly [x: string]: unknown;
readonly reasoning?: string | null | undefined;
readonly reasoning_content?: string | null | undefined;
readonly reasoning_text?: string | null | undefined;
readonly content?: string | null | undefined;
readonly tool_calls?: readonly {
readonly function?: {
readonly name?: string | null | undefined;
readonly arguments?: string | null | undefined;
} | null | undefined;
readonly id?: string | null | undefined;
readonly index?: number | null | undefined;
}[] | null | undefined;
readonly reasoning_details?: unknown;
} | null | undefined;
readonly finish_reason?: string | null | undefined;
readonly native_finish_reason?: string | null | undefined;
}[] | null | undefined;
}, OpenAIChat.ParserState>;
export declare const route: Route<{
readonly [x: string]: any;
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>;
export declare const routes: Route<{
readonly [x: string]: any;
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>[];
export declare const configure: (input?: LanguageModelOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenRouterProviderOptionsInput>;
configure: (input?: LanguageModelOptions) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenRouterProviderOptionsInput>;
configure: (input?: LanguageModelOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<OpenRouterProviderOptionsInput>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, OpenRouterProviderOptionsInput>["model"];
export {};
import { Effect, Schema } from "effect";
import { Route } from "../route/client.js";
import { Endpoint } from "../route/endpoint.js";
import { Framing } from "../route/framing.js";
import { Protocol } from "../route/protocol.js";
import { AuthOptions } from "../route/auth-options.js";
import { ProviderID } from "../schema/index.js";
import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js";
import * as OpenAIChat from "../protocols/openai-chat.js";
import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js";
import { isRecord } from "../protocols/shared.js";
export const profile = OpenAICompatibleProfiles.profiles.openrouter;
export const id = ProviderID.make(profile.provider);
const ADAPTER = "openrouter";
const OpenRouterBody = Schema.StructWithRest(Schema.Struct(OpenAIChat.bodyFields), [
Schema.Record(Schema.String, Schema.Any),
]);
export const protocol = Protocol.make({
id: "openrouter-chat",
body: {
schema: OpenRouterBody,
from: (request) => OpenAIChat.fromRequest(request, { cacheControl: cacheControl() }).pipe(Effect.map((body) => {
const sourceAssistants = request.messages.filter((message) => message.role === "assistant");
let assistantIndex = 0;
const messages = body.messages.map((message) => {
if (message.role !== "assistant")
return message;
const source = sourceAssistants[assistantIndex++];
const reasoning = source?.content
.filter((part) => part.type === "reasoning")
.map((part) => part.text)
.join("");
const reasoningDetails = Array.isArray(message.reasoning_details) ? message.reasoning_details : undefined;
return {
...message,
reasoning_content: undefined,
reasoning_text: undefined,
reasoning: reasoning && reasoningDetails && reasoningDetails.length > 0 ? reasoning : undefined,
reasoning_details: reasoningDetails,
};
});
return {
...body,
messages,
...bodyOptions(request.providerOptions?.openrouter),
...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
};
})),
},
stream: OpenAIChat.protocol.stream,
});
const cacheControl = () => {
const breakpoints = newBreakpoints(4);
return (cache) => {
if (cache === undefined || breakpoints.remaining === 0)
return undefined;
breakpoints.remaining -= 1;
return {
type: "ephemeral",
...(ttlBucket(cache.ttlSeconds) === "1h" ? { ttl: "1h" } : {}),
};
};
};
const bodyOptions = (input) => {
const openrouter = isRecord(input) ? input : {};
const { usage, models, provider, plugins, web_search_options, debug, user, reasoning, promptCacheKey, ...options } = openrouter;
return {
...options,
...(usage === undefined || usage === true
? { usage: { include: true } }
: usage === false
? { usage: { include: false } }
: isRecord(usage)
? { usage }
: {}),
...(Array.isArray(models) ? { models } : {}),
...(isRecord(provider) ? { provider } : {}),
...(Array.isArray(plugins) ? { plugins } : {}),
...(isRecord(web_search_options) ? { web_search_options } : {}),
...(isRecord(debug) ? { debug } : {}),
...(typeof user === "string" ? { user } : {}),
...(isRecord(reasoning) ? { reasoning } : {}),
};
};
export const route = Route.make({
id: ADAPTER,
provider: profile.provider,
protocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: profile.baseURL }),
framing: Framing.sse,
});
export const routes = [route];
const configuredRoute = (input) => {
const { apiKey: _, auth: _auth, baseURL, ...rest } = input;
return route.with({
...rest,
endpoint: { baseURL: baseURL ?? profile.baseURL },
auth: AuthOptions.bearer(input, "OPENROUTER_API_KEY"),
});
};
export const configure = (input = {}) => {
const route = configuredRoute(input);
return {
id,
model: (modelID) => route.model({ id: modelID }),
configure,
};
};
export const provider = configure();
export const model = (modelID, settings) => configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
}).model(modelID);
import { type ProviderAuthOption } from "../route/auth-options.js";
import { Route, type RouteDefaultsInput } from "../route/client.js";
import { type ModelID, type ProviderOptions } from "../schema/index.js";
import type { OpenAIOptionsInput } from "./openai-options.js";
import type { ProviderPackage } from "../provider-package.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type XAIProviderOptionsInput = ProviderOptions & {
readonly xai?: OpenAIOptionsInput;
};
export type LanguageModelOptions = Omit<RouteDefaultsInput, "providerOptions"> & ProviderAuthOption<"optional"> & {
readonly baseURL?: string;
readonly providerOptions?: XAIProviderOptionsInput;
};
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string;
readonly baseURL?: string;
readonly providerOptions?: XAIProviderOptionsInput;
}
export type { XAIImageOptions } from "../protocols/xai-images.js";
export declare const routes: (Route<{
readonly model: string;
readonly messages: readonly ({
readonly role: "system";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly role: "user";
readonly content: string | readonly ({
readonly type: "text";
readonly text: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
} | {
readonly type: "image_url";
readonly image_url: {
readonly url: string;
};
})[];
} | {
readonly [x: string]: unknown;
readonly content: string | null;
readonly role: "assistant";
readonly reasoning?: string | undefined;
readonly reasoning_content?: string | undefined;
readonly reasoning_text?: string | undefined;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
readonly tool_calls?: readonly {
readonly id: string;
readonly type: "function";
readonly function: {
readonly name: string;
readonly arguments: string;
};
}[] | undefined;
readonly reasoning_details?: unknown;
} | {
readonly content: string;
readonly role: "tool";
readonly tool_call_id: string;
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
})[];
readonly stream: true;
readonly stop?: readonly string[] | undefined;
readonly max_completion_tokens?: number | undefined;
readonly max_tokens?: number | undefined;
readonly tools?: readonly {
readonly function: {
readonly name: string;
readonly description: string;
readonly parameters: {
readonly [x: string]: unknown;
};
};
readonly type: "function";
readonly cache_control?: {
readonly type: "ephemeral";
readonly ttl?: string | undefined;
} | undefined;
}[] | undefined;
readonly temperature?: number | undefined;
readonly seed?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly function: {
readonly name: string;
};
} | undefined;
readonly top_p?: number | undefined;
readonly store?: boolean | undefined;
readonly stream_options?: {
readonly include_usage: boolean;
} | undefined;
readonly prompt_cache_key?: string | undefined;
readonly reasoning_effort?: string | undefined;
readonly frequency_penalty?: number | undefined;
readonly presence_penalty?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>> | Route<{
readonly input: readonly ({
readonly type: "reasoning";
readonly summary: readonly {
readonly type: "summary_text";
readonly text: string;
}[];
readonly id?: string | undefined;
readonly encrypted_content?: string | null | undefined;
} | {
readonly type: "item_reference";
readonly id: string;
} | {
readonly role: "system";
readonly content: string;
} | {
readonly role: "user";
readonly content: readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | undefined;
} | {
readonly type: "function_call";
readonly call_id: string;
readonly name: string;
readonly arguments: string;
} | {
readonly type: "function_call_output";
readonly call_id: string;
readonly output: string | readonly ({
readonly type: "input_image";
readonly image_url: string;
} | {
readonly type: "input_file";
readonly filename: string;
readonly file_data: string;
readonly mime_type?: string | undefined;
} | {
readonly type: "input_text";
readonly text: string;
})[];
} | {
readonly content: readonly {
readonly type: "output_text";
readonly text: string;
}[];
readonly role: "assistant";
readonly phase?: "commentary" | "final_answer" | null | undefined;
})[];
readonly model: string;
readonly stream: true;
readonly instructions?: string | undefined;
readonly reasoning?: {
readonly summary?: "auto" | "concise" | "detailed" | undefined;
readonly effort?: string | undefined;
} | undefined;
readonly tools?: readonly ({
readonly type: "function";
readonly description: string;
readonly name: string;
readonly parameters: {
readonly [x: string]: unknown;
};
readonly strict?: boolean | undefined;
} | {
readonly type: "image_generation";
readonly size?: string | undefined;
readonly action?: "generate" | "auto" | "edit" | undefined;
readonly output_format?: "png" | "jpeg" | "webp" | undefined;
readonly quality?: "low" | "medium" | "high" | "auto" | undefined;
readonly background?: "auto" | "opaque" | "transparent" | undefined;
readonly output_compression?: number | undefined;
readonly input_fidelity?: "low" | "high" | undefined;
readonly partial_images?: number | undefined;
})[] | undefined;
readonly text?: {
readonly verbosity?: "low" | "medium" | "high" | undefined;
} | undefined;
readonly temperature?: number | undefined;
readonly tool_choice?: "required" | "none" | "auto" | {
readonly type: "function";
readonly name: string;
} | {
readonly type: "image_generation";
} | undefined;
readonly top_p?: number | undefined;
readonly include?: readonly ("file_search_call.results" | "web_search_call.results" | "web_search_call.action.sources" | "message.input_image.image_url" | "computer_call_output.output.image_url" | "code_interpreter_call.outputs" | "reasoning.encrypted_content" | "message.output_text.logprobs")[] | undefined;
readonly store?: boolean | undefined;
readonly prompt_cache_key?: string | undefined;
readonly service_tier?: "default" | "auto" | "flex" | "priority" | undefined;
readonly max_output_tokens?: number | undefined;
}, import("../route/transport/http.js").HttpPrepared<string>>)[];
export declare const configure: (input?: LanguageModelOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
configure: (input?: LanguageModelOptions) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
configure: (input?: LanguageModelOptions) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
model: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
configure: /*elided*/ any;
};
};
export declare const model: ProviderPackage.Definition<Settings, XAIProviderOptionsInput>["model"];
export declare const responses: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
export declare const chat: (modelID: string | ModelID) => import("../schema/options.js").LanguageModel<XAIProviderOptionsInput>;
export declare const image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/xai-images.js").XAIImageOptions>;
import { AuthOptions } from "../route/auth-options.js";
import { Route } from "../route/client.js";
import { Endpoint } from "../route/endpoint.js";
import { HttpOptions, ProviderID } from "../schema/index.js";
import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js";
import * as OpenAICompatibleChat from "../protocols/openai-compatible-chat.js";
import * as OpenAIChat from "../protocols/openai-chat.js";
import * as OpenAIResponses from "../protocols/openai-responses.js";
import { XAIImages } from "../protocols/xai-images.js";
export const id = ProviderID.make("xai");
const responsesRoute = Route.make({
id: "openai-responses",
provider: id,
providerMetadataKey: "xai",
protocol: OpenAIResponses.protocol,
endpoint: Endpoint.path("/responses", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
transport: OpenAIResponses.httpTransport,
defaults: { providerOptions: { xai: { store: false } } },
});
const chatRoute = Route.make({
id: "openai-compatible-chat",
provider: id,
providerMetadataKey: "xai",
protocol: OpenAIChat.protocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: OpenAICompatibleProfiles.profiles.xai.baseURL }),
transport: OpenAICompatibleChat.route.transport,
headers: ({ request }) => request.promptCacheKey ? { "x-grok-conv-id": request.promptCacheKey } : {},
});
export const routes = [responsesRoute, chatRoute];
const auth = (options) => AuthOptions.bearer(options, "XAI_API_KEY");
const configuredResponsesRoute = (input) => {
const { apiKey: _, auth: _auth, baseURL, ...rest } = input;
return responsesRoute.with({
...rest,
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
auth: auth(input),
});
};
const configuredChatRoute = (input) => {
const { apiKey: _, auth: _auth, baseURL, ...rest } = input;
return chatRoute.with({
...rest,
endpoint: { baseURL: baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL },
auth: auth(input),
});
};
export const configure = (input = {}) => {
const responsesRoute = configuredResponsesRoute(input);
const chatRoute = configuredChatRoute(input);
const responses = (modelID) => responsesRoute.model({ id: modelID });
const chat = (modelID) => chatRoute.model({ id: modelID });
const image = (modelID) => XAIImages.model({
id: modelID,
auth: auth(input),
baseURL: input.baseURL ?? OpenAICompatibleProfiles.profiles.xai.baseURL,
headers: input.headers,
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
});
return {
id,
model: responses,
responses,
chat,
image,
configure,
};
};
export const provider = configure();
export const model = (modelID, settings) => configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
limits: settings.limits,
providerOptions: settings.providerOptions,
}).model(modelID);
export const responses = provider.responses;
export const chat = provider.chat;
export const image = provider.image;
import { type ProviderAuthOption } from "../route/auth-options.js";
import { HttpOptions, type ModelID } from "../schema/index.js";
export declare const id: string & import("effect/Brand").Brand<"AI.ProviderID">;
export type Config = ProviderAuthOption<"optional"> & {
readonly baseURL?: string;
readonly headers?: Record<string, string>;
readonly http?: HttpOptions.Input;
};
export type { ZAIImageOptions } from "../protocols/zai-images.js";
export declare const configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/zai-images.js").ZAIImageOptions>;
configure: (input?: Config) => /*elided*/ any;
};
export declare const provider: {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/zai-images.js").ZAIImageOptions>;
configure: (input?: Config) => {
id: string & import("effect/Brand").Brand<"AI.ProviderID">;
image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/zai-images.js").ZAIImageOptions>;
configure: /*elided*/ any;
};
};
export declare const image: (modelID: string | ModelID) => import("../image.js").ImageModel<import("../protocols/zai-images.js").ZAIImageOptions>;
import { ZAIImages } from "../protocols/zai-images.js";
import { AuthOptions } from "../route/auth-options.js";
import { HttpOptions, ProviderID } from "../schema/index.js";
export const id = ProviderID.make("zai");
const auth = (options) => AuthOptions.bearer(options, "ZAI_API_KEY");
export const configure = (input = {}) => {
const image = (modelID) => ZAIImages.model({
id: modelID,
auth: auth(input),
baseURL: input.baseURL,
headers: input.headers,
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
});
return {
id,
image,
configure,
};
};
export const provider = configure();
export const image = provider.image;
export * from "./route/index.js";
export * from "./route/index.js";
import type { Config, Redacted } from "effect";
import { Auth } from "./auth.js";
export type ApiKeyMode = "optional" | "required";
export type AuthOverride = {
readonly auth: Auth.Definition;
readonly apiKey?: never;
};
export type OptionalApiKeyAuth = {
readonly apiKey?: string | Redacted.Redacted<string> | Config.Config<string | Redacted.Redacted<string>>;
readonly auth?: never;
};
export type RequiredApiKeyAuth = {
readonly apiKey: string | Redacted.Redacted<string> | Config.Config<string | Redacted.Redacted<string>>;
readonly auth?: never;
};
export type ProviderAuthOption<Mode extends ApiKeyMode> = AuthOverride | (Mode extends "optional" ? OptionalApiKeyAuth : RequiredApiKeyAuth);
export type LanguageModelOptions<Base, Mode extends ApiKeyMode> = Omit<Base, "apiKey" | "auth"> & ProviderAuthOption<Mode>;
export type LanguageModelArgs<Base, Mode extends ApiKeyMode> = Mode extends "optional" ? readonly [options?: LanguageModelOptions<Base, Mode>] : readonly [options: LanguageModelOptions<Base, Mode>];
export type LanguageModelFactory<Base, Mode extends ApiKeyMode, LanguageModel> = (id: string, ...args: LanguageModelArgs<Base, Mode>) => LanguageModel;
/**
* Require at least one of the keys in `T`. Use for option shapes where any
* subset of fields is acceptable but at least one must be present (e.g. Azure
* accepts `resourceName` or `baseURL`).
*/
export type AtLeastOne<T> = {
[K in keyof T]: Required<Pick<T, K>> & Partial<Omit<T, K>>;
}[keyof T];
/**
* Standard bearer-auth resolution for providers: honor an explicit `auth`
* override, otherwise resolve `apiKey` (option > config var) and apply it as
* a bearer token.
*/
export declare const bearer: (options: ProviderAuthOption<"optional">, envVar: string | ReadonlyArray<string>) => Auth.Definition;
export * as AuthOptions from "./auth-options.js";
import { Auth } from "./auth.js";
/**
* Standard bearer-auth resolution for providers: honor an explicit `auth`
* override, otherwise resolve `apiKey` (option > config var) and apply it as
* a bearer token.
*/
export const bearer = (options, envVar) => {
if ("auth" in options && options.auth)
return options.auth;
return (Array.isArray(envVar) ? envVar : [envVar])
.reduce((auth, name) => auth.orElse(Auth.config(name)), Auth.optional("apiKey" in options ? options.apiKey : undefined, "apiKey"))
.bearer();
};
export * as AuthOptions from "./auth-options.js";
import { Config, Effect, Redacted } from "effect";
import { Headers } from "effect/unstable/http";
import { AIError, type HttpOptions } from "../schema/index.js";
export declare class MissingCredentialError extends Error {
readonly source: string;
readonly _tag = "MissingCredentialError";
constructor(source: string);
}
export type CredentialError = MissingCredentialError | Config.ConfigError;
export type AuthError = CredentialError | AIError;
type Secret = string | Redacted.Redacted | Config.Config<string | Redacted.Redacted>;
export interface AuthInput {
readonly request: {
readonly http?: HttpOptions;
};
readonly method: "POST" | "GET";
readonly url: string;
readonly body: string;
readonly headers: Headers.Headers;
}
export interface Credential {
readonly load: Effect.Effect<Redacted.Redacted, CredentialError>;
readonly orElse: (that: Credential) => Credential;
readonly bearer: () => Definition;
readonly header: (name: string) => Definition;
readonly pipe: <A>(f: (self: Credential) => A) => A;
}
export interface Definition {
readonly apply: (input: AuthInput) => Effect.Effect<Headers.Headers, AuthError>;
readonly andThen: (that: Definition) => Definition;
readonly orElse: (that: Definition) => Definition;
readonly pipe: <A>(f: (self: Definition) => A) => A;
}
export declare const isAuth: (input: unknown) => input is Definition;
export declare const value: (secret: string, source?: string) => Credential;
export declare const optional: (secret: Secret | undefined, source?: string) => Credential;
export declare const config: (name: string) => Credential;
export declare const effect: (load: Effect.Effect<Redacted.Redacted, CredentialError>) => Credential;
export declare const none: Definition;
export declare const headers: (input: Headers.Input) => Definition;
export declare const remove: (name: string) => Definition;
export declare const custom: (apply: (input: AuthInput) => Effect.Effect<Headers.Headers, AIError>) => Definition;
export declare const passthrough: Definition;
export declare function bearer(source: Secret | Credential): Definition;
export declare const apiKey: typeof bearer;
export declare function header(name: string): (source: Secret | Credential) => Definition;
export declare function header(name: string, source: Secret | Credential): Definition;
export declare function bearerHeader(name: string): (source: Secret | Credential) => Definition;
export declare function bearerHeader(name: string, source: Secret | Credential): Definition;
export declare const toEffect: (input: Definition) => (authInput: AuthInput) => Effect.Effect<Headers.Headers, AIError>;
export * as Auth from "./auth.js";
import { Config, Effect, Redacted } from "effect";
import { Headers } from "effect/unstable/http";
import { AuthenticationReason, InvalidRequestReason, AIError } from "../schema/index.js";
export class MissingCredentialError extends Error {
source;
_tag = "MissingCredentialError";
constructor(source) {
super(`Missing auth credential: ${source}`);
this.source = source;
}
}
export const isAuth = (input) => typeof input === "object" && input !== null && "apply" in input && typeof input.apply === "function";
const credential = (load) => {
const self = {
load,
orElse: (that) => credential(load.pipe(Effect.catch(() => that.load))),
bearer: () => fromCredential(self, (secret) => ({ authorization: `Bearer ${secret}` })),
header: (name) => fromCredential(self, (secret) => ({ [name]: secret })),
pipe: (f) => f(self),
};
return self;
};
const auth = (apply) => {
const self = {
apply,
andThen: (that) => auth((input) => apply(input).pipe(Effect.flatMap((headers) => that.apply({ ...input, headers })))),
orElse: (that) => auth((input) => apply(input).pipe(Effect.catch(() => that.apply(input)))),
pipe: (f) => f(self),
};
return self;
};
const fromCredential = (source, render) => auth((input) => source.load.pipe(Effect.map((secret) => Headers.setAll(input.headers, render(Redacted.value(secret))))));
const secretEffect = (secret, source) => {
const redacted = typeof secret === "string" ? Redacted.make(secret) : secret;
if (Redacted.value(redacted) === "")
return Effect.fail(new MissingCredentialError(source));
return Effect.succeed(redacted);
};
const credentialFromSecret = (secret, source) => {
if (typeof secret === "string" || Redacted.isRedacted(secret))
return credential(secretEffect(secret, source));
return credential(Effect.gen(function* () {
return yield* secretEffect(yield* secret, source);
}));
};
export const value = (secret, source = "value") => credentialFromSecret(secret, source);
export const optional = (secret, source = "optional value") => secret === undefined
? credential(Effect.fail(new MissingCredentialError(source)))
: credentialFromSecret(secret, source);
export const config = (name) => credentialFromSecret(Config.redacted(name), name);
export const effect = (load) => credential(load);
export const none = auth((input) => Effect.succeed(input.headers));
export const headers = (input) => auth((inputAuth) => Effect.succeed(Headers.setAll(inputAuth.headers, input)));
export const remove = (name) => auth((input) => Effect.succeed(Headers.remove(input.headers, name)));
export const custom = (apply) => auth(apply);
export const passthrough = none;
const credentialInput = (source) => typeof source === "string" || Redacted.isRedacted(source) || Config.isConfig(source)
? credentialFromSecret(source, "value")
: source;
export function bearer(source) {
return credentialInput(source).bearer();
}
export const apiKey = bearer;
export function header(name, source) {
if (source === undefined) {
return (next) => credentialInput(next).header(name);
}
return credentialInput(source).header(name);
}
export function bearerHeader(name, source) {
const render = (input) => fromCredential(credentialInput(input), (secret) => ({ [name]: `Bearer ${secret}` }));
if (source === undefined)
return render;
return render(source);
}
const toAIError = (error) => {
if (error instanceof MissingCredentialError || error instanceof Config.ConfigError) {
return new AIError({
module: "Auth",
method: "apply",
reason: error instanceof MissingCredentialError
? new AuthenticationReason({ message: error.message, kind: "missing" })
: new InvalidRequestReason({ message: `Failed to resolve auth config: ${error.message}` }),
});
}
return error;
};
export const toEffect = (input) => (authInput) => input.apply(authInput).pipe(Effect.mapError(toAIError));
export * as Auth from "./auth.js";
import { Context, Effect, Layer, Schema, Stream } from "effect";
import { Auth } from "./auth.js";
import { Endpoint, type EndpointPatch } from "./endpoint.js";
import { RequestExecutor } from "./executor.js";
import { Framing } from "./framing.js";
import { HttpTransport } from "./transport/index.js";
import type { HttpMiddleware, Transport, TransportRuntime, WebSocketChannelExecutor } from "./transport/index.js";
import type { Protocol } from "./protocol.js";
import type { ProtocolID, ProviderOptions } from "../schema/index.js";
import { AIError, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LanguageModelLimits, LLMEvent, ProviderID } from "../schema/index.js";
export interface RouteBody<Body> {
/** Schema for the validated provider-native body sent as the JSON request. */
readonly schema: Schema.Codec<Body, unknown>;
/** Build the provider-native body from a common `LLMRequest`. */
readonly from: (request: LLMRequest) => Effect.Effect<Body, AIError>;
}
export interface Route<Body, Prepared = unknown> {
readonly id: string;
readonly provider?: ProviderID;
/** ProviderMetadata namespace emitted and consumed by this route. */
readonly providerMetadataKey?: string;
readonly protocol: ProtocolID;
readonly endpoint: Endpoint.Definition<Body>;
readonly auth: Auth.Definition;
readonly transport: Transport<Body, Prepared, unknown>;
readonly defaults: RouteDefaults;
readonly body: RouteBody<Body>;
readonly with: (patch: RoutePatch<Body, Prepared>) => Route<Body, Prepared>;
readonly model: <Options extends ProviderOptions = ProviderOptions>(input: RouteMappedLanguageModelInput) => LanguageModel<Options>;
readonly prepareTransport: (body: Body, request: LLMRequest, options?: StreamOptions) => Effect.Effect<Prepared, AIError>;
readonly streamPrepared: (prepared: Prepared, request: LLMRequest, runtime: TransportRuntime, options?: StreamOptions) => Stream.Stream<LLMEvent, AIError>;
}
export type AnyRoute = Route<any, any>;
export type HttpOptionsInput = HttpOptions.Input;
export type RouteLanguageModelInput = Omit<LanguageModel.Input, "provider" | "route">;
export type RouteRoutedLanguageModelInput = Omit<LanguageModel.Input, "route">;
export interface RouteDefaults {
readonly headers?: Record<string, string>;
readonly limits?: LanguageModelLimits;
readonly generation?: GenerationOptions;
readonly providerOptions?: ProviderOptions;
readonly http?: HttpOptions;
}
export interface RouteDefaultsInput {
readonly headers?: Record<string, string>;
readonly limits?: LanguageModelLimits.Input;
readonly generation?: GenerationOptions.Input;
readonly providerOptions?: ProviderOptions;
readonly http?: HttpOptions.Input;
}
export interface RoutePatch<Body, Prepared> extends RouteDefaultsInput {
readonly id?: string;
readonly provider?: string | ProviderID;
readonly auth?: Auth.Definition;
readonly transport?: Transport<Body, Prepared, unknown>;
readonly endpoint?: EndpointPatch<Body>;
}
type RouteMappedLanguageModelInput = RouteLanguageModelInput | RouteRoutedLanguageModelInput;
export declare const generationOptions: (input: GenerationOptions.Input | undefined) => GenerationOptions | undefined;
export declare const httpOptions: (input: HttpOptionsInput | undefined) => HttpOptions | undefined;
export interface Interface {
readonly stream: StreamMethod;
readonly generate: GenerateMethod;
}
export interface StreamOptions {
readonly http?: HttpMiddleware;
readonly webSocket?: WebSocketChannelExecutor;
}
export interface StreamMethod {
(request: LLMRequest, options?: StreamOptions): Stream.Stream<LLMEvent, AIError>;
}
export interface GenerateMethod {
(request: LLMRequest, options?: StreamOptions): Effect.Effect<LLMResponse, AIError>;
}
declare const Service_base: Context.ServiceClass<Service, "@opencode/LLMClient", Interface>;
export declare class Service extends Service_base {
}
export interface MakeInput<Body, Frame, Event, State> {
/** Route id used in diagnostics and prepared request metadata. */
readonly id: string;
/** Provider identity for route-owned model construction. */
readonly provider?: string | ProviderID;
/** ProviderMetadata namespace emitted and consumed by this route. */
readonly providerMetadataKey?: string;
/** Semantic API contract — owns body construction, body schema, and parsing. */
readonly protocol: Protocol<Body, Frame, Event, State>;
/** Where the request is sent. */
readonly endpoint: Endpoint.Definition<Body>;
/** Per-request transport auth. Provider facades override this via `route.with(...)`. */
readonly auth?: Auth.Definition;
/** Stream framing — bytes -> frames before `protocol.stream.event` decoding. */
readonly framing: Framing.Definition<Frame>;
/** Static / per-request headers added before `auth` runs. */
readonly headers?: (input: {
readonly request: LLMRequest;
}) => Record<string, string>;
/** Route/request defaults used when compiling requests for this route. */
readonly defaults?: RouteDefaultsInput;
}
export interface MakeTransportInput<Body, Prepared, Frame, Event, State> {
/** Route id used in diagnostics and prepared request metadata. */
readonly id: string;
/** Provider identity for route-owned model construction. */
readonly provider?: string | ProviderID;
/** ProviderMetadata namespace emitted and consumed by this route. */
readonly providerMetadataKey?: string;
/** Semantic API contract — owns body construction, body schema, and parsing. */
readonly protocol: Protocol<Body, Frame, Event, State>;
/** Where the request is sent. */
readonly endpoint: Endpoint.Definition<Body>;
/** Per-request transport auth. Provider facades override this via `route.with(...)`. */
readonly auth?: Auth.Definition;
/** Static / per-request headers added before `auth` runs. */
readonly headers?: (input: {
readonly request: LLMRequest;
}) => Record<string, string>;
/** Runnable transport route. */
readonly transport: Transport<Body, Prepared, Frame>;
/** Route/request defaults used when compiling requests for this route. */
readonly defaults?: RouteDefaultsInput;
}
export declare function make<Body, Prepared, Frame, Event, State>(input: MakeTransportInput<Body, Prepared, Frame, Event, State>): Route<Body, Prepared>;
/**
* Build a `Route` by composing the four orthogonal pieces of a deployment:
*
* - `Protocol` — what is the API I'm speaking?
* - `Endpoint` — where do I send the request?
* - `Auth` — how do I authenticate it?
* - `Framing` — how do I cut the response stream into protocol frames?
*
* Plus optional `headers` for cross-cutting deployment concerns (provider
* version pins, per-deployment quirks).
*
* This is the canonical route constructor. If a new route does not fit
* this four-axis model, add a purpose-built constructor rather than widening
* the public surface preemptively.
*/
export declare function make<Body, Frame, Event, State>(input: MakeInput<Body, Frame, Event, State>): Route<Body, HttpTransport.HttpPrepared<Frame>>;
/** @internal Test-only projection of the execution compiler; not exported from package barrels. */
export declare const compileRequest: (request: LLMRequest) => Effect.Effect<{
id: string;
route: string;
protocol: string;
model: LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>;
body: any;
metadata: {
transport: string;
};
}, AIError, never>;
export declare function stream(request: LLMRequest, options?: StreamOptions): Stream.Stream<LLMEvent, AIError, Service>;
export declare function generate(request: LLMRequest, options?: StreamOptions): Effect.Effect<LLMResponse, AIError, Service>;
export declare const streamRequest: (request: LLMRequest, options?: StreamOptions) => Stream.Stream<{
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("../schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
}, AIError, Service>;
export declare const layer: Layer.Layer<Service, never, RequestExecutor.Service>;
export declare const Route: {
readonly make: typeof make;
};
export declare const LLMClient: {
readonly Service: typeof Service;
readonly layer: Layer.Layer<Service, never, RequestExecutor.Service>;
readonly stream: typeof stream;
readonly generate: typeof generate;
};
export {};
import { Cause, Context, Effect, Layer, Schema, Stream } from "effect";
import { Auth } from "./auth.js";
import { Endpoint } from "./endpoint.js";
import { RequestExecutor } from "./executor.js";
import { Framing } from "./framing.js";
import { HttpTransport } from "./transport/index.js";
import { applyCachePolicy } from "../cache-policy.js";
import * as ProviderShared from "../protocols/shared.js";
import { AIError, GenerationOptions, HttpOptions, LLMRequest, LLMResponse, LanguageModel, LanguageModelLimits, LLMEvent, InvalidProviderOutputReason, ProviderID, mergeGenerationOptions, mergeHttpOptions, mergeProviderOptions, } from "../schema/index.js";
const makeRouteLanguageModel = (route, mapped) => {
const provider = route.provider ?? ("provider" in mapped ? mapped.provider : undefined);
if (!provider)
throw new Error(`Route.model(${route.id}) requires a provider`);
if (!endpointBaseURL(route.endpoint))
throw new Error(`Route.model(${route.id}) requires an endpoint baseURL — configure it on the route first`);
return LanguageModel.make({
...mapped,
provider,
route,
});
};
const mergeRouteDefaults = (base, patch) => {
const headers = mergeHeaders(base?.headers, patch.headers);
return {
...base,
...patch,
headers,
limits: patch.limits === undefined ? base?.limits : LanguageModelLimits.make(patch.limits),
generation: mergeGenerationOptions(generationOptions(base?.generation), generationOptions(patch.generation)),
providerOptions: mergeProviderOptions(base?.providerOptions, patch.providerOptions),
http: mergeHttpOptions(base?.http, httpOptions(patch.http), headers === undefined ? undefined : new HttpOptions({ headers })),
};
};
const endpointBaseURL = (endpoint) => typeof endpoint.baseURL === "string" ? endpoint.baseURL : undefined;
const mergeHeaders = (...items) => {
const entries = items.flatMap((item) => item === undefined ? [] : Object.entries(item).filter((entry) => entry[1] !== undefined));
if (entries.length === 0)
return undefined;
return Object.fromEntries(entries);
};
export const generationOptions = (input) => input === undefined ? undefined : GenerationOptions.make(input);
export const httpOptions = (input) => {
if (input === undefined)
return input;
return HttpOptions.make(input);
};
export class Service extends Context.Service()("@opencode/LLMClient") {
}
const resolveRequestOptions = (request) => {
const routeDefaults = request.model.route.defaults;
const modelDefaults = request.model.defaults;
const generation = mergeGenerationOptions(routeDefaults.generation, modelDefaults?.generation, request.generation);
return LLMRequest.update(request, {
generation: generation ?? new GenerationOptions({}),
providerOptions: mergeProviderOptions(routeDefaults.providerOptions, modelDefaults?.providerOptions, request.providerOptions),
http: mergeHttpOptions(routeDefaults.http, modelDefaults?.http, request.http),
});
};
const streamError = (route, message, cause) => {
const failed = cause.reasons.find(Cause.isFailReason)?.error;
if (failed instanceof AIError)
return failed;
return ProviderShared.eventError(route, message, Cause.pretty(cause));
};
const incompleteStreamError = (route) => new AIError({
module: "LLMClient",
method: "stream",
reason: new InvalidProviderOutputReason({
classification: "incomplete-stream",
message: "The provider response ended unexpectedly.",
route,
}),
});
const requireTerminalEvent = (route) => (events) => Stream.suspend(() => {
let terminal = false;
return events.pipe(Stream.mapEffect((event) => {
if (terminal)
return Effect.fail(ProviderShared.eventError(route, `Provider emitted ${event.type} after the terminal event`));
if (LLMEvent.is.finish(event) || LLMEvent.is.providerError(event))
terminal = true;
return Effect.succeed(event);
}), Stream.onEnd(Effect.suspend(() => (terminal ? Effect.void : Effect.fail(incompleteStreamError(route))))));
});
function makeFromTransport(input) {
const protocol = input.protocol;
const encodeBody = Schema.encodeSync(Schema.fromJsonString(protocol.body.schema));
const decodeEventEffect = Schema.decodeUnknownEffect(protocol.stream.event);
const decodeEvent = (route) => (frame) => decodeEventEffect(frame).pipe(Effect.mapError(() => ProviderShared.eventError(input.id, `Invalid ${route} stream event`, typeof frame === "string" ? frame : ProviderShared.encodeJson(frame))));
const build = (routeInput) => {
const route = {
id: routeInput.id,
provider: routeInput.provider === undefined ? undefined : ProviderID.make(routeInput.provider),
providerMetadataKey: routeInput.providerMetadataKey,
protocol: protocol.id,
endpoint: routeInput.endpoint,
auth: routeInput.auth ?? Auth.none,
transport: routeInput.transport,
defaults: routeInput.defaults ?? {},
body: protocol.body,
with: (patch) => {
const { id, provider, auth, transport, endpoint, ...defaults } = patch;
return build({
...routeInput,
id: id ?? routeInput.id,
provider: provider ?? routeInput.provider,
auth: auth ?? routeInput.auth,
endpoint: endpoint ? Endpoint.merge(routeInput.endpoint, endpoint) : routeInput.endpoint,
transport: transport ?? routeInput.transport,
defaults: mergeRouteDefaults(route.defaults, defaults),
});
},
model: (input) => makeRouteLanguageModel(route, input),
prepareTransport: (body, request, options) => routeInput.transport.prepare({
body,
request,
endpoint: routeInput.endpoint,
auth: routeInput.auth ?? Auth.none,
encodeBody,
headers: routeInput.headers,
middleware: options?.http,
webSocket: options?.webSocket,
}),
streamPrepared: (prepared, request, runtime, options) => {
const route = `${request.model.provider}/${request.model.route.id}`;
return Stream.unwrap(routeInput.transport.execute(prepared, request, runtime, options).pipe(Effect.map((execution) => {
const events = execution.frames.pipe(Stream.mapEffect(decodeEvent(route)), protocol.stream.terminal ? Stream.takeUntil(protocol.stream.terminal) : (stream) => stream);
const stream = events.pipe(Stream.mapAccumEffect(() => protocol.stream.initial(request), protocol.stream.step, protocol.stream.onHalt ? { onHalt: protocol.stream.onHalt } : undefined), Stream.catchCause((cause) => Stream.fail(streamError(route, `Failed to read ${route} stream`, cause))), requireTerminalEvent(route));
return execution.complete ? stream.pipe(Stream.onEnd(execution.complete)) : stream;
})));
},
};
return route;
};
return build({ ...input, defaults: mergeRouteDefaults(undefined, input.defaults ?? {}) });
}
export function make(input) {
if ("transport" in input)
return makeFromTransport(input);
const protocol = input.protocol;
return makeFromTransport({
id: input.id,
provider: input.provider,
providerMetadataKey: input.providerMetadataKey,
protocol,
endpoint: input.endpoint,
auth: input.auth,
headers: input.headers,
transport: HttpTransport.httpJson({ framing: input.framing }),
defaults: input.defaults,
});
}
const compile = Effect.fn("LLM.compile")(function* (request, options) {
const resolved = applyCachePolicy(resolveRequestOptions(request));
const route = resolved.model.route;
const body = yield* route.body
.from(resolved)
.pipe(Effect.flatMap(ProviderShared.validateWith(Schema.decodeUnknownEffect(route.body.schema))));
const prepared = yield* route.prepareTransport(body, resolved, options);
return {
request: resolved,
route,
body,
prepared,
};
});
/** @internal Test-only projection of the execution compiler; not exported from package barrels. */
export const compileRequest = Effect.fn("LLM.compileRequest")(function* (request) {
const compiled = yield* compile(request);
return {
id: compiled.request.id ?? "request",
route: compiled.route.id,
protocol: compiled.route.protocol,
model: compiled.request.model,
body: compiled.body,
metadata: { transport: compiled.route.transport.id },
};
});
const streamRequestWith = (runtime) => (request, options) => Stream.unwrap(Effect.gen(function* () {
const compiled = yield* compile(request, options);
return compiled.route.streamPrepared(compiled.prepared, compiled.request, runtime, options);
}));
const generateWith = (stream) => Effect.fn("LLM.generate")(function* (request, options) {
const state = yield* stream(request, options).pipe(Stream.runFold(LLMResponse.empty, LLMResponse.reduce));
const response = LLMResponse.complete(state);
if (response)
return response;
return yield* incompleteStreamError(`${request.model.provider}/${request.model.route.id}`);
});
export function stream(request, options) {
return Stream.unwrap(Effect.gen(function* () {
return (yield* Service).stream(request, options);
}));
}
export function generate(request, options) {
return Effect.gen(function* () {
return yield* (yield* Service).generate(request, options);
});
}
export const streamRequest = (request, options) => Stream.unwrap(Effect.gen(function* () {
return (yield* Service).stream(request, options);
}));
export const layer = Layer.effect(Service, Effect.gen(function* () {
const stream = streamRequestWith({
http: yield* RequestExecutor.Service,
});
return Service.of({ stream, generate: generateWith(stream) });
}));
export const Route = { make };
export const LLMClient = {
Service,
layer,
stream,
generate,
};
import type { LLMRequest } from "../schema/index.js";
export interface EndpointInput<Body> {
readonly request: LLMRequest;
readonly body: Body;
}
export type EndpointPart<Body> = string | ((input: EndpointInput<Body>) => string);
/**
* Declarative URL construction for one route.
*
* `Endpoint` carries URL construction for one route. Routes with a canonical
* host put `baseURL` here; provider helpers can override it by configuring the
* route before selecting a model.
*
* `path` may be a string or a function of `EndpointInput`, for routes whose
* URL embeds the model id, region, or another body field (e.g. Bedrock,
* Gemini).
*/
export interface Definition<Body> {
readonly baseURL?: string;
readonly path: EndpointPart<Body>;
readonly query?: Record<string, string>;
}
export type EndpointPatch<Body> = Partial<Definition<Body>>;
/** Construct an `Endpoint` from a path string or path function. */
export declare const path: <Body>(value: EndpointPart<Body>, options?: Omit<Definition<Body>, "path">) => Definition<Body>;
export declare const merge: <Body>(base: Definition<Body>, patch: EndpointPatch<Body>) => Definition<Body>;
export declare const render: <Body>(endpoint: Definition<Body>, input: EndpointInput<Body>) => URL;
export * as Endpoint from "./endpoint.js";
import * as ProviderShared from "../protocols/shared.js";
/** Construct an `Endpoint` from a path string or path function. */
export const path = (value, options = {}) => ({
...options,
path: value,
});
export const merge = (base, patch) => ({
...base,
...patch,
baseURL: patch.baseURL ?? base.baseURL,
path: patch.path ?? base.path,
query: patch.query === undefined ? base.query : { ...base.query, ...patch.query },
});
const renderPart = (part, input) => typeof part === "function" ? part(input) : part;
export const render = (endpoint, input) => {
const url = new URL(`${ProviderShared.trimBaseUrl(endpoint.baseURL ?? "")}${renderPart(endpoint.path, input)}`);
for (const [key, value] of Object.entries(endpoint.query ?? {}))
url.searchParams.set(key, value);
return url;
};
export * as Endpoint from "./endpoint.js";
import { Context, Effect, Layer, Stream } from "effect";
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http";
import { AIError, TransportReason } from "../schema/index.js";
export interface Interface {
readonly execute: (request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Effect.Effect<HttpClientResponse.HttpClientResponse, AIError>;
}
export type HttpHandler = (request: HttpClientRequest.HttpClientRequest) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
export type HttpMiddleware = (request: HttpClientRequest.HttpClientRequest, handler: HttpHandler) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error>;
declare const Service_base: Context.ServiceClass<Service, "@opencode/AI/RequestExecutor", Interface>;
export declare class Service extends Service_base {
}
export declare const classifyHttpFailure: (input: {
readonly message: string;
readonly url: string;
readonly status?: number | undefined;
readonly code?: string | undefined;
readonly responseHeaders?: Record<string, string> | undefined;
readonly responseBody?: string | undefined;
}) => import("../schema/errors.js").InvalidRequestReason | import("../schema/errors.js").NoRouteReason | import("../schema/errors.js").AuthenticationReason | import("../schema/errors.js").RateLimitReason | import("../schema/errors.js").QuotaExceededReason | import("../schema/errors.js").ContentPolicyReason | import("../schema/errors.js").ProviderInternalReason | TransportReason | import("../schema/errors.js").InvalidProviderOutputReason | import("../schema/errors.js").UnknownProviderReason;
export declare const stream: (executor: Interface, request: HttpClientRequest.HttpClientRequest, middleware?: HttpMiddleware) => Stream.Stream<Uint8Array, AIError>;
export declare const layer: Layer.Layer<Service, never, HttpClient.HttpClient>;
export declare const fetchLayer: Layer.Layer<Service, never, never>;
export * as RequestExecutor from "./executor.js";
import { Cause, Context, Effect, Layer, Option, Schema, Stream } from "effect";
import { FetchHttpClient, Headers, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, } from "effect/unstable/http";
import { HttpContext, HttpRateLimitDetails, HttpRequestDetails, HttpResponseDetails, AIError, TransportReason, } from "../schema/index.js";
import { classifyProviderFailure } from "../provider-error.js";
export class Service extends Context.Service()("@opencode/AI/RequestExecutor") {
}
const headerDetails = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, String(value)]));
const normalizedHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
const requestId = (headers) => {
return (headers["x-request-id"] ??
headers["request-id"] ??
headers["x-amzn-requestid"] ??
headers["x-amz-request-id"] ??
headers["x-goog-request-id"] ??
headers["cf-ray"]);
};
const retryAfterMs = (headers) => {
const millis = Number(headers["retry-after-ms"]);
if (Number.isFinite(millis))
return Math.max(0, millis);
const value = headers["retry-after"];
if (!value)
return undefined;
const seconds = Number(value);
if (Number.isFinite(seconds))
return Math.max(0, seconds * 1000);
const date = Date.parse(value);
if (!Number.isNaN(date))
return Math.max(0, date - Date.now());
return undefined;
};
const addRateLimitValue = (target, key, value) => {
if (key.length > 0)
target[key] = value;
};
const rateLimitDetails = (headers, retryAfter) => {
const limit = {};
const remaining = {};
const reset = {};
Object.entries(headers).forEach(([name, value]) => {
const openaiLimit = /^x-ratelimit-limit-(.+)$/.exec(name)?.[1];
if (openaiLimit)
return addRateLimitValue(limit, openaiLimit, value);
const openaiRemaining = /^x-ratelimit-remaining-(.+)$/.exec(name)?.[1];
if (openaiRemaining)
return addRateLimitValue(remaining, openaiRemaining, value);
const openaiReset = /^x-ratelimit-reset-(.+)$/.exec(name)?.[1];
if (openaiReset)
return addRateLimitValue(reset, openaiReset, value);
const anthropic = /^anthropic-ratelimit-(.+)-(limit|remaining|reset)$/.exec(name);
if (!anthropic)
return;
if (anthropic[2] === "limit")
return addRateLimitValue(limit, anthropic[1], value);
if (anthropic[2] === "remaining")
return addRateLimitValue(remaining, anthropic[1], value);
return addRateLimitValue(reset, anthropic[1], value);
});
if (retryAfter === undefined &&
Object.keys(limit).length === 0 &&
Object.keys(remaining).length === 0 &&
Object.keys(reset).length === 0)
return undefined;
return new HttpRateLimitDetails({
retryAfterMs: retryAfter,
limit: Object.keys(limit).length === 0 ? undefined : limit,
remaining: Object.keys(remaining).length === 0 ? undefined : remaining,
reset: Object.keys(reset).length === 0 ? undefined : reset,
});
};
const requestDetails = (request) => new HttpRequestDetails({
method: request.method,
url: request.url,
headers: headerDetails(request.headers),
});
const responseDetails = (response) => new HttpResponseDetails({
status: response.status,
headers: headerDetails(response.headers),
});
const responseBody = (body) => {
if (body === undefined)
return {};
return { body };
};
const decodeProviderBody = Schema.decodeUnknownOption(Schema.fromJsonString(Schema.Struct({
message: Schema.optionalKey(Schema.String),
error: Schema.optionalKey(Schema.Struct({ message: Schema.optionalKey(Schema.String) })),
})));
const providerMessage = (status, body) => {
const decoded = body === undefined ? undefined : Option.getOrUndefined(decodeProviderBody(body));
return ([decoded?.error?.message, decoded?.message].find((message) => message?.trim()) ??
`Provider request failed with HTTP ${status}`);
};
const responseHttp = (input) => new HttpContext({
request: requestDetails(input.request),
response: responseDetails(input.response),
...input.body,
requestId: input.requestId,
rateLimit: input.rateLimit,
});
const statusError = (request) => (response) => Effect.gen(function* () {
if (response.status < 400)
return response;
const body = yield* response.text.pipe(Effect.catch(() => Effect.void));
const headers = normalizedHeaders(response.headers);
const retryAfter = retryAfterMs(headers);
const rateLimit = rateLimitDetails(headers, retryAfter);
const details = responseBody(body);
return yield* new AIError({
module: "RequestExecutor",
method: "execute",
reason: classifyProviderFailure({
status: response.status,
message: providerMessage(response.status, body),
retryAfterMs: retryAfter,
rateLimit,
http: responseHttp({
request,
response,
body: details,
requestId: requestId(headers),
rateLimit,
}),
}),
});
});
// Classifies an HTTP failure captured outside the executor (for example by the
// AI SDK's own fetch) onto the same reason types and HttpContext that
// executor-driven requests produce. The originating request is not available on
// that path, so the method is assumed (language model calls are always POST),
// request headers are empty.
export const classifyHttpFailure = (input) => {
const headers = normalizedHeaders(Headers.fromInput(input.responseHeaders));
const retryAfter = retryAfterMs(headers);
const rateLimit = rateLimitDetails(headers, retryAfter);
const details = responseBody(input.responseBody);
return classifyProviderFailure({
message: input.message,
status: input.status,
code: input.code,
retryAfterMs: retryAfter,
rateLimit,
http: new HttpContext({
request: new HttpRequestDetails({ method: "POST", url: input.url, headers: {} }),
response: input.status === undefined
? undefined
: new HttpResponseDetails({ status: input.status, headers: headerDetails(Headers.fromInput(headers)) }),
...details,
requestId: requestId(headers),
rateLimit,
}),
});
};
const NativeTransportFailure = Schema.Struct({
message: Schema.String,
code: Schema.optionalKey(Schema.String),
cause: Schema.optionalKey(Schema.Unknown),
});
const decodeNativeTransportFailure = Schema.decodeUnknownOption(NativeTransportFailure);
const nativeTransportFailure = (error) => {
const failure = Option.getOrUndefined(decodeNativeTransportFailure(error));
if (!failure)
return undefined;
if (failure.code !== undefined)
return failure;
const cause = Option.getOrUndefined(decodeNativeTransportFailure(failure.cause));
if (cause?.code !== undefined)
return cause;
return failure;
};
const httpError = (input) => {
const request = HttpClientError.isHttpClientError(input.error) ? input.error.request : input.request;
const transportError = (failure) => new AIError({
module: "RequestExecutor",
method: input.operation,
reason: new TransportReason({
message: failure.message,
transport: "http",
operation: input.operation,
code: failure.code,
url: request.url,
http: new HttpContext({ request: requestDetails(request) }),
}),
});
const source = HttpClientError.isHttpClientError(input.error) && "cause" in input.error.reason
? input.error.reason.cause
: input.error;
const native = nativeTransportFailure(source);
const code = native?.code;
const raw = native?.message ?? (input.error instanceof Error ? input.error.message : undefined);
const detail = raw;
const message = code && detail && !detail.includes(code) ? `${code}: ${detail}` : detail;
if (Cause.isTimeoutError(input.error) || Cause.isTimeoutError(source))
return transportError({ message: message ?? "HTTP transport timed out", code: code ?? "Timeout" });
if (!HttpClientError.isHttpClientError(input.error))
return transportError({ message: message ?? "HTTP transport failed", code });
if (input.error.reason._tag === "TransportError") {
return transportError({
message: message ?? input.error.reason.description ?? "HTTP transport failed",
code: code ?? input.error.reason._tag,
});
}
return transportError({
message: message ?? `HTTP transport failed: ${input.error.reason._tag}`,
code: code ?? input.error.reason._tag,
});
};
export const stream = (executor, request, middleware) => Stream.unwrap(Effect.gen(function* () {
const response = yield* executor.execute(request, middleware);
return response.stream.pipe(Stream.mapError((error) => httpError({ error, request: response.request, operation: "read" })));
}));
export const layer = Layer.effect(Service, Effect.gen(function* () {
const http = yield* HttpClient.HttpClient;
const executeOnce = (request, middleware) => Effect.gen(function* () {
if (!middleware)
return yield* http.execute(request).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request" })), Effect.flatMap(statusError(request)));
const response = yield* middleware(request, (input) => http
.execute(input)
.pipe(Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause)))))).pipe(Effect.mapError((error) => httpError({ error, request, operation: "request" })));
return yield* statusError(response.request)(response);
});
return Service.of({
execute: executeOnce,
});
}));
export const fetchLayer = layer.pipe(Layer.provide(FetchHttpClient.layer));
export * as RequestExecutor from "./executor.js";
import type { Stream } from "effect";
import type { AIError } from "../schema/index.js";
/**
* Decode a streaming HTTP response body into provider-protocol frames.
*
* `Framing` is the byte-stream-shaped seam between transport and protocol:
*
* - SSE (`Framing.sse`) — UTF-8 decode the body, run the SSE channel decoder,
* drop empty / `[DONE]` keep-alives. Each emitted frame is the JSON `data:`
* payload of one event.
* - AWS event stream — length-prefixed binary frames with CRC checksums.
* Each emitted frame is one parsed binary event record.
*
* The frame type is opaque to this layer; the protocol's `decode` step turns
* a frame into a typed chunk.
*/
export interface Definition<Frame> {
readonly id: string;
readonly frame: (bytes: Stream.Stream<Uint8Array, AIError>) => Stream.Stream<Frame, AIError>;
}
/** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
export declare const sse: Definition<string>;
export * as Framing from "./framing.js";
import * as ProviderShared from "../protocols/shared.js";
/** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
export const sse = { id: "sse", frame: ProviderShared.sseFraming };
export * as Framing from "./framing.js";
export { Route, LLMClient } from "./client.js";
export type { Route as RouteShape, RouteLanguageModelInput, RouteRoutedLanguageModelInput, RouteDefaults, RouteDefaultsInput, AnyRoute, Interface as LLMClientShape, Service as LLMClientService, StreamOptions, } from "./client.js";
export * from "./executor.js";
export { Auth } from "./auth.js";
export { AuthOptions } from "./auth-options.js";
export { Endpoint } from "./endpoint.js";
export { Framing } from "./framing.js";
export { Protocol } from "./protocol.js";
export { HttpTransport, WebSocketTransport } from "./transport/index.js";
export * as Transport from "./transport/index.js";
export type { Definition as AuthShape, AuthInput, Credential, CredentialError } from "./auth.js";
export type { ApiKeyMode, AuthOverride, ProviderAuthOption } from "./auth-options.js";
export type { Definition as EndpointFn, EndpointInput } from "./endpoint.js";
export type { Definition as FramingDef } from "./framing.js";
export type { Protocol as ProtocolDef } from "./protocol.js";
export type { ChannelCheckpoint, ChannelCreate, ChannelObservation, HttpHandler, HttpMiddleware, Transport as TransportDef, TransportExecuteOptions, TransportExecution, TransportRuntime, WebSocketConnection, WebSocketChannelDriver, WebSocketChannelExchange, WebSocketChannelExecution, WebSocketChannelExecutor, WebSocketConnector, WebSocketRequest, } from "./transport/index.js";
export { Route, LLMClient } from "./client.js";
export * from "./executor.js";
export { Auth } from "./auth.js";
export { AuthOptions } from "./auth-options.js";
export { Endpoint } from "./endpoint.js";
export { Framing } from "./framing.js";
export { Protocol } from "./protocol.js";
export { HttpTransport, WebSocketTransport } from "./transport/index.js";
export * as Transport from "./transport/index.js";
import { Schema, type Effect } from "effect";
import type { AIError, LLMEvent, LLMRequest, ProtocolID } from "../schema/index.js";
/**
* The semantic API contract of one model server family.
*
* A `Protocol` owns the parts of a route that are intrinsic to "what does
* this API look like": how a common `LLMRequest` becomes a provider-native
* body, what schema that body must satisfy before it is JSON-encoded, and
* how the streaming response decodes back into common `LLMEvent`s.
*
* Examples:
*
* - `OpenAIChat.protocol` — chat completions style
* - `OpenResponses.protocol` — provider-neutral Responses API baseline
* - `OpenAIResponses.protocol` — OpenAI extensions to that baseline
* - `AnthropicMessages.protocol` — messages API with content blocks
* - `Gemini.protocol` — generateContent
* - `BedrockConverse.protocol` — Converse with binary event-stream framing
*
* A `Protocol` is **not** a deployment. It does not know which URL, which
* headers, or which auth scheme to use. Those are deployment concerns owned
* by `Route.make(...)` along with the chosen `Endpoint`, `Auth`,
* and `Framing`. This separation is what lets DeepSeek, TogetherAI, Cerebras,
* etc. all reuse `OpenAIChat.protocol` without forking 300 lines per provider.
*
* The four type parameters reflect the pipeline:
*
* - `Body` — provider-native request body candidate. `Route.make(...)`
* validates and JSON-encodes it with `body.schema`.
* - `Frame` — one unit of the framed response stream. SSE: a JSON data
* string. AWS event stream: a parsed binary frame.
* - `Event` — schema-decoded provider event produced from one frame.
* - `State` — accumulator threaded through `stream.step` to translate event
* sequences into `LLMEvent` sequences.
*/
export interface Protocol<Body, Frame, Event, State> {
/** Stable id for the wire protocol implementation. */
readonly id: ProtocolID;
/** Request side: schema for the provider-native body and how to build it. */
readonly body: ProtocolBody<Body>;
/** Response side: streaming state machine. */
readonly stream: ProtocolStream<Frame, Event, State>;
}
export interface ProtocolBody<Body> {
/** Schema for the validated provider-native body sent as the JSON request. */
readonly schema: Schema.Codec<Body, unknown>;
/** Build the provider-native body from a common `LLMRequest`. */
readonly from: (request: LLMRequest) => Effect.Effect<Body, AIError>;
}
export interface ProtocolStream<Frame, Event, State> {
/** Schema for one decoded streaming event, decoded from a transport frame. */
readonly event: Schema.Codec<Event, Frame>;
/** Initial parser state. Called once per response with the resolved request. */
readonly initial: (request: LLMRequest) => State;
/** Translate one event into emitted `LLMEvent`s plus the next state. */
readonly step: (state: State, event: Event) => Effect.Effect<readonly [State, ReadonlyArray<LLMEvent>], AIError>;
/** Optional request-completion signal for transports that do not end naturally. */
readonly terminal?: (event: Event) => boolean;
/** Optional flush emitted when the framed stream ends. */
readonly onHalt?: (state: State) => ReadonlyArray<LLMEvent>;
}
/**
* Construct a `Protocol` from its body and stream pieces:
*
* - `body.schema` infers the provider-native request body shape.
* - `body.from` ties the common `LLMRequest` to the provider body.
* - `stream.event` infers the decoded streaming event and the wire frame.
* - `stream.initial`, `stream.step`, and `stream.onHalt` infer the parser state.
*
* Provider implementations should usually call `Protocol.make({ ... })`
* without explicit type arguments; the schemas and parser functions are the
* source of truth. The constructor remains as the public seam for future
* cross-cutting concerns such as tracing or instrumentation.
*/
export declare const make: <Body, Frame, Event, State>(input: Protocol<Body, Frame, Event, State>) => Protocol<Body, Frame, Event, State>;
export declare const jsonEvent: <const S extends Schema.Top>(schema: S) => Schema.fromJsonString<S>;
export * as Protocol from "./protocol.js";
import { Schema } from "effect";
/**
* Construct a `Protocol` from its body and stream pieces:
*
* - `body.schema` infers the provider-native request body shape.
* - `body.from` ties the common `LLMRequest` to the provider body.
* - `stream.event` infers the decoded streaming event and the wire frame.
* - `stream.initial`, `stream.step`, and `stream.onHalt` infer the parser state.
*
* Provider implementations should usually call `Protocol.make({ ... })`
* without explicit type arguments; the schemas and parser functions are the
* source of truth. The constructor remains as the public seam for future
* cross-cutting concerns such as tracing or instrumentation.
*/
export const make = (input) => input;
export const jsonEvent = (schema) => Schema.fromJsonString(schema);
export * as Protocol from "./protocol.js";
import { Effect } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { Framing } from "../framing.js";
import type { HttpMiddleware, Transport, TransportPrepareInput } from "./index.js";
export type JsonRequestInput<Body> = TransportPrepareInput<Body>;
export interface JsonRequestParts<Body = unknown> {
readonly url: string;
readonly jsonBody: Body | Record<string, unknown>;
readonly bodyText: string;
readonly headers: Headers.Headers;
}
export interface HttpPrepared<Frame> {
readonly request: HttpClientRequest.HttpClientRequest;
readonly framing: Framing.Definition<Frame>;
readonly middleware?: HttpMiddleware;
}
export declare const jsonRequestParts: <Body>(input: JsonRequestInput<Body>) => Effect.Effect<{
url: string;
jsonBody: Record<string, unknown> | Body;
bodyText: string;
headers: Headers.Headers;
}, import("../../schema/errors.js").AIError, never>;
export interface HttpJsonInput<_Body, Frame> {
readonly framing: Framing.Definition<Frame>;
}
export type HttpJsonPatch<Body, Frame> = Partial<HttpJsonInput<Body, Frame>>;
export interface HttpJsonTransport<Body, Frame> extends Transport<Body, HttpPrepared<Frame>, Frame> {
readonly with: (patch: HttpJsonPatch<Body, Frame>) => HttpJsonTransport<Body, Frame>;
}
export declare const httpJson: <Body, Frame>(input: HttpJsonInput<Body, Frame>) => HttpJsonTransport<Body, Frame>;
export declare const sseJson: {
readonly id: "http-json/sse";
readonly with: <Body>() => HttpJsonTransport<Body, string>;
};
import { Effect } from "effect";
import { Headers, HttpClientRequest } from "effect/unstable/http";
import { Auth } from "../auth.js";
import { render as renderEndpoint } from "../endpoint.js";
import { Framing } from "../framing.js";
import * as ProviderShared from "../../protocols/shared.js";
import { mergeJsonRecords } from "../../schema/index.js";
import { RequestExecutor } from "../executor.js";
const applyQuery = (url, query) => {
if (!query)
return url;
const next = new URL(url);
Object.entries(query).forEach(([key, value]) => next.searchParams.set(key, value));
return next.toString();
};
const bodyWithOverlay = (body, request, encodeBody) => Effect.gen(function* () {
if (request.http?.body === undefined)
return { jsonBody: body, bodyText: encodeBody(body) };
if (ProviderShared.isRecord(body)) {
const overlaid = mergeJsonRecords(body, request.http.body) ?? {};
return { jsonBody: overlaid, bodyText: ProviderShared.encodeJson(overlaid) };
}
return yield* ProviderShared.invalidRequest("http.body can only overlay JSON object request bodies");
});
export const jsonRequestParts = (input) => Effect.gen(function* () {
const url = applyQuery(renderEndpoint(input.endpoint, { request: input.request, body: input.body }).toString(), input.request.http?.query);
const body = yield* bodyWithOverlay(input.body, input.request, input.encodeBody);
const headers = yield* Auth.toEffect(input.auth)({
request: input.request,
method: "POST",
url,
body: body.bodyText,
headers: Headers.fromInput({
...input.headers?.({ request: input.request }),
...input.request.http?.headers,
}),
});
return { url, jsonBody: body.jsonBody, bodyText: body.bodyText, headers };
});
export const httpJson = (input) => ({
id: "http-json",
with: (patch) => httpJson({ ...input, ...patch }),
prepare: (prepareInput) => Effect.gen(function* () {
const parts = yield* jsonRequestParts({ ...prepareInput });
const request = ProviderShared.jsonPost({
url: parts.url,
body: parts.bodyText,
headers: parts.headers,
});
return {
request,
framing: input.framing,
middleware: prepareInput.middleware,
};
}),
execute: (prepared, _request, runtime) => Effect.succeed({
frames: prepared.framing.frame(RequestExecutor.stream(runtime.http, prepared.request, prepared.middleware)),
}),
});
export const sseJson = {
id: "http-json/sse",
with: () => httpJson({ framing: Framing.sse }),
};
import type { Effect, Scope, Stream } from "effect";
import { Endpoint } from "../endpoint.js";
import { Auth } from "../auth.js";
import type { HttpMiddleware, Interface as RequestExecutorInterface } from "../executor.js";
import type { WebSocketChannelExecutor } from "./websocket-channel.js";
import type { AIError, LLMRequest } from "../../schema/index.js";
export interface TransportRuntime {
readonly http: RequestExecutorInterface;
}
export interface TransportExecution<Frame> {
readonly frames: Stream.Stream<Frame, AIError>;
/** Optional successful-consumption acknowledgement. HTTP leaves this absent. */
readonly complete?: Effect.Effect<void>;
}
export interface TransportExecuteOptions {
readonly webSocket?: WebSocketChannelExecutor;
}
export interface Transport<Body, Prepared, Frame> {
readonly id: string;
readonly prepare: (input: TransportPrepareInput<Body>) => Effect.Effect<Prepared, AIError>;
readonly execute: (prepared: Prepared, request: LLMRequest, runtime: TransportRuntime, options?: TransportExecuteOptions) => Effect.Effect<TransportExecution<Frame>, AIError, Scope.Scope>;
}
export interface TransportPrepareInput<Body> {
readonly body: Body;
readonly request: LLMRequest;
readonly endpoint: Endpoint.Definition<Body>;
readonly auth: Auth.Definition;
readonly encodeBody: (body: Body) => string;
readonly headers?: (input: {
readonly request: LLMRequest;
}) => Record<string, string>;
readonly middleware?: HttpMiddleware;
readonly webSocket?: WebSocketChannelExecutor;
}
export * as HttpTransport from "./http.js";
export type { HttpHandler, HttpMiddleware } from "../executor.js";
export type { ChannelCheckpoint, ChannelCreate, ChannelObservation, WebSocketChannelDriver, WebSocketChannelExchange, WebSocketChannelExecution, WebSocketChannelExecutor, } from "./websocket-channel.js";
export type { WebSocketConnection, WebSocketConnector, WebSocketRequest } from "./websocket.js";
export { WebSocketTransport } from "./websocket.js";
import { Endpoint } from "../endpoint.js";
import { Auth } from "../auth.js";
export * as HttpTransport from "./http.js";
export { WebSocketTransport } from "./websocket.js";
import type { Effect, Scope, Stream } from "effect";
import type { Headers } from "effect/unstable/http";
import type { AIError } from "../../schema/index.js";
export interface WebSocketChannelExecutor {
readonly execute: (exchange: WebSocketChannelExchange) => Effect.Effect<WebSocketChannelExecution, AIError, Scope.Scope>;
}
export interface WebSocketChannelExecution {
readonly frames: Stream.Stream<string, AIError>;
/** Commits staged state after the decoded Route stream ends successfully. */
readonly complete: Effect.Effect<void>;
}
export interface WebSocketChannelExchange {
readonly id: string;
readonly connect: {
readonly url: string;
readonly headers: Headers.Headers;
/** Provider-safe connection age after which Core should rotate before sending. */
readonly rotateAfterMs?: number;
};
readonly fallback: () => Stream.Stream<string, AIError>;
readonly driver: WebSocketChannelDriver;
}
export interface WebSocketChannelDriver {
readonly create: (checkpoint: ChannelCheckpoint | undefined) => Effect.Effect<ChannelCreate, AIError>;
readonly observe: (create: ChannelCreate, frame: string) => Effect.Effect<ChannelObservation, AIError>;
}
export interface ChannelCreate {
readonly message: string;
readonly mode: "full" | "incremental";
}
export type ChannelObservation = {
readonly type: "frame";
readonly frame: string;
} | {
readonly type: "completed";
readonly frame: string;
readonly checkpoint?: ChannelCheckpoint;
} | {
readonly type: "incomplete";
readonly frame: string;
} | {
readonly type: "provider-failure";
readonly error: AIError;
} | {
readonly type: "rejected";
readonly error: AIError;
readonly recovery: "retry-full";
} | {
readonly type: "rejected";
readonly error: AIError;
readonly recovery: "rotate-and-retry-full";
};
export interface ChannelCheckpoint {
readonly protocol: string;
readonly value: unknown;
}
import { Effect, Stream } from "effect";
import { Headers } from "effect/unstable/http";
import { Socket } from "effect/unstable/socket";
import { AIError } from "../../schema/index.js";
import type { Transport } from "./index.js";
import type { WebSocketChannelExecutor } from "./websocket-channel.js";
export interface WebSocketRequest {
readonly url: string;
readonly headers: Headers.Headers;
}
export interface WebSocketConnection {
readonly sendText: (message: string) => Effect.Effect<void, AIError>;
readonly messages: Stream.Stream<string | Uint8Array, AIError>;
readonly close: Effect.Effect<void, never>;
}
export interface WebSocketConnector {
readonly open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
}
export declare const toWebSocketUrl: (value: string) => Effect.Effect<string, AIError, never>;
export declare const open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError, Socket.WebSocketConstructor>;
export declare const fromWebSocket: (ws: globalThis.WebSocket, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
export declare const messageText: (message: string | Uint8Array, decoder: TextDecoder) => string;
export declare const makeDirect: (connector: WebSocketConnector) => WebSocketChannelExecutor;
export declare const direct: Effect.Effect<WebSocketChannelExecutor, never, Socket.WebSocketConstructor>;
export interface JsonPrepared {
readonly url: string;
readonly headers: Headers.Headers;
readonly message: string;
}
export interface JsonInput<Body, Message> {
readonly toMessage: (body: Body | Record<string, unknown>) => Effect.Effect<Message, AIError>;
readonly encodeMessage: (message: Message) => string;
}
export type JsonPatch<Body, Message> = Partial<JsonInput<Body, Message>>;
export interface JsonTransport<Body, Message> extends Transport<Body, JsonPrepared, string> {
readonly with: (patch: JsonPatch<Body, Message>) => JsonTransport<Body, Message>;
}
export declare const json: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
export declare const jsonTransport: {
readonly id: "websocket-json";
readonly with: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
};
export declare const WebSocketTransport: {
readonly json: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
readonly jsonTransport: {
readonly id: "websocket-json";
readonly with: <Body, Message>(input: JsonInput<Body, Message>) => JsonTransport<Body, Message>;
};
readonly direct: Effect.Effect<WebSocketChannelExecutor, never, Socket.WebSocketConstructor>;
readonly makeDirect: (connector: WebSocketConnector) => WebSocketChannelExecutor;
readonly open: (input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError, Socket.WebSocketConstructor>;
readonly fromWebSocket: (ws: globalThis.WebSocket, input: WebSocketRequest) => Effect.Effect<WebSocketConnection, AIError>;
readonly messageText: (message: string | Uint8Array, decoder: TextDecoder) => string;
readonly toWebSocketUrl: (value: string) => Effect.Effect<string, AIError, never>;
};
import { Cause, Effect, Queue, Stream } from "effect";
import { Headers } from "effect/unstable/http";
import { Socket } from "effect/unstable/socket";
import { AIError, TransportReason } from "../../schema/index.js";
import * as HttpTransport from "./http.js";
const MAX_FRAME_BYTES = 16 * 1024 * 1024;
const transportError = (method, message, input) => new AIError({
module: "WebSocketConnector",
method,
reason: new TransportReason({
message,
transport: "websocket",
operation: input.operation,
url: input.url,
code: input.code,
phase: input.phase,
delivery: input.delivery,
}),
});
const annotateTransportError = (error, input) => error.reason._tag === "Transport"
? new AIError({
module: error.module,
method: error.method,
reason: new TransportReason({
message: error.reason.message,
transport: error.reason.transport,
operation: error.reason.operation,
code: error.reason.code,
url: error.reason.url,
http: error.reason.http,
phase: input.phase,
delivery: input.delivery,
recovery: error.reason.recovery,
}),
})
: error;
const eventMessage = (event) => {
if ("message" in event && typeof event.message === "string")
return event.message;
return event.type;
};
const binaryMessage = (data) => {
if (data instanceof Uint8Array)
return data;
if (data instanceof ArrayBuffer)
return new Uint8Array(data);
if (ArrayBuffer.isView(data))
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
return undefined;
};
const waitOpen = (ws, input) => {
if (ws.readyState === globalThis.WebSocket.OPEN)
return Effect.void;
if (ws.readyState === globalThis.WebSocket.CLOSING || ws.readyState === globalThis.WebSocket.CLOSED) {
return Effect.fail(transportError("open", `WebSocket closed before opening (state ${ws.readyState})`, {
url: input.url,
operation: "request",
code: "closed",
phase: "connect",
delivery: "not-sent",
}));
}
return Effect.callback((resume, signal) => {
const cleanup = () => {
ws.removeEventListener("open", onOpen);
ws.removeEventListener("error", onError);
ws.removeEventListener("close", onClose);
signal.removeEventListener("abort", onAbort);
};
const onAbort = () => {
cleanup();
if (ws.readyState !== globalThis.WebSocket.CLOSED && ws.readyState !== globalThis.WebSocket.CLOSING)
ws.close(1000);
};
const onOpen = () => {
cleanup();
resume(Effect.void);
};
const onError = (event) => {
cleanup();
resume(Effect.fail(transportError("open", `Failed to open WebSocket: ${eventMessage(event)}`, {
url: input.url,
operation: "request",
phase: "connect",
delivery: "not-sent",
})));
};
const onClose = (event) => {
cleanup();
resume(Effect.fail(transportError("open", `WebSocket closed before opening with code ${event.code}`, {
url: input.url,
operation: "request",
code: String(event.code),
phase: "connect",
delivery: "not-sent",
})));
};
ws.addEventListener("open", onOpen, { once: true });
ws.addEventListener("error", onError, { once: true });
ws.addEventListener("close", onClose, { once: true });
signal.addEventListener("abort", onAbort, { once: true });
});
};
export const toWebSocketUrl = (value) => Effect.try({
try: () => {
const url = new URL(value);
if (url.protocol === "https:") {
url.protocol = "wss:";
return url.toString();
}
if (url.protocol === "http:") {
url.protocol = "ws:";
return url.toString();
}
throw new Error(`Unsupported WebSocket URL protocol ${url.protocol}`);
},
catch: (error) => transportError("prepare", error instanceof Error ? error.message : "Invalid WebSocket URL", {
url: value,
operation: "request",
code: "invalid-url",
phase: "prepare",
delivery: "not-sent",
}),
});
export const open = (input) => Effect.gen(function* () {
const constructor = yield* Socket.WebSocketConstructor;
const ws = yield* Effect.try({
try: () =>
// Platform implementations may extend Effect's browser-compatible constructor with handshake options.
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
constructor(input.url, {
headers: input.headers,
}),
catch: (error) => transportError("open", error instanceof Error ? error.message : "Failed to construct WebSocket", {
url: input.url,
operation: "request",
phase: "connect",
delivery: "not-sent",
}),
});
return yield* fromWebSocket(ws, input);
});
export const fromWebSocket = (ws, input) => Effect.gen(function* () {
yield* waitOpen(ws, input);
const messages = yield* Queue.bounded(128);
const oversized = (message) => typeof message === "string" ? new Blob([message]).size > MAX_FRAME_BYTES : message.byteLength > MAX_FRAME_BYTES;
const rejectOversized = (message) => {
if (!oversized(message))
return false;
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "WebSocket message exceeds the 16 MiB limit", {
url: input.url,
operation: "read",
code: "message-too-large",
phase: "receive",
})));
if (ws.readyState === globalThis.WebSocket.OPEN)
ws.close(1009, "Message too large");
return true;
};
const offer = (message) => {
if (rejectOversized(message))
return;
if (Queue.offerUnsafe(messages, message))
return;
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "WebSocket inbound queue overflow", {
url: input.url,
operation: "read",
code: "queue-overflow",
phase: "receive",
})));
};
const onMessage = (event) => {
if (typeof event.data === "string")
return offer(event.data);
const binary = binaryMessage(event.data);
if (binary)
return offer(binary);
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", "Unsupported WebSocket message payload", {
url: input.url,
operation: "read",
code: "message",
phase: "receive",
})));
};
const onError = (event) => {
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket error: ${eventMessage(event)}`, {
url: input.url,
operation: "read",
code: "message",
phase: "receive",
})));
};
const onClose = (event) => {
Queue.failCauseUnsafe(messages, Cause.fail(transportError("message", `WebSocket closed with code ${event.code}`, {
url: input.url,
operation: "read",
code: String(event.code),
phase: "close",
})));
};
const cleanup = Effect.sync(() => {
ws.removeEventListener("message", onMessage);
ws.removeEventListener("error", onError);
ws.removeEventListener("close", onClose);
}).pipe(Effect.andThen(Queue.shutdown(messages)));
ws.addEventListener("message", onMessage);
ws.addEventListener("error", onError);
ws.addEventListener("close", onClose);
return {
sendText: (message) => Effect.suspend(() => {
if (ws.readyState !== globalThis.WebSocket.OPEN)
return Effect.fail(transportError("sendText", `WebSocket is not open (state ${ws.readyState})`, {
url: input.url,
operation: "write",
phase: "send",
delivery: "not-sent",
}));
return Effect.try({
try: () => ws.send(message),
catch: (error) => transportError("sendText", error instanceof Error ? error.message : "Failed to send WebSocket message", {
url: input.url,
operation: "write",
phase: "send",
delivery: "not-sent",
}),
});
}),
messages: Stream.fromQueue(messages),
close: cleanup.pipe(Effect.andThen(Effect.sync(() => {
if (ws.readyState === globalThis.WebSocket.CLOSED || ws.readyState === globalThis.WebSocket.CLOSING)
return;
ws.close(1000);
}))),
};
});
export const messageText = (message, decoder) => typeof message === "string" ? message : decoder.decode(message);
const observationFrame = (observation) => {
if (observation.type === "frame" || observation.type === "completed" || observation.type === "incomplete")
return Effect.succeed(observation.frame);
return Effect.fail(observation.error);
};
const observationTerminal = (observation) => observation.type !== "frame";
export const makeDirect = (connector) => ({
execute: (exchange) => Effect.gen(function* () {
const connection = yield* Effect.acquireRelease(connector
.open(exchange.connect)
.pipe(Effect.mapError((error) => annotateTransportError(error, { phase: "connect", delivery: "not-sent" }))), (connection) => connection.close);
const create = yield* exchange.driver.create(undefined);
yield* connection.sendText(create.message);
const decoder = new TextDecoder();
let observed = false;
return {
frames: connection.messages.pipe(Stream.map((message) => {
observed = true;
return messageText(message, decoder);
}), Stream.mapError((error) => annotateTransportError(error, {
phase: error.reason._tag === "Transport" && error.reason.phase === "close" ? "close" : "receive",
delivery: observed ? "accepted" : "ambiguous",
})), Stream.mapEffect((frame) => exchange.driver.observe(create, frame)), Stream.takeUntil(observationTerminal), Stream.mapEffect(observationFrame)),
complete: Effect.void,
};
}),
});
export const direct = Effect.gen(function* () {
const constructor = yield* Socket.WebSocketConstructor;
return makeDirect({
open: (input) => open(input).pipe(Effect.provideService(Socket.WebSocketConstructor, constructor)),
});
});
export const json = (input) => ({
id: "websocket-json",
with: (patch) => json({ ...input, ...patch }),
prepare: (prepareInput) => Effect.gen(function* () {
const parts = yield* HttpTransport.jsonRequestParts({
...prepareInput,
});
return {
url: yield* toWebSocketUrl(parts.url),
headers: parts.headers,
message: input.encodeMessage(yield* input.toMessage(parts.jsonBody)),
};
}),
execute: (prepared, request, _runtime, options) => {
const webSocket = options?.webSocket;
if (!webSocket) {
return Effect.fail(transportError("json", "WebSocket JSON transport requires StreamOptions.webSocket", {
url: prepared.url,
operation: "request",
code: "unavailable",
phase: "prepare",
delivery: "not-sent",
}));
}
const driver = {
create: () => Effect.succeed({ message: prepared.message, mode: "full" }),
observe: (_create, frame) => Effect.succeed({ type: "frame", frame }),
};
const exchange = {
id: request.id ?? "request",
connect: { url: prepared.url, headers: prepared.headers },
fallback: () => Stream.fail(transportError("fallback", "WebSocket JSON transport does not provide HTTP fallback", {
url: prepared.url,
operation: "request",
code: "websocket",
phase: "fallback",
delivery: "not-sent",
})),
driver,
};
return webSocket.execute(exchange);
},
});
export const jsonTransport = {
id: "websocket-json",
with: json,
};
export const WebSocketTransport = {
json,
jsonTransport,
direct,
makeDirect,
open,
fromWebSocket,
messageText,
toWebSocketUrl,
};
import { Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
export declare const ProviderFailureClassification: Schema.Literals<readonly ["context-overflow", "payload-too-large"]>;
export type ProviderFailureClassification = typeof ProviderFailureClassification.Type;
declare const HttpRequestDetails_base: Schema.Class<HttpRequestDetails, Schema.Struct<{
readonly method: Schema.String;
readonly url: Schema.String;
readonly headers: Schema.$Record<Schema.String, Schema.String>;
}>, {}>;
export declare class HttpRequestDetails extends HttpRequestDetails_base {
}
declare const HttpResponseDetails_base: Schema.Class<HttpResponseDetails, Schema.Struct<{
readonly status: Schema.Number;
readonly headers: Schema.$Record<Schema.String, Schema.String>;
}>, {}>;
export declare class HttpResponseDetails extends HttpResponseDetails_base {
}
declare const HttpRateLimitDetails_base: Schema.Class<HttpRateLimitDetails, Schema.Struct<{
readonly retryAfterMs: Schema.optional<Schema.Number>;
readonly limit: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
readonly remaining: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
readonly reset: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
}>, {}>;
export declare class HttpRateLimitDetails extends HttpRateLimitDetails_base {
}
declare const HttpContext_base: Schema.Class<HttpContext, Schema.Struct<{
readonly request: typeof HttpRequestDetails;
readonly response: Schema.optional<typeof HttpResponseDetails>;
readonly body: Schema.optional<Schema.String>;
readonly bodyTruncated: Schema.optional<Schema.Boolean>;
readonly requestId: Schema.optional<Schema.String>;
readonly rateLimit: Schema.optional<typeof HttpRateLimitDetails>;
}>, {}>;
export declare class HttpContext extends HttpContext_base {
}
declare const InvalidRequestReason_base: Schema.Class<InvalidRequestReason, Schema.Struct<{
readonly _tag: Schema.tag<"InvalidRequest">;
readonly message: Schema.String;
readonly parameter: Schema.optional<Schema.String>;
readonly classification: Schema.optional<Schema.Literals<readonly ["context-overflow", "payload-too-large"]>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class InvalidRequestReason extends InvalidRequestReason_base {
}
declare const NoRouteReason_base: Schema.Class<NoRouteReason, Schema.Struct<{
readonly _tag: Schema.tag<"NoRoute">;
readonly route: Schema.String;
readonly provider: Schema.brand<Schema.String, "AI.ProviderID">;
readonly model: Schema.brand<Schema.String, "AI.ModelID">;
}>, {}>;
export declare class NoRouteReason extends NoRouteReason_base {
get message(): string;
}
declare const AuthenticationReason_base: Schema.Class<AuthenticationReason, Schema.Struct<{
readonly _tag: Schema.tag<"Authentication">;
readonly message: Schema.String;
readonly kind: Schema.Literals<readonly ["missing", "invalid", "expired", "insufficient-permissions", "unknown"]>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class AuthenticationReason extends AuthenticationReason_base {
}
declare const RateLimitReason_base: Schema.Class<RateLimitReason, Schema.Struct<{
readonly _tag: Schema.tag<"RateLimit">;
readonly message: Schema.String;
readonly retryAfterMs: Schema.optional<Schema.Number>;
readonly rateLimit: Schema.optional<typeof HttpRateLimitDetails>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class RateLimitReason extends RateLimitReason_base {
}
declare const QuotaExceededReason_base: Schema.Class<QuotaExceededReason, Schema.Struct<{
readonly _tag: Schema.tag<"QuotaExceeded">;
readonly message: Schema.String;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class QuotaExceededReason extends QuotaExceededReason_base {
}
declare const ContentPolicyReason_base: Schema.Class<ContentPolicyReason, Schema.Struct<{
readonly _tag: Schema.tag<"ContentPolicy">;
readonly message: Schema.String;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class ContentPolicyReason extends ContentPolicyReason_base {
}
declare const ProviderInternalReason_base: Schema.Class<ProviderInternalReason, Schema.Struct<{
readonly _tag: Schema.tag<"ProviderInternal">;
readonly message: Schema.String;
readonly status: Schema.optional<Schema.Number>;
readonly retryAfterMs: Schema.optional<Schema.Number>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class ProviderInternalReason extends ProviderInternalReason_base {
}
export declare const TransportType: Schema.Literals<readonly ["http", "websocket"]>;
export type TransportType = typeof TransportType.Type;
export declare const TransportOperation: Schema.Literals<readonly ["request", "read", "write"]>;
export type TransportOperation = typeof TransportOperation.Type;
declare const TransportReason_base: Schema.Class<TransportReason, Schema.Struct<{
readonly _tag: Schema.tag<"Transport">;
readonly message: Schema.String;
readonly transport: Schema.Literals<readonly ["http", "websocket"]>;
readonly operation: Schema.Literals<readonly ["request", "read", "write"]>;
readonly code: Schema.optional<Schema.String>;
readonly url: Schema.optional<Schema.String>;
readonly http: Schema.optional<typeof HttpContext>;
readonly phase: Schema.optional<Schema.Literals<readonly ["prepare", "queue", "connect", "send", "receive", "decode", "complete", "fallback", "close"]>>;
readonly delivery: Schema.optional<Schema.Literals<readonly ["not-sent", "rejected", "ambiguous", "accepted"]>>;
readonly recovery: Schema.optional<Schema.Literals<readonly ["retry-connect", "retry-full", "rotate-and-retry-full", "fallback-http", "fail"]>>;
}>, {}>;
export declare class TransportReason extends TransportReason_base {
}
declare const InvalidProviderOutputReason_base: Schema.Class<InvalidProviderOutputReason, Schema.Struct<{
readonly _tag: Schema.tag<"InvalidProviderOutput">;
readonly message: Schema.String;
readonly classification: Schema.optional<Schema.Literals<readonly ["incomplete-stream"]>>;
readonly route: Schema.optional<Schema.String>;
readonly raw: Schema.optional<Schema.String>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>, {}>;
export declare class InvalidProviderOutputReason extends InvalidProviderOutputReason_base {
}
declare const UnknownProviderReason_base: Schema.Class<UnknownProviderReason, Schema.Struct<{
readonly _tag: Schema.tag<"UnknownProvider">;
readonly message: Schema.String;
readonly status: Schema.optional<Schema.Number>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpContext>;
}>, {}>;
export declare class UnknownProviderReason extends UnknownProviderReason_base {
}
export declare const AIErrorReason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestReason, typeof NoRouteReason, typeof AuthenticationReason, typeof RateLimitReason, typeof QuotaExceededReason, typeof ContentPolicyReason, typeof ProviderInternalReason, typeof TransportReason, typeof InvalidProviderOutputReason, typeof UnknownProviderReason]>;
export type AIErrorReason = Schema.Schema.Type<typeof AIErrorReason>;
declare const AIError_base: Schema.Class<AIError, Schema.TaggedStruct<"AI.Error", {
readonly module: Schema.String;
readonly method: Schema.String;
readonly reason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestReason, typeof NoRouteReason, typeof AuthenticationReason, typeof RateLimitReason, typeof QuotaExceededReason, typeof ContentPolicyReason, typeof ProviderInternalReason, typeof TransportReason, typeof InvalidProviderOutputReason, typeof UnknownProviderReason]>;
}>, import("effect/Cause").YieldableError>;
export declare class AIError extends AIError_base {
readonly cause: InvalidRequestReason | NoRouteReason | AuthenticationReason | RateLimitReason | QuotaExceededReason | ContentPolicyReason | ProviderInternalReason | TransportReason | InvalidProviderOutputReason | UnknownProviderReason;
get message(): string;
}
/**
* Failure type for tool execute handlers. Handlers must map their internal
* errors to this shape; the runtime catches `ToolFailure`s and surfaces them
* as `tool-error` events plus a `tool-result` of `type: "error"` so the model
* can self-correct.
*
* Anything thrown or yielded by a handler that is not a `ToolFailure` is
* treated as a defect and fails the stream.
*/
export declare class ToolFailure extends Tool.Error {
}
export {};
import { Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { ModelID, ProviderID, ProviderMetadata, RouteID } from "./ids.js";
export const ProviderFailureClassification = Schema.Literals(["context-overflow", "payload-too-large"]);
export class HttpRequestDetails extends Schema.Class("AI.HttpRequestDetails")({
method: Schema.String,
url: Schema.String,
headers: Schema.Record(Schema.String, Schema.String),
}) {
}
export class HttpResponseDetails extends Schema.Class("AI.HttpResponseDetails")({
status: Schema.Number,
headers: Schema.Record(Schema.String, Schema.String),
}) {
}
export class HttpRateLimitDetails extends Schema.Class("AI.HttpRateLimitDetails")({
retryAfterMs: Schema.optional(Schema.Number),
limit: Schema.optional(Schema.Record(Schema.String, Schema.String)),
remaining: Schema.optional(Schema.Record(Schema.String, Schema.String)),
reset: Schema.optional(Schema.Record(Schema.String, Schema.String)),
}) {
}
export class HttpContext extends Schema.Class("AI.HttpContext")({
request: HttpRequestDetails,
response: Schema.optional(HttpResponseDetails),
body: Schema.optional(Schema.String),
bodyTruncated: Schema.optional(Schema.Boolean),
requestId: Schema.optional(Schema.String),
rateLimit: Schema.optional(HttpRateLimitDetails),
}) {
}
export class InvalidRequestReason extends Schema.Class("AI.Error.InvalidRequest")({
_tag: Schema.tag("InvalidRequest"),
message: Schema.String,
parameter: Schema.optional(Schema.String),
classification: Schema.optional(ProviderFailureClassification),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class NoRouteReason extends Schema.Class("AI.Error.NoRoute")({
_tag: Schema.tag("NoRoute"),
route: RouteID,
provider: ProviderID,
model: ModelID,
}) {
get message() {
return `No AI route for ${this.provider}/${this.model} using ${this.route}`;
}
}
export class AuthenticationReason extends Schema.Class("AI.Error.Authentication")({
_tag: Schema.tag("Authentication"),
message: Schema.String,
kind: Schema.Literals(["missing", "invalid", "expired", "insufficient-permissions", "unknown"]),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class RateLimitReason extends Schema.Class("AI.Error.RateLimit")({
_tag: Schema.tag("RateLimit"),
message: Schema.String,
retryAfterMs: Schema.optional(Schema.Number),
rateLimit: Schema.optional(HttpRateLimitDetails),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class QuotaExceededReason extends Schema.Class("AI.Error.QuotaExceeded")({
_tag: Schema.tag("QuotaExceeded"),
message: Schema.String,
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class ContentPolicyReason extends Schema.Class("AI.Error.ContentPolicy")({
_tag: Schema.tag("ContentPolicy"),
message: Schema.String,
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export class ProviderInternalReason extends Schema.Class("AI.Error.ProviderInternal")({
_tag: Schema.tag("ProviderInternal"),
message: Schema.String,
status: Schema.optional(Schema.Number),
retryAfterMs: Schema.optional(Schema.Number),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export const TransportType = Schema.Literals(["http", "websocket"]);
export const TransportOperation = Schema.Literals(["request", "read", "write"]);
export class TransportReason extends Schema.Class("AI.Error.Transport")({
_tag: Schema.tag("Transport"),
message: Schema.String,
transport: TransportType,
operation: TransportOperation,
code: Schema.optional(Schema.String),
url: Schema.optional(Schema.String),
http: Schema.optional(HttpContext),
phase: Schema.optional(Schema.Literals(["prepare", "queue", "connect", "send", "receive", "decode", "complete", "fallback", "close"])),
delivery: Schema.optional(Schema.Literals(["not-sent", "rejected", "ambiguous", "accepted"])),
recovery: Schema.optional(Schema.Literals(["retry-connect", "retry-full", "rotate-and-retry-full", "fallback-http", "fail"])),
}) {
}
export class InvalidProviderOutputReason extends Schema.Class("AI.Error.InvalidProviderOutput")({
_tag: Schema.tag("InvalidProviderOutput"),
message: Schema.String,
classification: Schema.optional(Schema.Literals(["incomplete-stream"])),
route: Schema.optional(Schema.String),
raw: Schema.optional(Schema.String),
providerMetadata: Schema.optional(ProviderMetadata),
}) {
}
export class UnknownProviderReason extends Schema.Class("AI.Error.UnknownProvider")({
_tag: Schema.tag("UnknownProvider"),
message: Schema.String,
status: Schema.optional(Schema.Number),
providerMetadata: Schema.optional(ProviderMetadata),
http: Schema.optional(HttpContext),
}) {
}
export const AIErrorReason = Schema.Union([
InvalidRequestReason,
NoRouteReason,
AuthenticationReason,
RateLimitReason,
QuotaExceededReason,
ContentPolicyReason,
ProviderInternalReason,
TransportReason,
InvalidProviderOutputReason,
UnknownProviderReason,
]).pipe(Schema.toTaggedUnion("_tag"));
export class AIError extends Schema.TaggedErrorClass()("AI.Error", {
module: Schema.String,
method: Schema.String,
reason: AIErrorReason,
}) {
cause = this.reason;
get message() {
return `${this.module}.${this.method}: ${this.reason.message}`;
}
}
/**
* Failure type for tool execute handlers. Handlers must map their internal
* errors to this shape; the runtime catches `ToolFailure`s and surfaces them
* as `tool-error` events plus a `tool-result` of `type: "error"` so the model
* can self-correct.
*
* Anything thrown or yielded by a handler that is not a `ToolFailure` is
* treated as a defect and fails the stream.
*/
export class ToolFailure extends Tool.Error {
}

Sorry, the diff of this file is too big to display

import { Schema } from "effect";
import { ContentBlockID, FinishReason, ProviderMetadata, ToolCallID } from "./ids.js";
import { Message, ToolCallPart, ToolOutput, ToolResultPart, ToolResultValue } from "./messages.js";
import { ProviderFailureClassification } from "./errors.js";
/**
* Token usage reported by an LLM provider.
*
* **Inclusive totals** (match AI SDK / OpenAI / LangChain convention — a
* reader from any of those ecosystems sees the number they expect):
*
* - `inputTokens` — total prompt tokens, *including* cached reads/writes.
* - `outputTokens` — total output tokens, *including* reasoning.
* - `totalTokens` — provider-supplied total, or `inputTokens + outputTokens`.
*
* **Non-overlapping breakdown** (every field is independently meaningful;
* consumers never have to subtract):
*
* - `nonCachedInputTokens` — the "fresh" portion of the prompt.
* - `cacheReadInputTokens` — input tokens served from cache.
* - `cacheWriteInputTokens` — input tokens written to cache.
* - `reasoningTokens` — subset of `outputTokens` spent on hidden reasoning.
*
* **Invariant**: `nonCachedInputTokens + cacheReadInputTokens +
* cacheWriteInputTokens = inputTokens`, and `reasoningTokens ≤ outputTokens`.
* Each protocol mapper computes whichever side it doesn't get natively,
* with `Math.max(0, …)` clamping for defense against provider bugs. Because
* every breakdown field is stored independently, downstream consumers can
* read whatever they need (cost-by-category, context-pressure, AI-SDK-style
* inclusive total) without ever subtracting — eliminating the underflow
* class of bug where a clamped difference would silently store the wrong
* value.
*
* **Semantics by provider**:
*
* - OpenAI Chat / Responses / Gemini: provider reports inclusive
* `inputTokens` and an inclusive `outputTokens`; mapper subtracts to
* derive the breakdown.
* - Anthropic and Bedrock report the input breakdown natively: Anthropic's
* `input_tokens` and Bedrock's `inputTokens` are non-cached only. Their
* mappers sum the breakdown to derive the inclusive `inputTokens`.
* Anthropic's `outputTokens` includes extended thinking. Newer responses
* expose that subset as `output_tokens_details.thinking_tokens`, which maps
* to `reasoningTokens`; older responses leave it undefined.
*
* `providerMetadata` always carries the provider's raw usage payload —
* keyed by provider name (`{ openai: ... }`, `{ anthropic: ... }`, etc.)
* — for fields we don't normalize and for billing-level audit trails.
* Matches the same escape-hatch field on `LLMEvent`.
*/
export class Usage extends Schema.Class("AI.Usage")({
inputTokens: Schema.optional(Schema.Number),
outputTokens: Schema.optional(Schema.Number),
nonCachedInputTokens: Schema.optional(Schema.Number),
cacheReadInputTokens: Schema.optional(Schema.Number),
cacheWriteInputTokens: Schema.optional(Schema.Number),
reasoningTokens: Schema.optional(Schema.Number),
totalTokens: Schema.optional(Schema.Number),
providerMetadata: Schema.optional(ProviderMetadata),
}) {
/**
* Visible output tokens — `outputTokens` minus `reasoningTokens`, clamped
* to zero. The one place subtraction happens in this contract; the clamp
* means a provider reporting `reasoningTokens > outputTokens` produces a
* harmless zero rather than a negative that crashes downstream schemas.
*/
get visibleOutputTokens() {
return Math.max(0, (this.outputTokens ?? 0) - (this.reasoningTokens ?? 0));
}
static from(input) {
return input instanceof Usage ? input : new Usage(input);
}
}
export const StepStart = Schema.Struct({
type: Schema.tag("step-start"),
index: Schema.Number,
}).annotate({ identifier: "LLM.Event.StepStart" });
export const TextStart = Schema.Struct({
type: Schema.tag("text-start"),
id: ContentBlockID,
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.TextStart" });
export const TextDelta = Schema.Struct({
type: Schema.tag("text-delta"),
id: ContentBlockID,
text: Schema.String,
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.TextDelta" });
export const TextEnd = Schema.Struct({
type: Schema.tag("text-end"),
id: ContentBlockID,
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.TextEnd" });
export const ReasoningStart = Schema.Struct({
type: Schema.tag("reasoning-start"),
id: ContentBlockID,
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ReasoningStart" });
export const ReasoningDelta = Schema.Struct({
type: Schema.tag("reasoning-delta"),
id: ContentBlockID,
text: Schema.String,
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ReasoningDelta" });
export const ReasoningEnd = Schema.Struct({
type: Schema.tag("reasoning-end"),
id: ContentBlockID,
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ReasoningEnd" });
export const ToolInputStart = Schema.Struct({
type: Schema.tag("tool-input-start"),
id: ToolCallID,
name: Schema.String,
providerExecuted: Schema.optional(Schema.Boolean),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ToolInputStart" });
export const ToolInputDelta = Schema.Struct({
type: Schema.tag("tool-input-delta"),
id: ToolCallID,
name: Schema.String,
text: Schema.String,
}).annotate({ identifier: "LLM.Event.ToolInputDelta" });
export const ToolInputEnd = Schema.Struct({
type: Schema.tag("tool-input-end"),
id: ToolCallID,
name: Schema.String,
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ToolInputEnd" });
/** A local tool call whose final input could not be decoded. */
export const ToolInputError = Schema.Struct({
type: Schema.tag("tool-input-error"),
id: ToolCallID,
name: Schema.String,
raw: Schema.String,
}).annotate({ identifier: "LLM.Event.ToolInputError" });
export const ToolCall = Schema.Struct({
type: Schema.tag("tool-call"),
id: ToolCallID,
name: Schema.String,
input: Schema.Unknown,
providerExecuted: Schema.optional(Schema.Boolean),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ToolCall" });
export const ToolResult = Schema.Struct({
type: Schema.tag("tool-result"),
id: ToolCallID,
name: Schema.String,
result: ToolResultValue,
output: Schema.optional(ToolOutput),
providerExecuted: Schema.optional(Schema.Boolean),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ToolResult" });
export const ToolError = Schema.Struct({
type: Schema.tag("tool-error"),
id: ToolCallID,
name: Schema.String,
message: Schema.String,
error: Schema.optional(Schema.Defect()),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ToolError" });
export const FinishReasonDetails = Schema.Struct({
normalized: FinishReason,
raw: Schema.optional(Schema.String),
}).annotate({ identifier: "LLM.FinishReasonDetails" });
export const StepFinish = Schema.Struct({
type: Schema.tag("step-finish"),
index: Schema.Number,
reason: FinishReasonDetails,
usage: Schema.optional(Usage),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.StepFinish" });
export const Finish = Schema.Struct({
type: Schema.tag("finish"),
reason: FinishReasonDetails,
usage: Schema.optional(Usage),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.Finish" });
export const ProviderErrorEvent = Schema.Struct({
type: Schema.tag("provider-error"),
message: Schema.String,
classification: Schema.optional(ProviderFailureClassification),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Event.ProviderError" });
const llmEventTagged = Schema.Union([
StepStart,
TextStart,
TextDelta,
TextEnd,
ReasoningStart,
ReasoningDelta,
ReasoningEnd,
ToolInputStart,
ToolInputDelta,
ToolInputEnd,
ToolInputError,
ToolCall,
ToolResult,
ToolError,
StepFinish,
Finish,
ProviderErrorEvent,
]).pipe(Schema.toTaggedUnion("type"));
const contentBlockID = (value) => ContentBlockID.make(value);
const toolCallID = (value) => ToolCallID.make(value);
/**
* camelCase aliases for `LLMEvent.guards` (provided by `Schema.toTaggedUnion`).
* Lets consumers write `events.filter(LLMEvent.is.toolCall)` instead of
* `events.filter(LLMEvent.guards["tool-call"])`.
*/
export const LLMEvent = Object.assign(llmEventTagged, {
stepStart: StepStart.make,
textStart: (input) => TextStart.make({ ...input, id: contentBlockID(input.id) }),
textDelta: (input) => TextDelta.make({ ...input, id: contentBlockID(input.id) }),
textEnd: (input) => TextEnd.make({ ...input, id: contentBlockID(input.id) }),
reasoningStart: (input) => ReasoningStart.make({ ...input, id: contentBlockID(input.id) }),
reasoningDelta: (input) => ReasoningDelta.make({ ...input, id: contentBlockID(input.id) }),
reasoningEnd: (input) => ReasoningEnd.make({ ...input, id: contentBlockID(input.id) }),
toolInputStart: (input) => ToolInputStart.make({ ...input, id: toolCallID(input.id) }),
toolInputDelta: (input) => ToolInputDelta.make({ ...input, id: toolCallID(input.id) }),
toolInputEnd: (input) => ToolInputEnd.make({ ...input, id: toolCallID(input.id) }),
toolInputError: (input) => ToolInputError.make({ ...input, id: toolCallID(input.id) }),
toolCall: (input) => ToolCall.make({ ...input, id: toolCallID(input.id) }),
toolResult: (input) => ToolResult.make({
...input,
id: toolCallID(input.id),
output: input.output === undefined ? undefined : ToolOutput.make(input.output.structured, input.output.content),
}),
toolError: (input) => ToolError.make({ ...input, id: toolCallID(input.id) }),
stepFinish: (input) => StepFinish.make({
...input,
usage: input.usage === undefined ? undefined : Usage.from(input.usage),
}),
finish: (input) => Finish.make({
...input,
usage: input.usage === undefined ? undefined : Usage.from(input.usage),
}),
providerError: ProviderErrorEvent.make,
is: {
stepStart: llmEventTagged.guards["step-start"],
textStart: llmEventTagged.guards["text-start"],
textDelta: llmEventTagged.guards["text-delta"],
textEnd: llmEventTagged.guards["text-end"],
reasoningStart: llmEventTagged.guards["reasoning-start"],
reasoningDelta: llmEventTagged.guards["reasoning-delta"],
reasoningEnd: llmEventTagged.guards["reasoning-end"],
toolInputStart: llmEventTagged.guards["tool-input-start"],
toolInputDelta: llmEventTagged.guards["tool-input-delta"],
toolInputEnd: llmEventTagged.guards["tool-input-end"],
toolInputError: llmEventTagged.guards["tool-input-error"],
toolCall: llmEventTagged.guards["tool-call"],
toolResult: llmEventTagged.guards["tool-result"],
toolError: llmEventTagged.guards["tool-error"],
stepFinish: llmEventTagged.guards["step-finish"],
finish: llmEventTagged.guards.finish,
providerError: llmEventTagged.guards["provider-error"],
},
});
const responseText = (events) => events
.filter(LLMEvent.is.textDelta)
.map((event) => event.text)
.join("");
const responseReasoning = (events) => events
.filter(LLMEvent.is.reasoningDelta)
.map((event) => event.text)
.join("");
const responseUsage = (events) => events.reduce((usage, event) => ("usage" in event && event.usage !== undefined ? event.usage : usage), undefined);
const emptyResponseState = () => ({
events: [],
message: Message.assistant([]),
textParts: {},
reasoningParts: {},
toolInputs: {},
});
const appendEvent = (state, event) => {
const events = [...state.events, event];
if (LLMEvent.is.finish(event)) {
return {
...state,
events,
usage: event.usage ?? state.usage,
finishReason: event.reason,
};
}
if (LLMEvent.is.providerError(event)) {
return {
...state,
events,
finishReason: state.finishReason ?? { normalized: "error" },
};
}
return {
...state,
events,
usage: "usage" in event && event.usage !== undefined ? event.usage : state.usage,
};
};
const textContent = (text, providerMetadata) => providerMetadata === undefined ? { type: "text", text } : { type: "text", text, providerMetadata };
const reasoningContent = (text, providerMetadata) => providerMetadata === undefined ? { type: "reasoning", text } : { type: "reasoning", text, providerMetadata };
const contentWith = (state, content) => ({
...state,
message: Message.assistant(content),
});
const appendContent = (state, part) => contentWith(state, [...state.message.content, part]);
const replaceContent = (state, index, part) => contentWith(state, state.message.content.map((item, itemIndex) => (itemIndex === index ? part : item)));
const ensureText = (state, id, providerMetadata) => {
if (state.textParts[id])
return state;
return {
...appendContent(state, textContent("", providerMetadata)),
textParts: {
...state.textParts,
[id]: { contentIndex: state.message.content.length, text: "", providerMetadata },
},
};
};
const reduceTextDelta = (state, event) => {
const started = ensureText(state, event.id, event.providerMetadata);
const current = started.textParts[event.id];
if (!current)
return started;
const text = current.text + event.text;
const providerMetadata = event.providerMetadata ?? current.providerMetadata;
return {
...replaceContent(started, current.contentIndex, textContent(text, providerMetadata)),
textParts: { ...started.textParts, [event.id]: { ...current, text, providerMetadata } },
};
};
const reduceTextEnd = (state, event) => {
const current = state.textParts[event.id];
if (!current)
return state;
const providerMetadata = event.providerMetadata ?? current.providerMetadata;
return {
...replaceContent(state, current.contentIndex, textContent(current.text, providerMetadata)),
textParts: { ...state.textParts, [event.id]: { ...current, providerMetadata } },
};
};
const ensureReasoning = (state, id, providerMetadata) => {
if (state.reasoningParts[id])
return state;
return {
...appendContent(state, reasoningContent("", providerMetadata)),
reasoningParts: {
...state.reasoningParts,
[id]: { contentIndex: state.message.content.length, text: "", providerMetadata },
},
};
};
const reduceReasoningDelta = (state, event) => {
const started = ensureReasoning(state, event.id, event.providerMetadata);
const current = started.reasoningParts[event.id];
if (!current)
return started;
const text = current.text + event.text;
const providerMetadata = event.providerMetadata ?? current.providerMetadata;
return {
...replaceContent(started, current.contentIndex, reasoningContent(text, providerMetadata)),
reasoningParts: { ...started.reasoningParts, [event.id]: { ...current, text, providerMetadata } },
};
};
const reduceReasoningEnd = (state, event) => {
const current = state.reasoningParts[event.id];
if (!current)
return state;
const providerMetadata = event.providerMetadata ?? current.providerMetadata;
return {
...replaceContent(state, current.contentIndex, reasoningContent(current.text, providerMetadata)),
reasoningParts: { ...state.reasoningParts, [event.id]: { ...current, providerMetadata } },
};
};
const reduceToolInputStart = (state, event) => ({
...state,
toolInputs: {
...state.toolInputs,
[event.id]: { name: event.name, text: "", providerMetadata: event.providerMetadata },
},
});
const reduceToolInputDelta = (state, event) => {
const current = state.toolInputs[event.id] ?? { name: event.name, text: "" };
return {
...state,
toolInputs: { ...state.toolInputs, [event.id]: { ...current, text: current.text + event.text } },
};
};
const reduceToolInputEnd = (state, event) => {
const current = state.toolInputs[event.id] ?? { name: event.name, text: "" };
return {
...state,
toolInputs: {
...state.toolInputs,
[event.id]: {
...current,
name: event.name,
providerMetadata: event.providerMetadata ?? current.providerMetadata,
},
},
};
};
const toolCallContent = (event) => ToolCallPart.make({
id: event.id,
name: event.name,
input: event.input,
...(event.providerExecuted === undefined ? {} : { providerExecuted: event.providerExecuted }),
...(event.providerMetadata === undefined ? {} : { providerMetadata: event.providerMetadata }),
});
const toolResultContent = (event) => ToolResultPart.make({
id: event.id,
name: event.name,
result: event.result,
...(event.providerExecuted === undefined ? {} : { providerExecuted: event.providerExecuted }),
...(event.providerMetadata === undefined ? {} : { providerMetadata: event.providerMetadata }),
});
const reduceToolCall = (state, event) => {
const { [event.id]: _finished, ...toolInputs } = state.toolInputs;
return { ...appendContent(state, toolCallContent(event)), toolInputs };
};
const reduceResponseState = (state, event) => {
const next = appendEvent(state, event);
switch (event.type) {
case "text-start":
return ensureText(next, event.id, event.providerMetadata);
case "text-delta":
return reduceTextDelta(next, event);
case "text-end":
return reduceTextEnd(next, event);
case "reasoning-start":
return ensureReasoning(next, event.id, event.providerMetadata);
case "reasoning-delta":
return reduceReasoningDelta(next, event);
case "reasoning-end":
return reduceReasoningEnd(next, event);
case "tool-input-start":
return reduceToolInputStart(next, event);
case "tool-input-delta":
return reduceToolInputDelta(next, event);
case "tool-input-end":
return reduceToolInputEnd(next, event);
case "tool-input-error": {
const { [event.id]: _finished, ...toolInputs } = next.toolInputs;
return { ...next, toolInputs };
}
case "tool-call":
return reduceToolCall(next, event);
case "tool-result":
return appendContent(next, toolResultContent(event));
default:
return next;
}
};
export class LLMResponse extends Schema.Class("LLM.Response")({
message: Message,
events: Schema.Array(LLMEvent),
usage: Schema.optional(Usage),
finishReason: FinishReasonDetails,
}) {
/** Concatenated assistant text assembled from streamed `text-delta` events. */
get text() {
return responseText(this.events);
}
/** Concatenated reasoning text assembled from streamed `reasoning-delta` events. */
get reasoning() {
return responseReasoning(this.events);
}
/** Completed tool calls emitted by the provider. */
get toolCalls() {
return this.events.filter(LLMEvent.is.toolCall);
}
}
(function (LLMResponse) {
/** Initial reducer state for assembling one provider attempt. */
LLMResponse.empty = emptyResponseState;
/** Purely fold one provider-neutral event into the attempt assembly state. */
LLMResponse.reduce = reduceResponseState;
/** Return a completed response only after a terminal finish or provider error. */
LLMResponse.complete = (state) => state.finishReason === undefined
? undefined
: new LLMResponse({
message: state.message,
events: [...state.events],
usage: state.usage,
finishReason: state.finishReason,
});
/** Convenience reducer for callers that already have a collected event list. */
LLMResponse.fromEvents = (events) => LLMResponse.complete(events.reduce(LLMResponse.reduce, LLMResponse.empty()));
/** Concatenate assistant text from a response or collected event list. */
LLMResponse.text = (response) => responseText(response.events);
/** Return response usage, falling back to the latest usage-bearing event. */
LLMResponse.usage = (response) => response.usage ?? responseUsage(response.events);
/** Return completed tool calls from a response or collected event list. */
LLMResponse.toolCalls = (response) => response.events.filter(LLMEvent.is.toolCall);
/** Concatenate reasoning text from a response or collected event list. */
LLMResponse.reasoning = (response) => responseReasoning(response.events);
})(LLMResponse || (LLMResponse = {}));
import { Schema } from "effect";
import { ProviderMetadata } from "@opencode-ai/schema/ai";
export { ProviderMetadata };
/** Stable string identifier for a protocol implementation. */
export declare const ProtocolID: Schema.String;
export type ProtocolID = Schema.Schema.Type<typeof ProtocolID>;
/** Stable string identifier for the runnable route. */
export declare const RouteID: Schema.String;
export type RouteID = Schema.Schema.Type<typeof RouteID>;
export declare const ModelID: Schema.brand<Schema.String, "AI.ModelID">;
export type ModelID = typeof ModelID.Type;
export declare const ProviderID: Schema.brand<Schema.String, "AI.ProviderID">;
export type ProviderID = typeof ProviderID.Type;
export declare const ResponseID: Schema.String;
export type ResponseID = Schema.Schema.Type<typeof ResponseID>;
export declare const ContentBlockID: Schema.String;
export type ContentBlockID = Schema.Schema.Type<typeof ContentBlockID>;
export declare const ToolCallID: Schema.String;
export type ToolCallID = Schema.Schema.Type<typeof ToolCallID>;
export declare const ReasoningEfforts: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
export declare const ReasoningEffort: Schema.String;
export type ReasoningEffort = Schema.Schema.Type<typeof ReasoningEffort>;
export declare const TextVerbosity: Schema.Literals<readonly ["low", "medium", "high"]>;
export type TextVerbosity = Schema.Schema.Type<typeof TextVerbosity>;
export declare const MessageRole: Schema.Literals<readonly ["system", "user", "assistant", "tool"]>;
export type MessageRole = Schema.Schema.Type<typeof MessageRole>;
export declare const FinishReason: Schema.Literals<readonly ["stop", "length", "tool-calls", "content-filter", "error", "unknown"]>;
export type FinishReason = Schema.Schema.Type<typeof FinishReason>;
export declare const JsonSchema: Schema.$Record<Schema.String, Schema.Unknown>;
export type JsonSchema = Schema.Schema.Type<typeof JsonSchema>;
import { Schema } from "effect";
import { ProviderMetadata } from "@opencode-ai/schema/ai";
import { LLM } from "@opencode-ai/schema/llm";
export { ProviderMetadata };
/** Stable string identifier for a protocol implementation. */
export const ProtocolID = Schema.String;
/** Stable string identifier for the runnable route. */
export const RouteID = Schema.String;
export const ModelID = Schema.String.pipe(Schema.brand("AI.ModelID"));
export const ProviderID = Schema.String.pipe(Schema.brand("AI.ProviderID"));
export const ResponseID = Schema.String;
export const ContentBlockID = Schema.String;
export const ToolCallID = Schema.String;
export const ReasoningEfforts = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
export const ReasoningEffort = Schema.String;
export const TextVerbosity = Schema.Literals(["low", "medium", "high"]);
export const MessageRole = Schema.Literals(["system", "user", "assistant", "tool"]);
export const FinishReason = LLM.FinishReason;
export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown);
export * from "./ids.js";
export * from "./options.js";
export * from "./messages.js";
export * from "./events.js";
export * from "./errors.js";
export * from "./ids.js";
export * from "./options.js";
export * from "./messages.js";
export * from "./events.js";
export * from "./errors.js";
import { Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { CacheHint, GenerationOptions, HttpOptions } from "./options.js";
declare const systemPartSchema: Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>;
export type SystemPart = Schema.Schema.Type<typeof systemPartSchema>;
export declare const SystemPart: Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}> & {
make: (text: string) => SystemPart;
content: (input?: string | SystemPart | ReadonlyArray<SystemPart>) => any[];
};
export declare const TextPart: Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>;
export type TextPart = Schema.Schema.Type<typeof TextPart>;
export declare const MediaPart: Schema.Struct<{
readonly type: Schema.Literal<"media">;
readonly mediaType: Schema.String;
readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
readonly filename: Schema.optional<Schema.String>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>;
export type MediaPart = Schema.Schema.Type<typeof MediaPart>;
declare const toolResultValueSchema: Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"json">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"error">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"content">;
readonly value: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file">;
readonly uri: Schema.String;
readonly mime: Schema.String;
readonly name: Schema.optional<Schema.String>;
}>]>>;
}>]>;
export type ToolResultValue = Schema.Schema.Type<typeof toolResultValueSchema>;
export declare const ToolResultValue: Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"json">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"error">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"content">;
readonly value: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file">;
readonly uri: Schema.String;
readonly mime: Schema.String;
readonly name: Schema.optional<Schema.String>;
}>]>>;
}>]> & {
is: (value: unknown) => value is ToolResultValue;
make: (value: unknown, type?: ToolResultValue["type"]) => ToolResultValue;
};
export interface ToolOutput {
readonly structured: unknown;
readonly content: ReadonlyArray<Tool.Content>;
}
export declare const ToolOutput: Schema.Struct<{
readonly structured: Schema.Unknown;
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file">;
readonly uri: Schema.String;
readonly mime: Schema.String;
readonly name: Schema.optional<Schema.String>;
}>]>>;
}> & {
make: (structured: unknown, content?: ReadonlyArray<Tool.Content>) => ToolOutput;
fromResultValue: (result: ToolResultValue) => ToolOutput | undefined;
toResultValue: (output: ToolOutput) => ToolResultValue;
};
export declare const ToolCallPart: Schema.Struct<{
readonly type: Schema.Literal<"tool-call">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly input: Schema.Unknown;
readonly providerExecuted: Schema.optional<Schema.Boolean>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}> & {
make: (input: Omit<ToolCallPart, "type">) => ToolCallPart;
};
export type ToolCallPart = Schema.Schema.Type<typeof ToolCallPart>;
export declare const ToolResultPart: Schema.Struct<{
readonly type: Schema.Literal<"tool-result">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly result: Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"json">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"error">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"content">;
readonly value: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file">;
readonly uri: Schema.String;
readonly mime: Schema.String;
readonly name: Schema.optional<Schema.String>;
}>]>>;
}>]> & {
is: (value: unknown) => value is ToolResultValue;
make: (value: unknown, type?: ToolResultValue["type"]) => ToolResultValue;
};
readonly providerExecuted: Schema.optional<Schema.Boolean>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}> & {
make: (input: Omit<ToolResultPart, "type" | "result"> & {
readonly result: unknown;
readonly resultType?: ToolResultValue["type"];
}) => ToolResultPart;
};
export type ToolResultPart = Schema.Schema.Type<typeof ToolResultPart>;
export declare const ReasoningPart: Schema.Struct<{
readonly type: Schema.Literal<"reasoning">;
readonly text: Schema.String;
readonly encrypted: Schema.optional<Schema.String>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>;
export type ReasoningPart = Schema.Schema.Type<typeof ReasoningPart>;
export declare const ContentPart: Schema.toTaggedUnion<"type", readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"media">;
readonly mediaType: Schema.String;
readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
readonly filename: Schema.optional<Schema.String>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"tool-call">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly input: Schema.Unknown;
readonly providerExecuted: Schema.optional<Schema.Boolean>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}> & {
make: (input: Omit<ToolCallPart, "type">) => ToolCallPart;
}, Schema.Struct<{
readonly type: Schema.Literal<"tool-result">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly result: Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"json">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"error">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"content">;
readonly value: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file">;
readonly uri: Schema.String;
readonly mime: Schema.String;
readonly name: Schema.optional<Schema.String>;
}>]>>;
}>]> & {
is: (value: unknown) => value is ToolResultValue;
make: (value: unknown, type?: ToolResultValue["type"]) => ToolResultValue;
};
readonly providerExecuted: Schema.optional<Schema.Boolean>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}> & {
make: (input: Omit<ToolResultPart, "type" | "result"> & {
readonly result: unknown;
readonly resultType?: ToolResultValue["type"];
}) => ToolResultPart;
}, Schema.Struct<{
readonly type: Schema.Literal<"reasoning">;
readonly text: Schema.String;
readonly encrypted: Schema.optional<Schema.String>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>]>;
export type ContentPart = Schema.Schema.Type<typeof ContentPart>;
declare const Message_base: Schema.Class<Message, Schema.Struct<{
readonly id: Schema.optional<Schema.String>;
readonly role: Schema.Literals<readonly ["system", "user", "assistant", "tool"]>;
readonly content: Schema.$Array<Schema.toTaggedUnion<"type", readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"media">;
readonly mediaType: Schema.String;
readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array]>;
readonly filename: Schema.optional<Schema.String>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>, Schema.Struct<{
readonly type: Schema.Literal<"tool-call">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly input: Schema.Unknown;
readonly providerExecuted: Schema.optional<Schema.Boolean>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}> & {
make: (input: Omit<ToolCallPart, "type">) => ToolCallPart;
}, Schema.Struct<{
readonly type: Schema.Literal<"tool-result">;
readonly id: Schema.String;
readonly name: Schema.String;
readonly result: Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"json">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"error">;
readonly value: Schema.Unknown;
}>, Schema.Struct<{
readonly type: Schema.Literal<"content">;
readonly value: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
}>, Schema.Struct<{
readonly type: Schema.Literal<"file">;
readonly uri: Schema.String;
readonly mime: Schema.String;
readonly name: Schema.optional<Schema.String>;
}>]>>;
}>]> & {
is: (value: unknown) => value is ToolResultValue;
make: (value: unknown, type?: ToolResultValue["type"]) => ToolResultValue;
};
readonly providerExecuted: Schema.optional<Schema.Boolean>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}> & {
make: (input: Omit<ToolResultPart, "type" | "result"> & {
readonly result: unknown;
readonly resultType?: ToolResultValue["type"];
}) => ToolResultPart;
}, Schema.Struct<{
readonly type: Schema.Literal<"reasoning">;
readonly text: Schema.String;
readonly encrypted: Schema.optional<Schema.String>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly providerMetadata: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
}>]>>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly native: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>, {}>;
export declare class Message extends Message_base {
}
export declare namespace Message {
type ContentInput = string | ContentPart | ReadonlyArray<ContentPart>;
type SystemContentInput = string | TextPart | ReadonlyArray<TextPart>;
type Input = Omit<ConstructorParameters<typeof Message>[0], "content"> & {
readonly content: ContentInput;
};
const text: (value: string) => ContentPart;
const content: (input: ContentInput) => any[];
const make: (input: Message | Input) => Message;
const user: (content: ContentInput) => Message;
const assistant: (content: ContentInput) => Message;
/**
* Add an operator-authored instruction at this chronological point in the
* conversation. This is distinct from the initial `LLMRequest.system`
* prompt. Keep raw retrieved, tool, and web content out of privileged system
* updates; pass that untrusted content through ordinary user/tool channels.
*/
const system: (content: SystemContentInput) => Message;
const tool: (result: ToolResultPart | Parameters<typeof ToolResultPart.make>[0]) => Message;
}
declare const ToolDefinition_base: Schema.Class<ToolDefinition, Schema.Struct<{
readonly name: Schema.String;
readonly description: Schema.String;
readonly inputSchema: Schema.$Record<Schema.String, Schema.Unknown>;
readonly outputSchema: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly native: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>, {}>;
export declare class ToolDefinition extends ToolDefinition_base {
}
export declare namespace ToolDefinition {
type Input = ToolDefinition | ConstructorParameters<typeof ToolDefinition>[0];
/** Normalize tool definition input into the canonical `ToolDefinition` class. */
const make: (input: Input) => ToolDefinition;
}
declare const ToolChoice_base: Schema.Class<ToolChoice, Schema.Struct<{
readonly type: Schema.Literals<readonly ["auto", "none", "required", "tool"]>;
readonly name: Schema.optional<Schema.String>;
}>, {}>;
export declare class ToolChoice extends ToolChoice_base {
}
export declare namespace ToolChoice {
type Mode = Exclude<ToolChoice["type"], "tool">;
type Input = ToolChoice | ConstructorParameters<typeof ToolChoice>[0] | ToolDefinition | string;
/** Select a specific named tool. */
const named: (value: string) => ToolChoice;
/** Normalize ergonomic tool-choice inputs into the canonical `ToolChoice` class. */
const make: (input: Input) => ToolChoice;
}
declare const LLMRequest_base: Schema.Class<LLMRequest, Schema.Struct<{
readonly id: Schema.optional<Schema.String>;
readonly model: Schema.declare<import("./options.js").LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>, import("./options.js").LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>>;
readonly system: Schema.$Array<Schema.Struct<{
readonly type: Schema.Literal<"text">;
readonly text: Schema.String;
readonly cache: Schema.optional<typeof CacheHint>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}> & {
make: (text: string) => SystemPart;
content: (input?: string | SystemPart | ReadonlyArray<SystemPart>) => any[];
}>;
readonly messages: Schema.$Array<typeof Message>;
readonly tools: Schema.$Array<typeof ToolDefinition>;
readonly toolChoice: Schema.optional<typeof ToolChoice>;
readonly generation: Schema.optional<typeof GenerationOptions>;
readonly providerOptions: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpOptions>;
readonly cache: Schema.optional<Schema.Union<readonly [Schema.Literal<"auto">, Schema.Literal<"none">, Schema.Struct<{
readonly tools: Schema.optional<Schema.Boolean>;
readonly system: Schema.optional<Schema.Boolean>;
readonly messages: Schema.optional<Schema.Union<readonly [Schema.Literal<"latest-user-message">, Schema.Literal<"latest-assistant">, Schema.Struct<{
readonly tail: Schema.Number;
}>]>>;
readonly ttlSeconds: Schema.optional<Schema.Number>;
}>]>>;
readonly promptCacheKey: Schema.optional<Schema.String>;
readonly metadata: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
}>, {}>;
export declare class LLMRequest extends LLMRequest_base {
}
export declare namespace LLMRequest {
type Input = ConstructorParameters<typeof LLMRequest>[0];
const input: (request: LLMRequest) => Input;
const update: (request: LLMRequest, patch: Partial<Input>) => LLMRequest;
}
export {};
import { Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { JsonSchema, MessageRole, ProviderMetadata } from "./ids.js";
import { CacheHint, CachePolicy, GenerationOptions, HttpOptions, LanguageModelSchema, ProviderOptions, } from "./options.js";
import { isRecord } from "../utils/record.js";
const systemPartSchema = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.SystemPart" });
const makeSystemPart = (text) => ({ type: "text", text });
export const SystemPart = Object.assign(systemPartSchema, {
make: makeSystemPart,
content: (input) => {
if (input === undefined)
return [];
return typeof input === "string" ? [makeSystemPart(input)] : Array.isArray(input) ? [...input] : [input];
},
});
export const TextPart = Schema.Struct({
type: Schema.Literal("text"),
text: Schema.String,
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Content.Text" });
export const MediaPart = Schema.Struct({
type: Schema.Literal("media"),
mediaType: Schema.String,
data: Schema.Union([Schema.String, Schema.Uint8Array]),
filename: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}).annotate({ identifier: "LLM.Content.Media" });
const isToolResultValue = (value) => isRecord(value) &&
(value.type === "text" || value.type === "json" || value.type === "error" || value.type === "content") &&
"value" in value;
const toolResultValueSchema = Schema.Union([
Schema.Struct({
type: Schema.Literal("json"),
value: Schema.Unknown,
}),
Schema.Struct({
type: Schema.Literal("text"),
value: Schema.Unknown,
}),
Schema.Struct({
type: Schema.Literal("error"),
value: Schema.Unknown,
}),
Schema.Struct({
type: Schema.Literal("content"),
value: Schema.Array(Tool.Content),
}),
]).annotate({ identifier: "LLM.ToolResult" });
export const ToolResultValue = Object.assign(toolResultValueSchema, {
is: isToolResultValue,
make: (value, type = "json") => {
if (isToolResultValue(value))
return value;
if (type === "content")
return { type, value: Array.isArray(value) ? value : [] };
return { type, value };
},
});
export const ToolOutput = Object.assign(Schema.Struct({
structured: Schema.Unknown,
content: Schema.Array(Tool.Content),
}).annotate({ identifier: "LLM.ToolOutput" }), {
make: (structured, content = []) => ({ structured, content }),
fromResultValue: (result) => {
switch (result.type) {
case "json":
return { structured: result.value, content: [] };
case "text":
return { structured: {}, content: [{ type: "text", text: toolResultText(result.value) }] };
case "content":
return { structured: {}, content: result.value };
case "error":
return undefined;
}
},
toResultValue: (output) => {
if (output.content.length === 0)
return { type: "json", value: output.structured };
if (output.content.length === 1 && output.content[0]?.type === "text")
return { type: "text", value: output.content[0].text };
return { type: "content", value: output.content };
},
});
const toolResultText = (value) => {
if (typeof value === "string")
return value;
try {
return JSON.stringify(value) ?? String(value);
}
catch {
return String(value);
}
};
export const ToolCallPart = Object.assign(Schema.Struct({
type: Schema.Literal("tool-call"),
id: Schema.String,
name: Schema.String,
input: Schema.Unknown,
providerExecuted: Schema.optional(Schema.Boolean),
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Content.ToolCall" }), {
make: (input) => ({ type: "tool-call", ...input }),
});
export const ToolResultPart = Object.assign(Schema.Struct({
type: Schema.Literal("tool-result"),
id: Schema.String,
name: Schema.String,
result: ToolResultValue,
providerExecuted: Schema.optional(Schema.Boolean),
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Content.ToolResult" }), {
make: (input) => ({
type: "tool-result",
id: input.id,
name: input.name,
result: ToolResultValue.make(input.result, input.resultType),
providerExecuted: input.providerExecuted,
cache: input.cache,
metadata: input.metadata,
providerMetadata: input.providerMetadata,
}),
});
export const ReasoningPart = Schema.Struct({
type: Schema.Literal("reasoning"),
text: Schema.String,
encrypted: Schema.optional(Schema.String),
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
providerMetadata: Schema.optional(ProviderMetadata),
}).annotate({ identifier: "LLM.Content.Reasoning" });
export const ContentPart = Schema.Union([TextPart, MediaPart, ToolCallPart, ToolResultPart, ReasoningPart]).pipe(Schema.toTaggedUnion("type"));
export class Message extends Schema.Class("LLM.Message")({
id: Schema.optional(Schema.String),
role: MessageRole,
content: Schema.Array(ContentPart),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {
}
(function (Message) {
Message.text = (value) => ({ type: "text", text: value });
Message.content = (input) => typeof input === "string" ? [Message.text(input)] : Array.isArray(input) ? [...input] : [input];
Message.make = (input) => {
if (input instanceof Message)
return input;
return new Message({ ...input, content: Message.content(input.content) });
};
Message.user = (content) => Message.make({ role: "user", content });
Message.assistant = (content) => Message.make({ role: "assistant", content });
/**
* Add an operator-authored instruction at this chronological point in the
* conversation. This is distinct from the initial `LLMRequest.system`
* prompt. Keep raw retrieved, tool, and web content out of privileged system
* updates; pass that untrusted content through ordinary user/tool channels.
*/
Message.system = (content) => Message.make({ role: "system", content });
Message.tool = (result) => Message.make({ role: "tool", content: ["type" in result ? result : ToolResultPart.make(result)] });
})(Message || (Message = {}));
export class ToolDefinition extends Schema.Class("LLM.ToolDefinition")({
name: Schema.String,
description: Schema.String,
inputSchema: JsonSchema,
outputSchema: Schema.optional(JsonSchema),
cache: Schema.optional(CacheHint),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
native: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {
}
(function (ToolDefinition) {
/** Normalize tool definition input into the canonical `ToolDefinition` class. */
ToolDefinition.make = (input) => (input instanceof ToolDefinition ? input : new ToolDefinition(input));
})(ToolDefinition || (ToolDefinition = {}));
export class ToolChoice extends Schema.Class("LLM.ToolChoice")({
type: Schema.Literals(["auto", "none", "required", "tool"]),
name: Schema.optional(Schema.String),
}) {
}
(function (ToolChoice) {
const isMode = (value) => value === "auto" || value === "none" || value === "required";
/** Select a specific named tool. */
ToolChoice.named = (value) => new ToolChoice({ type: "tool", name: value });
/** Normalize ergonomic tool-choice inputs into the canonical `ToolChoice` class. */
ToolChoice.make = (input) => {
if (input instanceof ToolChoice)
return input;
if (input instanceof ToolDefinition)
return ToolChoice.named(input.name);
if (typeof input === "string")
return isMode(input) ? new ToolChoice({ type: input }) : ToolChoice.named(input);
return new ToolChoice(input);
};
})(ToolChoice || (ToolChoice = {}));
export class LLMRequest extends Schema.Class("LLM.Request")({
id: Schema.optional(Schema.String),
model: LanguageModelSchema,
system: Schema.Array(SystemPart),
messages: Schema.Array(Message),
tools: Schema.Array(ToolDefinition),
toolChoice: Schema.optional(ToolChoice),
generation: Schema.optional(GenerationOptions),
providerOptions: Schema.optional(ProviderOptions),
http: Schema.optional(HttpOptions),
cache: Schema.optional(CachePolicy),
// Stable cache affinity for protocols that support provider-managed prompt caching.
promptCacheKey: Schema.optional(Schema.String),
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
}) {
}
(function (LLMRequest) {
LLMRequest.input = (request) => ({
id: request.id,
model: request.model,
system: request.system,
messages: request.messages,
tools: request.tools,
toolChoice: request.toolChoice,
generation: request.generation,
providerOptions: request.providerOptions,
http: request.http,
cache: request.cache,
promptCacheKey: request.promptCacheKey,
metadata: request.metadata,
});
LLMRequest.update = (request, patch) => {
if (Object.keys(patch).length === 0)
return request;
return new LLMRequest({
...LLMRequest.input(request),
...patch,
model: patch.model ?? request.model,
});
};
})(LLMRequest || (LLMRequest = {}));
import { Schema } from "effect";
import { ModelID, ProviderID } from "./ids.js";
import type { AnyRoute } from "../route/client.js";
export declare const mergeJsonRecords: (...items: ReadonlyArray<Record<string, unknown> | undefined>) => Record<string, unknown> | undefined;
export declare const ProviderOptions: Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>;
export type ProviderOptions = Schema.Schema.Type<typeof ProviderOptions>;
export declare const mergeProviderOptions: (...items: ReadonlyArray<ProviderOptions | undefined>) => ProviderOptions | undefined;
declare const HttpOptions_base: Schema.Class<HttpOptions, Schema.Struct<{
readonly body: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
readonly headers: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
readonly query: Schema.optional<Schema.$Record<Schema.String, Schema.String>>;
}>, {}>;
export declare class HttpOptions extends HttpOptions_base {
}
export declare namespace HttpOptions {
type Input = HttpOptions | ConstructorParameters<typeof HttpOptions>[0];
/** Normalize HTTP option input into the canonical `HttpOptions` class. */
const make: (input: Input) => HttpOptions;
}
export declare const mergeHttpOptions: (...items: ReadonlyArray<HttpOptions | undefined>) => HttpOptions | undefined;
declare const GenerationOptions_base: Schema.Class<GenerationOptions, Schema.Struct<{
readonly maxTokens: Schema.optional<Schema.Number>;
readonly temperature: Schema.optional<Schema.Number>;
readonly topP: Schema.optional<Schema.Number>;
readonly topK: Schema.optional<Schema.Number>;
readonly frequencyPenalty: Schema.optional<Schema.Number>;
readonly presencePenalty: Schema.optional<Schema.Number>;
readonly seed: Schema.optional<Schema.Number>;
readonly stop: Schema.optional<Schema.$Array<Schema.String>>;
}>, {}>;
export declare class GenerationOptions extends GenerationOptions_base {
}
export declare namespace GenerationOptions {
type Input = GenerationOptions | ConstructorParameters<typeof GenerationOptions>[0];
/** Normalize generation option input into the canonical `GenerationOptions` class. */
const make: (input?: Input) => GenerationOptions;
}
export type GenerationOptionsFields = {
readonly maxTokens?: number;
readonly temperature?: number;
readonly topP?: number;
readonly topK?: number;
readonly frequencyPenalty?: number;
readonly presencePenalty?: number;
readonly seed?: number;
readonly stop?: ReadonlyArray<string>;
};
export type GenerationOptionsInput = GenerationOptions | GenerationOptionsFields;
export declare const mergeGenerationOptions: (...items: ReadonlyArray<GenerationOptionsInput | undefined>) => GenerationOptions | undefined;
declare const LanguageModelLimits_base: Schema.Class<LanguageModelLimits, Schema.Struct<{
readonly context: Schema.optional<Schema.Number>;
readonly input: Schema.optional<Schema.Number>;
readonly output: Schema.optional<Schema.Number>;
}>, {}>;
export declare class LanguageModelLimits extends LanguageModelLimits_base {
}
export declare namespace LanguageModelLimits {
type Input = LanguageModelLimits | ConstructorParameters<typeof LanguageModelLimits>[0];
/** Normalize model limit input into the canonical `LanguageModelLimits` class. */
const make: (input: Input | undefined) => LanguageModelLimits;
}
declare const LanguageModelDefaults_base: Schema.Class<LanguageModelDefaults, Schema.Struct<{
readonly limits: Schema.optional<typeof LanguageModelLimits>;
readonly generation: Schema.optional<typeof GenerationOptions>;
readonly providerOptions: Schema.optional<Schema.$Record<Schema.String, Schema.$Record<Schema.String, Schema.Unknown>>>;
readonly http: Schema.optional<typeof HttpOptions>;
}>, {}>;
export declare class LanguageModelDefaults extends LanguageModelDefaults_base {
}
export declare namespace LanguageModelDefaults {
type Input = LanguageModelDefaults | {
readonly limits?: LanguageModelLimits.Input;
readonly generation?: GenerationOptions.Input;
readonly providerOptions?: ProviderOptions;
readonly http?: HttpOptions.Input;
};
/** Normalize selected-model request defaults without applying precedence. */
const make: (input: Input) => LanguageModelDefaults;
}
export declare const LanguageModelToolSchemaCompatibility: Schema.Literals<readonly ["gemini", "moonshot"]>;
export type LanguageModelToolSchemaCompatibility = Schema.Schema.Type<typeof LanguageModelToolSchemaCompatibility>;
export declare const LanguageModelMaxTokensFieldCompatibility: Schema.Literals<readonly ["max_completion_tokens", "max_tokens"]>;
export type LanguageModelMaxTokensFieldCompatibility = Schema.Schema.Type<typeof LanguageModelMaxTokensFieldCompatibility>;
declare const LanguageModelCompatibility_base: Schema.Class<LanguageModelCompatibility, Schema.Struct<{
readonly toolSchema: Schema.optional<Schema.Literals<readonly ["gemini", "moonshot"]>>;
readonly reasoningField: Schema.optional<Schema.String>;
readonly maxTokensField: Schema.optional<Schema.Literals<readonly ["max_completion_tokens", "max_tokens"]>>;
readonly requireFinishReason: Schema.optional<Schema.Boolean>;
}>, {}>;
export declare class LanguageModelCompatibility extends LanguageModelCompatibility_base {
}
export declare namespace LanguageModelCompatibility {
type Input = LanguageModelCompatibility | ConstructorParameters<typeof LanguageModelCompatibility>[0];
/** Normalize model/upstream compatibility metadata without projecting requests. */
const make: (input: Input) => LanguageModelCompatibility;
}
export declare class LanguageModel<Options extends ProviderOptions = ProviderOptions> {
protected readonly _ProviderOptions: Options;
readonly id: ModelID;
readonly provider: ProviderID;
readonly route: AnyRoute;
readonly defaults?: LanguageModelDefaults;
readonly compatibility?: LanguageModelCompatibility;
constructor(input: LanguageModel.ConstructorInput);
static make<Options extends ProviderOptions = ProviderOptions>(input: LanguageModel.Input): LanguageModel<Options>;
static input<Options extends ProviderOptions>(model: LanguageModel<Options>): LanguageModel.ConstructorInput;
static update<Options extends ProviderOptions>(model: LanguageModel<Options>, patch: Partial<LanguageModel.Input>): LanguageModel<Options>;
}
export declare namespace LanguageModel {
type ConstructorInput = {
readonly id: ModelID;
readonly provider: ProviderID;
readonly route: AnyRoute;
readonly defaults?: LanguageModelDefaults;
readonly compatibility?: LanguageModelCompatibility;
};
type Input = Omit<ConstructorInput, "id" | "provider" | "defaults" | "compatibility"> & {
readonly id: string | ModelID;
readonly provider: string | ProviderID;
readonly defaults?: LanguageModelDefaults.Input;
readonly compatibility?: LanguageModelCompatibility.Input;
};
}
export type LanguageModelInput = LanguageModel.Input;
export type LanguageModelProviderOptions<SelectedModel> = SelectedModel extends LanguageModel<infer Options> ? Options : never;
export declare const LanguageModelSchema: Schema.declare<LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>, LanguageModel<{
readonly [x: string]: {
readonly [x: string]: unknown;
};
}>>;
declare const CacheHint_base: Schema.Class<CacheHint, Schema.Struct<{
readonly type: Schema.Literals<readonly ["ephemeral", "persistent"]>;
readonly ttlSeconds: Schema.optional<Schema.Number>;
}>, {}>;
export declare class CacheHint extends CacheHint_base {
}
export declare const CachePolicyObject: Schema.Struct<{
readonly tools: Schema.optional<Schema.Boolean>;
readonly system: Schema.optional<Schema.Boolean>;
readonly messages: Schema.optional<Schema.Union<readonly [Schema.Literal<"latest-user-message">, Schema.Literal<"latest-assistant">, Schema.Struct<{
readonly tail: Schema.Number;
}>]>>;
readonly ttlSeconds: Schema.optional<Schema.Number>;
}>;
export type CachePolicyObject = Schema.Schema.Type<typeof CachePolicyObject>;
export declare const CachePolicy: Schema.Union<readonly [Schema.Literal<"auto">, Schema.Literal<"none">, Schema.Struct<{
readonly tools: Schema.optional<Schema.Boolean>;
readonly system: Schema.optional<Schema.Boolean>;
readonly messages: Schema.optional<Schema.Union<readonly [Schema.Literal<"latest-user-message">, Schema.Literal<"latest-assistant">, Schema.Struct<{
readonly tail: Schema.Number;
}>]>>;
readonly ttlSeconds: Schema.optional<Schema.Number>;
}>]>;
export type CachePolicy = Schema.Schema.Type<typeof CachePolicy>;
export {};
import { Schema } from "effect";
import { JsonSchema, ModelID, ProviderID } from "./ids.js";
import { isRecord } from "../utils/record.js";
export const mergeJsonRecords = (...items) => {
const defined = items.filter((item) => item !== undefined);
if (defined.length === 0)
return undefined;
if (defined.length === 1 && Object.values(defined[0]).every((value) => value !== undefined))
return defined[0];
const result = {};
for (const item of defined) {
for (const [key, value] of Object.entries(item)) {
if (value === undefined)
continue;
result[key] = isRecord(result[key]) && isRecord(value) ? mergeJsonRecords(result[key], value) : value;
}
}
return Object.keys(result).length === 0 ? undefined : result;
};
const mergeStringRecords = (...items) => {
const defined = items.filter((item) => item !== undefined);
if (defined.length === 0)
return undefined;
if (defined.length === 1)
return defined[0];
const result = Object.fromEntries(defined.flatMap((item) => Object.entries(item).filter((entry) => entry[1] !== undefined)));
return Object.keys(result).length === 0 ? undefined : result;
};
export const ProviderOptions = Schema.Record(Schema.String, Schema.Record(Schema.String, Schema.Unknown));
export const mergeProviderOptions = (...items) => {
const result = {};
for (const item of items) {
if (!item)
continue;
for (const [provider, options] of Object.entries(item)) {
const merged = mergeJsonRecords(result[provider], options);
if (merged)
result[provider] = merged;
}
}
return Object.keys(result).length === 0 ? undefined : result;
};
export class HttpOptions extends Schema.Class("AI.HttpOptions")({
body: Schema.optional(JsonSchema),
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
query: Schema.optional(Schema.Record(Schema.String, Schema.String)),
}) {
}
(function (HttpOptions) {
/** Normalize HTTP option input into the canonical `HttpOptions` class. */
HttpOptions.make = (input) => (input instanceof HttpOptions ? input : new HttpOptions(input));
})(HttpOptions || (HttpOptions = {}));
export const mergeHttpOptions = (...items) => {
const body = mergeJsonRecords(...items.map((item) => item?.body));
const headers = mergeStringRecords(...items.map((item) => item?.headers));
const query = mergeStringRecords(...items.map((item) => item?.query));
if (!body && !headers && !query)
return undefined;
return new HttpOptions({ body, headers, query });
};
export class GenerationOptions extends Schema.Class("LLM.GenerationOptions")({
maxTokens: Schema.optional(Schema.Number),
temperature: Schema.optional(Schema.Number),
topP: Schema.optional(Schema.Number),
topK: Schema.optional(Schema.Number),
frequencyPenalty: Schema.optional(Schema.Number),
presencePenalty: Schema.optional(Schema.Number),
seed: Schema.optional(Schema.Number),
stop: Schema.optional(Schema.Array(Schema.String)),
}) {
}
(function (GenerationOptions) {
/** Normalize generation option input into the canonical `GenerationOptions` class. */
GenerationOptions.make = (input = {}) => (input instanceof GenerationOptions ? input : new GenerationOptions(input));
})(GenerationOptions || (GenerationOptions = {}));
const latestGeneration = (items, key) => items.findLast((item) => item?.[key] !== undefined)?.[key];
export const mergeGenerationOptions = (...items) => {
const result = new GenerationOptions({
maxTokens: latestGeneration(items, "maxTokens"),
temperature: latestGeneration(items, "temperature"),
topP: latestGeneration(items, "topP"),
topK: latestGeneration(items, "topK"),
frequencyPenalty: latestGeneration(items, "frequencyPenalty"),
presencePenalty: latestGeneration(items, "presencePenalty"),
seed: latestGeneration(items, "seed"),
stop: latestGeneration(items, "stop"),
});
return Object.values(result).some((value) => value !== undefined) ? result : undefined;
};
export class LanguageModelLimits extends Schema.Class("LLM.LanguageModelLimits")({
context: Schema.optional(Schema.Number),
input: Schema.optional(Schema.Number),
output: Schema.optional(Schema.Number),
}) {
}
(function (LanguageModelLimits) {
/** Normalize model limit input into the canonical `LanguageModelLimits` class. */
LanguageModelLimits.make = (input) => input instanceof LanguageModelLimits ? input : new LanguageModelLimits(input ?? {});
})(LanguageModelLimits || (LanguageModelLimits = {}));
export class LanguageModelDefaults extends Schema.Class("LLM.LanguageModelDefaults")({
limits: Schema.optional(LanguageModelLimits),
generation: Schema.optional(GenerationOptions),
providerOptions: Schema.optional(ProviderOptions),
http: Schema.optional(HttpOptions),
}) {
}
(function (LanguageModelDefaults) {
/** Normalize selected-model request defaults without applying precedence. */
LanguageModelDefaults.make = (input) => {
if (input instanceof LanguageModelDefaults)
return input;
return new LanguageModelDefaults({
limits: input.limits === undefined ? undefined : LanguageModelLimits.make(input.limits),
generation: input.generation === undefined ? undefined : GenerationOptions.make(input.generation),
providerOptions: input.providerOptions,
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
});
};
})(LanguageModelDefaults || (LanguageModelDefaults = {}));
export const LanguageModelToolSchemaCompatibility = Schema.Literals(["gemini", "moonshot"]);
export const LanguageModelMaxTokensFieldCompatibility = Schema.Literals(["max_completion_tokens", "max_tokens"]);
export class LanguageModelCompatibility extends Schema.Class("LLM.LanguageModelCompatibility")({
toolSchema: Schema.optional(LanguageModelToolSchemaCompatibility),
reasoningField: Schema.optional(Schema.String),
maxTokensField: Schema.optional(LanguageModelMaxTokensFieldCompatibility),
requireFinishReason: Schema.optional(Schema.Boolean),
}) {
}
(function (LanguageModelCompatibility) {
/** Normalize model/upstream compatibility metadata without projecting requests. */
LanguageModelCompatibility.make = (input) => input instanceof LanguageModelCompatibility ? input : new LanguageModelCompatibility(input);
})(LanguageModelCompatibility || (LanguageModelCompatibility = {}));
export class LanguageModel {
id;
provider;
route;
defaults;
compatibility;
constructor(input) {
this.id = input.id;
this.provider = input.provider;
this.route = input.route;
this.defaults = input.defaults;
this.compatibility = input.compatibility;
}
static make(input) {
return new LanguageModel({
id: ModelID.make(input.id),
provider: ProviderID.make(input.provider),
route: input.route,
defaults: input.defaults === undefined ? undefined : LanguageModelDefaults.make(input.defaults),
compatibility: input.compatibility === undefined ? undefined : LanguageModelCompatibility.make(input.compatibility),
});
}
static input(model) {
return {
id: model.id,
provider: model.provider,
route: model.route,
defaults: model.defaults,
compatibility: model.compatibility,
};
}
static update(model, patch) {
if (Object.keys(patch).length === 0)
return model;
return LanguageModel.make({
...LanguageModel.input(model),
...patch,
});
}
}
export const LanguageModelSchema = Schema.declare((value) => value instanceof LanguageModel, {
expected: "LLM.LanguageModel",
});
export class CacheHint extends Schema.Class("LLM.CacheHint")({
type: Schema.Literals(["ephemeral", "persistent"]),
ttlSeconds: Schema.optional(Schema.Number),
}) {
}
// Auto-placement policy for prompt caching. The protocol-neutral lowering step
// reads this and injects `CacheHint`s at the configured boundaries; the
// per-protocol body builders then translate those hints into wire markers as
// usual. `"auto"` is the recommended default for agent loops — it places
// breakpoints at the last tool definition, the first and last distinct system
// parts, and the conversation tail. The rolling message breakpoint keeps a
// prior cache entry within Anthropic/Bedrock's 20-block lookback during long
// tool loops.
//
// Pass `"none"` to opt out entirely (the legacy behavior). Pass the granular
// object form to override individual choices.
export const CachePolicyObject = Schema.Struct({
tools: Schema.optional(Schema.Boolean),
system: Schema.optional(Schema.Boolean),
messages: Schema.optional(Schema.Union([
Schema.Literal("latest-user-message"),
Schema.Literal("latest-assistant"),
Schema.Struct({ tail: Schema.Number }),
])),
ttlSeconds: Schema.optional(Schema.Number),
});
export const CachePolicy = Schema.Union([Schema.Literal("auto"), Schema.Literal("none"), CachePolicyObject]);
export * as TestLLM from "./testing.js";
import { type Interface as LLMClientShape } from "./route/client.js";
import { LLMEvent, type FinishReasonDetails, type AIError, type LLMRequest, type UsageInput } from "./schema/index.js";
import { Context, Effect, Layer, Scope, Stream } from "effect";
export type Response = readonly LLMEvent[] | Stream.Stream<LLMEvent, AIError>;
export type Gate = Readonly<{
started: Effect.Effect<void>;
release: Effect.Effect<void>;
}>;
export interface Interface {
readonly requests: LLMRequest[];
readonly push: (...responses: readonly Response[]) => Effect.Effect<void>;
readonly always: (response: Response) => Effect.Effect<void>;
readonly wait: (count: number) => Effect.Effect<void>;
readonly gate: Effect.Effect<Gate, never, Scope.Scope>;
readonly client: LLMClientShape;
}
export interface LayerOptions {
readonly transformRequest?: (request: LLMRequest) => LLMRequest;
/** Used after the one-shot response queue is exhausted. Omit to defect on unexpected requests. */
readonly fallback?: Response;
}
declare const Service_base: Context.ServiceClass<Service, "@opencode/ai/TestLLM", Interface>;
export declare class Service extends Service_base {
}
export declare const complete: (options: {
readonly reason: FinishReasonDetails;
readonly usage?: UsageInput;
}, ...events: readonly LLMEvent[]) => ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
export declare const stop: (...events: readonly LLMEvent[]) => ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
export declare const toolCalls: (...events: readonly LLMEvent[]) => ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
export declare const text: (value: string, id: string) => ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
export declare const textWithUsage: (value: string, id: string, inputTokens: number) => ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
export declare const tool: (id: string, name: string, input: unknown) => ({
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
})[];
export declare const failAfter: (error: AIError, ...events: readonly LLMEvent[]) => Stream.Stream<{
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
}, AIError, never>;
export declare const hangAfter: (...events: readonly LLMEvent[]) => Stream.Stream<{
readonly type: "step-start";
readonly index: number;
} | {
readonly id: string;
readonly type: "text-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "text-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-start";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-delta";
readonly text: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "reasoning-end";
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly id: string;
readonly type: "tool-input-start";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly type: "tool-input-delta";
readonly id: string;
readonly name: string;
readonly text: string;
} | {
readonly id: string;
readonly type: "tool-input-end";
readonly name: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "tool-input-error";
readonly id: string;
readonly name: string;
readonly raw: string;
} | {
readonly id: string;
readonly type: "tool-call";
readonly name: string;
readonly input: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-result";
readonly name: string;
readonly result: {
readonly type: "json";
readonly value: unknown;
} | {
readonly type: "text";
readonly value: unknown;
} | {
readonly type: "error";
readonly value: unknown;
} | {
readonly type: "content";
readonly value: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
};
readonly output?: {
readonly structured: unknown;
readonly content: readonly ({
readonly type: "text";
readonly text: string;
} | {
readonly type: "file";
readonly uri: string;
readonly mime: string;
readonly name?: string | undefined;
})[];
} | undefined;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly providerExecuted?: boolean | undefined;
} | {
readonly id: string;
readonly type: "tool-error";
readonly name: string;
readonly message: string;
readonly error?: unknown;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
} | {
readonly type: "step-finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly index: number;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "finish";
readonly reason: {
readonly normalized: "length" | "stop" | "tool-calls" | "content-filter" | "error" | "unknown";
readonly raw?: string | undefined;
};
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly usage?: import("./schema/events.js").Usage | undefined;
} | {
readonly type: "provider-error";
readonly message: string;
readonly providerMetadata?: {
readonly [x: string]: {
readonly [x: string]: unknown;
};
} | undefined;
readonly classification?: "context-overflow" | "payload-too-large" | undefined;
}, never, never>;
export declare const layer: (options?: LayerOptions) => Layer.Layer<Service, never, never>;
export declare const clientLayer: Layer.Layer<import("./route/client.js").Service, never, Service>;
export declare const push: (...responses: readonly Response[]) => Effect.Effect<void, never, Service>;
export declare const always: (response: Response) => Effect.Effect<void, never, Service>;
export declare const wait: (count: number) => Effect.Effect<void, never, Service>;
export declare const gate: Effect.Effect<Readonly<{
started: Effect.Effect<void>;
release: Effect.Effect<void>;
}>, never, Scope.Scope | Service>;
export * as TestLLM from "./testing.js";
import { LLMClient } from "./route/client.js";
import { LLMEvent, LLMResponse, } from "./schema/index.js";
import { Context, Deferred, Effect, Latch, Layer, Queue, Scope, Stream } from "effect";
export class Service extends Context.Service()("@opencode/ai/TestLLM") {
}
export const complete = (options, ...events) => [
LLMEvent.stepStart({ index: 0 }),
...events,
LLMEvent.stepFinish({ index: 0, reason: options.reason, usage: options.usage }),
LLMEvent.finish({ reason: options.reason }),
];
export const stop = (...events) => complete({ reason: { normalized: "stop" } }, ...events);
export const toolCalls = (...events) => complete({ reason: { normalized: "tool-calls" } }, ...events);
const textEvents = (value, id) => [
LLMEvent.textStart({ id }),
LLMEvent.textDelta({ id, text: value }),
LLMEvent.textEnd({ id }),
];
export const text = (value, id) => stop(...textEvents(value, id));
export const textWithUsage = (value, id, inputTokens) => complete({ reason: { normalized: "stop" }, usage: { inputTokens, nonCachedInputTokens: inputTokens } }, ...textEvents(value, id));
export const tool = (id, name, input) => toolCalls(LLMEvent.toolCall({ id, name, input }));
export const failAfter = (error, ...events) => Stream.fromIterable(events).pipe(Stream.concat(Stream.fail(error)));
export const hangAfter = (...events) => Stream.concat(Stream.fromIterable(events), Stream.never);
const toStream = (response) => (Stream.isStream(response) ? response : Stream.fromIterable(response));
export const layer = (options = {}) => Layer.effect(Service, Effect.gen(function* () {
const requests = [];
const responses = [];
let started = Deferred.makeUnsafe();
let fallback = options.fallback;
let activeGate;
const wait = (count) => Effect.suspend(() => requests.length >= count ? Effect.void : Deferred.await(started).pipe(Effect.andThen(wait(count))));
const stream = ((request) => {
requests.push(options.transformRequest?.(request) ?? request);
const waiting = started;
started = Deferred.makeUnsafe();
Deferred.doneUnsafe(waiting, Effect.void);
const response = responses.shift() ?? fallback;
if (!response)
return Stream.die(new Error(`TestLLM has no response for request ${requests.length}`));
const streamed = toStream(response);
const gate = activeGate;
if (!gate)
return streamed;
return Stream.unwrap(Queue.offer(gate.started, undefined).pipe(Effect.andThen(gate.release.await), Effect.as(streamed)));
});
const client = LLMClient.Service.of({
stream,
generate: (request) => stream(request).pipe(Stream.runFold(LLMResponse.empty, LLMResponse.reduce), Effect.flatMap((state) => {
const response = LLMResponse.complete(state);
if (response)
return Effect.succeed(response);
return Effect.die("TestLLM response ended without a terminal finish event");
})),
});
return Service.of({
requests,
push: (...input) => Effect.sync(() => {
responses.push(...input);
}),
always: (response) => Effect.sync(() => {
fallback = response;
}),
wait,
gate: Effect.gen(function* () {
const gate = {
started: yield* Effect.acquireRelease(Queue.unbounded(), Queue.shutdown),
release: yield* Latch.make(),
};
activeGate = gate;
const release = Effect.sync(() => {
if (activeGate === gate)
activeGate = undefined;
}).pipe(Effect.andThen(gate.release.open), Effect.asVoid);
yield* Effect.addFinalizer(() => release);
return {
started: Queue.take(gate.started),
release,
};
}),
client,
});
}));
export const clientLayer = Layer.effect(LLMClient.Service, Effect.map(Service, (service) => service.client));
export const push = (...responses) => Service.use((service) => service.push(...responses));
export const always = (response) => Service.use((service) => service.always(response));
export const wait = (count) => Service.use((service) => service.wait(count));
export const gate = Service.use((service) => service.gate);
import { Effect } from "effect";
import { LLMEvent, type ToolCallPart, type ToolOutput as ToolOutputType, type ToolResultValue as ToolResultValueType } from "./schema/index.js";
import { type Tools } from "./tool.js";
export interface ToolSettlement {
readonly result: ToolResultValueType;
readonly output?: ToolOutputType;
}
export interface DispatchResult extends ToolSettlement {
readonly events: ReadonlyArray<LLMEvent>;
}
/** Execute one canonical tool call without owning provider IO or continuation. */
export declare const dispatch: (tools: Tools, call: ToolCallPart) => Effect.Effect<DispatchResult>;
export declare const ToolRuntime: {
readonly dispatch: (tools: Tools, call: ToolCallPart) => Effect.Effect<DispatchResult>;
};
import { Effect } from "effect";
import { LLMEvent, ToolFailure, ToolOutput, ToolResultValue, } from "./schema/index.js";
import {} from "./tool.js";
/** Execute one canonical tool call without owning provider IO or continuation. */
export const dispatch = (tools, call) => {
const tool = tools[call.name];
if (!tool)
return Effect.succeed(result(call, { type: "error", value: `Unknown tool: ${call.name}` }));
if (!tool.execute)
return Effect.succeed(result(call, { type: "error", value: `Tool has no execute handler: ${call.name}` }));
return decodeAndExecute(tool, call).pipe(Effect.map((value) => result(call, value)), Effect.catchTag("Tool.Error", (failure) => Effect.succeed(result(call, { type: "error", value: failure.message }, failure.error))));
};
const decodeAndExecute = (tool, call) => tool._decode(call.input).pipe(Effect.mapError((error) => new ToolFailure({ message: `Invalid tool input: ${error.message}` })), Effect.flatMap((decoded) => tool.execute(decoded, { id: call.id, name: call.name }).pipe(Effect.flatMap((value) => tool._encode(value).pipe(Effect.mapError((error) => new ToolFailure({
message: `Tool returned an invalid value for its success schema: ${error.message}`,
})))), Effect.map((encoded) => {
if (tool._legacyResult && ToolResultValue.is(encoded))
return { result: encoded, output: ToolOutput.fromResultValue(encoded) };
const output = tool._project(decoded, call.id, encoded);
const result = ToolOutput.toResultValue(output);
return result.type === "error" ? { result } : { result, output };
}))));
const result = (call, value, error) => {
const settlement = ToolResultValue.is(value) ? { result: value } : value;
return {
result: settlement.result,
output: settlement.output,
events: settlement.result.type === "error"
? [
LLMEvent.toolError({
id: call.id,
name: call.name,
message: String(settlement.result.value),
error,
providerMetadata: call.providerMetadata,
}),
LLMEvent.toolResult({
id: call.id,
name: call.name,
result: settlement.result,
providerMetadata: call.providerMetadata,
}),
]
: [
LLMEvent.toolResult({
id: call.id,
name: call.name,
result: settlement.result,
output: settlement.output,
providerMetadata: call.providerMetadata,
}),
],
};
};
export const ToolRuntime = { dispatch };
import { Effect, JsonSchema, Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import type { ToolCallPart, ToolDefinition as ToolDefinitionClass, ToolOutput as ToolOutputType } from "./schema/index.js";
import { ToolFailure } from "./schema/index.js";
/**
* Schema constraint for tool parameters / success values: no decoding or
* encoding services are allowed. Tools should be self-contained — anything
* beyond pure data conversion belongs in the handler closure.
*/
export type ToolSchema<T> = Schema.Codec<T, any, never, never>;
export interface ToolExecuteContext {
readonly id: ToolCallPart["id"];
readonly name: ToolCallPart["name"];
}
export type ToolExecute<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> = (params: Schema.Schema.Type<Parameters>, context?: ToolExecuteContext) => Effect.Effect<Schema.Schema.Type<Success>, ToolFailure>;
export interface ToolModelOutputInput<Parameters, Output> {
readonly id: ToolCallPart["id"];
readonly parameters: Parameters;
readonly output: Output;
}
export type ToolToModelOutput<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> = (input: ToolModelOutputInput<Schema.Schema.Type<Parameters>, Success["Encoded"]>) => ReadonlyArray<Tool.Content>;
/**
* A type-safe LLM tool. Each tool bundles its own description, parameter
* Schema and success Schema. The execute handler is optional: omit it when you
* only want to expose a tool schema to the model and handle tool calls outside
* this package.
*
* Errors must be expressed as `ToolFailure`. Unmapped errors and defects fail
* the stream.
*
* Internally each tool also carries memoized codecs and a precomputed
* `ToolDefinition` so callers do not rebuild them per invocation.
*/
export interface Definition<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> {
readonly description: string;
readonly parameters: Parameters;
readonly success: Success;
readonly execute?: ToolExecute<Parameters, Success>;
readonly toModelOutput?: ToolToModelOutput<Parameters, Success>;
readonly toStructuredOutput?: (output: Success["Encoded"]) => unknown;
/** @internal */
readonly _decode: (input: unknown) => Effect.Effect<Schema.Schema.Type<Parameters>, Schema.SchemaError>;
/** @internal */
readonly _encode: (value: Schema.Schema.Type<Success>) => Effect.Effect<unknown, Schema.SchemaError>;
/** @internal */
readonly _project: (parameters: Schema.Schema.Type<Parameters>, id: ToolCallPart["id"], output: unknown) => ToolOutputType;
/** @internal */
readonly _legacyResult: boolean;
/** @internal */
readonly _definition: ToolDefinitionClass;
}
export type AnyTool = Definition<any, any>;
export type ExecutableTool<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>> = Definition<Parameters, Success> & {
readonly execute: ToolExecute<Parameters, Success>;
};
export type AnyExecutableTool = ExecutableTool<any, any>;
export type ExecutableTools = Record<string, AnyExecutableTool>;
/**
* Constructs a tool. Two input modes:
*
* 1. **Typed** — pass Effect `parameters` and `success` Schemas; inputs and
* outputs are statically typed and decoded/encoded automatically.
*
* ```ts
* Tool.make({
* description: "Get current weather",
* parameters: Schema.Struct({ city: Schema.String }),
* success: Schema.Struct({ temperature: Schema.Number }),
* execute: ({ city }) => Effect.succeed({ temperature: 22 }),
* })
* ```
*
* 2. **Dynamic** — pass raw JSON Schema as `jsonSchema`. Use this when the
* schema comes from an external source (MCP server, plugin manifest,
* dynamic config) and is not known at compile time. Inputs are typed as
* `unknown`; the handler is responsible for any validation it needs.
*
* ```ts
* Tool.make({
* description: "Look something up",
* jsonSchema: { type: "object", properties: { ... } },
* execute: (params) => Effect.succeed(...),
* })
* ```
*
* In both modes the produced tool flows through `toDefinitions(...)`
* identically.
*/
export declare function make<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>>(config: {
readonly description: string;
readonly parameters: Parameters;
readonly success: Success;
readonly execute: ToolExecute<Parameters, Success>;
readonly toModelOutput?: ToolToModelOutput<Parameters, Success>;
readonly toStructuredOutput?: (output: Success["Encoded"]) => unknown;
}): ExecutableTool<Parameters, Success>;
export declare function make<Parameters extends ToolSchema<any>, Success extends ToolSchema<any>>(config: {
readonly description: string;
readonly parameters: Parameters;
readonly success: Success;
readonly execute?: undefined;
readonly toModelOutput?: ToolToModelOutput<Parameters, Success>;
readonly toStructuredOutput?: (output: Success["Encoded"]) => unknown;
}): Definition<Parameters, Success>;
export declare function make(config: {
readonly description: string;
readonly jsonSchema: JsonSchema.JsonSchema;
readonly outputSchema?: JsonSchema.JsonSchema;
readonly execute: (params: unknown, context?: ToolExecuteContext) => Effect.Effect<unknown, ToolFailure>;
readonly toModelOutput?: (input: ToolModelOutputInput<unknown, unknown>) => ReadonlyArray<Tool.Content>;
readonly toStructuredOutput?: (output: unknown) => unknown;
}): AnyExecutableTool;
export declare function make(config: {
readonly description: string;
readonly jsonSchema: JsonSchema.JsonSchema;
readonly outputSchema?: JsonSchema.JsonSchema;
readonly execute?: undefined;
readonly toModelOutput?: (input: ToolModelOutputInput<unknown, unknown>) => ReadonlyArray<Tool.Content>;
readonly toStructuredOutput?: (output: unknown) => unknown;
}): AnyTool;
/**
* A record of named tools. The record key becomes the tool name on the wire.
*/
export type Tools = Record<string, AnyTool>;
/**
* Convert a tools record into the `ToolDefinition[]` shape that
* `LLMRequest.tools` expects.
*
* Tool names come from the record keys, so the per-tool cached
* `_definition` is rebuilt with the correct name here. The JSON Schema body
* is reused.
*/
export declare const toDefinitions: (tools: Tools) => ReadonlyArray<ToolDefinitionClass>;
export { ToolFailure };
export * as Tool from "./tool.js";
import { Effect, JsonSchema, Schema } from "effect";
import { Tool } from "@opencode-ai/schema/tool";
import { ToolDefinition, ToolFailure, ToolOutput } from "./schema/index.js";
export function make(config) {
if ("jsonSchema" in config) {
return {
description: config.description,
parameters: Schema.Unknown,
success: Schema.Unknown,
execute: config.execute,
toModelOutput: config.toModelOutput,
toStructuredOutput: config.toStructuredOutput,
_decode: Effect.succeed,
_encode: Effect.succeed,
_project: (parameters, id, output) => project(config.toModelOutput, config.toStructuredOutput, parameters, id, output),
_legacyResult: config.toModelOutput === undefined && config.toStructuredOutput === undefined,
_definition: new ToolDefinition({
name: "",
description: config.description,
inputSchema: config.jsonSchema,
outputSchema: config.outputSchema,
}),
};
}
return {
description: config.description,
parameters: config.parameters,
success: config.success,
execute: config.execute,
toModelOutput: config.toModelOutput,
toStructuredOutput: config.toStructuredOutput,
_decode: Schema.decodeUnknownEffect(config.parameters),
_encode: Schema.encodeEffect(config.success),
_project: (parameters, id, output) => project(config.toModelOutput, config.toStructuredOutput, parameters, id, output),
_legacyResult: false,
_definition: new ToolDefinition({
name: "",
description: config.description,
inputSchema: toJsonSchema(config.parameters),
outputSchema: toJsonSchema(config.success),
}),
};
}
/**
* Convert a tools record into the `ToolDefinition[]` shape that
* `LLMRequest.tools` expects.
*
* Tool names come from the record keys, so the per-tool cached
* `_definition` is rebuilt with the correct name here. The JSON Schema body
* is reused.
*/
export const toDefinitions = (tools) => Object.entries(tools).map(([name, item]) => new ToolDefinition({
name,
description: item._definition.description,
inputSchema: item._definition.inputSchema,
outputSchema: item._definition.outputSchema,
}));
const toJsonSchema = (schema) => {
const document = Schema.toJsonSchemaDocument(schema);
if (Object.keys(document.definitions).length === 0)
return document.schema;
return { ...document.schema, $defs: document.definitions };
};
const project = (toModelOutput, toStructuredOutput, parameters, id, output) => ToolOutput.make(toStructuredOutput?.(output) ?? output, toModelOutput?.({ id, parameters, output }) ?? (typeof output === "string" ? [{ type: "text", text: output }] : []));
export { ToolFailure };
export * as Tool from "./tool.js";
/** Plain-record narrowing. Excludes arrays so JSON object checks don't accept tuples as key/value bags. */
export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
/** Plain-record narrowing. Excludes arrays so JSON object checks don't accept tuples as key/value bags. */
export const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
+40
-9
{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.0.0-dev-17471",
"name": "@opencode-ai/ai",
"version": "0.0.0-bootstrap.0",
"description": "Bootstrap package for @opencode-ai/ai",
"type": "module",
"license": "MIT",
"exports": "./index.js",
"scripts": {
"setup:recording-env": "bun run script/setup-recording-env.ts",
"test": "bun test --timeout 30000 --only-failures",
"typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.types.json",
"build": "tsc -p tsconfig.build.json"
},
"files": [
"index.js",
"README.md"
"dist"
],
"publishConfig": {
"access": "public",
"provenance": false
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./testing": {
"import": "./dist/testing.js",
"types": "./dist/testing.d.ts"
},
"./*": {
"import": "./dist/*.js",
"types": "./dist/*.d.ts"
}
},
"devDependencies": {
"@clack/prompts": "1.0.0-alpha.1",
"@effect/platform-node": "4.0.0-beta.101",
"@opencode-ai/http-recorder": "0.0.0-dev-17471",
"@tsconfig/bun": "1.0.9",
"@types/bun": "1.3.13",
"@typescript/native-preview": "7.0.0-dev.20251207.1",
"typescript": "5.8.2"
},
"dependencies": {
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
"@opencode-ai/schema": "0.0.0-dev-17471",
"aws4fetch": "1.0.20",
"effect": "4.0.0-beta.101",
"google-auth-library": "10.5.0"
}
}
}
+390
-1
# @opencode-ai/ai
Bootstrap release. Use a current `next` or `latest` version for the real package.
Schema-first AI primitives for opencode. Provider quirks live in adapters, not in calling code.
```ts
import { Effect, Layer } from "effect"
import { LLM, LLMClient } from "@opencode-ai/ai"
import { RequestExecutor } from "@opencode-ai/ai/route"
import { OpenAI } from "@opencode-ai/ai/providers"
const model = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
const request = LLM.request({
model,
system: "You are concise.",
prompt: "Say hello in one short sentence.",
generation: { maxTokens: 40 },
})
const program = Effect.gen(function* () {
const response = yield* LLMClient.generate(request)
console.log(response.text)
})
const llmLayer = LLMClient.layer.pipe(Layer.provide(RequestExecutor.fetchLayer))
await Effect.runPromise(program.pipe(Effect.provide(llmLayer)))
```
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
## Image generation
Use `Image.generate` with an image model for direct asset generation:
```ts
import { Image, ImageInput } from "@opencode-ai/ai"
import { OpenAI } from "@opencode-ai/ai/providers"
const program = Effect.gen(function* () {
const response = yield* Image.generate({
model: OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).image("gpt-image-2"),
prompt: "A robot tending a rooftop garden",
options: {
n: 2,
size: "1024x1024",
quality: "high", // inferred from the OpenAI image model
outputFormat: "webp",
future_option: true, // unknown native options pass through unchanged
},
})
return response.images // GeneratedImage[] with owned bytes or a provider URL
})
```
Pass ordered image inputs to the same method for editing, composition, or image-conditioned generation:
```ts
const response =
yield *
Image.generate({
model,
prompt: "Combine these product photos into one studio scene",
images: [
ImageInput.bytes(firstBytes, "image/png"),
ImageInput.url("https://example.com/second.webp"),
ImageInput.file("file_123"),
],
options,
http,
})
```
`ImageInput.fileUri(uri, mediaType)` represents provider file URIs such as Gemini Files. Raw strings are not
accepted as image inputs, avoiding ambiguity between base64, URLs, and provider IDs. Empty or omitted `images`
uses text-to-image generation; a non-empty array selects the provider's edit behavior without enforcing provider
image-count limits locally. `images` is the only common image-editing field. OpenAI uses multipart for byte/data-URL
edits and its JSON reference body for URL or file-ID edits. Its provider-specific `options.mask` accepts an
`ImageInput` for inpainting:
```ts
yield *
Image.generate({
model: OpenAI.configure({ apiKey }).image("gpt-image-2"),
prompt,
images: [ImageInput.bytes(sourceBytes, "image/png")],
options: { mask: ImageInput.bytes(maskBytes, "image/png") },
})
```
The OpenAI adapter extracts this helper value into the edit request's native `mask` field rather than passing the
tagged `ImageInput` object through as an ordinary option. On multipart requests, `http.body` can override option
fields but not structural `model`, `prompt`, `image[]`, or `mask` fields, and the transport owns the multipart
`Content-Type` boundary. For JSON requests, `http.body` remains the final raw-native overlay. Gemini does not fetch
public HTTP URLs, and hosted Z.ai image generation does not accept image inputs. These cases fail with
`InvalidRequest` before network I/O.
Provider-native image options belong to each request. Raw `http.body` fields have final precedence over them:
```ts
const model = OpenAI.configure({ apiKey }).image("gpt-image-2")
yield *
Image.generate({
model,
prompt,
options: { quality: "medium" },
http,
})
```
xAI image models use the same request API with xAI-native controls:
```ts
yield *
Image.generate({
model: XAI.configure({ apiKey }).image("any-model-id"),
prompt,
options: {
n: 2,
aspectRatio: "16:9",
resolution: "1k",
responseFormat: "b64_json",
future_option: true,
},
http,
})
```
Google's current Gemini image models use the same direct API:
```ts
import { Google } from "@opencode-ai/ai/providers"
const googleProgram = Effect.gen(function* () {
const response = yield* Image.generate({
model: Google.configure({ apiKey }).image("any-model-id"),
prompt: "A robot tending a rooftop garden",
options: {
aspectRatio: "16:9",
imageSize: "2K",
seed: 42,
thinkingLevel: "HIGH",
includeThoughts: true,
futureOption: true,
},
http,
})
return response.images
})
```
Google image options are request-scoped and inferred from the selected model. Known fields autocomplete while
future string values and arbitrary native Gemini `generationConfig` fields remain available. Native fields override
their mapped aliases, and `http.body` is the final deep overlay. The selected model ID is sent to Gemini
`generateContent` without a local allowlist.
Z.ai image models infer open Z.ai-native options from the selected model:
```ts
yield *
Image.generate({
model: ZAI.configure({ apiKey }).image("any-model-id"),
prompt,
options: {
quality: "hd",
userID: "user-123",
future_option: true,
},
http,
})
```
Z.ai does not include trustworthy MIME metadata for output URLs, so generated images use
`application/octet-stream`. Output URLs expire after 30 days; download and persist them promptly if they must
remain available.
Conversational image generation remains part of the LLM interaction. OpenAI Responses exposes it through its hosted image tool:
```ts
const program = Effect.gen(function* () {
const response = yield* LLM.generate(
LLM.request({
model: OpenAI.configure({ apiKey }).responses("gpt-5"),
prompt: "Design a solarpunk rooftop garden, then show me.",
tools: [OpenAI.imageGeneration({ quality: "high" })],
}),
)
return response.message
})
```
The hosted result is represented as a provider-executed tool call and tool result. Its image is a `file` content item with a data URI, so retaining `response.message` preserves the generated image for continuation.
## Public API
- **`LLM.request({...})`** — build a provider-neutral `LLMRequest`. Accepts ergonomic inputs (`system: string`, `prompt: string`) that normalize into the canonical Schema classes.
- **`LLM.generate` / `LLM.stream`** — re-exported from `LLMClient` for one-import use.
- **`Message.user(...)` / `Message.assistant(...)` / `Message.tool(...)`** — message constructors from the canonical schema model.
- **`LanguageModel.make(...)` / `ToolCallPart.make(...)` / `ToolResultPart.make(...)` / `ToolDefinition.make(...)`** — model and tool-related constructors from the canonical schema model.
- **`LLMEvent.is.*`** — typed guards (`is.textDelta`, `is.toolCall`, `is.finish`, …) for filtering streams.
- **`Image.generate({...})`** — generate images through a provider-neutral image request and response model.
- **`ImageClient`** — Effect service and layer for image execution, parallel to `LLMClient`.
## Testing
Use the deterministic test client from `@opencode-ai/ai/testing` to script provider-neutral responses and inspect
the requests sent by code under test:
```ts
import { Effect } from "effect"
import { TestLLM } from "@opencode-ai/ai/testing"
const testLLM = TestLLM.layer({
fallback: TestLLM.text("Hello from the test model", "text-1"),
})
// TestLLM.clientLayer provides LLMClient.Service and consumes TestLLM.Service.
const programWithTestClient = Effect.gen(function* () {
const result = yield* program
const test = yield* TestLLM.Service
console.log(test.requests)
return result
}).pipe(Effect.provide(TestLLM.clientLayer), Effect.provide(testLLM))
```
`TestLLM.push(...)` scripts one-shot responses, `TestLLM.always(...)` changes the fallback, and
`TestLLM.wait(...)` lets concurrent tests wait until a request has arrived. Every received canonical request is
available on the yielded `TestLLM.Service`.
## Caching
Prompt caching is **on by default**. Every `LLMRequest` resolves to `cache: "auto"` unless the caller opts out with `cache: "none"`. Each protocol translates `CacheHint`s to its wire format (`cache_control` on Anthropic, `cachePoint` on Bedrock; OpenAI and Gemini do implicit caching server-side and don't need inline markers — auto is a no-op there).
### Auto placement
`"auto"` places up to four breakpoints — the last tool definition, the first system part, the last system part when distinct, and the final message boundary. These expose successively larger reusable prefixes for tools, the base agent, project instructions, and the active conversation. The rolling final-message boundary is the load-bearing detail in tool loops: it advances on every request so the previous cache entry stays within Anthropic's 20-block lookback.
Tools precede every system and conversation block in the provider prefix, so tool definitions must remain byte-stable and deterministically ordered for downstream breakpoints to remain reusable.
The math justifies the default: Anthropic's 5-minute cache write is 1.25× base, read is 0.1×, so a single reuse within 5 minutes already wins. One-shot completions below the per-model minimum-cacheable-token threshold silently no-op on the wire, so the worst case is harmless.
### Opting out
```ts
LLM.request({
model,
system,
prompt: "one-off question",
cache: "none",
})
```
### Granular policy
```ts
cache: {
tools?: boolean,
system?: boolean,
messages?: "latest-user-message" | "latest-assistant" | { tail: number },
ttlSeconds?: number, // ≥ 3600 → 1h on Anthropic/Bedrock; else 5m
}
```
### Manual hints
Inline `CacheHint` on any text / system / tool / tool-result part overrides automatic placement. The auto policy preserves manual hints, counts them against Anthropic and Bedrock's four-breakpoint limit, and only fills the remaining slots.
```ts
LLM.request({
model,
system: [
{ type: "text", text: "stable system prompt", cache: { type: "ephemeral" } },
],
...
})
```
### Provider behavior table
| Protocol | `cache: "auto"` |
| ----------------------- | ------------------------------------------------------------------------- |
| Anthropic Messages | emits up to 4 `cache_control` markers (4-breakpoint cap enforced) |
| Bedrock Converse | emits up to 4 `cachePoint` blocks (4-breakpoint cap enforced) |
| OpenAI Chat / Responses | no-op (implicit caching above 1024 tokens) |
| Gemini | no-op (implicit caching on 2.5+; explicit `CachedContent` is out-of-band) |
Normalized cache usage is read back into `response.usage.cacheReadInputTokens` and `cacheWriteInputTokens` across every provider.
## Providers
Provider facades configure endpoint/auth/deployment details first, then expose model selectors that take only a model or deployment id. The selected model carries the executable route value used at runtime.
```ts
import { OpenAI, CloudflareAIGateway } from "@opencode-ai/ai/providers"
const openai = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
const gateway = CloudflareAIGateway.configure({
accountId: process.env.CLOUDFLARE_ACCOUNT_ID,
gatewayApiKey: process.env.CLOUDFLARE_API_TOKEN,
}).model("workers-ai/@cf/meta/llama-3.1-8b-instruct")
```
Included providers: OpenAI, Anthropic, Google (Gemini), Google Vertex Gemini and Anthropic, Amazon Bedrock, Azure OpenAI, Cloudflare AI Gateway, Cloudflare Workers AI, GitHub Copilot, OpenRouter, xAI, Z.ai, plus generic OpenAI-compatible Chat and Responses entrypoints and an Anthropic Messages-compatible entrypoint.
### Package-like entrypoints
Native catalog integrations load provider behavior through package-like entrypoints. These are export paths from the same `@opencode-ai/ai` npm package, not independently published packages. Each entrypoint exports the same `model(modelID, settings)` contract, and `settings` contains serializable provider configuration plus common `headers`, `body`, and `limits` overlays.
```ts
import { model } from "@opencode-ai/ai/providers/openai/responses"
const selected = model("gpt-5", {
apiKey: process.env.OPENAI_API_KEY,
headers: { "x-application": "opencode" },
limits: { context: 200_000, output: 64_000 },
})
```
OpenAI Chat and OpenAI Responses are separate semantic entrypoints:
- `@opencode-ai/ai/providers/openai/chat`
- `@opencode-ai/ai/providers/openai/responses`
- `@opencode-ai/ai/providers/openai-compatible/responses`
- `@opencode-ai/ai/providers/anthropic-compatible`
- `@opencode-ai/ai/providers/google-vertex/gemini`
- `@opencode-ai/ai/providers/google-vertex/chat`
- `@opencode-ai/ai/providers/google-vertex/responses`
- `@opencode-ai/ai/providers/google-vertex/messages`
OpenAI Responses has one semantic route and uses HTTP by default. Advanced callers may supply a per-call WebSocket channel executor through `StreamOptions`; transport policy does not change provider settings, model identity, or route identity. The provider-neutral Open Responses implementation owns the reusable WebSocket request and event contract, while each provider opts in with its own handshake and connection policy. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; the Responses adapter at `providers/openai-compatible/responses` uses the provider-neutral Open Responses protocol. OpenAI Responses extends that baseline with OpenAI tools, event variants, metadata, and defaults. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
Vertex Gemini, Vertex Chat, Vertex Responses, and Vertex Messages are separate API entrypoints. All accept `project`, `location`, and an optional `accessToken`; when no explicit token or auth override is supplied they lazily use Google Application Default Credentials. Vertex Gemini instead selects express mode when `apiKey` or `GOOGLE_VERTEX_API_KEY` is present. Vertex Chat targets MaaS models through the OpenAI-compatible Chat Completions endpoint, while Vertex Responses targets Grok models and defaults `store` to `false` as required by Vertex. `providers/google-vertex` remains the default alias for `providers/google-vertex/gemini`.
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/gemini"
model("gemini-3.5-flash", { project: "my-project", location: "global" })
```
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/chat"
model("deepseek-ai/deepseek-v3.2-maas", { project: "my-project", location: "global" })
```
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/responses"
model("xai/grok-4.20-reasoning", { project: "my-project", location: "global" })
```
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/messages"
model("claude-sonnet-4-6", { project: "my-project", location: "global" })
```
Provider facades such as `OpenAI.configure(...).responses(...)` remain the direct application API. Package-like entrypoints are the self-similar loading contract used when a catalog selects behavior by export path.
Other provider exports listed above remain direct facades until they explicitly implement the package-like contract. Exporting a provider facade does not implicitly make it a catalog-loadable provider package.
## Provider options & HTTP overlays
Request options in order of stability:
1. **`generation`** — portable knobs (`maxTokens`, `temperature`, `topP`, `topK`, penalties, seed, stop).
2. **`promptCacheKey`** — stable cache affinity lowered by every protocol that supports it.
3. **`providerOptions: { <provider>: {...} }`** — typed-at-the-facade provider-specific knobs (OpenAI `store`, Anthropic `thinking`, Gemini `thinkingConfig`, OpenRouter routing).
4. **`http: { body, headers, query }`** — last-resort serializable overlays merged into the final HTTP request. Reach for this only when a stable typed path doesn't yet exist.
Route/provider defaults are overridden by request-level values for each axis.
## Routes
Adding a new model or deployment is usually 5-15 lines using `Route.make({ protocol, endpoint, auth, framing, ... })`. The route owns endpoint/auth/framing and the protocol owns body construction plus stream parsing. Transports are reusable IO templates that receive route endpoint/auth at compile time. Capability/catalog metadata lives outside this low-level package; unsupported request shapes fail during protocol lowering. See `AGENTS.md` for the architectural detail.
## Effect
This package is built on Effect. Public methods return `Effect` or `Stream`; provide `LLMClient.layer` for LLM dispatch and `ImageClient.layer` for image dispatch, then import the provider/protocol modules for the routes you use. The example at `example/tutorial.ts` is a runnable walkthrough.
## See also
- `AGENTS.md` — architecture, route construction, contributor guide
- `STATUS.md` — native provider parity status and AI SDK migration gaps
- `example/tutorial.ts` — runnable end-to-end walkthrough
- `test/provider/*.test.ts` — fixture-first protocol tests; `*.recorded.test.ts` files cover live cassettes
-1
export {}