@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
166 lines • 10.8 kB
TypeScript
import type { LucidRawRunner } from './lucid.js';
/**
* The twelve agent table names. They match the cross-adapter snake_case contract the reference Drizzle
* store uses, so a dashboard or migration can point at any adapter and see the same physical schema.
*/
export declare const AGENT_TABLES: {
/** Independent action proposals, including durable execution work and its lease. */
readonly actionProposals: 'agent_action_proposal';
readonly threads: 'agent_thread';
readonly messages: 'agent_message';
readonly toolCalls: 'agent_tool_call';
readonly tokenUsage: 'agent_token_usage';
readonly modelPricing: 'agent_model_pricing';
readonly runs: 'agent_run';
/** Messages sent while a turn was running, waiting to run after it (the chat queue). */
readonly queuedMessages: 'agent_queued_message';
/** Confirm tokens already spent, by hash (`LucidConfirmTokenStore`, single-use confirmed writes). */
readonly confirmTokens: 'agent_confirm_token';
/** A run's live stream, buffered for replay by the Lucid token sink (`tokenSinks.lucid()`). */
readonly streamFrames: 'agent_stream_frame';
/**
* Text channels' short-lived state (`lucidChannelStore`): the provider message ids already taken,
* questions waiting for an answer, outcomes already relayed — each row expiring at `expires_at`.
*/
readonly channelState: 'agent_channel_state';
/**
* Generative-UI telemetry: one row per UI tool call (`recordGenuiCalls`) — metadata only, the
* call's input only when its tool samples payloads. Read by the dashboard's Generative UI page.
*/
readonly genuiCalls: 'agent_genui_call';
};
/**
* One statement of this module, as `db`'s dialect has to read it. The statements are written once,
* Postgres/SQLite-style; MySQL needs four things changed, and every one of them used to fail or
* silently misbehave there:
*
* - identifiers in backticks — a `"quoted"` name is a string literal to MySQL, so every statement
* failed to parse;
* - `TEXT` as `LONGTEXT` — MySQL's `TEXT` holds 64 KB, and a long answer, a tool's output or a stream
* frame past that failed the insert;
* - no `IF NOT EXISTS` on `CREATE INDEX`, which MySQL does not have (the catalog is read first, see
* {@link existingIndexes});
* - InnoDB with a case-sensitive collation: the foreign keys need InnoDB, and under MySQL's default
* `utf8mb4_0900_ai_ci` an actor `alice` read the threads of an actor `ALICE`. Postgres and SQLite
* compare those exactly; `utf8mb4_bin` makes MySQL do the same.
*/
export declare function forDialect(statement: string, mysql: boolean): string;
/**
* `CREATE TABLE IF NOT EXISTS` for the Lucid token sink's frame buffer, under `table` (the sink takes
* a `tableName`). One row per frame of a run's stream, numbered from 1 in write order; the row whose
* `frame` is `NULL` is the end marker. `created_at` is what the sink's TTL purge reads. No foreign
* key: a sink is usable with any store, and a delegated run writes under its ANCESTOR's run id.
*/
export declare function streamFrameTableStatement(table?: string): string;
/**
* `CREATE TABLE IF NOT EXISTS` for text channels' state (`lucidChannelStore`), under `table`. The
* primary key IS the dedupe lock: two deliveries of one message race on the insert and one loses.
* No foreign key: the rows name provider ids, not agent rows.
*/
export declare function channelStateTableStatements(table?: string): string[];
/**
* `CREATE TABLE IF NOT EXISTS` DDL for the twelve agent tables plus their indexes, one statement per
* array element so each can be issued through Lucid's `rawQuery`. Portable across SQLite / Postgres /
* MySQL: quoted identifiers, epoch-ms `BIGINT` timestamps, `INTEGER` booleans (0/1) and `TEXT` JSON
* columns — no dialect-only types. A real deployment should prefer the bundled migration stub so the
* schema is versioned; this helper lets a store stand itself up in tests and scripts.
*
* A tool call is addressed by the model-supplied `toolCallId`, kept as-is in `call_id`. The row's PK
* is that id scoped to its assistant message (see `toolCallRowId` in `lucid.ts`): a provider only
* promises the id is unique within one response, and some (short `call_<digits>` ids behind
* OpenRouter) repeat one across runs. A row from before `call_id` existed has it NULL and its PK is
* the bare model id.
*
* The `agent_run` table records each run (turn) lifecycle; `agent_message` / `agent_tool_call` /
* `agent_token_usage` each carry a nullable `run_id` correlation column (logically referencing
* `agent_run.id`, deliberately WITHOUT a DB-level foreign key — like the reference — so the additive
* migration can `ALTER TABLE ADD COLUMN` portably and a row recorded before run tracking shipped can
* keep a `null` run_id).
*/
export declare function createTableStatements(): string[];
/**
* Whether `agent_tool_call` has its `call_id` column (#183). What `LucidAgentStore` checks when the
* app owns the schema (`autoCreateTables: false`) and so may boot a new version before its migration
* has added the column. Probed on {@link schemaRunner}'s client, outside any global transaction.
*/
export declare function hasToolCallIdColumn(db: LucidRawRunner): Promise<boolean>;
/**
* Whether `agent_message` and `agent_queued_message` have their `sent_at` columns — what
* `LucidAgentStore` checks under `autoCreateTables: false`, where the app may boot this version before
* its migration has added them. Probed like {@link hasToolCallIdColumn}.
*/
export declare function hasSentAtColumns(db: LucidRawRunner): Promise<boolean>;
/** Rows of a raw query result, whatever the driver wraps them in (`{ rows }`, `[rows, fields]`, rows). */
export declare function rowsOf(result: unknown): Record<string, unknown>[];
/**
* Idempotently provision the twelve agent tables through Lucid's async raw runner (`CREATE TABLE IF
* NOT EXISTS`), then additively repair a database that predates run tracking by ALTERing in the
* `run_id` columns its three older tables are missing.
*
* Both halves matter, and for different populations. A fresh database gets everything from the
* `CREATE TABLE` statements and never reaches a repair branch — which is what makes a non-empty
* `repairs` result mean "this schema was out of date", not "boot happened". A database created before
* run tracking shipped has all three tables already, so every `CREATE TABLE IF NOT EXISTS` no-ops and
* only the repair adds the columns the store now writes on every turn.
*
* Against a schema that is already current it issues no DDL that takes a table lock: an existing
* index is found in the catalog instead of re-issued (see {@link existingIndexes}). And it is safe
* with several processes provisioning at once (see {@link issue}).
*
* Returns the `<table>.<column>` repairs actually issued, so a caller can report them. Works on every
* Lucid dialect. For an AdonisJS app prefer the published migration
* (`node ace configure @adonis-agora/agent`); this helper is what that migration calls, and what the
* stores call themselves when `autoCreateTables` is on.
*/
export declare function createAgentTables(db: LucidRawRunner): Promise<string[]>;
/**
* `DROP TABLE IF EXISTS` for the twelve agent tables, in reverse dependency order so a dialect that
* enforces the `REFERENCES` clauses never refuses a drop for a child that still exists. The mirror of
* {@link createAgentTables}, and what the published migration's `down()` calls.
*/
export declare function dropTableStatements(): string[];
/** Drop the twelve agent tables. Destructive and irreversible — this erases every thread and every ledger row. */
export declare function dropAgentTables(db: LucidRawRunner): Promise<void>;
/**
* The client schema DDL should run on: one of its OWN, outside a global transaction the caller has
* open (`db.beginGlobalTransaction()` — what `testUtils.db().withGlobalTransaction()` does for a Japa
* suite). Inside that transaction the DDL would be rolled back with the test while the process still
* remembers the schema as provisioned, and on Postgres the column probes — which fail by design on a
* missing column — would abort the whole transaction.
*
* Lucid's manager answers every `connection()` with the global transaction while one is registered,
* so the registration is lifted for the one synchronous call that builds a plain client and put back
* before anything else can run.
*
* SQLite is left where it is: it has one writer, so a second connection could only wait for the very
* transaction it is trying to avoid (and with a pool of one there is no second connection at all).
* Its DDL is transactional and a failed probe does not abort anything, so running inside is correct.
*
* Anything that is not the `Database` manager — a client from `db.connection(name)`, a fake — is
* returned as is: a plain client is already outside, and a transaction client cannot be escaped.
*/
export declare function schemaRunner(db: LucidRawRunner): LucidRawRunner;
/**
* Idempotently ensure the twelve agent tables exist, memoized per db client. This is what the stores
* run when `autoCreateTables` is on (the default): the agent provider calls each store's
* `ensureSchema()` once as the app starts, and a store used without the provider (a script, a test
* that builds one by hand) falls back to it on first use — whichever store touches the connection
* first provisions the shared schema, so pricing seeds and governance reads work even before the
* first agent run. The DDL runs on {@link schemaRunner}'s client, never inside a global transaction.
*/
export declare function ensureAgentTables(db: LucidRawRunner): Promise<void>;
/**
* Idempotently create the Lucid token sink's frame table, under whatever name the sink was given.
* What the sink runs for itself (`autoCreateTables`), so it stands up next to ANY store — the
* in-memory one included — without provisioning the other seven tables. {@link createAgentTables}
* creates the same table under its default name, so the published migration covers it too.
*/
export declare function ensureStreamFrameTable(db: LucidRawRunner, table?: string): Promise<void>;
/**
* Idempotently create text channels' state table, under whatever name the store was given — what
* `lucidChannelStore` runs for itself (`autoCreateTables`), so it stands up next to any agent store.
* {@link createAgentTables} creates the same table under its default name, so the published
* migration covers it too.
*/
export declare function ensureChannelStateTable(db: LucidRawRunner, table?: string): Promise<void>;
//# sourceMappingURL=lucid-schema.d.ts.map