@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
260 lines • 9.49 kB
JavaScript
import { forDialect } from '../stores/lucid-schema.js';
import { isMySql } from '../stores/sql-dialect.js';
export class InMemoryPoppyConnectionStore {
#connections = new Map();
#pending = new Map();
async listConnections(owner) {
return [...this.#connections.values()].filter((c) => c.owner === owner).map((c) => ({ ...c }));
}
async getConnection(owner, issuer) {
const found = this.#connections.get(`${owner}\n${issuer}`);
return found ? { ...found } : null;
}
async saveConnection(record) {
this.#connections.set(`${record.owner}\n${record.issuer}`, { ...record });
}
async deleteConnection(owner, issuer) {
this.#connections.delete(`${owner}\n${issuer}`);
}
async createPending(record) {
if (!this.#pending.has(record.id))
this.#pending.set(record.id, structuredClone(record));
}
async getPending(id) {
const found = this.#pending.get(id);
return found ? structuredClone(found) : null;
}
async updatePending(id, patch) {
const found = this.#pending.get(id);
if (found)
Object.assign(found, patch);
}
}
export const POPPY_CONNECTION_TABLES = {
connections: 'agent_poppy_connection',
pending: 'agent_poppy_connect',
};
export function poppyConnectionTableStatements() {
const t = POPPY_CONNECTION_TABLES;
return [
`CREATE TABLE IF NOT EXISTS "${t.connections}" (
"owner" VARCHAR(255) NOT NULL,
"issuer" VARCHAR(1024) NOT NULL,
"domain" VARCHAR(255) NOT NULL,
"organization" VARCHAR(255) NOT NULL,
"scope" TEXT NOT NULL,
"status" VARCHAR(16) NOT NULL,
"state" TEXT NOT NULL,
"created_at" BIGINT NOT NULL,
"updated_at" BIGINT NOT NULL,
PRIMARY KEY ("owner", "issuer")
)`,
`CREATE TABLE IF NOT EXISTS "${t.pending}" (
"id" VARCHAR(64) PRIMARY KEY NOT NULL,
"owner" VARCHAR(255) NOT NULL,
"domain" VARCHAR(255) NOT NULL,
"issuer" VARCHAR(1024) NOT NULL,
"organization" VARCHAR(255) NOT NULL,
"scope" TEXT NOT NULL,
"actor" TEXT NULL,
"thread_id" VARCHAR(255) NULL,
"run_id" VARCHAR(255) NULL,
"tool_call_id" VARCHAR(255) NULL,
"flow" TEXT NULL,
"status" VARCHAR(16) NOT NULL,
"expires_at" BIGINT NOT NULL,
"created_at" BIGINT NOT NULL
)`,
];
}
export async function ensurePoppyConnectionTables(db) {
const mysql = isMySql(db);
for (const statement of poppyConnectionTableStatements()) {
await db.rawQuery(forDialect(statement, mysql));
}
}
const int = (v) => (typeof v === 'number' ? v : Number(v ?? 0));
const str = (v) => (v == null ? null : String(v));
/** {@link PoppyConnectionStore} over Lucid (SQLite / Postgres / MySQL); tables created on first use. */
export class LucidPoppyConnectionStore {
db;
#ready = null;
#autoCreate;
constructor(db, options = {}) {
this.db = db;
this.#autoCreate = options.autoCreateTables ?? true;
}
ready() {
if (!this.#autoCreate)
return Promise.resolve();
if (this.#ready === null) {
this.#ready = ensurePoppyConnectionTables(this.db);
this.#ready.catch(() => {
this.#ready = null;
});
}
return this.#ready;
}
#connection(row) {
return {
owner: String(row.owner),
issuer: String(row.issuer),
domain: String(row.domain),
organization: String(row.organization),
scope: String(row.scope ?? ''),
status: String(row.status),
state: String(row.state),
createdAt: int(row.created_at),
updatedAt: int(row.updated_at),
};
}
async listConnections(owner) {
await this.ready();
const rows = await this.db
.from(POPPY_CONNECTION_TABLES.connections)
.where('owner', owner)
.orderBy('domain', 'asc')
.select('*');
return rows.map((row) => this.#connection(row));
}
async getConnection(owner, issuer) {
await this.ready();
const row = await this.db
.from(POPPY_CONNECTION_TABLES.connections)
.where('owner', owner)
.where('issuer', issuer)
.first();
return row ? this.#connection(row) : null;
}
async saveConnection(record) {
await this.ready();
const columns = {
domain: record.domain,
organization: record.organization,
scope: record.scope,
status: record.status,
state: record.state,
updated_at: record.updatedAt,
};
const existing = await this.db
.from(POPPY_CONNECTION_TABLES.connections)
.where('owner', record.owner)
.where('issuer', record.issuer)
.first();
if (existing) {
await this.db
.from(POPPY_CONNECTION_TABLES.connections)
.where('owner', record.owner)
.where('issuer', record.issuer)
.update(columns);
return;
}
await this.db.table(POPPY_CONNECTION_TABLES.connections).insert({
owner: record.owner,
issuer: record.issuer,
created_at: record.createdAt,
...columns,
});
}
async deleteConnection(owner, issuer) {
await this.ready();
await this.db
.from(POPPY_CONNECTION_TABLES.connections)
.where('owner', owner)
.where('issuer', issuer)
.delete();
}
async createPending(record) {
await this.ready();
if (await this.getPending(record.id))
return;
await this.db.table(POPPY_CONNECTION_TABLES.pending).insert({
id: record.id,
owner: record.owner,
domain: record.domain,
issuer: record.issuer,
organization: record.organization,
scope: record.scope,
actor: record.actor === null ? null : JSON.stringify(record.actor),
thread_id: record.threadId,
run_id: record.runId,
tool_call_id: record.toolCallId,
flow: record.flow,
status: record.status,
expires_at: record.expiresAt,
created_at: record.createdAt,
});
}
async getPending(id) {
await this.ready();
const row = await this.db.from(POPPY_CONNECTION_TABLES.pending).where('id', id).first();
if (!row)
return null;
return {
id: String(row.id),
owner: String(row.owner),
domain: String(row.domain),
issuer: String(row.issuer),
organization: String(row.organization),
scope: String(row.scope ?? ''),
actor: row.actor == null ? null : JSON.parse(String(row.actor)),
threadId: str(row.thread_id),
runId: str(row.run_id),
toolCallId: str(row.tool_call_id),
flow: str(row.flow),
status: String(row.status),
expiresAt: int(row.expires_at),
createdAt: int(row.created_at),
};
}
async updatePending(id, patch) {
await this.ready();
const row = {};
if (patch.flow !== undefined)
row.flow = patch.flow;
if (patch.status !== undefined)
row.status = patch.status;
if (Object.keys(row).length === 0)
return;
await this.db.from(POPPY_CONNECTION_TABLES.pending).where('id', id).update(row);
}
}
/** {@link PoppyConnectionStore} over Redis: JSON values, an index set per owner. */
export function redisPoppyConnectionStore(redis, options = {}) {
const p = options.prefix ?? 'agent:poppy-connections:';
const key = (owner, issuer) => `${p}conn:${owner}\n${issuer}`;
const index = (owner) => `${p}owner:${owner}`;
const pendingKey = (id) => `${p}pending:${id}`;
const get = async (k) => {
const raw = await redis.get(k);
return raw === null ? null : JSON.parse(raw);
};
return {
async listConnections(owner) {
const issuers = await redis.smembers(index(owner));
const found = await Promise.all(issuers.map((i) => get(key(owner, i))));
return found.filter((c) => c !== null);
},
getConnection: (owner, issuer) => get(key(owner, issuer)),
async saveConnection(record) {
await redis.set(key(record.owner, record.issuer), JSON.stringify(record));
await redis.sadd(index(record.owner), record.issuer);
},
async deleteConnection(owner, issuer) {
await redis.del(key(owner, issuer));
await redis.srem(index(owner), issuer);
},
async createPending(record) {
const ttl = Math.max(1, Math.ceil((record.expiresAt - Date.now()) / 1000) + 3600);
await redis.set(pendingKey(record.id), JSON.stringify(record), 'EX', ttl, 'NX');
},
getPending: (id) => get(pendingKey(id)),
async updatePending(id, patch) {
const found = await get(pendingKey(id));
if (!found)
return;
await redis.set(pendingKey(id), JSON.stringify({ ...found, ...patch }), 'KEEPTTL');
},
};
}
//# sourceMappingURL=store.js.map