@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 +6 -0
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
- package/src/__tests__/sqlite-runner.e2e.test.ts +50 -25
- package/src/__tests__/sqlite-runner.test.ts +81 -33
- package/src/sqlite-runner.ts +61 -41
- package/tsup.config.ts +5 -5
package/CHANGELOG.md
CHANGED
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -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":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,OAKK;AACP,SAAqB,qBAAqB;AAC1C;AAAA,EAIE;AAAA,EAEA;AAAA,OACK;AACP,OAAO,cAAc;AAErB,IAAM,iBAAiB;AA0BvB,IAAM,qBAAqB,oBAAI,IAAqC;AAE7D,IAAM,oBAAN,cAAgC,YAAY;AAAA,EACzC;AAAA,EAER,YAAY,UAAoC,CAAC,GAAG;AAClD,UAAM;AACN,UAAM,SAAS,QAAQ,UAAU;AAEjC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI;AAAA,QACR;AAAA,MAQF;AAAA,IACF;AAEA,SAAK,KAAK,IAAI,SAAS,MAAM;AAC7B,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEQ,mBAAyB;AAE/B,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAWZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAOZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA,KAGZ;AAGD,SAAK,GAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,KAKZ;AAGD,UAAM,iBAAiB,KAAK,GACzB;AAAA,MACC;AAAA,IACF,EACC,IAAI;AAEP,QAAI,CAAC,kBAAkB,eAAe,UAAU,gBAAgB;AAC9D,WAAK,GACF;AAAA,QACC;AAAA,MACF,EACC,IAAI,gBAAgB,KAAK,IAAI,CAAC;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,SACN,UACA,OACA,QACA,OACA,aACM;AAEN,UAAM,kBAAkB,cAAc,MAAM;AAE5C,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG5B;AAED,SAAK;AAAA,MACH;AAAA,MACA;AAAA,MACA,eAAe;AAAA,MACf,KAAK,UAAU,eAAe;AAAA;AAAA,MAC9B,KAAK,UAAU,KAAK;AAAA,MACpB,KAAK,IAAI;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,gBAAgB,UAAoC;AAC1D,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAe5B;AAED,UAAM,OAAO,KAAK,IAAI,UAAU,QAAQ;AAExC,WAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MACxB,IAAI,IAAI;AAAA,MACR,WAAW,IAAI;AAAA,MACf,QAAQ,IAAI;AAAA,MACZ,eAAe,IAAI;AAAA,MACnB,QAAQ,KAAK,MAAM,IAAI,MAAM;AAAA,MAC7B,OAAO,KAAK,MAAM,IAAI,KAAK;AAAA,MAC3B,YAAY,IAAI;AAAA,MAChB,SAAS,IAAI;AAAA,IACf,EAAE;AAAA,EACJ;AAAA,EAEQ,eAAe,UAAiC;AACtD,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,KAK5B;AAED,UAAM,SAAS,KAAK,IAAI,QAAQ;AAChC,WAAO,QAAQ,UAAU;AAAA,EAC3B;AAAA,EAEQ,YACN,UACA,WACA,OACM;AACN,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA;AAAA,KAG5B;AACD,SAAK,IAAI,UAAU,YAAY,IAAI,GAAG,SAAS,MAAM,KAAK,IAAI,CAAC;AAAA,EACjE;AAAA,EAEQ,YAAY,UAGlB;AACA,UAAM,OAAO,KAAK,GAAG,QAAQ;AAAA;AAAA,KAE5B;AACD,UAAM,SAAS,KAAK,IAAI,QAAQ;AAIhC,WAAO;AAAA,MACL,WAAW,QAAQ,eAAe;AAAA,MAClC,cAAc,QAAQ,kBAAkB;AAAA,IAC1C;AAAA,EACF;AAAA,EAEA,IAAI,SAAuD;AAEzD,UAAM,WAAW,KAAK,YAAY,QAAQ,QAAQ;AAClD,QAAI,SAAS,WAAW;AACtB,YAAM,IAAI,MAAM,wBAAwB;AAAA,IAC1C;AAGA,SAAK,YAAY,QAAQ,UAAU,MAAM,QAAQ,MAAM,KAAK;AAG5D,UAAM,iBAAiB,oBAAI,IAAY;AACvC,UAAM,mBAAgC,CAAC;AAGvC,UAAM,eAAe,KAAK,gBAAgB,QAAQ,QAAQ;AAC1D,UAAM,qBAAqB,oBAAI,IAAY;AAC3C,eAAW,OAAO,cAAc;AAC9B,iBAAW,SAAS,IAAI,QAAQ;AAC9B,YAAI,eAAe,SAAS,OAAO,MAAM,cAAc,UAAU;AAC/D,6BAAmB,IAAI,MAAM,SAAS;AAAA,QACxC;AACA,YAAI,MAAM,SAAS,UAAU,aAAa;AACxC,gBAAM,aAAa;AACnB,gBAAM,WAAW,WAAW,OAAO,YAAY,CAAC;AAChD,qBAAW,WAAW,UAAU;AAC9B,+BAAmB,IAAI,QAAQ,EAAE;AAAA,UACnC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAGA,UAAM,cAAc,IAAI,cAAyB,QAAQ;AACzD,UAAM,iBAAiB,mBAAmB,IAAI,QAAQ,QAAQ;AAC9D,UAAM,cAAc,gBAAgB;AAGpC,UAAM,aAAa,IAAI,cAAyB,QAAQ;AAGxD,uBAAmB,IAAI,QAAQ,UAAU;AAAA,MACvC,SAAS;AAAA,MACT,OAAO,QAAQ;AAAA,MACf;AAAA,MACA,eAAe;AAAA,MACf,eAAe;AAAA,IACjB,CAAC;AAGD,UAAM,WAAW,YAAY;AAE3B,YAAM,cAAc,KAAK,eAAe,QAAQ,QAAQ;AAExD,UAAI;AACF,cAAM,QAAQ,MAAM,SAAS,QAAQ,OAAO;AAAA,UAC1C,SAAS,CAAC,EAAE,MAAM,MAAM;AACtB,gBAAI,iBAA4B;AAChC,gBAAI,MAAM,SAAS,UAAU,aAAa;AACxC,oBAAM,kBAAkB;AACxB,kBAAI,CAAC,gBAAgB,OAAO;AAC1B,sBAAM,oBAAoB,QAAQ,MAAM,WACpC,QAAQ,MAAM,SAAS;AAAA,kBACrB,CAAC,YAAY,CAAC,mBAAmB,IAAI,QAAQ,EAAE;AAAA,gBACjD,IACA;AACJ,sBAAM,eAAe;AAAA,kBACnB,GAAG,QAAQ;AAAA,kBACX,GAAI,sBAAsB,SACtB,EAAE,UAAU,kBAAkB,IAC9B,CAAC;AAAA,gBACP;AACA,iCAAiB;AAAA,kBACf,GAAG;AAAA,kBACH,OAAO;AAAA,gBACT;AAAA,cACF;AAAA,YACF;AAEA,uBAAW,KAAK,cAAc;AAC9B,wBAAY,KAAK,cAAc;AAC/B,6BAAiB,KAAK,cAAc;AAAA,UACtC;AAAA,UACA,cAAc,CAAC,EAAE,QAAQ,MAAM;AAE7B,gBAAI,CAAC,eAAe,IAAI,QAAQ,EAAE,GAAG;AACnC,6BAAe,IAAI,QAAQ,EAAE;AAAA,YAC/B;AAAA,UACF;AAAA,UACA,mBAAmB,MAAM;AAEvB,gBAAI,QAAQ,MAAM,UAAU;AAC1B,yBAAW,WAAW,QAAQ,MAAM,UAAU;AAC5C,oBAAI,CAAC,eAAe,IAAI,QAAQ,EAAE,GAAG;AACnC,iCAAe,IAAI,QAAQ,EAAE;AAAA,gBAC/B;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF,CAAC;AAED,cAAM,aAAa,mBAAmB,IAAI,QAAQ,QAAQ;AAC1D,cAAM,iBAAiB,kBAAkB,kBAAkB;AAAA,UACzD,eAAe,YAAY,iBAAiB;AAAA,QAC9C,CAAC;AACD,mBAAW,SAAS,gBAAgB;AAClC,qBAAW,KAAK,KAAK;AACrB,sBAAY,KAAK,KAAK;AAAA,QACxB;AAGA,aAAK;AAAA,UACH,QAAQ;AAAA,UACR,QAAQ,MAAM;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,UACR;AAAA,QACF;AAGA,aAAK,YAAY,QAAQ,UAAU,KAAK;AAExC,YAAI,YAAY;AACd,qBAAW,QAAQ;AACnB,qBAAW,aAAa;AACxB,qBAAW,gBAAgB;AAC3B,qBAAW,gBAAgB;AAAA,QAC7B;AAGA,mBAAW,SAAS;AACpB,oBAAY,SAAS;AAErB,2BAAmB,OAAO,QAAQ,QAAQ;AAAA,MAC5C,QAAQ;AACN,cAAM,aAAa,mBAAmB,IAAI,QAAQ,QAAQ;AAC1D,cAAM,iBAAiB,kBAAkB,kBAAkB;AAAA,UACzD,eAAe,YAAY,iBAAiB;AAAA,QAC9C,CAAC;AACD,mBAAW,SAAS,gBAAgB;AAClC,qBAAW,KAAK,KAAK;AACrB,sBAAY,KAAK,KAAK;AAAA,QACxB;AAGA,YAAI,iBAAiB,SAAS,GAAG;AAC/B,eAAK;AAAA,YACH,QAAQ;AAAA,YACR,QAAQ,MAAM;AAAA,YACd;AAAA,YACA,QAAQ;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAGA,aAAK,YAAY,QAAQ,UAAU,KAAK;AAExC,YAAI,YAAY;AACd,qBAAW,QAAQ;AACnB,qBAAW,aAAa;AACxB,qBAAW,gBAAgB;AAC3B,qBAAW,gBAAgB;AAAA,QAC7B;AAIA,mBAAW,SAAS;AACpB,oBAAY,SAAS;AAErB,2BAAmB,OAAO,QAAQ,QAAQ;AAAA,MAC5C;AAAA,IACF;AAGA,QAAI,aAAa;AACf,kBAAY,UAAU;AAAA,QACpB,MAAM,CAAC,MAAM,YAAY,KAAK,CAAC;AAAA,QAC/B,OAAO,CAAC,QAAQ,YAAY,MAAM,GAAG;AAAA,QACrC,UAAU,MAAM;AAAA,QAEhB;AAAA,MACF,CAAC;AAAA,IACH;AAGA,aAAS;AAGT,WAAO,WAAW,aAAa;AAAA,EACjC;AAAA,EAEA,QAAQ,SAA2D;AACjE,UAAM,oBAAoB,IAAI,cAAyB,QAAQ;AAG/D,UAAM,eAAe,KAAK,gBAAgB,QAAQ,QAAQ;AAG1D,UAAM,oBAAiC,CAAC;AACxC,eAAW,OAAO,cAAc;AAC9B,wBAAkB,KAAK,GAAG,IAAI,MAAM;AAAA,IACtC;AAGA,UAAM,kBAAkB,cAAc,iBAAiB;AAGvD,UAAM,oBAAoB,oBAAI,IAAY;AAC1C,eAAW,SAAS,iBAAiB;AACnC,wBAAkB,KAAK,KAAK;AAC5B,UAAI,eAAe,SAAS,OAAO,MAAM,cAAc,UAAU;AAC/D,0BAAkB,IAAI,MAAM,SAAS;AAAA,MACvC;AAAA,IACF;AAGA,UAAM,mBAAmB,mBAAmB,IAAI,QAAQ,QAAQ;AAChE,UAAM,WAAW,KAAK,YAAY,QAAQ,QAAQ;AAElD,QACE,qBACC,SAAS,aAAa,iBAAiB,gBACxC;AACA,uBAAiB,QAAQ,UAAU;AAAA,QACjC,MAAM,CAAC,UAAU;AAEf,cACE,eAAe,SACf,OAAO,MAAM,cAAc,YAC3B,kBAAkB,IAAI,MAAM,SAAS,GACrC;AACA;AAAA,UACF;AACA,4BAAkB,KAAK,KAAK;AAAA,QAC9B;AAAA,QACA,UAAU,MAAM,kBAAkB,SAAS;AAAA,QAC3C,OAAO,CAAC,QAAQ,kBAAkB,MAAM,GAAG;AAAA,MAC7C,CAAC;AAAA,IACH,OAAO;AAEL,wBAAkB,SAAS;AAAA,IAC7B;AAEA,WAAO,kBAAkB,aAAa;AAAA,EACxC;AAAA,EAEA,UAAU,SAAwD;AAChE,UAAM,WAAW,KAAK,YAAY,QAAQ,QAAQ;AAClD,WAAO,QAAQ,QAAQ,SAAS,SAAS;AAAA,EAC3C;AAAA,EAEA,KAAK,SAA+D;AAClE,UAAM,WAAW,KAAK,YAAY,QAAQ,QAAQ;AAClD,QAAI,CAAC,SAAS,WAAW;AACvB,aAAO,QAAQ,QAAQ,KAAK;AAAA,IAC9B;AAEA,UAAM,aAAa,mBAAmB,IAAI,QAAQ,QAAQ;AAC1D,UAAM,QAAQ,YAAY;AAE1B,QAAI,CAAC,cAAc,CAAC,OAAO;AACzB,aAAO,QAAQ,QAAQ,KAAK;AAAA,IAC9B;AAEA,QAAI,WAAW,eAAe;AAC5B,aAAO,QAAQ,QAAQ,KAAK;AAAA,IAC9B;AAEA,eAAW,gBAAgB;AAC3B,SAAK,YAAY,QAAQ,UAAU,KAAK;AAExC,QAAI;AACF,YAAM,SAAS;AACf,aAAO,QAAQ,QAAQ,IAAI;AAAA,IAC7B,SAAS,OAAO;AACd,cAAQ,MAAM,oCAAoC,KAAK;AACvD,iBAAW,gBAAgB;AAC3B,WAAK,YAAY,QAAQ,UAAU,IAAI;AACvC,aAAO,QAAQ,QAAQ,KAAK;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAM;AAAA,IAChB;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkitnext/sqlite-runner",
|
|
3
|
-
"version": "1.51.4-next.
|
|
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.
|
|
26
|
-
"@copilotkitnext/typescript-config": "1.51.4-next.
|
|
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.
|
|
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: {
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
352
|
-
replayAgent.messages.
|
|
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(
|
|
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(
|
|
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(
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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(
|
|
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(
|
|
614
|
-
|
|
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(
|
|
52
|
-
event
|
|
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
|
-
{
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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 = {
|
|
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(
|
|
254
|
-
|
|
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(
|
|
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(
|
|
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
|
-
{
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
EventType.
|
|
343
|
-
|
|
344
|
-
|
|
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(
|
|
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 () => {
|
package/src/sqlite-runner.ts
CHANGED
|
@@ -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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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): {
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
|
-
entry: [
|
|
5
|
-
format: [
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["cjs", "esm"],
|
|
6
6
|
dts: true,
|
|
7
7
|
sourcemap: true,
|
|
8
8
|
clean: true,
|
|
9
|
-
target:
|
|
10
|
-
outDir:
|
|
9
|
+
target: "es2022",
|
|
10
|
+
outDir: "dist",
|
|
11
11
|
});
|