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