UNPKG

@adonis-agora/agent

Version:

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

139 lines • 7.27 kB
import type { StandardSchemaV1 } from '@standard-schema/spec'; import { type BrandedFunctionalTool, type DefineToolOptions } from './ai-tool-ref.js'; import type { ConfirmTokenStore } from './spi/confirm-token-store.js'; import type { AiToolCtx } from './spi/tool.js'; /** The two fields {@link defineConfirmedTool} adds to a tool's input. */ export interface ConfirmFields { /** `true` to commit; absent (or `false`) to preview. */ confirm?: boolean; /** The `confirmToken` the preview returned. */ confirmToken?: string; } /** The words a confirmed tool says to the model. Each has an English default. */ export interface ConfirmedToolMessages { /** Sent with every preview: how to confirm. `{minutes}` is replaced with the token's lifetime. */ confirm?: string; /** Thrown for a missing, tampered, mismatched or expired token. */ invalid?: string; /** Thrown for a token that already committed. */ used?: string; } /** Thrown by a confirmed tool that refused a confirmation. Nothing was written. */ export declare class ConfirmTokenError extends Error { /** `invalid`: missing, tampered, mismatched or expired. `used`: it already committed once. */ readonly reason: 'invalid' | 'used'; constructor( /** `invalid`: missing, tampered, mismatched or expired. `used`: it already committed once. */ reason: 'invalid' | 'used', message: string); } /** What {@link defineConfirmedTool} takes besides the usual tool options. */ export interface ConfirmedToolOptions extends Omit<DefineToolOptions, 'kind'> { name: string; /** * The HMAC key the tokens are signed with — the app key, or a secret of its own. A function is * read on every call. Required: there is no default, and an empty one throws. */ secret: string | (() => string); /** How long a preview stays confirmable. Default 15 minutes. */ ttlMs?: number; /** * Makes a token single use. WITHOUT a store a token commits as many times as it is sent until it * expires — a retry or a repeated call writes twice. */ store?: ConfirmTokenStore; messages?: ConfirmedToolMessages; } /** What a step reports: a sentence for the model to relay, and the structured detail behind it. */ export interface ConfirmedToolOutcome { summary: string; data?: unknown; } /** The three steps of a confirmed write. */ export interface ConfirmedToolSteps<Args, Prepared> { /** * Resolve and validate everything the write needs, applying the SAME rules the commit relies on. * Throw to refuse. Writes nothing. Runs for the preview AND again for the confirmation — state may * have changed in between — and a refusal here does not spend the token. */ prepare(args: Args, ctx: AiToolCtx): Promise<Prepared> | Prepared; /** What is about to happen. Writes nothing. */ preview(prepared: Prepared, ctx: AiToolCtx): Promise<ConfirmedToolOutcome> | ConfirmedToolOutcome; /** Write, and say what was written. A throw gives the token back. */ commit(prepared: Prepared, ctx: AiToolCtx): Promise<ConfirmedToolOutcome> | ConfirmedToolOutcome; } /** A confirmed tool's answer to a call without `confirm`. */ export interface ConfirmedToolPreview { status: 'preview'; summary: string; data?: unknown; confirmToken: string; /** ISO-8601 instant the token stops working. */ expiresAt: string; /** Instructions for the model: show the preview, then call again with the token. */ confirm: string; } /** A confirmed tool's answer to a confirmation that committed. */ export interface ConfirmedToolDone { status: 'done'; summary: string; data?: unknown; } export type ConfirmedToolResult = ConfirmedToolPreview | ConfirmedToolDone; /** The JSON Schema of {@link ConfirmFields}, merged into the schema the model is shown. */ export declare const CONFIRM_JSON_SCHEMA_PROPERTIES: { readonly confirm: { readonly type: 'boolean'; readonly description: 'Omit to get a preview (nothing is written). Send true, with confirmToken, to commit.'; }; readonly confirmToken: { readonly type: 'string'; readonly description: 'The confirmToken the preview returned. Required when confirm is true.'; }; }; /** * Wrap a tool's input schema so it also accepts {@link ConfirmFields}, without the app declaring * them. `validate` takes the two fields off, checks them itself, hands the REST to the app's schema * (so a strict schema never sees a key it did not declare) and puts them back on the validated value. * * The wrapper always carries the Standard JSON Schema extension, because that is the only form the * model gets to see: the app schema's own JSON Schema plus the two properties. A schema without the * extension (Zod 3, a bare Standard Schema) degrades to a permissive object that still declares the * two fields — `validate` stays the authority either way. */ export declare function withConfirmFields<Schema extends StandardSchemaV1>(schema: Schema): StandardSchemaV1<StandardSchemaV1.InferInput<Schema> & ConfirmFields, StandardSchemaV1.InferOutput<Schema> & ConfirmFields>; /** * A write with a human gate that lives INSIDE the tool: preview first, commit on confirmation. * * - Called without `confirm`: runs `prepare`, writes nothing, and returns the `preview` with a signed * `confirmToken`. * - Called again with the SAME arguments, `confirm: true` and that token: runs `prepare` again, then * `commit`. * * The token is an HMAC over the tool, `ctx.actor.id`, `ctx.actor.tenantRef`, the expiry and the * canonical arguments, so it cannot be replayed by another actor, in another tenant, on another * tool or with a changed argument. With a `store` it is also single use: it is claimed right before * `commit` (a refusal in `prepare` does not spend it) and released if `commit` throws. * * Registered as `kind: 'read'` on purpose. An `action` parks on the loop's HITL approval, which an * MCP caller cannot answer — `createMcpServer` keeps `action` tools off the surface, and MCP clients * without elicitation have no other channel. The gate being in the handler is what lets the same * tool serve the chat loop and MCP; the user sees the preview because the model has to relay it to * get a token it can confirm with. * * ```ts * export const refundOrder = defineConfirmedTool( * { name: 'refund_order', description: '...', input: z.object({ orderId: z.string() }), * secret: () => env.get('APP_KEY').release(), store: new LucidConfirmTokenStore(db) }, * { * prepare: async ({ orderId }, ctx) => loadRefundableOrder(orderId, ctx.actor), * preview: (order) => ({ summary: `Refund ${order.total} to ${order.customer}?`, data: order }), * commit: async (order) => ({ summary: 'Refunded.', data: await refund(order) }), * }, * ) * ``` * * `input` is the app's own schema: `confirm` and `confirmToken` are added by * {@link withConfirmFields}, and `prepare` receives the arguments without them. */ export declare function defineConfirmedTool<Args = unknown, Prepared = unknown>(options: ConfirmedToolOptions, steps: ConfirmedToolSteps<Args, Prepared>): BrandedFunctionalTool; //# sourceMappingURL=confirmed-tool.d.ts.map