@copilotkitnext/runtime 0.0.21 → 0.0.22-alpha.0

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.
@@ -2,250 +2,353 @@
2
2
  var AgentRunner = class {
3
3
  };
4
4
 
5
- // src/runner/in-memory.ts
5
+ // src/runner/base.ts
6
6
  import { ReplaySubject } from "rxjs";
7
- import {
8
- EventType,
9
- compactEvents
10
- } from "@ag-ui/client";
7
+ import { EventType as EventType2, compactEvents } from "@ag-ui/client";
11
8
  import { finalizeRunEvents } from "@copilotkitnext/shared";
12
- var InMemoryEventStore = class {
13
- constructor(threadId) {
14
- this.threadId = threadId;
15
- }
16
- /** The subject that current consumers subscribe to. */
17
- subject = null;
18
- /** True while a run is actively producing events. */
19
- isRunning = false;
20
- /** Current run ID */
21
- currentRunId = null;
22
- /** Historic completed runs */
23
- historicRuns = [];
24
- /** Currently running agent instance (if any). */
25
- agent = null;
26
- /** Subject returned from run() while the run is active. */
27
- runSubject = null;
28
- /** True once stop() has been requested but the run has not yet finalized. */
29
- stopRequested = false;
30
- /** Reference to the events emitted in the current run. */
31
- currentEvents = null;
32
- };
33
- var GLOBAL_STORE = /* @__PURE__ */ new Map();
34
- var InMemoryAgentRunner = class extends AgentRunner {
35
- run(request) {
36
- let existingStore = GLOBAL_STORE.get(request.threadId);
37
- if (!existingStore) {
38
- existingStore = new InMemoryEventStore(request.threadId);
39
- GLOBAL_STORE.set(request.threadId, existingStore);
40
- }
41
- const store = existingStore;
42
- if (store.isRunning) {
43
- throw new Error("Thread already running");
44
- }
45
- store.isRunning = true;
46
- store.currentRunId = request.input.runId;
47
- store.agent = request.agent;
48
- store.stopRequested = false;
49
- const seenMessageIds = /* @__PURE__ */ new Set();
50
- const currentRunEvents = [];
51
- store.currentEvents = currentRunEvents;
52
- const historicMessageIds = /* @__PURE__ */ new Set();
53
- for (const run of store.historicRuns) {
54
- for (const event of run.events) {
55
- if ("messageId" in event && typeof event.messageId === "string") {
56
- historicMessageIds.add(event.messageId);
9
+
10
+ // src/runner/utils.ts
11
+ import { EventType } from "@ag-ui/client";
12
+ function buildHistoricMessageIdIndex(runs) {
13
+ const ids = /* @__PURE__ */ new Set();
14
+ for (const run of runs) {
15
+ for (const event of run.events) {
16
+ const msgId = event.messageId;
17
+ if (typeof msgId === "string") ids.add(msgId);
18
+ if (event.type === EventType.RUN_STARTED) {
19
+ const e = event;
20
+ for (const m of e.input?.messages ?? []) {
21
+ if (m?.id) ids.add(m.id);
57
22
  }
58
- if (event.type === EventType.RUN_STARTED) {
59
- const runStarted = event;
60
- const messages = runStarted.input?.messages ?? [];
61
- for (const message of messages) {
62
- historicMessageIds.add(message.id);
23
+ }
24
+ }
25
+ }
26
+ return ids;
27
+ }
28
+ function sanitizeRunStarted(event, input, historicIds) {
29
+ if (event.input) return event;
30
+ const sanitizedMessages = input.messages ? input.messages.filter((m) => !historicIds.has(m.id)) : void 0;
31
+ const updatedInput = {
32
+ ...input,
33
+ ...sanitizedMessages !== void 0 ? { messages: sanitizedMessages } : {}
34
+ };
35
+ return { ...event, input: updatedInput };
36
+ }
37
+ function deriveThreadMetadata(params) {
38
+ const { threadId, runs, isRunning, resourceId, properties } = params;
39
+ const createdAt = runs.length > 0 ? Math.min(...runs.map((r) => r.createdAt)) : Date.now();
40
+ const lastActivityAt = runs.length > 0 ? Math.max(...runs.map((r) => r.createdAt)) : createdAt;
41
+ let messageCount = 0;
42
+ let firstMessage;
43
+ for (const run of runs) {
44
+ for (const event of run.events) {
45
+ if (event.role && typeof event.content === "string") {
46
+ messageCount += 1;
47
+ if (!firstMessage) firstMessage = event.content;
48
+ }
49
+ if (event.type === EventType.RUN_STARTED) {
50
+ const e = event;
51
+ for (const m of e.input?.messages ?? []) {
52
+ messageCount += 1;
53
+ if (!firstMessage && typeof m.content === "string") {
54
+ firstMessage = m.content;
63
55
  }
64
56
  }
65
57
  }
66
58
  }
67
- const nextSubject = new ReplaySubject(Infinity);
68
- const prevSubject = store.subject;
69
- store.subject = nextSubject;
59
+ }
60
+ return {
61
+ threadId,
62
+ createdAt,
63
+ lastActivityAt,
64
+ isRunning,
65
+ messageCount,
66
+ firstMessage,
67
+ resourceId,
68
+ properties
69
+ };
70
+ }
71
+ function matchesScope(thread, scope) {
72
+ if (!scope) return true;
73
+ const { resourceId, properties } = scope;
74
+ if (resourceId !== void 0) {
75
+ if (Array.isArray(resourceId)) {
76
+ if (!resourceId.includes(thread.resourceId ?? "")) return false;
77
+ } else {
78
+ if ((thread.resourceId ?? "") !== resourceId) return false;
79
+ }
80
+ }
81
+ if (properties) {
82
+ const t = thread.properties ?? {};
83
+ for (const [k, v] of Object.entries(properties)) {
84
+ if (t[k] !== v) return false;
85
+ }
86
+ }
87
+ return true;
88
+ }
89
+
90
+ // src/runner/base.ts
91
+ var AgentRunnerBase = class extends AgentRunner {
92
+ active = /* @__PURE__ */ new Map();
93
+ threadScope = /* @__PURE__ */ new Map();
94
+ constructor() {
95
+ super();
96
+ }
97
+ run(request) {
98
+ const { threadId, input, agent, scope } = request;
99
+ if (scope !== void 0) {
100
+ if (!this.threadScope.has(threadId)) this.threadScope.set(threadId, scope ?? null);
101
+ }
102
+ if (this.active.has(threadId)) {
103
+ throw new Error("Thread already running");
104
+ }
70
105
  const runSubject = new ReplaySubject(Infinity);
71
- store.runSubject = runSubject;
72
- const runAgent = async () => {
73
- const lastRun = store.historicRuns[store.historicRuns.length - 1];
74
- const parentRunId = lastRun?.runId ?? null;
106
+ void (async () => {
107
+ this.active.set(threadId, { agent, stopRequested: false });
108
+ const acquired = await this.acquireRun(threadId, input.runId);
109
+ if (!acquired) {
110
+ this.active.delete(threadId);
111
+ runSubject.error(new Error("Thread already running"));
112
+ return;
113
+ }
114
+ const currentRunEvents = [];
115
+ const priorRuns = await this.listRuns(threadId);
116
+ const historicIds = buildHistoricMessageIdIndex(priorRuns);
117
+ const onEvent = (event) => {
118
+ let processed = event;
119
+ if (event.type === EventType2.RUN_STARTED) {
120
+ processed = sanitizeRunStarted(event, input, historicIds);
121
+ }
122
+ runSubject.next(processed);
123
+ currentRunEvents.push(processed);
124
+ this.publishLive(threadId, processed);
125
+ };
75
126
  try {
76
- await request.agent.runAgent(request.input, {
77
- onEvent: ({ event }) => {
78
- let processedEvent = event;
79
- if (event.type === EventType.RUN_STARTED) {
80
- const runStartedEvent = event;
81
- if (!runStartedEvent.input) {
82
- const sanitizedMessages = request.input.messages ? request.input.messages.filter(
83
- (message) => !historicMessageIds.has(message.id)
84
- ) : void 0;
85
- const updatedInput = {
86
- ...request.input,
87
- ...sanitizedMessages !== void 0 ? { messages: sanitizedMessages } : {}
88
- };
89
- processedEvent = {
90
- ...runStartedEvent,
91
- input: updatedInput
92
- };
93
- }
94
- }
95
- runSubject.next(processedEvent);
96
- nextSubject.next(processedEvent);
97
- currentRunEvents.push(processedEvent);
98
- },
127
+ await agent.runAgent(input, {
128
+ onEvent: ({ event }) => onEvent(event),
99
129
  onNewMessage: ({ message }) => {
100
- if (!seenMessageIds.has(message.id)) {
101
- seenMessageIds.add(message.id);
102
- }
103
130
  },
104
131
  onRunStartedEvent: () => {
105
- if (request.input.messages) {
106
- for (const message of request.input.messages) {
107
- if (!seenMessageIds.has(message.id)) {
108
- seenMessageIds.add(message.id);
109
- }
110
- }
111
- }
112
132
  }
113
133
  });
114
- const appendedEvents = finalizeRunEvents(currentRunEvents, {
115
- stopRequested: store.stopRequested
116
- });
117
- for (const event of appendedEvents) {
118
- runSubject.next(event);
119
- nextSubject.next(event);
134
+ const ctx = this.active.get(threadId);
135
+ const appended = finalizeRunEvents(currentRunEvents, { stopRequested: ctx?.stopRequested ?? false });
136
+ for (const e of appended) {
137
+ runSubject.next(e);
138
+ this.publishLive(threadId, e);
120
139
  }
121
- if (store.currentRunId) {
122
- const compactedEvents = compactEvents(currentRunEvents);
123
- store.historicRuns.push({
124
- threadId: request.threadId,
125
- runId: store.currentRunId,
126
- parentRunId,
127
- events: compactedEvents,
128
- createdAt: Date.now()
129
- });
130
- }
131
- store.currentEvents = null;
132
- store.currentRunId = null;
133
- store.agent = null;
134
- store.runSubject = null;
135
- store.stopRequested = false;
136
- store.isRunning = false;
137
- runSubject.complete();
138
- nextSubject.complete();
139
- } catch {
140
- const appendedEvents = finalizeRunEvents(currentRunEvents, {
141
- stopRequested: store.stopRequested
140
+ const parentRunId = priorRuns.at(-1)?.runId ?? null;
141
+ const compacted = compactEvents(currentRunEvents);
142
+ await this.saveRun(threadId, input.runId, compacted, input, parentRunId);
143
+ } catch (error) {
144
+ const ctx = this.active.get(threadId);
145
+ const appended = finalizeRunEvents(currentRunEvents, {
146
+ stopRequested: ctx?.stopRequested ?? false,
147
+ interruptionMessage: error instanceof Error ? error.message : void 0
142
148
  });
143
- for (const event of appendedEvents) {
144
- runSubject.next(event);
145
- nextSubject.next(event);
149
+ for (const e of appended) {
150
+ runSubject.next(e);
151
+ this.publishLive(threadId, e);
146
152
  }
147
- if (store.currentRunId && currentRunEvents.length > 0) {
148
- const compactedEvents = compactEvents(currentRunEvents);
149
- store.historicRuns.push({
150
- threadId: request.threadId,
151
- runId: store.currentRunId,
152
- parentRunId,
153
- events: compactedEvents,
154
- createdAt: Date.now()
155
- });
153
+ if (currentRunEvents.length > 0) {
154
+ const parentRunId = priorRuns.at(-1)?.runId ?? null;
155
+ const compacted = compactEvents(currentRunEvents);
156
+ await this.saveRun(threadId, input.runId, compacted, input, parentRunId);
156
157
  }
157
- store.currentEvents = null;
158
- store.currentRunId = null;
159
- store.agent = null;
160
- store.runSubject = null;
161
- store.stopRequested = false;
162
- store.isRunning = false;
158
+ } finally {
159
+ await this.releaseRun(threadId);
160
+ this.active.delete(threadId);
161
+ this.completeLive(threadId);
163
162
  runSubject.complete();
164
- nextSubject.complete();
165
163
  }
166
- };
167
- if (prevSubject) {
168
- prevSubject.subscribe({
169
- next: (e) => nextSubject.next(e),
170
- error: (err) => nextSubject.error(err),
171
- complete: () => {
172
- }
173
- });
174
- }
175
- runAgent();
164
+ })();
176
165
  return runSubject.asObservable();
177
166
  }
178
167
  connect(request) {
179
- const store = GLOBAL_STORE.get(request.threadId);
180
- const connectionSubject = new ReplaySubject(Infinity);
181
- if (!store) {
182
- connectionSubject.complete();
183
- return connectionSubject.asObservable();
184
- }
185
- const allHistoricEvents = [];
186
- for (const run of store.historicRuns) {
187
- allHistoricEvents.push(...run.events);
188
- }
189
- const compactedEvents = compactEvents(allHistoricEvents);
190
- const emittedMessageIds = /* @__PURE__ */ new Set();
191
- for (const event of compactedEvents) {
192
- connectionSubject.next(event);
193
- if ("messageId" in event && typeof event.messageId === "string") {
194
- emittedMessageIds.add(event.messageId);
168
+ const { threadId } = request;
169
+ const subject = new ReplaySubject(Infinity);
170
+ void (async () => {
171
+ const priorRuns = await this.listRuns(threadId);
172
+ const allHistoric = [];
173
+ for (const r of priorRuns) allHistoric.push(...r.events);
174
+ const compacted = compactEvents(allHistoric);
175
+ const emittedIds = /* @__PURE__ */ new Set();
176
+ for (const e of compacted) {
177
+ subject.next(e);
178
+ const id = e.messageId;
179
+ if (typeof id === "string") emittedIds.add(id);
195
180
  }
196
- }
197
- if (store.subject && (store.isRunning || store.stopRequested)) {
198
- store.subject.subscribe({
199
- next: (event) => {
200
- if ("messageId" in event && typeof event.messageId === "string" && emittedMessageIds.has(event.messageId)) {
201
- return;
181
+ const running = await this.isRunningState(threadId);
182
+ if (!running) {
183
+ subject.complete();
184
+ return;
185
+ }
186
+ const live = this.subscribeLive(threadId);
187
+ let completed = false;
188
+ live.subscribe({
189
+ next: (e) => {
190
+ const id = e.messageId;
191
+ if (typeof id === "string" && emittedIds.has(id)) return;
192
+ subject.next(e);
193
+ if (e.type === EventType2.RUN_FINISHED || e.type === EventType2.RUN_ERROR) {
194
+ if (!completed) {
195
+ completed = true;
196
+ subject.complete();
197
+ }
202
198
  }
203
- connectionSubject.next(event);
204
199
  },
205
- complete: () => connectionSubject.complete(),
206
- error: (err) => connectionSubject.error(err)
200
+ error: (err) => subject.error(err),
201
+ complete: () => {
202
+ if (!completed) {
203
+ completed = true;
204
+ subject.complete();
205
+ }
206
+ }
207
207
  });
208
- } else {
209
- connectionSubject.complete();
208
+ })();
209
+ return subject.asObservable();
210
+ }
211
+ async isRunning(request) {
212
+ return this.isRunningState(request.threadId);
213
+ }
214
+ async stop(request) {
215
+ const ctx = this.active.get(request.threadId);
216
+ if (!ctx?.agent) return false;
217
+ if (ctx.stopRequested) return false;
218
+ ctx.stopRequested = true;
219
+ await this.releaseRun(request.threadId);
220
+ try {
221
+ ctx.agent.abortRun();
222
+ return true;
223
+ } catch {
224
+ ctx.stopRequested = false;
225
+ return false;
210
226
  }
211
- return connectionSubject.asObservable();
212
227
  }
213
- isRunning(request) {
214
- const store = GLOBAL_STORE.get(request.threadId);
215
- return Promise.resolve(store?.isRunning ?? false);
228
+ async listThreads(request) {
229
+ const limit = request.limit ?? 20;
230
+ const offset = request.offset ?? 0;
231
+ const page = await this.pageThreads({ scope: request.scope, limit: limit + offset, offset: 0 });
232
+ const resultThreads = [];
233
+ for (const id of page.threadIds) {
234
+ const md = await this.getThreadMetadata(id, request.scope);
235
+ if (!md) continue;
236
+ if (request.scope ? matchesScope(md, request.scope) : true) {
237
+ resultThreads.push(md);
238
+ }
239
+ }
240
+ resultThreads.sort((a, b) => b.lastActivityAt - a.lastActivityAt);
241
+ const sliced = resultThreads.slice(offset, offset + limit);
242
+ return { threads: sliced, total: page.total };
216
243
  }
217
- stop(request) {
218
- const store = GLOBAL_STORE.get(request.threadId);
219
- if (!store || !store.isRunning) {
220
- return Promise.resolve(false);
244
+ async getThreadMetadata(threadId, scope) {
245
+ const runs = await this.listRuns(threadId);
246
+ if (runs.length === 0) return null;
247
+ const isRunning = await this.isRunningState(threadId);
248
+ const s = this.threadScope.get(threadId) ?? null;
249
+ return deriveThreadMetadata({
250
+ threadId,
251
+ runs,
252
+ isRunning,
253
+ resourceId: s?.resourceId ?? void 0,
254
+ properties: s?.properties
255
+ });
256
+ }
257
+ async deleteThread(threadId, scope) {
258
+ const running = await this.isRunningState(threadId);
259
+ if (running) {
260
+ throw new Error("Cannot delete a running thread");
221
261
  }
222
- if (store.stopRequested) {
223
- return Promise.resolve(false);
262
+ await this.deleteThreadStorage(threadId, scope);
263
+ if (this.closeLive) {
264
+ await this.closeLive(threadId);
224
265
  }
225
- store.stopRequested = true;
226
- store.isRunning = false;
227
- const agent = store.agent;
228
- if (!agent) {
229
- store.stopRequested = false;
230
- store.isRunning = false;
231
- return Promise.resolve(false);
266
+ this.threadScope.delete(threadId);
267
+ }
268
+ };
269
+
270
+ // src/runner/in-memory-runner.ts
271
+ import { ReplaySubject as ReplaySubject2 } from "rxjs";
272
+ var InMemoryAgentRunner = class extends AgentRunnerBase {
273
+ state = /* @__PURE__ */ new Map();
274
+ runs = /* @__PURE__ */ new Map();
275
+ channels = /* @__PURE__ */ new Map();
276
+ constructor() {
277
+ super();
278
+ }
279
+ // Live channel helpers
280
+ ensureChannel(threadId) {
281
+ let s = this.channels.get(threadId);
282
+ if (!s || s.closed) {
283
+ s = new ReplaySubject2(Infinity);
284
+ this.channels.set(threadId, s);
232
285
  }
233
- try {
234
- agent.abortRun();
235
- return Promise.resolve(true);
236
- } catch (error) {
237
- console.error("Failed to abort agent run", error);
238
- store.stopRequested = false;
239
- store.isRunning = true;
240
- return Promise.resolve(false);
286
+ return s;
287
+ }
288
+ // Hooks implementation
289
+ async acquireRun(threadId, runId) {
290
+ const entry = this.state.get(threadId) ?? { isRunning: false, runId: null };
291
+ if (entry.isRunning) return false;
292
+ entry.isRunning = true;
293
+ entry.runId = runId;
294
+ this.state.set(threadId, entry);
295
+ return true;
296
+ }
297
+ async releaseRun(threadId) {
298
+ const entry = this.state.get(threadId) ?? { isRunning: false, runId: null };
299
+ entry.isRunning = false;
300
+ this.state.set(threadId, entry);
301
+ }
302
+ async isRunningState(threadId) {
303
+ return this.state.get(threadId)?.isRunning ?? false;
304
+ }
305
+ async listRuns(threadId) {
306
+ return this.runs.get(threadId) ?? [];
307
+ }
308
+ async saveRun(threadId, runId, events, input, parentRunId) {
309
+ const list = this.runs.get(threadId) ?? [];
310
+ list.push({ runId, events, createdAt: Date.now() });
311
+ this.runs.set(threadId, list);
312
+ }
313
+ async pageThreads(params) {
314
+ const all = [];
315
+ for (const [id, arr] of this.runs.entries()) {
316
+ const last = arr.length > 0 ? Math.max(...arr.map((r) => r.createdAt)) : 0;
317
+ all.push({ id, last });
241
318
  }
319
+ all.sort((a, b) => b.last - a.last);
320
+ const total = all.length;
321
+ const offset = params.offset ?? 0;
322
+ const limit = params.limit ?? 20;
323
+ const ids = all.slice(offset, offset + limit).map((x) => x.id);
324
+ return { threadIds: ids, total };
325
+ }
326
+ async deleteThreadStorage(threadId) {
327
+ this.runs.delete(threadId);
328
+ this.state.delete(threadId);
329
+ }
330
+ publishLive(threadId, event) {
331
+ const s = this.ensureChannel(threadId);
332
+ s.next(event);
333
+ }
334
+ completeLive(threadId) {
335
+ const s = this.ensureChannel(threadId);
336
+ if (!s.closed) s.complete();
337
+ }
338
+ subscribeLive(threadId) {
339
+ return this.ensureChannel(threadId).asObservable();
340
+ }
341
+ async closeLive(threadId) {
342
+ const s = this.channels.get(threadId);
343
+ if (s && !s.closed) s.complete();
344
+ this.channels.delete(threadId);
242
345
  }
243
346
  };
244
347
 
245
348
  // package.json
246
349
  var package_default = {
247
350
  name: "@copilotkitnext/runtime",
248
- version: "0.0.21",
351
+ version: "0.0.22-alpha.0",
249
352
  description: "Server-side runtime package for CopilotKit2",
250
353
  main: "dist/index.js",
251
354
  types: "dist/index.d.ts",
@@ -604,7 +707,7 @@ async function handleConnectAgent({
604
707
  }
605
708
 
606
709
  // src/handlers/handle-stop.ts
607
- import { EventType as EventType2 } from "@ag-ui/client";
710
+ import { EventType as EventType3 } from "@ag-ui/client";
608
711
  async function handleStopAgent({
609
712
  runtime,
610
713
  request,
@@ -642,7 +745,7 @@ async function handleStopAgent({
642
745
  JSON.stringify({
643
746
  stopped: true,
644
747
  interrupt: {
645
- type: EventType2.RUN_ERROR,
748
+ type: EventType3.RUN_ERROR,
646
749
  message: "Run stopped by user",
647
750
  code: "STOPPED"
648
751
  }
@@ -922,6 +1025,7 @@ export {
922
1025
  handleConnectAgent,
923
1026
  handleStopAgent,
924
1027
  AgentRunner,
1028
+ AgentRunnerBase,
925
1029
  InMemoryAgentRunner,
926
1030
  VERSION,
927
1031
  CopilotRuntime,
@@ -933,4 +1037,4 @@ export {
933
1037
  expectString,
934
1038
  createJsonRequest
935
1039
  };
936
- //# sourceMappingURL=chunk-SZFA2SCT.mjs.map
1040
+ //# sourceMappingURL=chunk-LDTC5BLU.mjs.map