UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

151 lines • 6.99 kB
/** * The symbol the `@AiTool` decorator stamps its options onto (the tool class), read back by * discovery to register the class into the {@link import('./tool-registry.js').ToolRegistry}. A * global-registry symbol (`Symbol.for`) so it survives duplicate copies of this package in a tree. */ export const AI_TOOL_META_KEY = Symbol.for('@agora/agent:ai-tool-meta'); /** * Brand stamped on the object {@link defineTool} returns so discovery picks it up when it walks a * module's exports — the functional alternative to an `@AiTool` class. */ export const AGENT_TOOL_BRAND = Symbol.for('@agora/agent:functional-tool'); /** `GetWeatherTool` → `getWeather`; `SQLQueryTool` → `sqlQuery`. */ export function toolNameFromClass(className) { const base = className.replace(/Tool$/, '') || className; const leadingCaps = /^[A-Z]+(?=[A-Z][a-z]|$)/.exec(base)?.[0]; if (leadingCaps !== undefined && leadingCaps.length > 1) { return leadingCaps.toLowerCase() + base.slice(leadingCaps.length); } return base.charAt(0).toLowerCase() + base.slice(1); } /** Apply the `name` / `kind` defaults, the name taken from `className`. */ function resolveMeta(options, className) { return { ...options, name: options.name ?? toolNameFromClass(className), kind: options.kind ?? 'read', }; } /** * Marks a class as an AI tool. The class must implement `execute(input, ctx)`. The provider's * `app/agent_tools` discovery (or the generated `hooks/tools` barrel) registers every `@AiTool` * class into the shared `ToolRegistry` at boot. * * ```ts * @AiTool({ name: 'getWeather', kind: 'read', description: '...', input: z.object({ city: z.string() }) }) * export default class GetWeatherTool implements ToolHandler<{ city: string }> { * async execute(input: { city: string }, ctx: AiToolCtx) { return { tempC: 21 } } * } * ``` */ export function AiTool(options) { return (target) => { Object.defineProperty(target, AI_TOOL_META_KEY, { value: resolveMeta(options, target.name ?? ''), enumerable: false, configurable: true, }); return target; }; } /** Read the `@AiTool` decorator's stamped {@link AI_TOOL_META_KEY} metadata off a value, if present. */ function decoratorMeta(target) { if (target === null || (typeof target !== 'function' && typeof target !== 'object')) { return undefined; } return target[AI_TOOL_META_KEY]; } /** Read a class's tool metadata off its `static tool = { name, kind, … }` config, if any. */ function staticToolMeta(target) { if (typeof target !== 'function') return undefined; const config = target.tool; if (config && typeof config === 'object' && typeof config.description === 'string') { // `kind` may come from the config itself (BaseTool / @AiTool) or from the kind-specific base's static // (ReadTool → 'read', ActionTool → 'action'), which keeps it out of the subclass's `static tool`. const kind = config.kind ?? target.kind; return resolveMeta({ ...config, ...(kind !== undefined ? { kind } : {}) }, target.name ?? ''); } return undefined; } /** Resolve either authoring mechanism — `@AiTool` decorator or `static tool` — from one value. */ function metaOn(target) { return decoratorMeta(target) ?? staticToolMeta(target); } /** * Read a tool class's {@link AiToolMeta}. Two authoring forms are supported: * * 1. The `@AiTool({ … })` decorator (stamps {@link AI_TOOL_META_KEY}). * 2. A decorator-free `static tool = { name, kind, description, input, … }` config on the class — the * same shape, mirroring `@adonis-agora/durable`'s `static workflow`. Preferred where you'd rather * not rely on decorators. * * Accepts either the class itself or an instance: each mechanism is tried on the value, then on its * constructor (so an instance resolves the class's metadata). */ export function readAiToolMeta(target) { if (target === null || (typeof target !== 'function' && typeof target !== 'object')) { return undefined; } const ctor = target.constructor; return metaOn(target) ?? metaOn(ctor); } /** Narrows an arbitrary module export to a branded functional tool for boot-time registration. */ export function isBrandedFunctionalTool(value) { return (typeof value === 'object' && value !== null && AGENT_TOOL_BRAND in value && 'spec' in value && 'handler' in value); } export function defineTool(options, given) { const execute = given ?? options; if (typeof execute !== 'function' && typeof execute.execute !== 'function') { throw new TypeError('defineTool requires an execute function'); } const spec = { name: options.name, kind: options.kind ?? 'read', description: options.description, inputSchema: options.input, ...(options.roles !== undefined ? { roles: options.roles } : {}), ...(options.ability !== undefined ? { ability: options.ability } : {}), ...(options.enabled !== undefined ? { enabled: options.enabled } : {}), ...(options.presentation !== undefined ? { presentation: options.presentation } : {}), ...(options.replacementKey !== undefined ? { replacementKey: options.replacementKey } : {}), ...(options.terminal === true ? { terminal: true } : {}), }; // In the single-object form this is the handler method, not a second options gate. const canUse = given === undefined ? undefined : options.canUse?.bind(options); const implementation = typeof execute === 'function' ? undefined : execute; return { [AGENT_TOOL_BRAND]: true, spec, handler: { ...(implementation?.present !== undefined ? { present: (output, ctx) => implementation.present(output, ctx) } : {}), ...(implementation?.preflight !== undefined ? { preflight: (input, ctx, options) => implementation.preflight(input, ctx, options), } : {}), ...(implementation?.isEnabled !== undefined ? { isEnabled: () => implementation.isEnabled() } : {}), ...(implementation?.describe !== undefined ? { describe: (scope) => implementation.describe(scope) } : {}), execute: (input, ctx) => Promise.resolve(typeof execute === 'function' ? execute(input, ctx) : execute.execute(input, ctx)), ...(canUse !== undefined || implementation?.canUse !== undefined ? { canUse: async (actor) => (canUse === undefined || (await canUse(actor))) && (implementation?.canUse === undefined || (await implementation.canUse(actor))), } : {}), }, }; } //# sourceMappingURL=ai-tool-ref.js.map