@copilotkitnext/core 1.54.0 → 1.54.1-next.1

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/index.cjs CHANGED
@@ -3,32 +3,510 @@ let _ag_ui_client = require("@ag-ui/client");
3
3
  let _copilotkitnext_shared = require("@copilotkitnext/shared");
4
4
  let rxjs = require("rxjs");
5
5
  let rxjs_operators = require("rxjs/operators");
6
- let zod_to_json_schema = require("zod-to-json-schema");
7
6
  let phoenix = require("phoenix");
7
+ let zod_to_json_schema = require("zod-to-json-schema");
8
+ let rxjs_fetch = require("rxjs/fetch");
8
9
 
9
- //#region src/agent.ts
10
+ //#region src/utils/phoenix-observable.ts
10
11
  /**
11
- * Check if an error is a ZodError (validation error).
12
- * These can occur when the SSE stream is aborted/truncated mid-event.
12
+ * Adapt Phoenix socket open/error callbacks into an observable signal stream.
13
+ *
14
+ * The returned observable is shared and replayable by the caller when needed,
15
+ * but this helper itself does not own socket connection teardown.
13
16
  */
14
- function isZodError(error) {
15
- return error !== null && typeof error === "object" && "name" in error && error.name === "ZodError";
17
+ function ɵcreatePhoenixSocketSignals$(socket) {
18
+ return new rxjs.Observable((observer) => {
19
+ socket.onOpen(() => observer.next({ type: "open" }));
20
+ socket.onError((error) => observer.next({
21
+ type: "error",
22
+ error
23
+ }));
24
+ });
25
+ }
26
+ /**
27
+ * Adapt a Phoenix channel join attempt into a single-outcome observable.
28
+ */
29
+ function ɵcreatePhoenixJoinOutcome$(channel) {
30
+ return new rxjs.Observable((observer) => {
31
+ channel.join().receive("ok", () => {
32
+ observer.next({ type: "joined" });
33
+ observer.complete();
34
+ }).receive("error", (response) => {
35
+ observer.next({
36
+ type: "error",
37
+ response
38
+ });
39
+ observer.complete();
40
+ }).receive("timeout", () => {
41
+ observer.next({ type: "timeout" });
42
+ observer.complete();
43
+ });
44
+ });
45
+ }
46
+ /**
47
+ * Create a cold Phoenix socket session.
48
+ *
49
+ * The socket is constructed and connected on subscription, and disconnected on
50
+ * teardown. Each subscription creates an isolated socket instance.
51
+ */
52
+ function ɵphoenixSocket$(options) {
53
+ return (0, rxjs.defer)(() => {
54
+ const socket = new phoenix.Socket(options.url, options.options);
55
+ const signals$ = ɵcreatePhoenixSocketSignals$(socket).pipe((0, rxjs_operators.shareReplay)({
56
+ bufferSize: 1,
57
+ refCount: true
58
+ }));
59
+ socket.connect();
60
+ return (0, rxjs.concat)((0, rxjs.of)({
61
+ socket,
62
+ signals$
63
+ }), rxjs.NEVER).pipe((0, rxjs_operators.finalize)(() => socket.disconnect()));
64
+ });
65
+ }
66
+ /**
67
+ * Create a cold Phoenix channel session from a socket session stream.
68
+ *
69
+ * A channel is created and joined for each active socket session. If the
70
+ * upstream socket session changes, the previous channel is left before the
71
+ * next one becomes active.
72
+ */
73
+ function ɵphoenixChannel$(options) {
74
+ return options.socket$.pipe((0, rxjs_operators.switchMap)(({ socket }) => (0, rxjs.defer)(() => {
75
+ const channel = socket.channel(options.topic, options.params);
76
+ return (0, rxjs.concat)((0, rxjs.of)({
77
+ channel,
78
+ joinOutcome$: ɵcreatePhoenixJoinOutcome$(channel).pipe((0, rxjs_operators.shareReplay)({
79
+ bufferSize: 1,
80
+ refCount: true
81
+ }))
82
+ }), rxjs.NEVER).pipe((0, rxjs_operators.finalize)(() => {
83
+ if (options.leaveOnUnsubscribe !== false) channel.leave();
84
+ }));
85
+ })));
16
86
  }
17
87
  /**
18
- * Wrap an Observable to catch and suppress ZodErrors that occur during stream abort.
19
- * These errors are expected when the connection is cancelled mid-stream.
88
+ * Observe a named Phoenix channel event as an observable payload stream.
20
89
  */
90
+ function ɵobservePhoenixEvent$(channel, eventName) {
91
+ return new rxjs.Observable((observer) => {
92
+ const ref = channel.on(eventName, (payload) => observer.next(payload));
93
+ return () => {
94
+ channel.off(eventName, ref);
95
+ };
96
+ });
97
+ }
98
+ /**
99
+ * Flatten channel sessions into their join-outcome stream.
100
+ */
101
+ function ɵobservePhoenixJoinOutcome$(channel$) {
102
+ return channel$.pipe((0, rxjs_operators.switchMap)((session) => session.joinOutcome$));
103
+ }
104
+ /**
105
+ * Complete when a channel joins successfully, or error if the join fails.
106
+ */
107
+ function ɵjoinPhoenixChannel$(channel$) {
108
+ return ɵobservePhoenixJoinOutcome$(channel$).pipe((0, rxjs_operators.take)(1), (0, rxjs_operators.mergeMap)((outcome) => {
109
+ if (outcome.type === "joined") return rxjs.EMPTY;
110
+ throw outcome.type === "timeout" ? /* @__PURE__ */ new Error("Timed out joining channel") : /* @__PURE__ */ new Error(`Failed to join channel: ${JSON.stringify(outcome.response)}`);
111
+ }));
112
+ }
113
+ /**
114
+ * Flatten socket sessions into their lifecycle signal stream.
115
+ */
116
+ function ɵobservePhoenixSocketSignals$(socket$) {
117
+ return socket$.pipe((0, rxjs_operators.switchMap)((session) => session.signals$));
118
+ }
119
+ /**
120
+ * Error after a socket emits the configured number of consecutive error
121
+ * signals, resetting the counter after each successful open signal.
122
+ */
123
+ function ɵobservePhoenixSocketHealth$(socketSignals$, maxConsecutiveErrors) {
124
+ return socketSignals$.pipe((0, rxjs_operators.scan)((consecutiveErrors, signal) => signal.type === "open" ? 0 : consecutiveErrors + 1, 0), (0, rxjs_operators.filter)((consecutiveErrors) => consecutiveErrors >= maxConsecutiveErrors), (0, rxjs_operators.take)(1), (0, rxjs_operators.mergeMap)((consecutiveErrors) => (0, rxjs.throwError)(() => /* @__PURE__ */ new Error(`WebSocket connection failed after ${consecutiveErrors} consecutive errors`))));
125
+ }
126
+
127
+ //#endregion
128
+ //#region src/intelligence-agent.ts
129
+ const CLIENT_AG_UI_EVENT = "ag_ui_event";
130
+ const STOP_RUN_EVENT = "stop_run";
131
+ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAgent {
132
+ config;
133
+ socket = null;
134
+ activeChannel = null;
135
+ runId = null;
136
+ sharedState;
137
+ constructor(config, sharedState = { lastSeenEventIds: /* @__PURE__ */ new Map() }) {
138
+ super();
139
+ this.config = config;
140
+ this.sharedState = sharedState;
141
+ }
142
+ clone() {
143
+ return new IntelligenceAgent(this.config, this.sharedState);
144
+ }
145
+ /**
146
+ * Override of AbstractAgent.connectAgent that removes the `verifyEvents` step.
147
+ *
148
+ * Background: AbstractAgent's connectAgent pipeline runs events through
149
+ * `verifyEvents`, which validates that the stream follows the AG-UI protocol
150
+ * lifecycle — specifically, it expects a RUN_STARTED event before any content
151
+ * events and a RUN_FINISHED/RUN_ERROR event to complete the stream.
152
+ *
153
+ * IntelligenceAgent uses long-lived WebSocket connections rather than
154
+ * request-scoped SSE streams. When connecting to replay historical messages
155
+ * for an existing thread, the connection semantics don't map to a single
156
+ * agent run start/stop cycle. The replayed events may not include
157
+ * RUN_STARTED/RUN_FINISHED bookends (or may contain events from multiple
158
+ * past runs), which causes verifyEvents to either never complete or to
159
+ * error out.
160
+ *
161
+ * This override replicates the base connectAgent implementation exactly,
162
+ * substituting only `transformChunks` (which is still needed for message
163
+ * reassembly) and omitting `verifyEvents`.
164
+ *
165
+ * TODO: Remove this override once AG-UI's AbstractAgent supports opting out
166
+ * of verifyEvents for transports with different connection life-cycles.
167
+ */
168
+ async connectAgent(parameters, subscriber) {
169
+ const self = this;
170
+ try {
171
+ this.isRunning = true;
172
+ this.agentId = this.agentId ?? (0, _ag_ui_client.randomUUID)();
173
+ const input = this.prepareRunAgentInput(parameters);
174
+ let result;
175
+ const previousMessageIds = new Set(this.messages.map((m) => m.id));
176
+ const subscribers = [
177
+ { onRunFinishedEvent: (event) => {
178
+ result = event.result;
179
+ } },
180
+ ...this.subscribers,
181
+ subscriber ?? {}
182
+ ];
183
+ await this.onInitialize(input, subscribers);
184
+ self.activeRunDetach$ = new rxjs.Subject();
185
+ let resolveCompletion;
186
+ self.activeRunCompletionPromise = new Promise((resolve) => {
187
+ resolveCompletion = resolve;
188
+ });
189
+ const source$ = (0, rxjs.defer)(() => this.connect(input)).pipe((0, _ag_ui_client.transformChunks)(this.debug), (0, rxjs_operators.takeUntil)(self.activeRunDetach$));
190
+ const applied$ = this.apply(input, source$, subscribers);
191
+ await (0, rxjs.lastValueFrom)(this.processApplyEvents(input, applied$, subscribers).pipe((0, rxjs_operators.catchError)((error) => {
192
+ this.isRunning = false;
193
+ return this.onError(input, error, subscribers);
194
+ }), (0, rxjs_operators.finalize)(() => {
195
+ this.isRunning = false;
196
+ this.onFinalize(input, subscribers);
197
+ resolveCompletion?.();
198
+ resolveCompletion = void 0;
199
+ self.activeRunCompletionPromise = void 0;
200
+ self.activeRunDetach$ = void 0;
201
+ })), { defaultValue: void 0 });
202
+ const newMessages = (0, _ag_ui_client.structuredClone_)(this.messages).filter((m) => !previousMessageIds.has(m.id));
203
+ return {
204
+ result,
205
+ newMessages
206
+ };
207
+ } finally {
208
+ this.isRunning = false;
209
+ }
210
+ }
211
+ abortRun() {
212
+ if (this.activeChannel && this.runId) {
213
+ const fallback = setTimeout(() => clear(), 5e3);
214
+ const clear = () => {
215
+ clearTimeout(fallback);
216
+ this.detachActiveRun();
217
+ this.cleanup();
218
+ };
219
+ this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.runId }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
220
+ } else {
221
+ this.detachActiveRun();
222
+ this.cleanup();
223
+ }
224
+ }
225
+ /**
226
+ * Trigger the run via REST, then join the realtime thread channel and relay
227
+ * server-pushed AG-UI events to the Observable subscriber.
228
+ */
229
+ run(input) {
230
+ this.threadId = input.threadId;
231
+ this.runId = input.runId;
232
+ return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => this.observeThread$(input, credentials, {
233
+ completeOnRunError: false,
234
+ streamMode: "run"
235
+ })));
236
+ }
237
+ /**
238
+ * Reconnect to an existing thread by fetching websocket credentials and
239
+ * joining the realtime thread channel.
240
+ */
241
+ connect(input) {
242
+ this.threadId = input.threadId;
243
+ this.runId = input.runId;
244
+ return (0, rxjs.defer)(() => this.requestConnectPlan$(input)).pipe((0, rxjs.switchMap)((plan) => {
245
+ if (plan === null) return rxjs.EMPTY;
246
+ if (plan.mode === "bootstrap") {
247
+ this.setLastSeenEventId(input.threadId, plan.latestEventId);
248
+ for (const event of plan.events) this.updateRunIdFromEvent(event);
249
+ return (0, rxjs.from)(plan.events);
250
+ }
251
+ this.setLastSeenEventId(input.threadId, plan.joinFromEventId);
252
+ for (const event of plan.events) this.updateRunIdFromEvent(event);
253
+ return (0, rxjs.concat)((0, rxjs.from)(plan.events), this.observeThread$(input, { joinToken: plan.joinToken }, {
254
+ completeOnRunError: true,
255
+ streamMode: "connect",
256
+ replayCursor: plan.joinFromEventId
257
+ }));
258
+ }));
259
+ }
260
+ /**
261
+ * Tear down a specific channel + socket pair that belongs to one pipeline.
262
+ * Only nulls instance references when they still point to the owned resource,
263
+ * so a concurrent pipeline's resources are never clobbered.
264
+ */
265
+ cleanupOwned(ownChannel, ownSocket) {
266
+ if (ownChannel) {
267
+ ownChannel.leave();
268
+ if (this.activeChannel === ownChannel) this.activeChannel = null;
269
+ }
270
+ if (ownSocket) {
271
+ ownSocket.disconnect();
272
+ if (this.socket === ownSocket) this.socket = null;
273
+ }
274
+ if (this.threadId) this.sharedState.lastSeenEventIds.delete(this.threadId);
275
+ this.runId = null;
276
+ }
277
+ cleanup() {
278
+ this.cleanupOwned(this.activeChannel, this.socket);
279
+ }
280
+ requestJoinCredentials$(mode, input) {
281
+ return (0, rxjs.defer)(async () => {
282
+ try {
283
+ const response = await fetch(this.buildRuntimeUrl(mode), {
284
+ method: "POST",
285
+ headers: {
286
+ "Content-Type": "application/json",
287
+ ...this.config.headers
288
+ },
289
+ body: JSON.stringify({
290
+ threadId: input.threadId,
291
+ runId: input.runId,
292
+ messages: input.messages,
293
+ tools: input.tools,
294
+ context: input.context,
295
+ state: input.state,
296
+ forwardedProps: input.forwardedProps
297
+ }),
298
+ ...this.config.credentials ? { credentials: this.config.credentials } : {}
299
+ });
300
+ if (!response.ok) {
301
+ const text = await response.text().catch(() => "");
302
+ throw new Error(text || response.statusText || String(response.status));
303
+ }
304
+ const payload = await response.json();
305
+ if (!payload.joinToken) throw new Error("missing joinToken");
306
+ return { joinToken: payload.joinToken };
307
+ } catch (error) {
308
+ throw new Error(`REST ${mode} request failed: ${error instanceof Error ? error.message : String(error)}`);
309
+ }
310
+ });
311
+ }
312
+ requestConnectPlan$(input) {
313
+ return (0, rxjs.defer)(async () => {
314
+ try {
315
+ const response = await fetch(this.buildRuntimeUrl("connect"), {
316
+ method: "POST",
317
+ headers: {
318
+ "Content-Type": "application/json",
319
+ ...this.config.headers
320
+ },
321
+ body: JSON.stringify({
322
+ threadId: input.threadId,
323
+ runId: input.runId,
324
+ messages: input.messages,
325
+ tools: input.tools,
326
+ context: input.context,
327
+ state: input.state,
328
+ forwardedProps: input.forwardedProps,
329
+ lastSeenEventId: this.getReconnectCursor(input)
330
+ }),
331
+ ...this.config.credentials ? { credentials: this.config.credentials } : {}
332
+ });
333
+ if (response.status === 204) return null;
334
+ if (!response.ok) {
335
+ const text = await response.text().catch(() => "");
336
+ throw new Error(text || response.statusText || String(response.status));
337
+ }
338
+ return this.normalizeConnectPlan(await response.json());
339
+ } catch (error) {
340
+ throw new Error(`REST connect request failed: ${error instanceof Error ? error.message : String(error)}`);
341
+ }
342
+ });
343
+ }
344
+ normalizeConnectPlan(payload) {
345
+ const envelope = payload && typeof payload === "object" ? payload : null;
346
+ if (envelope?.mode === "bootstrap") return {
347
+ mode: "bootstrap",
348
+ latestEventId: typeof envelope.latestEventId === "string" ? envelope.latestEventId : null,
349
+ events: Array.isArray(envelope.events) ? envelope.events : []
350
+ };
351
+ if (envelope?.mode === "live") {
352
+ if (typeof envelope.joinToken !== "string" || envelope.joinToken.length === 0) throw new Error("missing joinToken");
353
+ return {
354
+ mode: "live",
355
+ joinToken: envelope.joinToken,
356
+ joinFromEventId: typeof envelope.joinFromEventId === "string" ? envelope.joinFromEventId : null,
357
+ events: Array.isArray(envelope.events) ? envelope.events : []
358
+ };
359
+ }
360
+ throw new Error("invalid connect plan");
361
+ }
362
+ observeThread$(input, credentials, options) {
363
+ return (0, rxjs.defer)(() => {
364
+ let ownSocket = null;
365
+ let ownChannel = null;
366
+ const socket$ = ɵphoenixSocket$({
367
+ url: this.config.url,
368
+ options: {
369
+ params: {
370
+ ...this.config.socketParams ?? {},
371
+ join_token: credentials.joinToken
372
+ },
373
+ reconnectAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(100, 1e4),
374
+ rejoinAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(1e3, 3e4)
375
+ }
376
+ }).pipe((0, rxjs_operators.tap)(({ socket }) => {
377
+ ownSocket = socket;
378
+ this.socket = ownSocket;
379
+ }), (0, rxjs_operators.shareReplay)({
380
+ bufferSize: 1,
381
+ refCount: true
382
+ }));
383
+ const { topic, params } = this.createThreadChannelDescriptor(input, options.streamMode, options.replayCursor);
384
+ const channel$ = ɵphoenixChannel$({
385
+ socket$,
386
+ topic,
387
+ params
388
+ }).pipe((0, rxjs_operators.tap)(({ channel }) => {
389
+ ownChannel = channel;
390
+ this.activeChannel = ownChannel;
391
+ }), (0, rxjs_operators.shareReplay)({
392
+ bufferSize: 1,
393
+ refCount: true
394
+ }));
395
+ const threadEvents$ = this.observeThreadEvents$(input.threadId, channel$, options).pipe((0, rxjs_operators.share)());
396
+ const threadCompleted$ = threadEvents$.pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.endWith)(null), (0, rxjs_operators.take)(1));
397
+ return (0, rxjs.merge)(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$).pipe((0, rxjs_operators.takeUntil)(threadCompleted$)), threadEvents$).pipe((0, rxjs_operators.finalize)(() => this.cleanupOwned(ownChannel, ownSocket)));
398
+ });
399
+ }
400
+ joinThreadChannel$(channel$) {
401
+ return ɵjoinPhoenixChannel$(channel$);
402
+ }
403
+ observeSocketHealth$(socket$) {
404
+ return ɵobservePhoenixSocketHealth$(ɵobservePhoenixSocketSignals$(socket$), 5);
405
+ }
406
+ observeThreadEvents$(threadId, channel$, options) {
407
+ return channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => this.observeChannelEvent$(channel, CLIENT_AG_UI_EVENT)), (0, rxjs_operators.tap)((payload) => {
408
+ this.updateLastSeenEventId(threadId, payload);
409
+ this.updateRunIdFromEvent(payload);
410
+ }), (0, rxjs_operators.mergeMap)((payload) => (0, rxjs.from)(this.createThreadNotifications(payload, options.completeOnRunError))), (0, rxjs.dematerialize)());
411
+ }
412
+ observeChannelEvent$(channel, eventName) {
413
+ return ɵobservePhoenixEvent$(channel, eventName);
414
+ }
415
+ createThreadNotifications(payload, completeOnRunError) {
416
+ if (payload.type === _ag_ui_client.EventType.RUN_FINISHED) return [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()];
417
+ if (payload.type === _ag_ui_client.EventType.RUN_ERROR) {
418
+ const errorMessage = payload.message ?? "Run error";
419
+ return completeOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : [rxjs.Notification.createNext(payload), rxjs.Notification.createError(new Error(errorMessage))];
420
+ }
421
+ return [rxjs.Notification.createNext(payload)];
422
+ }
423
+ buildRuntimeUrl(mode) {
424
+ const path = `${this.config.runtimeUrl}/agent/${encodeURIComponent(this.config.agentId)}/${mode}`;
425
+ const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
426
+ return new URL(path, new URL(this.config.runtimeUrl, origin)).toString();
427
+ }
428
+ createThreadChannelDescriptor(input, streamMode, replayCursor) {
429
+ const params = streamMode === "run" ? {
430
+ stream_mode: "run",
431
+ run_id: input.runId
432
+ } : {
433
+ stream_mode: "connect",
434
+ last_seen_event_id: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor
435
+ };
436
+ return {
437
+ topic: `thread:${input.threadId}`,
438
+ params
439
+ };
440
+ }
441
+ getLastSeenEventId(threadId) {
442
+ return this.sharedState.lastSeenEventIds.get(threadId) ?? null;
443
+ }
444
+ getReconnectCursor(input) {
445
+ return this.hasLocalThreadMessages(input) ? this.getLastSeenEventId(input.threadId) : null;
446
+ }
447
+ hasLocalThreadMessages(input) {
448
+ return Array.isArray(input.messages) && input.messages.length > 0;
449
+ }
450
+ updateLastSeenEventId(threadId, payload) {
451
+ const eventId = this.readEventId(payload);
452
+ if (!eventId) return;
453
+ this.sharedState.lastSeenEventIds.set(threadId, eventId);
454
+ }
455
+ setLastSeenEventId(threadId, eventId) {
456
+ if (!eventId) return;
457
+ this.sharedState.lastSeenEventIds.set(threadId, eventId);
458
+ }
459
+ /**
460
+ * Keep `this.runId` in sync with the backend's actual run ID.
461
+ *
462
+ * During a `connect` (resume) flow the client generates a fresh `runId`
463
+ * via `prepareRunAgentInput`, but the backend is running under its own
464
+ * run ID. If the client later sends `STOP_RUN_EVENT` with the wrong
465
+ * `runId`, the gateway's runner channel will not match it and the agent
466
+ * keeps running. Extracting the run ID from live events fixes this.
467
+ *
468
+ * The runner normalises events to `run_id` (snake_case) before pushing
469
+ * to the gateway, so we check both `runId` and `run_id`.
470
+ */
471
+ updateRunIdFromEvent(payload) {
472
+ const record = payload;
473
+ const eventRunId = record.runId ?? record.run_id;
474
+ if (typeof eventRunId === "string" && eventRunId.length > 0) this.runId = eventRunId;
475
+ }
476
+ readEventId(payload) {
477
+ const metadata = payload.metadata;
478
+ if (!metadata || typeof metadata !== "object") return null;
479
+ const runnerEventId = metadata.cpki_event_id;
480
+ return typeof runnerEventId === "string" ? runnerEventId : null;
481
+ }
482
+ };
483
+
484
+ //#endregion
485
+ //#region src/agent.ts
486
+ function hasHeaders(agent) {
487
+ return "headers" in agent;
488
+ }
489
+ function hasCredentials(agent) {
490
+ return "credentials" in agent;
491
+ }
492
+ function isZodError(error) {
493
+ return error !== null && typeof error === "object" && "name" in error && error.name === "ZodError";
494
+ }
21
495
  function withAbortErrorHandling(observable) {
22
496
  return observable.pipe((0, rxjs_operators.catchError)((error) => {
23
497
  if (isZodError(error)) return rxjs.EMPTY;
24
498
  throw error;
25
499
  }));
26
500
  }
