@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
153 lines • 8.26 kB
TypeScript
import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { RolesPolicy } from './spi/roles-policy.js';
import type { AiToolCtx, ToolDescribeScope, ToolHandler, ToolInputPreview, ToolInputPreviewScope, ToolPreflightResult } from './spi/tool.js';
import type { Actor, ToolDefinition, ToolSpec } from './types.js';
/** Thrown when an actor invokes a tool their role is not allowed. */
export declare class ToolForbiddenError extends Error {
readonly toolName: string;
constructor(toolName: string);
}
/**
* Thrown when a registered tool is invoked while this deployment has it turned off (`enabled` /
* `isEnabled()`). Distinct from {@link ToolForbiddenError}, which is about the actor, and from
* {@link ToolNotFoundError}, which is about a name nobody registered — an operator reading a log
* needs to tell "you flipped the flag" apart from "that tool does not exist in this build".
*
* Reachable in normal operation, not just from a forged call: a HITL `action` approved before the
* flag was turned off runs its tool afterwards.
*/
export declare class ToolDisabledError extends Error {
readonly toolName: string;
constructor(toolName: string);
}
/** Thrown when a tool is invoked that was never registered. */
export declare class ToolNotFoundError extends Error {
readonly toolName: string;
constructor(toolName: string);
}
/** Thrown when a tool's input fails its Standard Schema validation. */
export declare class ToolInputInvalidError extends Error {
readonly toolName: string;
readonly issues: readonly StandardSchemaV1.Issue[];
constructor(toolName: string, issues: readonly StandardSchemaV1.Issue[]);
}
export declare class ToolPreflightDeniedError extends Error {
readonly toolName: string;
readonly reason: string;
constructor(toolName: string, reason: string);
}
/** Trusted preparation or a fresh schema parse changed the approved JSON input. */
export declare class ToolInputDriftError extends Error {
readonly toolName: string;
constructor(toolName: string);
}
/**
* Holds every registered tool and gates invocation.
*
* Note: `definitionsFor` returns NEUTRAL definitions (no `execute`). The agent loop runs
* each tool itself as a (durable) step — so even read tools are not auto-executed by the
* model. `action` tools additionally require HITL approval before the loop runs them.
*/
export declare class ToolRegistry {
private readonly entries;
register(spec: ToolSpec, handler: ToolHandler): void;
/**
* Drop a tool, so it is neither offered to the model nor invocable. Returns whether there was one.
*
* For a tool whose EXISTENCE is not decided here: an MCP server may stop exporting a tool between
* one `tools/list` and the next, and leaving the old spec registered would go on offering the
* model a tool whose next call fails on somebody else's machine. Whoever registered a name is
* responsible for deciding when it is gone — nothing in the registry itself expires.
*/
unregister(name: string): boolean;
has(name: string): boolean;
hasPresentation(name: string): boolean;
/** Used by a journaled already-completed preflight; never executes the domain handler. */
presentResult(name: string, output: unknown, ctx: AiToolCtx): Promise<void>;
/**
* How a streamed call of `name` is previewed while its input arrives (`ToolHandler.previewInput`),
* or `undefined`. A preview that fails to build is no preview: it never fails the turn.
*/
previewInput(name: string, scope: ToolInputPreviewScope): Promise<ToolInputPreview | undefined>;
spec(name: string): ToolSpec | undefined;
allSpecs(): ToolSpec[];
/**
* The tools to offer the model for this actor+agent, after the four filter layers: what this
* agent pinned, what this deployment has enabled, what this actor's role allows, and what each
* tool's own `canUse` allows this actor.
*
* Every layer only ever removes tools, so no arrangement of them can widen what a turn reaches.
* The allow-list goes FIRST, even though it is the narrowest statement: it is a pure name-set
* match, while each of the three below it may be a round trip — an authz service, a feature-flag
* store, an entitlement table — and this runs once per model step.
*/
definitionsFor(actor: Actor, policy: RolesPolicy, allowedTools?: string[], scope?: Omit<ToolDescribeScope, 'actor'>): Promise<ToolDefinition[]>;
/**
* The specs {@link definitionsFor} offers the model, whole — the same four gates (allow-list,
* enabled, role, `canUse`) in the same order. For a surface that lists what an actor can reach
* (`GET <path>/tools`), which must never disagree with what the model is actually shown.
*/
visibleSpecs(actor: Actor, policy: RolesPolicy, allowedTools?: string[]): Promise<ToolSpec[]>;
private visibleEntries;
/**
* Run a tool. Re-checks that the tool is enabled, that the role allows it and that the tool's own
* `canUse` admits the actor (defense-in-depth — a call can reach here from a replayed durable step
* or an approval granted before the flag moved, neither of which went through `definitionsFor`
* again) and re-parses the input. With `options.allowedTools` (the turn's persona allow-list) a
* tool off that list is refused last, the same way the offer narrowed it. A tool whose
* `describe()` answers `available: false` for the call's scope is refused as not registered.
*/
private validated;
prepare(name: string, input: unknown, ctx: AiToolCtx, policy: RolesPolicy, options?: PrepareOptions): Promise<ToolPreflightResult>;
/** Expose the single schema parse; snapshotInput opts proposal callers into immutable JSON. */
prepareValidated(name: string, input: unknown, ctx: AiToolCtx, policy: RolesPolicy, options?: PrepareOptions): Promise<ToolPreparationResult>;
invoke(name: string, input: unknown, ctx: AiToolCtx, policy: RolesPolicy, options?: InvokeOptions): Promise<unknown>;
}
/** Per-call narrowing for {@link ToolRegistry.invoke}. */
export interface InvokeOptions {
/**
* Only these tool names may run — the turn's persona allow-list. Checked after `enabled`, the roles
* policy and `canUse`. Undefined → no such check (every caller predating personas).
*/
allowedTools?: readonly string[];
/** Trusted normalized JSON input. Own-property presence requires comparison even if undefined. */
approvedInput?: unknown;
}
export interface PrepareOptions extends InvokeOptions {
/** Preserve a detached normalized JSON snapshot and refuse mutation by the prepare hook. */
snapshotInput?: boolean;
}
export interface ToolPreparationResult {
input: unknown;
preflight: ToolPreflightResult;
}
/**
* Default gate: one of the actor's roles must be in `spec.roles` (else the default roles). An empty
* list means no restriction — the default: a tool is callable by whoever the actor resolver resolved,
* an anonymous visitor included. `action` tools still park on approval regardless.
*/
export declare class DefaultRolesPolicy implements RolesPolicy {
private readonly defaultRoles;
private readonly emptyRoles;
constructor(defaultRoles?: string[], options?: RolesPolicyOptions);
can(actor: Actor, tool: ToolSpec): boolean;
}
/**
* What an empty roles list means to {@link DefaultRolesPolicy}: `'allow'` (the default) — no
* restriction; `'deny'` — nobody.
*/
export type EmptyRoles = 'allow' | 'deny';
export interface RolesPolicyOptions {
/** What an empty roles list means. Default `'allow'`. */
emptyRoles?: EmptyRoles;
}
/**
* The closed gate — {@link DefaultRolesPolicy} with `emptyRoles: 'deny'`: the actor needs a role the
* tool declares (else one of the default roles). A tool with no `roles` and no default roles, or with
* an explicitly empty list, reaches nobody. For apps where an empty list means "no one" — a computed
* `roles` that can come out empty, a multi-tenant MCP surface.
*/
export declare class ClosedRolesPolicy extends DefaultRolesPolicy {
constructor(defaultRoles?: string[]);
}
//# sourceMappingURL=tool-registry.d.ts.map