@copilotkitnext/sqlite-runner 1.51.4-next.7 → 1.51.4-next.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @copilotkitnext/sqlite-runner
2
2
 
3
+ ## 1.51.4-next.8
4
+
5
+ ### Patch Changes
6
+
7
+ - @copilotkitnext/runtime@1.51.4-next.8
8
+
3
9
  ## 1.51.4-next.7
4
10
 
5
11
  ### Patch Changes
package/dist/index.js CHANGED
@@ -85,9 +85,13 @@ var SqliteAgentRunner = class extends import_runtime.AgentRunner {
85
85
  applied_at INTEGER NOT NULL
86
86
  )
87
87
  `);
88
- const currentVersion = this.db.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
88
+ const currentVersion = this.db.prepare(
89
+ "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1"
90
+ ).get();
89
91
  if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {
90
- this.db.prepare("INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)").run(SCHEMA_VERSION, Date.now());
92
+ this.db.prepare(
93
+ "INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)"
94
+ ).run(SCHEMA_VERSION, Date.now());
91
95
  }
92
96
  }
93
97
  storeRun(threadId, runId, events, input, parentRunId) {
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 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"]}
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(\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 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 (\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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","names":["Database"]}
package/dist/index.mjs CHANGED
@@ -55,9 +55,13 @@ var SqliteAgentRunner = class extends AgentRunner {
55
55
  applied_at INTEGER NOT NULL
56
56
  )
57
57
  `);
58
- const currentVersion = this.db.prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1").get();
58
+ const currentVersion = this.db.prepare(
59
+ "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1"
60
+ ).get();
59
61
  if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {
60
- this.db.prepare("INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)").run(SCHEMA_VERSION, Date.now());
62
+ this.db.prepare(
63
+ "INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)"
64
+ ).run(SCHEMA_VERSION, Date.now());
61
65
  }
62
66
  }
