@axarai/axar
Version:
TypeScript-based agent framework for building agentic applications powered by LLMs
268 lines (267 loc) • 10.1 kB
JavaScript
;
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;
};
}