27
- var ProxiedCopilotRuntimeAgent = class extends _ag_ui_client.HttpAgent {
501
+ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui_client.HttpAgent {
28
502
  runtimeUrl;
29
503
  credentials;
30
504
  transport;
31
505
  singleEndpointUrl;
506
+ runtimeMode;
507
+ intelligence;
508
+ delegate;
509
+ runtimeInfoPromise;
32
510
  constructor(config) {
33
511
  const normalizedRuntimeUrl = config.runtimeUrl ? config.runtimeUrl.replace(/\/$/, "") : void 0;
34
512
  const transport = config.transport ?? "rest";
@@ -41,9 +519,21 @@ var ProxiedCopilotRuntimeAgent = class extends _ag_ui_client.HttpAgent {
41
519
  this.runtimeUrl = normalizedRuntimeUrl ?? config.runtimeUrl;
42
520
  this.credentials = config.credentials;
43
521
  this.transport = transport;
522
+ this.runtimeMode = config.runtimeMode ?? _copilotkitnext_shared.RUNTIME_MODE_SSE;
523
+ this.intelligence = config.intelligence;
44
524
  if (this.transport === "single") this.singleEndpointUrl = this.runtimeUrl;
45
525
  }
526
+ async detachActiveRun() {
527
+ if (this.delegate) await this.delegate.detachActiveRun();
528
+ await super.detachActiveRun();
529
+ }
46
530
  abortRun() {
531
+ if (this.delegate) {
532
+ this.syncDelegate(this.delegate);
533
+ this.delegate.abortRun();
534
+ this.detachActiveRun();
535
+ return;
536
+ }
47
537
  if (!this.agentId || !this.threadId) return;
48
538
  if (typeof fetch === "undefined") return;
49
539
  if (this.transport === "single") {
@@ -84,7 +574,52 @@ var ProxiedCopilotRuntimeAgent = class extends _ag_ui_client.HttpAgent {
84
574
  console.error("ProxiedCopilotRuntimeAgent: stop request failed", error);
85
575
  });
86
576
  }
577
+ async connectAgent(parameters, subscriber) {
578
+ if (this.runtimeMode !== _copilotkitnext_shared.RUNTIME_MODE_INTELLIGENCE) return super.connectAgent(parameters, subscriber);
579
+ if (this.delegate) await this.delegate.detachActiveRun();
580
+ await this.resolveDelegate();
581
+ const delegate = this.delegate;
582
+ const bridgeSub = delegate.subscribe({
583
+ onMessagesChanged: () => {
584
+ this.setMessages([...delegate.messages]);
585
+ },
586
+ onStateChanged: () => {
587
+ this.setState({ ...delegate.state });
588
+ },
589
+ onRunInitialized: () => {
590
+ this.isRunning = true;
591
+ },
592
+ onRunFinalized: () => {
593
+ this.isRunning = false;
594
+ },
595
+ onRunFailed: () => {
596
+ this.isRunning = false;
597
+ }
598
+ });
599
+ const forwardedSubs = this.subscribers.map((s) => delegate.subscribe(s));
600
+ try {
601
+ const result = await delegate.connectAgent(parameters, subscriber);
602
+ this.setMessages([...delegate.messages]);
603
+ this.setState({ ...delegate.state });
604
+ return result;
605
+ } finally {
606
+ this.isRunning = false;
607
+ bridgeSub.unsubscribe();
608
+ for (const sub of forwardedSubs) sub.unsubscribe();
609
+ }
610
+ }
87
611
  connect(input) {
612
+ if (this.runtimeMode === _copilotkitnext_shared.RUNTIME_MODE_INTELLIGENCE) return this.#connectViaDelegate(input);
613
+ return this.#connectViaHttp(input);
614
+ }
615
+ run(input) {
616
+ if (this.runtimeMode === _copilotkitnext_shared.RUNTIME_MODE_INTELLIGENCE) return this.#runViaDelegate(input);
617
+ return this.#runViaHttp(input);
618
+ }
619
+ #connectViaDelegate(input) {
620
+ return (0, rxjs.defer)(() => (0, rxjs.from)(this.resolveDelegate())).pipe((0, rxjs_operators.switchMap)((delegate) => withAbortErrorHandling(delegate.connect(input))));
621
+ }
622
+ #connectViaHttp(input) {
88
623
  if (this.transport === "single") {
89
624
  if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
90
625
  const requestInit = this.createSingleRouteRequestInit(input, "agent/connect", { agentId: this.agentId });
@@ -92,7 +627,10 @@ var ProxiedCopilotRuntimeAgent = class extends _ag_ui_client.HttpAgent {
92
627
  }
93
628
  return withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(`${this.runtimeUrl}/agent/${this.agentId}/connect`, this.requestInit(input))));
94
629
  }