63
67
  storeRun(threadId, runId, events, input, parentRunId) {
@@ -1 +1 @@
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":[]}
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(\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 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 (\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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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@copilotkitnext/sqlite-runner",
3
- "version": "1.51.4-next.7",
3
+ "version": "1.51.4-next.8",
4
4
  "description": "SQLite-backed agent runner for CopilotKit2",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,13 +22,13 @@
22
22
  "tsup": "^8.5.0",
23
23
  "typescript": "5.8.2",
24
24
  "vitest": "^3.0.5",
25
- "@copilotkitnext/eslint-config": "1.51.4-next.7",
26
- "@copilotkitnext/typescript-config": "1.51.4-next.7"
25
+ "@copilotkitnext/eslint-config": "1.51.4-next.8",
26
+ "@copilotkitnext/typescript-config": "1.51.4-next.8"
27
27
  },
28
28
  "dependencies": {
29
29
  "@ag-ui/client": "0.0.42",
30
30
  "rxjs": "7.8.1",
31
- "@copilotkitnext/runtime": "1.51.4-next.7"
31
+ "@copilotkitnext/runtime": "1.51.4-next.8"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "better-sqlite3": "^12.2.0"
@@ -46,7 +46,6 @@
46
46
  "dev": "tsup --watch",
47
47
  "lint": "eslint .",
48
48
  "check-types": "tsc --noEmit",
49
- "clean": "rm -rf dist",
50
49
  "test": "vitest run",
51
50
  "test:watch": "vitest",
52
51
  "test:coverage": "vitest run --coverage"
@@ -39,7 +39,10 @@ interface EmitAgentOptions {
39
39
  emitDefaultRunStarted?: boolean;
40
40
  includeRunFinished?: boolean;
41
41
  runFinishedEvent?: RunFinishedEvent;
42
- afterEvent?: (args: { event: BaseEvent; index: number }) => void | Promise<void>;
42
+ afterEvent?: (args: {
43
+ event: BaseEvent;
44
+ index: number;
45
+ }) => void | Promise<void>;
43
46
  }
44
47
 
45
48
  class EmitAgent extends AbstractAgent {
@@ -85,12 +88,11 @@ class EmitAgent extends AbstractAgent {
85
88
  runFinishedEvent?.type === EventType.RUN_FINISHED;
86
89
 
87
90
  if (includeRunFinished && !hasRunFinishedEvent) {
88
- const finishEvent: RunFinishedEvent =
89
- runFinishedEvent ?? {
90
- type: EventType.RUN_FINISHED,
91
- threadId: input.threadId,
92
- runId: input.runId,
93
- };
91
+ const finishEvent: RunFinishedEvent = runFinishedEvent ?? {
92
+ type: EventType.RUN_FINISHED,
93
+ threadId: input.threadId,
94
+ runId: input.runId,
95
+ };
94
96
  await emit(finishEvent);
95
97
  }
96
98
  }
@@ -112,7 +114,10 @@ class EmitAgent extends AbstractAgent {
112
114
  }
113
115
 
114
116
  class ReplayAgent extends AbstractAgent {
115
- constructor(private readonly replayEvents: BaseEvent[], threadId: string) {
117
+ constructor(
118
+ private readonly replayEvents: BaseEvent[],
119
+ threadId: string,
120
+ ) {
116
121
  super({ threadId });
117
122
  }
118
123
 
@@ -130,7 +135,10 @@ class ReplayAgent extends AbstractAgent {
130
135
  }
131
136
 
132
137
  class RunnerConnectAgent extends AbstractAgent {
133
- constructor(private readonly runner: SqliteAgentRunner, threadId: string) {
138
+ constructor(
139
+ private readonly runner: SqliteAgentRunner,
140
+ threadId: string,
141
+ ) {
134
142
  super({ threadId });
135
143
  }
136
144
 
@@ -142,7 +150,9 @@ class RunnerConnectAgent extends AbstractAgent {
142
150
  return EMPTY;
143
151
  }
144
152
 
145
- protected connect(input: RunAgentInput): ReturnType<AbstractAgent["connect"]> {
153
+ protected connect(
154
+ input: RunAgentInput,
155
+ ): ReturnType<AbstractAgent["connect"]> {
146
156
  return this.runner.connect({ threadId: input.threadId });
147
157
  }
148
158
  }
@@ -163,7 +173,11 @@ function createDeferred<T>(): Deferred<T> {
163
173
  return { promise, resolve, reject };
164
174
  }
165
175
 
166
- async function collectEvents(observable: ReturnType<SqliteAgentRunner["run"]> | ReturnType<SqliteAgentRunner["connect"]>) {
176
+ async function collectEvents(
177
+ observable:
178
+ | ReturnType<SqliteAgentRunner["run"]>
179
+ | ReturnType<SqliteAgentRunner["connect"]>,
180
+ ) {
167
181
  return firstValueFrom(observable.pipe(toArray()));
168
182
  }
169
183
 
@@ -348,9 +362,9 @@ describe("SqliteAgentRunner e2e", () => {
348
362
  await replayAgent.connectAgent({ runId: "replay-run" });
349
363
 
350
364
  expect(replayAgent.messages).toEqual([existingMessage, newMessage]);
351
- expect(new Set(replayAgent.messages.map((message) => message.id)).size).toBe(
352
- replayAgent.messages.length,
353
- );
365
+ expect(
366
+ new Set(replayAgent.messages.map((message) => message.id)).size,
367
+ ).toBe(replayAgent.messages.length);
354
368
  });
355
369
  });
356
370
 
@@ -438,7 +452,9 @@ describe("SqliteAgentRunner e2e", () => {
438
452
  );
439
453
 
440
454
  expectRunStartedEvent(runEvents[0], baseMessages);
441
- expect(runEvents.filter((event) => event.type === EventType.TOOL_CALL_RESULT)).toHaveLength(1);
455
+ expect(
456
+ runEvents.filter((event) => event.type === EventType.TOOL_CALL_RESULT),
457
+ ).toHaveLength(1);
442
458
 
443
459
  const replayEvents = await collectEvents(runner.connect({ threadId }));
444
460
  const replayAgent = new ReplayAgent(replayEvents, threadId);
@@ -470,7 +486,9 @@ describe("SqliteAgentRunner e2e", () => {
470
486
  toolCallId,
471
487
  },
472
488
  ]);
473
- expect(replayAgent.messages.filter((message) => message.role === "tool")).toHaveLength(1);
489
+ expect(
490
+ replayAgent.messages.filter((message) => message.role === "tool"),
491
+ ).toHaveLength(1);
474
492
  });
475
493
  });
476
494
 
@@ -538,11 +556,16 @@ describe("SqliteAgentRunner e2e", () => {
538
556
  await replayAgent.connectAgent({ runId: "replay-final" });
539
557
 
540
558
  const finalMessages = replayAgent.messages;
541
- expect(new Set(finalMessages.map((message) => message.id)).size).toBe(finalMessages.length);
542
- const roleCounts = finalMessages.reduce<Record<string, number>>((counts, message) => {
543
- counts[message.role] = (counts[message.role] ?? 0) + 1;
544
- return counts;
545
- }, {});
559
+ expect(new Set(finalMessages.map((message) => message.id)).size).toBe(
560
+ finalMessages.length,
561
+ );
562
+ const roleCounts = finalMessages.reduce<Record<string, number>>(
563
+ (counts, message) => {
564
+ counts[message.role] = (counts[message.role] ?? 0) + 1;
565
+ return counts;
566
+ },
567
+ {},
568
+ );
546
569
  expect(roleCounts.system).toBe(1);
547
570
  expect(roleCounts.user).toBe(3);
548
571
  expect(roleCounts.assistant).toBe(3);
@@ -605,14 +628,16 @@ describe("SqliteAgentRunner e2e", () => {
605
628
  );
606
629
 
607
630
  expect(runEvents[0]).toEqual(customRunStarted);
608
- expect(runEvents.filter((event) => event.type === EventType.RUN_FINISHED)).toHaveLength(1);
631
+ expect(
632
+ runEvents.filter((event) => event.type === EventType.RUN_FINISHED),
633
+ ).toHaveLength(1);
609
634
 
610
635
  const replayEvents = await collectEvents(runner.connect({ threadId }));
611
636
  const replayAgent = new ReplayAgent(replayEvents, threadId);
612
637
  await replayAgent.connectAgent({ runId: "replay-run" });
613
- expect(replayAgent.messages.find((message) => message.id === "custom-user")).toEqual(
614
- customMessages[0],
615
- );
638
+ expect(
639
+ replayAgent.messages.find((message) => message.id === "custom-user"),
640
+ ).toEqual(customMessages[0]);
616
641
  });
617
642
  });
618
643
 
@@ -48,8 +48,10 @@ class MockAgent extends AbstractAgent {
48
48
  await callbacks.onEvent({ event });
49
49
  }
50
50
 
51
- const hasTerminalEvent = this.events.some((event) =>
52
- event.type === EventType.RUN_FINISHED || event.type === EventType.RUN_ERROR,
51
+ const hasTerminalEvent = this.events.some(
52
+ (event) =>
53
+ event.type === EventType.RUN_FINISHED ||
54
+ event.type === EventType.RUN_ERROR,
53
55
  );
54
56
 
55
57
  if (!hasTerminalEvent) {
@@ -84,10 +86,7 @@ class StoppableAgent extends AbstractAgent {
84
86
  this.eventDelay = eventDelay;
85
87
  }
86
88
 
87
- async runAgent(
88
- input: RunAgentInput,
89
- callbacks: RunCallbacks,
90
- ): Promise<void> {
89
+ async runAgent(input: RunAgentInput, callbacks: RunCallbacks): Promise<void> {
91
90
  this.shouldStop = false;
92
91
  let counter = 0;
93
92
 
@@ -123,10 +122,7 @@ class StoppableAgent extends AbstractAgent {
123
122
  class OpenEventsAgent extends AbstractAgent {
124
123
  private shouldStop = false;
125
124
 
126
- async runAgent(
127
- input: RunAgentInput,
128
- callbacks: RunCallbacks,
129
- ): Promise<void> {
125
+ async runAgent(input: RunAgentInput, callbacks: RunCallbacks): Promise<void> {
130
126
  this.shouldStop = false;
131
127
  const messageId = "open-message";
132
128
  const toolCallId = "open-tool";
@@ -182,6 +178,8 @@ describe("SqliteAgentRunner", () => {
182
178
  });
183
179
 
184
180
  afterEach(() => {
181
+ runner.close();
182
+
185
183
  if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
186
184
  if (fs.existsSync(tempDir)) fs.rmdirSync(tempDir);
187
185
  });
@@ -189,10 +187,25 @@ describe("SqliteAgentRunner", () => {
189
187
  it("emits RUN_STARTED and agent events", async () => {
190
188
  const threadId = "sqlite-basic";
191
189
  const agent = new MockAgent([
192
- { type: EventType.TEXT_MESSAGE_START, messageId: "msg-1", role: "assistant" } as TextMessageStartEvent,
193
- { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg-1", delta: "Hello" } as TextMessageContentEvent,
194
- { type: EventType.TEXT_MESSAGE_END, messageId: "msg-1" } as TextMessageEndEvent,
195
- { type: EventType.RUN_FINISHED, threadId, runId: "run-1" } as RunFinishedEvent,
190
+ {
191
+ type: EventType.TEXT_MESSAGE_START,
192
+ messageId: "msg-1",
193
+ role: "assistant",
194
+ } as TextMessageStartEvent,
195
+ {
196
+ type: EventType.TEXT_MESSAGE_CONTENT,
197
+ messageId: "msg-1",
198
+ delta: "Hello",
199
+ } as TextMessageContentEvent,
200
+ {
201
+ type: EventType.TEXT_MESSAGE_END,
202
+ messageId: "msg-1",
203
+ } as TextMessageEndEvent,
204
+ {
205
+ type: EventType.RUN_FINISHED,
206
+ threadId,
207
+ runId: "run-1",
208
+ } as RunFinishedEvent,
196
209
  ]);
197
210
 
198
211
  const events = await firstValueFrom(
@@ -228,7 +241,11 @@ describe("SqliteAgentRunner", () => {
228
241
  .pipe(toArray()),
229
242
  );
230
243
 
231
- const newMessage: Message = { id: "new", role: "user", content: "follow up" };
244
+ const newMessage: Message = {
245
+ id: "new",
246
+ role: "user",
247
+ content: "follow up",
248
+ };
232
249
 
233
250
  const secondRun = await firstValueFrom(
234
251
  runner
@@ -250,18 +267,26 @@ describe("SqliteAgentRunner", () => {
250
267
 
251
268
  const db = new Database(dbPath);
252
269
  const rows = db
253
- .prepare("SELECT events FROM agent_runs WHERE thread_id = ? ORDER BY created_at")
254
- .all(threadId) as { events: string }[];
270
+ .prepare(
271
+ "SELECT events FROM agent_runs WHERE thread_id = ? ORDER BY created_at",
272
+ )
273
+ .all(threadId) as {
274
+ events: string;
275
+ }[];
255
276
  db.close();
256
277
 
257
278
  expect(rows).toHaveLength(2);
258
279
  const run1Stored = JSON.parse(rows[0].events) as BaseEvent[];
259
280
  const run2Stored = JSON.parse(rows[1].events) as BaseEvent[];
260
281
 
261
- const run1Started = run1Stored.find((event) => event.type === EventType.RUN_STARTED) as RunStartedEvent;
282
+ const run1Started = run1Stored.find(
283
+ (event) => event.type === EventType.RUN_STARTED,
284
+ ) as RunStartedEvent;
262
285
  expect(run1Started.input?.messages?.map((m) => m.id)).toEqual(["existing"]);
263
286
 
264
- const run2Started = run2Stored.find((event) => event.type === EventType.RUN_STARTED) as RunStartedEvent;
287
+ const run2Started = run2Stored.find(
288
+ (event) => event.type === EventType.RUN_STARTED,
289
+ ) as RunStartedEvent;
265
290
  expect(run2Started.input?.messages?.map((m) => m.id)).toEqual(["new"]);
266
291
  });
267
292
 
@@ -317,10 +342,25 @@ describe("SqliteAgentRunner", () => {
317
342
  it("persists events across runner instances", async () => {
318
343
  const threadId = "sqlite-persist";
319
344
  const agent = new MockAgent([
320
- { type: EventType.TEXT_MESSAGE_START, messageId: "msg", role: "assistant" } as TextMessageStartEvent,
321
- { type: EventType.TEXT_MESSAGE_CONTENT, messageId: "msg", delta: "hi" } as TextMessageContentEvent,
322
- { type: EventType.TEXT_MESSAGE_END, messageId: "msg" } as TextMessageEndEvent,
323
- { type: EventType.RUN_FINISHED, threadId, runId: "run-1" } as RunFinishedEvent,
345
+ {
346
+ type: EventType.TEXT_MESSAGE_START,
347
+ messageId: "msg",
348
+ role: "assistant",
349
+ } as TextMessageStartEvent,
350
+ {
351
+ type: EventType.TEXT_MESSAGE_CONTENT,
352
+ messageId: "msg",
353
+ delta: "hi",
354
+ } as TextMessageContentEvent,
355
+ {
356
+ type: EventType.TEXT_MESSAGE_END,
357
+ messageId: "msg",
358
+ } as TextMessageEndEvent,
359
+ {
360
+ type: EventType.RUN_FINISHED,
361
+ threadId,
362
+ runId: "run-1",
363
+ } as RunFinishedEvent,
324
364
  ]);
325
365
 
326
366
  await firstValueFrom(
@@ -334,19 +374,27 @@ describe("SqliteAgentRunner", () => {
334
374
  );
335
375
 
336
376
  const newRunner = new SqliteAgentRunner({ dbPath });
337
- const replayed = await firstValueFrom(newRunner.connect({ threadId }).pipe(toArray()));
338
-
339
- expect(replayed[0].type).toBe(EventType.RUN_STARTED);
340
- expect(replayed.slice(1).map((event) => event.type)).toEqual([
341
- EventType.TEXT_MESSAGE_START,
342
- EventType.TEXT_MESSAGE_CONTENT,
343
- EventType.TEXT_MESSAGE_END,
344
- EventType.RUN_FINISHED,
345
- ]);
377
+ try {
378
+ const replayed = await firstValueFrom(
379
+ newRunner.connect({ threadId }).pipe(toArray()),
380
+ );
381
+
382
+ expect(replayed[0].type).toBe(EventType.RUN_STARTED);
383
+ expect(replayed.slice(1).map((event) => event.type)).toEqual([
384
+ EventType.TEXT_MESSAGE_START,
385
+ EventType.TEXT_MESSAGE_CONTENT,
386
+ EventType.TEXT_MESSAGE_END,
387
+ EventType.RUN_FINISHED,
388
+ ]);
389
+ } finally {
390
+ newRunner.close();
391
+ }
346
392
  });
347
393
 
348
394
  it("returns false when stopping a thread that is not running", async () => {
349
- await expect(runner.stop({ threadId: "sqlite-missing" })).resolves.toBe(false);
395
+ await expect(runner.stop({ threadId: "sqlite-missing" })).resolves.toBe(
396
+ false,
397
+ );
350
398
  });
351
399
 
352
400
  it("stops an active run and completes observables", async () => {
@@ -51,20 +51,20 @@ export class SqliteAgentRunner extends AgentRunner {
51
51
  constructor(options: SqliteAgentRunnerOptions = {}) {
52
52
  super();
53
53
  const dbPath = options.dbPath ?? ":memory:";
54
-
54
+
55
55
  if (!Database) {
56
56
  throw new Error(
57
- 'better-sqlite3 is required for SqliteAgentRunner but was not found.\n' +
58
- 'Please install it in your project:\n' +
59
- ' npm install better-sqlite3\n' +
60
- ' or\n' +
61
- ' pnpm add better-sqlite3\n' +
62
- ' or\n' +
63
- ' yarn add better-sqlite3\n\n' +
64
- 'If you don\'t need persistence, use InMemoryAgentRunner instead.'
57
+ "better-sqlite3 is required for SqliteAgentRunner but was not found.\n" +
58
+ "Please install it in your project:\n" +
59
+ " npm install better-sqlite3\n" +
60
+ " or\n" +
61
+ " pnpm add better-sqlite3\n" +
62
+ " or\n" +
63
+ " yarn add better-sqlite3\n\n" +
64
+ "If you don't need persistence, use InMemoryAgentRunner instead.",
65
65
  );
66
66
  }
67
-
67
+
68
68
  this.db = new Database(dbPath);
69
69
  this.initializeSchema();
70
70
  }
@@ -110,12 +110,16 @@ export class SqliteAgentRunner extends AgentRunner {
110
110
 
111
111
  // Check and set schema version
112
112
  const currentVersion = this.db
113
- .prepare("SELECT version FROM schema_version ORDER BY version DESC LIMIT 1")
113
+ .prepare(
114
+ "SELECT version FROM schema_version ORDER BY version DESC LIMIT 1",
115
+ )
114
116
  .get() as { version: number } | undefined;
115
117
 
116
118
  if (!currentVersion || currentVersion.version < SCHEMA_VERSION) {
117
119
  this.db
118
- .prepare("INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)")
120
+ .prepare(
121
+ "INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, ?)",
122
+ )
119
123
  .run(SCHEMA_VERSION, Date.now());
120
124
  }
121
125
  }
@@ -125,11 +129,11 @@ export class SqliteAgentRunner extends AgentRunner {
125
129
  runId: string,
126
130
  events: BaseEvent[],
127
131
  input: RunAgentInput,
128
- parentRunId?: string | null
132
+ parentRunId?: string | null,
129
133
  ): void {
130
134
  // Compact ONLY the events from this run
131
135
  const compactedEvents = compactEvents(events);
132
-
136
+
133
137
  const stmt = this.db.prepare(`
134
138
  INSERT INTO agent_runs (thread_id, run_id, parent_run_id, events, input, created_at, version)
135
139
  VALUES (?, ?, ?, ?, ?, ?, ?)
@@ -142,7 +146,7 @@ export class SqliteAgentRunner extends AgentRunner {
142
146
  JSON.stringify(compactedEvents), // Store only this run's compacted events
143
147
  JSON.stringify(input),
144
148
  Date.now(),
145
- SCHEMA_VERSION
149
+ SCHEMA_VERSION,
146
150
  );
147
151
  }
148
152
 
@@ -165,8 +169,8 @@ export class SqliteAgentRunner extends AgentRunner {
165
169
  `);
166
170
 
167
171
  const rows = stmt.all(threadId, threadId) as any[];
168
-
169
- return rows.map(row => ({
172
+
173
+ return rows.map((row) => ({
170
174
  id: row.id,
171
175
  thread_id: row.thread_id,
172
176
  run_id: row.run_id,
@@ -174,7 +178,7 @@ export class SqliteAgentRunner extends AgentRunner {
174
178
  events: JSON.parse(row.events),
175
179
  input: JSON.parse(row.input),
176
180
  created_at: row.created_at,
177
- version: row.version
181
+ version: row.version,
178
182
  }));
179
183
  }
180
184
 
@@ -190,7 +194,11 @@ export class SqliteAgentRunner extends AgentRunner {
190
194
  return result?.run_id ?? null;
191
195
  }
192
196
 
193
- private setRunState(threadId: string, isRunning: boolean, runId?: string): void {
197
+ private setRunState(
198
+ threadId: string,
199
+ isRunning: boolean,
200
+ runId?: string,
201
+ ): void {
194
202
  const stmt = this.db.prepare(`
195
203
  INSERT OR REPLACE INTO run_state (thread_id, is_running, current_run_id, updated_at)
196
204
  VALUES (?, ?, ?, ?)
@@ -198,15 +206,20 @@ export class SqliteAgentRunner extends AgentRunner {
198
206
  stmt.run(threadId, isRunning ? 1 : 0, runId ?? null, Date.now());
199
207
  }
200
208
 
201
- private getRunState(threadId: string): { isRunning: boolean; currentRunId: string | null } {
209
+ private getRunState(threadId: string): {
210
+ isRunning: boolean;
211
+ currentRunId: string | null;
212
+ } {
202
213
  const stmt = this.db.prepare(`
203
214
  SELECT is_running, current_run_id FROM run_state WHERE thread_id = ?
204
215
  `);
205
- const result = stmt.get(threadId) as { is_running: number; current_run_id: string | null } | undefined;
206
-
216
+ const result = stmt.get(threadId) as
217
+ | { is_running: number; current_run_id: string | null }
218
+ | undefined;
219
+
207
220
  return {
208
221
  isRunning: result?.is_running === 1,
209
- currentRunId: result?.current_run_id ?? null
222
+ currentRunId: result?.current_run_id ?? null,
210
223
  };
211
224
  }
212
225
 
@@ -223,13 +236,13 @@ export class SqliteAgentRunner extends AgentRunner {
223
236
  // Track seen message IDs and current run events in memory for this run
224
237
  const seenMessageIds = new Set<string>();
225
238
  const currentRunEvents: BaseEvent[] = [];
226
-
239
+
227
240
  // Get all previously seen message IDs from historic runs
228
241
  const historicRuns = this.getHistoricRuns(request.threadId);
229
242
  const historicMessageIds = new Set<string>();
230
243
  for (const run of historicRuns) {
231
244
  for (const event of run.events) {
232
- if ('messageId' in event && typeof event.messageId === 'string') {
245
+ if ("messageId" in event && typeof event.messageId === "string") {
233
246
  historicMessageIds.add(event.messageId);
234
247
  }
235
248
  if (event.type === EventType.RUN_STARTED) {
@@ -246,7 +259,7 @@ export class SqliteAgentRunner extends AgentRunner {
246
259
  const nextSubject = new ReplaySubject<BaseEvent>(Infinity);
247
260
  const prevConnection = ACTIVE_CONNECTIONS.get(request.threadId);
248
261
  const prevSubject = prevConnection?.subject;
249
-
262
+
250
263
  // Create a subject for run() return value
251
264
  const runSubject = new ReplaySubject<BaseEvent>(Infinity);
252
265
 
@@ -263,7 +276,7 @@ export class SqliteAgentRunner extends AgentRunner {
263
276
  const runAgent = async () => {
264
277
  // Get parent run ID for chaining
265
278
  const parentRunId = this.getLatestRunId(request.threadId);
266
-
279
+
267
280
  try {
268
281
  await request.agent.runAgent(request.input, {
269
282
  onEvent: ({ event }) => {
@@ -310,7 +323,7 @@ export class SqliteAgentRunner extends AgentRunner {
310
323
  }
311
324
  },
312
325
  });
313
-
326
+
314
327
  const connection = ACTIVE_CONNECTIONS.get(request.threadId);
315
328
  const appendedEvents = finalizeRunEvents(currentRunEvents, {
316
329
  stopRequested: connection?.stopRequested ?? false,
@@ -326,9 +339,9 @@ export class SqliteAgentRunner extends AgentRunner {
326
339
  request.input.runId,
327
340
  currentRunEvents,
328
341
  request.input,
329
- parentRunId
342
+ parentRunId,
330
343
  );
331
-
344
+
332
345
  // Mark run as complete in database
333
346
  this.setRunState(request.threadId, false);
334
347
 
@@ -361,10 +374,10 @@ export class SqliteAgentRunner extends AgentRunner {
361
374
  request.input.runId,
362
375
  currentRunEvents,
363
376
  request.input,
364
- parentRunId
377
+ parentRunId,
365
378
  );
366
379
  }
367
-
380
+
368
381
  // Mark run as complete in database
369
382
  this.setRunState(request.threadId, false);
370
383
 
@@ -407,46 +420,53 @@ export class SqliteAgentRunner extends AgentRunner {
407
420
 
408
421
  // Load historic runs from database
409
422
  const historicRuns = this.getHistoricRuns(request.threadId);
410
-
423
+
411
424
  // Collect all historic events from database
412
425
  const allHistoricEvents: BaseEvent[] = [];
413
426
  for (const run of historicRuns) {
414
427
  allHistoricEvents.push(...run.events);
415
428
  }
416
-
429
+
417
430
  // Compact all events together before emitting
418
431
  const compactedEvents = compactEvents(allHistoricEvents);
419
-
432
+
420
433
  // Emit compacted events and track message IDs
421
434
  const emittedMessageIds = new Set<string>();
422
435
  for (const event of compactedEvents) {
423
436
  connectionSubject.next(event);
424
- if ('messageId' in event && typeof event.messageId === 'string') {
437
+ if ("messageId" in event && typeof event.messageId === "string") {
425
438
  emittedMessageIds.add(event.messageId);
426
439
  }
427
440
  }
428
-
441
+
429
442
  // Bridge active run to connection if exists
430
443
  const activeConnection = ACTIVE_CONNECTIONS.get(request.threadId);
431
444
  const runState = this.getRunState(request.threadId);
432
445
 
433
- if (activeConnection && (runState.isRunning || activeConnection.stopRequested)) {
446
+ if (
447
+ activeConnection &&
448
+ (runState.isRunning || activeConnection.stopRequested)
449
+ ) {
434
450
  activeConnection.subject.subscribe({
435
451
  next: (event) => {
436
452
  // Skip message events that we've already emitted from historic
437
- if ('messageId' in event && typeof event.messageId === 'string' && emittedMessageIds.has(event.messageId)) {
453
+ if (
454
+ "messageId" in event &&
455
+ typeof event.messageId === "string" &&
456
+ emittedMessageIds.has(event.messageId)
457
+ ) {
438
458
  return;
439
459
  }
440
460
  connectionSubject.next(event);
441
461
  },
442
462
  complete: () => connectionSubject.complete(),
443
- error: (err) => connectionSubject.error(err)
463
+ error: (err) => connectionSubject.error(err),
444
464
  });
445
465
  } else {
446
466
  // No active run, complete after historic events
447
467
  connectionSubject.complete();
448
468
  }
449
-
469
+
450
470
  return connectionSubject.asObservable();
451
471
  }
452
472
 
package/tsup.config.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { defineConfig } from 'tsup';
1
+ import { defineConfig } from "tsup";
2
2
 
3
3
  export default defineConfig({
4
- entry: ['src/index.ts'],
5
- format: ['cjs', 'esm'],
4
+ entry: ["src/index.ts"],
5
+ format: ["cjs", "esm"],
6
6
  dts: true,
7
7
  sourcemap: true,
8
8
  clean: true,
9
- target: 'es2022',
10
- outDir: 'dist',
9
+ target: "es2022",
10
+ outDir: "dist",
11
11
  });