@copilotkit/runtime 1.66.2 → 1.66.4

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.
Files changed (75) hide show
  1. package/dist/agent/converters/aisdk.cjs +17 -9
  2. package/dist/agent/converters/aisdk.cjs.map +1 -1
  3. package/dist/agent/converters/aisdk.d.cts +1 -1
  4. package/dist/agent/converters/aisdk.d.cts.map +1 -1
  5. package/dist/agent/converters/aisdk.d.mts +1 -1
  6. package/dist/agent/converters/aisdk.d.mts.map +1 -1
  7. package/dist/agent/converters/aisdk.mjs +17 -9
  8. package/dist/agent/converters/aisdk.mjs.map +1 -1
  9. package/dist/agent/converters/tanstack.cjs +17 -9
  10. package/dist/agent/converters/tanstack.cjs.map +1 -1
  11. package/dist/agent/converters/tanstack.d.cts +1 -1
  12. package/dist/agent/converters/tanstack.d.cts.map +1 -1
  13. package/dist/agent/converters/tanstack.d.mts +1 -1
  14. package/dist/agent/converters/tanstack.d.mts.map +1 -1
  15. package/dist/agent/converters/tanstack.mjs +17 -9
  16. package/dist/agent/converters/tanstack.mjs.map +1 -1
  17. package/dist/agent/index.cjs +6 -4
  18. package/dist/agent/index.cjs.map +1 -1
  19. package/dist/agent/index.d.cts.map +1 -1
  20. package/dist/agent/index.d.mts.map +1 -1
  21. package/dist/agent/index.mjs +6 -4
  22. package/dist/agent/index.mjs.map +1 -1
  23. package/dist/agent/state-delta.cjs +322 -0
  24. package/dist/agent/state-delta.cjs.map +1 -0
  25. package/dist/agent/state-delta.mjs +321 -0
  26. package/dist/agent/state-delta.mjs.map +1 -0
  27. package/dist/package.cjs +1 -1
  28. package/dist/runtime/package.mjs +1 -1
  29. package/dist/v2/index.cjs +6 -1
  30. package/dist/v2/index.d.cts +2 -2
  31. package/dist/v2/index.d.mts +2 -2
  32. package/dist/v2/index.mjs +2 -2
  33. package/dist/v2/runtime/core/channel-manager.cjs +110 -9
  34. package/dist/v2/runtime/core/channel-manager.cjs.map +1 -1
  35. package/dist/v2/runtime/core/channel-manager.d.cts +79 -8
  36. package/dist/v2/runtime/core/channel-manager.d.cts.map +1 -1
  37. package/dist/v2/runtime/core/channel-manager.d.mts +79 -8
  38. package/dist/v2/runtime/core/channel-manager.d.mts.map +1 -1
  39. package/dist/v2/runtime/core/channel-manager.mjs +110 -9
  40. package/dist/v2/runtime/core/channel-manager.mjs.map +1 -1
  41. package/dist/v2/runtime/core/runtime.cjs.map +1 -1
  42. package/dist/v2/runtime/core/runtime.d.cts +1 -0
  43. package/dist/v2/runtime/core/runtime.d.cts.map +1 -1
  44. package/dist/v2/runtime/core/runtime.d.mts +1 -0
  45. package/dist/v2/runtime/core/runtime.d.mts.map +1 -1
  46. package/dist/v2/runtime/core/runtime.mjs.map +1 -1
  47. package/dist/v2/runtime/handlers/shared/agent-utils.cjs +2 -5
  48. package/dist/v2/runtime/handlers/shared/agent-utils.cjs.map +1 -1
  49. package/dist/v2/runtime/handlers/shared/agent-utils.mjs +2 -5
  50. package/dist/v2/runtime/handlers/shared/agent-utils.mjs.map +1 -1
  51. package/dist/v2/runtime/handlers/shared/mcp-apps-servers.cjs +23 -0
  52. package/dist/v2/runtime/handlers/shared/mcp-apps-servers.cjs.map +1 -0
  53. package/dist/v2/runtime/handlers/shared/mcp-apps-servers.mjs +22 -0
  54. package/dist/v2/runtime/handlers/shared/mcp-apps-servers.mjs.map +1 -0
  55. package/dist/v2/runtime/index.d.cts +1 -1
  56. package/dist/v2/runtime/index.d.mts +1 -1
  57. package/dist/v2/runtime/open-generative-ui-middleware.cjs +1 -0
  58. package/dist/v2/runtime/open-generative-ui-middleware.cjs.map +1 -1
  59. package/dist/v2/runtime/open-generative-ui-middleware.mjs +1 -0
  60. package/dist/v2/runtime/open-generative-ui-middleware.mjs.map +1 -1
  61. package/dist/v2/runtime/runner/in-memory.cjs +335 -101
  62. package/dist/v2/runtime/runner/in-memory.cjs.map +1 -1
  63. package/dist/v2/runtime/runner/in-memory.d.cts +246 -6
  64. package/dist/v2/runtime/runner/in-memory.d.cts.map +1 -1
  65. package/dist/v2/runtime/runner/in-memory.d.mts +246 -6
  66. package/dist/v2/runtime/runner/in-memory.d.mts.map +1 -1
  67. package/dist/v2/runtime/runner/in-memory.mjs +331 -102
  68. package/dist/v2/runtime/runner/in-memory.mjs.map +1 -1
  69. package/dist/v2/runtime/runner/index.d.cts +1 -1
  70. package/dist/v2/runtime/runner/index.d.mts +1 -1
  71. package/dist/v2/runtime/runner/index.mjs +1 -1
  72. package/package.json +5 -5
  73. package/skills/runtime/references/agent-runners-in-memory.md +49 -34
  74. package/skills/runtime/references/agent-runners.md +47 -21
  75. package/skills/runtime/references/wiring-mcp-apps-middleware.md +5 -0
