ai-functions
Version:
Core AI primitives for building intelligent applications
718 lines (664 loc) • 21.6 kB
text/typescript
/**
* 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'
// ============================================================================
// Human Function Pending Types
// ============================================================================
/**
* Symbol used to identify pending human function results
*/
export const PENDING_HUMAN_RESULT_SYMBOL = Symbol.for('HumanFunctionPending')
/**
* 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 function isPendingHumanResult<T>(
value: T | HumanFunctionPending
): value is HumanFunctionPending {
return (
value !== null &&
typeof value === 'object' &&
'_pending' in value &&
value._pending === true &&
PENDING_HUMAN_RESULT_SYMBOL in value
)
}
// ============================================================================
// AI Function Types
// ============================================================================
/**
* 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[]
}
// ============================================================================
// Function Definition Types
// ============================================================================
/**
* 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
}