@copilotkit/runtime 1.56.4 → 1.56.5-canary.1777671752
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/dist/agent/converters/tanstack.cjs +121 -25
- package/dist/agent/converters/tanstack.cjs.map +1 -1
- package/dist/agent/converters/tanstack.d.cts.map +1 -1
- package/dist/agent/converters/tanstack.d.mts.map +1 -1
- package/dist/agent/converters/tanstack.mjs +121 -25
- package/dist/agent/converters/tanstack.mjs.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.cjs +8 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.cjs.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.d.cts.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.d.mts.map +1 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs +8 -1
- package/dist/lib/runtime/agent-integrations/langgraph/agent.mjs.map +1 -1
- package/dist/package.cjs +6 -6
- package/dist/package.mjs +6 -6
- package/dist/v2/index.d.cts +2 -2
- package/dist/v2/index.d.mts +2 -2
- package/dist/v2/runtime/core/fetch-handler.cjs +16 -0
- package/dist/v2/runtime/core/fetch-handler.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.cts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.d.mts.map +1 -1
- package/dist/v2/runtime/core/fetch-handler.mjs +17 -1
- package/dist/v2/runtime/core/fetch-handler.mjs.map +1 -1
- package/dist/v2/runtime/core/fetch-router.cjs +18 -1
- package/dist/v2/runtime/core/fetch-router.cjs.map +1 -1
- package/dist/v2/runtime/core/fetch-router.mjs +18 -1
- package/dist/v2/runtime/core/fetch-router.mjs.map +1 -1
- package/dist/v2/runtime/core/hooks.cjs.map +1 -1
- package/dist/v2/runtime/core/hooks.d.cts +8 -0
- package/dist/v2/runtime/core/hooks.d.cts.map +1 -1
- package/dist/v2/runtime/core/hooks.d.mts +8 -0
- package/dist/v2/runtime/core/hooks.d.mts.map +1 -1
- package/dist/v2/runtime/core/hooks.mjs.map +1 -1
- package/dist/v2/runtime/endpoints/express.cjs +5 -5
- package/dist/v2/runtime/endpoints/express.cjs.map +1 -1
- package/dist/v2/runtime/endpoints/express.mjs +5 -5
- package/dist/v2/runtime/endpoints/express.mjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.cjs +1 -0
- package/dist/v2/runtime/handlers/handle-run.cjs.map +1 -1
- package/dist/v2/runtime/handlers/handle-run.mjs +1 -0
- package/dist/v2/runtime/handlers/handle-run.mjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/threads.cjs +124 -12
- package/dist/v2/runtime/handlers/intelligence/threads.cjs.map +1 -1
- package/dist/v2/runtime/handlers/intelligence/threads.mjs +122 -13
- package/dist/v2/runtime/handlers/intelligence/threads.mjs.map +1 -1
- package/dist/v2/runtime/index.d.cts +1 -1
- package/dist/v2/runtime/index.d.mts +1 -1
- package/dist/v2/runtime/intelligence-platform/client.cjs +30 -0
- package/dist/v2/runtime/intelligence-platform/client.cjs.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.cts +66 -0
- package/dist/v2/runtime/intelligence-platform/client.d.cts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.d.mts +66 -0
- package/dist/v2/runtime/intelligence-platform/client.d.mts.map +1 -1
- package/dist/v2/runtime/intelligence-platform/client.mjs +30 -0
- package/dist/v2/runtime/intelligence-platform/client.mjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.cjs +94 -22
- package/dist/v2/runtime/runner/in-memory.cjs.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.cts +65 -2
- package/dist/v2/runtime/runner/in-memory.d.cts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.d.mts +65 -2
- package/dist/v2/runtime/runner/in-memory.d.mts.map +1 -1
- package/dist/v2/runtime/runner/in-memory.mjs +94 -22
- package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
- package/dist/v2/runtime/runner/index.d.cts +1 -1
- package/dist/v2/runtime/runner/index.d.mts +1 -1
- package/package.json +7 -7
- package/src/agent/__tests__/agent-test-helpers.ts +31 -1
- package/src/agent/__tests__/converter-tanstack.test.ts +280 -0
- package/src/agent/converters/tanstack.ts +167 -10
- package/src/lib/runtime/agent-integrations/langgraph/agent.ts +8 -1
- package/src/v2/runtime/__tests__/express-fetch-bridge.test.ts +1 -1
- package/src/v2/runtime/__tests__/fetch-handler-validation.test.ts +68 -0
- package/src/v2/runtime/__tests__/fetch-router.test.ts +46 -0
- package/src/v2/runtime/__tests__/handle-run.test.ts +97 -1
- package/src/v2/runtime/__tests__/handle-threads.test.ts +493 -13
- package/src/v2/runtime/core/fetch-handler.ts +19 -0
- package/src/v2/runtime/core/fetch-router.ts +33 -1
- package/src/v2/runtime/core/hooks.ts +3 -0
- package/src/v2/runtime/endpoints/express.ts +9 -3
- package/src/v2/runtime/handlers/handle-run.ts +4 -0
- package/src/v2/runtime/handlers/handle-threads.ts +3 -0
- package/src/v2/runtime/handlers/intelligence/threads.ts +200 -41
- package/src/v2/runtime/intelligence-platform/client.ts +76 -0
- package/src/v2/runtime/runner/__tests__/in-memory-runner.test.ts +417 -3
- package/src/v2/runtime/runner/in-memory.ts +137 -51
|
@@ -18,26 +18,7 @@ var InMemoryEventStore = class {
|
|
|
18
18
|
this.currentEvents = null;
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
|
-
const
|
|
22
|
-
function getGlobalStore() {
|
|
23
|
-
const globalAny = globalThis;
|
|
24
|
-
if (!globalAny[GLOBAL_STORE_KEY]) globalAny[GLOBAL_STORE_KEY] = {
|
|
25
|
-
stores: /* @__PURE__ */ new Map(),
|
|
26
|
-
historicRunsBackup: /* @__PURE__ */ new Map()
|
|
27
|
-
};
|
|
28
|
-
const data = globalAny[GLOBAL_STORE_KEY];
|
|
29
|
-
if (data.stores.size === 0 && data.historicRunsBackup.size > 0) for (const [threadId, historicRuns] of data.historicRunsBackup) {
|
|
30
|
-
const store = new InMemoryEventStore(threadId);
|
|
31
|
-
store.historicRuns = historicRuns;
|
|
32
|
-
data.stores.set(threadId, store);
|
|
33
|
-
}
|
|
34
|
-
return data.stores;
|
|
35
|
-
}
|
|
36
|
-
function backupHistoricRuns(threadId, historicRuns) {
|
|
37
|
-
const globalAny = globalThis;
|
|
38
|
-
if (globalAny[GLOBAL_STORE_KEY]) globalAny[GLOBAL_STORE_KEY].historicRunsBackup.set(threadId, historicRuns);
|
|
39
|
-
}
|
|
40
|
-
const GLOBAL_STORE = getGlobalStore();
|
|
21
|
+
const GLOBAL_STORE = /* @__PURE__ */ new Map();
|
|
41
22
|
var InMemoryAgentRunner = class extends AgentRunner {
|
|
42
23
|
run(request) {
|
|
43
24
|
let existingStore = GLOBAL_STORE.get(request.threadId);
|
|
@@ -110,11 +91,12 @@ var InMemoryAgentRunner = class extends AgentRunner {
|
|
|
110
91
|
store.historicRuns.push({
|
|
111
92
|
threadId: request.threadId,
|
|
112
93
|
runId: store.currentRunId,
|
|
94
|
+
agentId: request.agent.agentId ?? "default",
|
|
113
95
|
parentRunId,
|
|
114
96
|
events: compactedEvents,
|
|
97
|
+
messages: Array.isArray(request.agent.messages) ? [...request.agent.messages] : [],
|
|
115
98
|
createdAt: Date.now()
|
|
116
99
|
});
|
|
117
|
-
backupHistoricRuns(request.threadId, store.historicRuns);
|
|
118
100
|
}
|
|
119
101
|
store.currentEvents = null;
|
|
120
102
|
store.currentRunId = null;
|
|
@@ -139,11 +121,12 @@ var InMemoryAgentRunner = class extends AgentRunner {
|
|
|
139
121
|
store.historicRuns.push({
|
|
140
122
|
threadId: request.threadId,
|
|
141
123
|
runId: store.currentRunId,
|
|
124
|
+
agentId: request.agent.agentId ?? "default",
|
|
142
125
|
parentRunId,
|
|
143
126
|
events: compactedEvents,
|
|
127
|
+
messages: Array.isArray(request.agent.messages) ? [...request.agent.messages] : [],
|
|
144
128
|
createdAt: Date.now()
|
|
145
129
|
});
|
|
146
|
-
backupHistoricRuns(request.threadId, store.historicRuns);
|
|
147
130
|
}
|
|
148
131
|
store.currentEvents = null;
|
|
149
132
|
store.currentRunId = null;
|
|
@@ -215,6 +198,95 @@ var InMemoryAgentRunner = class extends AgentRunner {
|
|
|
215
198
|
return Promise.resolve(false);
|
|
216
199
|
}
|
|
217
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Returns a summary of every thread that has been run through this runner.
|
|
203
|
+
*
|
|
204
|
+
* This powers the local-dev fallback for `GET /threads` when the Intelligence
|
|
205
|
+
* platform is not configured. Each entry mirrors the shape of a platform
|
|
206
|
+
* `ThreadRecord` so the HTTP handler can use the same response envelope.
|
|
207
|
+
*/
|
|
208
|
+
listThreads() {
|
|
209
|
+
const threads = [];
|
|
210
|
+
for (const [threadId, store] of GLOBAL_STORE) {
|
|
211
|
+
if (store.historicRuns.length === 0) continue;
|
|
212
|
+
const firstRun = store.historicRuns[0];
|
|
213
|
+
const lastRun = store.historicRuns[store.historicRuns.length - 1];
|
|
214
|
+
threads.push({
|
|
215
|
+
id: threadId,
|
|
216
|
+
name: null,
|
|
217
|
+
agentId: lastRun.agentId,
|
|
218
|
+
organizationId: "",
|
|
219
|
+
createdById: "",
|
|
220
|
+
archived: false,
|
|
221
|
+
createdAt: new Date(firstRun.createdAt).toISOString(),
|
|
222
|
+
updatedAt: new Date(lastRun.createdAt).toISOString()
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return threads.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Returns all messages for a thread, using the snapshot captured at the end
|
|
229
|
+
* of the most recent run.
|
|
230
|
+
*
|
|
231
|
+
* This powers the local-dev fallback for `GET /threads/:threadId/messages`
|
|
232
|
+
* when the Intelligence platform is not configured. The returned `Message[]`
|
|
233
|
+
* objects come directly from the ag-ui agent, so their shape is compatible
|
|
234
|
+
* with the Intelligence platform's `ThreadMessage` type.
|
|
235
|
+
*/
|
|
236
|
+
getThreadMessages(threadId) {
|
|
237
|
+
const store = GLOBAL_STORE.get(threadId);
|
|
238
|
+
if (!store || store.historicRuns.length === 0) return [];
|
|
239
|
+
return store.historicRuns[store.historicRuns.length - 1].messages;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Returns all AG-UI events for a thread, compacted across historic runs.
|
|
243
|
+
*
|
|
244
|
+
* Powers the local-dev fallback for `GET /threads/:threadId/events` when the
|
|
245
|
+
* Intelligence platform is not configured. The compaction logic matches
|
|
246
|
+
* the connection-replay path in {@link connect}, so the stream a
|
|
247
|
+
* late-joining inspector sees matches what this method returns.
|
|
248
|
+
*/
|
|
249
|
+
getThreadEvents(threadId) {
|
|
250
|
+
const store = GLOBAL_STORE.get(threadId);
|
|
251
|
+
if (!store || store.historicRuns.length === 0) return [];
|
|
252
|
+
const all = [];
|
|
253
|
+
for (const run of store.historicRuns) all.push(...run.events);
|
|
254
|
+
return compactEvents(all);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Returns the agent state snapshot for a thread.
|
|
258
|
+
*
|
|
259
|
+
* Derived from the last `STATE_SNAPSHOT` in the compacted event stream. The
|
|
260
|
+
* AG-UI `compactEvents` helper consolidates STATE_DELTA events and produces
|
|
261
|
+
* a single trailing STATE_SNAPSHOT when state changes exist, so this is a
|
|
262
|
+
* faithful view of state at the end of the most recent run.
|
|
263
|
+
*
|
|
264
|
+
* Returns `null` when the thread has never emitted a STATE_SNAPSHOT.
|
|
265
|
+
*/
|
|
266
|
+
getThreadState(threadId) {
|
|
267
|
+
const events = this.getThreadEvents(threadId);
|
|
268
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
269
|
+
const event = events[i];
|
|
270
|
+
if (event.type === EventType.STATE_SNAPSHOT) {
|
|
271
|
+
const snapshot = event.snapshot;
|
|
272
|
+
if (snapshot && typeof snapshot === "object") return snapshot;
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Clears all in-memory thread history.
|
|
280
|
+
*
|
|
281
|
+
* Powers the local-dev fallback for `POST /threads/clear`, letting consumers
|
|
282
|
+
* (e.g. the demo's Clear button) reset to an empty thread list without
|
|
283
|
+
* restarting the runtime. Intentionally not exposed on the Intelligence
|
|
284
|
+
* platform path: there, thread history lives in a real database and must
|
|
285
|
+
* not be wiped this way.
|
|
286
|
+
*/
|
|
287
|
+
clearThreads() {
|
|
288
|
+
GLOBAL_STORE.clear();
|
|
289
|
+
}
|
|
218
290
|
};
|
|
219
291
|
|
|
220
292
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"in-memory.mjs","names":[],"sources":["../../../../src/v2/runtime/runner/in-memory.ts"],"sourcesContent":["import {\n AgentRunner,\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"./agent-runner\";\nimport { Observable, ReplaySubject } from \"rxjs\";\nimport {\n AbstractAgent,\n BaseEvent,\n EventType,\n MessagesSnapshotEvent,\n RunStartedEvent,\n compactEvents,\n} from \"@ag-ui/client\";\nimport { finalizeRunEvents } from \"@copilotkit/shared\";\n\ninterface HistoricRun {\n threadId: string;\n runId: string;\n parentRunId: string | null;\n events: BaseEvent[];\n createdAt: number;\n}\n\nclass InMemoryEventStore {\n constructor(public threadId: string) {}\n\n /** The subject that current consumers subscribe to. */\n subject: ReplaySubject<BaseEvent> | null = null;\n\n /** True while a run is actively producing events. */\n isRunning = false;\n\n /** Current run ID */\n currentRunId: string | null = null;\n\n /** Historic completed runs */\n historicRuns: HistoricRun[] = [];\n\n /** Currently running agent instance (if any). */\n agent: AbstractAgent | null = null;\n\n /** Subject returned from run() while the run is active. */\n runSubject: ReplaySubject<BaseEvent> | null = null;\n\n /** True once stop() has been requested but the run has not yet finalized. */\n stopRequested = false;\n\n /** Reference to the events emitted in the current run. */\n currentEvents: BaseEvent[] | null = null;\n}\n\n// Use a symbol key on globalThis to survive hot reloads in development\nconst GLOBAL_STORE_KEY = Symbol.for(\"@copilotkit/runtime/in-memory-store\");\n\ninterface GlobalStoreData {\n stores: Map<string, InMemoryEventStore>;\n historicRunsBackup: Map<string, HistoricRun[]>;\n}\n\nfunction getGlobalStore(): Map<string, InMemoryEventStore> {\n const globalAny = globalThis as unknown as Record<symbol, GlobalStoreData>;\n\n if (!globalAny[GLOBAL_STORE_KEY]) {\n globalAny[GLOBAL_STORE_KEY] = {\n stores: new Map<string, InMemoryEventStore>(),\n historicRunsBackup: new Map<string, HistoricRun[]>(),\n };\n }\n\n const data = globalAny[GLOBAL_STORE_KEY];\n\n // Restore historic runs from backup after hot reload\n // (when stores map is empty but backup has data)\n if (data.stores.size === 0 && data.historicRunsBackup.size > 0) {\n for (const [threadId, historicRuns] of data.historicRunsBackup) {\n const store = new InMemoryEventStore(threadId);\n store.historicRuns = historicRuns;\n data.stores.set(threadId, store);\n }\n }\n\n return data.stores;\n}\n\nfunction backupHistoricRuns(\n threadId: string,\n historicRuns: HistoricRun[],\n): void {\n const globalAny = globalThis as unknown as Record<symbol, GlobalStoreData>;\n if (globalAny[GLOBAL_STORE_KEY]) {\n globalAny[GLOBAL_STORE_KEY].historicRunsBackup.set(threadId, historicRuns);\n }\n}\n\nconst GLOBAL_STORE = getGlobalStore();\n\nexport class InMemoryAgentRunner extends AgentRunner {\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n let existingStore = GLOBAL_STORE.get(request.threadId);\n if (!existingStore) {\n existingStore = new InMemoryEventStore(request.threadId);\n GLOBAL_STORE.set(request.threadId, existingStore);\n }\n const store = existingStore; // Now store is const and non-null\n\n if (store.isRunning) {\n throw new Error(\"Thread already running\");\n }\n store.isRunning = true;\n store.currentRunId = request.input.runId;\n store.agent = request.agent;\n store.stopRequested = false;\n\n // Track seen message IDs and current run events for this run\n const seenMessageIds = new Set<string>();\n const currentRunEvents: BaseEvent[] = [];\n store.currentEvents = currentRunEvents;\n\n // Get all previously seen message IDs from historic runs\n const historicMessageIds = new Set<string>();\n for (const run of store.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 const nextSubject = new ReplaySubject<BaseEvent>(Infinity);\n const prevSubject = store.subject;\n\n // Update the store's subject immediately\n store.subject = nextSubject;\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\n store.runSubject = runSubject;\n\n // Helper function to run the agent and handle errors\n const runAgent = async () => {\n // Get parent run ID for chaining\n const lastRun = store.historicRuns[store.historicRuns.length - 1];\n const parentRunId = lastRun?.runId ?? null;\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 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 any messages from the input as seen so they aren't emitted twice\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 appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: store.stopRequested,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the completed run in memory with ONLY its events\n if (store.currentRunId) {\n // Compact the events before storing (like SQLite does)\n const compactedEvents = compactEvents(currentRunEvents);\n\n store.historicRuns.push({\n threadId: request.threadId,\n runId: store.currentRunId,\n parentRunId,\n events: compactedEvents,\n createdAt: Date.now(),\n });\n\n // Backup for hot reload survival\n backupHistoricRuns(request.threadId, store.historicRuns);\n }\n\n // Complete the run\n store.currentEvents = null;\n store.currentRunId = null;\n store.agent = null;\n store.runSubject = null;\n store.stopRequested = false;\n store.isRunning = false;\n runSubject.complete();\n nextSubject.complete();\n } catch (error) {\n const interruptionMessage =\n error instanceof Error ? error.message : String(error);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: store.stopRequested,\n interruptionMessage,\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 (store.currentRunId && currentRunEvents.length > 0) {\n // Compact the events before storing (like SQLite does)\n const compactedEvents = compactEvents(currentRunEvents);\n store.historicRuns.push({\n threadId: request.threadId,\n runId: store.currentRunId,\n parentRunId,\n events: compactedEvents,\n createdAt: Date.now(),\n });\n\n // Backup for hot reload survival\n backupHistoricRuns(request.threadId, store.historicRuns);\n }\n\n // Complete the run\n store.currentEvents = null;\n store.currentRunId = null;\n store.agent = null;\n store.runSubject = null;\n store.stopRequested = false;\n store.isRunning = false;\n runSubject.complete();\n nextSubject.complete();\n }\n };\n\n // Bridge previous events if they exist\n if (prevSubject) {\n prevSubject.subscribe({\n next: (e) => nextSubject.next(e),\n error: (err) => nextSubject.error(err),\n complete: () => {\n // Don't complete nextSubject here - it needs to stay open for new events\n },\n });\n }\n\n // Start the agent execution immediately (not lazily)\n runAgent();\n\n // Return the run subject (only agent events, no injected messages)\n return runSubject.asObservable();\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const store = GLOBAL_STORE.get(request.threadId);\n const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n if (!store) {\n // No store means no events\n connectionSubject.complete();\n return connectionSubject.asObservable();\n }\n\n // Collect all historic events from memory\n const allHistoricEvents: BaseEvent[] = [];\n for (const run of store.historicRuns) {\n allHistoricEvents.push(...run.events);\n }\n\n // Apply compaction to all historic events together (like SQLite)\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 if (store.subject && (store.isRunning || store.stopRequested)) {\n store.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 store = GLOBAL_STORE.get(request.threadId);\n return Promise.resolve(store?.isRunning ?? false);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const store = GLOBAL_STORE.get(request.threadId);\n if (!store || !store.isRunning) {\n return Promise.resolve(false);\n }\n if (store.stopRequested) {\n return Promise.resolve(false);\n }\n\n store.stopRequested = true;\n store.isRunning = false;\n\n const agent = store.agent;\n if (!agent) {\n store.stopRequested = false;\n store.isRunning = false;\n return Promise.resolve(false);\n }\n\n try {\n agent.abortRun();\n return Promise.resolve(true);\n } catch (error) {\n console.error(\"Failed to abort agent run\", error);\n store.stopRequested = false;\n store.isRunning = true;\n return Promise.resolve(false);\n }\n }\n}\n"],"mappings":";;;;;;;AA0BA,IAAM,qBAAN,MAAyB;CACvB,YAAY,AAAO,UAAkB;EAAlB;iBAGwB;mBAG/B;sBAGkB;sBAGA,EAAE;eAGF;oBAGgB;uBAG9B;uBAGoB;;;AAItC,MAAM,mBAAmB,OAAO,IAAI,sCAAsC;AAO1E,SAAS,iBAAkD;CACzD,MAAM,YAAY;AAElB,KAAI,CAAC,UAAU,kBACb,WAAU,oBAAoB;EAC5B,wBAAQ,IAAI,KAAiC;EAC7C,oCAAoB,IAAI,KAA4B;EACrD;CAGH,MAAM,OAAO,UAAU;AAIvB,KAAI,KAAK,OAAO,SAAS,KAAK,KAAK,mBAAmB,OAAO,EAC3D,MAAK,MAAM,CAAC,UAAU,iBAAiB,KAAK,oBAAoB;EAC9D,MAAM,QAAQ,IAAI,mBAAmB,SAAS;AAC9C,QAAM,eAAe;AACrB,OAAK,OAAO,IAAI,UAAU,MAAM;;AAIpC,QAAO,KAAK;;AAGd,SAAS,mBACP,UACA,cACM;CACN,MAAM,YAAY;AAClB,KAAI,UAAU,kBACZ,WAAU,kBAAkB,mBAAmB,IAAI,UAAU,aAAa;;AAI9E,MAAM,eAAe,gBAAgB;AAErC,IAAa,sBAAb,cAAyC,YAAY;CACnD,IAAI,SAAuD;EACzD,IAAI,gBAAgB,aAAa,IAAI,QAAQ,SAAS;AACtD,MAAI,CAAC,eAAe;AAClB,mBAAgB,IAAI,mBAAmB,QAAQ,SAAS;AACxD,gBAAa,IAAI,QAAQ,UAAU,cAAc;;EAEnD,MAAM,QAAQ;AAEd,MAAI,MAAM,UACR,OAAM,IAAI,MAAM,yBAAyB;AAE3C,QAAM,YAAY;AAClB,QAAM,eAAe,QAAQ,MAAM;AACnC,QAAM,QAAQ,QAAQ;AACtB,QAAM,gBAAgB;EAGtB,MAAM,iCAAiB,IAAI,KAAa;EACxC,MAAM,mBAAgC,EAAE;AACxC,QAAM,gBAAgB;EAGtB,MAAM,qCAAqB,IAAI,KAAa;AAC5C,OAAK,MAAM,OAAO,MAAM,aACtB,MAAK,MAAM,SAAS,IAAI,QAAQ;AAC9B,OAAI,eAAe,SAAS,OAAO,MAAM,cAAc,SACrD,oBAAmB,IAAI,MAAM,UAAU;AAEzC,OAAI,MAAM,SAAS,UAAU,aAAa;IAExC,MAAM,WADa,MACS,OAAO,YAAY,EAAE;AACjD,SAAK,MAAM,WAAW,SACpB,oBAAmB,IAAI,QAAQ,GAAG;;;EAM1C,MAAM,cAAc,IAAI,cAAyB,SAAS;EAC1D,MAAM,cAAc,MAAM;AAG1B,QAAM,UAAU;EAGhB,MAAM,aAAa,IAAI,cAAyB,SAAS;AACzD,QAAM,aAAa;EAGnB,MAAM,WAAW,YAAY;GAG3B,MAAM,cADU,MAAM,aAAa,MAAM,aAAa,SAAS,IAClC,SAAS;AAEtC,OAAI;AACF,UAAM,QAAQ,MAAM,SAAS,QAAQ,OAAO;KAC1C,UAAU,EAAE,YAAY;MACtB,IAAI,iBAA4B;AAChC,UAAI,MAAM,SAAS,UAAU,aAAa;OACxC,MAAM,kBAAkB;AACxB,WAAI,CAAC,gBAAgB,OAAO;QAC1B,MAAM,oBAAoB,QAAQ,MAAM,WACpC,QAAQ,MAAM,SAAS,QACpB,YAAY,CAAC,mBAAmB,IAAI,QAAQ,GAAG,CACjD,GACD;QACJ,MAAM,eAAe;SACnB,GAAG,QAAQ;SACX,GAAI,sBAAsB,SACtB,EAAE,UAAU,mBAAmB,GAC/B,EAAE;SACP;AACD,yBAAiB;SACf,GAAG;SACH,OAAO;SACR;;;AAIL,iBAAW,KAAK,eAAe;AAC/B,kBAAY,KAAK,eAAe;AAChC,uBAAiB,KAAK,eAAe;;KAEvC,eAAe,EAAE,cAAc;AAE7B,UAAI,CAAC,eAAe,IAAI,QAAQ,GAAG,CACjC,gBAAe,IAAI,QAAQ,GAAG;;KAGlC,yBAAyB;AAEvB,UAAI,QAAQ,MAAM,UAChB;YAAK,MAAM,WAAW,QAAQ,MAAM,SAClC,KAAI,CAAC,eAAe,IAAI,QAAQ,GAAG,CACjC,gBAAe,IAAI,QAAQ,GAAG;;;KAKvC,CAAC;IAEF,MAAM,iBAAiB,kBAAkB,kBAAkB,EACzD,eAAe,MAAM,eACtB,CAAC;AACF,SAAK,MAAM,SAAS,gBAAgB;AAClC,gBAAW,KAAK,MAAM;AACtB,iBAAY,KAAK,MAAM;;AAIzB,QAAI,MAAM,cAAc;KAEtB,MAAM,kBAAkB,cAAc,iBAAiB;AAEvD,WAAM,aAAa,KAAK;MACtB,UAAU,QAAQ;MAClB,OAAO,MAAM;MACb;MACA,QAAQ;MACR,WAAW,KAAK,KAAK;MACtB,CAAC;AAGF,wBAAmB,QAAQ,UAAU,MAAM,aAAa;;AAI1D,UAAM,gBAAgB;AACtB,UAAM,eAAe;AACrB,UAAM,QAAQ;AACd,UAAM,aAAa;AACnB,UAAM,gBAAgB;AACtB,UAAM,YAAY;AAClB,eAAW,UAAU;AACrB,gBAAY,UAAU;YACf,OAAO;IACd,MAAM,sBACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IACxD,MAAM,iBAAiB,kBAAkB,kBAAkB;KACzD,eAAe,MAAM;KACrB;KACD,CAAC;AACF,SAAK,MAAM,SAAS,gBAAgB;AAClC,gBAAW,KAAK,MAAM;AACtB,iBAAY,KAAK,MAAM;;AAIzB,QAAI,MAAM,gBAAgB,iBAAiB,SAAS,GAAG;KAErD,MAAM,kBAAkB,cAAc,iBAAiB;AACvD,WAAM,aAAa,KAAK;MACtB,UAAU,QAAQ;MAClB,OAAO,MAAM;MACb;MACA,QAAQ;MACR,WAAW,KAAK,KAAK;MACtB,CAAC;AAGF,wBAAmB,QAAQ,UAAU,MAAM,aAAa;;AAI1D,UAAM,gBAAgB;AACtB,UAAM,eAAe;AACrB,UAAM,QAAQ;AACd,UAAM,aAAa;AACnB,UAAM,gBAAgB;AACtB,UAAM,YAAY;AAClB,eAAW,UAAU;AACrB,gBAAY,UAAU;;;AAK1B,MAAI,YACF,aAAY,UAAU;GACpB,OAAO,MAAM,YAAY,KAAK,EAAE;GAChC,QAAQ,QAAQ,YAAY,MAAM,IAAI;GACtC,gBAAgB;GAGjB,CAAC;AAIJ,YAAU;AAGV,SAAO,WAAW,cAAc;;CAGlC,QAAQ,SAA2D;EACjE,MAAM,QAAQ,aAAa,IAAI,QAAQ,SAAS;EAChD,MAAM,oBAAoB,IAAI,cAAyB,SAAS;AAEhE,MAAI,CAAC,OAAO;AAEV,qBAAkB,UAAU;AAC5B,UAAO,kBAAkB,cAAc;;EAIzC,MAAM,oBAAiC,EAAE;AACzC,OAAK,MAAM,OAAO,MAAM,aACtB,mBAAkB,KAAK,GAAG,IAAI,OAAO;EAIvC,MAAM,kBAAkB,cAAc,kBAAkB;EAGxD,MAAM,oCAAoB,IAAI,KAAa;AAC3C,OAAK,MAAM,SAAS,iBAAiB;AACnC,qBAAkB,KAAK,MAAM;AAC7B,OAAI,eAAe,SAAS,OAAO,MAAM,cAAc,SACrD,mBAAkB,IAAI,MAAM,UAAU;;AAK1C,MAAI,MAAM,YAAY,MAAM,aAAa,MAAM,eAC7C,OAAM,QAAQ,UAAU;GACtB,OAAO,UAAU;AAEf,QACE,eAAe,SACf,OAAO,MAAM,cAAc,YAC3B,kBAAkB,IAAI,MAAM,UAAU,CAEtC;AAEF,sBAAkB,KAAK,MAAM;;GAE/B,gBAAgB,kBAAkB,UAAU;GAC5C,QAAQ,QAAQ,kBAAkB,MAAM,IAAI;GAC7C,CAAC;MAGF,mBAAkB,UAAU;AAG9B,SAAO,kBAAkB,cAAc;;CAGzC,UAAU,SAAwD;EAChE,MAAM,QAAQ,aAAa,IAAI,QAAQ,SAAS;AAChD,SAAO,QAAQ,QAAQ,OAAO,aAAa,MAAM;;CAGnD,KAAK,SAA+D;EAClE,MAAM,QAAQ,aAAa,IAAI,QAAQ,SAAS;AAChD,MAAI,CAAC,SAAS,CAAC,MAAM,UACnB,QAAO,QAAQ,QAAQ,MAAM;AAE/B,MAAI,MAAM,cACR,QAAO,QAAQ,QAAQ,MAAM;AAG/B,QAAM,gBAAgB;AACtB,QAAM,YAAY;EAElB,MAAM,QAAQ,MAAM;AACpB,MAAI,CAAC,OAAO;AACV,SAAM,gBAAgB;AACtB,SAAM,YAAY;AAClB,UAAO,QAAQ,QAAQ,MAAM;;AAG/B,MAAI;AACF,SAAM,UAAU;AAChB,UAAO,QAAQ,QAAQ,KAAK;WACrB,OAAO;AACd,WAAQ,MAAM,6BAA6B,MAAM;AACjD,SAAM,gBAAgB;AACtB,SAAM,YAAY;AAClB,UAAO,QAAQ,QAAQ,MAAM"}
|
|
1
|
+
{"version":3,"file":"in-memory.mjs","names":[],"sources":["../../../../src/v2/runtime/runner/in-memory.ts"],"sourcesContent":["import {\n AgentRunner,\n AgentRunnerConnectRequest,\n AgentRunnerIsRunningRequest,\n AgentRunnerRunRequest,\n type AgentRunnerStopRequest,\n} from \"./agent-runner\";\nimport { Observable, ReplaySubject } from \"rxjs\";\nimport {\n AbstractAgent,\n BaseEvent,\n EventType,\n Message,\n RunStartedEvent,\n StateSnapshotEvent,\n compactEvents,\n} from \"@ag-ui/client\";\nimport { finalizeRunEvents } from \"@copilotkit/shared\";\n\ninterface HistoricRun {\n threadId: string;\n runId: string;\n /** ID of the agent that executed this run. */\n agentId: string;\n parentRunId: string | null;\n events: BaseEvent[];\n /**\n * Snapshot of all messages (input + generated) at the end of this run.\n * Used by the local thread-messages fallback endpoint.\n */\n messages: Message[];\n createdAt: number;\n}\n\n/**\n * Lightweight thread summary returned by {@link InMemoryAgentRunner.listThreads}.\n * Shape matches the Intelligence platform's ThreadRecord so the same HTTP\n * response envelope can be used for both backends.\n */\nexport interface InMemoryThread {\n id: string;\n name: string | null;\n agentId: string;\n organizationId: \"\"; // always empty in in-memory mode\n createdById: \"\"; // always empty in in-memory mode\n archived: false; // always false in in-memory mode\n createdAt: string;\n updatedAt: string;\n}\n\nclass InMemoryEventStore {\n constructor(public threadId: string) {}\n\n /** The subject that current consumers subscribe to. */\n subject: ReplaySubject<BaseEvent> | null = null;\n\n /** True while a run is actively producing events. */\n isRunning = false;\n\n /** Current run ID */\n currentRunId: string | null = null;\n\n /** Historic completed runs */\n historicRuns: HistoricRun[] = [];\n\n /** Currently running agent instance (if any). */\n agent: AbstractAgent | null = null;\n\n /** Subject returned from run() while the run is active. */\n runSubject: ReplaySubject<BaseEvent> | null = null;\n\n /** True once stop() has been requested but the run has not yet finalized. */\n stopRequested = false;\n\n /** Reference to the events emitted in the current run. */\n currentEvents: BaseEvent[] | null = null;\n}\n\nconst GLOBAL_STORE = new Map<string, InMemoryEventStore>();\n\nexport class InMemoryAgentRunner extends AgentRunner {\n run(request: AgentRunnerRunRequest): Observable<BaseEvent> {\n let existingStore = GLOBAL_STORE.get(request.threadId);\n if (!existingStore) {\n existingStore = new InMemoryEventStore(request.threadId);\n GLOBAL_STORE.set(request.threadId, existingStore);\n }\n const store = existingStore; // Now store is const and non-null\n\n if (store.isRunning) {\n throw new Error(\"Thread already running\");\n }\n store.isRunning = true;\n store.currentRunId = request.input.runId;\n store.agent = request.agent;\n store.stopRequested = false;\n\n // Track seen message IDs and current run events for this run\n const seenMessageIds = new Set<string>();\n const currentRunEvents: BaseEvent[] = [];\n store.currentEvents = currentRunEvents;\n\n // Get all previously seen message IDs from historic runs\n const historicMessageIds = new Set<string>();\n for (const run of store.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 const nextSubject = new ReplaySubject<BaseEvent>(Infinity);\n const prevSubject = store.subject;\n\n // Update the store's subject immediately\n store.subject = nextSubject;\n\n // Create a subject for run() return value\n const runSubject = new ReplaySubject<BaseEvent>(Infinity);\n store.runSubject = runSubject;\n\n // Helper function to run the agent and handle errors\n const runAgent = async () => {\n // Get parent run ID for chaining\n const lastRun = store.historicRuns[store.historicRuns.length - 1];\n const parentRunId = lastRun?.runId ?? null;\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 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 any messages from the input as seen so they aren't emitted twice\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 appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: store.stopRequested,\n });\n for (const event of appendedEvents) {\n runSubject.next(event);\n nextSubject.next(event);\n }\n\n // Store the completed run in memory with ONLY its events\n if (store.currentRunId) {\n // Compact the events before storing (like SQLite does)\n const compactedEvents = compactEvents(currentRunEvents);\n\n store.historicRuns.push({\n threadId: request.threadId,\n runId: store.currentRunId,\n agentId: request.agent.agentId ?? \"default\",\n parentRunId,\n events: compactedEvents,\n // Snapshot all messages (input + generated) for the thread-messages endpoint\n messages: Array.isArray(request.agent.messages)\n ? [...request.agent.messages]\n : [],\n createdAt: Date.now(),\n });\n }\n\n // Complete the run\n store.currentEvents = null;\n store.currentRunId = null;\n store.agent = null;\n store.runSubject = null;\n store.stopRequested = false;\n store.isRunning = false;\n runSubject.complete();\n nextSubject.complete();\n } catch (error) {\n const interruptionMessage =\n error instanceof Error ? error.message : String(error);\n const appendedEvents = finalizeRunEvents(currentRunEvents, {\n stopRequested: store.stopRequested,\n interruptionMessage,\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 (store.currentRunId && currentRunEvents.length > 0) {\n // Compact the events before storing (like SQLite does)\n const compactedEvents = compactEvents(currentRunEvents);\n store.historicRuns.push({\n threadId: request.threadId,\n runId: store.currentRunId,\n agentId: request.agent.agentId ?? \"default\",\n parentRunId,\n events: compactedEvents,\n messages: Array.isArray(request.agent.messages)\n ? [...request.agent.messages]\n : [],\n createdAt: Date.now(),\n });\n }\n\n // Complete the run\n store.currentEvents = null;\n store.currentRunId = null;\n store.agent = null;\n store.runSubject = null;\n store.stopRequested = false;\n store.isRunning = false;\n runSubject.complete();\n nextSubject.complete();\n }\n };\n\n // Bridge previous events if they exist\n if (prevSubject) {\n prevSubject.subscribe({\n next: (e) => nextSubject.next(e),\n error: (err) => nextSubject.error(err),\n complete: () => {\n // Don't complete nextSubject here - it needs to stay open for new events\n },\n });\n }\n\n // Start the agent execution immediately (not lazily)\n runAgent();\n\n // Return the run subject (only agent events, no injected messages)\n return runSubject.asObservable();\n }\n\n connect(request: AgentRunnerConnectRequest): Observable<BaseEvent> {\n const store = GLOBAL_STORE.get(request.threadId);\n const connectionSubject = new ReplaySubject<BaseEvent>(Infinity);\n\n if (!store) {\n // No store means no events\n connectionSubject.complete();\n return connectionSubject.asObservable();\n }\n\n // Collect all historic events from memory\n const allHistoricEvents: BaseEvent[] = [];\n for (const run of store.historicRuns) {\n allHistoricEvents.push(...run.events);\n }\n\n // Apply compaction to all historic events together (like SQLite)\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 if (store.subject && (store.isRunning || store.stopRequested)) {\n store.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 store = GLOBAL_STORE.get(request.threadId);\n return Promise.resolve(store?.isRunning ?? false);\n }\n\n stop(request: AgentRunnerStopRequest): Promise<boolean | undefined> {\n const store = GLOBAL_STORE.get(request.threadId);\n if (!store || !store.isRunning) {\n return Promise.resolve(false);\n }\n if (store.stopRequested) {\n return Promise.resolve(false);\n }\n\n store.stopRequested = true;\n store.isRunning = false;\n\n const agent = store.agent;\n if (!agent) {\n store.stopRequested = false;\n store.isRunning = false;\n return Promise.resolve(false);\n }\n\n try {\n agent.abortRun();\n return Promise.resolve(true);\n } catch (error) {\n console.error(\"Failed to abort agent run\", error);\n store.stopRequested = false;\n store.isRunning = true;\n return Promise.resolve(false);\n }\n }\n\n /**\n * Returns a summary of every thread that has been run through this runner.\n *\n * This powers the local-dev fallback for `GET /threads` when the Intelligence\n * platform is not configured. Each entry mirrors the shape of a platform\n * `ThreadRecord` so the HTTP handler can use the same response envelope.\n */\n listThreads(): InMemoryThread[] {\n const threads: InMemoryThread[] = [];\n for (const [threadId, store] of GLOBAL_STORE) {\n if (store.historicRuns.length === 0) continue;\n const firstRun = store.historicRuns[0]!;\n const lastRun = store.historicRuns[store.historicRuns.length - 1]!;\n threads.push({\n id: threadId,\n name: null,\n agentId: lastRun.agentId,\n organizationId: \"\",\n createdById: \"\",\n archived: false,\n createdAt: new Date(firstRun.createdAt).toISOString(),\n updatedAt: new Date(lastRun.createdAt).toISOString(),\n });\n }\n // Most recently updated first\n return threads.sort(\n (a, b) =>\n new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),\n );\n }\n\n /**\n * Returns all messages for a thread, using the snapshot captured at the end\n * of the most recent run.\n *\n * This powers the local-dev fallback for `GET /threads/:threadId/messages`\n * when the Intelligence platform is not configured. The returned `Message[]`\n * objects come directly from the ag-ui agent, so their shape is compatible\n * with the Intelligence platform's `ThreadMessage` type.\n */\n getThreadMessages(threadId: string): Message[] {\n const store = GLOBAL_STORE.get(threadId);\n if (!store || store.historicRuns.length === 0) return [];\n // The last run's snapshot has the complete conversation history\n return store.historicRuns[store.historicRuns.length - 1]!.messages;\n }\n\n /**\n * Returns all AG-UI events for a thread, compacted across historic runs.\n *\n * Powers the local-dev fallback for `GET /threads/:threadId/events` when the\n * Intelligence platform is not configured. The compaction logic matches\n * the connection-replay path in {@link connect}, so the stream a\n * late-joining inspector sees matches what this method returns.\n */\n getThreadEvents(threadId: string): BaseEvent[] {\n const store = GLOBAL_STORE.get(threadId);\n if (!store || store.historicRuns.length === 0) return [];\n const all: BaseEvent[] = [];\n for (const run of store.historicRuns) all.push(...run.events);\n return compactEvents(all);\n }\n\n /**\n * Returns the agent state snapshot for a thread.\n *\n * Derived from the last `STATE_SNAPSHOT` in the compacted event stream. The\n * AG-UI `compactEvents` helper consolidates STATE_DELTA events and produces\n * a single trailing STATE_SNAPSHOT when state changes exist, so this is a\n * faithful view of state at the end of the most recent run.\n *\n * Returns `null` when the thread has never emitted a STATE_SNAPSHOT.\n */\n getThreadState(threadId: string): Record<string, unknown> | null {\n const events = this.getThreadEvents(threadId);\n // Walk backwards — the last snapshot wins.\n for (let i = events.length - 1; i >= 0; i--) {\n const event = events[i]!;\n if (event.type === EventType.STATE_SNAPSHOT) {\n const snapshot = (event as StateSnapshotEvent).snapshot;\n if (snapshot && typeof snapshot === \"object\") {\n return snapshot as Record<string, unknown>;\n }\n return null;\n }\n }\n return null;\n }\n\n /**\n * Clears all in-memory thread history.\n *\n * Powers the local-dev fallback for `POST /threads/clear`, letting consumers\n * (e.g. the demo's Clear button) reset to an empty thread list without\n * restarting the runtime. Intentionally not exposed on the Intelligence\n * platform path: there, thread history lives in a real database and must\n * not be wiped this way.\n */\n clearThreads(): void {\n GLOBAL_STORE.clear();\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest } from "./agent-runner.cjs";
|
|
3
|
-
import { InMemoryAgentRunner } from "./in-memory.cjs";
|
|
3
|
+
import { InMemoryAgentRunner, InMemoryThread } from "./in-memory.cjs";
|
|
4
4
|
import { IntelligenceAgentRunner, IntelligenceAgentRunnerOptions, RunnerStartupBoundary } from "./intelligence.cjs";
|
|
5
5
|
import { finalizeRunEvents } from "@copilotkit/shared";
|
|
6
6
|
export { finalizeRunEvents };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { AgentRunner, AgentRunnerConnectRequest, AgentRunnerIsRunningRequest, AgentRunnerRunRequest, AgentRunnerStopRequest } from "./agent-runner.mjs";
|
|
3
|
-
import { InMemoryAgentRunner } from "./in-memory.mjs";
|
|
3
|
+
import { InMemoryAgentRunner, InMemoryThread } from "./in-memory.mjs";
|
|
4
4
|
import { IntelligenceAgentRunner, IntelligenceAgentRunnerOptions, RunnerStartupBoundary } from "./intelligence.mjs";
|
|
5
5
|
import { finalizeRunEvents as finalizeRunEvents$1 } from "@copilotkit/shared";
|
|
6
6
|
export { finalizeRunEvents$1 as finalizeRunEvents };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/runtime",
|
|
3
|
-
"version": "1.56.
|
|
3
|
+
"version": "1.56.5-canary.1777671752",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -77,10 +77,10 @@
|
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
79
|
"@ag-ui/a2ui-middleware": "0.0.5",
|
|
80
|
-
"@ag-ui/client": "0.0.
|
|
81
|
-
"@ag-ui/core": "0.0.
|
|
82
|
-
"@ag-ui/encoder": "0.0.
|
|
83
|
-
"@ag-ui/langgraph": "0.0.
|
|
80
|
+
"@ag-ui/client": "0.0.53",
|
|
81
|
+
"@ag-ui/core": "0.0.53",
|
|
82
|
+
"@ag-ui/encoder": "0.0.53",
|
|
83
|
+
"@ag-ui/langgraph": "0.0.31",
|
|
84
84
|
"@ag-ui/mcp-apps-middleware": "0.0.3",
|
|
85
85
|
"@ai-sdk/anthropic": "^3.0.49",
|
|
86
86
|
"@ai-sdk/google": "^3.0.33",
|
|
@@ -115,10 +115,10 @@
|
|
|
115
115
|
"uuid": "^10.0.0",
|
|
116
116
|
"ws": "^8.18.0",
|
|
117
117
|
"zod": "^3.23.3",
|
|
118
|
-
"@copilotkit/shared": "1.56.
|
|
118
|
+
"@copilotkit/shared": "1.56.5-canary.1777671752"
|
|
119
119
|
},
|
|
120
120
|
"devDependencies": {
|
|
121
|
-
"@copilotkit/aimock": "
|
|
121
|
+
"@copilotkit/aimock": "latest",
|
|
122
122
|
"@swc/core": "1.5.28",
|
|
123
123
|
"@types/cors": "^2.8.17",
|
|
124
124
|
"@types/express": "^4.17.21",
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* BuiltInAgent-specific factories, mock stream builders, and assertion helpers.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { EventType
|
|
8
|
+
import { EventType } from "@ag-ui/client";
|
|
9
|
+
import type { BaseEvent, RunAgentInput } from "@ag-ui/client";
|
|
9
10
|
import type { Observable } from "rxjs";
|
|
10
11
|
import { BuiltInAgent } from "../index";
|
|
11
12
|
import type { AgentFactoryContext, BuiltInAgentFactoryConfig } from "../index";
|
|
@@ -85,6 +86,35 @@ export function tanstackToolCallEnd(toolCallId: string) {
|
|
|
85
86
|
return { type: "TOOL_CALL_END", toolCallId } as const;
|
|
86
87
|
}
|
|
87
88
|
|
|
89
|
+
/** TanStack tool-call result chunk. `content` is what the tool's `execute()` returned. */
|
|
90
|
+
export function tanstackToolCallResult(toolCallId: string, content: unknown) {
|
|
91
|
+
return { type: "TOOL_CALL_RESULT", toolCallId, content } as const;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** TanStack reasoning lifecycle chunk builders */
|
|
95
|
+
export function tanstackReasoningStart(messageId: string) {
|
|
96
|
+
return { type: "REASONING_START", messageId } as const;
|
|
97
|
+
}
|
|
98
|
+
export function tanstackReasoningMessageStart(messageId: string) {
|
|
99
|
+
return {
|
|
100
|
+
type: "REASONING_MESSAGE_START",
|
|
101
|
+
messageId,
|
|
102
|
+
role: "reasoning",
|
|
103
|
+
} as const;
|
|
104
|
+
}
|
|
105
|
+
export function tanstackReasoningMessageContent(
|
|
106
|
+
messageId: string,
|
|
107
|
+
delta: string,
|
|
108
|
+
) {
|
|
109
|
+
return { type: "REASONING_MESSAGE_CONTENT", messageId, delta } as const;
|
|
110
|
+
}
|
|
111
|
+
export function tanstackReasoningMessageEnd(messageId: string) {
|
|
112
|
+
return { type: "REASONING_MESSAGE_END", messageId } as const;
|
|
113
|
+
}
|
|
114
|
+
export function tanstackReasoningEnd(messageId: string) {
|
|
115
|
+
return { type: "REASONING_END", messageId } as const;
|
|
116
|
+
}
|
|
117
|
+
|
|
88
118
|
// ---------------------------------------------------------------------------
|
|
89
119
|
// Mock async iterable builders
|
|
90
120
|
// ---------------------------------------------------------------------------
|
|
@@ -11,6 +11,12 @@ import {
|
|
|
11
11
|
tanstackToolCallStart,
|
|
12
12
|
tanstackToolCallArgs,
|
|
13
13
|
tanstackToolCallEnd,
|
|
14
|
+
tanstackToolCallResult,
|
|
15
|
+
tanstackReasoningStart,
|
|
16
|
+
tanstackReasoningMessageStart,
|
|
17
|
+
tanstackReasoningMessageContent,
|
|
18
|
+
tanstackReasoningMessageEnd,
|
|
19
|
+
tanstackReasoningEnd,
|
|
14
20
|
} from "./agent-test-helpers";
|
|
15
21
|
|
|
16
22
|
describe("TanStack AI converter (via Agent)", () => {
|
|
@@ -312,3 +318,277 @@ describe("TanStack AI converter (via Agent)", () => {
|
|
|
312
318
|
});
|
|
313
319
|
});
|
|
314
320
|
});
|
|
321
|
+
|
|
322
|
+
describe("TanStack AI converter — state tools", () => {
|
|
323
|
+
it("emits STATE_SNAPSHOT before TOOL_CALL_RESULT for AGUISendStateSnapshot", async () => {
|
|
324
|
+
const snapshot = { counter: 5, items: ["x", "y"] };
|
|
325
|
+
const agent = createAgent("tanstack", [
|
|
326
|
+
tanstackToolCallStart("call1", "AGUISendStateSnapshot"),
|
|
327
|
+
tanstackToolCallEnd("call1"),
|
|
328
|
+
tanstackToolCallResult("call1", { success: true, snapshot }),
|
|
329
|
+
]);
|
|
330
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
331
|
+
|
|
332
|
+
expectLifecycleWrapped(events);
|
|
333
|
+
|
|
334
|
+
const snapshotIdx = events.findIndex(
|
|
335
|
+
(e) => e.type === EventType.STATE_SNAPSHOT,
|
|
336
|
+
);
|
|
337
|
+
const resultIdx = events.findIndex(
|
|
338
|
+
(e) => e.type === EventType.TOOL_CALL_RESULT,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(snapshotIdx).toBeGreaterThanOrEqual(0);
|
|
342
|
+
expect(resultIdx).toBeGreaterThanOrEqual(0);
|
|
343
|
+
expect(snapshotIdx).toBeLessThan(resultIdx);
|
|
344
|
+
expect(eventField<unknown>(events[snapshotIdx], "snapshot")).toEqual(
|
|
345
|
+
snapshot,
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("emits STATE_DELTA before TOOL_CALL_RESULT for AGUISendStateDelta", async () => {
|
|
350
|
+
const delta = [{ op: "replace", path: "/counter", value: 7 }];
|
|
351
|
+
const agent = createAgent("tanstack", [
|
|
352
|
+
tanstackToolCallStart("call1", "AGUISendStateDelta"),
|
|
353
|
+
tanstackToolCallEnd("call1"),
|
|
354
|
+
tanstackToolCallResult("call1", { success: true, delta }),
|
|
355
|
+
]);
|
|
356
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
357
|
+
|
|
358
|
+
expectLifecycleWrapped(events);
|
|
359
|
+
|
|
360
|
+
const deltaIdx = events.findIndex((e) => e.type === EventType.STATE_DELTA);
|
|
361
|
+
const resultIdx = events.findIndex(
|
|
362
|
+
(e) => e.type === EventType.TOOL_CALL_RESULT,
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
expect(deltaIdx).toBeGreaterThanOrEqual(0);
|
|
366
|
+
expect(deltaIdx).toBeLessThan(resultIdx);
|
|
367
|
+
expect(eventField<unknown>(events[deltaIdx], "delta")).toEqual(delta);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("emits STATE_SNAPSHOT when payload arrives in raw.result instead of raw.content", async () => {
|
|
371
|
+
// Regression: serialization fell back to raw.result (`?? raw.result ?? null`)
|
|
372
|
+
// but state-tool detection only inspected raw.content, so STATE_* events
|
|
373
|
+
// were silently dropped if upstream used `result` for the state-tool body.
|
|
374
|
+
// See the "TOOL_CALL_RESULT with object content serializes to JSON" test
|
|
375
|
+
// above which already exercises the `result` field on a non-state tool.
|
|
376
|
+
const snapshot = { counter: 99 };
|
|
377
|
+
const agent = createAgent("tanstack", [
|
|
378
|
+
tanstackToolCallStart("call1", "AGUISendStateSnapshot"),
|
|
379
|
+
tanstackToolCallEnd("call1"),
|
|
380
|
+
{
|
|
381
|
+
type: "TOOL_CALL_RESULT",
|
|
382
|
+
toolCallId: "call1",
|
|
383
|
+
result: { success: true, snapshot },
|
|
384
|
+
},
|
|
385
|
+
]);
|
|
386
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
387
|
+
|
|
388
|
+
expectLifecycleWrapped(events);
|
|
389
|
+
|
|
390
|
+
const snapshotIdx = events.findIndex(
|
|
391
|
+
(e) => e.type === EventType.STATE_SNAPSHOT,
|
|
392
|
+
);
|
|
393
|
+
expect(snapshotIdx).toBeGreaterThanOrEqual(0);
|
|
394
|
+
expect(eventField<unknown>(events[snapshotIdx], "snapshot")).toEqual(
|
|
395
|
+
snapshot,
|
|
396
|
+
);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("does NOT emit STATE_* for non-state tool results", async () => {
|
|
400
|
+
const agent = createAgent("tanstack", [
|
|
401
|
+
tanstackToolCallStart("call1", "getWeather"),
|
|
402
|
+
tanstackToolCallEnd("call1"),
|
|
403
|
+
tanstackToolCallResult("call1", {
|
|
404
|
+
snapshot: { spoofed: true },
|
|
405
|
+
delta: [{ op: "x" }],
|
|
406
|
+
}),
|
|
407
|
+
]);
|
|
408
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
409
|
+
|
|
410
|
+
expectLifecycleWrapped(events);
|
|
411
|
+
|
|
412
|
+
expect(
|
|
413
|
+
events.find(
|
|
414
|
+
(e) =>
|
|
415
|
+
e.type === EventType.STATE_SNAPSHOT ||
|
|
416
|
+
e.type === EventType.STATE_DELTA,
|
|
417
|
+
),
|
|
418
|
+
).toBeUndefined();
|
|
419
|
+
expect(
|
|
420
|
+
events.find((e) => e.type === EventType.TOOL_CALL_RESULT),
|
|
421
|
+
).toBeDefined();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
describe("TanStack AI converter — reasoning", () => {
|
|
426
|
+
it("emits the full REASONING lifecycle for reasoning chunks", async () => {
|
|
427
|
+
const agent = createAgent("tanstack", [
|
|
428
|
+
tanstackReasoningStart("r1"),
|
|
429
|
+
tanstackReasoningMessageStart("r1"),
|
|
430
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
431
|
+
tanstackReasoningMessageEnd("r1"),
|
|
432
|
+
tanstackReasoningEnd("r1"),
|
|
433
|
+
]);
|
|
434
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
435
|
+
|
|
436
|
+
expectLifecycleWrapped(events);
|
|
437
|
+
|
|
438
|
+
// Strip the lifecycle wrap and inspect the inner sequence.
|
|
439
|
+
const inner = events.slice(1, -1).map((e) => e.type);
|
|
440
|
+
expect(inner).toEqual([
|
|
441
|
+
EventType.REASONING_START,
|
|
442
|
+
EventType.REASONING_MESSAGE_START,
|
|
443
|
+
EventType.REASONING_MESSAGE_CONTENT,
|
|
444
|
+
EventType.REASONING_MESSAGE_END,
|
|
445
|
+
EventType.REASONING_END,
|
|
446
|
+
]);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("auto-closes an open reasoning lifecycle when text starts", async () => {
|
|
450
|
+
const agent = createAgent("tanstack", [
|
|
451
|
+
tanstackReasoningStart("r1"),
|
|
452
|
+
tanstackReasoningMessageStart("r1"),
|
|
453
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
454
|
+
// No REASONING_MESSAGE_END / REASONING_END before text
|
|
455
|
+
tanstackTextChunk("Hi"),
|
|
456
|
+
]);
|
|
457
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
458
|
+
const types = events.map((e) => e.type);
|
|
459
|
+
|
|
460
|
+
const reasonEndIdx = types.indexOf(EventType.REASONING_END);
|
|
461
|
+
const reasonMsgEndIdx = types.indexOf(EventType.REASONING_MESSAGE_END);
|
|
462
|
+
const textIdx = types.indexOf(EventType.TEXT_MESSAGE_CHUNK);
|
|
463
|
+
|
|
464
|
+
expect(reasonMsgEndIdx).toBeGreaterThan(-1);
|
|
465
|
+
expect(reasonEndIdx).toBeGreaterThan(-1);
|
|
466
|
+
expect(reasonEndIdx).toBeLessThan(textIdx);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it("auto-closes an open reasoning lifecycle when a tool call starts", async () => {
|
|
470
|
+
const agent = createAgent("tanstack", [
|
|
471
|
+
tanstackReasoningStart("r1"),
|
|
472
|
+
tanstackReasoningMessageStart("r1"),
|
|
473
|
+
tanstackReasoningMessageContent("r1", "..."),
|
|
474
|
+
tanstackToolCallStart("t1", "x"),
|
|
475
|
+
]);
|
|
476
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
477
|
+
const types = events.map((e) => e.type);
|
|
478
|
+
|
|
479
|
+
expect(types).toContain(EventType.REASONING_MESSAGE_END);
|
|
480
|
+
expect(types).toContain(EventType.REASONING_END);
|
|
481
|
+
expect(types.indexOf(EventType.REASONING_END)).toBeLessThan(
|
|
482
|
+
types.indexOf(EventType.TOOL_CALL_START),
|
|
483
|
+
);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it("auto-closes when the stream ends without explicit reasoning end", async () => {
|
|
487
|
+
const agent = createAgent("tanstack", [
|
|
488
|
+
tanstackReasoningStart("r1"),
|
|
489
|
+
tanstackReasoningMessageStart("r1"),
|
|
490
|
+
tanstackReasoningMessageContent("r1", "x"),
|
|
491
|
+
]);
|
|
492
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
493
|
+
const types = events.map((e) => e.type);
|
|
494
|
+
|
|
495
|
+
expect(types).toContain(EventType.REASONING_MESSAGE_END);
|
|
496
|
+
expect(types).toContain(EventType.REASONING_END);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it("emits REASONING_MESSAGE_END before REASONING_END when upstream sends END with message still open", async () => {
|
|
500
|
+
// Regression: if the converter received REASONING_END while a message
|
|
501
|
+
// was still open, it cleared run-open and emitted END only — leaving
|
|
502
|
+
// message-open true. The next non-reasoning chunk then triggered
|
|
503
|
+
// closeReasoningIfOpen, which emitted MSG_END AFTER END (wrong order).
|
|
504
|
+
// Fix: REASONING_END handler closes any open message first.
|
|
505
|
+
const agent = createAgent("tanstack", [
|
|
506
|
+
tanstackReasoningStart("r1"),
|
|
507
|
+
tanstackReasoningMessageStart("r1"),
|
|
508
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
509
|
+
// Upstream skips MSG_END and goes straight to END
|
|
510
|
+
tanstackReasoningEnd("r1"),
|
|
511
|
+
tanstackTextChunk("Hi"),
|
|
512
|
+
]);
|
|
513
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
514
|
+
const types = events.map((e) => e.type);
|
|
515
|
+
|
|
516
|
+
const msgEndIdx = types.indexOf(EventType.REASONING_MESSAGE_END);
|
|
517
|
+
const endIdx = types.indexOf(EventType.REASONING_END);
|
|
518
|
+
|
|
519
|
+
expect(msgEndIdx).toBeGreaterThan(-1);
|
|
520
|
+
expect(endIdx).toBeGreaterThan(-1);
|
|
521
|
+
expect(msgEndIdx).toBeLessThan(endIdx);
|
|
522
|
+
// No duplicate MSG_END or END from auto-close on the text chunk
|
|
523
|
+
expect(
|
|
524
|
+
types.filter((t) => t === EventType.REASONING_MESSAGE_END),
|
|
525
|
+
).toHaveLength(1);
|
|
526
|
+
expect(types.filter((t) => t === EventType.REASONING_END)).toHaveLength(1);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it("auto-closes prior reasoning run when a new REASONING_START arrives without END", async () => {
|
|
530
|
+
// Regression: REASONING_START used to overwrite reasoningMessageId
|
|
531
|
+
// unconditionally, orphaning the prior run's MSG_END / END.
|
|
532
|
+
// Fix: REASONING_START handler calls closeReasoningIfOpen() first.
|
|
533
|
+
const agent = createAgent("tanstack", [
|
|
534
|
+
tanstackReasoningStart("r1"),
|
|
535
|
+
tanstackReasoningMessageStart("r1"),
|
|
536
|
+
tanstackReasoningMessageContent("r1", "first"),
|
|
537
|
+
// Second START with no intervening END
|
|
538
|
+
tanstackReasoningStart("r2"),
|
|
539
|
+
tanstackReasoningMessageStart("r2"),
|
|
540
|
+
tanstackReasoningMessageContent("r2", "second"),
|
|
541
|
+
tanstackReasoningMessageEnd("r2"),
|
|
542
|
+
tanstackReasoningEnd("r2"),
|
|
543
|
+
]);
|
|
544
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
545
|
+
const types = events.map((e) => e.type);
|
|
546
|
+
|
|
547
|
+
// Two complete START → MSG_START → ... → MSG_END → END sequences
|
|
548
|
+
expect(types.filter((t) => t === EventType.REASONING_START)).toHaveLength(
|
|
549
|
+
2,
|
|
550
|
+
);
|
|
551
|
+
expect(types.filter((t) => t === EventType.REASONING_END)).toHaveLength(2);
|
|
552
|
+
expect(
|
|
553
|
+
types.filter((t) => t === EventType.REASONING_MESSAGE_END),
|
|
554
|
+
).toHaveLength(2);
|
|
555
|
+
|
|
556
|
+
// First START's prior message gets closed BEFORE the second START
|
|
557
|
+
const firstEndIdx = types.indexOf(EventType.REASONING_END);
|
|
558
|
+
const secondStartIdx = types.indexOf(
|
|
559
|
+
EventType.REASONING_START,
|
|
560
|
+
firstEndIdx + 1,
|
|
561
|
+
);
|
|
562
|
+
expect(firstEndIdx).toBeLessThan(secondStartIdx);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it("does NOT duplicate REASONING_MESSAGE_END when upstream emits it explicitly before text", async () => {
|
|
566
|
+
// Regression: a single isInReasoning flag conflated message-open with
|
|
567
|
+
// run-open, so closeReasoningIfOpen on TEXT_MESSAGE_CONTENT emitted a
|
|
568
|
+
// second REASONING_MESSAGE_END after upstream's own. Track message-open
|
|
569
|
+
// and run-open separately so closeReasoningIfOpen owes only what's still
|
|
570
|
+
// open.
|
|
571
|
+
const agent = createAgent("tanstack", [
|
|
572
|
+
tanstackReasoningStart("r1"),
|
|
573
|
+
tanstackReasoningMessageStart("r1"),
|
|
574
|
+
tanstackReasoningMessageContent("r1", "thinking"),
|
|
575
|
+
tanstackReasoningMessageEnd("r1"),
|
|
576
|
+
// No explicit REASONING_END before text — closeReasoningIfOpen should
|
|
577
|
+
// emit REASONING_END but NOT a second REASONING_MESSAGE_END.
|
|
578
|
+
tanstackTextChunk("Hi"),
|
|
579
|
+
]);
|
|
580
|
+
const events = await collectEvents(agent.run(createDefaultInput()));
|
|
581
|
+
const types = events.map((e) => e.type);
|
|
582
|
+
|
|
583
|
+
const msgEndCount = types.filter(
|
|
584
|
+
(t) => t === EventType.REASONING_MESSAGE_END,
|
|
585
|
+
).length;
|
|
586
|
+
const endCount = types.filter((t) => t === EventType.REASONING_END).length;
|
|
587
|
+
|
|
588
|
+
expect(msgEndCount).toBe(1);
|
|
589
|
+
expect(endCount).toBe(1);
|
|
590
|
+
expect(types.indexOf(EventType.REASONING_END)).toBeLessThan(
|
|
591
|
+
types.indexOf(EventType.TEXT_MESSAGE_CHUNK),
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
});
|