@@ -5,6 +5,71 @@ import { ReplaySubject } from "rxjs";
5
5
  import { EventType, compactEvents } from "@ag-ui/client";
6
6
 
7
7
  //#region src/v2/runtime/runner/in-memory.ts
8
+ const ɵINMEMORY_DEFAULTS = {
9
+ maxThreads: 1e3,
10
+ maxRunsPerThread: 100,
11
+ maxBytes: 512 * 1024 ** 2
12
+ };
13
+ /**
14
+ * A limit value is well-formed iff it is a non-negative integer OR `+Infinity`.
15
+ * `+Infinity` is the documented "disabled/unbounded" sentinel and `0` is the
16
+ * documented run-cap disable sentinel; both are non-negative and pass. Every
17
+ * enforcement site (`evictThreadsIfNeeded`, `enforceRunCap`,
18
+ * `evictByBytesIfNeeded`) compares its counter against the limit with `>` in a
19
+ * `while`/`if` guard, so only these shapes keep those loops finite and correct.
20
+ * Rejected: negatives (drive `count > -1` true on an empty collection, so
21
+ * `enforceRunCap` `shift()!`s `undefined` and throws), `-Infinity` (loops never
22
+ * terminate their intent — always "over"), `NaN` (every `>` is false, silently
23
+ * disabling the bound), and non-integer finites (fractional caps are nonsense).
24
+ */
25
+ function ɵisValidLimit(value) {
26
+ return value === Infinity || Number.isInteger(value) && value >= 0;
27
+ }
28
+ /**
29
+ * Normalize a fully-resolved limits bag so every field is well-formed before it
30
+ * can reach an enforcement loop. Each field is validated independently against
31
+ * {@link ɵisValidLimit}; an invalid value is CLAMPED to its
32
+ * {@link ɵINMEMORY_DEFAULTS} floor and a single `console.warn` naming the field
33
+ * and the received value is emitted.
34
+ *
35
+ * Clamp-and-warn (rather than throw) is deliberate and matches this file's
36
+ * established posture toward bad input: `ɵestimateBytes` swallows serialization
37
+ * failures and returns 0, the limits-clobber path warns rather than throwing,
38
+ * and both the eviction and clobber logs are wrapped so "logging must never
39
+ * break construction/a run". Constructing a bounded in-memory runner is a
40
+ * best-effort, non-durable convenience; a typo'd bound must degrade to a safe
41
+ * default, never abort construction or (worse) surface later as an unhandled
42
+ * rejection from the fire-and-forget finalize path.
43
+ */
44
+ function ɵnormalizeLimits(limits) {
45
+ const normalized = { ...limits };
46
+ for (const field of Object.keys(ɵINMEMORY_DEFAULTS)) {
47
+ const value = limits[field];
48
+ if (!ɵisValidLimit(value)) {
49
+ const fallback = ɵINMEMORY_DEFAULTS[field];
50
+ normalized[field] = fallback;
51
+ try {
52
+ console.warn(`[CopilotKit] InMemoryAgentRunner: invalid ${field} value ${String(value)} (expected a non-negative integer or Infinity); falling back to ${String(fallback)}.`);
53
+ } catch {}
54
+ }
55
+ }
56
+ return normalized;
57
+ }
58
+ const EVICTION_GUIDANCE = "[CopilotKit] InMemoryAgentRunner evicted in-memory thread history to stay under memory limits. This runner is bounded and non-durable by design. For durable or production threads, configure an Intelligence backend.";
59
+ const LIMITS_CLOBBER_GUIDANCE = "[CopilotKit] InMemoryAgentRunner was constructed with in-memory limits that differ from the already-configured process-global store; the last-constructed runner's limits apply to ALL in-memory threads (the store is shared per-process). Configure a single consistent set of limits, or use an Intelligence backend for isolated bounds.";
60
+ /**
61
+ * Best-effort approximate byte size of a value, via serialized length.
62
+ * Never throws — returns 0 when the value cannot be serialized. This is an
63
+ * approximation (UTF-16 length, not exact heap bytes), used only for relative
64
+ * accounting against `maxBytes`.
65
+ */
66
+ function ɵestimateBytes(value) {
67
+ try {
68
+ return JSON.stringify(value)?.length ?? 0;
69
+ } catch {
70
+ return 0;
71
+ }
72
+ }
8
73
  var InMemoryEventStore = class {
9
74
  constructor(threadId) {
10
75
  this.threadId = threadId;
@@ -15,39 +80,240 @@ var InMemoryEventStore = class {
15
80
  this.agent = null;
16
81
  this.runSubject = null;
17
82
  this.stopRequested = false;
83
+ this.activeFinalize = null;
18
84
  this.currentEvents = null;
85
+ this.messagesSnapshot = [];
86
+ this.approxMessagesSnapshotBytes = 0;
87
+ this.createdAt = null;
88
+ }
89
+ };
90
+ var ɵBoundedThreadStore = class {
91
+ constructor(limits) {
92
+ this.map = /* @__PURE__ */ new Map();
93
+ this.totalBytes = 0;
94
+ this.warned = false;
95
+ this.limitsExplicitlySet = false;
96
+ this.clobberWarned = false;
97
+ this.limits = ɵnormalizeLimits(limits);
98
+ }
99
+ get byteTotal() {
100
+ return this.totalBytes;
101
+ }
102
+ /**
103
+ * The store's CURRENT effective bounds. Exposed (with the `ɵ` internal-API
104
+ * prefix) so a partial `setLimits` can coalesce unspecified fields against the
105
+ * live config rather than the hardcoded {@link ɵINMEMORY_DEFAULTS} — a partial
106
+ * update must be a partial update, never a silent reset of the fields the
107
+ * caller did not mention. Returns a copy so callers cannot mutate the store's
108
+ * bounds through it.
109
+ */
110
+ get ɵlimits() {
111
+ return { ...this.limits };
112
+ }
113
+ /**
114
+ * Reconfigure the process-global store's bounds. Called by the
115
+ * {@link InMemoryAgentRunner} constructor when limits are passed. Because the
116
+ * store is a per-process singleton, this replaces the bounds for ALL in-memory
117
+ * threads. Emits {@link LIMITS_CLOBBER_GUIDANCE} at most ONCE per store when a
118
+ * SECOND (or later) explicit set arrives whose resolved values differ from the
119
+ * prior explicit set — i.e. a genuine clobber of an already-customized config.
120
+ * The first explicit customization (defaults → custom) is the intended
121
+ * override and never warns; identical re-sets never warn.
122
+ */
123
+ setLimits(limits) {
124
+ const normalized = ɵnormalizeLimits(limits);
125
+ if (this.limitsExplicitlySet && !this.clobberWarned && (normalized.maxThreads !== this.limits.maxThreads || normalized.maxRunsPerThread !== this.limits.maxRunsPerThread || normalized.maxBytes !== this.limits.maxBytes)) {
126
+ this.clobberWarned = true;
127
+ try {
128
+ console.warn(LIMITS_CLOBBER_GUIDANCE);
129
+ } catch {}
130
+ }
131
+ this.limitsExplicitlySet = true;
132
+ this.limits = normalized;
133
+ }
134
+ get size() {
135
+ return this.map.size;
136
+ }
137
+ /** Re-insert at the tail so Map iteration order stays LRU-first. */
138
+ touchOrder(threadId, store) {
139
+ this.map.delete(threadId);
140
+ this.map.set(threadId, store);
141
+ }
142
+ getOrCreate(threadId) {
143
+ const existing = this.map.get(threadId);
144
+ if (existing) {
145
+ this.touchOrder(threadId, existing);
146
+ return existing;
147
+ }
148
+ const store = new InMemoryEventStore(threadId);
149
+ this.map.set(threadId, store);
150
+ this.evictThreadsIfNeeded(threadId);
151
+ return store;
152
+ }
153
+ get(threadId, opts) {
154
+ const store = this.map.get(threadId);
155
+ if (store && opts.touch) this.touchOrder(threadId, store);
156
+ return store;
157
+ }
158
+ peek(threadId) {
159
+ return this.map.get(threadId);
160
+ }
161
+ /**
162
+ * Evict the least-recently-used thread that is neither running NOR
163
+ * mid-finalization. Returns false if none evictable. The `protect` thread
164
+ * (typically the one just created) is never evicted, so a fresh thread is not
165
+ * immediately dropped when it is the only non-running candidate.
166
+ *
167
+ * A thread is skipped while `isRunning` OR `stopRequested` is set.
168
+ * `stop()` flips `isRunning` to false the moment it aborts the agent, but the
169
+ * run keeps finalizing asynchronously (the abort trips the `catch` in
170
+ * `runAgent`, which later calls `appendRun`). During that window
171
+ * `stopRequested` stays true; evicting the thread then would make the pending
172
+ * `appendRun` hit `if (!store) return` and silently drop the aborted run's
173
+ * history. Guarding on `stopRequested` keeps the thread alive until
174
+ * finalization completes.
175
+ */
176
+ evictOneLru(protect) {
177
+ for (const [threadId, store] of this.map) {
178
+ if (threadId === protect) continue;
179
+ if (store.isRunning || store.stopRequested) continue;
180
+ this.removeThread(threadId, store);
181
+ this.noteEviction();
182
+ return true;
183
+ }
184
+ return false;
185
+ }
186
+ appendRun(threadId, run) {
187
+ const store = this.map.get(threadId);
188
+ if (!store) return;
189
+ if (store.createdAt === null) store.createdAt = run.createdAt;
190
+ if (run.messages.length > 0) {
191
+ this.totalBytes -= store.approxMessagesSnapshotBytes;
192
+ store.messagesSnapshot = run.messages;
193
+ store.approxMessagesSnapshotBytes = ɵestimateBytes(run.messages);
194
+ this.totalBytes += store.approxMessagesSnapshotBytes;
195
+ }
196
+ run.messages = [];
197
+ run.approxMessageBytes = 0;
198
+ run.approxEventBytes = ɵestimateBytes(run.events);
199
+ store.historicRuns.push(run);
200
+ this.totalBytes += run.approxEventBytes;
201
+ this.touchOrder(threadId, store);
202
+ this.enforceRunCap(store);
203
+ this.evictByBytesIfNeeded(threadId);
204
+ }
205
+ enforceRunCap(store) {
206
+ const cap = this.limits.maxRunsPerThread;
207
+ if (!cap || cap === Infinity) return;
208
+ while (store.historicRuns.length > cap) {
209
+ const dropped = store.historicRuns.shift();
210
+ this.totalBytes -= dropped.approxEventBytes ?? 0;
211
+ this.noteEviction();
212
+ }
213
+ }
214
+ /**
215
+ * Trim the store back under the byte ceiling by evicting LRU non-running
216
+ * threads. `protect` (the just-appended thread) is never self-evicted, so a
217
+ * fresh run pushes OTHER threads out rather than dropping itself.
218
+ */
219
+ evictByBytesIfNeeded(protect) {
220
+ while (this.totalBytes > this.limits.maxBytes) if (!this.evictOneLru(protect)) break;
221
+ }
222
+ removeThread(threadId, store) {
223
+ for (const run of store.historicRuns) this.totalBytes -= run.approxEventBytes ?? 0;
224
+ this.totalBytes -= store.approxMessagesSnapshotBytes;
225
+ this.map.delete(threadId);
226
+ }
227
+ evictThreadsIfNeeded(protect) {
228
+ while (this.map.size > this.limits.maxThreads) if (!this.evictOneLru(protect)) break;
229
+ }
230
+ noteEviction() {
231
+ if (this.warned) return;
232
+ this.warned = true;
233
+ try {
234
+ console.warn(EVICTION_GUIDANCE);
235
+ } catch {}
236
+ }
237
+ listThreads() {
238
+ const threads = [];
239
+ for (const [threadId, store] of this.map) {
240
+ if (store.historicRuns.length === 0) continue;
241
+ const lastRun = store.historicRuns[store.historicRuns.length - 1];
242
+ threads.push({
243
+ id: threadId,
244
+ name: null,
245
+ agentId: lastRun.agentId,
246
+ organizationId: "",
247
+ createdById: "",
248
+ archived: false,
249
+ createdAt: new Date(store.createdAt ?? lastRun.createdAt).toISOString(),
250
+ updatedAt: new Date(lastRun.createdAt).toISOString()
251
+ });
252
+ }
253
+ return threads.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
254
+ }
255
+ clear() {
256
+ this.map.clear();
257
+ this.totalBytes = 0;
258
+ this.warned = false;
19
259
  }
20
260
  };
21
- const GLOBAL_STORE = /* @__PURE__ */ new Map();
261
+ /**
262
+ * Process-wide singleton backing every {@link InMemoryAgentRunner}. Exported
263
+ * (with the `ɵ` internal-API prefix) so tests can inspect the exact store the
264
+ * runner writes to; not part of the public API.
265
+ */
266
+ const ɵGLOBAL_STORE = new ɵBoundedThreadStore(ɵINMEMORY_DEFAULTS);
267
+ const sharedStore = ɵGLOBAL_STORE;
22
268
  var InMemoryAgentRunner = class extends AgentRunner {
269
+ /**
270
+ * @param options Per-runner behavior (`onConcurrentRun`) plus optional bounds
271
+ * for the in-memory store ({@link InMemoryLimits}).
272
+ *
273
+ * Note the differing scopes: `onConcurrentRun` is per-runner instance, while
274
+ * the limits reconfigure the PROCESS-GLOBAL store shared by every
275
+ * `InMemoryAgentRunner`. Omit the limits for safe defaults
276
+ * ({@link ɵINMEMORY_DEFAULTS}); passing none leaves the store untouched. When
277
+ * multiple runners are constructed with differing limits, the last-constructed
278
+ * wins — in practice the OSS/SSE default construction passes nothing. If a
279
+ * second (or later) runner is constructed with limits that DIFFER from an
280
+ * already-customized store, a one-time `console.warn` is emitted to signal that
281
+ * the shared store's bounds are being clobbered for ALL in-memory threads.
282
+ */
23
283
  constructor(options) {
24
284
  super();
25
285
  this.ɵsupportsLocalThreadEndpoints = true;
26
- this.onConcurrentRun = options?.onConcurrentRun ?? "throw";
286
+ const { onConcurrentRun, ...limits } = options ?? {};
287
+ this.onConcurrentRun = onConcurrentRun ?? "throw";
288
+ if (limits.maxThreads !== void 0 || limits.maxRunsPerThread !== void 0 || limits.maxBytes !== void 0) {
289
+ const current = sharedStore.ɵlimits;
290
+ sharedStore.setLimits({
291
+ maxThreads: limits.maxThreads ?? current.maxThreads,
292
+ maxRunsPerThread: limits.maxRunsPerThread ?? current.maxRunsPerThread,
293
+ maxBytes: limits.maxBytes ?? current.maxBytes
294
+ });
295
+ }
27
296
  }
28
297
  run(request) {
29
- let existingStore = GLOBAL_STORE.get(request.threadId);
30
- if (!existingStore) {
31
- existingStore = new InMemoryEventStore(request.threadId);
32
- GLOBAL_STORE.set(request.threadId, existingStore);
33
- }
34
- const store = existingStore;
35
- if (store.isRunning) {
298
+ const store = sharedStore.getOrCreate(request.threadId);
299
+ if (store.isRunning || store.stopRequested) {
36
300
  if (this.onConcurrentRun !== "supersede") throw new Error("Thread already running");
37
301
  const priorAgent = store.agent;
38
- store.stopRequested = true;
302
+ const priorFinalize = store.activeFinalize;
303
+ if (priorFinalize) priorFinalize.stopRequested = true;
39
304
  store.isRunning = false;
40
305
  if (priorAgent) try {
41
306
  priorAgent.abortRun();
42
307
  } catch (error) {
43
308
  console.error("Failed to abort superseded run", error);
44
309
  }
45
- store.stopRequested = false;
46
310
  }
47
311
  store.isRunning = true;
48
312
  store.currentRunId = request.input.runId;
49
313
  store.agent = request.agent;
50
314
  store.stopRequested = false;
315
+ const finalizeControl = { stopRequested: false };
316
+ store.activeFinalize = finalizeControl;
51
317
  const seenMessageIds = /* @__PURE__ */ new Set();
52
318
  const currentRunEvents = [];
53
319
  store.currentEvents = currentRunEvents;
@@ -60,12 +326,48 @@ var InMemoryAgentRunner = class extends AgentRunner {
60
326
  }
61
327
  }
62
328
  const nextSubject = new ReplaySubject(Infinity);
63
- const prevSubject = store.subject;
64
329
  store.subject = nextSubject;
65
330
  const runSubject = new ReplaySubject(Infinity);
66
331
  store.runSubject = runSubject;
67
332
  const runAgent = async () => {
68
333
  const parentRunId = store.historicRuns[store.historicRuns.length - 1]?.runId ?? null;
334
+ const finalizeRun = (opts) => {
335
+ const isError = opts.interruptionMessage !== void 0;
336
+ const preFinalizeEventCount = currentRunEvents.length;
337
+ const appendedEvents = finalizeRunEvents(currentRunEvents, {
338
+ stopRequested: finalizeControl.stopRequested,
339
+ ...isError ? { interruptionMessage: opts.interruptionMessage } : {}
340
+ });
341
+ for (const event of appendedEvents) {
342
+ runSubject.next(event);
343
+ nextSubject.next(event);
344
+ }
345
+ const ownsThread = store.currentRunId === request.input.runId;
346
+ if (ownsThread && (!isError || preFinalizeEventCount > 0)) {
347
+ const compactedEvents = compactEvents(currentRunEvents);
348
+ sharedStore.appendRun(request.threadId, {
349
+ threadId: request.threadId,
350
+ runId: request.input.runId,
351
+ agentId: request.agent.agentId ?? "default",
352
+ parentRunId,
353
+ events: compactedEvents,
354
+ messages: Array.isArray(request.agent.messages) ? [...request.agent.messages] : [],
355
+ createdAt: Date.now()
356
+ });
357
+ }
358
+ if (ownsThread) {
359
+ store.currentEvents = null;
360
+ store.currentRunId = null;
361
+ store.agent = null;
362
+ store.runSubject = null;
363
+ store.stopRequested = false;
364
+ store.isRunning = false;
365
+ store.activeFinalize = null;
366
+ }
367
+ runSubject.complete();
368
+ nextSubject.complete();
369
+ if (store.subject === nextSubject) store.subject = null;
370
+ };
69
371
  try {
70
372
  await request.agent.runAgent(request.input, {
71
373
  onEvent: ({ event }) => {
@@ -94,77 +396,16 @@ var InMemoryAgentRunner = class extends AgentRunner {
94
396
  }
95
397
  }
96
398
  });
97
- const appendedEvents = finalizeRunEvents(currentRunEvents, { stopRequested: store.stopRequested });
98
- for (const event of appendedEvents) {
99
- runSubject.next(event);
100
- nextSubject.next(event);
101
- }
102
- if (store.currentRunId === request.input.runId) {
103
- const compactedEvents = compactEvents(currentRunEvents);
104
- store.historicRuns.push({
105
- threadId: request.threadId,
106
- runId: request.input.runId,
107
- agentId: request.agent.agentId ?? "default",
108
- parentRunId,
109
- events: compactedEvents,
110
- messages: Array.isArray(request.agent.messages) ? [...request.agent.messages] : [],
111
- createdAt: Date.now()
112
- });
113
- }
114
- if (store.currentRunId === request.input.runId) {
115
- store.currentEvents = null;
116
- store.currentRunId = null;
117
- store.agent = null;
118
- store.runSubject = null;
119
- store.stopRequested = false;
120
- store.isRunning = false;
121
- }
122
- runSubject.complete();
123
- nextSubject.complete();
399
+ finalizeRun({});
124
400
  } catch (error) {
125
- const interruptionMessage = error instanceof Error ? error.message : String(error);
126
- const appendedEvents = finalizeRunEvents(currentRunEvents, {
127
- stopRequested: store.stopRequested,
128
- interruptionMessage
129
- });
130
- for (const event of appendedEvents) {
131
- runSubject.next(event);
132
- nextSubject.next(event);
133
- }
134
- if (store.currentRunId === request.input.runId && currentRunEvents.length > 0) {
135
- const compactedEvents = compactEvents(currentRunEvents);
136
- store.historicRuns.push({
137
- threadId: request.threadId,
138
- runId: request.input.runId,
139
- agentId: request.agent.agentId ?? "default",
140
- parentRunId,
141
- events: compactedEvents,
142
- messages: Array.isArray(request.agent.messages) ? [...request.agent.messages] : [],
143
- createdAt: Date.now()
144
- });
145
- }
146
- if (store.currentRunId === request.input.runId) {
147
- store.currentEvents = null;
148
- store.currentRunId = null;
149
- store.agent = null;
150
- store.runSubject = null;
151
- store.stopRequested = false;
152
- store.isRunning = false;
153
- }
154
- runSubject.complete();
155
- nextSubject.complete();
401
+ finalizeRun({ interruptionMessage: error instanceof Error ? error.message : String(error) });
156
402
  }
157
403
  };
158
- if (prevSubject) prevSubject.subscribe({
159
- next: (e) => nextSubject.next(e),
160
- error: (err) => nextSubject.error(err),
161
- complete: () => {}
162
- });
163
404
  runAgent();
164
405
  return runSubject.asObservable();
165
406
  }
166
407
  connect(request) {
167
- const store = GLOBAL_STORE.get(request.threadId);
408
+ const store = sharedStore.get(request.threadId, { touch: true });
168
409
  const connectionSubject = new ReplaySubject(Infinity);
169
410
  if (!store) {
170
411
  connectionSubject.complete();
@@ -190,20 +431,23 @@ var InMemoryAgentRunner = class extends AgentRunner {
190
431
  return connectionSubject.asObservable();
191
432
  }
192
433
  isRunning(request) {
193
- const store = GLOBAL_STORE.get(request.threadId);
434
+ const store = sharedStore.peek(request.threadId);
194
435
  return Promise.resolve(store?.isRunning ?? false);
195
436
  }
196
437
  stop(request) {
197
- const store = GLOBAL_STORE.get(request.threadId);
438
+ const store = sharedStore.peek(request.threadId);
198
439
  if (!store || !store.isRunning) return Promise.resolve(false);
199
440
  if (request.runId !== void 0 && store.currentRunId !== request.runId) return Promise.resolve(false);
200
441
  if (store.stopRequested) return Promise.resolve(false);
201
442
  store.stopRequested = true;
202
443
  store.isRunning = false;
444
+ const finalizeControl = store.activeFinalize;
445
+ if (finalizeControl) finalizeControl.stopRequested = true;
203
446
  const agent = store.agent;
204
447
  if (!agent) {
205
448
  store.stopRequested = false;
206
449
  store.isRunning = false;
450
+ if (finalizeControl) finalizeControl.stopRequested = false;
207
451
  return Promise.resolve(false);
208
452
  }
209
453
  try {
@@ -213,6 +457,7 @@ var InMemoryAgentRunner = class extends AgentRunner {
213
457
  console.error("Failed to abort agent run", error);
214
458
  store.stopRequested = false;
215
459
  store.isRunning = true;
460
+ if (finalizeControl) finalizeControl.stopRequested = false;
216
461
  return Promise.resolve(false);
217
462
  }
218
463
  }
@@ -224,23 +469,7 @@ var InMemoryAgentRunner = class extends AgentRunner {
224
469
  * `ThreadRecord` so the HTTP handler can use the same response envelope.
225
470
  */
226
471
  listThreads() {
227
- const threads = [];
228
- for (const [threadId, store] of GLOBAL_STORE) {
229
- if (store.historicRuns.length === 0) continue;
230
- const firstRun = store.historicRuns[0];
231
- const lastRun = store.historicRuns[store.historicRuns.length - 1];
232
- threads.push({
233
- id: threadId,
234
- name: null,
235
- agentId: lastRun.agentId,
236
- organizationId: "",
237
- createdById: "",
238
- archived: false,
239
- createdAt: new Date(firstRun.createdAt).toISOString(),
240
- updatedAt: new Date(lastRun.createdAt).toISOString()
241
- });
242
- }
243
- return threads.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
472
+ return sharedStore.listThreads();
244
473
  }
245
474
  /**
246
475
  * Returns all messages for a thread, using the snapshot captured at the end
@@ -252,9 +481,9 @@ var InMemoryAgentRunner = class extends AgentRunner {
252
481
  * with the Intelligence platform's `ThreadMessage` type.
253
482
  */
254
483
  getThreadMessages(threadId) {
255
- const store = GLOBAL_STORE.get(threadId);
256
- if (!store || store.historicRuns.length === 0) return [];
257
- return store.historicRuns[store.historicRuns.length - 1].messages;
484
+ const store = sharedStore.peek(threadId);
485
+ if (!store) return [];
486
+ return [...store.messagesSnapshot];
258
487
  }
259
488
  /**
260
489
  * Returns all AG-UI events for a thread, compacted across historic runs.
@@ -265,7 +494,7 @@ var InMemoryAgentRunner = class extends AgentRunner {
265
494
  * late-joining inspector sees matches what this method returns.
266
495
  */
267
496
  getThreadEvents(threadId) {
268
- const store = GLOBAL_STORE.get(threadId);
497
+ const store = sharedStore.peek(threadId);
269
498
  if (!store || store.historicRuns.length === 0) return [];
270
499
  const all = [];
271
500
  for (const run of store.historicRuns) all.push(...run.events);
@@ -287,7 +516,7 @@ var InMemoryAgentRunner = class extends AgentRunner {
287
516
  const event = events[i];
288
517
  if (event.type === EventType.STATE_SNAPSHOT) {
289
518
  const snapshot = event.snapshot;
290
- if (snapshot && typeof snapshot === "object") return snapshot;
519
+ if (snapshot && typeof snapshot === "object" && !Array.isArray(snapshot)) return { ...snapshot };
291
520
  return null;
292
521
  }
293
522
  }
@@ -303,10 +532,10 @@ var InMemoryAgentRunner = class extends AgentRunner {
303
532
  * not be wiped this way.
304
533
  */
305
534
  clearThreads() {
306
- GLOBAL_STORE.clear();
535
+ sharedStore.clear();
307
536
  }
308
537
  };
309
538
 
310
539
  //#endregion
311
- export { InMemoryAgentRunner };
540
+ export { InMemoryAgentRunner, ɵBoundedThreadStore, ɵGLOBAL_STORE, ɵINMEMORY_DEFAULTS, ɵestimateBytes, ɵnormalizeLimits };
312
541
  //# sourceMappingURL=in-memory.mjs.map