@copilotkit/sqlite-runner 1.81.0 → 1.83.0

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.
@@ -6,16 +6,48 @@ let better_sqlite3 = require("better-sqlite3");
6
6
  better_sqlite3 = require_runtime.__toESM(better_sqlite3);
7
7
 
8
8
  //#region src/sqlite-runner.ts
9
- const SCHEMA_VERSION = 1;
10
- const ACTIVE_CONNECTIONS = /* @__PURE__ */ new Map();
9
+ /**
10
+ * 2: adds `thread_owners`. Also written as the `version` of each stored run,
11
+ * so runs stored from now on are tagged 2.
12
+ */
13
+ const SCHEMA_VERSION = 2;
14
+ /**
15
+ * Owner of a legacy thread: one whose runs were stored before owners were
16
+ * recorded (schema 1), so it has runs but no `thread_owners` row. Before the
17
+ * upgrade, every caller that held a thread id could reach that thread. A
18
+ * legacy thread keeps exactly that: it stays a capability, reachable by id for
19
+ * every caller and every identity, and continuing it never claims it. Only a
20
+ * thread that has no runs yet is claimed, by its first run, so no new thread
21
+ * can become legacy. The value holds a NUL character, which no request
22
+ * identity can hold.
23
+ */
24
+ const LEGACY_THREAD_OWNER = "\0legacy";
11
25
  var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
12
26
  db;
27
+ /**
28
+ * Live runs of this runner, by thread id, for streaming and stop. Kept per
29
+ * instance: owners are checked against this instance's database, so a live
30
+ * run of another runner, which may hold the same thread id for a different
31
+ * owner, must never be bridged, stopped or torn down from here.
32
+ */
33
+ activeConnections = /* @__PURE__ */ new Map();
13
34
  constructor(options = {}) {
14
35
  super();
15
36
  const dbPath = options.dbPath ?? ":memory:";
16
37
  if (!better_sqlite3.default) throw new Error("better-sqlite3 is required for SqliteAgentRunner but was not found.\nPlease install it in your project:\n npm install better-sqlite3\n or\n pnpm add better-sqlite3\n or\n yarn add better-sqlite3\n\nIf you don't need persistence, use InMemoryAgentRunner instead.");
17
38
  this.db = new better_sqlite3.default(dbPath);
18
39
  this.initializeSchema();
40
+ this.warnIfLegacyThreads();
41
+ }
42
+ /**
43
+ * Logs once, when the database opens, how many legacy threads it holds
44
+ * (see {@link LEGACY_THREAD_OWNER}), so operators know those threads are
45
+ * still reachable by id for every caller.
46
+ */
47
+ warnIfLegacyThreads() {
48
+ const count = this.db.prepare("SELECT COUNT(DISTINCT thread_id) AS count FROM agent_runs WHERE thread_id NOT IN (SELECT thread_id FROM thread_owners)").get()?.count ?? 0;
49
+ if (count === 0) return;
50
+ console.warn(`[CopilotKit] SqliteAgentRunner: ${count} thread${count === 1 ? " was" : "s were"} stored before thread owners were recorded. Anyone who holds the id of one of them can still read, continue and stop it, as before the upgrade, whatever identifyUser returns. Threads created from now on belong to the identity whose run created them.`);
19
51
  }
