@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
147 lines • 7.42 kB
TypeScript
import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { AiToolCtx, ToolHandler } from './spi/tool.js';
import type { ToolPresentation } from './tool-presentation.js';
import type { Actor, ToolSpec } from './types.js';
/**
* 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 declare const AI_TOOL_META_KEY: unique symbol;
/**
* 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 declare const AGENT_TOOL_BRAND: unique symbol;
export interface AiToolOptions {
/**
* What the model calls it. Omit on a class → the class name, camelCased, with a trailing `Tool`
* dropped (`GetWeatherTool` → `getWeather`). Required on {@link defineTool}, which has no class.
*/
name?: string;
/**
* `read` auto-executes; `action` requires HITL approval. Default `'read'`. (Core's `ToolKind` also
* has `agent` for delegation, but that kind is synthesized from an agent's `delegatesTo` — never
* authored here.)
*/
kind?: 'read' | 'action';
description: string;
/**
* Input schema as a [Standard Schema](https://standardschema.dev) — Zod, Valibot, or ArkType.
* Validated (again) before the handler runs.
*/
input: StandardSchemaV1;
/** Roles allowed to invoke. Omit to inherit the config's `defaultRoles` (unrestricted by default). */
roles?: string[];
/**
* Authz ability checked by an ability-aware `RolesPolicy` (e.g. an `@adonis-agora/authz` Bouncer
* adapter → `bouncer.forUser(actor).allows(ability)`). Ignored by the default role-based policy.
*/
ability?: string;
/**
* Whether this tool exists in this deployment. `false` — or a predicate returning `false`, which
* is re-evaluated every turn — drops it before the role filter, so the model is never shown it.
* Omit → enabled.
*
* Use this for availability that is knowable without the container (a constant, `env.get(...)`, a
* closure over something the app already holds). When the answer lives in an injected service,
* implement `isEnabled()` on the tool class instead: this object is built at import time and
* cannot reach the container.
*/
enabled?: boolean | (() => boolean | Promise<boolean>);
/**
* How a chat surface talks about this tool without naming it — sentence templates over the call's
* input, an icon key, the approval prompt's wording, and how its output reads. Never shown to the
* model. Served by `GET <path>/tools` to the actors who can reach the tool:
*
* ```ts
* presentation: {
* label: 'Cache purge', running: 'Purging {key}', done: 'Purged {key}', icon: 'cache',
* tone: 'destructive', confirm: { title: 'Purge {key}?', verb: 'Purge' },
* }
* ```
*/
presentation?: ToolPresentation;
replacementKey?: ToolSpec['replacementKey'];
/** A successful call ends the turn — no model call narrates what it already showed. */
terminal?: boolean;
}
/**
* The metadata a tool class carries for discovery + registration — from `@AiTool` or `static tool` —
* with the defaults applied.
*/
export type AiToolMeta = AiToolOptions & {
name: string;
kind: 'read' | 'action';
};
/** `GetWeatherTool` → `getWeather`; `SQLQueryTool` → `sqlQuery`. */
export declare function toolNameFromClass(className: string): string;
/** Structural shape of a tool class: a constructor whose instance implements `execute`. */
export type ToolClass = abstract new (...args: never[]) => ToolHandler;
/**
* 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 declare function AiTool(options: AiToolOptions): <T extends ToolClass>(target: T) => T;
/**
* 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 declare function readAiToolMeta(target: unknown): AiToolMeta | undefined;
/**
* What {@link defineTool} takes: the `@AiTool` options with a required `name`, plus the per-actor
* gate a class would implement as a `canUse()` method.
*/
export interface DefineToolOptions extends AiToolOptions {
name: string;
/**
* Whether THIS actor may use the tool, decided per turn — after `enabled` and the `RolesPolicy`,
* on both the offered list and the invoke. For a decision the app-wide policy cannot express (the
* actor's plan, an entitlement row, ownership of the record). Omit → the role gate alone decides.
*/
canUse?: (actor: Actor) => boolean | Promise<boolean>;
}
/** A tool expressed as data + handler (from {@link defineTool}), not an `@AiTool` class. */
export interface FunctionalTool {
spec: ToolSpec;
handler: ToolHandler;
}
/** A {@link FunctionalTool} carrying {@link AGENT_TOOL_BRAND} — what {@link defineTool} returns. */
export interface BrandedFunctionalTool extends FunctionalTool {
readonly [AGENT_TOOL_BRAND]: true;
}
/** Narrows an arbitrary module export to a branded functional tool for boot-time registration. */
export declare function isBrandedFunctionalTool(value: unknown): value is BrandedFunctionalTool;
/**
* The functional form of a tool: pass the same options as `@AiTool` plus an `execute` function, get
* back a branded `{ spec, handler }` that discovery auto-registers. Export it from an `app/agent_tools`
* module (or pass it to `defineConfig({ tools })`).
*
* ```ts
* export const purgeCache = defineTool(
* { name: 'purgeCache', kind: 'action', description: '...', input: z.object({ key: z.string() }) },
* async ({ key }, ctx) => { ... },
* )
* ```
*/
export type DefineToolObjectOptions<S extends StandardSchemaV1, O> = Omit<DefineToolOptions, 'input'> & {
input: S;
} & ToolHandler<StandardSchemaV1.InferOutput<S>, O>;
export declare function defineTool<S extends StandardSchemaV1, O>(options: DefineToolObjectOptions<S, O>): BrandedFunctionalTool;
export declare function defineTool<I = unknown, O = unknown>(options: DefineToolOptions, execute: ((input: I, ctx: AiToolCtx) => Promise<O> | O) | ToolHandler<I, O>): BrandedFunctionalTool;
//# sourceMappingURL=ai-tool-ref.d.ts.map