UNPKG

ai-functions

Version:

Core AI primitives for building intelligent applications

139 lines (122 loc) 4.17 kB
/** * Simplified schema syntax for AI generation * * Converts human-readable schema definitions to Zod schemas: * - 'description' → z.string().describe('description') * - 'description (number)' → z.number().describe('description') * - 'description (boolean)' → z.boolean().describe('description') * - 'description (integer)' → z.number().int().describe('description') * - 'description (date)' → z.string().datetime().describe('description') * - 'opt1 | opt2 | opt3' → z.enum(['opt1', 'opt2', 'opt3']) * - ['description'] → z.array(z.string()).describe('description') * - { nested } → z.object() recursively * * @packageDocumentation */ import { z, type ZodTypeAny } from 'zod' import { isZodSchema } from './type-guards.js' /** * Simplified schema types */ export type SimpleSchema = | string // z.string().describe(value) | [string] // z.array(z.string()).describe(value) | [number] // z.array(z.number()).describe(value) | [SimpleSchema] // z.array(converted).describe(value) | { [key: string]: SimpleSchema } // z.object() recursively | ZodTypeAny // Pass-through for actual Zod schemas /** * Convert a simplified schema to a Zod schema * * @example * ```ts * import { schema } from 'ai-functions' * import { z } from 'zod' * * // These are equivalent: * const simple = schema({ * name: 'What is the recipe name?', * ingredients: ['List all ingredients'], * steps: ['List all cooking steps'], * }) * * const zod = z.object({ * name: z.string().describe('What is the recipe name?'), * ingredients: z.array(z.string()).describe('List all ingredients'), * steps: z.array(z.string()).describe('List all cooking steps'), * }) * ``` */ export function schema<T extends SimpleSchema>(input: T): ZodTypeAny { return convertToZod(input) } function convertToZod(input: SimpleSchema): ZodTypeAny { // Already a Zod schema - pass through if (isZodSchema(input)) { return input } // String handling if (typeof input === 'string') { // Enum syntax: 'option1 | option2 | option3' if (input.includes(' | ')) { const options = input.split(' | ').map((s) => s.trim()) return z.enum(options as [string, ...string[]]) } // Type hint syntax: 'description (type)' const typeMatch = input.match(/^(.+?)\s*\((number|boolean|integer|date)\)$/i) if (typeMatch) { const [, description, type] = typeMatch const desc = description!.trim() switch (type!.toLowerCase()) { case 'number': return z.number().describe(desc) case 'integer': return z.number().int().describe(desc) case 'boolean': return z.boolean().describe(desc) case 'date': return z.string().datetime().describe(desc) default: return z.string().describe(desc) } } // Regular string description → z.string().describe() return z.string().describe(input) } // Array with single element → z.array().describe() if (Array.isArray(input) && input.length === 1) { const [desc] = input // [string] → z.array(z.string()).describe(string) if (typeof desc === 'string') { return z.array(z.string()).describe(desc) } // [number] → z.array(z.number()) - number as type indicator if (typeof desc === 'number') { return z.array(z.number()) } // [SimpleSchema] → z.array(converted) return z.array(convertToZod(desc as SimpleSchema)) } // Object → z.object() with recursive conversion if (typeof input === 'object' && input !== null) { const shape: Record<string, ZodTypeAny> = {} for (const [key, value] of Object.entries(input)) { shape[key] = convertToZod(value as SimpleSchema) } return z.object(shape) } // Fallback - shouldn't reach here return z.unknown() } /** * Type helper to infer the output type from a simple schema */ export type InferSimpleSchema<T> = T extends string ? string : T extends [string] ? string[] : T extends [number] ? number[] : T extends { [K in keyof T]: SimpleSchema } ? { [K in keyof T]: InferSimpleSchema<T[K]> } : unknown