95
- run(input) {
630
+ #runViaDelegate(input) {
631
+ return (0, rxjs.defer)(() => (0, rxjs.from)(this.resolveDelegate())).pipe((0, rxjs_operators.switchMap)((delegate) => withAbortErrorHandling(delegate.run(input))));
632
+ }
633
+ #runViaHttp(input) {
96
634
  if (this.transport === "single") {
97
635
  if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
98
636
  const requestInit = this.createSingleRouteRequestInit(input, "agent/run", { agentId: this.agentId });
@@ -101,13 +639,67 @@ var ProxiedCopilotRuntimeAgent = class extends _ag_ui_client.HttpAgent {
101
639
  return withAbortErrorHandling(super.run(input));
102
640
  }
103
641
  clone() {
104
- const cloned = super.clone();
105
- cloned.runtimeUrl = this.runtimeUrl;
106
- cloned.credentials = this.credentials;
107
- cloned.transport = this.transport;
108
- cloned.singleEndpointUrl = this.singleEndpointUrl;
642
+ const cloned = new ProxiedCopilotRuntimeAgent({
643
+ runtimeUrl: this.runtimeUrl,
644
+ agentId: this.agentId,
645
+ description: this.description,
646
+ headers: { ...this.headers },
647
+ credentials: this.credentials,
648
+ transport: this.transport,
649
+ runtimeMode: this.runtimeMode,
650
+ intelligence: this.intelligence
651
+ });
652
+ cloned.threadId = this.threadId;
653
+ cloned.setState(this.state);
654
+ cloned.setMessages(this.messages);
655
+ if (this.delegate) {
656
+ cloned.delegate = this.delegate.clone();
657
+ cloned.syncDelegate(cloned.delegate);
658
+ }
109
659
  return cloned;
110
660
  }
661
+ async resolveDelegate() {
662
+ await this.ensureRuntimeMode();
663
+ if (!this.delegate) {
664
+ if (this.runtimeMode !== _copilotkitnext_shared.RUNTIME_MODE_INTELLIGENCE) throw new Error("A delegate is only created for Intelligence mode");
665
+ this.delegate = this.createIntelligenceDelegate();
666
+ }
667
+ this.syncDelegate(this.delegate);
668
+ return this.delegate;
669
+ }
670
+ async ensureRuntimeMode() {
671
+ if (this.runtimeMode !== "pending") return;
672
+ if (!this.runtimeUrl) throw new Error("Runtime URL is not set");
673
+ this.runtimeInfoPromise ??= this.fetchRuntimeInfo().then((runtimeInfo) => {
674
+ this.runtimeMode = runtimeInfo.mode ?? _copilotkitnext_shared.RUNTIME_MODE_SSE;
675
+ this.intelligence = runtimeInfo.intelligence;
676
+ });
677
+ await this.runtimeInfoPromise;
678
+ }
679
+ async fetchRuntimeInfo() {
680
+ const headers = { ...this.headers };
681
+ let init;
682
+ let url;
683
+ if (this.transport === "single") {
684
+ if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
685
+ if (!headers["Content-Type"]) headers["Content-Type"] = "application/json";
686
+ url = this.runtimeUrl;
687
+ init = {
688
+ method: "POST",
689
+ body: JSON.stringify({ method: "info" })
690
+ };
691
+ } else {
692
+ url = `${this.runtimeUrl}/info`;
693
+ init = {};
694
+ }
695
+ const response = await fetch(url, {
696
+ ...init,
697
+ headers,
698
+ ...this.credentials ? { credentials: this.credentials } : {}
699
+ });
700
+ if (!response.ok) throw new Error(`Runtime info request failed with status ${response.status}`);
701
+ return await response.json();
702
+ }
111
703
  createSingleRouteRequestInit(input, method, params) {
112
704
  if (!this.agentId) throw new Error("ProxiedCopilotRuntimeAgent requires agentId to make runtime requests");
113
705
  const baseInit = super.requestInit(input);
@@ -119,7 +711,6 @@ var ProxiedCopilotRuntimeAgent = class extends _ag_ui_client.HttpAgent {
119
711
  originalBody = JSON.parse(baseInit.body);
120
712
  } catch (error) {
121
713
  console.warn("ProxiedCopilotRuntimeAgent: failed to parse request body for single route transport", error);
122
- originalBody = void 0;
123
714
  }
124
715
  const envelope = { method };
125
716
  if (params && Object.keys(params).length > 0) envelope.params = params;
@@ -131,6 +722,25 @@ var ProxiedCopilotRuntimeAgent = class extends _ag_ui_client.HttpAgent {
131
722
  ...this.credentials ? { credentials: this.credentials } : {}
132
723
  };
133
724
  }
725
+ createIntelligenceDelegate() {
726
+ if (!this.runtimeUrl || !this.agentId || !this.intelligence?.wsUrl) throw new Error("Intelligence mode requires runtimeUrl, agentId, and intelligence websocket metadata");
727
+ return new IntelligenceAgent({
728
+ url: this.intelligence.wsUrl,
729
+ runtimeUrl: this.runtimeUrl,
730
+ agentId: this.agentId,
731
+ headers: { ...this.headers },
732
+ credentials: this.credentials
733
+ });
734
+ }
735
+ syncDelegate(delegate) {
736
+ delegate.agentId = this.agentId;
737
+ delegate.description = this.description;
738
+ delegate.threadId = this.threadId;
739
+ delegate.setMessages(this.messages);
740
+ delegate.setState(this.state);
741
+ if (hasHeaders(delegate)) delegate.headers = { ...this.headers };
742
+ if (hasCredentials(delegate)) delegate.credentials = this.credentials;
743
+ }
134
744
  };
