@copilotkitnext/core 1.54.0 → 1.54.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: {
@@ -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
@@ -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
  };
@@ -1560,6 +2187,12 @@ var CopilotKitCore = class {
1560
2187
  get audioFileTranscriptionEnabled() {
1561
2188
  return this.agentRegistry.audioFileTranscriptionEnabled;
1562
2189
  }
2190
+ get runtimeMode() {
2191
+ return this.agentRegistry.runtimeMode;
2192
+ }
2193
+ get intelligence() {
2194
+ return this.agentRegistry.intelligence;
2195
+ }
1563
2196
  get a2uiEnabled() {
1564
2197
  return this.agentRegistry.a2uiEnabled;
1565
2198
  }
@@ -1890,180 +2523,631 @@ function completePartialMarkdown(input) {
1890
2523
  }
1891
2524
 
1892
2525
  //#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);
2526
+ //#region src/utils/micro-redux.ts
2527
+ const INTERNAL_ACTION_TYPES = {
2528
+ boot: "@@micro-redux/boot",
2529
+ init: "@@micro-redux/init",
2530
+ stop: "@@micro-redux/stop"
2531
+ };
2532
+ const INTERNAL_BOOT_ACTION = { type: INTERNAL_ACTION_TYPES.boot };
2533
+ /**
2534
+ * Builds a typed action creator from a type string and payload factory.
2535
+ */
2536
+ function createTypedActionCreator(type, factory) {
2537
+ const creator = ((...args) => ({
2538
+ ...factory(...args),
2539
+ type
2540
+ }));
2541
+ creator.type = type;
2542
+ creator.match = (action) => action.type === type;
2543
+ return creator;
2544
+ }
2545
+ /**
2546
+ * Declares a payload-based action config for `createActionGroup`.
2547
+ *
2548
+ * @example
2549
+ * ```ts
2550
+ * const actions = createActionGroup("User", {
2551
+ * loaded: props<{ id: string }>(),
2552
+ * });
2553
+ * ```
2554
+ */
2555
+ function props() {
2556
+ return { kind: "props" };
2557
+ }
2558
+ /**
2559
+ * Declares a no-payload action config for `createActionGroup`.
2560
+ *
2561
+ * @example
2562
+ * ```ts
2563
+ * const actions = createActionGroup("User", {
2564
+ * reset: empty(),
2565
+ * });
2566
+ * ```
2567
+ */
2568
+ function empty() {
2569
+ return { kind: "empty" };
2570
+ }
2571
+ /**
2572
+ * Creates a namespaced group of typed action creators.
2573
+ *
2574
+ * Action types are formatted as: `[Source] actionName`.
2575
+ */
2576
+ function createActionGroup(source, config) {
2577
+ const group = {};
2578
+ for (const eventName of Object.keys(config)) {
2579
+ const eventConfig = config[eventName];
2580
+ if (!eventConfig) continue;
2581
+ const actionType = `[${source}] ${eventName}`;
2582
+ if (eventConfig.kind === "props") {
2583
+ group[eventName] = createTypedActionCreator(actionType, (payload) => ({ ...payload }));
2584
+ continue;
2585
+ }
2586
+ group[eventName] = createTypedActionCreator(actionType, () => ({}));
1905
2587
  }
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();
2588
+ return group;
2589
+ }
2590
+ /**
2591
+ * Registers one reducer handler for one or more action creators.
2592
+ *
2593
+ * @throws Error when called without at least one action creator and reducer.
2594
+ */
2595
+ function on(...args) {
2596
+ if (args.length < 2) throw new Error("on requires at least one action creator and one reducer");
2597
+ const reducer = args[args.length - 1];
2598
+ return {
2599
+ creators: args.slice(0, -1),
2600
+ reducer
2601
+ };
2602
+ }
2603
+ /**
2604
+ * Creates a reducer from an initial state and `on(...)` handler entries.
2605
+ *
2606
+ * Unknown action types return the current state unchanged.
2607
+ */
2608
+ function createReducer(initialState, ...entries) {
2609
+ const reducerMap = /* @__PURE__ */ new Map();
2610
+ for (const entry of entries) for (const creator of entry.creators) {
2611
+ const handlers = reducerMap.get(creator.type) ?? [];
2612
+ handlers.push(entry.reducer);
2613
+ reducerMap.set(creator.type, handlers);
2614
+ }
2615
+ return (state, action) => {
2616
+ const currentState = state ?? initialState;
2617
+ const handlers = reducerMap.get(action.type);
2618
+ if (!handlers || handlers.length === 0) return currentState;
2619
+ let nextState = currentState;
2620
+ for (const handler of handlers) nextState = handler(nextState, action);
2621
+ return nextState;
2622
+ };
2623
+ }
2624
+ /**
2625
+ * Creates a selector that caches and reuses the last computed result
2626
+ * when all input references are unchanged.
2627
+ */
2628
+ function createSelector(...args) {
2629
+ if (args.length === 1) {
2630
+ const projector = args[0];
2631
+ let hasCached = false;
2632
+ let lastState;
2633
+ let lastResult;
2634
+ return (state) => {
2635
+ if (hasCached && state === lastState) return lastResult;
2636
+ lastState = state;
2637
+ lastResult = projector(state);
2638
+ hasCached = true;
2639
+ return lastResult;
2640
+ };
1919
2641
  }
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
2642
+ const projector = args[args.length - 1];
2643
+ const selectors = args.slice(0, -1);
2644
+ let hasCached = false;
2645
+ let lastInputs = [];
2646
+ let lastResult;
2647
+ return (state) => {
2648
+ const inputs = selectors.map((selector) => selector(state));
2649
+ if (hasCached && inputs.length === lastInputs.length && inputs.every((value, index) => value === lastInputs[index])) return lastResult;
2650
+ lastInputs = inputs;
2651
+ lastResult = projector(...inputs);
2652
+ hasCached = true;
2653
+ return lastResult;
2654
+ };
2655
+ }
2656
+ /**
2657
+ * RxJS operator that maps state emissions through a selector and suppresses
2658
+ * unchanged projected values via reference equality.
2659
+ */
2660
+ function select(selector) {
2661
+ return (source$) => source$.pipe(map(selector), distinctUntilChanged());
2662
+ }
2663
+ /**
2664
+ * RxJS operator that filters an action stream by action creators and narrows
2665
+ * the output action type to the matched creator union.
2666
+ *
2667
+ * @throws Error when called without at least one action creator.
2668
+ */
2669
+ function ofType(...creators) {
2670
+ if (creators.length === 0) throw new Error("ofType requires at least one action creator");
2671
+ const actionTypes = new Set(creators.map((creator) => creator.type));
2672
+ return (source$) => {
2673
+ return source$.pipe(filter((action) => {
2674
+ return actionTypes.has(action.type);
2675
+ }));
2676
+ };
2677
+ }
2678
+ /**
2679
+ * Creates an effect descriptor consumed by `createStore`.
2680
+ */
2681
+ function createEffect(factory, options = {}) {
2682
+ if (options.dispatch === false) return {
2683
+ run: factory,
2684
+ dispatch: false
2685
+ };
2686
+ return {
2687
+ run: factory,
2688
+ dispatch: true
2689
+ };
2690
+ }
2691
+ /**
2692
+ * Creates a small observable store with reducer + effects.
2693
+ *
2694
+ * Behavior:
2695
+ * - `init()` starts effects and dispatches `@@micro-redux/init`.
2696
+ * - `stop()` dispatches `@@micro-redux/stop` and unsubscribes all effects.
2697
+ * - Effect action observation is scheduled on `asapScheduler` to avoid
2698
+ * synchronous re-entrancy in the effect loop.
2699
+ * - Any effect error triggers fail-fast teardown and errors both `actions$`
2700
+ * and `state$`.
2701
+ */
2702
+ function createStore(options) {
2703
+ const reducer = options.reducer;
2704
+ const effects = options.effects ?? [];
2705
+ let hasFatalError = false;
2706
+ let isRunning = false;
2707
+ let effectSubscriptions = new Subscription();
2708
+ let currentState = reducer(void 0, INTERNAL_BOOT_ACTION);
2709
+ const stateSubject = new BehaviorSubject(currentState);
2710
+ const actionsSubject = new Subject();
2711
+ const dispatchInternal = (action) => {
2712
+ if (hasFatalError) throw new Error("Store is in a failed state due to an effect error");
2713
+ currentState = reducer(currentState, action);
2714
+ stateSubject.next(currentState);
2715
+ actionsSubject.next(action);
2716
+ };
2717
+ const failFast = (error) => {
2718
+ if (hasFatalError) return;
2719
+ hasFatalError = true;
2720
+ isRunning = false;
2721
+ effectSubscriptions.unsubscribe();
2722
+ effectSubscriptions = new Subscription();
2723
+ actionsSubject.error(error);
2724
+ stateSubject.error(error);
2725
+ };
2726
+ const startEffects = () => {
2727
+ for (const effect of effects) {
2728
+ const scheduledActions$ = actionsSubject.asObservable().pipe(observeOn(asapScheduler));
2729
+ const state$ = stateSubject.asObservable();
2730
+ if (effect.dispatch) {
2731
+ const subscription = effect.run(scheduledActions$, state$).subscribe({
2732
+ next: (effectAction) => {
2733
+ if (hasFatalError) return;
2734
+ dispatchInternal(effectAction);
1974
2735
  },
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();
2736
+ error: (error) => {
2737
+ failFast(error);
2738
+ }
1988
2739
  });
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
- };
2740
+ effectSubscriptions.add(subscription);
2741
+ continue;
2742
+ }
2743
+ const subscription = effect.run(scheduledActions$, state$).subscribe({ error: (error) => {
2744
+ failFast(error);
2745
+ } });
2746
+ effectSubscriptions.add(subscription);
2747
+ }
2748
+ };
2749
+ return {
2750
+ dispatch(action) {
2751
+ dispatchInternal(action);
2752
+ },
2753
+ getState() {
2754
+ return currentState;
2755
+ },
2756
+ get state$() {
2757
+ return stateSubject.asObservable();
2758
+ },
2759
+ get actions$() {
2760
+ return actionsSubject.asObservable();
2761
+ },
2762
+ select(selector) {
2763
+ return stateSubject.asObservable().pipe(select(selector));
2764
+ },
2765
+ init() {
2766
+ if (hasFatalError || isRunning) return;
2767
+ isRunning = true;
2768
+ startEffects();
2769
+ if (hasFatalError) return;
2770
+ dispatchInternal({ type: INTERNAL_ACTION_TYPES.init });
2771
+ },
2772
+ stop() {
2773
+ if (hasFatalError || !isRunning) return;
2774
+ dispatchInternal({ type: INTERNAL_ACTION_TYPES.stop });
2775
+ effectSubscriptions.unsubscribe();
2776
+ effectSubscriptions = new Subscription();
2777
+ isRunning = false;
2778
+ }
2779
+ };
2780
+ }
2781
+
2782
+ //#endregion
2783
+ //#region src/threads.ts
2784
+ const THREADS_CHANNEL_EVENT = "thread_metadata";
2785
+ const THREAD_SUBSCRIBE_PATH = "/threads/subscribe";
2786
+ const MAX_SOCKET_RETRIES = 5;
2787
+ const REQUEST_TIMEOUT_MS = 15e3;
2788
+ const initialThreadState = {
2789
+ threads: [],
2790
+ isLoading: false,
2791
+ error: null,
2792
+ context: null,
2793
+ sessionId: 0,
2794
+ metadataCredentialsRequested: false
2795
+ };
2796
+ const threadAdapterEvents = createActionGroup("Thread Adapter", {
2797
+ started: empty(),
2798
+ stopped: empty(),
2799
+ contextChanged: props(),
2800
+ renameRequested: props(),
2801
+ archiveRequested: props(),
2802
+ deleteRequested: props()
2803
+ });
2804
+ const threadRestEvents = createActionGroup("Thread REST", {
2805
+ listRequested: props(),
2806
+ listSucceeded: props(),
2807
+ listFailed: props(),
2808
+ metadataCredentialsRequested: props(),
2809
+ metadataCredentialsSucceeded: props(),
2810
+ metadataCredentialsFailed: props(),
2811
+ mutationFinished: props()
2812
+ });
2813
+ const threadSocketEvents = createActionGroup("Thread Socket", {
2814
+ opened: props(),
2815
+ errored: props(),
2816
+ joinFailed: props(),
2817
+ joinTimedOut: props(),
2818
+ metadataReceived: props()
2819
+ });
2820
+ const threadDomainEvents = createActionGroup("Thread Domain", {
2821
+ threadUpserted: props(),
2822
+ threadDeleted: props()
2823
+ });
2824
+ function sortThreadsByUpdatedAt(threads) {
2825
+ return [...threads].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
2826
+ }
2827
+ function upsertThread(threads, thread) {
2828
+ const existingIndex = threads.findIndex((item) => item.id === thread.id);
2829
+ if (existingIndex === -1) return sortThreadsByUpdatedAt([...threads, thread]);
2830
+ const next = [...threads];
2831
+ next[existingIndex] = thread;
2832
+ return sortThreadsByUpdatedAt(next);
2833
+ }
2834
+ const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.contextChanged, (state, { context }) => ({
2835
+ ...state,
2836
+ context,
2837
+ sessionId: state.sessionId + 1,
2838
+ threads: [],
2839
+ isLoading: Boolean(context),
2840
+ error: null,
2841
+ metadataCredentialsRequested: false
2842
+ })), on(threadAdapterEvents.stopped, (state) => ({
2843
+ ...state,
2844
+ threads: [],
2845
+ isLoading: false,
2846
+ error: null,
2847
+ metadataCredentialsRequested: false
2848
+ })), on(threadRestEvents.listRequested, (state, { sessionId }) => {
2849
+ if (sessionId !== state.sessionId || !state.context) return state;
2850
+ return {
2851
+ ...state,
2852
+ isLoading: true,
2853
+ error: null
2854
+ };
2855
+ }), on(threadRestEvents.listSucceeded, (state, { sessionId, threads }) => {
2856
+ if (sessionId !== state.sessionId) return state;
2857
+ return {
2858
+ ...state,
2859
+ threads: sortThreadsByUpdatedAt(threads),
2860
+ isLoading: false,
2861
+ error: null
2862
+ };
2863
+ }), on(threadRestEvents.listFailed, (state, { sessionId, error }) => {
2864
+ if (sessionId !== state.sessionId) return state;
2865
+ return {
2866
+ ...state,
2867
+ isLoading: false,
2868
+ error
2869
+ };
2870
+ }), on(threadRestEvents.metadataCredentialsFailed, (state, { sessionId, error }) => {
2871
+ if (sessionId !== state.sessionId) return state;
2872
+ return {
2873
+ ...state,
2874
+ error
2875
+ };
2876
+ }), on(threadRestEvents.metadataCredentialsRequested, (state, { sessionId }) => {
2877
+ if (sessionId !== state.sessionId) return state;
2878
+ return {
2879
+ ...state,
2880
+ metadataCredentialsRequested: true
2881
+ };
2882
+ }), on(threadRestEvents.mutationFinished, (state, { outcome }) => ({
2883
+ ...state,
2884
+ error: outcome.ok ? state.error : outcome.error
2885
+ })), on(threadDomainEvents.threadUpserted, (state, { sessionId, thread }) => {
2886
+ if (sessionId !== state.sessionId) return state;
2887
+ return {
2888
+ ...state,
2889
+ threads: upsertThread(state.threads, thread)
2890
+ };
2891
+ }), on(threadDomainEvents.threadDeleted, (state, { sessionId, threadId }) => {
2892
+ if (sessionId !== state.sessionId) return state;
2893
+ return {
2894
+ ...state,
2895
+ threads: state.threads.filter((thread) => thread.id !== threadId)
2896
+ };
2897
+ }));
2898
+ const selectThreads = createSelector((state) => state.threads);
2899
+ const selectThreadsIsLoading = createSelector((state) => state.isLoading);
2900
+ const selectThreadsError = createSelector((state) => state.error);
2901
+ let threadRequestId = 0;
2902
+ function createThreadRequestId() {
2903
+ threadRequestId += 1;
2904
+ return `thread-request-${threadRequestId}`;
2905
+ }
2906
+ function createThreadFetchObservable(environment, context, sessionId) {
2907
+ return defer(() => {
2908
+ const params = new URLSearchParams({
2909
+ userId: context.userId,
2910
+ agentId: context.agentId
1999
2911
  });
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
- }
2912
+ return fromFetch(`${context.runtimeUrl}/threads?${params.toString()}`, {
2913
+ selector: (response) => {
2914
+ if (!response.ok) throw new Error(`Failed to fetch threads: ${response.status}`);
2915
+ return response.json();
2916
+ },
2917
+ fetch: environment.fetch,
2918
+ method: "GET",
2919
+ headers: { ...context.headers }
2920
+ }).pipe(timeout({
2921
+ first: REQUEST_TIMEOUT_MS,
2922
+ with: () => {
2923
+ throw new Error("Request timed out");
2924
+ }
2925
+ }), map((data) => threadRestEvents.listSucceeded({
2926
+ sessionId,
2927
+ threads: data.threads
2928
+ })), catchError((error) => {
2929
+ return of(threadRestEvents.listFailed({
2930
+ sessionId,
2931
+ error: error instanceof Error ? error : new Error(String(error))
2932
+ }));
2933
+ }));
2934
+ });
2935
+ }
2936
+ function createThreadMetadataCredentialsObservable(environment, context, sessionId) {
2937
+ return defer(() => {
2938
+ return fromFetch(`${context.runtimeUrl}${THREAD_SUBSCRIBE_PATH}`, {
2939
+ selector: async (response) => {
2940
+ if (!response.ok) throw new Error(`Failed to fetch thread metadata credentials: ${response.status}`);
2941
+ return response.json();
2942
+ },
2943
+ fetch: environment.fetch,
2944
+ method: "POST",
2945
+ headers: {
2946
+ ...context.headers,
2947
+ "Content-Type": "application/json"
2948
+ },
2949
+ body: JSON.stringify({ userId: context.userId })
2950
+ }).pipe(timeout({
2951
+ first: REQUEST_TIMEOUT_MS,
2952
+ with: () => {
2953
+ throw new Error("Request timed out");
2954
+ }
2955
+ }), map((data) => {
2956
+ if (typeof data.joinToken !== "string" || data.joinToken.length === 0) throw new Error("missing joinToken");
2957
+ return threadRestEvents.metadataCredentialsSucceeded({
2958
+ sessionId,
2959
+ joinToken: data.joinToken
2023
2960
  });
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();
2961
+ }), catchError((error) => {
2962
+ return of(threadRestEvents.metadataCredentialsFailed({
2963
+ sessionId,
2964
+ error: error instanceof Error ? error : new Error(String(error))
2965
+ }));
2966
+ }));
2967
+ });
2968
+ }
2969
+ function createThreadMutationObservable(environment, context, request) {
2970
+ return defer(() => {
2971
+ return fromFetch(`${context.runtimeUrl}${request.path}`, {
2972
+ selector: async (response) => {
2973
+ if (!response.ok) throw new Error(`Request failed: ${response.status}`);
2974
+ return null;
2975
+ },
2976
+ fetch: environment.fetch,
2977
+ method: request.method,
2978
+ headers: {
2979
+ ...context.headers,
2980
+ "Content-Type": "application/json"
2981
+ },
2982
+ body: JSON.stringify(request.body)
2983
+ }).pipe(map(() => threadRestEvents.mutationFinished({ outcome: {
2984
+ requestId: request.requestId,
2985
+ ok: true
2986
+ } })), catchError((error) => {
2987
+ return of(threadRestEvents.mutationFinished({ outcome: {
2988
+ requestId: request.requestId,
2989
+ ok: false,
2990
+ error: error instanceof Error ? error : new Error(String(error))
2991
+ } }));
2992
+ }));
2993
+ });
2994
+ }
2995
+ function createThreadStore(environment) {
2996
+ const store = createStore({
2997
+ reducer: threadReducer,
2998
+ effects: [
2999
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadAdapterEvents.contextChanged), withLatestFrom(state$), filter(([, state]) => Boolean(state.context)), map(([, state]) => threadRestEvents.listRequested({ sessionId: state.sessionId })))),
3000
+ 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) => ({
3001
+ action,
3002
+ context
3003
+ })), takeUntil(actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped))), switchMap$1(({ action: currentAction, context }) => createThreadFetchObservable(environment, context, currentAction.sessionId)))))),
3004
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.listSucceeded), withLatestFrom(state$), filter(([action, state]) => {
3005
+ return action.sessionId === state.sessionId && !state.metadataCredentialsRequested && Boolean(state.context?.wsUrl);
3006
+ }), map(([action]) => threadRestEvents.metadataCredentialsRequested({ sessionId: action.sessionId })))),
3007
+ 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) => ({
3008
+ action,
3009
+ context
3010
+ })), takeUntil(actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped))), switchMap$1(({ action: currentAction, context }) => createThreadMetadataCredentialsObservable(environment, context, currentAction.sessionId)))))),
3011
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadRestEvents.metadataCredentialsSucceeded), withLatestFrom(state$), filter(([action, state]) => {
3012
+ return action.sessionId === state.sessionId && Boolean(state.context?.wsUrl);
3013
+ }), switchMap$1(([action, state]) => {
3014
+ const context = state.context;
3015
+ const joinToken = action.joinToken;
3016
+ const shutdown$ = actions$.pipe(ofType(threadAdapterEvents.contextChanged, threadAdapterEvents.stopped));
3017
+ return defer(() => {
3018
+ const socket$ = ɵphoenixSocket$({
3019
+ url: context.wsUrl,
3020
+ options: {
3021
+ params: { join_token: joinToken },
3022
+ reconnectAfterMs: phoenixExponentialBackoff(100, 1e4),
3023
+ rejoinAfterMs: phoenixExponentialBackoff(1e3, 3e4)
3024
+ }
3025
+ }).pipe(shareReplay({
3026
+ bufferSize: 1,
3027
+ refCount: true
3028
+ }));
3029
+ const channel$ = ɵphoenixChannel$({
3030
+ socket$,
3031
+ topic: `user_meta:${context.userId}`
3032
+ }).pipe(shareReplay({
3033
+ bufferSize: 1,
3034
+ refCount: true
3035
+ }));
3036
+ const socketSignals$ = ɵobservePhoenixSocketSignals$(socket$).pipe(share());
3037
+ const fatalSocketShutdown$ = ɵobservePhoenixSocketHealth$(socketSignals$, MAX_SOCKET_RETRIES).pipe(catchError(() => {
3038
+ console.warn(`[threads] WebSocket failed after ${MAX_SOCKET_RETRIES} attempts, giving up`);
3039
+ return of(void 0);
3040
+ }), share());
3041
+ 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({
3042
+ sessionId: action.sessionId,
3043
+ payload
3044
+ }))), ɵ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$)));
3045
+ });
3046
+ }))),
3047
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadSocketEvents.metadataReceived), withLatestFrom(state$), filter(([action, state]) => {
3048
+ return action.sessionId === state.sessionId && action.payload.userId === state.context?.userId;
3049
+ }), map(([action]) => {
3050
+ if (action.payload.operation === "deleted") return threadDomainEvents.threadDeleted({
3051
+ sessionId: action.sessionId,
3052
+ threadId: action.payload.deleted.id
3053
+ });
3054
+ return threadDomainEvents.threadUpserted({
3055
+ sessionId: action.sessionId,
3056
+ thread: action.payload.thread
3057
+ });
3058
+ }))),
3059
+ createEffect((actions$, state$) => actions$.pipe(ofType(threadAdapterEvents.renameRequested, threadAdapterEvents.archiveRequested, threadAdapterEvents.deleteRequested), withLatestFrom(state$), mergeMap(([action, state]) => {
3060
+ const context = state.context;
3061
+ if (!context?.runtimeUrl) {
3062
+ const requestId = action.requestId;
3063
+ return of(threadRestEvents.mutationFinished({ outcome: {
3064
+ requestId,
3065
+ ok: false,
3066
+ error: /* @__PURE__ */ new Error("Runtime URL is not configured")
3067
+ } }));
2031
3068
  }
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 }
3069
+ const commonBody = {
3070
+ userId: context.userId,
3071
+ agentId: context.agentId
3072
+ };
3073
+ if (threadAdapterEvents.renameRequested.match(action)) return createThreadMutationObservable(environment, context, {
3074
+ requestId: action.requestId,
3075
+ method: "PATCH",
3076
+ path: `/threads/${encodeURIComponent(action.threadId)}`,
3077
+ body: {
3078
+ ...commonBody,
3079
+ name: action.name
3080
+ }
2042
3081
  });
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
- };
3082
+ if (threadAdapterEvents.archiveRequested.match(action)) return createThreadMutationObservable(environment, context, {
3083
+ requestId: action.requestId,
3084
+ method: "POST",
3085
+ path: `/threads/${encodeURIComponent(action.threadId)}/archive`,
3086
+ body: commonBody
3087
+ });
3088
+ return createThreadMutationObservable(environment, context, {
3089
+ requestId: action.requestId,
3090
+ method: "DELETE",
3091
+ path: `/threads/${encodeURIComponent(action.threadId)}`,
3092
+ body: commonBody
3093
+ });
3094
+ })))
3095
+ ]
3096
+ });
3097
+ function trackMutation(dispatchAction) {
3098
+ 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(() => ({
3099
+ requestId: dispatchAction.requestId,
3100
+ ok: false,
3101
+ error: /* @__PURE__ */ new Error("Thread store stopped before mutation completed")
3102
+ })))).pipe(take(1))).then((outcome) => {
3103
+ if (outcome.ok) return;
3104
+ throw outcome.error;
2053
3105
  });
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
- };
3106
+ store.dispatch(dispatchAction);
3107
+ return resultPromise;
3108
+ }
3109
+ return {
3110
+ start() {
3111
+ store.init();
3112
+ store.dispatch(threadAdapterEvents.started());
3113
+ },
3114
+ stop() {
3115
+ store.dispatch(threadAdapterEvents.stopped());
3116
+ store.stop();
3117
+ },
3118
+ setContext(context) {
3119
+ store.dispatch(threadAdapterEvents.contextChanged({ context }));
3120
+ },
3121
+ renameThread(threadId, name) {
3122
+ return trackMutation(threadAdapterEvents.renameRequested({
3123
+ requestId: createThreadRequestId(),
3124
+ threadId,
3125
+ name
3126
+ }));
3127
+ },
3128
+ archiveThread(threadId) {
3129
+ return trackMutation(threadAdapterEvents.archiveRequested({
3130
+ requestId: createThreadRequestId(),
3131
+ threadId
3132
+ }));
3133
+ },
3134
+ deleteThread(threadId) {
3135
+ return trackMutation(threadAdapterEvents.deleteRequested({
3136
+ requestId: createThreadRequestId(),
3137
+ threadId
3138
+ }));
3139
+ },
3140
+ getState() {
3141
+ return store.getState();
3142
+ },
3143
+ select: store.select.bind(store)
3144
+ };
3145
+ }
3146
+ const ɵthreadAdapterEvents = threadAdapterEvents;
3147
+ const ɵselectThreads = selectThreads;
3148
+ const ɵselectThreadsIsLoading = selectThreadsIsLoading;
3149
+ const ɵselectThreadsError = selectThreadsError;
2066
3150
 
