UNPKG

@axarai/axar

Version:

TypeScript-based agent framework for building agentic applications powered by LLMs

268 lines (267 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.input = exports.output = void 0; exports.model = model; exports.systemPrompt = systemPrompt; exports.tool = tool; require("reflect-metadata"); const zod_1 = require("zod"); const meta_keys_1 = require("./meta-keys"); const schema_1 = require("../schema"); /** * `model` decorator to associate a model identifier and configuration with an agent. * * @param modelIdentifier - The model identifier string. List of model identifiers available at https://axar-ai.gitbook.io/axar/basics/model * @param config - Optional configuration for the model * @returns A class decorator function. * * @example * ```typescript * // Basic usage * @model('openai:gpt-4-mini') * class MyAgent extends Agent<string, string> {} * * // With configuration * @model('openai:gpt-4-mini', { * maxTokens: 100, * temperature: 0.5, * maxRetries: 3, * maxSteps: 3, * toolChoice: 'auto' * }) * class MyConfiguredAgent extends Agent<string, string> {} * ``` */ function model(modelIdentifier, config) { return function (target) { Reflect.defineMetadata(meta_keys_1.META_KEYS.MODEL, modelIdentifier, target); if (config) { Reflect.defineMetadata(meta_keys_1.META_KEYS.MODEL_CONFIG, config, target); } return target; }; } /** * Creates a schema from the provided type specification * * @param type - The type specification (ZodSchema, class decorated with @schema, or primitive constructor) * @param decoratorName - The name of the decorator that we're creating the schema for * @returns The created schema */ function createSchema(type, decoratorName) { if (type instanceof zod_1.ZodSchema) { return type; } const primitiveSchemas = { [String.name]: zod_1.z.string(), [Number.name]: zod_1.z.number(), [Boolean.name]: zod_1.z.boolean(), }; const primitiveSchema = primitiveSchemas[type.name]; if (primitiveSchema) { return primitiveSchema; } if ((0, schema_1.hasSchemaDef)(type)) { return (0, schema_1.getSchemaDef)(type); } const typeName = typeof type === 'function' && type.name ? type.name : String(type); throw new Error(`${decoratorName} error: Could not create a schema for "${typeName}". ` + `Type must be a Zod schema, a class decorated with @schema, or a primitive constructor (String, Number, Boolean).`); } /** * * Creates a decorator for input/output schema definition * * @param metaKey - Input/Output symbol * @param decoratorName - '@input' or '@output' * @returns a decorator */ function createSchemaDecorator(metaKey, decoratorName) { return function (type) { return function (target) { const schema = createSchema(type, decoratorName); Reflect.defineMetadata(metaKey, schema, target); return target; }; }; } /** * Specifies the output schema for a class. * * @param type - The output type specification, which can be: * - A Zod schema * - A class decorated with @schema * - A primitive type (String, Number, Boolean) * * @example * ```typescript * // Using primitive * @output(String) * class StringAgent extends Agent<string, string> {} * * // Using schema-decorated class * @output(UserProfile) * class UserAgent extends Agent<string, UserProfile> {} * * // Using Zod schema directly * @output(z.boolean()) * class BooleanAgent extends Agent<string, boolean> {} * ``` */ exports.output = createSchemaDecorator(meta_keys_1.META_KEYS.OUTPUT, '@output'); /** * Specifies the input schema for a class. * * @param type - The input type specification, which can be: * - A Zod schema * - A class decorated with @schema * - A primitive type (String, Number, Boolean) * * @example * ```typescript * // Using primitive * @input(Boolean) * class BooleanAgent extends Agent<boolean, string> {} * * // Using schema-decorated class * @input(UserProfile) * class UserAgent extends Agent<UserProfile, string> {} * * // Using Zod schema directly * @input(z.boolean()) * class BooleanAgent extends Agent<boolean, string> {} * ``` */ exports.input = createSchemaDecorator(meta_keys_1.META_KEYS.INPUT, '@input'); // Implementation function systemPrompt(prompt) { // Class Decorator if (typeof prompt === 'string') { return systemPromptClass(prompt); } else { // Method Decorator return systemPromptMethod(); } } // Internal helper for class-level system prompts function systemPromptClass(prompt) { return function (target) { const systemPrompts = Reflect.getMetadata(meta_keys_1.META_KEYS.SYSTEM_PROMPTS, target) || []; // Add class prompt to the beginning systemPrompts.unshift(async () => prompt); Reflect.defineMetadata(meta_keys_1.META_KEYS.SYSTEM_PROMPTS, systemPrompts, target); return target; }; } // Internal helper for method-level system prompts function systemPromptMethod() { return function (target, propertyKey, descriptor) { if (typeof descriptor.value !== 'function') { throw new Error(`@systemPrompt can only be applied to methods, not to property '${String(propertyKey)}'.`); } // Retrieve existing system prompts or initialize const systemPrompts = Reflect.getMetadata(meta_keys_1.META_KEYS.SYSTEM_PROMPTS, target.constructor) || []; systemPrompts.push(async function () { const result = await descriptor.value.apply(this); // Use the actual instance's `this` if (typeof result !== 'string') { throw new Error(`Method '${String(propertyKey)}' decorated with @systemPrompt must return a string.`); } return result; }); Reflect.defineMetadata(meta_keys_1.META_KEYS.SYSTEM_PROMPTS, systemPrompts, target.constructor); return descriptor; }; } /** * `@tool` decorator to mark a method as a tool with a description and schema. * Supports both explicit ZodSchema and class-based schema derivation. * * Usage with ZodSchema: * @tool("Description", z.object({ ... })) * * Usage with class-based schema: * @tool("Description") * async method(params: ClassBasedParams): Promise<ReturnType> { ... } * * @param description - Description of the tool's functionality. * @param schemaOrClass - Optional Zod schema or class constructor. * @returns A method decorator function. */ function tool(description, schemaOrClass) { return function (target, propertyKey, descriptor) { if (typeof descriptor.value !== 'function') { throw new Error(`@tool can only be applied to methods, not to property '${String(propertyKey)}'.`); } let schema; if (schemaOrClass) { if (schemaOrClass instanceof zod_1.z.ZodSchema) { // Explicit Zod schema provided schema = schemaOrClass; } else if ((0, schema_1.hasSchemaDef)(schemaOrClass)) { schema = (0, schema_1.getSchemaDef)(schemaOrClass); } else { throw new Error(`${schemaOrClass.name} must be either a Zod schema or a class decorated with @schema`); } } else { // No schema provided, derive via reflection const paramTypes = Reflect.getMetadata('design:paramtypes', target, propertyKey); if (!paramTypes || paramTypes.length === 0) { // Empty parameter list, so we use an empty schema schema = zod_1.z.object({}); } else { const paramType = paramTypes[0]; // Validate parameter count if (paramTypes.length > 1) { throw new Error(`@tool ${String(propertyKey)}: Expected a single parameter, but found ${paramTypes.length}.`); } // Exclude primitive types const primitiveTypes = [ String, Number, Boolean, Symbol, BigInt, Function, ]; if (primitiveTypes.includes(paramType)) { throw new Error(`@tool ${String(propertyKey)}: The parameter type (${paramType.name}) is a primitive, not an object.`); } // Ensure paramType is constructable and produces an object if (typeof paramType !== 'function' || !paramType.prototype) { throw new Error(`@tool ${String(propertyKey)}: The parameter type (${paramType.name}) is not a valid class or constructor.`); } // Check if paramType is a class decorated with @schema if (!(0, schema_1.hasSchemaDef)(paramType)) { throw new Error(`@tool decorator on ${String(propertyKey)} requires an explicit Zod schema or a parameter class decorated with @schema.`); } // Convert the parameter class to Zod schema schema = (0, schema_1.getSchemaDef)(paramType); } } // Retrieve existing tools metadata or initialize an empty array const tools = Reflect.getMetadata(meta_keys_1.META_KEYS.TOOLS, target.constructor) || []; // Add the new tool to the metadata tools.push({ name: String(propertyKey), description, method: String(propertyKey), parameters: schema, }); // Define the updated tools metadata on the constructor Reflect.defineMetadata(meta_keys_1.META_KEYS.TOOLS, tools, target.constructor); // Wrap the original method to validate input const originalMethod = descriptor.value; descriptor.value = function (...args) { if (args[0]) { schema.parse(args[0]); // This will throw if validation fails } return originalMethod.apply(this, args); }; return descriptor; }; }