UNPKG

@copilotkit/runtime

Version:

<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />

294 lines (292 loc) • 10.6 kB
import "reflect-metadata"; import { AgentRunner } from "./agent-runner.mjs"; import { finalizeRunEvents } from "@copilotkit/shared"; import { ReplaySubject } from "rxjs"; import { EventType, compactEvents } from "@ag-ui/client"; //#region src/v2/runtime/runner/in-memory.ts var InMemoryEventStore = class { constructor(threadId) { this.threadId = threadId; this.subject = null; this.isRunning = false; this.currentRunId = null; this.historicRuns = []; this.agent = null; this.runSubject = null; this.stopRequested = false; this.currentEvents = null; } }; const GLOBAL_STORE = /* @__PURE__ */ new Map(); var InMemoryAgentRunner = class extends AgentRunner { run(request) { let existingStore = GLOBAL_STORE.get(request.threadId); if (!existingStore) { existingStore = new InMemoryEventStore(request.threadId); GLOBAL_STORE.set(request.threadId, existingStore); } const store = existingStore; if (store.isRunning) throw new Error("Thread already running"); store.isRunning = true; store.currentRunId = request.input.runId; store.agent = request.agent; store.stopRequested = false; const seenMessageIds = /* @__PURE__ */ new Set(); const currentRunEvents = []; store.currentEvents = currentRunEvents; const historicMessageIds = /* @__PURE__ */ new Set(); for (const run of store.historicRuns) for (const event of run.events) { if ("messageId" in event && typeof event.messageId === "string") historicMessageIds.add(event.messageId); if (event.type === EventType.RUN_STARTED) { const messages = event.input?.messages ?? []; for (const message of messages) historicMessageIds.add(message.id); } } const nextSubject = new ReplaySubject(Infinity); const prevSubject = store.subject; store.subject = nextSubject; const runSubject = new ReplaySubject(Infinity); store.runSubject = runSubject; const runAgent = async () => { const parentRunId = store.historicRuns[store.historicRuns.length - 1]?.runId ?? null; try { await request.agent.runAgent(request.input, { onEvent: ({ event }) => { let processedEvent = event; if (event.type === EventType.RUN_STARTED) { const runStartedEvent = event; if (!runStartedEvent.input) { const sanitizedMessages = request.input.messages ? request.input.messages.filter((message) => !historicMessageIds.has(message.id)) : void 0; const updatedInput = { ...request.input, ...sanitizedMessages !== void 0 ? { messages: sanitizedMessages } : {} }; processedEvent = { ...runStartedEvent, input: updatedInput }; } } runSubject.next(processedEvent); nextSubject.next(processedEvent); currentRunEvents.push(processedEvent); }, onNewMessage: ({ message }) => { if (!seenMessageIds.has(message.id)) seenMessageIds.add(message.id); }, onRunStartedEvent: () => { if (request.input.messages) { for (const message of request.input.messages) if (!seenMessageIds.has(message.id)) seenMessageIds.add(message.id); } } }); const appendedEvents = finalizeRunEvents(currentRunEvents, { stopRequested: store.stopRequested }); for (const event of appendedEvents) { runSubject.next(event); nextSubject.next(event); } if (store.currentRunId) { const compactedEvents = compactEvents(currentRunEvents); store.historicRuns.push({ threadId: request.threadId, runId: store.currentRunId, agentId: request.agent.agentId ?? "default", parentRunId, events: compactedEvents, messages: Array.isArray(request.agent.messages) ? [...request.agent.messages] : [], createdAt: Date.now() }); } store.currentEvents = null; store.currentRunId = null; store.agent = null; store.runSubject = null; store.stopRequested = false; store.isRunning = false; runSubject.complete(); nextSubject.complete(); } catch (error) { const interruptionMessage = error instanceof Error ? error.message : String(error); const appendedEvents = finalizeRunEvents(currentRunEvents, { stopRequested: store.stopRequested, interruptionMessage }); for (const event of appendedEvents) { runSubject.next(event); nextSubject.next(event); } if (store.currentRunId && currentRunEvents.length > 0) { const compactedEvents = compactEvents(currentRunEvents); store.historicRuns.push({ threadId: request.threadId, runId: store.currentRunId, agentId: request.agent.agentId ?? "default", parentRunId, events: compactedEvents, messages: Array.isArray(request.agent.messages) ? [...request.agent.messages] : [], createdAt: Date.now() }); } store.currentEvents = null; store.currentRunId = null; store.agent = null; store.runSubject = null; store.stopRequested = false; store.isRunning = false; runSubject.complete(); nextSubject.complete(); } }; if (prevSubject) prevSubject.subscribe({ next: (e) => nextSubject.next(e), error: (err) => nextSubject.error(err), complete: () => {} }); runAgent(); return runSubject.asObservable(); } connect(request) { const store = GLOBAL_STORE.get(request.threadId); const connectionSubject = new ReplaySubject(Infinity); if (!store) { connectionSubject.complete(); return connectionSubject.asObservable(); } const allHistoricEvents = []; for (const run of store.historicRuns) allHistoricEvents.push(...run.events); const compactedEvents = compactEvents(allHistoricEvents); const emittedMessageIds = /* @__PURE__ */ new Set(); for (const event of compactedEvents) { connectionSubject.next(event); if ("messageId" in event && typeof event.messageId === "string") emittedMessageIds.add(event.messageId); } if (store.subject && (store.isRunning || store.stopRequested)) store.subject.subscribe({ next: (event) => { if ("messageId" in event && typeof event.messageId === "string" && emittedMessageIds.has(event.messageId)) return; connectionSubject.next(event); }, complete: () => connectionSubject.complete(), error: (err) => connectionSubject.error(err) }); else connectionSubject.complete(); return connectionSubject.asObservable(); } isRunning(request) { const store = GLOBAL_STORE.get(request.threadId); return Promise.resolve(store?.isRunning ?? false); } stop(request) { const store = GLOBAL_STORE.get(request.threadId); if (!store || !store.isRunning) return Promise.resolve(false); if (store.stopRequested) return Promise.resolve(false); store.stopRequested = true; store.isRunning = false; const agent = store.agent; if (!agent) { store.stopRequested = false; store.isRunning = false; return Promise.resolve(false); } try { agent.abortRun(); return Promise.resolve(true); } catch (error) { console.error("Failed to abort agent run", error); store.stopRequested = false; store.isRunning = true; return Promise.resolve(false); } } /** * Returns a summary of every thread that has been run through this runner. * * This powers the local-dev fallback for `GET /threads` when the Intelligence * platform is not configured. Each entry mirrors the shape of a platform * `ThreadRecord` so the HTTP handler can use the same response envelope. */ listThreads() { const threads = []; for (const [threadId, store] of GLOBAL_STORE) { if (store.historicRuns.length === 0) continue; const firstRun = store.historicRuns[0]; const lastRun = store.historicRuns[store.historicRuns.length - 1]; threads.push({ id: threadId, name: null, agentId: lastRun.agentId, organizationId: "", createdById: "", archived: false, createdAt: new Date(firstRun.createdAt).toISOString(), updatedAt: new Date(lastRun.createdAt).toISOString() }); } return threads.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()); } /** * Returns all messages for a thread, using the snapshot captured at the end * of the most recent run. * * This powers the local-dev fallback for `GET /threads/:threadId/messages` * when the Intelligence platform is not configured. The returned `Message[]` * objects come directly from the ag-ui agent, so their shape is compatible * with the Intelligence platform's `ThreadMessage` type. */ getThreadMessages(threadId) { const store = GLOBAL_STORE.get(threadId); if (!store || store.historicRuns.length === 0) return []; return store.historicRuns[store.historicRuns.length - 1].messages; } /** * Returns all AG-UI events for a thread, compacted across historic runs. * * Powers the local-dev fallback for `GET /threads/:threadId/events` when the * Intelligence platform is not configured. The compaction logic matches * the connection-replay path in {@link connect}, so the stream a * late-joining inspector sees matches what this method returns. */ getThreadEvents(threadId) { const store = GLOBAL_STORE.get(threadId); if (!store || store.historicRuns.length === 0) return []; const all = []; for (const run of store.historicRuns) all.push(...run.events); return compactEvents(all); } /** * Returns the agent state snapshot for a thread. * * Derived from the last `STATE_SNAPSHOT` in the compacted event stream. The * AG-UI `compactEvents` helper consolidates STATE_DELTA events and produces * a single trailing STATE_SNAPSHOT when state changes exist, so this is a * faithful view of state at the end of the most recent run. * * Returns `null` when the thread has never emitted a STATE_SNAPSHOT. */ getThreadState(threadId) { const events = this.getThreadEvents(threadId); for (let i = events.length - 1; i >= 0; i--) { const event = events[i]; if (event.type === EventType.STATE_SNAPSHOT) { const snapshot = event.snapshot; if (snapshot && typeof snapshot === "object") return snapshot; return null; } } return null; } /** * Clears all in-memory thread history. * * Powers the local-dev fallback for `POST /threads/clear`, letting consumers * (e.g. the demo's Clear button) reset to an empty thread list without * restarting the runtime. Intentionally not exposed on the Intelligence * platform path: there, thread history lives in a real database and must * not be wiped this way. */ clearThreads() { GLOBAL_STORE.clear(); } }; //#endregion export { InMemoryAgentRunner }; //# sourceMappingURL=in-memory.mjs.map