源码
packages/ai/src/types.ts
import type { TSchema } from "typebox";
import type { AssistantMessageEventStream } from "./utils/event-stream.ts";
export type { AssistantMessageEventStream } from "./utils/event-stream.ts";
export type ProviderApi = "anthropic-messages" | "openai-completions" | "openai-responses";
export type Api = ProviderApi | (string & {});
export type CacheRetention = "none" | "short" | "long";
export type ProviderEnv = Record<string, string>;
export type ProviderHeaders = Record<string, string | null>;
export interface ThinkingBudgets {
minimal?: number;
low?: number;
medium?: number;
high?: number;
}
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | JsonObject;
export type JsonObject = { [key: string]: JsonValue };
export type ThinkingLevel = "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | (string & {});
export interface ReasoningConfig {
levels: string[];
map: string;
}
export interface OpenAICompletionsCompat {
thinkingFormat?: "deepseek" | "zai";
structuredOutput?: "prompt" | "json_object" | "json_schema";
maxTokensField?: "max_tokens" | "max_completion_tokens";
supportsDeveloperRole?: boolean;
supportsReasoningEffort?: boolean;
supportsUsageInStreaming?: boolean;
requiresReasoningContentOnAssistantMessages?: boolean;
}
export const openAICompletionsCompatKeys = [
"thinkingFormat",
"structuredOutput",
"maxTokensField",
"supportsDeveloperRole",
"supportsReasoningEffort",
"supportsUsageInStreaming",
"requiresReasoningContentOnAssistantMessages",
] as const satisfies readonly (keyof OpenAICompletionsCompat)[];
/** Compatibility settings for Anthropic Messages-compatible APIs. */
export interface AnthropicMessagesCompat {
/**
* Whether the provider accepts per-tool `eager_input_streaming`.
* When false, the Anthropic provider omits `tools[].eager_input_streaming`
* and sends the legacy `fine-grained-tool-streaming-2025-05-14` beta header
* for tool-enabled requests.
* Default: true.
*/
supportsEagerToolInputStreaming?: boolean;
/** Whether the provider supports Anthropic long cache retention (`cache_control.ttl: "1h"`). Default: true. */
supportsLongCacheRetention?: boolean;
/**
* Whether to send the `x-session-affinity` header from `options.sessionId`
* when caching is enabled. Required for providers like Fireworks that use
* session affinity for prompt cache routing (requests to the same replica
* maximize cache hits).
* Default: false.
*/
sendSessionAffinityHeaders?: boolean;
/** Session-affinity format. `"openrouter"` sends `x-session-id`; when unset, sends `x-session-affinity`. */
sessionAffinityFormat?: "openrouter";
/**
* Whether the provider supports Anthropic-style `cache_control` markers on
* tool definitions. When false, `cache_control` is omitted from tool params.
* Some Anthropic-compatible providers (e.g., Fireworks) do not support this
* field on tools and may reject or ignore it.
* Default: true.
*/
supportsCacheControlOnTools?: boolean;
/**
* Whether the model accepts the Anthropic `temperature` request field.
* Claude Opus 4.7+ rejects non-default temperature values.
* Default: true.
*/
supportsTemperature?: boolean;
/**
* Whether to force adaptive thinking (`thinking.type: "adaptive"` plus
* `output_config.effort`) regardless of the model id. Built-in models that
* require adaptive thinking set this in generated metadata. Custom
* Anthropic-compatible providers can set this to `true` for any model whose
* upstream requires the adaptive format. Set to `false` to
* opt out on overridden built-in models.
* Default: false.
*/
forceAdaptiveThinking?: boolean;
/** Whether to replay empty thinking signatures as `signature: ""` instead of converting thinking to text. Default: false. */
allowEmptySignature?: boolean;
/** Whether the provider supports Anthropic strict tool schemas. Default: false; generated Anthropic models enable it explicitly. */
supportsStrictTools?: boolean;
/** Whether the exact model transport supports effort-only system messages and thinking binding controls. Default: false. */
supportsMidConvoEffort?: boolean;
/** Whether the exact model accepts system-role messages inside the conversation. When false, updates remain in place as user messages. Default: false. */
supportsMidConvoSystemMessages?: boolean;
/** Whether the exact model accepts mid-conversation `tool_addition` and `tool_removal` blocks. Requires `supportsMidConvoSystemMessages`. Default: false. */
supportsMidConvoToolChanges?: boolean;
/**
* Models Anthropic accepts in `fallbacks` for server-side refusal fallback,
* with local pricing metadata for returned fallback responses. When absent or
* empty, callers must omit `fallbacks`; Anthropic rejects the field for models
* with no permitted fallback targets.
*/
allowedFallbackModels?: AnthropicAllowedFallbackModel[];
}
export interface AnthropicAllowedFallbackModel {
provider: string;
model: string;
cost: Model["cost"];
}
export type ModelCompat = OpenAICompletionsCompat & AnthropicMessagesCompat;
export const anthropicMessagesCompatKeys = [
"supportsEagerToolInputStreaming",
"supportsLongCacheRetention",
"sendSessionAffinityHeaders",
"sessionAffinityFormat",
"supportsCacheControlOnTools",
"supportsTemperature",
"forceAdaptiveThinking",
"allowEmptySignature",
"supportsStrictTools",
"supportsMidConvoEffort",
"supportsMidConvoSystemMessages",
"supportsMidConvoToolChanges",
"allowedFallbackModels",
] as const satisfies readonly (keyof AnthropicMessagesCompat)[];
export interface Model<TApi extends Api = Api> {
id: string;
name: string;
api: TApi;
provider: string;
baseUrl: string;
auth?: "chatgpt";
input: ("text" | "image")[];
reasoning: boolean;
cost: {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
tiers?: {
inputTokensAbove: number;
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
}[];
};
contextWindow: number;
maxTokens: number;
headers?: Record<string, string>;
compat?: ModelCompat;
thinkingLevelMap?: Partial<Record<"off" | ThinkingLevel, string | JsonObject | null>>;
reasoningConfig?: ReasoningConfig;
defaultThinkingLevel?: "off" | ThinkingLevel;
samplingParams?: Record<string, unknown>;
}
export interface TextContent {
type: "text";
text: string;
textSignature?: string;
phase?: "commentary" | "partial_answer" | "final_answer";
}
export interface ThinkingContent {
type: "thinking";
thinking: string;
thinkingSignature?: string;
redacted?: boolean;
}
export interface ImageContent {
name?: string;
type: "image";
data: string;
mimeType: string;
}
export interface ToolCall {
type: "toolCall";
id: string;
name: string;
arguments: JsonObject;
thoughtSignature?: string;
namespace?: string;
}
export interface Tool<TParameters extends TSchema = TSchema> {
name: string;
description: string;
parameters: TParameters;
constrainedSampling?: { type: "json_schema"; strict: "prefer" | "require" };
}
export interface CacheControl {
type: "ephemeral";
}
export interface SystemMessage {
cacheControl?: CacheControl;
role: "system";
content: string | TextContent[];
timestamp: number;
/** Named context patches: a string replaces the entire block; null withdraws every earlier version. */
sections?: Record<string, string | null>;
toolsAdded?: Tool[];
toolsRemoved?: { name: string }[];
}
export interface ImageAttachment {
id: string;
name: string;
mimeType: string;
width: number;
height: number;
size: number;
warning?: string;
}
export interface FileAttachment {
id: string;
name: string;
mimeType: string;
size: number;
path: string;
}
export type Attachment = ImageAttachment | FileAttachment;
export interface UserGuide {
text: string;
fileReferences: string[];
attachments: Attachment[];
}
export interface UserMessage {
/** Stable identity for queued input; never sent as provider message content. */
inputId?: string;
/** Display-only provenance for a real user input accepted inside an existing run. */
userGuide?: UserGuide;
cacheControl?: CacheControl;
synthetic?: boolean;
role: "user";
content: string | (TextContent | ImageContent)[];
timestamp: number;
}
export interface Usage {
input: number;
/** Total generated tokens, including reasoning; `reasoning` is a breakdown, not an addition. */
output: number;
cacheRead: number;
cacheWrite: number;
cacheWrite1h?: number;
reasoning?: number;
totalTokens: number;
cost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };
}
export type StopReason = "stop" | "length" | "toolUse" | "error" | "aborted";
export interface ContextBreakdownItem {
source: "messages" | "system_tools" | "system_prompt" | "skills" | "other";
chars: number;
}
export interface AssistantMessage {
cacheControl?: CacheControl;
role: "assistant";
content: (TextContent | ThinkingContent | ToolCall)[];
api: Api;
provider: string;
model: string;
responseModel?: string;
responseId?: string;
providerThinkingLevel?: string;
thinkingLevel?: "off" | ThinkingLevel;
usage: Usage;
usageAvailable?: boolean;
cacheUsageAvailable?: boolean;
contextBreakdown?: ContextBreakdownItem[];
stopReason: StopReason;
errorMessage?: string;
errorDetails?: ModelFailure;
rawStopReason?: string;
diagnostics?: import("./utils/diagnostics.ts").AssistantMessageDiagnostic[];
endTurn?: boolean;
timestamp: number;
}
export interface ModelFailure {
/** Undefined when structured facts cannot classify the failure. */
retryable?: boolean;
code?: string;
idleMs?: number;
timeoutMs?: number;
status?: number;
retryAfterMs?: number;
}
export interface ModelRetryStatus {
/** Defaults to automatic recovery when omitted by a provider. */
origin?: "manual" | "auto";
attempt: number;
maxRetries: number;
retryDelayMs: number;
errorStatus: number | null;
}
export interface ModelWaitStatus {
phase: "first_response" | "stream";
startedAt: number;
}
export interface ToolResultMessage {
cacheControl?: CacheControl;
role: "toolResult";
toolCallId: string;
toolName: string;
content: (TextContent | ImageContent)[];
details?: JsonValue;
isError: boolean;
timestamp: number;
}
export type Message = SystemMessage | UserMessage | AssistantMessage | ToolResultMessage;
export interface Context {
systemPrompt?: string;
messages: Message[];
tools?: Tool[];
}
declare const transcriptBrand: unique symbol;
export type TranscriptContext = { messages: Message[]; readonly [transcriptBrand]: true };
/** A provider-reported token count paired with its own content interval, never wall-clock elapsed time. */
export interface ModelPerformanceSample {
requestMs?: number;
ttftMs?: number;
outputTokens?: number;
generationMs?: number;
}
export interface StreamOptions {
/** Request-local media omission statistics; omitted when the request fits. Never sent to the provider. */
onMediaProjection?: (stats: import("./utils/media-policy.ts").MediaBudgetStats) => void;
/** Move the tail cache breakpoint before the final request-local prompt on protocols with explicit boundaries. */
skipCacheWrite?: boolean;
/** Transient measurements from actual provider attempts; an empty sample starts a fresh attempt. */
onPerformance?: (sample: ModelPerformanceSample) => void;
/** Best available provider retention. Legacy OpenAI automatic caching cannot be disabled with "none". */
cacheRetention?: CacheRetention;
metadata?: Record<string, unknown>;
env?: ProviderEnv;
signal?: AbortSignal;
sessionId?: string;
apiKey?: string;
/** Optional conversation-owned Responses connection; dispose it with the conversation. */
responsesSession?: import("./api/responses-session.ts").ResponsesSession;
/** Prepare request state with generate:false; never performs model generation or HTTP fallback. */
responsesWarmup?: boolean;
fetch?: typeof globalThis.fetch;
headers?: Record<string, string | null>;
temperature?: number;
maxTokens?: number;
timeoutMs?: number;
/** First meaningful model event deadline; defaults to 120s, <= 0 disables it. */
streamFirstResponseTimeoutMs?: number;
/** Model-event idle deadline after output starts; defaults to 300s, <= 0 disables it. */
streamIdleTimeoutMs?: number;
/** Absolute recovery deadline, independent of model-event activity. */
streamDeadlineMs?: number;
/** Retry this request without cancelling the enclosing agent turn. */
retrySignal?: AbortSignal;
/** Transient waiting state; emitted only after 15s without a meaningful model event. */
onModelWait?: (status: ModelWaitStatus | null) => void;
maxRetries?: number;
maxRetryDelayMs?: number;
onRetry?: (status: ModelRetryStatus | null) => void;
samplingParams?: Record<string, unknown>;
onPayload?: (payload: unknown, model: Model) => unknown | undefined | Promise<unknown | undefined>;
onResponse?: (
response: { status: number; headers: Record<string, string> },
model: Model,
) => void | Promise<void>;
}
export interface SimpleStreamOptions extends StreamOptions {
reasoning?: "off" | ThinkingLevel;
thinkingBudgets?: ThinkingBudgets;
toolChoice?: "auto" | "none";
}
export type StreamFunction<TApi extends Api = Api, TOptions extends StreamOptions = StreamOptions> = (
model: Model<TApi>,
context: TranscriptContext,
options?: TOptions,
) => AssistantMessageEventStream;
export type AssistantMessageEvent =
| { type: "start"; partial: AssistantMessage }
| { type: "reset"; partial: AssistantMessage }
| {
type: "text_start" | "thinking_start" | "toolcall_start";
contentIndex: number;
partial: AssistantMessage;
}
| {
type: "text_delta" | "thinking_delta" | "toolcall_delta";
contentIndex: number;
delta: string;
partial: AssistantMessage;
}
| { type: "text_end" | "thinking_end"; contentIndex: number; content: string; partial: AssistantMessage }
| { type: "toolcall_end"; contentIndex: number; toolCall: ToolCall; partial: AssistantMessage }
| { type: "done"; reason: "stop" | "length" | "toolUse"; message: AssistantMessage }
| { type: "error"; reason: "error" | "aborted"; error: AssistantMessage };