@copilotkitnext/sqlite-runner 0.0.17-alpha.0 → 0.0.18

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.
@@ -1,23 +1,23 @@
1
1
 
2
2
  
3
- > @copilotkitnext/sqlite-runner@0.0.17-alpha.0 build /Users/brandonmcconnell/Projects/@CopilotKit/vnext_experimental/packages/sqlite-runner
3
+ > @copilotkitnext/sqlite-runner@0.0.18 build /Users/mme/Projects/CopilotKit2/main/packages/sqlite-runner
4
4
  > tsup
5
5
 
6
6
  CLI Building entry: src/index.ts
7
7
  CLI Using tsconfig: tsconfig.json
8
8
  CLI tsup v8.5.0
9
- CLI Using tsup config: /Users/brandonmcconnell/Projects/@CopilotKit/vnext_experimental/packages/sqlite-runner/tsup.config.ts
9
+ CLI Using tsup config: /Users/mme/Projects/CopilotKit2/main/packages/sqlite-runner/tsup.config.ts
10
10
  CLI Target: es2022
11
11
  CLI Cleaning output folder
12
12
  CJS Build start
13
13
  ESM Build start
14
- ESM dist/index.mjs 9.50 KB
15
- ESM dist/index.mjs.map 18.71 KB
16
- ESM ⚡️ Build success in 12ms
17
- CJS dist/index.js 11.26 KB
18
- CJS dist/index.js.map 18.80 KB
14
+ ESM dist/index.mjs 11.55 KB
15
+ ESM dist/index.mjs.map 22.22 KB
16
+ ESM ⚡️ Build success in 11ms
17
+ CJS dist/index.js 13.32 KB
18
+ CJS dist/index.js.map 22.31 KB
19
19
  CJS ⚡️ Build success in 12ms
20
20
  DTS Build start
21
- DTS ⚡️ Build success in 793ms
22
- DTS dist/index.d.ts 1010.00 B
23
- DTS dist/index.d.mts 1010.00 B
21
+ DTS ⚡️ Build success in 1174ms
22
+ DTS dist/index.d.ts 1009.00 B
23
+ DTS dist/index.d.mts 1009.00 B
package/dist/index.d.mts CHANGED
@@ -17,7 +17,7 @@ declare class SqliteAgentRunner extends AgentRunner {
17
17
  run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
18
18
  connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
19
19
  isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
20
- stop(_request: AgentRunnerStopRequest): Promise<boolean | undefined>;
20
+ stop(request: AgentRunnerStopRequest): Promise<boolean | undefined>;
21
21
  /**
22
22
  * Close the database connection (for cleanup)
23
23
  */
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ declare class SqliteAgentRunner extends AgentRunner {
17
17
  run(request: AgentRunnerRunRequest): Observable<BaseEvent>;
18
18
  connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
19
19
  isRunning(request: AgentRunnerIsRunningRequest): Promise<boolean>;
20
- stop(_request: AgentRunnerStopRequest): Promise<boolean | undefined>;
20
+ stop(request: AgentRunnerStopRequest): Promise<boolean | undefined>;
21
21
  /**
22
22
  * Close the database connection (for cleanup)
23
23
  */
package/dist/index.js CHANGED
@@ -188,9 +188,16 @@ var SqliteAgentRunner = class extends import_runtime.AgentRunner {
188
188
  }
189
189
  }
190
190
  const nextSubject = new import_rxjs.ReplaySubject(Infinity);
191
- const prevSubject = ACTIVE_CONNECTIONS.get(request.threadId);
192
- ACTIVE_CONNECTIONS.set(request.threadId, nextSubject);
191
+ const prevConnection = ACTIVE_CONNECTIONS.get(request.threadId);
192
+ const prevSubject = prevConnection?.subject;
193
193
  const runSubject = new import_rxjs.ReplaySubject(Infinity);
194
+ ACTIVE_CONNECTIONS.set(request.threadId, {
195
+ subject: nextSubject,
196
+ agent: request.agent,
197
+ runSubject,
198
+ currentEvents: currentRunEvents,
199
+ stopRequested: false
200
+ });
194
201
  const runAgent = async () => {
195
202
  const parentRunId = this.getLatestRunId(request.threadId);
196
203
  try {
@@ -232,6 +239,14 @@ var SqliteAgentRunner = class extends import_runtime.AgentRunner {
232
239
  }
233
240
  }
234
241
  });