20
52
  initializeSchema() {
21
53
  this.db.exec(`
@@ -37,6 +69,13 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
37
69
  current_run_id TEXT,
38
70
  updated_at INTEGER NOT NULL
39
71
  )
72
+ `);
73
+ this.db.exec(`
74
+ CREATE TABLE IF NOT EXISTS thread_owners (
75
+ thread_id TEXT PRIMARY KEY,
76
+ owner_id TEXT NOT NULL,
77
+ created_at INTEGER NOT NULL
78
+ )
40
79
  `);
41
80
  this.db.exec(`
42
81
  CREATE INDEX IF NOT EXISTS idx_thread_id ON agent_runs(thread_id);
@@ -108,8 +147,59 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
108
147
  currentRunId: result?.current_run_id ?? null
109
148
  };
110
149
  }
150
+ /**
151
+ * Owner of a thread: the recorded owner, {@link LEGACY_THREAD_OWNER} for a
152
+ * thread whose runs predate owner records, or `undefined` for an unknown
153
+ * thread.
154
+ *
155
+ * The owner record and the run check are read by one statement, so they
156
+ * come from one snapshot of the database. With two reads, another runner on
157
+ * the same file could claim the thread and store its first run in between,
158
+ * and this call would see runs but no owner and take that thread for a
159
+ * legacy one, open to every caller.
160
+ */
161
+ getThreadOwner(threadId) {
162
+ const row = this.db.prepare("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").get(threadId, threadId);
163
+ if (row?.owner_id != null) return row.owner_id;
164
+ return row?.has_runs ? LEGACY_THREAD_OWNER : void 0;
165
+ }
166
+ /**
167
+ * True when a call acting as `ownerId` may reach an existing thread: its
168
+ * owner, or anyone for a legacy thread.
169
+ */
170
+ isOwnedBy(threadId, ownerId) {
171
+ const owner = this.getThreadOwner(threadId);
172
+ return owner === LEGACY_THREAD_OWNER || owner === (0, _copilotkit_runtime_v2.ɵresolveThreadOwner)(ownerId);
173
+ }
174
+ /**
175
+ * False when the thread is held by an owner other than `scope`'s. A legacy
176
+ * thread is held by nobody, so it is accessible to every caller.
177
+ */
178
+ isThreadAccessible(threadId, scope) {
179
+ const owner = this.getThreadOwner(threadId);
180
+ return owner === void 0 || owner === LEGACY_THREAD_OWNER || owner === (0, _copilotkit_runtime_v2.ɵresolveThreadOwner)(scope.ownerId);
181
+ }
182
+ /**
183
+ * Records `owner` as the owner of a new thread, and returns true when the
184
+ * run may go on as `owner`: `owner` holds the thread afterward, or the
185
+ * thread is legacy, which stays unowned. The read, the insert and the
186
+ * re-read run in one IMMEDIATE transaction, so when two runners or
187
+ * processes on one database file claim a new thread at once, exactly one
188
+ * wins and the other reads the winner's row.
189
+ */
190
+ claimThread(threadId, owner) {
191
+ return this.db.transaction(() => {
192
+ const current = this.getThreadOwner(threadId);
193
+ if (current === LEGACY_THREAD_OWNER) return true;
194
+ if (current === void 0) this.db.prepare("INSERT INTO thread_owners (thread_id, owner_id, created_at) VALUES (?, ?, ?) ON CONFLICT(thread_id) DO NOTHING").run(threadId, owner, Date.now());
195
+ return this.getThreadOwner(threadId) === owner;
196
+ }).immediate();
197
+ }
111
198
  run(request) {
199
+ const owner = (0, _copilotkit_runtime_v2.ɵresolveThreadOwner)(request.ownerId);
200
+ if (!this.isThreadAccessible(request.threadId, { ownerId: owner })) throw new _copilotkit_runtime_v2.ThreadAccessDeniedError();
112
201
  if (this.getRunState(request.threadId).isRunning) throw new _copilotkit_runtime_v2.ThreadAlreadyRunningError();
202
+ if (!this.claimThread(request.threadId, owner)) throw new _copilotkit_runtime_v2.ThreadAccessDeniedError();
113
203
  this.setRunState(request.threadId, true, request.input.runId);
114
204
  const seenMessageIds = /* @__PURE__ */ new Set();
115
205
  const currentRunEvents = [];
@@ -123,15 +213,16 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
123
213
  }
124
214
  }
125
215
  const nextSubject = new rxjs.ReplaySubject(Infinity);
126
- const prevSubject = ACTIVE_CONNECTIONS.get(request.threadId)?.subject;
216
+ const prevSubject = this.activeConnections.get(request.threadId)?.subject;
127
217
  const runSubject = new rxjs.ReplaySubject(Infinity);
128
- ACTIVE_CONNECTIONS.set(request.threadId, {
218
+ const runConnection = {
129
219
  subject: nextSubject,
130
220
  agent: request.agent,
131
221
  runSubject,
132
222
  currentEvents: currentRunEvents,
133
223
  stopRequested: false
134
- });
224
+ };
225
+ this.activeConnections.set(request.threadId, runConnection);
135
226
  const runAgent = async () => {
136
227
  const parentRunId = this.getLatestRunId(request.threadId);
137
228
  const ensureStoppedRunStarted = (stopRequested) => {
@@ -180,7 +271,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
180
271
  }
181
272
  }
182
273
  });
183
- const connection = ACTIVE_CONNECTIONS.get(request.threadId);
274
+ const connection = this.activeConnections.get(request.threadId);
184
275
  ensureStoppedRunStarted(connection?.stopRequested ?? false);
185
276
  const appendedEvents = (0, _copilotkit_runtime_v2.finalizeRunEvents)(currentRunEvents, {
186
277
  stopRequested: connection?.stopRequested ?? false,
@@ -200,9 +291,13 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
200
291
  }
201
292
  runSubject.complete();
202
293
  nextSubject.complete();
203
- ACTIVE_CONNECTIONS.delete(request.threadId);
204
- } catch {
205
- const connection = ACTIVE_CONNECTIONS.get(request.threadId);
294
+ this.activeConnections.delete(request.threadId);
295
+ } catch (error) {
296
+ const connection = this.activeConnections.get(request.threadId);
297
+ if (!connection?.stopRequested) console.error("Sqlite agent run failed", {
298
+ threadId: request.threadId,
299
+ runId: request.input.runId
300
+ }, error);
206
301
  ensureStoppedRunStarted(connection?.stopRequested ?? false);
207
302
  const appendedEvents = (0, _copilotkit_runtime_v2.finalizeRunEvents)(currentRunEvents, {
208
303
  stopRequested: connection?.stopRequested ?? false,
@@ -222,7 +317,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
222
317
  }
223
318
  runSubject.complete();
224
319
  nextSubject.complete();
225
- ACTIVE_CONNECTIONS.delete(request.threadId);
320
+ this.activeConnections.delete(request.threadId);
226
321
  }
227
322
  };
228
323
  if (prevSubject) prevSubject.subscribe({
@@ -230,11 +325,29 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
230
325
  error: (err) => nextSubject.error(err),
231
326
  complete: () => {}
232
327
  });
233
- runAgent();
328
+ runAgent().catch((error) => {
329
+ console.error("Failed to finish sqlite agent run", {
330
+ threadId: request.threadId,
331
+ runId: request.input.runId
332
+ }, error);
333
+ try {
334
+ const state = this.getRunState(request.threadId);
335
+ if (state.currentRunId === null || state.currentRunId === request.input.runId) this.setRunState(request.threadId, false);
336
+ } catch (stateError) {
337
+ console.error("Failed to reset sqlite run state", stateError);
338
+ }
339
+ if (this.activeConnections.get(request.threadId) === runConnection) this.activeConnections.delete(request.threadId);
340
+ runSubject.complete();
341
+ nextSubject.complete();
342
+ });
234
343
  return runSubject.asObservable();
235
344
  }
236
345
  connect(request) {
237
346
  const connectionSubject = new rxjs.ReplaySubject(Infinity);
347
+ if (!this.isOwnedBy(request.threadId, request.ownerId)) {
348
+ connectionSubject.complete();
349
+ return connectionSubject.asObservable();
350
+ }
238
351
  const historicRuns = this.getHistoricRuns(request.threadId);
239
352
  const allHistoricEvents = [];
240
353
  for (const run of historicRuns) allHistoricEvents.push(...run.events);
@@ -244,7 +357,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
244
357
  connectionSubject.next(event);
245
358
  if ("messageId" in event && typeof event.messageId === "string") emittedMessageIds.add(event.messageId);
246
359
  }
247
- const activeConnection = ACTIVE_CONNECTIONS.get(request.threadId);
360
+ const activeConnection = this.activeConnections.get(request.threadId);
248
361
  const runState = this.getRunState(request.threadId);
249
362
  if (activeConnection && (runState.isRunning || activeConnection.stopRequested)) activeConnection.subject.subscribe({
250
363
  next: (event) => {
@@ -258,12 +371,16 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
258
371
  return connectionSubject.asObservable();
259
372
  }
260
373
  isRunning(request) {
374
+ if (!this.isOwnedBy(request.threadId, request.ownerId)) return Promise.resolve(false);
261
375
  const runState = this.getRunState(request.threadId);
262
376
  return Promise.resolve(runState.isRunning);
263
377
  }
264
378
  stop(request) {
265
- if (!this.getRunState(request.threadId).isRunning) return Promise.resolve(false);
266
- const connection = ACTIVE_CONNECTIONS.get(request.threadId);
379
+ const runState = this.getRunState(request.threadId);
380
+ if (!runState.isRunning) return Promise.resolve(false);
381
+ if (!this.isOwnedBy(request.threadId, request.ownerId)) return Promise.resolve(false);
382
+ if (request.runId !== void 0 && runState.currentRunId !== request.runId) return Promise.resolve(false);
383
+ const connection = this.activeConnections.get(request.threadId);
267
384
  const agent = connection?.agent;
268
385
  if (!connection || !agent) return Promise.resolve(false);
269
386
  if (connection.stopRequested) return Promise.resolve(false);
@@ -275,7 +392,7 @@ var SqliteAgentRunner = class extends _copilotkit_runtime_v2.AgentRunner {
275
392
  } catch (error) {
276
393
  console.error("Failed to abort sqlite agent run", error);
277
394
  connection.stopRequested = false;
278
- this.setRunState(request.threadId, true);
395
+ this.setRunState(request.threadId, true, runState.currentRunId ?? void 0);
279
396
  return Promise.resolve(false);
280
397
  }
281
398
  }
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-runner.cjs","names":["AgentRunner","Database","ThreadAlreadyRunningError","EventType","ReplaySubject"],"sources":["../src/sqlite-runner.ts"],"sourcesContent":["import {\n AgentRunner,\n ThreadAlreadyRunningError,\n finalizeRunEvents,\n} from \"@copilotkit/runtime/v2\";\nimport type {\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n AgentRunnerStopRequest,\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\nconst SCHEMA_VERSION = 1;\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\n// Active connections for streaming events and stop support\nconst ACTIVE_CONNECTIONS = new Map<string, ActiveConnectionContext>();\n\nexport class SqliteAgentRunner extends AgentRunner {\n private db: any;\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 }\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 // 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 run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\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 // 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 = ACTIVE_CONNECTIONS.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 ACTIVE_CONNECTIONS.set(request.threadId, {\n subject: nextSubject,\n agent: request.agent,\n runSubject,\n currentEvents: currentRunEvents,\n stopRequested: false,\n });\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 = ACTIVE_CONNECTIONS.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 ACTIVE_CONNECTIONS.delete(request.threadId);\n } catch {\n const connection = ACTIVE_CONNECTIONS.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 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 ACTIVE_CONNECTIONS.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)\n runAgent();\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 // 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 = ACTIVE_CONNECTIONS.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 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\n const connection = ACTIVE_CONNECTIONS.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 this.setRunState(request.threadId, true);\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/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,4 +1,4 @@
1
- import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest } from "@copilotkit/runtime/v2";
1
+ import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest, LocalThreadEndpointScope } from "@copilotkit/runtime/v2";
2
2
  import { Observable } from "rxjs";
3
3
  import { BaseEvent } from "@ag-ui/client";
4
4
 
@@ -8,13 +8,57 @@ interface SqliteAgentRunnerOptions {
8
8
  }
9
9
  declare class SqliteAgentRunner extends AgentRunner {
10
10
  private db;
11
+ /**
12
+ * Live runs of this runner, by thread id, for streaming and stop. Kept per
13
+ * instance: owners are checked against this instance's database, so a live
14
+ * run of another runner, which may hold the same thread id for a different
15
+ * owner, must never be bridged, stopped or torn down from here.
16
+ */
17
+ private readonly activeConnections;
11
18
  constructor(options?: SqliteAgentRunnerOptions);
19
+ /**
20
+ * Logs once, when the database opens, how many legacy threads it holds
21
+ * (see {@link LEGACY_THREAD_OWNER}), so operators know those threads are
22
+ * still reachable by id for every caller.
23
+ */
24
+ private warnIfLegacyThreads;
12
25
  private initializeSchema;
13
26
  private storeRun;
14
27
  private getHistoricRuns;
15
28
  private getLatestRunId;
16
29
  private setRunState;
17
30
  private getRunState;
31
+ /**
32
+ * Owner of a thread: the recorded owner, {@link LEGACY_THREAD_OWNER} for a
33
+ * thread whose runs predate owner records, or `undefined` for an unknown
34
+ * thread.
35
+ *
36
+ * The owner record and the run check are read by one statement, so they
37
+ * come from one snapshot of the database. With two reads, another runner on
38
+ * the same file could claim the thread and store its first run in between,
39
+ * and this call would see runs but no owner and take that thread for a
40
+ * legacy one, open to every caller.
41
+ */
42
+ private getThreadOwner;
43
+ /**
44
+ * True when a call acting as `ownerId` may reach an existing thread: its
45
+ * owner, or anyone for a legacy thread.
46
+ */
47
+ private isOwnedBy;
48
+ /**
49
+ * False when the thread is held by an owner other than `scope`'s. A legacy
50
+ * thread is held by nobody, so it is accessible to every caller.
51
+ */
52
+ isThreadAccessible(threadId: string, scope: LocalThreadEndpointScope): boolean;
53
+ /**
54
+ * Records `owner` as the owner of a new thread, and returns true when the
55
+ * run may go on as `owner`: `owner` holds the thread afterward, or the
56
+ * thread is legacy, which stays unowned. The read, the insert and the
57
+ * re-read run in one IMMEDIATE transaction, so when two runners or
58
+ * processes on one database file claim a new thread at once, exactly one
59
+ * wins and the other reads the winner's row.
60
+ */
61
+ private claimThread;
18
62
  run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
19
63
  connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
20
64
  isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-runner.d.cts","names":[],"sources":["../src/sqlite-runner.ts"],"mappings":";;;;;UAmCiB,wBAAA;EACf,MAAA;AAAA;AAAA,cAcW,iBAAA,SAA0B,WAAA;EAAA,QAC7B,EAAA;cAEI,OAAA,GAAS,wBAAA;EAAA,QAqBb,gBAAA;EAAA,QAuDA,QAAA;EAAA,QA0BA,eAAA;EAAA,QAgCA,cAAA;EAAA,QAYA,WAAA;EAAA,QAYA,WAAA;EAiBR,GAAA,CAAI,OAAA,EAAS,qBAAA,GAAwB,UAAA,CAAW,SAAA;EA4NhD,OAAA,CAAQ,OAAA,EAAS,yBAAA,GAA4B,UAAA,CAAW,SAAA;EAuDxD,SAAA,CAAU,OAAA,EAAS,2BAAA,GAA8B,OAAA;EAKjD,IAAA,CAAK,OAAA,EAAS,sBAAA,GAAyB,OAAA;EALpB;;;EAuCnB,KAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"sqlite-runner.d.cts","names":[],"sources":["../src/sqlite-runner.ts"],"mappings":";;;;;UAsDiB,wBAAA;EACf,MAAA;AAAA;AAAA,cAWW,iBAAA,SAA0B,WAAA;EAAA,QAC7B,EAAA;EAZF;AAWR;;;;;EAXQ,iBAoBW,iBAAA;cAKL,OAAA,GAAS,wBAAA;EA0RgB;;;;;EAAA,QA/P7B,mBAAA;EAAA,QAiBA,gBAAA;EAAA,QAgEA,QAAA;EAAA,QA0BA,eAAA;EAAA,QAgCA,cAAA;EAAA,QAYA,WAAA;EAAA,QAYA,WAAA;EA3MA;;;;;;;;;;;EAAA,QAuOA,cAAA;EAAA;;;;EAAA,QAgBA,SAAA;EAaN;;;;EAFF,kBAAA,CACE,QAAA,UACA,KAAA,EAAO,wBAAA;EAmC4B;;;;;;;;EAAA,QAjB7B,WAAA;EAiBR,GAAA,CAAI,OAAA,EAAS,qBAAA,GAAwB,UAAA,CAAW,SAAA;EAyQhD,OAAA,CAAQ,OAAA,EAAS,yBAAA,GAA4B,UAAA,CAAW,SAAA;EA6DxD,SAAA,CAAU,OAAA,EAAS,2BAAA,GAA8B,OAAA;EASjD,IAAA,CAAK,OAAA,EAAS,sBAAA,GAAyB,OAAA;EAAlC;;;EAkDL,KAAA,CAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest } from "@copilotkit/runtime/v2";
1
+ import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest, LocalThreadEndpointScope } from "@copilotkit/runtime/v2";
2
2
  import { Observable } from "rxjs";
3
3
  import { BaseEvent } from "@ag-ui/client";
4
4
 
@@ -8,13 +8,57 @@ interface SqliteAgentRunnerOptions {
8
8
  }
9
9
  declare class SqliteAgentRunner extends AgentRunner {
10
10
  private db;
11
+ /**
12
+ * Live runs of this runner, by thread id, for streaming and stop. Kept per
13
+ * instance: owners are checked against this instance's database, so a live
14
+ * run of another runner, which may hold the same thread id for a different
15
+ * owner, must never be bridged, stopped or torn down from here.
16
+ */
17
+ private readonly activeConnections;
11
18
  constructor(options?: SqliteAgentRunnerOptions);
19
+ /**
20
+ * Logs once, when the database opens, how many legacy threads it holds
21
+ * (see {@link LEGACY_THREAD_OWNER}), so operators know those threads are
22
+ * still reachable by id for every caller.
23
+ */
24
+ private warnIfLegacyThreads;
12
25
  private initializeSchema;
13
26
  private storeRun;
14
27
  private getHistoricRuns;
15
28
  private getLatestRunId;
16
29
  private setRunState;
17
30
  private getRunState;
31
+ /**
32
+ * Owner of a thread: the recorded owner, {@link LEGACY_THREAD_OWNER} for a
33
+ * thread whose runs predate owner records, or `undefined` for an unknown
34
+ * thread.
35
+ *
36
+ * The owner record and the run check are read by one statement, so they
37
+ * come from one snapshot of the database. With two reads, another runner on
38
+ * the same file could claim the thread and store its first run in between,
39
+ * and this call would see runs but no owner and take that thread for a
40
+ * legacy one, open to every caller.
41
+ */
42
+ private getThreadOwner;
43
+ /**
44
+ * True when a call acting as `ownerId` may reach an existing thread: its
45
+ * owner, or anyone for a legacy thread.
46
+ */
47
+ private isOwnedBy;
48
+ /**
49
+ * False when the thread is held by an owner other than `scope`'s. A legacy
50
+ * thread is held by nobody, so it is accessible to every caller.
51
+ */
52
+ isThreadAccessible(threadId: string, scope: LocalThreadEndpointScope): boolean;
53
+ /**
54
+ * Records `owner` as the owner of a new thread, and returns true when the
55
+ * run may go on as `owner`: `owner` holds the thread afterward, or the
56
+ * thread is legacy, which stays unowned. The read, the insert and the
57
+ * re-read run in one IMMEDIATE transaction, so when two runners or
58
+ * processes on one database file claim a new thread at once, exactly one
59
+ * wins and the other reads the winner's row.
60
+ */
61
+ private claimThread;
18
62
  run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
19
63
  connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
20
64
  isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-runner.d.mts","names":[],"sources":["../src/sqlite-runner.ts"],"mappings":";;;;;UAmCiB,wBAAA;EACf,MAAA;AAAA;AAAA,cAcW,iBAAA,SAA0B,WAAA;EAAA,QAC7B,EAAA;cAEI,OAAA,GAAS,wBAAA;EAAA,QAqBb,gBAAA;EAAA,QAuDA,QAAA;EAAA,QA0BA,eAAA;EAAA,QAgCA,cAAA;EAAA,QAYA,WAAA;EAAA,QAYA,WAAA;EAiBR,GAAA,CAAI,OAAA,EAAS,qBAAA,GAAwB,UAAA,CAAW,SAAA;EA4NhD,OAAA,CAAQ,OAAA,EAAS,yBAAA,GAA4B,UAAA,CAAW,SAAA;EAuDxD,SAAA,CAAU,OAAA,EAAS,2BAAA,GAA8B,OAAA;EAKjD,IAAA,CAAK,OAAA,EAAS,sBAAA,GAAyB,OAAA;EALpB;;;EAuCnB,KAAA,CAAA;AAAA"}
1
+ {"version":3,"file":"sqlite-runner.d.mts","names":[],"sources":["../src/sqlite-runner.ts"],"mappings":";;;;;UAsDiB,wBAAA;EACf,MAAA;AAAA;AAAA,cAWW,iBAAA,SAA0B,WAAA;EAAA,QAC7B,EAAA;EAZF;AAWR;;;;;EAXQ,iBAoBW,iBAAA;cAKL,OAAA,GAAS,wBAAA;EA0RgB;;;;;EAAA,QA/P7B,mBAAA;EAAA,QAiBA,gBAAA;EAAA,QAgEA,QAAA;EAAA,QA0BA,eAAA;EAAA,QAgCA,cAAA;EAAA,QAYA,WAAA;EAAA,QAYA,WAAA;EA3MA;;;;;;;;;;;EAAA,QAuOA,cAAA;EAAA;;;;EAAA,QAgBA,SAAA;EAaN;;;;EAFF,kBAAA,CACE,QAAA,UACA,KAAA,EAAO,wBAAA;EAmC4B;;;;;;;;EAAA,QAjB7B,WAAA;EAiBR,GAAA,CAAI,OAAA,EAAS,qBAAA,GAAwB,UAAA,CAAW,SAAA;EAyQhD,OAAA,CAAQ,OAAA,EAAS,yBAAA,GAA4B,UAAA,CAAW,SAAA;EA6DxD,SAAA,CAAU,OAAA,EAAS,2BAAA,GAA8B,OAAA;EASjD,IAAA,CAAK,OAAA,EAAS,sBAAA,GAAyB,OAAA;EAAlC;;;EAkDL,KAAA,CAAA;AAAA"}