135
745
 
136
746
  //#endregion
@@ -148,6 +758,8 @@ var AgentRegistry = class {
148
758
  _runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Disconnected;
149
759
  _runtimeTransport = "rest";
150
760
  _audioFileTranscriptionEnabled = false;
761
+ _runtimeMode = _copilotkitnext_shared.RUNTIME_MODE_SSE;
762
+ _intelligence;
151
763
  _a2uiEnabled = false;
152
764
  constructor(core) {
153
765
  this.core = core;
@@ -173,6 +785,12 @@ var AgentRegistry = class {
173
785
  get audioFileTranscriptionEnabled() {
174
786
  return this._audioFileTranscriptionEnabled;
175
787
  }
788
+ get runtimeMode() {
789
+ return this._runtimeMode;
790
+ }
791
+ get intelligence() {
792
+ return this._intelligence;
793
+ }
176
794
  get a2uiEnabled() {
177
795
  return this._a2uiEnabled;
178
796
  }
@@ -282,6 +900,8 @@ var AgentRegistry = class {
282
900
  this._runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Disconnected;
283
901
  this._runtimeVersion = void 0;
284
902
  this._audioFileTranscriptionEnabled = false;
903
+ this._runtimeMode = _copilotkitnext_shared.RUNTIME_MODE_SSE;
904
+ this._intelligence = void 0;
285
905
  this._a2uiEnabled = false;
286
906
  this.remoteAgents = {};
287
907
  this._agents = this.localAgents;
@@ -301,7 +921,9 @@ var AgentRegistry = class {
301
921
  agentId: id,
302
922
  description,
303
923
  transport: this._runtimeTransport,
304
- credentials
924
+ credentials,
925
+ runtimeMode: runtimeInfoResponse.mode ?? _copilotkitnext_shared.RUNTIME_MODE_SSE,
926
+ intelligence: runtimeInfoResponse.intelligence
305
927
  });
306
928
  this.applyHeadersToAgent(agent);
307
929
  return [id, agent];
@@ -313,6 +935,8 @@ var AgentRegistry = class {
313
935
  this._runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Connected;
314
936
  this._runtimeVersion = version;
315
937
  this._audioFileTranscriptionEnabled = runtimeInfoResponse.audioFileTranscriptionEnabled ?? false;
938
+ this._runtimeMode = runtimeInfoResponse.mode ?? _copilotkitnext_shared.RUNTIME_MODE_SSE;
939
+ this._intelligence = runtimeInfoResponse.intelligence;
316
940
  this._a2uiEnabled = runtimeInfoResponse.a2uiEnabled ?? false;
317
941
  await this.notifyRuntimeStatusChanged(CopilotKitCoreRuntimeConnectionStatus.Connected);
318
942
  await this.notifyAgentsChanged();
@@ -320,6 +944,8 @@ var AgentRegistry = class {
320
944
  this._runtimeConnectionStatus = CopilotKitCoreRuntimeConnectionStatus.Error;
321
945
  this._runtimeVersion = void 0;
322
946
  this._audioFileTranscriptionEnabled = false;
947
+ this._runtimeMode = _copilotkitnext_shared.RUNTIME_MODE_SSE;
948
+ this._intelligence = void 0;
323
949
  this._a2uiEnabled = false;
324
950
  this.remoteAgents = {};
325
951
  this._agents = this.localAgents;
@@ -841,6 +1467,7 @@ var RunHandler = class {
841
1467
  async runAgent({ agent, forwardedProps }) {
842
1468
  if (agent.agentId) this._internal.suggestionEngine.clearSuggestions(agent.agentId);
843
1469
  if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...this._internal.headers };
1470
+ if (agent.detachActiveRun) agent.detachActiveRun();
844
1471
  try {
845
1472
  const runAgentResult = await agent.runAgent({
846
1473
  forwardedProps: {
@@ -910,7 +1537,7 @@ var RunHandler = class {
910
1537
  let isArgumentError = false;
911
1538
  let parsedArgs;
912
1539
  try {
913
- parsedArgs = ensureObjectArgs(typeof handlerArgs === "string" ? JSON.parse(handlerArgs) : handlerArgs, toolCall.function.name);
1540
+ parsedArgs = parseToolArguments(handlerArgs, toolCall.function.name);
914
1541
  } catch (error) {
915
1542
  const parseError = error instanceof Error ? error : new Error(String(error));
916
1543
  errorMessage = parseError.message;
@@ -1020,7 +1647,7 @@ var RunHandler = class {
1020
1647
  if (wildcardTool?.handler) {
1021
1648
  let parsedArgs;
1022
1649
  try {
1023
- parsedArgs = ensureObjectArgs(JSON.parse(toolCall.function.arguments), toolCall.function.name);
1650
+ parsedArgs = parseToolArguments(toolCall.function.arguments, toolCall.function.name);
1024
1651
  } catch (error) {
1025
1652
  const parseError = error instanceof Error ? error : new Error(String(error));
1026
1653
  errorMessage = parseError.message;
@@ -1267,6 +1894,25 @@ function ensureObjectArgs(parsed, toolName) {
1267
1894
  if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
1268
1895
  throw new Error(`Tool arguments for ${toolName} parsed to non-object (${typeof parsed})`);
1269
1896
  }
1897
+ /**
1898
+ * Parses raw tool call arguments into a validated object.
1899
+ *
1900
+ * Some LLM providers (e.g. @ai-sdk/openai-compatible) may send empty string "",
1901
+ * null, or undefined instead of "{}". This function normalises those cases to an
1902
+ * empty object so callers don't crash on JSON.parse("").
1903
+ *
1904
+ * A debug-level warning is emitted when the fallback triggers so silent coercion
1905
+ * is observable in logs.
1906
+ *
1907
+ * @internal Exported for testing only.
1908
+ */
1909
+ function parseToolArguments(rawArgs, toolName) {
1910
+ if (rawArgs === "" || rawArgs === null || rawArgs === void 0) {
1911
+ _copilotkitnext_shared.logger.debug(`[parseToolArguments] Tool "${toolName}" received empty/null/undefined arguments — defaulting to {}`);
1912
+ return {};
1913
+ }
1914
+ return ensureObjectArgs(typeof rawArgs === "string" ? JSON.parse(rawArgs) : rawArgs, toolName);
1915
+ }
1270
1916
 
1271
1917
  //#endregion
1272
1918
  //#region src/core/state-manager.ts
@@ -1561,6 +2207,12 @@ var CopilotKitCore = class {
1561
2207
  get audioFileTranscriptionEnabled() {
1562
2208
  return this.agentRegistry.audioFileTranscriptionEnabled;
1563
2209
  }
2210
+ get runtimeMode() {
2211
+ return this.agentRegistry.runtimeMode;
2212
+ }
2213
+ get intelligence() {
2214
+ return this.agentRegistry.intelligence;
2215
+ }
1564
2216
  get a2uiEnabled() {
1565
2217
  return this.agentRegistry.a2uiEnabled;
1566
2218
  }
@@ -1891,179 +2543,630 @@ function completePartialMarkdown(input) {
1891
2543
  }
1892
2544
 
1893
2545
  //#endregion
1894
- //#region src/intelligence-agent.ts
1895
- var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAgent {
1896
- config;
1897
- socket = null;
1898
- activeChannel = null;
1899
- threadId = null;
1900
- constructor(config) {
1901
- super();
1902
- this.config = config;
1903
- }
1904
- clone() {
1905
- return new IntelligenceAgent(this.config);
2546
+ //#region src/utils/micro-redux.ts
2547
+ const INTERNAL_ACTION_TYPES = {
2548
+ boot: "@@micro-redux/boot",
2549
+ init: "@@micro-redux/init",
2550
+ stop: "@@micro-redux/stop"
2551
+ };
2552
+ const INTERNAL_BOOT_ACTION = { type: INTERNAL_ACTION_TYPES.boot };
2553
+ /**
2554
+ * Builds a typed action creator from a type string and payload factory.
2555
+ */
2556
+ function createTypedActionCreator(type, factory) {
2557
+ const creator = ((...args) => ({
2558
+ ...factory(...args),
2559
+ type
2560
+ }));
2561
+ creator.type = type;
2562
+ creator.match = (action) => action.type === type;
2563
+ return creator;
2564
+ }
2565
+ /**
2566
+ * Declares a payload-based action config for `createActionGroup`.
2567
+ *
2568
+ * @example
2569
+ * ```ts
2570
+ * const actions = createActionGroup("User", {
2571
+ * loaded: props<{ id: string }>(),
2572
+ * });
2573
+ * ```
2574
+ */
2575
+ function props() {
2576
+ return { kind: "props" };
2577
+ }
2578
+ /**
2579
+ * Declares a no-payload action config for `createActionGroup`.
2580
+ *
2581
+ * @example
2582
+ * ```ts
2583
+ * const actions = createActionGroup("User", {
2584
+ * reset: empty(),
2585
+ * });
2586
+ * ```
2587
+ */
2588
+ function empty() {
2589
+ return { kind: "empty" };
2590
+ }
2591
+ /**
2592
+ * Creates a namespaced group of typed action creators.
2593
+ *
2594
+ * Action types are formatted as: `[Source] actionName`.
2595
+ */
2596
+ function createActionGroup(source, config) {
2597
+ const group = {};
2598
+ for (const eventName of Object.keys(config)) {
2599
+ const eventConfig = config[eventName];
2600
+ if (!eventConfig) continue;
2601
+ const actionType = `[${source}] ${eventName}`;
2602
+ if (eventConfig.kind === "props") {
2603
+ group[eventName] = createTypedActionCreator(actionType, (payload) => ({ ...payload }));
2604
+ continue;
2605
+ }
2606
+ group[eventName] = createTypedActionCreator(actionType, () => ({}));
1906
2607
  }
1907
- abortRun() {
1908
- if (this.activeChannel && this.threadId) {
1909
- const fallback = setTimeout(() => this.cleanup(), 5e3);
1910
- const clear = () => {
1911
- clearTimeout(fallback);
1912
- this.cleanup();
1913
- };
1914
- this.activeChannel.push(_copilotkitnext_shared.AG_UI_CHANNEL_EVENT, {
1915
- type: _ag_ui_client.EventType.CUSTOM,
1916
- name: "stop",
1917
- value: { threadId: this.threadId }
1918
- }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
1919
- } else this.cleanup();
2608
+ return group;
2609
+ }
2610
+ /**
2611
+ * Registers one reducer handler for one or more action creators.
2612
+ *
2613
+ * @throws Error when called without at least one action creator and reducer.
2614
+ */
2615
+ function on(...args) {
2616
+ if (args.length < 2) throw new Error("on requires at least one action creator and one reducer");
2617
+ const reducer = args[args.length - 1];
2618
+ return {
2619
+ creators: args.slice(0, -1),
2620
+ reducer
2621
+ };
2622
+ }
2623
+ /**
2624
+ * Creates a reducer from an initial state and `on(...)` handler entries.
2625
+ *
2626
+ * Unknown action types return the current state unchanged.
2627
+ */
2628
+ function createReducer(initialState, ...entries) {
2629
+ const reducerMap = /* @__PURE__ */ new Map();
2630
+ for (const entry of entries) for (const creator of entry.creators) {
2631
+ const handlers = reducerMap.get(creator.type) ?? [];
2632
+ handlers.push(entry.reducer);
2633
+ reducerMap.set(creator.type, handlers);
2634
+ }
2635
+ return (state, action) => {
2636
+ const currentState = state ?? initialState;
2637
+ const handlers = reducerMap.get(action.type);
2638
+ if (!handlers || handlers.length === 0) return currentState;
2639
+ let nextState = currentState;
2640
+ for (const handler of handlers) nextState = handler(nextState, action);
2641
+ return nextState;
2642
+ };
2643
+ }
2644
+ /**
2645
+ * Creates a selector that caches and reuses the last computed result
2646
+ * when all input references are unchanged.
2647
+ */
2648
+ function createSelector(...args) {
2649
+ if (args.length === 1) {
2650
+ const projector = args[0];
2651
+ let hasCached = false;
2652
+ let lastState;
2653
+ let lastResult;
2654
+ return (state) => {
2655
+ if (hasCached && state === lastState) return lastResult;
2656
+ lastState = state;
2657
+ lastResult = projector(state);
2658
+ hasCached = true;
2659
+ return lastResult;
2660
+ };
1920
2661
  }
1921
- /**
1922
- * Connect to a Phoenix channel scoped to the thread, trigger the run via
1923
- * REST, and relay server-pushed AG-UI events to the Observable subscriber.
1924
- *
1925
- * The server pushes each AG-UI event using its EventType string as the
1926
- * Phoenix event name (e.g. "TEXT_MESSAGE_CHUNK", "TOOL_CALL_START"), with
1927
- * the full BaseEvent as payload. RUN_FINISHED and RUN_ERROR are terminal
1928
- * events that complete or error the Observable.
1929
- */
1930
- run(input) {
1931
- return new rxjs.Observable((observer) => {
1932
- this.threadId = input.threadId;
1933
- const socket = new phoenix.Socket(this.config.url, {
1934
- params: this.config.socketParams ?? {},
1935
- reconnectAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(100, 1e4),
1936
- rejoinAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(1e3, 3e4)
1937
- });
1938
- this.socket = socket;
1939
- socket.connect();
1940
- const channel = socket.channel(`agent:${input.threadId}`, { runId: input.runId });
1941
- this.activeChannel = channel;
1942
- channel.on(_copilotkitnext_shared.AG_UI_CHANNEL_EVENT, (payload) => {
1943
- observer.next(payload);
1944
- if (payload.type === _ag_ui_client.EventType.RUN_FINISHED) {
1945
- observer.complete();
1946
- this.cleanup();
1947
- } else if (payload.type === _ag_ui_client.EventType.RUN_ERROR) {
1948
- observer.error(new Error(payload.message ?? "Run error"));
1949
- this.cleanup();
1950
- }
1951
- });
1952
- const MAX_CONSECUTIVE_ERRORS = 5;
1953
- let consecutiveErrors = 0;
1954
- socket.onError(() => {
1955
- consecutiveErrors++;
1956
- if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
1957
- observer.error(/* @__PURE__ */ new Error(`WebSocket connection failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors`));
1958
- this.cleanup();
1959
- }
1960
- });
1961
- socket.onOpen(() => {
1962
- consecutiveErrors = 0;
1963
- });
1964
- channel.onError(() => {});
1965
- channel.join().receive("ok", () => {
1966
- const { runtimeUrl, agentId, headers, credentials } = this.config;
1967
- const runPath = `${runtimeUrl}/agent/${encodeURIComponent(agentId)}/run`;
1968
- const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
1969
- const runUrl = new URL(runPath, new URL(runtimeUrl, origin));
1970
- fetch(runUrl.toString(), {
1971
- method: "POST",
1972
- headers: {
1973
- "Content-Type": "application/json",
1974
- ...headers
2662
+ const projector = args[args.length - 1];
2663
+ const selectors = args.slice(0, -1);
2664
+ let hasCached = false;
2665
+ let lastInputs = [];
2666
+ let lastResult;
2667
+ return (state) => {
2668
+ const inputs = selectors.map((selector) => selector(state));
2669
+ if (hasCached && inputs.length === lastInputs.length && inputs.every((value, index) => value === lastInputs[index])) return lastResult;
2670
+ lastInputs = inputs;
2671
+ lastResult = projector(...inputs);
2672
+ hasCached = true;
2673
+ return lastResult;
2674
+ };
2675
+ }
2676
+ /**
2677
+ * RxJS operator that maps state emissions through a selector and suppresses
2678
+ * unchanged projected values via reference equality.
2679
+ */
2680
+ function select(selector) {
2681
+ return (source$) => source$.pipe((0, rxjs_operators.map)(selector), (0, rxjs_operators.distinctUntilChanged)());
2682
+ }
2683
+ /**
2684
+ * RxJS operator that filters an action stream by action creators and narrows
2685
+ * the output action type to the matched creator union.
2686
+ *
2687
+ * @throws Error when called without at least one action creator.
2688
+ */
2689
+ function ofType(...creators) {
2690
+ if (creators.length === 0) throw new Error("ofType requires at least one action creator");
2691
+ const actionTypes = new Set(creators.map((creator) => creator.type));
2692
+ return (source$) => {
2693
+ return source$.pipe((0, rxjs_operators.filter)((action) => {
2694
+ return actionTypes.has(action.type);
2695
+ }));
2696
+ };
2697
+ }
2698
+ /**
2699
+ * Creates an effect descriptor consumed by `createStore`.
2700
+ */
2701
+ function createEffect(factory, options = {}) {
2702
+ if (options.dispatch === false) return {
2703
+ run: factory,
2704
+ dispatch: false
2705
+ };
2706
+ return {
2707
+ run: factory,
2708
+ dispatch: true
2709
+ };
2710
+ }
2711
+ /**
2712
+ * Creates a small observable store with reducer + effects.
2713
+ *
2714
+ * Behavior:
2715
+ * - `init()` starts effects and dispatches `@@micro-redux/init`.
2716
+ * - `stop()` dispatches `@@micro-redux/stop` and unsubscribes all effects.
2717
+ * - Effect action observation is scheduled on `asapScheduler` to avoid
2718
+ * synchronous re-entrancy in the effect loop.
2719
+ * - Any effect error triggers fail-fast teardown and errors both `actions$`
2720
+ * and `state$`.
2721
+ */
2722
+ function createStore(options) {
2723
+ const reducer = options.reducer;
2724
+ const effects = options.effects ?? [];
2725
+ let hasFatalError = false;
2726
+ let isRunning = false;
2727
+ let effectSubscriptions = new rxjs.Subscription();
2728
+ let currentState = reducer(void 0, INTERNAL_BOOT_ACTION);
2729
+ const stateSubject = new rxjs.BehaviorSubject(currentState);
2730
+ const actionsSubject = new rxjs.Subject();
2731
+ const dispatchInternal = (action) => {
2732
+ if (hasFatalError) throw new Error("Store is in a failed state due to an effect error");
2733
+ currentState = reducer(currentState, action);
2734
+ stateSubject.next(currentState);
2735
+ actionsSubject.next(action);
2736
+ };
2737
+ const failFast = (error) => {
2738
+ if (hasFatalError) return;
2739
+ hasFatalError = true;
2740
+ isRunning = false;
2741
+ effectSubscriptions.unsubscribe();
2742
+ effectSubscriptions = new rxjs.Subscription();
2743
+ actionsSubject.error(error);
2744
+ stateSubject.error(error);
2745
+ };
2746
+ const startEffects = () => {
2747
+ for (const effect of effects) {
2748
+ const scheduledActions$ = actionsSubject.asObservable().pipe((0, rxjs_operators.observeOn)(rxjs.asapScheduler));
2749
+ const state$ = stateSubject.asObservable();
2750
+ if (effect.dispatch) {
2751
+ const subscription = effect.run(scheduledActions$, state$).subscribe({
2752
+ next: (effectAction) => {
2753
+ if (hasFatalError) return;
2754
+ dispatchInternal(effectAction);
1975
2755
  },
1976
- body: JSON.stringify({
1977
- threadId: input.threadId,
1978
- runId: input.runId,
1979
- messages: input.messages,
1980
- tools: input.tools,
1981
- context: input.context,
1982
- state: input.state,
1983
- forwardedProps: input.forwardedProps
1984
- }),
1985
- ...credentials ? { credentials } : {}
1986
- }).catch((error) => {
1987
- observer.error(/* @__PURE__ */ new Error(`REST run request failed: ${error.message ?? error}`));
1988
- this.cleanup();
2756
+ error: (error) => {
2757
+ failFast(error);
2758
+ }
1989
2759
  });
1990
- }).receive("error", (resp) => {
1991
- observer.error(/* @__PURE__ */ new Error(`Failed to join channel: ${JSON.stringify(resp)}`));
1992
- this.cleanup();
1993
- }).receive("timeout", () => {
1994
- observer.error(/* @__PURE__ */ new Error("Timed out joining channel"));
1995
- this.cleanup();
1996
- });
1997
- return () => {
1998
- this.cleanup();
1999
- };
2760
+ effectSubscriptions.add(subscription);
2761
+ continue;
2762
+ }
2763
+ const subscription = effect.run(scheduledActions$, state$).subscribe({ error: (error) => {
2764
+ failFast(error);
2765
+ } });
2766
+ effectSubscriptions.add(subscription);
2767
+ }
2768
+ };
2769
+ return {
2770
+ dispatch(action) {
2771
+ dispatchInternal(action);
2772
+ },
2773
+ getState() {
2774
+ return currentState;
2775
+ },
2776
+ get state$() {
2777
+ return stateSubject.asObservable();
2778
+ },
2779
+ get actions$() {
2780
+ return actionsSubject.asObservable();
2781
+ },
2782
+ select(selector) {
2783
+ return stateSubject.asObservable().pipe(select(selector));
2784
+ },
2785
+ init() {
2786
+ if (hasFatalError || isRunning) return;
2787
+ isRunning = true;
2788
+ startEffects();
2789
+ if (hasFatalError) return;
2790
+ dispatchInternal({ type: INTERNAL_ACTION_TYPES.init });
2791
+ },
2792
+ stop() {
2793
+ if (hasFatalError || !isRunning) return;
2794
+ dispatchInternal({ type: INTERNAL_ACTION_TYPES.stop });
2795
+ effectSubscriptions.unsubscribe();
2796
+ effectSubscriptions = new rxjs.Subscription();
2797
+ isRunning = false;
2798
+ }
2799
+ };
2800
+ }
2801
+
2802
+ //#endregion
2803
+ //#region src/threads.ts
2804
+ const THREADS_CHANNEL_EVENT = "thread_metadata";
2805
+ const THREAD_SUBSCRIBE_PATH = "/threads/subscribe";
2806
+ const MAX_SOCKET_RETRIES = 5;
2807
+ const REQUEST_TIMEOUT_MS = 15e3;
2808
+ const initialThreadState = {
2809
+ threads: [],
2810
+ isLoading: false,
2811
+ error: null,
2812
+ context: null,
2813
+ sessionId: 0,
2814
+ metadataCredentialsRequested: false
2815
+ };
2816
+ const threadAdapterEvents = createActionGroup("Thread Adapter", {
2817
+ started: empty(),
2818
+ stopped: empty(),
2819
+ contextChanged: props(),
2820
+ renameRequested: props(),
2821
+ archiveRequested: props(),
2822
+ deleteRequested: props()
2823
+ });
2824
+ const threadRestEvents = createActionGroup("Thread REST", {
2825
+ listRequested: props(),
2826
+ listSucceeded: props(),
2827
+ listFailed: props(),
2828
+ metadataCredentialsRequested: props(),
2829
+ metadataCredentialsSucceeded: props(),
2830
+ metadataCredentialsFailed: props(),
2831
+ mutationFinished: props()
2832
+ });
2833
+ const threadSocketEvents = createActionGroup("Thread Socket", {
2834
+ opened: props(),
2835
+ errored: props(),
2836
+ joinFailed: props(),
2837
+ joinTimedOut: props(),
2838
+ metadataReceived: props()
2839
+ });
2840
+ const threadDomainEvents = createActionGroup("Thread Domain", {
2841
+ threadUpserted: props(),
2842
+ threadDeleted: props()
2843
+ });
2844
+ function sortThreadsByUpdatedAt(threads) {
2845
+ return [...threads].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
2846
+ }
2847
+ function upsertThread(threads, thread) {
2848
+ const existingIndex = threads.findIndex((item) => item.id === thread.id);
2849
+ if (existingIndex === -1) return sortThreadsByUpdatedAt([...threads, thread]);
2850
+ const next = [...threads];
2851
+ next[existingIndex] = thread;
2852
+ return sortThreadsByUpdatedAt(next);
2853
+ }
2854
+ const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.contextChanged, (state, { context }) => ({
2855
+ ...state,
2856
+ context,
2857
+ sessionId: state.sessionId + 1,
2858
+ threads: [],
2859
+ isLoading: Boolean(context),
2860
+ error: null,
2861
+ metadataCredentialsRequested: false
2862
+ })), on(threadAdapterEvents.stopped, (state) => ({
2863
+ ...state,
2864
+ threads: [],
2865
+ isLoading: false,
2866
+ error: null,
2867
+ metadataCredentialsRequested: false
2868
+ })), on(threadRestEvents.listRequested, (state, { sessionId }) => {
2869
+ if (sessionId !== state.sessionId || !state.context) return state;
2870
+ return {
2871
+ ...state,
2872
+ isLoading: true,
2873
+ error: null
2874
+ };
2875
+ }), on(threadRestEvents.listSucceeded, (state, { sessionId, threads }) => {
2876
+ if (sessionId !== state.sessionId) return state;
2877
+ return {
2878
+ ...state,
2879
+ threads: sortThreadsByUpdatedAt(threads),
2880
+ isLoading: false,
2881
+ error: null
2882
+ };
2883
+ }), on(threadRestEvents.listFailed, (state, { sessionId, error }) => {
2884
+ if (sessionId !== state.sessionId) return state;
2885
+ return {
2886
+ ...state,
2887
+ isLoading: false,
2888
+ error
2889
+ };
2890
+ }), on(threadRestEvents.metadataCredentialsFailed, (state, { sessionId, error }) => {
2891
+ if (sessionId !== state.sessionId) return state;
2892
+ return {
2893
+ ...state,
2894
+ error
2895
+ };
2896
+ }), on(threadRestEvents.metadataCredentialsRequested, (state, { sessionId }) => {
2897
+ if (sessionId !== state.sessionId) return state;
2898
+ return {
2899
+ ...state,
2900
+ metadataCredentialsRequested: true
2901
+ };
2902
+ }), on(threadRestEvents.mutationFinished, (state, { outcome }) => ({
2903
+ ...state,
2904
+ error: outcome.ok ? state.error : outcome.error
2905
+ })), on(threadDomainEvents.threadUpserted, (state, { sessionId, thread }) => {
2906
+ if (sessionId !== state.sessionId) return state;
2907
+ return {
2908
+ ...state,
2909
+ threads: upsertThread(state.threads, thread)
2910
+ };
2911
+ }), on(threadDomainEvents.threadDeleted, (state, { sessionId, threadId }) => {
2912
+ if (sessionId !== state.sessionId) return state;
2913
+ return {
2914
+ ...state,
2915
+ threads: state.threads.filter((thread) => thread.id !== threadId)
2916
+ };
2917
+ }));
2918
+ const selectThreads = createSelector((state) => state.threads);
2919
+ const selectThreadsIsLoading = createSelector((state) => state.isLoading);
2920
+ const selectThreadsError = createSelector((state) => state.error);
2921
+ let threadRequestId = 0;
2922
+ function createThreadRequestId() {
2923
+ threadRequestId += 1;
2924
+ return `thread-request-${threadRequestId}`;
2925
+ }
2926
+ function createThreadFetchObservable(environment, context, sessionId) {
2927
+ return (0, rxjs.defer)(() => {
2928
+ const params = new URLSearchParams({
2929
+ userId: context.userId,
2930
+ agentId: context.agentId
2000
2931
  });
2001
- }
2002
- /**
2003
- * Reconnect to an existing thread by joining the Phoenix channel in
2004
- * "connect" mode and requesting the server replay history.
2005
- */
2006
- connect(input) {
2007
- return new rxjs.Observable((observer) => {
2008
- this.threadId = input.threadId;
2009
- const socket = new phoenix.Socket(this.config.url, {
2010
- params: this.config.socketParams ?? {},
2011
- reconnectAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(100, 1e4),
2012
- rejoinAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(1e3, 3e4)
2013
- });
2014
- this.socket = socket;
2015
- socket.connect();
2016
- const channel = socket.channel(`agent:${input.threadId}`, { mode: "connect" });
2017
- this.activeChannel = channel;
2018
- channel.on(_copilotkitnext_shared.AG_UI_CHANNEL_EVENT, (payload) => {
2019
- observer.next(payload);
2020
- if (payload.type === _ag_ui_client.EventType.RUN_FINISHED || payload.type === _ag_ui_client.EventType.RUN_ERROR) {
2021
- observer.complete();
2022
- this.cleanup();
2023
- }
2932
+ return (0, rxjs_fetch.fromFetch)(`${context.runtimeUrl}/threads?${params.toString()}`, {
2933
+ selector: (response) => {
2934
+ if (!response.ok) throw new Error(`Failed to fetch threads: ${response.status}`);
2935
+ return response.json();
2936
+ },
2937
+ fetch: environment.fetch,
2938
+ method: "GET",
2939
+ headers: { ...context.headers }
2940
+ }).pipe((0, rxjs_operators.timeout)({
2941
+ first: REQUEST_TIMEOUT_MS,
2942
+ with: () => {
2943
+ throw new Error("Request timed out");
2944
+ }
2945
+ }), (0, rxjs_operators.map)((data) => threadRestEvents.listSucceeded({
2946
+ sessionId,
2947
+ threads: data.threads
2948
+ })), (0, rxjs_operators.catchError)((error) => {
2949
+ return (0, rxjs.of)(threadRestEvents.listFailed({
2950
+ sessionId,
2951
+ error: error instanceof Error ? error : new Error(String(error))
2952
+ }));
2953
+ }));
2954
+ });
2955
+ }
2956
+ function createThreadMetadataCredentialsObservable(environment, context, sessionId) {
2957
+ return (0, rxjs.defer)(() => {
2958
+ return (0, rxjs_fetch.fromFetch)(`${context.runtimeUrl}${THREAD_SUBSCRIBE_PATH}`, {
2959
+ selector: async (response) => {
2960
+ if (!response.ok) throw new Error(`Failed to fetch thread metadata credentials: ${response.status}`);
2961
+ return response.json();
2962
+ },
2963
+ fetch: environment.fetch,
2964
+ method: "POST",
2965
+ headers: {
2966
+ ...context.headers,
2967
+ "Content-Type": "application/json"
2968
+ },
2969
+ body: JSON.stringify({ userId: context.userId })
2970
+ }).pipe((0, rxjs_operators.timeout)({
2971
+ first: REQUEST_TIMEOUT_MS,
2972
+ with: () => {
2973
+ throw new Error("Request timed out");
2974
+ }
2975
+ }), (0, rxjs_operators.map)((data) => {
2976
+ if (typeof data.joinToken !== "string" || data.joinToken.length === 0) throw new Error("missing joinToken");
2977
+ return threadRestEvents.metadataCredentialsSucceeded({
2978
+ sessionId,
2979
+ joinToken: data.joinToken
2024
2980
  });
2025
- const MAX_CONSECUTIVE_ERRORS = 5;
2026
- let consecutiveErrors = 0;
2027
- socket.onError(() => {
2028
- consecutiveErrors++;
2029
- if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
2030
- observer.error(/* @__PURE__ */ new Error(`WebSocket connection failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors`));
2031
- this.cleanup();
2981
+ }), (0, rxjs_operators.catchError)((error) => {
2982
+ return (0, rxjs.of)(threadRestEvents.metadataCredentialsFailed({
2983
+ sessionId,
2984
+ error: error instanceof Error ? error : new Error(String(error))
2985
+ }));
2986
+ }));
2987
+ });
2988
+ }
2989
+ function createThreadMutationObservable(environment, context, request) {
2990
+ return (0, rxjs.defer)(() => {
2991
+ return (0, rxjs_fetch.fromFetch)(`${context.runtimeUrl}${request.path}`, {
2992
+ selector: async (response) => {
2993
+ if (!response.ok) throw new Error(`Request failed: ${response.status}`);
2994
+ return null;
2995
+ },
2996
+ fetch: environment.fetch,
2997
+ method: request.method,
2998
+ headers: {
2999
+ ...context.headers,
3000
+ "Content-Type": "application/json"
3001
+ },
3002
+ body: JSON.stringify(request.body)
3003
+ }).pipe((0, rxjs_operators.map)(() => threadRestEvents.mutationFinished({ outcome: {
3004
+ requestId: request.requestId,
3005
+ ok: true
3006
+ } })), (0, rxjs_operators.catchError)((error) => {
3007
+ return (0, rxjs.of)(threadRestEvents.mutationFinished({ outcome: {
3008
+ requestId: request.requestId,
3009
+ ok: false,
3010
+ error: error instanceof Error ? error : new Error(String(error))
3011
+ } }));
3012
+ }));
3013
+ });
3014
+ }
3015
+ function createThreadStore(environment) {
3016
+ const store = createStore({
3017
+ reducer: threadReducer,
3018
+ effects: [
3019
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadAdapterEvents.contextChanged), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([, state]) => Boolean(state.context)), (0, rxjs_operators.map)(([, state]) => threadRestEvents.listRequested({ sessionId: state.sessionId })))),
3020
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.listRequested), (0, rxjs_operators.switchMap)((action) => state$.pipe((0, rxjs_operators.map)((state) => state.context), (0, rxjs_operators.filter)((context) => Boolean(context)), (0, rxjs_operators.take)(1), (0, rxjs_operators.map)((context) => ({
3021
+ action,
3022
+ context
3023
+ })), (0, rxjs_operators.takeUntil)(actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped))), (0, rxjs_operators.switchMap)(({ action: currentAction, context }) => createThreadFetchObservable(environment, context, currentAction.sessionId)))))),
3024
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.listSucceeded), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([action, state]) => {
3025
+ return action.sessionId === state.sessionId && !state.metadataCredentialsRequested && Boolean(state.context?.wsUrl);
3026
+ }), (0, rxjs_operators.map)(([action]) => threadRestEvents.metadataCredentialsRequested({ sessionId: action.sessionId })))),
3027
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.metadataCredentialsRequested), (0, rxjs_operators.switchMap)((action) => state$.pipe((0, rxjs_operators.map)((state) => state.context), (0, rxjs_operators.filter)((context) => Boolean(context)), (0, rxjs_operators.take)(1), (0, rxjs_operators.map)((context) => ({
3028
+ action,
3029
+ context
3030
+ })), (0, rxjs_operators.takeUntil)(actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped))), (0, rxjs_operators.switchMap)(({ action: currentAction, context }) => createThreadMetadataCredentialsObservable(environment, context, currentAction.sessionId)))))),
3031
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.metadataCredentialsSucceeded), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([action, state]) => {
3032
+ return action.sessionId === state.sessionId && Boolean(state.context?.wsUrl);
3033
+ }), (0, rxjs_operators.switchMap)(([action, state]) => {
3034
+ const context = state.context;
3035
+ const joinToken = action.joinToken;
3036
+ const shutdown$ = actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped));
3037
+ return (0, rxjs.defer)(() => {
3038
+ const socket$ = ɵphoenixSocket$({
3039
+ url: context.wsUrl,
3040
+ options: {
3041
+ params: { join_token: joinToken },
3042
+ reconnectAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(100, 1e4),
3043
+ rejoinAfterMs: (0, _copilotkitnext_shared.phoenixExponentialBackoff)(1e3, 3e4)
3044
+ }
3045
+ }).pipe((0, rxjs_operators.shareReplay)({
3046
+ bufferSize: 1,
3047
+ refCount: true
3048
+ }));
3049
+ const channel$ = ɵphoenixChannel$({
3050
+ socket$,
3051
+ topic: `user_meta:${context.userId}`
3052
+ }).pipe((0, rxjs_operators.shareReplay)({
3053
+ bufferSize: 1,
3054
+ refCount: true
3055
+ }));
3056
+ const socketSignals$ = ɵobservePhoenixSocketSignals$(socket$).pipe((0, rxjs_operators.share)());
3057
+ const fatalSocketShutdown$ = ɵobservePhoenixSocketHealth$(socketSignals$, MAX_SOCKET_RETRIES).pipe((0, rxjs_operators.catchError)(() => {
3058
+ console.warn(`[threads] WebSocket failed after ${MAX_SOCKET_RETRIES} attempts, giving up`);
3059
+ return (0, rxjs.of)(void 0);
3060
+ }), (0, rxjs_operators.share)());
3061
+ return (0, rxjs.merge)(socketSignals$.pipe((0, rxjs_operators.map)((signal) => signal.type === "open" ? threadSocketEvents.opened({ sessionId: action.sessionId }) : threadSocketEvents.errored({ sessionId: action.sessionId }))), channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => ɵobservePhoenixEvent$(channel, THREADS_CHANNEL_EVENT)), (0, rxjs_operators.map)((payload) => threadSocketEvents.metadataReceived({
3062
+ sessionId: action.sessionId,
3063
+ payload
3064
+ }))), ɵobservePhoenixJoinOutcome$(channel$).pipe((0, rxjs_operators.filter)((outcome) => outcome.type !== "joined"), (0, rxjs_operators.map)((outcome) => outcome.type === "timeout" ? threadSocketEvents.joinTimedOut({ sessionId: action.sessionId }) : threadSocketEvents.joinFailed({ sessionId: action.sessionId })))).pipe((0, rxjs_operators.takeUntil)((0, rxjs.merge)(shutdown$, fatalSocketShutdown$)));
3065
+ });
3066
+ }))),
3067
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadSocketEvents.metadataReceived), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.filter)(([action, state]) => {
3068
+ return action.sessionId === state.sessionId && action.payload.userId === state.context?.userId;
3069
+ }), (0, rxjs_operators.map)(([action]) => {
3070
+ if (action.payload.operation === "deleted") return threadDomainEvents.threadDeleted({
3071
+ sessionId: action.sessionId,
3072
+ threadId: action.payload.deleted.id
3073
+ });
3074
+ return threadDomainEvents.threadUpserted({
3075
+ sessionId: action.sessionId,
3076
+ thread: action.payload.thread
3077
+ });
3078
+ }))),
3079
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadAdapterEvents.renameRequested, threadAdapterEvents.archiveRequested, threadAdapterEvents.deleteRequested), (0, rxjs_operators.withLatestFrom)(state$), (0, rxjs_operators.mergeMap)(([action, state]) => {
3080
+ const context = state.context;
3081
+ if (!context?.runtimeUrl) {
3082
+ const requestId = action.requestId;
3083
+ return (0, rxjs.of)(threadRestEvents.mutationFinished({ outcome: {
3084
+ requestId,
3085
+ ok: false,
3086
+ error: /* @__PURE__ */ new Error("Runtime URL is not configured")
3087
+ } }));
2032
3088
  }
2033
- });
2034
- socket.onOpen(() => {
2035
- consecutiveErrors = 0;
2036
- });
2037
- channel.onError(() => {});
2038
- channel.join().receive("ok", () => {
2039
- channel.push(_ag_ui_client.EventType.CUSTOM, {
2040
- type: _ag_ui_client.EventType.CUSTOM,
2041
- name: "connect",
2042
- value: { threadId: input.threadId }
3089
+ const commonBody = {
3090
+ userId: context.userId,
3091
+ agentId: context.agentId
3092
+ };
3093
+ if (threadAdapterEvents.renameRequested.match(action)) return createThreadMutationObservable(environment, context, {
3094
+ requestId: action.requestId,
3095
+ method: "PATCH",
3096
+ path: `/threads/${encodeURIComponent(action.threadId)}`,
3097
+ body: {
3098
+ ...commonBody,
3099
+ name: action.name
3100
+ }
2043
3101
  });
2044
- }).receive("error", (resp) => {
2045
- observer.error(/* @__PURE__ */ new Error(`Failed to join channel: ${JSON.stringify(resp)}`));
2046
- this.cleanup();
2047
- }).receive("timeout", () => {
2048
- observer.error(/* @__PURE__ */ new Error("Timed out joining channel"));
2049
- this.cleanup();
2050
- });
2051
- return () => {
2052
- this.cleanup();
2053
- };
3102
+ if (threadAdapterEvents.archiveRequested.match(action)) return createThreadMutationObservable(environment, context, {
3103
+ requestId: action.requestId,
3104
+ method: "POST",
3105
+ path: `/threads/${encodeURIComponent(action.threadId)}/archive`,
3106
+ body: commonBody
3107
+ });
3108
+ return createThreadMutationObservable(environment, context, {
3109
+ requestId: action.requestId,
3110
+ method: "DELETE",
3111
+ path: `/threads/${encodeURIComponent(action.threadId)}`,
3112
+ body: commonBody
3113
+ });
3114
+ })))
3115
+ ]
3116
+ });
3117
+ function trackMutation(dispatchAction) {
3118
+ const resultPromise = (0, rxjs.firstValueFrom)((0, rxjs.merge)(store.actions$.pipe(ofType(threadRestEvents.mutationFinished), (0, rxjs_operators.filter)((action) => action.outcome.requestId === dispatchAction.requestId), (0, rxjs_operators.map)((action) => action.outcome)), store.actions$.pipe(ofType(threadAdapterEvents.stopped), (0, rxjs_operators.map)(() => ({
3119
+ requestId: dispatchAction.requestId,
3120
+ ok: false,
3121
+ error: /* @__PURE__ */ new Error("Thread store stopped before mutation completed")
3122
+ })))).pipe((0, rxjs_operators.take)(1))).then((outcome) => {
3123
+ if (outcome.ok) return;
3124
+ throw outcome.error;
2054
3125
  });
2055
- }
2056
- cleanup() {
2057
- if (this.activeChannel) {
2058
- this.activeChannel.leave();
2059
- this.activeChannel = null;
2060
- }
2061
- if (this.socket) {
2062
- this.socket.disconnect();
2063
- this.socket = null;
2064
- }
2065
- }
2066
- };
3126
+ store.dispatch(dispatchAction);
3127
+ return resultPromise;
3128
+ }
3129
+ return {
3130
+ start() {
3131
+ store.init();
3132
+ store.dispatch(threadAdapterEvents.started());
3133
+ },
3134
+ stop() {
3135
+ store.dispatch(threadAdapterEvents.stopped());
3136
+ store.stop();
3137
+ },
3138
+ setContext(context) {
3139
+ store.dispatch(threadAdapterEvents.contextChanged({ context }));
3140
+ },
3141
+ renameThread(threadId, name) {
3142
+ return trackMutation(threadAdapterEvents.renameRequested({
3143
+ requestId: createThreadRequestId(),
3144
+ threadId,
3145
+ name
3146
+ }));
3147
+ },
3148
+ archiveThread(threadId) {
3149
+ return trackMutation(threadAdapterEvents.archiveRequested({
3150
+ requestId: createThreadRequestId(),
3151
+ threadId
3152
+ }));
3153
+ },
3154
+ deleteThread(threadId) {
3155
+ return trackMutation(threadAdapterEvents.deleteRequested({
3156
+ requestId: createThreadRequestId(),
3157
+ threadId
3158
+ }));
3159
+ },
3160
+ getState() {
3161
+ return store.getState();
3162
+ },
3163
+ select: store.select.bind(store)
3164
+ };
3165
+ }
3166
+ const ɵthreadAdapterEvents = threadAdapterEvents;
3167
+ const ɵselectThreads = selectThreads;
3168
+ const ɵselectThreadsIsLoading = selectThreadsIsLoading;
3169
+ const ɵselectThreadsError = selectThreadsError;
2067
3170
 
