import type { z } from 'zod'; /** * Provider-neutral model abstraction (CLAUDE.md §168). Nothing outside packages/ai imports a * vendor SDK; callers speak in roles and these request shapes. */ export type Role = 'normalize' | 'classify' | 'resolve' | 'research' | 'vision' | 'embed' | 'summarize'; export type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max'; export interface CostContext { connectorId?: string | null; categorySlug?: string | null; userId?: string | null; endpoint?: string | null; metadata?: Record; } export interface ImageInput { /** base64-encoded bytes (no data: prefix) or an https URL */ data?: string; url?: string; mediaType: 'image/jpeg' | 'image/png' | 'image/webp' | 'image/gif'; } export type ContentPart = { type: 'text'; text: string } | { type: 'image'; image: ImageInput }; export interface ChatMessage { role: 'user' | 'assistant'; content: string | ContentPart[]; } export interface ToolDefinition { name: string; description: string; /** JSON Schema (draft 2020-12 subset) for the input object */ inputSchema: Record; } export interface CompletionRequest { system?: string; messages: ChatMessage[]; maxTokens?: number; temperature?: number; effort?: Effort; /** ask for JSON output (best effort; use extract() for schema-validated output) */ json?: boolean; stopSequences?: string[]; cost?: CostContext; } export interface Usage { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; } export interface Completion { text: string; model: string; provider: string; usage: Usage; usdEst: number; stopReason: string | null; refusal: { category: string | null; explanation: string | null } | null; } export type StreamDelta = | { type: 'text'; text: string } | { type: 'thinking'; text: string } | { type: 'tool_call'; id: string; name: string; input: unknown } | { type: 'tool_result'; id: string; name: string; output: unknown; isError: boolean; durationMs: number } | { type: 'usage'; usage: Usage; usdEst: number; model: string } | { type: 'error'; message: string } | { type: 'done'; stopReason: string | null }; export interface ToolAgentRequest extends CompletionRequest { tools: ToolDefinition[]; /** execute a tool call; return JSON-serialisable output (throw to signal an error result) */ execute: (name: string, input: unknown) => Promise; /** max model↔tool round trips (default 8) */ maxIterations?: number; signal?: AbortSignal; } export interface ExtractRequest { schema: z.ZodType; /** instruction for the extraction */ prompt: string; /** raw input: text or content parts (images allowed) */ input: string | ContentPart[]; system?: string; maxTokens?: number; effort?: Effort; cost?: CostContext; } export interface ExtractResult { data: T; /** 0–1: the model's self-reported confidence when the schema has one, else 1 when parsed */ confidence: number; model: string; provider: string; usage: Usage; usdEst: number; } export interface EmbedRequest { texts: string[]; cost?: CostContext; } export interface EmbedResult { vectors: number[][]; model: string; provider: string; dimensions: number; usdEst: number; } export interface ModelProvider { readonly id: string; /** which roles this provider can serve given its configuration */ supports(role: Role): boolean; complete(role: Role, req: CompletionRequest): Promise; stream(role: Role, req: CompletionRequest): AsyncIterable; /** tool-use agent loop with streaming events */ runTools(role: Role, req: ToolAgentRequest): AsyncIterable; extract(role: Role, req: ExtractRequest): Promise>; vision(req: ExtractRequest): Promise>; embed(req: EmbedRequest): Promise; modelFor(role: Role): string; } export class AiNotConfiguredError extends Error { readonly code = 'ai_not_configured'; constructor(detail = 'AI provider not configured') { super(detail); this.name = 'AiNotConfiguredError'; } } export class AiRefusalError extends Error { readonly code = 'ai_refusal'; constructor(public readonly category: string | null, explanation: string | null) { super(explanation ?? 'The model declined this request'); this.name = 'AiRefusalError'; } }