@copilotkit/sqlite-runner 1.83.0 → 2.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/sqlite-runner.cjs +10 -10
- package/dist/sqlite-runner.cjs.map +1 -1
- package/dist/sqlite-runner.d.cts +1 -1
- package/dist/sqlite-runner.d.mts +1 -1
- package/dist/sqlite-runner.mjs +1 -1
- package/dist/sqlite-runner.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/sqlite-ownership.test.ts +1 -1
- package/src/sqlite-runner.ts +2 -2
package/dist/sqlite-runner.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
|
|
2
|
-
let
|
|
2
|
+
let _copilotkit_runtime = require("@copilotkit/runtime");
|
|
3
3
|
let rxjs = require("rxjs");
|
|
4
4
|
let _ag_ui_client = require("@ag-ui/client");
|
|
5
5
|
let better_sqlite3 = require("better-sqlite3");
|
|
@@ -22,7 +22,7 @@ const SCHEMA_VERSION = 2;
|
|
|
22
22
|
* identity can hold.
|
|
23
23
|
*/
|
|
24
24
|
const LEGACY_THREAD_OWNER = "\0legacy";
|
|
25
|
-
var SqliteAgentRunner = class extends
|
|
25
|
+
var SqliteAgentRunner = class extends _copilotkit_runtime.AgentRunner {
|
|
26
26
|
db;
|
|
27
27
|
/**
|
|
28
28
|
* Live runs of this runner, by thread id, for streaming and stop. Kept per
|
|
@@ -169,7 +169,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
|
|
|
169
169
|
*/
|
|
170
170
|
isOwnedBy(threadId, ownerId) {
|
|
171
171
|
const owner = this.getThreadOwner(threadId);
|
|
172
|
-
return owner === LEGACY_THREAD_OWNER || owner === (0,
|
|
172
|
+
return owner === LEGACY_THREAD_OWNER || owner === (0, _copilotkit_runtime.ɵresolveThreadOwner)(ownerId);
|
|
173
173
|
}
|
|
174
174
|
/**
|
|
175
175
|
* False when the thread is held by an owner other than `scope`'s. A legacy
|
|
@@ -177,7 +177,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
|
|
|
177
177
|
*/
|
|
178
178
|
isThreadAccessible(threadId, scope) {
|
|
179
179
|
const owner = this.getThreadOwner(threadId);
|
|
180
|
-
return owner === void 0 || owner === LEGACY_THREAD_OWNER || owner === (0,
|
|
180
|
+
return owner === void 0 || owner === LEGACY_THREAD_OWNER || owner === (0, _copilotkit_runtime.ɵresolveThreadOwner)(scope.ownerId);
|
|
181
181
|
}
|
|
182
182
|
/**
|
|
183
183
|
* Records `owner` as the owner of a new thread, and returns true when the
|
|
@@ -196,10 +196,10 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
|
|
|
196
196
|
}).immediate();
|
|
197
197
|
}
|
|
198
198
|
run(request) {
|
|
199
|
-
const owner = (0,
|
|
200
|
-
if (!this.isThreadAccessible(request.threadId, { ownerId: owner })) throw new
|
|
201
|
-
if (this.getRunState(request.threadId).isRunning) throw new
|
|
202
|
-
if (!this.claimThread(request.threadId, owner)) throw new
|
|
199
|
+
const owner = (0, _copilotkit_runtime.ɵresolveThreadOwner)(request.ownerId);
|
|
200
|
+
if (!this.isThreadAccessible(request.threadId, { ownerId: owner })) throw new _copilotkit_runtime.ThreadAccessDeniedError();
|
|
201
|
+
if (this.getRunState(request.threadId).isRunning) throw new _copilotkit_runtime.ThreadAlreadyRunningError();
|
|
202
|
+
if (!this.claimThread(request.threadId, owner)) throw new _copilotkit_runtime.ThreadAccessDeniedError();
|
|
203
203
|
this.setRunState(request.threadId, true, request.input.runId);
|
|
204
204
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
205
205
|
const currentRunEvents = [];
|
|
@@ -273,7 +273,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
|
|
|
273
273
|
});
|
|
274
274
|
const connection = this.activeConnections.get(request.threadId);
|
|
275
275
|
ensureStoppedRunStarted(connection?.stopRequested ?? false);
|
|
276
|
-
const appendedEvents = (0,
|
|
276
|
+
const appendedEvents = (0, _copilotkit_runtime.finalizeRunEvents)(currentRunEvents, {
|
|
277
277
|
stopRequested: connection?.stopRequested ?? false,
|
|
278
278
|
protocolVersion: request.input.protocolVersion
|
|
279
279
|
});
|
|
@@ -299,7 +299,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
|
|
|
299
299
|
runId: request.input.runId
|
|
300
300
|
}, error);
|
|
301
301
|
ensureStoppedRunStarted(connection?.stopRequested ?? false);
|
|
302
|
-
const appendedEvents = (0,
|
|
302
|
+
const appendedEvents = (0, _copilotkit_runtime.finalizeRunEvents)(currentRunEvents, {
|
|
303
303
|
stopRequested: connection?.stopRequested ?? false,
|
|
304
304
|
protocolVersion: request.input.protocolVersion
|
|
305
305
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-runner.cjs","names":["AgentRunner","Database","ThreadAccessDeniedError","ThreadAlreadyRunningError","EventType","ReplaySubject"],"sources":["../src/sqlite-runner.ts"],"sourcesContent":["import {\n AgentRunner,\n ThreadAccessDeniedError,\n ThreadAlreadyRunningError,\n finalizeRunEvents,\n ɵresolveThreadOwner,\n} from \"@copilotkit/runtime/v2\";\nimport type {\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n AgentRunnerStopRequest,\n LocalThreadEndpointScope,\n} from \"@copilotkit/runtime/v2\";\nimport type { Observable } from \"rxjs\";\nimport { ReplaySubject } from \"rxjs\";\nimport type {\n AbstractAgent,\n BaseEvent,\n RunAgentInput,\n RunStartedEvent,\n} from \"@ag-ui/client\";\nimport { EventType, compactEvents } from \"@ag-ui/client\";\nimport Database from \"better-sqlite3\";\n\n/**\n * 2: adds `thread_owners`. Also written as the `version` of each stored run,\n * so runs stored from now on are tagged 2.\n */\nconst SCHEMA_VERSION = 2;\n\n/**\n * Owner of a legacy thread: one whose runs were stored before owners were\n * recorded (schema 1), so it has runs but no `thread_owners` row. Before the\n * upgrade, every caller that held a thread id could reach that thread. A\n * legacy thread keeps exactly that: it stays a capability, reachable by id for\n * every caller and every identity, and continuing it never claims it. Only a\n * thread that has no runs yet is claimed, by its first run, so no new thread\n * can become legacy. The value holds a NUL character, which no request\n * identity can hold.\n */\nconst LEGACY_THREAD_OWNER = \"\\u0000legacy\";\n\ninterface AgentRunRecord {\n id: number;\n thread_id: string;\n run_id: string;\n parent_run_id: string | null;\n events: BaseEvent[];\n input: RunAgentInput;\n created_at: number;\n version: number;\n}\n\nexport interface SqliteAgentRunnerOptions {\n dbPath?: string;\n}\n\ninterface ActiveConnectionContext {\n subject: ReplaySubject<BaseEvent>;\n agent?: AbstractAgent;\n runSubject?: ReplaySubject<BaseEvent>;\n currentEvents?: BaseEvent[];\n stopRequested?: boolean;\n}\n\nexport class SqliteAgentRunner extends AgentRunner {\n private db: any;\n\n /**\n * Live runs of this runner, by thread id, for streaming and stop. Kept per\n * instance: owners are checked against this instance's database, so a live\n * run of another runner, which may hold the same thread id for a different\n * owner, must never be bridged, stopped or torn down from here.\n */\n private readonly activeConnections = new Map<\n string,\n ActiveConnectionContext\n >();\n\n constructor(options: SqliteAgentRunnerOptions = {}) {\n super();\n const dbPath = options.dbPath ?? \":memory:\";\n\n if (!Database) {\n throw new Error(\n \"better-sqlite3 is required for SqliteAgentRunner but was not found.\\n\" +\n \"Please install it in your project:\\n\" +\n \" npm install better-sqlite3\\n\" +\n \" or\\n\" +\n \" pnpm add better-sqlite3\\n\" +\n \" or\\n\" +\n \" yarn add better-sqlite3\\n\\n\" +\n \"If you don't need persistence, use InMemoryAgentRunner instead.\",\n );\n }\n\n this.db = new Database(dbPath);\n this.initializeSchema();\n this.warnIfLegacyThreads();\n }\n\n /**\n * Logs once, when the database opens, how many legacy threads it holds\n * (see {@link LEGACY_THREAD_OWNER}), so operators know those threads are\n * still reachable by id for every caller.\n */\n private warnIfLegacyThreads(): void {\n const row = this.db\n .prepare(\n \"SELECT COUNT(DISTINCT thread_id) AS count FROM agent_runs WHERE thread_id NOT IN (SELECT thread_id FROM thread_owners)\",\n )\n .get() as { count: number } | undefined;\n const count = row?.count ?? 0;\n if (count === 0) return;\n console.warn(\n `[CopilotKit] SqliteAgentRunner: ${count} thread${count === 1 ? \" was\" : \"s were\"} ` +\n \"stored before thread owners were recorded. Anyone who holds the id of \" +\n \"one of them can still read, continue and stop it, as before the upgrade, \" +\n \"whatever identifyUser returns. Threads created from now on belong to the \" +\n \"identity whose run created them.\",\n );\n }\n\n private initializeSchema(): void {\n // Create the agent_runs table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS agent_runs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n thread_id TEXT NOT NULL,\n run_id TEXT NOT NULL UNIQUE,\n parent_run_id TEXT,\n events TEXT NOT NULL,\n input TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n version INTEGER NOT NULL\n )\n `);\n\n // Create run_state table to track active runs\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS run_state (\n thread_id TEXT PRIMARY KEY,\n is_running INTEGER DEFAULT 0,\n current_run_id TEXT,\n updated_at INTEGER NOT NULL\n )\n `);\n\n // The identity that owns each thread, recorded by the thread's first run.\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS thread_owners (\n thread_id TEXT PRIMARY KEY,\n owner_id TEXT NOT NULL,\n created_at INTEGER NOT NULL\n )\n `);\n\n // Create indexes for efficient queries\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);\n CREATE INDEX IF NOT EXISTS idx_parent_run_id ON agent_runs(parent_run_id);\n `);\n\n // Create schema version table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )\n `);\n\n // Check and set schema version\n const currentVersion = this.db\n .prepare(\n \"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\",\n )\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\n \"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\",\n )\n .run(SCHEMA_VERSION, Date.now());\n }\n }\n\n private storeRun(\n threadId: string,\n runId: string,\n events: BaseEvent[],\n input: RunAgentInput,\n parentRunId?: string | null,\n ): void {\n // Compact ONLY the events from this run\n const compactedEvents = compactEvents(events);\n\n const stmt = this.db.prepare(`\n INSERT INTO agent_runs (thread_id, run_id, parent_run_id, events, input, created_at, version)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n stmt.run(\n threadId,\n runId,\n parentRunId ?? null,\n JSON.stringify(compactedEvents), // Store only this run's compacted events\n JSON.stringify(input),\n Date.now(),\n SCHEMA_VERSION,\n );\n }\n\n private getHistoricRuns(threadId: string): AgentRunRecord[] {\n const stmt = this.db.prepare(`\n WITH RECURSIVE run_chain AS (\n -- Base case: find the root runs (those without parent)\n SELECT * FROM agent_runs \n WHERE thread_id = ? AND parent_run_id IS NULL\n \n UNION ALL\n \n -- Recursive case: find children of current level\n SELECT ar.* FROM agent_runs ar\n INNER JOIN run_chain rc ON ar.parent_run_id = rc.run_id\n WHERE ar.thread_id = ?\n )\n SELECT * FROM run_chain\n ORDER BY created_at ASC\n `);\n\n const rows = stmt.all(threadId, threadId) as any[];\n\n return rows.map((row) => ({\n id: row.id,\n thread_id: row.thread_id,\n run_id: row.run_id,\n parent_run_id: row.parent_run_id,\n events: JSON.parse(row.events),\n input: JSON.parse(row.input),\n created_at: row.created_at,\n version: row.version,\n }));\n }\n\n private getLatestRunId(threadId: string): string | null {\n const stmt = this.db.prepare(`\n SELECT run_id FROM agent_runs \n WHERE thread_id = ? \n ORDER BY created_at DESC \n LIMIT 1\n `);\n\n const result = stmt.get(threadId) as { run_id: string } | undefined;\n return result?.run_id ?? null;\n }\n\n private setRunState(\n threadId: string,\n isRunning: boolean,\n runId?: string,\n ): void {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO run_state (thread_id, is_running, current_run_id, updated_at)\n VALUES (?, ?, ?, ?)\n `);\n stmt.run(threadId, isRunning ? 1 : 0, runId ?? null, Date.now());\n }\n\n private getRunState(threadId: string): {\n isRunning: boolean;\n currentRunId: string | null;\n } {\n const stmt = this.db.prepare(`\n SELECT is_running, current_run_id FROM run_state WHERE thread_id = ?\n `);\n const result = stmt.get(threadId) as\n | { is_running: number; current_run_id: string | null }\n | undefined;\n\n return {\n isRunning: result?.is_running === 1,\n currentRunId: result?.current_run_id ?? null,\n };\n }\n\n /**\n * Owner of a thread: the recorded owner, {@link LEGACY_THREAD_OWNER} for a\n * thread whose runs predate owner records, or `undefined` for an unknown\n * thread.\n *\n * The owner record and the run check are read by one statement, so they\n * come from one snapshot of the database. With two reads, another runner on\n * the same file could claim the thread and store its first run in between,\n * and this call would see runs but no owner and take that thread for a\n * legacy one, open to every caller.\n */\n private getThreadOwner(threadId: string): string | undefined {\n const row = this.db\n .prepare(\n \"SELECT (SELECT owner_id FROM thread_owners WHERE thread_id = ?) AS owner_id, EXISTS (SELECT 1 FROM agent_runs WHERE thread_id = ?) AS has_runs\",\n )\n .get(threadId, threadId) as\n | { owner_id: string | null; has_runs: number }\n | undefined;\n if (row?.owner_id != null) return row.owner_id;\n return row?.has_runs ? LEGACY_THREAD_OWNER : undefined;\n }\n\n /**\n * True when a call acting as `ownerId` may reach an existing thread: its\n * owner, or anyone for a legacy thread.\n */\n private isOwnedBy(threadId: string, ownerId: string | undefined): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === LEGACY_THREAD_OWNER || owner === ɵresolveThreadOwner(ownerId)\n );\n }\n\n /**\n * False when the thread is held by an owner other than `scope`'s. A legacy\n * thread is held by nobody, so it is accessible to every caller.\n */\n isThreadAccessible(\n threadId: string,\n scope: LocalThreadEndpointScope,\n ): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === undefined ||\n owner === LEGACY_THREAD_OWNER ||\n owner === ɵresolveThreadOwner(scope.ownerId)\n );\n }\n\n /**\n * Records `owner` as the owner of a new thread, and returns true when the\n * run may go on as `owner`: `owner` holds the thread afterward, or the\n * thread is legacy, which stays unowned. The read, the insert and the\n * re-read run in one IMMEDIATE transaction, so when two runners or\n * processes on one database file claim a new thread at once, exactly one\n * wins and the other reads the winner's row.\n */\n private claimThread(threadId: string, owner: string): boolean {\n return this.db\n .transaction(() => {\n const current = this.getThreadOwner(threadId);\n if (current === LEGACY_THREAD_OWNER) return true;\n if (current === undefined) {\n this.db\n .prepare(\n \"INSERT INTO thread_owners (thread_id, owner_id, created_at) VALUES (?, ?, ?) ON CONFLICT(thread_id) DO NOTHING\",\n )\n .run(threadId, owner, Date.now());\n }\n return this.getThreadOwner(threadId) === owner;\n })\n .immediate();\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n // A thread belongs to the identity of its first run.\n const owner = ɵresolveThreadOwner(request.ownerId);\n if (!this.isThreadAccessible(request.threadId, { ownerId: owner })) {\n throw new ThreadAccessDeniedError();\n }\n\n // Check if thread is already running in database\n const runState = this.getRunState(request.threadId);\n if (runState.isRunning) {\n throw new ThreadAlreadyRunningError();\n }\n\n // The check above is per process. Another process on the same database\n // file can claim the thread in between, so the claim itself decides.\n if (!this.claimThread(request.threadId, owner)) {\n throw new ThreadAccessDeniedError();\n }\n\n // Mark thread as running in database\n this.setRunState(request.threadId, true, request.input.runId);\n\n // Track seen message IDs and current run events in memory for this run\n const seenMessageIds = new Set<string>();\n const currentRunEvents: BaseEvent[] = [];\n\n // Get all previously seen message IDs from historic runs\n const historicRuns = this.getHistoricRuns(request.threadId);\n const historicMessageIds = new Set<string>();\n for (const run of historicRuns) {\n for (const event of run.events) {\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n historicMessageIds.add(event.messageId);\n }\n if (event.type === EventType.RUN_STARTED) {\n const runStarted = event as RunStartedEvent;\n const messages = runStarted.input?.messages ?? [];\n for (const message of messages) {\n historicMessageIds.add(message.id);\n }\n }\n }\n }\n\n // Get or create subject for this thread's connections\n const nextSubject = new ReplaySubject<BaseEvent>(Infinity);\n const prevConnection = this.activeConnections.get(request.threadId);\n const prevSubject = prevConnection?.subject;\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // Update the active connection for this thread\n const runConnection: ActiveConnectionContext = {\n subject: nextSubject,\n agent: request.agent,\n runSubject,\n currentEvents: currentRunEvents,\n stopRequested: false,\n };\n this.activeConnections.set(request.threadId, runConnection);\n\n // Helper function to run the agent and handle errors\n const runAgent = async () => {\n // Get parent run ID for chaining\n const parentRunId = this.getLatestRunId(request.threadId);\n\n const ensureStoppedRunStarted = (stopRequested: boolean) => {\n if (\n !stopRequested ||\n currentRunEvents.some((event) => event.type === EventType.RUN_STARTED)\n ) {\n return;\n }\n // Stop can arrive before the agent sends its first event.\n const started: RunStartedEvent = {\n type: EventType.RUN_STARTED,\n threadId: request.threadId,\n runId: request.input.runId,\n input: {\n ...request.input,\n messages: request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n ),\n },\n };\n currentRunEvents.unshift(started);\n runSubject.next(started);\n nextSubject.next(started);\n };\n\n try {\n await request.agent.runAgent(request.input, {\n onEvent: ({ event }) => {\n let processedEvent: BaseEvent = event;\n if (event.type === EventType.RUN_STARTED) {\n const runStartedEvent = event as RunStartedEvent;\n if (!runStartedEvent.input) {\n const sanitizedMessages = request.input.messages\n ? request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n )\n : undefined;\n const updatedInput = {\n ...request.input,\n ...(sanitizedMessages !== undefined\n ? { messages: sanitizedMessages }\n : {}),\n };\n processedEvent = {\n ...runStartedEvent,\n input: updatedInput,\n } as RunStartedEvent;\n }\n }\n\n runSubject.next(processedEvent); // For run() return - only agent events\n nextSubject.next(processedEvent); // For connect() / store - all events\n currentRunEvents.push(processedEvent); // Accumulate for database storage\n },\n onNewMessage: ({ message }) => {\n // Called for each new message\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n },\n onRunStartedEvent: () => {\n // Mark input messages as seen without emitting duplicates\n if (request.input.messages) {\n for (const message of request.input.messages) {\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n }\n }\n },\n });\n\n const connection = this.activeConnections.get(request.threadId);\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run in database\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Complete the subjects\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n } catch (error) {\n const connection = this.activeConnections.get(request.threadId);\n if (!connection?.stopRequested) {\n console.error(\n \"Sqlite agent run failed\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n }\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run even if it failed (partial events)\n if (currentRunEvents.length > 0) {\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n }\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Don't emit error to the subject, just complete it\n // This allows subscribers to get events emitted before the error\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n }\n };\n\n // Bridge previous events if they exist\n if (prevSubject) {\n prevSubject.subscribe({\n next: (e) => nextSubject.next(e),\n error: (err) => nextSubject.error(err),\n complete: () => {\n // Don't complete nextSubject here - it needs to stay open for new events\n },\n });\n }\n\n // Start the agent execution immediately (not lazily). A failure while\n // storing or finishing the run must not leave the thread marked running\n // in the database, where it would refuse every later run, even after a\n // restart.\n runAgent().catch((error) => {\n console.error(\n \"Failed to finish sqlite agent run\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n try {\n const state = this.getRunState(request.threadId);\n if (\n state.currentRunId === null ||\n state.currentRunId === request.input.runId\n ) {\n this.setRunState(request.threadId, false);\n }\n } catch (stateError) {\n console.error(\"Failed to reset sqlite run state\", stateError);\n }\n if (this.activeConnections.get(request.threadId) === runConnection) {\n this.activeConnections.delete(request.threadId);\n }\n runSubject.complete();\n nextSubject.complete();\n });\n\n // Return the run subject (only agent events, no injected messages)\n return runSubject.asObservable();\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // A thread owned by another identity replays like an unknown one.\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n connectionSubject.complete();\n return connectionSubject.asObservable();\n }\n\n // Load historic runs from database\n const historicRuns = this.getHistoricRuns(request.threadId);\n\n // Collect all historic events from database\n const allHistoricEvents: BaseEvent[] = [];\n for (const run of historicRuns) {\n allHistoricEvents.push(...run.events);\n }\n\n // Compact all events together before emitting\n const compactedEvents = compactEvents(allHistoricEvents);\n\n // Emit compacted events and track message IDs\n const emittedMessageIds = new Set<string>();\n for (const event of compactedEvents) {\n connectionSubject.next(event);\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n emittedMessageIds.add(event.messageId);\n }\n }\n\n // Bridge active run to connection if exists\n const activeConnection = this.activeConnections.get(request.threadId);\n const runState = this.getRunState(request.threadId);\n\n if (\n activeConnection &&\n (runState.isRunning || activeConnection.stopRequested)\n ) {\n activeConnection.subject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if (\n \"messageId\" in event &&\n typeof event.messageId === \"string\" &&\n emittedMessageIds.has(event.messageId)\n ) {\n return;\n }\n connectionSubject.next(event);\n },\n complete: () => connectionSubject.complete(),\n error: (err) => connectionSubject.error(err),\n });\n } else {\n // No active run, complete after historic events\n connectionSubject.complete();\n }\n\n return connectionSubject.asObservable();\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n // Another identity's thread reports \"not running\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n const runState = this.getRunState(request.threadId);\n return Promise.resolve(runState.isRunning);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const runState = this.getRunState(request.threadId);\n if (!runState.isRunning) {\n return Promise.resolve(false);\n }\n // A run on another identity's thread reports \"no active run\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n // A run-scoped stop never stops a newer run on the thread.\n if (\n request.runId !== undefined &&\n runState.currentRunId !== request.runId\n ) {\n return Promise.resolve(false);\n }\n\n const connection = this.activeConnections.get(request.threadId);\n const agent = connection?.agent;\n\n if (!connection || !agent) {\n return Promise.resolve(false);\n }\n\n if (connection.stopRequested) {\n return Promise.resolve(false);\n }\n\n connection.stopRequested = true;\n this.setRunState(request.threadId, false);\n\n try {\n agent.abortRun();\n return Promise.resolve(true);\n } catch (error) {\n console.error(\"Failed to abort sqlite agent run\", error);\n connection.stopRequested = false;\n // Restore the run's id too, or a later stop by run id would refuse it.\n this.setRunState(\n request.threadId,\n true,\n runState.currentRunId ?? undefined,\n );\n return Promise.resolve(false);\n }\n }\n\n /**\n * Close the database connection (for cleanup)\n */\n close(): void {\n if (this.db) {\n this.db.close();\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite-runner.cjs","names":["AgentRunner","Database","ThreadAccessDeniedError","ThreadAlreadyRunningError","EventType","ReplaySubject"],"sources":["../src/sqlite-runner.ts"],"sourcesContent":["import {\n AgentRunner,\n ThreadAccessDeniedError,\n ThreadAlreadyRunningError,\n finalizeRunEvents,\n ɵresolveThreadOwner,\n} from \"@copilotkit/runtime\";\nimport type {\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n AgentRunnerStopRequest,\n LocalThreadEndpointScope,\n} from \"@copilotkit/runtime\";\nimport type { Observable } from \"rxjs\";\nimport { ReplaySubject } from \"rxjs\";\nimport type {\n AbstractAgent,\n BaseEvent,\n RunAgentInput,\n RunStartedEvent,\n} from \"@ag-ui/client\";\nimport { EventType, compactEvents } from \"@ag-ui/client\";\nimport Database from \"better-sqlite3\";\n\n/**\n * 2: adds `thread_owners`. Also written as the `version` of each stored run,\n * so runs stored from now on are tagged 2.\n */\nconst SCHEMA_VERSION = 2;\n\n/**\n * Owner of a legacy thread: one whose runs were stored before owners were\n * recorded (schema 1), so it has runs but no `thread_owners` row. Before the\n * upgrade, every caller that held a thread id could reach that thread. A\n * legacy thread keeps exactly that: it stays a capability, reachable by id for\n * every caller and every identity, and continuing it never claims it. Only a\n * thread that has no runs yet is claimed, by its first run, so no new thread\n * can become legacy. The value holds a NUL character, which no request\n * identity can hold.\n */\nconst LEGACY_THREAD_OWNER = \"\\u0000legacy\";\n\ninterface AgentRunRecord {\n id: number;\n thread_id: string;\n run_id: string;\n parent_run_id: string | null;\n events: BaseEvent[];\n input: RunAgentInput;\n created_at: number;\n version: number;\n}\n\nexport interface SqliteAgentRunnerOptions {\n dbPath?: string;\n}\n\ninterface ActiveConnectionContext {\n subject: ReplaySubject<BaseEvent>;\n agent?: AbstractAgent;\n runSubject?: ReplaySubject<BaseEvent>;\n currentEvents?: BaseEvent[];\n stopRequested?: boolean;\n}\n\nexport class SqliteAgentRunner extends AgentRunner {\n private db: any;\n\n /**\n * Live runs of this runner, by thread id, for streaming and stop. Kept per\n * instance: owners are checked against this instance's database, so a live\n * run of another runner, which may hold the same thread id for a different\n * owner, must never be bridged, stopped or torn down from here.\n */\n private readonly activeConnections = new Map<\n string,\n ActiveConnectionContext\n >();\n\n constructor(options: SqliteAgentRunnerOptions = {}) {\n super();\n const dbPath = options.dbPath ?? \":memory:\";\n\n if (!Database) {\n throw new Error(\n \"better-sqlite3 is required for SqliteAgentRunner but was not found.\\n\" +\n \"Please install it in your project:\\n\" +\n \" npm install better-sqlite3\\n\" +\n \" or\\n\" +\n \" pnpm add better-sqlite3\\n\" +\n \" or\\n\" +\n \" yarn add better-sqlite3\\n\\n\" +\n \"If you don't need persistence, use InMemoryAgentRunner instead.\",\n );\n }\n\n this.db = new Database(dbPath);\n this.initializeSchema();\n this.warnIfLegacyThreads();\n }\n\n /**\n * Logs once, when the database opens, how many legacy threads it holds\n * (see {@link LEGACY_THREAD_OWNER}), so operators know those threads are\n * still reachable by id for every caller.\n */\n private warnIfLegacyThreads(): void {\n const row = this.db\n .prepare(\n \"SELECT COUNT(DISTINCT thread_id) AS count FROM agent_runs WHERE thread_id NOT IN (SELECT thread_id FROM thread_owners)\",\n )\n .get() as { count: number } | undefined;\n const count = row?.count ?? 0;\n if (count === 0) return;\n console.warn(\n `[CopilotKit] SqliteAgentRunner: ${count} thread${count === 1 ? \" was\" : \"s were\"} ` +\n \"stored before thread owners were recorded. Anyone who holds the id of \" +\n \"one of them can still read, continue and stop it, as before the upgrade, \" +\n \"whatever identifyUser returns. Threads created from now on belong to the \" +\n \"identity whose run created them.\",\n );\n }\n\n private initializeSchema(): void {\n // Create the agent_runs table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS agent_runs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n thread_id TEXT NOT NULL,\n run_id TEXT NOT NULL UNIQUE,\n parent_run_id TEXT,\n events TEXT NOT NULL,\n input TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n version INTEGER NOT NULL\n )\n `);\n\n // Create run_state table to track active runs\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS run_state (\n thread_id TEXT PRIMARY KEY,\n is_running INTEGER DEFAULT 0,\n current_run_id TEXT,\n updated_at INTEGER NOT NULL\n )\n `);\n\n // The identity that owns each thread, recorded by the thread's first run.\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS thread_owners (\n thread_id TEXT PRIMARY KEY,\n owner_id TEXT NOT NULL,\n created_at INTEGER NOT NULL\n )\n `);\n\n // Create indexes for efficient queries\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);\n CREATE INDEX IF NOT EXISTS idx_parent_run_id ON agent_runs(parent_run_id);\n `);\n\n // Create schema version table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )\n `);\n\n // Check and set schema version\n const currentVersion = this.db\n .prepare(\n \"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\",\n )\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\n \"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\",\n )\n .run(SCHEMA_VERSION, Date.now());\n }\n }\n\n private storeRun(\n threadId: string,\n runId: string,\n events: BaseEvent[],\n input: RunAgentInput,\n parentRunId?: string | null,\n ): void {\n // Compact ONLY the events from this run\n const compactedEvents = compactEvents(events);\n\n const stmt = this.db.prepare(`\n INSERT INTO agent_runs (thread_id, run_id, parent_run_id, events, input, created_at, version)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n stmt.run(\n threadId,\n runId,\n parentRunId ?? null,\n JSON.stringify(compactedEvents), // Store only this run's compacted events\n JSON.stringify(input),\n Date.now(),\n SCHEMA_VERSION,\n );\n }\n\n private getHistoricRuns(threadId: string): AgentRunRecord[] {\n const stmt = this.db.prepare(`\n WITH RECURSIVE run_chain AS (\n -- Base case: find the root runs (those without parent)\n SELECT * FROM agent_runs \n WHERE thread_id = ? AND parent_run_id IS NULL\n \n UNION ALL\n \n -- Recursive case: find children of current level\n SELECT ar.* FROM agent_runs ar\n INNER JOIN run_chain rc ON ar.parent_run_id = rc.run_id\n WHERE ar.thread_id = ?\n )\n SELECT * FROM run_chain\n ORDER BY created_at ASC\n `);\n\n const rows = stmt.all(threadId, threadId) as any[];\n\n return rows.map((row) => ({\n id: row.id,\n thread_id: row.thread_id,\n run_id: row.run_id,\n parent_run_id: row.parent_run_id,\n events: JSON.parse(row.events),\n input: JSON.parse(row.input),\n created_at: row.created_at,\n version: row.version,\n }));\n }\n\n private getLatestRunId(threadId: string): string | null {\n const stmt = this.db.prepare(`\n SELECT run_id FROM agent_runs \n WHERE thread_id = ? \n ORDER BY created_at DESC \n LIMIT 1\n `);\n\n const result = stmt.get(threadId) as { run_id: string } | undefined;\n return result?.run_id ?? null;\n }\n\n private setRunState(\n threadId: string,\n isRunning: boolean,\n runId?: string,\n ): void {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO run_state (thread_id, is_running, current_run_id, updated_at)\n VALUES (?, ?, ?, ?)\n `);\n stmt.run(threadId, isRunning ? 1 : 0, runId ?? null, Date.now());\n }\n\n private getRunState(threadId: string): {\n isRunning: boolean;\n currentRunId: string | null;\n } {\n const stmt = this.db.prepare(`\n SELECT is_running, current_run_id FROM run_state WHERE thread_id = ?\n `);\n const result = stmt.get(threadId) as\n | { is_running: number; current_run_id: string | null }\n | undefined;\n\n return {\n isRunning: result?.is_running === 1,\n currentRunId: result?.current_run_id ?? null,\n };\n }\n\n /**\n * Owner of a thread: the recorded owner, {@link LEGACY_THREAD_OWNER} for a\n * thread whose runs predate owner records, or `undefined` for an unknown\n * thread.\n *\n * The owner record and the run check are read by one statement, so they\n * come from one snapshot of the database. With two reads, another runner on\n * the same file could claim the thread and store its first run in between,\n * and this call would see runs but no owner and take that thread for a\n * legacy one, open to every caller.\n */\n private getThreadOwner(threadId: string): string | undefined {\n const row = this.db\n .prepare(\n \"SELECT (SELECT owner_id FROM thread_owners WHERE thread_id = ?) AS owner_id, EXISTS (SELECT 1 FROM agent_runs WHERE thread_id = ?) AS has_runs\",\n )\n .get(threadId, threadId) as\n | { owner_id: string | null; has_runs: number }\n | undefined;\n if (row?.owner_id != null) return row.owner_id;\n return row?.has_runs ? LEGACY_THREAD_OWNER : undefined;\n }\n\n /**\n * True when a call acting as `ownerId` may reach an existing thread: its\n * owner, or anyone for a legacy thread.\n */\n private isOwnedBy(threadId: string, ownerId: string | undefined): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === LEGACY_THREAD_OWNER || owner === ɵresolveThreadOwner(ownerId)\n );\n }\n\n /**\n * False when the thread is held by an owner other than `scope`'s. A legacy\n * thread is held by nobody, so it is accessible to every caller.\n */\n isThreadAccessible(\n threadId: string,\n scope: LocalThreadEndpointScope,\n ): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === undefined ||\n owner === LEGACY_THREAD_OWNER ||\n owner === ɵresolveThreadOwner(scope.ownerId)\n );\n }\n\n /**\n * Records `owner` as the owner of a new thread, and returns true when the\n * run may go on as `owner`: `owner` holds the thread afterward, or the\n * thread is legacy, which stays unowned. The read, the insert and the\n * re-read run in one IMMEDIATE transaction, so when two runners or\n * processes on one database file claim a new thread at once, exactly one\n * wins and the other reads the winner's row.\n */\n private claimThread(threadId: string, owner: string): boolean {\n return this.db\n .transaction(() => {\n const current = this.getThreadOwner(threadId);\n if (current === LEGACY_THREAD_OWNER) return true;\n if (current === undefined) {\n this.db\n .prepare(\n \"INSERT INTO thread_owners (thread_id, owner_id, created_at) VALUES (?, ?, ?) ON CONFLICT(thread_id) DO NOTHING\",\n )\n .run(threadId, owner, Date.now());\n }\n return this.getThreadOwner(threadId) === owner;\n })\n .immediate();\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n // A thread belongs to the identity of its first run.\n const owner = ɵresolveThreadOwner(request.ownerId);\n if (!this.isThreadAccessible(request.threadId, { ownerId: owner })) {\n throw new ThreadAccessDeniedError();\n }\n\n // Check if thread is already running in database\n const runState = this.getRunState(request.threadId);\n if (runState.isRunning) {\n throw new ThreadAlreadyRunningError();\n }\n\n // The check above is per process. Another process on the same database\n // file can claim the thread in between, so the claim itself decides.\n if (!this.claimThread(request.threadId, owner)) {\n throw new ThreadAccessDeniedError();\n }\n\n // Mark thread as running in database\n this.setRunState(request.threadId, true, request.input.runId);\n\n // Track seen message IDs and current run events in memory for this run\n const seenMessageIds = new Set<string>();\n const currentRunEvents: BaseEvent[] = [];\n\n // Get all previously seen message IDs from historic runs\n const historicRuns = this.getHistoricRuns(request.threadId);\n const historicMessageIds = new Set<string>();\n for (const run of historicRuns) {\n for (const event of run.events) {\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n historicMessageIds.add(event.messageId);\n }\n if (event.type === EventType.RUN_STARTED) {\n const runStarted = event as RunStartedEvent;\n const messages = runStarted.input?.messages ?? [];\n for (const message of messages) {\n historicMessageIds.add(message.id);\n }\n }\n }\n }\n\n // Get or create subject for this thread's connections\n const nextSubject = new ReplaySubject<BaseEvent>(Infinity);\n const prevConnection = this.activeConnections.get(request.threadId);\n const prevSubject = prevConnection?.subject;\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // Update the active connection for this thread\n const runConnection: ActiveConnectionContext = {\n subject: nextSubject,\n agent: request.agent,\n runSubject,\n currentEvents: currentRunEvents,\n stopRequested: false,\n };\n this.activeConnections.set(request.threadId, runConnection);\n\n // Helper function to run the agent and handle errors\n const runAgent = async () => {\n // Get parent run ID for chaining\n const parentRunId = this.getLatestRunId(request.threadId);\n\n const ensureStoppedRunStarted = (stopRequested: boolean) => {\n if (\n !stopRequested ||\n currentRunEvents.some((event) => event.type === EventType.RUN_STARTED)\n ) {\n return;\n }\n // Stop can arrive before the agent sends its first event.\n const started: RunStartedEvent = {\n type: EventType.RUN_STARTED,\n threadId: request.threadId,\n runId: request.input.runId,\n input: {\n ...request.input,\n messages: request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n ),\n },\n };\n currentRunEvents.unshift(started);\n runSubject.next(started);\n nextSubject.next(started);\n };\n\n try {\n await request.agent.runAgent(request.input, {\n onEvent: ({ event }) => {\n let processedEvent: BaseEvent = event;\n if (event.type === EventType.RUN_STARTED) {\n const runStartedEvent = event as RunStartedEvent;\n if (!runStartedEvent.input) {\n const sanitizedMessages = request.input.messages\n ? request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n )\n : undefined;\n const updatedInput = {\n ...request.input,\n ...(sanitizedMessages !== undefined\n ? { messages: sanitizedMessages }\n : {}),\n };\n processedEvent = {\n ...runStartedEvent,\n input: updatedInput,\n } as RunStartedEvent;\n }\n }\n\n runSubject.next(processedEvent); // For run() return - only agent events\n nextSubject.next(processedEvent); // For connect() / store - all events\n currentRunEvents.push(processedEvent); // Accumulate for database storage\n },\n onNewMessage: ({ message }) => {\n // Called for each new message\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n },\n onRunStartedEvent: () => {\n // Mark input messages as seen without emitting duplicates\n if (request.input.messages) {\n for (const message of request.input.messages) {\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n }\n }\n },\n });\n\n const connection = this.activeConnections.get(request.threadId);\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run in database\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Complete the subjects\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n } catch (error) {\n const connection = this.activeConnections.get(request.threadId);\n if (!connection?.stopRequested) {\n console.error(\n \"Sqlite agent run failed\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n }\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run even if it failed (partial events)\n if (currentRunEvents.length > 0) {\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n }\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Don't emit error to the subject, just complete it\n // This allows subscribers to get events emitted before the error\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n }\n };\n\n // Bridge previous events if they exist\n if (prevSubject) {\n prevSubject.subscribe({\n next: (e) => nextSubject.next(e),\n error: (err) => nextSubject.error(err),\n complete: () => {\n // Don't complete nextSubject here - it needs to stay open for new events\n },\n });\n }\n\n // Start the agent execution immediately (not lazily). A failure while\n // storing or finishing the run must not leave the thread marked running\n // in the database, where it would refuse every later run, even after a\n // restart.\n runAgent().catch((error) => {\n console.error(\n \"Failed to finish sqlite agent run\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n try {\n const state = this.getRunState(request.threadId);\n if (\n state.currentRunId === null ||\n state.currentRunId === request.input.runId\n ) {\n this.setRunState(request.threadId, false);\n }\n } catch (stateError) {\n console.error(\"Failed to reset sqlite run state\", stateError);\n }\n if (this.activeConnections.get(request.threadId) === runConnection) {\n this.activeConnections.delete(request.threadId);\n }\n runSubject.complete();\n nextSubject.complete();\n });\n\n // Return the run subject (only agent events, no injected messages)\n return runSubject.asObservable();\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // A thread owned by another identity replays like an unknown one.\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n connectionSubject.complete();\n return connectionSubject.asObservable();\n }\n\n // Load historic runs from database\n const historicRuns = this.getHistoricRuns(request.threadId);\n\n // Collect all historic events from database\n const allHistoricEvents: BaseEvent[] = [];\n for (const run of historicRuns) {\n allHistoricEvents.push(...run.events);\n }\n\n // Compact all events together before emitting\n const compactedEvents = compactEvents(allHistoricEvents);\n\n // Emit compacted events and track message IDs\n const emittedMessageIds = new Set<string>();\n for (const event of compactedEvents) {\n connectionSubject.next(event);\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n emittedMessageIds.add(event.messageId);\n }\n }\n\n // Bridge active run to connection if exists\n const activeConnection = this.activeConnections.get(request.threadId);\n const runState = this.getRunState(request.threadId);\n\n if (\n activeConnection &&\n (runState.isRunning || activeConnection.stopRequested)\n ) {\n activeConnection.subject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if (\n \"messageId\" in event &&\n typeof event.messageId === \"string\" &&\n emittedMessageIds.has(event.messageId)\n ) {\n return;\n }\n connectionSubject.next(event);\n },\n complete: () => connectionSubject.complete(),\n error: (err) => connectionSubject.error(err),\n });\n } else {\n // No active run, complete after historic events\n connectionSubject.complete();\n }\n\n return connectionSubject.asObservable();\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n // Another identity's thread reports \"not running\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n const runState = this.getRunState(request.threadId);\n return Promise.resolve(runState.isRunning);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const runState = this.getRunState(request.threadId);\n if (!runState.isRunning) {\n return Promise.resolve(false);\n }\n // A run on another identity's thread reports \"no active run\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n // A run-scoped stop never stops a newer run on the thread.\n if (\n request.runId !== undefined &&\n runState.currentRunId !== request.runId\n ) {\n return Promise.resolve(false);\n }\n\n const connection = this.activeConnections.get(request.threadId);\n const agent = connection?.agent;\n\n if (!connection || !agent) {\n return Promise.resolve(false);\n }\n\n if (connection.stopRequested) {\n return Promise.resolve(false);\n }\n\n connection.stopRequested = true;\n this.setRunState(request.threadId, false);\n\n try {\n agent.abortRun();\n return Promise.resolve(true);\n } catch (error) {\n console.error(\"Failed to abort sqlite agent run\", error);\n connection.stopRequested = false;\n // Restore the run's id too, or a later stop by run id would refuse it.\n this.setRunState(\n request.threadId,\n true,\n runState.currentRunId ?? undefined,\n );\n return Promise.resolve(false);\n }\n }\n\n /**\n * Close the database connection (for cleanup)\n */\n close(): void {\n if (this.db) {\n this.db.close();\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/sqlite-runner.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest, LocalThreadEndpointScope } from "@copilotkit/runtime
|
|
1
|
+
import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest, LocalThreadEndpointScope } from "@copilotkit/runtime";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
3
|
import { BaseEvent } from "@ag-ui/client";
|
|
4
4
|
|
package/dist/sqlite-runner.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest, LocalThreadEndpointScope } from "@copilotkit/runtime
|
|
1
|
+
import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest, LocalThreadEndpointScope } from "@copilotkit/runtime";
|
|
2
2
|
import { Observable } from "rxjs";
|
|
3
3
|
import { BaseEvent } from "@ag-ui/client";
|
|
4
4
|
|
package/dist/sqlite-runner.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentRunner, ThreadAccessDeniedError, ThreadAlreadyRunningError, finalizeRunEvents, ɵresolveThreadOwner } from "@copilotkit/runtime
|
|
1
|
+
import { AgentRunner, ThreadAccessDeniedError, ThreadAlreadyRunningError, finalizeRunEvents, ɵresolveThreadOwner } from "@copilotkit/runtime";
|
|
2
2
|
import { ReplaySubject } from "rxjs";
|
|
3
3
|
import { EventType, compactEvents } from "@ag-ui/client";
|
|
4
4
|
import Database from "better-sqlite3";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-runner.mjs","names":[],"sources":["../src/sqlite-runner.ts"],"sourcesContent":["import {\n AgentRunner,\n ThreadAccessDeniedError,\n ThreadAlreadyRunningError,\n finalizeRunEvents,\n ɵresolveThreadOwner,\n} from \"@copilotkit/runtime/v2\";\nimport type {\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n AgentRunnerStopRequest,\n LocalThreadEndpointScope,\n} from \"@copilotkit/runtime/v2\";\nimport type { Observable } from \"rxjs\";\nimport { ReplaySubject } from \"rxjs\";\nimport type {\n AbstractAgent,\n BaseEvent,\n RunAgentInput,\n RunStartedEvent,\n} from \"@ag-ui/client\";\nimport { EventType, compactEvents } from \"@ag-ui/client\";\nimport Database from \"better-sqlite3\";\n\n/**\n * 2: adds `thread_owners`. Also written as the `version` of each stored run,\n * so runs stored from now on are tagged 2.\n */\nconst SCHEMA_VERSION = 2;\n\n/**\n * Owner of a legacy thread: one whose runs were stored before owners were\n * recorded (schema 1), so it has runs but no `thread_owners` row. Before the\n * upgrade, every caller that held a thread id could reach that thread. A\n * legacy thread keeps exactly that: it stays a capability, reachable by id for\n * every caller and every identity, and continuing it never claims it. Only a\n * thread that has no runs yet is claimed, by its first run, so no new thread\n * can become legacy. The value holds a NUL character, which no request\n * identity can hold.\n */\nconst LEGACY_THREAD_OWNER = \"\\u0000legacy\";\n\ninterface AgentRunRecord {\n id: number;\n thread_id: string;\n run_id: string;\n parent_run_id: string | null;\n events: BaseEvent[];\n input: RunAgentInput;\n created_at: number;\n version: number;\n}\n\nexport interface SqliteAgentRunnerOptions {\n dbPath?: string;\n}\n\ninterface ActiveConnectionContext {\n subject: ReplaySubject<BaseEvent>;\n agent?: AbstractAgent;\n runSubject?: ReplaySubject<BaseEvent>;\n currentEvents?: BaseEvent[];\n stopRequested?: boolean;\n}\n\nexport class SqliteAgentRunner extends AgentRunner {\n private db: any;\n\n /**\n * Live runs of this runner, by thread id, for streaming and stop. Kept per\n * instance: owners are checked against this instance's database, so a live\n * run of another runner, which may hold the same thread id for a different\n * owner, must never be bridged, stopped or torn down from here.\n */\n private readonly activeConnections = new Map<\n string,\n ActiveConnectionContext\n >();\n\n constructor(options: SqliteAgentRunnerOptions = {}) {\n super();\n const dbPath = options.dbPath ?? \":memory:\";\n\n if (!Database) {\n throw new Error(\n \"better-sqlite3 is required for SqliteAgentRunner but was not found.\\n\" +\n \"Please install it in your project:\\n\" +\n \" npm install better-sqlite3\\n\" +\n \" or\\n\" +\n \" pnpm add better-sqlite3\\n\" +\n \" or\\n\" +\n \" yarn add better-sqlite3\\n\\n\" +\n \"If you don't need persistence, use InMemoryAgentRunner instead.\",\n );\n }\n\n this.db = new Database(dbPath);\n this.initializeSchema();\n this.warnIfLegacyThreads();\n }\n\n /**\n * Logs once, when the database opens, how many legacy threads it holds\n * (see {@link LEGACY_THREAD_OWNER}), so operators know those threads are\n * still reachable by id for every caller.\n */\n private warnIfLegacyThreads(): void {\n const row = this.db\n .prepare(\n \"SELECT COUNT(DISTINCT thread_id) AS count FROM agent_runs WHERE thread_id NOT IN (SELECT thread_id FROM thread_owners)\",\n )\n .get() as { count: number } | undefined;\n const count = row?.count ?? 0;\n if (count === 0) return;\n console.warn(\n `[CopilotKit] SqliteAgentRunner: ${count} thread${count === 1 ? \" was\" : \"s were\"} ` +\n \"stored before thread owners were recorded. Anyone who holds the id of \" +\n \"one of them can still read, continue and stop it, as before the upgrade, \" +\n \"whatever identifyUser returns. Threads created from now on belong to the \" +\n \"identity whose run created them.\",\n );\n }\n\n private initializeSchema(): void {\n // Create the agent_runs table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS agent_runs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n thread_id TEXT NOT NULL,\n run_id TEXT NOT NULL UNIQUE,\n parent_run_id TEXT,\n events TEXT NOT NULL,\n input TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n version INTEGER NOT NULL\n )\n `);\n\n // Create run_state table to track active runs\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS run_state (\n thread_id TEXT PRIMARY KEY,\n is_running INTEGER DEFAULT 0,\n current_run_id TEXT,\n updated_at INTEGER NOT NULL\n )\n `);\n\n // The identity that owns each thread, recorded by the thread's first run.\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS thread_owners (\n thread_id TEXT PRIMARY KEY,\n owner_id TEXT NOT NULL,\n created_at INTEGER NOT NULL\n )\n `);\n\n // Create indexes for efficient queries\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);\n CREATE INDEX IF NOT EXISTS idx_parent_run_id ON agent_runs(parent_run_id);\n `);\n\n // Create schema version table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )\n `);\n\n // Check and set schema version\n const currentVersion = this.db\n .prepare(\n \"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\",\n )\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\n \"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\",\n )\n .run(SCHEMA_VERSION, Date.now());\n }\n }\n\n private storeRun(\n threadId: string,\n runId: string,\n events: BaseEvent[],\n input: RunAgentInput,\n parentRunId?: string | null,\n ): void {\n // Compact ONLY the events from this run\n const compactedEvents = compactEvents(events);\n\n const stmt = this.db.prepare(`\n INSERT INTO agent_runs (thread_id, run_id, parent_run_id, events, input, created_at, version)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n stmt.run(\n threadId,\n runId,\n parentRunId ?? null,\n JSON.stringify(compactedEvents), // Store only this run's compacted events\n JSON.stringify(input),\n Date.now(),\n SCHEMA_VERSION,\n );\n }\n\n private getHistoricRuns(threadId: string): AgentRunRecord[] {\n const stmt = this.db.prepare(`\n WITH RECURSIVE run_chain AS (\n -- Base case: find the root runs (those without parent)\n SELECT * FROM agent_runs \n WHERE thread_id = ? AND parent_run_id IS NULL\n \n UNION ALL\n \n -- Recursive case: find children of current level\n SELECT ar.* FROM agent_runs ar\n INNER JOIN run_chain rc ON ar.parent_run_id = rc.run_id\n WHERE ar.thread_id = ?\n )\n SELECT * FROM run_chain\n ORDER BY created_at ASC\n `);\n\n const rows = stmt.all(threadId, threadId) as any[];\n\n return rows.map((row) => ({\n id: row.id,\n thread_id: row.thread_id,\n run_id: row.run_id,\n parent_run_id: row.parent_run_id,\n events: JSON.parse(row.events),\n input: JSON.parse(row.input),\n created_at: row.created_at,\n version: row.version,\n }));\n }\n\n private getLatestRunId(threadId: string): string | null {\n const stmt = this.db.prepare(`\n SELECT run_id FROM agent_runs \n WHERE thread_id = ? \n ORDER BY created_at DESC \n LIMIT 1\n `);\n\n const result = stmt.get(threadId) as { run_id: string } | undefined;\n return result?.run_id ?? null;\n }\n\n private setRunState(\n threadId: string,\n isRunning: boolean,\n runId?: string,\n ): void {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO run_state (thread_id, is_running, current_run_id, updated_at)\n VALUES (?, ?, ?, ?)\n `);\n stmt.run(threadId, isRunning ? 1 : 0, runId ?? null, Date.now());\n }\n\n private getRunState(threadId: string): {\n isRunning: boolean;\n currentRunId: string | null;\n } {\n const stmt = this.db.prepare(`\n SELECT is_running, current_run_id FROM run_state WHERE thread_id = ?\n `);\n const result = stmt.get(threadId) as\n | { is_running: number; current_run_id: string | null }\n | undefined;\n\n return {\n isRunning: result?.is_running === 1,\n currentRunId: result?.current_run_id ?? null,\n };\n }\n\n /**\n * Owner of a thread: the recorded owner, {@link LEGACY_THREAD_OWNER} for a\n * thread whose runs predate owner records, or `undefined` for an unknown\n * thread.\n *\n * The owner record and the run check are read by one statement, so they\n * come from one snapshot of the database. With two reads, another runner on\n * the same file could claim the thread and store its first run in between,\n * and this call would see runs but no owner and take that thread for a\n * legacy one, open to every caller.\n */\n private getThreadOwner(threadId: string): string | undefined {\n const row = this.db\n .prepare(\n \"SELECT (SELECT owner_id FROM thread_owners WHERE thread_id = ?) AS owner_id, EXISTS (SELECT 1 FROM agent_runs WHERE thread_id = ?) AS has_runs\",\n )\n .get(threadId, threadId) as\n | { owner_id: string | null; has_runs: number }\n | undefined;\n if (row?.owner_id != null) return row.owner_id;\n return row?.has_runs ? LEGACY_THREAD_OWNER : undefined;\n }\n\n /**\n * True when a call acting as `ownerId` may reach an existing thread: its\n * owner, or anyone for a legacy thread.\n */\n private isOwnedBy(threadId: string, ownerId: string | undefined): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === LEGACY_THREAD_OWNER || owner === ɵresolveThreadOwner(ownerId)\n );\n }\n\n /**\n * False when the thread is held by an owner other than `scope`'s. A legacy\n * thread is held by nobody, so it is accessible to every caller.\n */\n isThreadAccessible(\n threadId: string,\n scope: LocalThreadEndpointScope,\n ): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === undefined ||\n owner === LEGACY_THREAD_OWNER ||\n owner === ɵresolveThreadOwner(scope.ownerId)\n );\n }\n\n /**\n * Records `owner` as the owner of a new thread, and returns true when the\n * run may go on as `owner`: `owner` holds the thread afterward, or the\n * thread is legacy, which stays unowned. The read, the insert and the\n * re-read run in one IMMEDIATE transaction, so when two runners or\n * processes on one database file claim a new thread at once, exactly one\n * wins and the other reads the winner's row.\n */\n private claimThread(threadId: string, owner: string): boolean {\n return this.db\n .transaction(() => {\n const current = this.getThreadOwner(threadId);\n if (current === LEGACY_THREAD_OWNER) return true;\n if (current === undefined) {\n this.db\n .prepare(\n \"INSERT INTO thread_owners (thread_id, owner_id, created_at) VALUES (?, ?, ?) ON CONFLICT(thread_id) DO NOTHING\",\n )\n .run(threadId, owner, Date.now());\n }\n return this.getThreadOwner(threadId) === owner;\n })\n .immediate();\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n // A thread belongs to the identity of its first run.\n const owner = ɵresolveThreadOwner(request.ownerId);\n if (!this.isThreadAccessible(request.threadId, { ownerId: owner })) {\n throw new ThreadAccessDeniedError();\n }\n\n // Check if thread is already running in database\n const runState = this.getRunState(request.threadId);\n if (runState.isRunning) {\n throw new ThreadAlreadyRunningError();\n }\n\n // The check above is per process. Another process on the same database\n // file can claim the thread in between, so the claim itself decides.\n if (!this.claimThread(request.threadId, owner)) {\n throw new ThreadAccessDeniedError();\n }\n\n // Mark thread as running in database\n this.setRunState(request.threadId, true, request.input.runId);\n\n // Track seen message IDs and current run events in memory for this run\n const seenMessageIds = new Set<string>();\n const currentRunEvents: BaseEvent[] = [];\n\n // Get all previously seen message IDs from historic runs\n const historicRuns = this.getHistoricRuns(request.threadId);\n const historicMessageIds = new Set<string>();\n for (const run of historicRuns) {\n for (const event of run.events) {\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n historicMessageIds.add(event.messageId);\n }\n if (event.type === EventType.RUN_STARTED) {\n const runStarted = event as RunStartedEvent;\n const messages = runStarted.input?.messages ?? [];\n for (const message of messages) {\n historicMessageIds.add(message.id);\n }\n }\n }\n }\n\n // Get or create subject for this thread's connections\n const nextSubject = new ReplaySubject<BaseEvent>(Infinity);\n const prevConnection = this.activeConnections.get(request.threadId);\n const prevSubject = prevConnection?.subject;\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // Update the active connection for this thread\n const runConnection: ActiveConnectionContext = {\n subject: nextSubject,\n agent: request.agent,\n runSubject,\n currentEvents: currentRunEvents,\n stopRequested: false,\n };\n this.activeConnections.set(request.threadId, runConnection);\n\n // Helper function to run the agent and handle errors\n const runAgent = async () => {\n // Get parent run ID for chaining\n const parentRunId = this.getLatestRunId(request.threadId);\n\n const ensureStoppedRunStarted = (stopRequested: boolean) => {\n if (\n !stopRequested ||\n currentRunEvents.some((event) => event.type === EventType.RUN_STARTED)\n ) {\n return;\n }\n // Stop can arrive before the agent sends its first event.\n const started: RunStartedEvent = {\n type: EventType.RUN_STARTED,\n threadId: request.threadId,\n runId: request.input.runId,\n input: {\n ...request.input,\n messages: request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n ),\n },\n };\n currentRunEvents.unshift(started);\n runSubject.next(started);\n nextSubject.next(started);\n };\n\n try {\n await request.agent.runAgent(request.input, {\n onEvent: ({ event }) => {\n let processedEvent: BaseEvent = event;\n if (event.type === EventType.RUN_STARTED) {\n const runStartedEvent = event as RunStartedEvent;\n if (!runStartedEvent.input) {\n const sanitizedMessages = request.input.messages\n ? request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n )\n : undefined;\n const updatedInput = {\n ...request.input,\n ...(sanitizedMessages !== undefined\n ? { messages: sanitizedMessages }\n : {}),\n };\n processedEvent = {\n ...runStartedEvent,\n input: updatedInput,\n } as RunStartedEvent;\n }\n }\n\n runSubject.next(processedEvent); // For run() return - only agent events\n nextSubject.next(processedEvent); // For connect() / store - all events\n currentRunEvents.push(processedEvent); // Accumulate for database storage\n },\n onNewMessage: ({ message }) => {\n // Called for each new message\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n },\n onRunStartedEvent: () => {\n // Mark input messages as seen without emitting duplicates\n if (request.input.messages) {\n for (const message of request.input.messages) {\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n }\n }\n },\n });\n\n const connection = this.activeConnections.get(request.threadId);\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run in database\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Complete the subjects\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n } catch (error) {\n const connection = this.activeConnections.get(request.threadId);\n if (!connection?.stopRequested) {\n console.error(\n \"Sqlite agent run failed\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n }\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run even if it failed (partial events)\n if (currentRunEvents.length > 0) {\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n }\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Don't emit error to the subject, just complete it\n // This allows subscribers to get events emitted before the error\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n }\n };\n\n // Bridge previous events if they exist\n if (prevSubject) {\n prevSubject.subscribe({\n next: (e) => nextSubject.next(e),\n error: (err) => nextSubject.error(err),\n complete: () => {\n // Don't complete nextSubject here - it needs to stay open for new events\n },\n });\n }\n\n // Start the agent execution immediately (not lazily). A failure while\n // storing or finishing the run must not leave the thread marked running\n // in the database, where it would refuse every later run, even after a\n // restart.\n runAgent().catch((error) => {\n console.error(\n \"Failed to finish sqlite agent run\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n try {\n const state = this.getRunState(request.threadId);\n if (\n state.currentRunId === null ||\n state.currentRunId === request.input.runId\n ) {\n this.setRunState(request.threadId, false);\n }\n } catch (stateError) {\n console.error(\"Failed to reset sqlite run state\", stateError);\n }\n if (this.activeConnections.get(request.threadId) === runConnection) {\n this.activeConnections.delete(request.threadId);\n }\n runSubject.complete();\n nextSubject.complete();\n });\n\n // Return the run subject (only agent events, no injected messages)\n return runSubject.asObservable();\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // A thread owned by another identity replays like an unknown one.\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n connectionSubject.complete();\n return connectionSubject.asObservable();\n }\n\n // Load historic runs from database\n const historicRuns = this.getHistoricRuns(request.threadId);\n\n // Collect all historic events from database\n const allHistoricEvents: BaseEvent[] = [];\n for (const run of historicRuns) {\n allHistoricEvents.push(...run.events);\n }\n\n // Compact all events together before emitting\n const compactedEvents = compactEvents(allHistoricEvents);\n\n // Emit compacted events and track message IDs\n const emittedMessageIds = new Set<string>();\n for (const event of compactedEvents) {\n connectionSubject.next(event);\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n emittedMessageIds.add(event.messageId);\n }\n }\n\n // Bridge active run to connection if exists\n const activeConnection = this.activeConnections.get(request.threadId);\n const runState = this.getRunState(request.threadId);\n\n if (\n activeConnection &&\n (runState.isRunning || activeConnection.stopRequested)\n ) {\n activeConnection.subject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if (\n \"messageId\" in event &&\n typeof event.messageId === \"string\" &&\n emittedMessageIds.has(event.messageId)\n ) {\n return;\n }\n connectionSubject.next(event);\n },\n complete: () => connectionSubject.complete(),\n error: (err) => connectionSubject.error(err),\n });\n } else {\n // No active run, complete after historic events\n connectionSubject.complete();\n }\n\n return connectionSubject.asObservable();\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n // Another identity's thread reports \"not running\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n const runState = this.getRunState(request.threadId);\n return Promise.resolve(runState.isRunning);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const runState = this.getRunState(request.threadId);\n if (!runState.isRunning) {\n return Promise.resolve(false);\n }\n // A run on another identity's thread reports \"no active run\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n // A run-scoped stop never stops a newer run on the thread.\n if (\n request.runId !== undefined &&\n runState.currentRunId !== request.runId\n ) {\n return Promise.resolve(false);\n }\n\n const connection = this.activeConnections.get(request.threadId);\n const agent = connection?.agent;\n\n if (!connection || !agent) {\n return Promise.resolve(false);\n }\n\n if (connection.stopRequested) {\n return Promise.resolve(false);\n }\n\n connection.stopRequested = true;\n this.setRunState(request.threadId, false);\n\n try {\n agent.abortRun();\n return Promise.resolve(true);\n } catch (error) {\n console.error(\"Failed to abort sqlite agent run\", error);\n connection.stopRequested = false;\n // Restore the run's id too, or a later stop by run id would refuse it.\n this.setRunState(\n request.threadId,\n true,\n runState.currentRunId ?? undefined,\n );\n return Promise.resolve(false);\n }\n }\n\n /**\n * Close the database connection (for cleanup)\n */\n close(): void {\n if (this.db) {\n this.db.close();\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite-runner.mjs","names":[],"sources":["../src/sqlite-runner.ts"],"sourcesContent":["import {\n AgentRunner,\n ThreadAccessDeniedError,\n ThreadAlreadyRunningError,\n finalizeRunEvents,\n ɵresolveThreadOwner,\n} from \"@copilotkit/runtime\";\nimport type {\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n AgentRunnerStopRequest,\n LocalThreadEndpointScope,\n} from \"@copilotkit/runtime\";\nimport type { Observable } from \"rxjs\";\nimport { ReplaySubject } from \"rxjs\";\nimport type {\n AbstractAgent,\n BaseEvent,\n RunAgentInput,\n RunStartedEvent,\n} from \"@ag-ui/client\";\nimport { EventType, compactEvents } from \"@ag-ui/client\";\nimport Database from \"better-sqlite3\";\n\n/**\n * 2: adds `thread_owners`. Also written as the `version` of each stored run,\n * so runs stored from now on are tagged 2.\n */\nconst SCHEMA_VERSION = 2;\n\n/**\n * Owner of a legacy thread: one whose runs were stored before owners were\n * recorded (schema 1), so it has runs but no `thread_owners` row. Before the\n * upgrade, every caller that held a thread id could reach that thread. A\n * legacy thread keeps exactly that: it stays a capability, reachable by id for\n * every caller and every identity, and continuing it never claims it. Only a\n * thread that has no runs yet is claimed, by its first run, so no new thread\n * can become legacy. The value holds a NUL character, which no request\n * identity can hold.\n */\nconst LEGACY_THREAD_OWNER = \"\\u0000legacy\";\n\ninterface AgentRunRecord {\n id: number;\n thread_id: string;\n run_id: string;\n parent_run_id: string | null;\n events: BaseEvent[];\n input: RunAgentInput;\n created_at: number;\n version: number;\n}\n\nexport interface SqliteAgentRunnerOptions {\n dbPath?: string;\n}\n\ninterface ActiveConnectionContext {\n subject: ReplaySubject<BaseEvent>;\n agent?: AbstractAgent;\n runSubject?: ReplaySubject<BaseEvent>;\n currentEvents?: BaseEvent[];\n stopRequested?: boolean;\n}\n\nexport class SqliteAgentRunner extends AgentRunner {\n private db: any;\n\n /**\n * Live runs of this runner, by thread id, for streaming and stop. Kept per\n * instance: owners are checked against this instance's database, so a live\n * run of another runner, which may hold the same thread id for a different\n * owner, must never be bridged, stopped or torn down from here.\n */\n private readonly activeConnections = new Map<\n string,\n ActiveConnectionContext\n >();\n\n constructor(options: SqliteAgentRunnerOptions = {}) {\n super();\n const dbPath = options.dbPath ?? \":memory:\";\n\n if (!Database) {\n throw new Error(\n \"better-sqlite3 is required for SqliteAgentRunner but was not found.\\n\" +\n \"Please install it in your project:\\n\" +\n \" npm install better-sqlite3\\n\" +\n \" or\\n\" +\n \" pnpm add better-sqlite3\\n\" +\n \" or\\n\" +\n \" yarn add better-sqlite3\\n\\n\" +\n \"If you don't need persistence, use InMemoryAgentRunner instead.\",\n );\n }\n\n this.db = new Database(dbPath);\n this.initializeSchema();\n this.warnIfLegacyThreads();\n }\n\n /**\n * Logs once, when the database opens, how many legacy threads it holds\n * (see {@link LEGACY_THREAD_OWNER}), so operators know those threads are\n * still reachable by id for every caller.\n */\n private warnIfLegacyThreads(): void {\n const row = this.db\n .prepare(\n \"SELECT COUNT(DISTINCT thread_id) AS count FROM agent_runs WHERE thread_id NOT IN (SELECT thread_id FROM thread_owners)\",\n )\n .get() as { count: number } | undefined;\n const count = row?.count ?? 0;\n if (count === 0) return;\n console.warn(\n `[CopilotKit] SqliteAgentRunner: ${count} thread${count === 1 ? \" was\" : \"s were\"} ` +\n \"stored before thread owners were recorded. Anyone who holds the id of \" +\n \"one of them can still read, continue and stop it, as before the upgrade, \" +\n \"whatever identifyUser returns. Threads created from now on belong to the \" +\n \"identity whose run created them.\",\n );\n }\n\n private initializeSchema(): void {\n // Create the agent_runs table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS agent_runs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n thread_id TEXT NOT NULL,\n run_id TEXT NOT NULL UNIQUE,\n parent_run_id TEXT,\n events TEXT NOT NULL,\n input TEXT NOT NULL,\n created_at INTEGER NOT NULL,\n version INTEGER NOT NULL\n )\n `);\n\n // Create run_state table to track active runs\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS run_state (\n thread_id TEXT PRIMARY KEY,\n is_running INTEGER DEFAULT 0,\n current_run_id TEXT,\n updated_at INTEGER NOT NULL\n )\n `);\n\n // The identity that owns each thread, recorded by the thread's first run.\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS thread_owners (\n thread_id TEXT PRIMARY KEY,\n owner_id TEXT NOT NULL,\n created_at INTEGER NOT NULL\n )\n `);\n\n // Create indexes for efficient queries\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);\n CREATE INDEX IF NOT EXISTS idx_parent_run_id ON agent_runs(parent_run_id);\n `);\n\n // Create schema version table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY,\n applied_at INTEGER NOT NULL\n )\n `);\n\n // Check and set schema version\n const currentVersion = this.db\n .prepare(\n \"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\",\n )\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\n \"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\",\n )\n .run(SCHEMA_VERSION, Date.now());\n }\n }\n\n private storeRun(\n threadId: string,\n runId: string,\n events: BaseEvent[],\n input: RunAgentInput,\n parentRunId?: string | null,\n ): void {\n // Compact ONLY the events from this run\n const compactedEvents = compactEvents(events);\n\n const stmt = this.db.prepare(`\n INSERT INTO agent_runs (thread_id, run_id, parent_run_id, events, input, created_at, version)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n `);\n\n stmt.run(\n threadId,\n runId,\n parentRunId ?? null,\n JSON.stringify(compactedEvents), // Store only this run's compacted events\n JSON.stringify(input),\n Date.now(),\n SCHEMA_VERSION,\n );\n }\n\n private getHistoricRuns(threadId: string): AgentRunRecord[] {\n const stmt = this.db.prepare(`\n WITH RECURSIVE run_chain AS (\n -- Base case: find the root runs (those without parent)\n SELECT * FROM agent_runs \n WHERE thread_id = ? AND parent_run_id IS NULL\n \n UNION ALL\n \n -- Recursive case: find children of current level\n SELECT ar.* FROM agent_runs ar\n INNER JOIN run_chain rc ON ar.parent_run_id = rc.run_id\n WHERE ar.thread_id = ?\n )\n SELECT * FROM run_chain\n ORDER BY created_at ASC\n `);\n\n const rows = stmt.all(threadId, threadId) as any[];\n\n return rows.map((row) => ({\n id: row.id,\n thread_id: row.thread_id,\n run_id: row.run_id,\n parent_run_id: row.parent_run_id,\n events: JSON.parse(row.events),\n input: JSON.parse(row.input),\n created_at: row.created_at,\n version: row.version,\n }));\n }\n\n private getLatestRunId(threadId: string): string | null {\n const stmt = this.db.prepare(`\n SELECT run_id FROM agent_runs \n WHERE thread_id = ? \n ORDER BY created_at DESC \n LIMIT 1\n `);\n\n const result = stmt.get(threadId) as { run_id: string } | undefined;\n return result?.run_id ?? null;\n }\n\n private setRunState(\n threadId: string,\n isRunning: boolean,\n runId?: string,\n ): void {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO run_state (thread_id, is_running, current_run_id, updated_at)\n VALUES (?, ?, ?, ?)\n `);\n stmt.run(threadId, isRunning ? 1 : 0, runId ?? null, Date.now());\n }\n\n private getRunState(threadId: string): {\n isRunning: boolean;\n currentRunId: string | null;\n } {\n const stmt = this.db.prepare(`\n SELECT is_running, current_run_id FROM run_state WHERE thread_id = ?\n `);\n const result = stmt.get(threadId) as\n | { is_running: number; current_run_id: string | null }\n | undefined;\n\n return {\n isRunning: result?.is_running === 1,\n currentRunId: result?.current_run_id ?? null,\n };\n }\n\n /**\n * Owner of a thread: the recorded owner, {@link LEGACY_THREAD_OWNER} for a\n * thread whose runs predate owner records, or `undefined` for an unknown\n * thread.\n *\n * The owner record and the run check are read by one statement, so they\n * come from one snapshot of the database. With two reads, another runner on\n * the same file could claim the thread and store its first run in between,\n * and this call would see runs but no owner and take that thread for a\n * legacy one, open to every caller.\n */\n private getThreadOwner(threadId: string): string | undefined {\n const row = this.db\n .prepare(\n \"SELECT (SELECT owner_id FROM thread_owners WHERE thread_id = ?) AS owner_id, EXISTS (SELECT 1 FROM agent_runs WHERE thread_id = ?) AS has_runs\",\n )\n .get(threadId, threadId) as\n | { owner_id: string | null; has_runs: number }\n | undefined;\n if (row?.owner_id != null) return row.owner_id;\n return row?.has_runs ? LEGACY_THREAD_OWNER : undefined;\n }\n\n /**\n * True when a call acting as `ownerId` may reach an existing thread: its\n * owner, or anyone for a legacy thread.\n */\n private isOwnedBy(threadId: string, ownerId: string | undefined): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === LEGACY_THREAD_OWNER || owner === ɵresolveThreadOwner(ownerId)\n );\n }\n\n /**\n * False when the thread is held by an owner other than `scope`'s. A legacy\n * thread is held by nobody, so it is accessible to every caller.\n */\n isThreadAccessible(\n threadId: string,\n scope: LocalThreadEndpointScope,\n ): boolean {\n const owner = this.getThreadOwner(threadId);\n return (\n owner === undefined ||\n owner === LEGACY_THREAD_OWNER ||\n owner === ɵresolveThreadOwner(scope.ownerId)\n );\n }\n\n /**\n * Records `owner` as the owner of a new thread, and returns true when the\n * run may go on as `owner`: `owner` holds the thread afterward, or the\n * thread is legacy, which stays unowned. The read, the insert and the\n * re-read run in one IMMEDIATE transaction, so when two runners or\n * processes on one database file claim a new thread at once, exactly one\n * wins and the other reads the winner's row.\n */\n private claimThread(threadId: string, owner: string): boolean {\n return this.db\n .transaction(() => {\n const current = this.getThreadOwner(threadId);\n if (current === LEGACY_THREAD_OWNER) return true;\n if (current === undefined) {\n this.db\n .prepare(\n \"INSERT INTO thread_owners (thread_id, owner_id, created_at) VALUES (?, ?, ?) ON CONFLICT(thread_id) DO NOTHING\",\n )\n .run(threadId, owner, Date.now());\n }\n return this.getThreadOwner(threadId) === owner;\n })\n .immediate();\n }\n\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n // A thread belongs to the identity of its first run.\n const owner = ɵresolveThreadOwner(request.ownerId);\n if (!this.isThreadAccessible(request.threadId, { ownerId: owner })) {\n throw new ThreadAccessDeniedError();\n }\n\n // Check if thread is already running in database\n const runState = this.getRunState(request.threadId);\n if (runState.isRunning) {\n throw new ThreadAlreadyRunningError();\n }\n\n // The check above is per process. Another process on the same database\n // file can claim the thread in between, so the claim itself decides.\n if (!this.claimThread(request.threadId, owner)) {\n throw new ThreadAccessDeniedError();\n }\n\n // Mark thread as running in database\n this.setRunState(request.threadId, true, request.input.runId);\n\n // Track seen message IDs and current run events in memory for this run\n const seenMessageIds = new Set<string>();\n const currentRunEvents: BaseEvent[] = [];\n\n // Get all previously seen message IDs from historic runs\n const historicRuns = this.getHistoricRuns(request.threadId);\n const historicMessageIds = new Set<string>();\n for (const run of historicRuns) {\n for (const event of run.events) {\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n historicMessageIds.add(event.messageId);\n }\n if (event.type === EventType.RUN_STARTED) {\n const runStarted = event as RunStartedEvent;\n const messages = runStarted.input?.messages ?? [];\n for (const message of messages) {\n historicMessageIds.add(message.id);\n }\n }\n }\n }\n\n // Get or create subject for this thread's connections\n const nextSubject = new ReplaySubject<BaseEvent>(Infinity);\n const prevConnection = this.activeConnections.get(request.threadId);\n const prevSubject = prevConnection?.subject;\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // Update the active connection for this thread\n const runConnection: ActiveConnectionContext = {\n subject: nextSubject,\n agent: request.agent,\n runSubject,\n currentEvents: currentRunEvents,\n stopRequested: false,\n };\n this.activeConnections.set(request.threadId, runConnection);\n\n // Helper function to run the agent and handle errors\n const runAgent = async () => {\n // Get parent run ID for chaining\n const parentRunId = this.getLatestRunId(request.threadId);\n\n const ensureStoppedRunStarted = (stopRequested: boolean) => {\n if (\n !stopRequested ||\n currentRunEvents.some((event) => event.type === EventType.RUN_STARTED)\n ) {\n return;\n }\n // Stop can arrive before the agent sends its first event.\n const started: RunStartedEvent = {\n type: EventType.RUN_STARTED,\n threadId: request.threadId,\n runId: request.input.runId,\n input: {\n ...request.input,\n messages: request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n ),\n },\n };\n currentRunEvents.unshift(started);\n runSubject.next(started);\n nextSubject.next(started);\n };\n\n try {\n await request.agent.runAgent(request.input, {\n onEvent: ({ event }) => {\n let processedEvent: BaseEvent = event;\n if (event.type === EventType.RUN_STARTED) {\n const runStartedEvent = event as RunStartedEvent;\n if (!runStartedEvent.input) {\n const sanitizedMessages = request.input.messages\n ? request.input.messages.filter(\n (message) => !historicMessageIds.has(message.id),\n )\n : undefined;\n const updatedInput = {\n ...request.input,\n ...(sanitizedMessages !== undefined\n ? { messages: sanitizedMessages }\n : {}),\n };\n processedEvent = {\n ...runStartedEvent,\n input: updatedInput,\n } as RunStartedEvent;\n }\n }\n\n runSubject.next(processedEvent); // For run() return - only agent events\n nextSubject.next(processedEvent); // For connect() / store - all events\n currentRunEvents.push(processedEvent); // Accumulate for database storage\n },\n onNewMessage: ({ message }) => {\n // Called for each new message\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n },\n onRunStartedEvent: () => {\n // Mark input messages as seen without emitting duplicates\n if (request.input.messages) {\n for (const message of request.input.messages) {\n if (!seenMessageIds.has(message.id)) {\n seenMessageIds.add(message.id);\n }\n }\n }\n },\n });\n\n const connection = this.activeConnections.get(request.threadId);\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run in database\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Complete the subjects\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n } catch (error) {\n const connection = this.activeConnections.get(request.threadId);\n if (!connection?.stopRequested) {\n console.error(\n \"Sqlite agent run failed\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n }\n ensureStoppedRunStarted(connection?.stopRequested ?? false);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\n protocolVersion: request.input.protocolVersion,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the run even if it failed (partial events)\n if (currentRunEvents.length > 0) {\n this.storeRun(\n request.threadId,\n request.input.runId,\n currentRunEvents,\n request.input,\n parentRunId,\n );\n }\n\n // Mark run as complete in database\n this.setRunState(request.threadId, false);\n\n if (connection) {\n connection.agent = undefined;\n connection.runSubject = undefined;\n connection.currentEvents = undefined;\n connection.stopRequested = false;\n }\n\n // Don't emit error to the subject, just complete it\n // This allows subscribers to get events emitted before the error\n runSubject.complete();\n nextSubject.complete();\n\n this.activeConnections.delete(request.threadId);\n }\n };\n\n // Bridge previous events if they exist\n if (prevSubject) {\n prevSubject.subscribe({\n next: (e) => nextSubject.next(e),\n error: (err) => nextSubject.error(err),\n complete: () => {\n // Don't complete nextSubject here - it needs to stay open for new events\n },\n });\n }\n\n // Start the agent execution immediately (not lazily). A failure while\n // storing or finishing the run must not leave the thread marked running\n // in the database, where it would refuse every later run, even after a\n // restart.\n runAgent().catch((error) => {\n console.error(\n \"Failed to finish sqlite agent run\",\n { threadId: request.threadId, runId: request.input.runId },\n error,\n );\n try {\n const state = this.getRunState(request.threadId);\n if (\n state.currentRunId === null ||\n state.currentRunId === request.input.runId\n ) {\n this.setRunState(request.threadId, false);\n }\n } catch (stateError) {\n console.error(\"Failed to reset sqlite run state\", stateError);\n }\n if (this.activeConnections.get(request.threadId) === runConnection) {\n this.activeConnections.delete(request.threadId);\n }\n runSubject.complete();\n nextSubject.complete();\n });\n\n // Return the run subject (only agent events, no injected messages)\n return runSubject.asObservable();\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n // A thread owned by another identity replays like an unknown one.\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n connectionSubject.complete();\n return connectionSubject.asObservable();\n }\n\n // Load historic runs from database\n const historicRuns = this.getHistoricRuns(request.threadId);\n\n // Collect all historic events from database\n const allHistoricEvents: BaseEvent[] = [];\n for (const run of historicRuns) {\n allHistoricEvents.push(...run.events);\n }\n\n // Compact all events together before emitting\n const compactedEvents = compactEvents(allHistoricEvents);\n\n // Emit compacted events and track message IDs\n const emittedMessageIds = new Set<string>();\n for (const event of compactedEvents) {\n connectionSubject.next(event);\n if (\"messageId\" in event && typeof event.messageId === \"string\") {\n emittedMessageIds.add(event.messageId);\n }\n }\n\n // Bridge active run to connection if exists\n const activeConnection = this.activeConnections.get(request.threadId);\n const runState = this.getRunState(request.threadId);\n\n if (\n activeConnection &&\n (runState.isRunning || activeConnection.stopRequested)\n ) {\n activeConnection.subject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if (\n \"messageId\" in event &&\n typeof event.messageId === \"string\" &&\n emittedMessageIds.has(event.messageId)\n ) {\n return;\n }\n connectionSubject.next(event);\n },\n complete: () => connectionSubject.complete(),\n error: (err) => connectionSubject.error(err),\n });\n } else {\n // No active run, complete after historic events\n connectionSubject.complete();\n }\n\n return connectionSubject.asObservable();\n }\n\n isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean> {\n // Another identity's thread reports \"not running\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n const runState = this.getRunState(request.threadId);\n return Promise.resolve(runState.isRunning);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const runState = this.getRunState(request.threadId);\n if (!runState.isRunning) {\n return Promise.resolve(false);\n }\n // A run on another identity's thread reports \"no active run\".\n if (!this.isOwnedBy(request.threadId, request.ownerId)) {\n return Promise.resolve(false);\n }\n // A run-scoped stop never stops a newer run on the thread.\n if (\n request.runId !== undefined &&\n runState.currentRunId !== request.runId\n ) {\n return Promise.resolve(false);\n }\n\n const connection = this.activeConnections.get(request.threadId);\n const agent = connection?.agent;\n\n if (!connection || !agent) {\n return Promise.resolve(false);\n }\n\n if (connection.stopRequested) {\n return Promise.resolve(false);\n }\n\n connection.stopRequested = true;\n this.setRunState(request.threadId, false);\n\n try {\n agent.abortRun();\n return Promise.resolve(true);\n } catch (error) {\n console.error(\"Failed to abort sqlite agent run\", error);\n connection.stopRequested = false;\n // Restore the run's id too, or a later stop by run id would refuse it.\n this.setRunState(\n request.threadId,\n true,\n runState.currentRunId ?? undefined,\n );\n return Promise.resolve(false);\n }\n }\n\n /**\n * Close the database connection (for cleanup)\n */\n close(): void {\n if (this.db) {\n this.db.close();\n }\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/sqlite-runner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-rc.1",
|
|
4
4
|
"description": "SQLite-backed agent runner for CopilotKit2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ag-ui/client": "1.0.2",
|
|
26
|
-
"
|
|
27
|
-
"
|
|
26
|
+
"@copilotkit/runtime": "2.0.0-rc.1",
|
|
27
|
+
"rxjs": "7.8.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/better-sqlite3": "^7.6.13",
|
package/src/sqlite-runner.ts
CHANGED
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
ThreadAlreadyRunningError,
|
|
5
5
|
finalizeRunEvents,
|
|
6
6
|
ɵresolveThreadOwner,
|
|
7
|
-
} from "@copilotkit/runtime
|
|
7
|
+
} from "@copilotkit/runtime";
|
|
8
8
|
import type {
|
|
9
9
|
AgentRunnerConnectRequest,
|
|
10
10
|
AgentRunnerIsRunningRequest,
|
|
11
11
|
AgentRunnerRunRequest,
|
|
12
12
|
AgentRunnerStopRequest,
|
|
13
13
|
LocalThreadEndpointScope,
|
|
14
|
-
} from "@copilotkit/runtime
|
|
14
|
+
} from "@copilotkit/runtime";
|
|
15
15
|
import type { Observable } from "rxjs";
|
|
16
16
|
import { ReplaySubject } from "rxjs";
|
|
17
17
|
import type {
|