242
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
243
+ const appendedEvents = (0, import_runtime.finalizeRunEvents)(currentRunEvents, {
244
+ stopRequested: connection?.stopRequested ?? false
245
+ });
246
+ for (const event of appendedEvents) {
247
+ runSubject.next(event);
248
+ nextSubject.next(event);
249
+ }
235
250
  this.storeRun(
236
251
  request.threadId,
237
252
  request.input.runId,
@@ -240,9 +255,24 @@ var SqliteAgentRunner = class extends import_runtime.AgentRunner {
240
255
  parentRunId
241
256
  );
242
257
  this.setRunState(request.threadId, false);
258
+ if (connection) {
259
+ connection.agent = void 0;
260
+ connection.runSubject = void 0;
261
+ connection.currentEvents = void 0;
262
+ connection.stopRequested = false;
263
+ }
243
264
  runSubject.complete();
244
265
  nextSubject.complete();
266
+ ACTIVE_CONNECTIONS.delete(request.threadId);
245
267
  } catch {
268
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
269
+ const appendedEvents = (0, import_runtime.finalizeRunEvents)(currentRunEvents, {
270
+ stopRequested: connection?.stopRequested ?? false
271
+ });
272
+ for (const event of appendedEvents) {
273
+ runSubject.next(event);
274
+ nextSubject.next(event);
275
+ }
246
276
  if (currentRunEvents.length > 0) {
247
277
  this.storeRun(
248
278
  request.threadId,
@@ -253,8 +283,15 @@ var SqliteAgentRunner = class extends import_runtime.AgentRunner {
253
283
  );
254
284
  }
255
285
  this.setRunState(request.threadId, false);
286
+ if (connection) {
287
+ connection.agent = void 0;
288
+ connection.runSubject = void 0;
289
+ connection.currentEvents = void 0;
290
+ connection.stopRequested = false;
291
+ }
256
292
  runSubject.complete();
257
293
  nextSubject.complete();
294
+ ACTIVE_CONNECTIONS.delete(request.threadId);
258
295
  }
259
296
  };
260
297
  if (prevSubject) {
@@ -283,10 +320,10 @@ var SqliteAgentRunner = class extends import_runtime.AgentRunner {
283
320
  emittedMessageIds.add(event.messageId);
284
321
  }
285
322
  }
286
- const activeSubject = ACTIVE_CONNECTIONS.get(request.threadId);
323
+ const activeConnection = ACTIVE_CONNECTIONS.get(request.threadId);
287
324
  const runState = this.getRunState(request.threadId);