2068
3171
  //#endregion
2069
3172
  exports.AgentRegistry = AgentRegistry;
@@ -2078,5 +3181,28 @@ exports.StateManager = StateManager;
2078
3181
  exports.SuggestionEngine = SuggestionEngine;
2079
3182
  exports.ToolCallStatus = ToolCallStatus;
2080
3183
  exports.completePartialMarkdown = completePartialMarkdown;
3184
+ exports.createActionGroup = createActionGroup;
3185
+ exports.createEffect = createEffect;
3186
+ exports.createReducer = createReducer;
3187
+ exports.createSelector = createSelector;
3188
+ exports.createStore = createStore;
3189
+ exports.empty = empty;
2081
3190
  exports.ensureObjectArgs = ensureObjectArgs;
3191
+ exports.ofType = ofType;
3192
+ exports.on = on;
3193
+ exports.parseToolArguments = parseToolArguments;
3194
+ exports.props = props;
3195
+ exports.select = select;
3196
+ exports.ɵcreateThreadStore = createThreadStore;
3197
+ exports.ɵjoinPhoenixChannel$ = ɵjoinPhoenixChannel$;
3198
+ exports.ɵobservePhoenixEvent$ = ɵobservePhoenixEvent$;
3199
+ exports.ɵobservePhoenixJoinOutcome$ = ɵobservePhoenixJoinOutcome$;
3200
+ exports.ɵobservePhoenixSocketHealth$ = ɵobservePhoenixSocketHealth$;
3201
+ exports.ɵobservePhoenixSocketSignals$ = ɵobservePhoenixSocketSignals$;
3202
+ exports.ɵphoenixChannel$ = ɵphoenixChannel$;
3203
+ exports.ɵphoenixSocket$ = ɵphoenixSocket$;
3204
+ exports.ɵselectThreads = ɵselectThreads;
3205
+ exports.ɵselectThreadsError = ɵselectThreadsError;
3206
+ exports.ɵselectThreadsIsLoading = ɵselectThreadsIsLoading;
3207
+ exports.ɵthreadAdapterEvents = ɵthreadAdapterEvents;
2082
3208
  //# sourceMappingURL=index.cjs.map