2067
3151
  //#endregion
2068
- export { AgentRegistry, ContextStore, CopilotKitCore, CopilotKitCoreErrorCode, CopilotKitCoreRuntimeConnectionStatus, IntelligenceAgent, ProxiedCopilotRuntimeAgent, RunHandler, StateManager, SuggestionEngine, ToolCallStatus, completePartialMarkdown, ensureObjectArgs };
3152
+ export { AgentRegistry, ContextStore, CopilotKitCore, CopilotKitCoreErrorCode, CopilotKitCoreRuntimeConnectionStatus, IntelligenceAgent, ProxiedCopilotRuntimeAgent, RunHandler, StateManager, SuggestionEngine, ToolCallStatus, completePartialMarkdown, createActionGroup, createEffect, createReducer, createSelector, createStore, empty, ensureObjectArgs, ofType, on, props, select, createThreadStore as ɵcreateThreadStore, ɵjoinPhoenixChannel$, ɵobservePhoenixEvent$, ɵobservePhoenixJoinOutcome$, ɵobservePhoenixSocketHealth$, ɵobservePhoenixSocketSignals$, ɵphoenixChannel$, ɵphoenixSocket$, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading, ɵthreadAdapterEvents };
2069
3153
  //# sourceMappingURL=index.mjs.map