UNPKG

@adonis-agora/agent

Version:

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

151 lines • 5.32 kB
/** Process-local {@link A2aStore} — tests and single-instance dev. */ export class InMemoryA2aStore { #contexts = new Map(); #messages = new Map(); async getContext(id) { const found = this.#contexts.get(id); return found ? { ...found } : null; } async createContext(context) { this.#contexts.set(context.id, { ...context }); } async bindAccount(id, accountRef) { const context = this.#contexts.get(id); if (!context) return false; if (context.accountRef === null) context.accountRef = accountRef; return context.accountRef === accountRef; } async claimMessage(contextId, messageId) { const key = `${contextId}\n${messageId}`; const found = this.#messages.get(key); if (!found) { this.#messages.set(key, { done: false }); return { status: 'claimed' }; } return found.done ? { status: 'done', reply: found.reply } : { status: 'pending' }; } async completeMessage(contextId, messageId, reply) { this.#messages.set(`${contextId}\n${messageId}`, { done: true, reply }); } async releaseMessage(contextId, messageId) { this.#messages.delete(`${contextId}\n${messageId}`); } } export const A2A_TABLES = { contexts: 'agent_a2a_context', messages: 'agent_a2a_message', }; /** * `CREATE TABLE IF NOT EXISTS` DDL for the two A2A tables — same portable dialect as the agent * tables (quoted identifiers, epoch-ms `BIGINT`, `TEXT` JSON). The message table's primary key IS the * idempotency lock: two deliveries of one `messageId` race on the insert and one loses. */ export function a2aTableStatements() { const t = A2A_TABLES; return [ `CREATE TABLE IF NOT EXISTS "${t.contexts}" ( "id" VARCHAR(255) PRIMARY KEY NOT NULL, "brand" VARCHAR(255) NOT NULL, "agent_issuer" VARCHAR(2048) NOT NULL, "agent_sub" VARCHAR(255) NOT NULL, "account_ref" VARCHAR(255) NULL, "created_at" BIGINT NOT NULL )`, `CREATE TABLE IF NOT EXISTS "${t.messages}" ( "context_id" VARCHAR(255) NOT NULL, "message_id" VARCHAR(255) NOT NULL, "reply" TEXT NULL, "created_at" BIGINT NOT NULL, PRIMARY KEY ("context_id", "message_id") )`, ]; } /** Create the A2A tables if missing. Idempotent. */ export async function ensureA2aTables(db) { for (const statement of a2aTableStatements()) { await db.rawQuery(statement); } } /** {@link A2aStore} over Lucid (any of SQLite / Postgres / MySQL). */ export class LucidA2aStore { db; constructor(db) { this.db = db; } async getContext(id) { const row = await this.db.from(A2A_TABLES.contexts).where('id', id).first(); if (!row) return null; return { id: String(row.id), brand: String(row.brand), agentIssuer: String(row.agent_issuer), agentSub: String(row.agent_sub), accountRef: row.account_ref == null ? null : String(row.account_ref), }; } async createContext(context) { await this.db.table(A2A_TABLES.contexts).insert({ id: context.id, brand: context.brand, agent_issuer: context.agentIssuer, agent_sub: context.agentSub, account_ref: context.accountRef, created_at: Date.now(), }); } async bindAccount(id, accountRef) { await this.db .from(A2A_TABLES.contexts) .where('id', id) .whereNull('account_ref') .update({ account_ref: accountRef }); // Re-read rather than trust the update count: a concurrent bind to the SAME account also wins. const context = await this.getContext(id); return context?.accountRef === accountRef; } async claimMessage(contextId, messageId) { try { await this.db.table(A2A_TABLES.messages).insert({ context_id: contextId, message_id: messageId, reply: null, created_at: Date.now(), }); return { status: 'claimed' }; } catch (error) { const row = await this.#message(contextId, messageId); if (!row) throw error; return row.reply == null ? { status: 'pending' } : { status: 'done', reply: JSON.parse(String(row.reply)) }; } } async completeMessage(contextId, messageId, reply) { await this.db .from(A2A_TABLES.messages) .where('context_id', contextId) .where('message_id', messageId) .update({ reply: JSON.stringify(reply) }); } async releaseMessage(contextId, messageId) { await this.db .from(A2A_TABLES.messages) .where('context_id', contextId) .where('message_id', messageId) .whereNull('reply') .delete(); } #message(contextId, messageId) { return this.db .from(A2A_TABLES.messages) .where('context_id', contextId) .where('message_id', messageId) .first(); } } //# sourceMappingURL=store.js.map