UNPKG

ai-functions

Version:

Core AI primitives for building intelligent applications

634 lines 21.6 kB
/** * Core types for AI functions */ import type { SandboxEnv } from 'ai-evaluate'; /** * Host Workers environment for the ai-evaluate sandbox. * * Re-exported from ai-evaluate so consumers can type the optional `env` * argument threaded through `DefinedFunction.call` / `generateAndRunCode` * without importing ai-evaluate directly. */ export type { SandboxEnv } from 'ai-evaluate'; /** * Symbol used to identify pending human function results */ export declare const PENDING_HUMAN_RESULT_SYMBOL: unique symbol; /** * Human interaction channels * * - chat: Real-time messaging (WebSocket, in-app) * - email: Async email communication * - phone: Voice calls * - sms: SMS text messages * - workspace: Team collaboration platforms (Slack, Teams, Discord) * - web: Web-based interface (browser, web app) */ export type HumanChannel = 'chat' | 'email' | 'phone' | 'sms' | 'workspace' | 'web'; /** * Pending human function result returned when human input is not yet available. * * This is returned by human functions that need actual human input but are running * in a placeholder mode (e.g., during testing or when channel integrations are not configured). * * Use `isPendingHumanResult()` to check if a result is pending before using it. * * @example * ```ts * const result = await approveRefund({ amount: 500, reason: 'Duplicate charge' }) * * if (isPendingHumanResult(result)) { * console.log('Waiting for human approval via:', result.channel) * // Handle pending state - queue for later, show UI, etc. * } else { * // result is the actual TOutput type * console.log('Approved:', result.approved) * } * ``` */ export interface HumanFunctionPending<TExpected = unknown> { /** Symbol marker for type identification */ readonly [PENDING_HUMAN_RESULT_SYMBOL]: true; /** Indicates this is a pending placeholder, not actual human response */ readonly _pending: true; /** The channel where human input was requested */ readonly channel: HumanChannel; /** Generated UI/content artifacts for the channel */ readonly artifacts: unknown; /** The expected response type schema */ readonly expectedResponseType: TExpected; } /** * Type guard to check if a result is a pending human function result. * * Use this to safely handle cases where human input is not yet available. * * @param value - The value to check * @returns True if the value is a HumanFunctionPending object * * @example * ```ts * const result = await reviewDocument({ docId: '123' }) * * if (isPendingHumanResult(result)) { * // result is HumanFunctionPending - human input needed * console.warn('Human review pending:', result.channel) * return { status: 'pending', channel: result.channel } * } * * // result is ReviewResult - actual human response * return { status: 'reviewed', approved: result.approved } * ``` */ export declare function isPendingHumanResult<T>(value: T | HumanFunctionPending): value is HumanFunctionPending; /** * A function definition that can be called by AI * * @typeParam TOutput - The return type of the function handler * @typeParam TInput - The input type accepted by the function handler */ export interface AIFunctionDefinition<TOutput = unknown, TInput = unknown> { /** Unique name for the function */ name: string; /** Human-readable description for the AI */ description: string; /** JSON Schema for the input parameters */ parameters: JSONSchema; /** The implementation */ handler: (input: TInput) => TOutput | Promise<TOutput>; } /** * JSON Schema type (simplified) */ export interface JSONSchema { type?: string; properties?: Record<string, JSONSchema>; items?: JSONSchema; required?: string[]; description?: string; enum?: unknown[]; default?: unknown; [key: string]: unknown; } /** * Options for AI generation */ export interface AIGenerateOptions { /** The prompt or input */ prompt?: string; /** System message */ system?: string; /** Model to use */ model?: string; /** Temperature (0-1) */ temperature?: number; /** Maximum tokens to generate */ maxTokens?: number; /** Stop sequences */ stop?: string[]; /** Structured output schema */ schema?: JSONSchema; /** Available functions */ functions?: AIFunctionDefinition[]; } /** * Result of AI generation */ export interface AIGenerateResult { /** Generated text */ text: string; /** Structured output (if schema was provided) */ object?: unknown; /** Function calls (if functions were provided) */ functionCalls?: AIFunctionCall[]; /** Token usage */ usage?: { promptTokens: number; completionTokens: number; totalTokens: number; }; } /** * A function call made by the AI */ export interface AIFunctionCall { /** Name of the function to call */ name: string; /** Arguments as JSON */ arguments: unknown; } /** * AI client interface - all methods return Promise for pipelining */ export interface AIClient { /** Generate text or structured output */ generate(options: AIGenerateOptions): Promise<AIGenerateResult>; /** Execute an action */ do(action: string, context?: unknown): Promise<unknown>; /** Type checking / validation */ is(value: unknown, type: string | JSONSchema): Promise<boolean>; /** Generate code */ code(prompt: string, language?: string): Promise<string>; /** Make a decision */ decide<T extends string>(options: T[], context?: string): Promise<T>; /** Generate a diagram */ diagram(description: string, format?: 'mermaid' | 'svg' | 'ascii'): Promise<string>; /** Generate an image */ image(prompt: string, options?: ImageOptions): Promise<ImageResult>; /** Generate a video */ video(prompt: string, options?: VideoOptions): Promise<VideoResult>; /** Write/generate text content */ write(prompt: string, options?: WriteOptions): Promise<string>; /** Generate a list of items with names and descriptions */ list(prompt: string): Promise<ListResult>; /** Generate multiple named lists of items */ lists(prompt: string): Promise<ListsResult>; } export interface ImageOptions { width?: number; height?: number; style?: string; model?: string; } export interface ImageResult { url: string; base64?: string; width: number; height: number; } export interface VideoOptions { duration?: number; width?: number; height?: number; fps?: number; model?: string; } export interface VideoResult { url: string; duration: number; width: number; height: number; } export interface WriteOptions { tone?: string; length?: 'short' | 'medium' | 'long'; format?: 'text' | 'markdown' | 'html'; } /** * Type for functions that support both regular calls and tagged template literals */ export type TemplateFunction<TArgs extends unknown[], TReturn> = ((prompt: string, ...args: TArgs) => TReturn) & ((strings: TemplateStringsArray, ...values: unknown[]) => TReturn); /** * A single item in a list */ export interface ListItem { name: string; description: string; } /** * Result of the list() function */ export interface ListResult { items: ListItem[]; } /** * A named list containing items */ export interface NamedList { name: string; items: ListItem[]; } /** * Result of the lists() function */ export interface ListsResult { lists: NamedList[]; } /** * Supported programming languages for code generation */ export type CodeLanguage = 'typescript' | 'javascript' | 'python' | 'go' | 'rust'; /** * Output types for generative functions */ export type GenerativeOutputType = 'string' | 'object' | 'image' | 'video'; /** * Legacy channel type for backwards compatibility * @deprecated Use HumanChannel instead */ export type LegacyHumanChannel = 'slack' | 'email' | 'web' | 'sms' | 'custom'; /** * Schema limitations that apply across providers * These constraints ensure compatibility with OpenAI, Anthropic, and Google */ export interface SchemaLimitations { /** OpenAI requires additionalProperties: false on all objects */ noAdditionalProperties: true; /** OpenAI requires all properties in 'required' - no optional keys */ allPropertiesRequired: true; /** OpenAI doesn't support default values */ noDefaultValues: true; /** No recursive schema definitions (all providers) */ noRecursiveSchemas: true; /** No external $ref URLs (all providers) */ noExternalRefs: true; /** Anthropic doesn't support min/max on numbers */ noNumericBounds: true; /** Anthropic doesn't support minLength/maxLength on strings */ noStringLengthBounds: true; /** Anthropic only supports minItems of 0 or 1 */ limitedArrayMinItems: true; /** Anthropic doesn't support complex types in enums */ simpleEnumsOnly: true; /** Anthropic doesn't support lookahead/lookbehind in regex */ simpleRegexOnly: true; } /** * Base definition shared by all function types * * @typeParam TOutput - The return type schema * @typeParam TInput - The arguments schema */ export interface BaseFunctionDefinition<TOutput = unknown, TInput = unknown> { /** Function name (used as the callable identifier) */ name: string; /** Human-readable description of what this function does */ description?: string; /** Arguments schema - SimpleSchema or Zod schema */ args: TInput; /** Return type schema - SimpleSchema or Zod schema (optional) */ returnType?: TOutput; } /** * Code function - a **deterministic** handler. * * `Code` is the deterministic kind: a fetch/transform/rule handler that runs * the **same logic every time** with no model in the execution path. When * called, it invokes the supplied {@link CodeFunctionDefinition.handler} * (or evaluates the inline {@link CodeFunctionDefinition.code} body) and * returns the result directly — no LLM is consulted at call time. * * This is the contract consumers bind to (e.g. ADR-0033's * "Code = deterministic"). If you want a model to *author* code, that is a * generation task: use {@link generateCode} / the `generate('code', …)` * primitive, or define a `Generative` function whose output is source text. * See the migration note in the package README. * * Exactly one of `handler` or `code` should be supplied. `handler` is the * canonical form (a native function reference); `code` is an inline source * body, deterministically compiled by the consumer's runtime — never sent to * a model. * * @example * ```ts * defineFunction({ * type: 'code', * name: 'calculateTax', * args: { * amount: 'The amount to calculate tax on (number)', * rate: 'Tax rate as decimal (number)', * }, * returnType: 'The calculated tax amount (number)', * handler: ({ amount, rate }) => amount * rate, * }) * ``` * * @typeParam TOutput - The return type schema * @typeParam TInput - The arguments schema */ export interface CodeFunctionDefinition<TOutput = unknown, TInput = unknown> extends BaseFunctionDefinition<TOutput, TInput> { type: 'code'; /** * The deterministic handler invoked on every call. Receives the parsed * `args` and returns (or resolves to) the result. No LLM is involved. * * Either `handler` or {@link CodeFunctionDefinition.code} must be provided; * `handler` takes precedence when both are present. */ handler?: (input: TInput) => TOutput | Promise<TOutput>; /** * Inline source body for the handler, as a string. Deterministically * compiled by the consumer's runtime (it is **not** generated by a model). * Used when a handler cannot be passed by reference (e.g. a definition * loaded from storage). The body receives the `args` in scope and should * `return` the result. * * Either `code` or {@link CodeFunctionDefinition.handler} must be provided. */ code?: string; /** Target programming language of `code` (default: `'typescript'`). Metadata only. */ language?: CodeLanguage; /** Optional human-readable note about what the handler does. Metadata only. */ instructions?: string; } /** * Definition for a code-**authoring** request — the explicit, opt-in path for * having a model *write* code. This is the behavior `type: 'code'` used to * have implicitly; it has been split out so that `Code` can mean * "deterministic handler" without a silent change of meaning. * * This is **not** part of the {@link FunctionDefinition} union and never * executes deterministically — calling it consults a model. Drive it via * {@link generateCode}. * * @typeParam TInput - The arguments schema describing the code to author */ export interface CodeGenerationDefinition<TInput = unknown> { /** Name of the function/query to author */ name: string; /** Human-readable description of what the code should do */ description?: string; /** Arguments/spec schema describing the code to generate */ args: TInput; /** Return type schema the authored code should produce (optional) */ returnType?: unknown; /** Target programming language */ language?: CodeLanguage; /** Additional context or requirements for code generation */ instructions?: string; /** Whether to include vitest tests (default: true) */ includeTests?: boolean; /** Whether to include example usage (default: true) */ includeExamples?: boolean; /** Model to use for authoring (defaults to 'sonnet') */ model?: string; } /** * Code function result with generated artifacts */ export interface CodeFunctionResult { /** The generated implementation code */ code: string; /** Generated vitest test code */ tests?: string; /** Generated example usage code */ examples?: string; /** The language the code was generated in */ language: CodeLanguage; /** JSDoc or equivalent documentation */ documentation: string; } /** * Generative function - uses AI to generate text, objects, or media * * @example * ```ts * defineFunction({ * type: 'generative', * name: 'summarize', * args: { text: 'The text to summarize' }, * output: 'string', * system: 'You are an expert summarizer.', * promptTemplate: 'Summarize the following text:\n\n{{text}}', * }) * ``` * * @typeParam TOutput - The return type schema * @typeParam TInput - The arguments schema */ export interface GenerativeFunctionDefinition<TOutput = unknown, TInput = unknown> extends BaseFunctionDefinition<TOutput, TInput> { type: 'generative'; /** What type of output this function produces */ output: GenerativeOutputType; /** System prompt for the AI */ system?: string; /** Prompt template with {{arg}} placeholders */ promptTemplate?: string; /** Model to use (defaults to 'sonnet') */ model?: string; /** Temperature for generation (0-2) */ temperature?: number; } /** * Generative function result */ export interface GenerativeFunctionResult<T = unknown> { /** Generated text (if output is 'string') */ text?: string; /** Generated object (if output is 'object') */ object?: T; /** Generated image (if output is 'image') */ image?: ImageResult; /** Generated video (if output is 'video') */ video?: VideoResult; } /** * Agentic function - runs in a loop with tools until completion * * @example * ```ts * defineFunction({ * type: 'agentic', * name: 'researchTopic', * args: { topic: 'The topic to research' }, * returnType: { * summary: 'Research summary', * sources: ['List of sources'], * }, * instructions: 'Research the topic thoroughly using available tools.', * tools: [searchTool, fetchTool], * maxIterations: 10, * }) * ``` * * @typeParam TOutput - The return type schema * @typeParam TInput - The arguments schema */ export interface AgenticFunctionDefinition<TOutput = unknown, TInput = unknown> extends BaseFunctionDefinition<TOutput, TInput> { type: 'agentic'; /** Instructions for the agent on how to accomplish the task */ instructions: string; /** Prompt template with {{arg}} placeholders */ promptTemplate?: string; /** Tools available to the agent */ tools?: AIFunctionDefinition[]; /** Maximum number of iterations before stopping (default: 10) */ maxIterations?: number; /** Model to use for the agent (defaults to 'sonnet') */ model?: string; /** Whether to stream intermediate results */ stream?: boolean; } /** * Agentic function execution state */ export interface AgenticExecutionState { /** Current iteration number */ iteration: number; /** Tool calls made so far */ toolCalls: AIFunctionCall[]; /** Intermediate results */ intermediateResults: unknown[]; /** Whether the agent has completed */ completed: boolean; /** Final result (if completed) */ result?: unknown; } /** * Human function - requires human input/approval * * Generates appropriate UI/interaction for the specified channel: * - slack: Generates Slack BlockKit JSON * - email: Generates email template * - web: Generates form/UI component * - sms: Generates SMS-friendly text * - custom: Provides structured data for custom implementation * * @example * ```ts * defineFunction({ * type: 'human', * name: 'approveExpense', * args: { * amount: 'Expense amount (number)', * description: 'What the expense is for', * submitter: 'Who submitted the expense', * }, * returnType: { * approved: 'Whether the expense was approved (boolean)', * notes: 'Any notes from the approver', * }, * channel: 'workspace', * instructions: 'Review the expense request and approve or reject it.', * }) * ``` * * @typeParam TOutput - The return type schema * @typeParam TInput - The arguments schema */ export interface HumanFunctionDefinition<TOutput = unknown, TInput = unknown> extends BaseFunctionDefinition<TOutput, TInput> { type: 'human'; /** How to interact with the human */ channel: HumanChannel; /** Instructions shown to the human */ instructions: string; /** Prompt template with {{arg}} placeholders for the request */ promptTemplate?: string; /** Timeout in milliseconds (default: none - wait indefinitely) */ timeout?: number; /** Who should handle this (e.g., user ID, email, channel ID) */ assignee?: string; } /** * Human function result with channel-specific artifacts */ export interface HumanFunctionResult<T = unknown> { /** The human's response */ response: T; /** Who responded */ respondedBy?: string; /** When they responded */ respondedAt?: Date; /** Channel-specific artifacts */ artifacts?: { /** Slack BlockKit JSON */ slackBlocks?: unknown[]; /** Email HTML template */ emailHtml?: string; /** Web form component */ webComponent?: string; /** SMS message text */ smsText?: string; }; } /** * Union of all function definition types * * @typeParam TOutput - The return type schema * @typeParam TInput - The arguments schema */ export type FunctionDefinition<TOutput = unknown, TInput = unknown> = CodeFunctionDefinition<TOutput, TInput> | GenerativeFunctionDefinition<TOutput, TInput> | AgenticFunctionDefinition<TOutput, TInput> | HumanFunctionDefinition<TOutput, TInput>; /** * Result of defineFunction - a callable with metadata * * @typeParam TOutput - The return type of the function * @typeParam TInput - The arguments type accepted by the function */ export interface DefinedFunction<TOutput = unknown, TInput = unknown> { /** The original definition */ definition: FunctionDefinition<TOutput, TInput>; /** * Call the function. * * @param args - The function arguments. * @param env - Optional host Workers env (carrying a `LOADER` worker-loader * binding, and `TEST` for the test path) used by `type: 'code'` functions * to run inline `code` bodies in ai-evaluate's sandbox. When omitted, the * inline-code path falls back to the Miniflare-backed Node runtime. Has no * effect on `handler`-based code functions or other function types. */ call: (args: TInput, env?: SandboxEnv) => Promise<TOutput>; /** Get the function as a tool definition for AI */ asTool: () => AIFunctionDefinition<TOutput, TInput>; } /** * Function registry for storing and retrieving defined functions * * Note: This is in-memory only. For persistence, use mdxai or mdxdb packages. */ export interface FunctionRegistry { /** Get a function by name */ get(name: string): DefinedFunction | undefined; /** Set/store a function */ set(name: string, fn: DefinedFunction): void; /** Check if a function exists */ has(name: string): boolean; /** List all function names */ list(): string[]; /** Delete a function */ delete(name: string): boolean; /** Clear all functions */ clear(): void; } /** * Result of auto-defining a function */ export interface AutoDefineResult { /** The determined function type */ type: 'code' | 'generative' | 'agentic' | 'human'; /** Reasoning for why this type was chosen */ reasoning: string; /** The complete function definition */ definition: FunctionDefinition; } //# sourceMappingURL=types.d.ts.map