288
- if (activeSubject && runState.isRunning) {
289
- activeSubject.subscribe({
325
+ if (activeConnection && (runState.isRunning || activeConnection.stopRequested)) {
326
+ activeConnection.subject.subscribe({
290
327
  next: (event) => {
291
328
  if ("messageId" in event && typeof event.messageId === "string" && emittedMessageIds.has(event.messageId)) {
292
329
  return;
@@ -305,9 +342,30 @@ var SqliteAgentRunner = class extends import_runtime.AgentRunner {
305
342
  const runState = this.getRunState(request.threadId);
306
343
  return Promise.resolve(runState.isRunning);
307
344
  }
308
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
309
- stop(_request) {
310
- throw new Error("Method not implemented.");
345
+ stop(request) {
346
+ const runState = this.getRunState(request.threadId);
347
+ if (!runState.isRunning) {
348
+ return Promise.resolve(false);
349
+ }
350
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
351
+ const agent = connection?.agent;
352
+ if (!connection || !agent) {
353
+ return Promise.resolve(false);
354
+ }
355
+ if (connection.stopRequested) {
356
+ return Promise.resolve(false);
357
+ }
358
+ connection.stopRequested = true;
359
+ this.setRunState(request.threadId, false);
360
+ try {
361
+ agent.abortRun();
362
+ return Promise.resolve(true);
363
+ } catch (error) {
364
+ console.error("Failed to abort sqlite agent run", error);
365
+ connection.stopRequested = false;
366
+ this.setRunState(request.threadId, true);
367
+ return Promise.resolve(false);
368
+ }
311
369
  }
312
370
  /**
313
371
  * Close the database connection (for cleanup)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/sqlite-runner.ts"],"sourcesContent":["export * from \"./sqlite-runner\";\n","import {\n AgentRunner,\n type AgentRunnerConnectRequest,\n type AgentRunnerIsRunningRequest,\n type AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"@copilotkitnext/runtime\";\nimport { Observable, ReplaySubject } from \"rxjs\";\nimport {\n BaseEvent,\n RunAgentInput,\n EventType,\n RunStartedEvent,\n compactEvents,\n} 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\n// Active connections for streaming events\n// This is the only in-memory state we need - just for active streaming\nconst ACTIVE_CONNECTIONS = new Map<string, ReplaySubject<BaseEvent>>();\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(\"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\")\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\")\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(threadId: string, isRunning: boolean, runId?: string): 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): { isRunning: boolean; currentRunId: string | null } {\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 { is_running: number; current_run_id: string | null } | 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 Error(\"Thread already running\");\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 prevSubject = ACTIVE_CONNECTIONS.get(request.threadId);\n \n // Update the active connection for this thread\n ACTIVE_CONNECTIONS.set(request.threadId, nextSubject);\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\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 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 // 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 // Complete the subjects\n runSubject.complete();\n nextSubject.complete();\n } catch {\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 // 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 };\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 activeSubject = ACTIVE_CONNECTIONS.get(request.threadId);\n const runState = this.getRunState(request.threadId);\n \n if (activeSubject && runState.isRunning) {\n activeSubject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if ('messageId' in event && typeof event.messageId === 'string' && emittedMessageIds.has(event.messageId)) {\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 // eslint-disable-next-line @typescript-eslint/no-unused-vars\n stop(_request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n throw new Error(\"Method not implemented.\");\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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","names":["Database"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/sqlite-runner.ts"],"sourcesContent":["export * from \"./sqlite-runner\";\n","import {\n AgentRunner,\n finalizeRunEvents,\n type AgentRunnerConnectRequest,\n type AgentRunnerIsRunningRequest,\n type AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"@copilotkitnext/runtime\";\nimport { Observable, ReplaySubject } from \"rxjs\";\nimport {\n AbstractAgent,\n BaseEvent,\n RunAgentInput,\n EventType,\n RunStartedEvent,\n compactEvents,\n} 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(\"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\")\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\")\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(threadId: string, isRunning: boolean, runId?: string): 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): { isRunning: boolean; currentRunId: string | null } {\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 { is_running: number; current_run_id: string | null } | 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 Error(\"Thread already running\");\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 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 const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\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 const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\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 (activeConnection && (runState.isRunning || activeConnection.stopRequested)) {\n activeConnection.subject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if ('messageId' in event && typeof event.messageId === 'string' && emittedMessageIds.has(event.messageId)) {\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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","names":["Database"]}
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/sqlite-runner.ts
2
2
  import {
3
- AgentRunner
3
+ AgentRunner,
4
+ finalizeRunEvents
4
5
  } from "@copilotkitnext/runtime";
5
6
  import { ReplaySubject } from "rxjs";
6
7
  import {
@@ -157,9 +158,16 @@ var SqliteAgentRunner = class extends AgentRunner {
157
158
  }
158
159
  }
159
160
  const nextSubject = new ReplaySubject(Infinity);
160
- const prevSubject = ACTIVE_CONNECTIONS.get(request.threadId);
161
- ACTIVE_CONNECTIONS.set(request.threadId, nextSubject);
161
+ const prevConnection = ACTIVE_CONNECTIONS.get(request.threadId);
162
+ const prevSubject = prevConnection?.subject;
162
163
  const runSubject = new ReplaySubject(Infinity);
164
+ ACTIVE_CONNECTIONS.set(request.threadId, {
165
+ subject: nextSubject,
166
+ agent: request.agent,
167
+ runSubject,
168
+ currentEvents: currentRunEvents,
169
+ stopRequested: false
170
+ });
163
171
  const runAgent = async () => {
164
172
  const parentRunId = this.getLatestRunId(request.threadId);
165
173
  try {
@@ -201,6 +209,14 @@ var SqliteAgentRunner = class extends AgentRunner {
201
209
  }
202
210
  }
203
211
  });
212
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
213
+ const appendedEvents = finalizeRunEvents(currentRunEvents, {
214
+ stopRequested: connection?.stopRequested ?? false
215
+ });
216
+ for (const event of appendedEvents) {
217
+ runSubject.next(event);
218
+ nextSubject.next(event);
219
+ }
204
220
  this.storeRun(
205
221
  request.threadId,
206
222
  request.input.runId,
@@ -209,9 +225,24 @@ var SqliteAgentRunner = class extends AgentRunner {
209
225
  parentRunId
210
226
  );
211
227
  this.setRunState(request.threadId, false);
228
+ if (connection) {
229
+ connection.agent = void 0;
230
+ connection.runSubject = void 0;
231
+ connection.currentEvents = void 0;
232
+ connection.stopRequested = false;
233
+ }
212
234
  runSubject.complete();
213
235
  nextSubject.complete();
236
+ ACTIVE_CONNECTIONS.delete(request.threadId);
214
237
  } catch {
238
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
239
+ const appendedEvents = finalizeRunEvents(currentRunEvents, {
240
+ stopRequested: connection?.stopRequested ?? false
241
+ });
242
+ for (const event of appendedEvents) {
243
+ runSubject.next(event);
244
+ nextSubject.next(event);
245
+ }
215
246
  if (currentRunEvents.length > 0) {
216
247
  this.storeRun(
217
248
  request.threadId,
@@ -222,8 +253,15 @@ var SqliteAgentRunner = class extends AgentRunner {
222
253
  );
223
254
  }
224
255
  this.setRunState(request.threadId, false);
256
+ if (connection) {
257
+ connection.agent = void 0;
258
+ connection.runSubject = void 0;
259
+ connection.currentEvents = void 0;
260
+ connection.stopRequested = false;
261
+ }
225
262
  runSubject.complete();
226
263
  nextSubject.complete();
264
+ ACTIVE_CONNECTIONS.delete(request.threadId);
227
265
  }
228
266
  };
229
267
  if (prevSubject) {
@@ -252,10 +290,10 @@ var SqliteAgentRunner = class extends AgentRunner {
252
290
  emittedMessageIds.add(event.messageId);
253
291
  }
254
292
  }
255
- const activeSubject = ACTIVE_CONNECTIONS.get(request.threadId);
293
+ const activeConnection = ACTIVE_CONNECTIONS.get(request.threadId);
256
294
  const runState = this.getRunState(request.threadId);
257
- if (activeSubject && runState.isRunning) {
258
- activeSubject.subscribe({
295
+ if (activeConnection && (runState.isRunning || activeConnection.stopRequested)) {
296
+ activeConnection.subject.subscribe({
259
297
  next: (event) => {
260
298
  if ("messageId" in event && typeof event.messageId === "string" && emittedMessageIds.has(event.messageId)) {
261
299
  return;
@@ -274,9 +312,30 @@ var SqliteAgentRunner = class extends AgentRunner {
274
312
  const runState = this.getRunState(request.threadId);
275
313
  return Promise.resolve(runState.isRunning);
276
314
  }
277
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
278
- stop(_request) {
279
- throw new Error("Method not implemented.");
315
+ stop(request) {
316
+ const runState = this.getRunState(request.threadId);
317
+ if (!runState.isRunning) {
318
+ return Promise.resolve(false);
319
+ }
320
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
321
+ const agent = connection?.agent;
322
+ if (!connection || !agent) {
323
+ return Promise.resolve(false);
324
+ }
325
+ if (connection.stopRequested) {
326
+ return Promise.resolve(false);
327
+ }
328
+ connection.stopRequested = true;
329
+ this.setRunState(request.threadId, false);
330
+ try {
331
+ agent.abortRun();
332
+ return Promise.resolve(true);
333
+ } catch (error) {
334
+ console.error("Failed to abort sqlite agent run", error);
335
+ connection.stopRequested = false;
336
+ this.setRunState(request.threadId, true);
337
+ return Promise.resolve(false);
338
+ }
280
339
  }
281
340
  /**
282
341
  * Close the database connection (for cleanup)
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sqlite-runner.ts"],"sourcesContent":["import {\n AgentRunner,\n type AgentRunnerConnectRequest,\n type AgentRunnerIsRunningRequest,\n type AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"@copilotkitnext/runtime\";\nimport { Observable, ReplaySubject } from \"rxjs\";\nimport {\n BaseEvent,\n RunAgentInput,\n EventType,\n RunStartedEvent,\n compactEvents,\n} 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\n// Active connections for streaming events\n// This is the only in-memory state we need - just for active streaming\nconst ACTIVE_CONNECTIONS = new Map<string, ReplaySubject<BaseEvent>>();\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(\"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\")\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\")\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(threadId: string, isRunning: boolean, runId?: string): 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): { isRunning: boolean; currentRunId: string | null } {\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 { is_running: number; current_run_id: string | null } | 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 Error(\"Thread already running\");\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 prevSubject = ACTIVE_CONNECTIONS.get(request.threadId);\n \n // Update the active connection for this thread\n ACTIVE_CONNECTIONS.set(request.threadId, nextSubject);\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\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 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 // 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 // Complete the subjects\n runSubject.complete();\n nextSubject.complete();\n } catch {\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 // 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 };\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 activeSubject = ACTIVE_CONNECTIONS.get(request.threadId);\n const runState = this.getRunState(request.threadId);\n \n if (activeSubject && runState.isRunning) {\n activeSubject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if ('messageId' in event && typeof event.messageId === 'string' && emittedMessageIds.has(event.messageId)) {\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 // eslint-disable-next-line @typescript-eslint/no-unused-vars\n stop(_request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n throw new Error(\"Method not implemented.\");\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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","names":[]}
1
+ {"version":3,"sources":["../src/sqlite-runner.ts"],"sourcesContent":["import {\n AgentRunner,\n finalizeRunEvents,\n type AgentRunnerConnectRequest,\n type AgentRunnerIsRunningRequest,\n type AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"@copilotkitnext/runtime\";\nimport { Observable, ReplaySubject } from \"rxjs\";\nimport {\n AbstractAgent,\n BaseEvent,\n RunAgentInput,\n EventType,\n RunStartedEvent,\n compactEvents,\n} 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(\"SELECT version FROM schema_version ORDER BY version DESC LIMIT 1\")\n .get() as { version: number } | undefined;\n\n if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {\n this.db\n .prepare(\"INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)\")\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(threadId: string, isRunning: boolean, runId?: string): 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): { isRunning: boolean; currentRunId: string | null } {\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 { is_running: number; current_run_id: string | null } | 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 Error(\"Thread already running\");\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 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 const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\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 const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: connection?.stopRequested ?? false,\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 (activeConnection && (runState.isRunning || activeConnection.stopRequested)) {\n activeConnection.subject.subscribe({\n next: (event) => {\n // Skip message events that we've already emitted from historic\n if ('messageId' in event && typeof event.messageId === 'string' && emittedMessageIds.has(event.messageId)) {\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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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkitnext/sqlite-runner",
3
- "version": "0.0.17-alpha.0",
3
+ "version": "0.0.18",
4
4
  "description": "SQLite-backed agent runner for CopilotKit2",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "@ag-ui/client": "0.0.40-alpha.6",
30
30
  "rxjs": "7.8.1",
31
- "@copilotkitnext/runtime": "0.0.17-alpha.0"
31
+ "@copilotkitnext/runtime": "0.0.18"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "better-sqlite3": "^12.2.0"
@@ -6,6 +6,7 @@ import {
6
6
  EventType,
7
7
  Message,
8
8
  RunAgentInput,
9
+ RunFinishedEvent,
9
10
  RunStartedEvent,
10
11
  TextMessageContentEvent,
11
12
  TextMessageEndEvent,
@@ -46,6 +47,19 @@ class MockAgent extends AbstractAgent {
46
47
  for (const event of this.events) {
47
48
  await callbacks.onEvent({ event });
48
49
  }
50
+
51
+ const hasTerminalEvent = this.events.some((event) =>
52
+ event.type === EventType.RUN_FINISHED || event.type === EventType.RUN_ERROR,
53
+ );
54
+
55
+ if (!hasTerminalEvent) {
56
+ const runFinished: RunFinishedEvent = {
57
+ type: EventType.RUN_FINISHED,
58
+ threadId: input.threadId,
59
+ runId: input.runId,
60
+ };
61
+ await callbacks.onEvent({ event: runFinished });
62
+ }
49
63
  }
50
64
 
51
65
  protected run(): ReturnType<AbstractAgent["run"]> {
@@ -61,6 +75,101 @@ class MockAgent extends AbstractAgent {
61
75
  }
62
76
  }
63
77
 
78
+ class StoppableAgent extends AbstractAgent {
79
+ private shouldStop = false;
80
+ private eventDelay: number;
81
+
82
+ constructor(eventDelay = 5) {
83
+ super();
84
+ this.eventDelay = eventDelay;
85
+ }
86
+
87
+ async runAgent(
88
+ input: RunAgentInput,
89
+ callbacks: RunCallbacks,
90
+ ): Promise<void> {
91
+ this.shouldStop = false;
92
+ let counter = 0;
93
+
94
+ const runStarted: RunStartedEvent = {
95
+ type: EventType.RUN_STARTED,
96
+ threadId: input.threadId,
97
+ runId: input.runId,
98
+ };
99
+ await callbacks.onEvent({ event: runStarted });
100
+ await callbacks.onRunStartedEvent?.();
101
+
102
+ while (!this.shouldStop && counter < 10_000) {
103
+ await new Promise((resolve) => setTimeout(resolve, this.eventDelay));
104
+ const event: BaseEvent = {
105
+ type: EventType.TEXT_MESSAGE_CONTENT,
106
+ messageId: `sqlite-stop-${counter}`,
107
+ delta: `chunk-${counter}`,
108
+ } as TextMessageContentEvent;
109
+ await callbacks.onEvent({ event });
110
+ counter += 1;
111
+ }
112
+ }
113
+
114
+ abortRun(): void {
115
+ this.shouldStop = true;
116
+ }
117
+
118
+ clone(): AbstractAgent {
119
+ return new StoppableAgent(this.eventDelay);
120
+ }
121
+ }
122
+
123
+ class OpenEventsAgent extends AbstractAgent {
124
+ private shouldStop = false;
125
+
126
+ async runAgent(
127
+ input: RunAgentInput,
128
+ callbacks: RunCallbacks,
129
+ ): Promise<void> {
130
+ this.shouldStop = false;
131
+ const messageId = "open-message";
132
+ const toolCallId = "open-tool";
133
+
134
+ await callbacks.onEvent({
135
+ event: {
136
+ type: EventType.TEXT_MESSAGE_START,
137
+ messageId,
138
+ role: "assistant",
139
+ } as BaseEvent,
140
+ });
141
+
142
+ await callbacks.onEvent({
143
+ event: {
144
+ type: EventType.TEXT_MESSAGE_CONTENT,
145
+ messageId,
146
+ delta: "Partial content",
147
+ } as BaseEvent,
148
+ });
149
+
150
+ await callbacks.onEvent({
151
+ event: {
152
+ type: EventType.TOOL_CALL_START,
153
+ toolCallId,
154
+ toolCallName: "testTool",
155
+ parentMessageId: messageId,
156
+ } as BaseEvent,
157
+ });
158
+
159
+ while (!this.shouldStop) {
160
+ await new Promise((resolve) => setTimeout(resolve, 5));
161
+ }
162
+ }
163
+
164
+ abortRun(): void {
165
+ this.shouldStop = true;
166
+ }
167
+
168
+ clone(): AbstractAgent {
169
+ return new OpenEventsAgent();
170
+ }
171
+ }
172
+
64
173
  describe("SqliteAgentRunner", () => {
65
174
  let tempDir: string;
66
175
  let dbPath: string;
@@ -83,6 +192,7 @@ describe("SqliteAgentRunner", () => {
83
192
  { type: EventType.TEXT_MESSAGE_START, messageId: "msg-1", role: "assistant" } as TextMessageStartEvent,
84
193
  { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg-1", delta: "Hello" } as TextMessageContentEvent,
85
194
  { type: EventType.TEXT_MESSAGE_END, messageId: "msg-1" } as TextMessageEndEvent,
195
+ { type: EventType.RUN_FINISHED, threadId, runId: "run-1" } as RunFinishedEvent,
86
196
  ]);
87
197
 
88
198
  const events = await firstValueFrom(
@@ -100,6 +210,7 @@ describe("SqliteAgentRunner", () => {
100
210
  EventType.TEXT_MESSAGE_START,
101
211
  EventType.TEXT_MESSAGE_CONTENT,
102
212
  EventType.TEXT_MESSAGE_END,
213
+ EventType.RUN_FINISHED,
103
214
  ]);
104
215
  });
105
216
 
@@ -171,6 +282,11 @@ describe("SqliteAgentRunner", () => {
171
282
  runId: "run-keep",
172
283
  input: providedInput,
173
284
  } as RunStartedEvent,
285
+ {
286
+ type: EventType.RUN_FINISHED,
287
+ threadId,
288
+ runId: "run-keep",
289
+ } as RunFinishedEvent,
174
290
  ],
175
291
  false,
176
292
  );
@@ -190,7 +306,10 @@ describe("SqliteAgentRunner", () => {
190
306
  .pipe(toArray()),
191
307
  );
192
308
 
193
- expect(events).toHaveLength(1);
309
+ expect(events.map((event) => event.type)).toEqual([
310
+ EventType.RUN_STARTED,
311
+ EventType.RUN_FINISHED,
312
+ ]);
194
313
  const runStarted = events[0] as RunStartedEvent;
195
314
  expect(runStarted.input).toBe(providedInput);
196
315
  });
@@ -201,6 +320,7 @@ describe("SqliteAgentRunner", () => {
201
320
  { type: EventType.TEXT_MESSAGE_START, messageId: "msg", role: "assistant" } as TextMessageStartEvent,
202
321
  { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg", delta: "hi" } as TextMessageContentEvent,
203
322
  { type: EventType.TEXT_MESSAGE_END, messageId: "msg" } as TextMessageEndEvent,
323
+ { type: EventType.RUN_FINISHED, threadId, runId: "run-1" } as RunFinishedEvent,
204
324
  ]);
205
325
 
206
326
  await firstValueFrom(
@@ -221,6 +341,62 @@ describe("SqliteAgentRunner", () => {
221
341
  EventType.TEXT_MESSAGE_START,
222
342
  EventType.TEXT_MESSAGE_CONTENT,
223
343
  EventType.TEXT_MESSAGE_END,
344
+ EventType.RUN_FINISHED,
345
+ ]);
346
+ });
347
+
348
+ it("returns false when stopping a thread that is not running", async () => {
349
+ await expect(runner.stop({ threadId: "sqlite-missing" })).resolves.toBe(false);
350
+ });
351
+
352
+ it("stops an active run and completes observables", async () => {
353
+ const threadId = "sqlite-stop";
354
+ const agent = new StoppableAgent(2);
355
+ const input: RunAgentInput = {
356
+ threadId,
357
+ runId: "sqlite-stop-run",
358
+ messages: [],
359
+ state: {},
360
+ };
361
+
362
+ const run$ = runner.run({ threadId, agent, input });
363
+ const collected = firstValueFrom(run$.pipe(toArray()));
364
+
365
+ await new Promise((resolve) => setTimeout(resolve, 20));
366
+ expect(await runner.isRunning({ threadId })).toBe(true);
367
+
368
+ const stopped = await runner.stop({ threadId });
369
+ expect(stopped).toBe(true);
370
+
371
+ const events = await collected;
372
+ expect(events.length).toBeGreaterThan(0);
373
+ expect(events[events.length - 1].type).toBe(EventType.RUN_FINISHED);
374
+ expect(await runner.isRunning({ threadId })).toBe(false);
375
+ });
376
+
377
+ it("closes open text and tool events when stopping", async () => {
378
+ const threadId = "sqlite-open-events";
379
+ const agent = new OpenEventsAgent();
380
+ const input: RunAgentInput = {
381
+ threadId,
382
+ runId: "sqlite-open-run",
383
+ messages: [],
384
+ state: {},
385
+ };
386
+
387
+ const run$ = runner.run({ threadId, agent, input });
388
+ const collected = firstValueFrom(run$.pipe(toArray()));
389
+
390
+ await new Promise((resolve) => setTimeout(resolve, 20));
391
+ await runner.stop({ threadId });
392
+
393
+ const events = await collected;
394
+ const endingTypes = events.slice(-4).map((event) => event.type);
395
+ expect(endingTypes).toEqual([
396
+ EventType.TEXT_MESSAGE_END,
397
+ EventType.TOOL_CALL_END,
398
+ EventType.TOOL_CALL_RESULT,
399
+ EventType.RUN_FINISHED,
224
400
  ]);
225
401
  });
226
402
  });
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  AgentRunner,
3
+ finalizeRunEvents,
3
4
  type AgentRunnerConnectRequest,
4
5
  type AgentRunnerIsRunningRequest,
5
6
  type AgentRunnerRunRequest,
@@ -7,6 +8,7 @@ import {
7
8
  } from "@copilotkitnext/runtime";
8
9
  import { Observable, ReplaySubject } from "rxjs";
9
10
  import {
11
+ AbstractAgent,
10
12
  BaseEvent,
11
13
  RunAgentInput,
12
14
  EventType,
@@ -32,9 +34,16 @@ export interface SqliteAgentRunnerOptions {
32
34
  dbPath?: string;
33
35
  }
34
36
 
35
- // Active connections for streaming events
36
- // This is the only in-memory state we need - just for active streaming
37
- const ACTIVE_CONNECTIONS = new Map<string, ReplaySubject<BaseEvent>>();
37
+ interface ActiveConnectionContext {
38
+ subject: ReplaySubject<BaseEvent>;
39
+ agent?: AbstractAgent;
40
+ runSubject?: ReplaySubject<BaseEvent>;
41
+ currentEvents?: BaseEvent[];
42
+ stopRequested?: boolean;
43
+ }
44
+
45
+ // Active connections for streaming events and stop support
46
+ const ACTIVE_CONNECTIONS = new Map<string, ActiveConnectionContext>();
38
47
 
39
48
  export class SqliteAgentRunner extends AgentRunner {
40
49
  private db: any;
@@ -235,14 +244,21 @@ export class SqliteAgentRunner extends AgentRunner {
235
244
 
236
245
  // Get or create subject for this thread's connections
237
246
  const nextSubject = new ReplaySubject<BaseEvent>(Infinity);
238
- const prevSubject = ACTIVE_CONNECTIONS.get(request.threadId);
247
+ const prevConnection = ACTIVE_CONNECTIONS.get(request.threadId);
248
+ const prevSubject = prevConnection?.subject;
239
249
 
240
- // Update the active connection for this thread
241
- ACTIVE_CONNECTIONS.set(request.threadId, nextSubject);
242
-
243
250
  // Create a subject for run() return value
244
251
  const runSubject = new ReplaySubject<BaseEvent>(Infinity);
245
252
 
253
+ // Update the active connection for this thread
254
+ ACTIVE_CONNECTIONS.set(request.threadId, {
255
+ subject: nextSubject,
256
+ agent: request.agent,
257
+ runSubject,
258
+ currentEvents: currentRunEvents,
259
+ stopRequested: false,
260
+ });
261
+
246
262
  // Helper function to run the agent and handle errors
247
263
  const runAgent = async () => {
248
264
  // Get parent run ID for chaining
@@ -295,6 +311,15 @@ export class SqliteAgentRunner extends AgentRunner {
295
311
  },
296
312
  });
297
313
 
314
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
315
+ const appendedEvents = finalizeRunEvents(currentRunEvents, {
316
+ stopRequested: connection?.stopRequested ?? false,
317
+ });
318
+ for (const event of appendedEvents) {
319
+ runSubject.next(event);
320
+ nextSubject.next(event);
321
+ }
322
+
298
323
  // Store the run in database
299
324
  this.storeRun(
300
325
  request.threadId,
@@ -306,11 +331,29 @@ export class SqliteAgentRunner extends AgentRunner {
306
331
 
307
332
  // Mark run as complete in database
308
333
  this.setRunState(request.threadId, false);
309
-
334
+
335
+ if (connection) {
336
+ connection.agent = undefined;
337
+ connection.runSubject = undefined;
338
+ connection.currentEvents = undefined;
339
+ connection.stopRequested = false;
340
+ }
341
+
310
342
  // Complete the subjects
311
343
  runSubject.complete();
312
344
  nextSubject.complete();
345
+
346
+ ACTIVE_CONNECTIONS.delete(request.threadId);
313
347
  } catch {
348
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
349
+ const appendedEvents = finalizeRunEvents(currentRunEvents, {
350
+ stopRequested: connection?.stopRequested ?? false,
351
+ });
352
+ for (const event of appendedEvents) {
353
+ runSubject.next(event);
354
+ nextSubject.next(event);
355
+ }
356
+
314
357
  // Store the run even if it failed (partial events)
315
358
  if (currentRunEvents.length > 0) {
316
359
  this.storeRun(
@@ -324,11 +367,20 @@ export class SqliteAgentRunner extends AgentRunner {
324
367
 
325
368
  // Mark run as complete in database
326
369
  this.setRunState(request.threadId, false);
327
-
370
+
371
+ if (connection) {
372
+ connection.agent = undefined;
373
+ connection.runSubject = undefined;
374
+ connection.currentEvents = undefined;
375
+ connection.stopRequested = false;
376
+ }
377
+
328
378
  // Don't emit error to the subject, just complete it
329
379
  // This allows subscribers to get events emitted before the error
330
380
  runSubject.complete();
331
381
  nextSubject.complete();
382
+
383
+ ACTIVE_CONNECTIONS.delete(request.threadId);
332
384
  }
333
385
  };
334
386
 
@@ -375,11 +427,11 @@ export class SqliteAgentRunner extends AgentRunner {
375
427
  }
376
428
 
377
429
  // Bridge active run to connection if exists
378
- const activeSubject = ACTIVE_CONNECTIONS.get(request.threadId);
430
+ const activeConnection = ACTIVE_CONNECTIONS.get(request.threadId);
379
431
  const runState = this.getRunState(request.threadId);
380
-
381
- if (activeSubject && runState.isRunning) {
382
- activeSubject.subscribe({
432
+
433
+ if (activeConnection && (runState.isRunning || activeConnection.stopRequested)) {
434
+ activeConnection.subject.subscribe({
383
435
  next: (event) => {
384
436
  // Skip message events that we've already emitted from historic
385
437
  if ('messageId' in event && typeof event.messageId === 'string' && emittedMessageIds.has(event.messageId)) {
@@ -403,9 +455,35 @@ export class SqliteAgentRunner extends AgentRunner {
403
455
  return Promise.resolve(runState.isRunning);
404
456
  }
405
457
 
406
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
407
- stop(_request: AgentRunnerStopRequest): Promise<boolean | undefined> {
408
- throw new Error("Method not implemented.");
458
+ stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {
459
+ const runState = this.getRunState(request.threadId);
460
+ if (!runState.isRunning) {
461
+ return Promise.resolve(false);
462
+ }
463
+
464
+ const connection = ACTIVE_CONNECTIONS.get(request.threadId);
465
+ const agent = connection?.agent;
466
+
467
+ if (!connection || !agent) {
468
+ return Promise.resolve(false);
469
+ }
470
+
471
+ if (connection.stopRequested) {
472
+ return Promise.resolve(false);
473
+ }
474
+
475
+ connection.stopRequested = true;
476
+ this.setRunState(request.threadId, false);
477
+
478
+ try {
479
+ agent.abortRun();
480
+ return Promise.resolve(true);
481
+ } catch (error) {
482
+ console.error("Failed to abort sqlite agent run", error);
483
+ connection.stopRequested = false;
484
+ this.setRunState(request.threadId, true);
485
+ return Promise.resolve(false);
486
+ }
409
487
  }
410
488
 
411
489
  /**
@@ -1,61 +0,0 @@
1
-
2
- 
3
- > @copilotkitnext/sqlite-runner@0.0.16 test /Users/brandonmcconnell/Projects/@CopilotKit/vnext_experimental/packages/sqlite-runner
4
- > vitest run
5
-
6
- [?25l
7
-  RUN  v3.2.4 /Users/brandonmcconnell/Projects/@CopilotKit/vnext_experimental/packages/sqlite-runner
8
-
9
- [?2026h
10
-  ❯ src/__tests__/sqlite-runner.test.ts [queued]
11
-
12
-  Test Files 0 passed (2)
13
-  Tests 0 passed (0)
14
-  Start at 10:10:47
15
-  Duration 778ms
16
- [?2026l[?2026h
17
-  ❯ src/__tests__/sqlite-runner.e2e.test.ts [queued]
18
-  ❯ src/__tests__/sqlite-runner.test.ts [queued]
19
-
20
-  Test Files 0 passed (2)
21
-  Tests 0 passed (0)
22
-  Start at 10:10:47
23
-  Duration 1.02s
24
- [?2026l[?2026h
25
-  ❯ src/__tests__/sqlite-runner.e2e.test.ts 0/8
26
-  ❯ src/__tests__/sqlite-runner.test.ts [queued]
27
-
28
-  Test Files 0 passed (2)
29
-  Tests 0 passed (8)
30
-  Start at 10:10:47
31
-  Duration 1.75s
32
- [?2026l[?2026h ✓ src/__tests__/sqlite-runner.e2e.test.ts (8 tests) 77ms
33
-
34
-  ❯ src/__tests__/sqlite-runner.test.ts [queued]
35
-
36
-  Test Files 1 passed (2)
37
-  Tests 8 passed (8)
38
-  Start at 10:10:47
39
-  Duration 1.96s
40
- [?2026l[?2026h
41
-  ❯ src/__tests__/sqlite-runner.test.ts 0/4
42
-
43
-  Test Files 1 passed (2)
44
-  Tests 8 passed (12)
45
-  Start at 10:10:47
46
-  Duration 2.17s
47
- [?2026l[?2026h
48
-  ❯ src/__tests__/sqlite-runner.test.ts 1/4
49
-
50
-  Test Files 1 passed (2)
51
-  Tests 9 passed (12)
52
-  Start at 10:10:47
53
-  Duration 2.37s
54
- [?2026l ✓ src/__tests__/sqlite-runner.test.ts (4 tests) 182ms
55
-
56
-  Test Files  2 passed (2)
57
-  Tests  12 passed (12)
58
-  Start at  10:10:47
59
-  Duration  2.47s (transform 444ms, setup 0ms, collect 2.36s, tests 259ms, environment 1ms, prepare 592ms)
60
-
61
- [?25h