@copilotkit/core 1.74.0 → 1.75.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
@@ -2,7 +2,7 @@ import { Throttler } from "@tanstack/pacer";
2
2
  import { AGUIConnectNotImplementedError, AbstractAgent, EventType, HttpAgent, buildResumeArray, isInterruptExpired, randomUUID, runHttpRequest, structuredClone_, transformChunks, transformHttpEventStream } from "@ag-ui/client";
3
3
  import { RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE, logger, parseInspectorMetadataV1, partialJSONParse, phoenixExponentialBackoff, randomUUID as randomUUID$1, resolveDebugConfig, schemaToJsonSchema } from "@copilotkit/shared";
4
4
  import { BehaviorSubject, EMPTY, NEVER, Notification, Observable, Subject, Subscription, asapScheduler, combineLatest, concat, defer, dematerialize, firstValueFrom, from, lastValueFrom, merge, observeOn, of, switchMap, throwError } from "rxjs";
5
- import { catchError, concatWith, delay, distinctUntilChanged, endWith, filter, finalize, ignoreElements, map, mergeMap, observeOn as observeOn$1, scan, share, shareReplay, switchMap as switchMap$1, take, takeUntil, tap, timeout, withLatestFrom } from "rxjs/operators";
5
+ import { catchError, concatWith, delay, distinctUntilChanged, endWith, filter, finalize, ignoreElements, map, mergeMap, observeOn as observeOn$1, scan, share, shareReplay, startWith, switchMap as switchMap$1, take, takeUntil, tap, timeout, withLatestFrom } from "rxjs/operators";
6
6
  import { Socket } from "phoenix";
7
7
  import { zodToJsonSchema } from "zod-to-json-schema";
8
8
 
@@ -24,20 +24,23 @@ import { zodToJsonSchema } from "zod-to-json-schema";
24
24
  *
25
25
  * `transformChunks` is still applied — message reassembly is needed either way.
26
26
  *
27
- * This mirrors the base `AbstractAgent.connectAgent` implementation exactly
28
- * apart from that omission, so callers keep the same subscriber notifications,
29
- * detach semantics, and `{ result, newMessages }` return shape.
27
+ * Connection-local replay hooks track the phase before applying events. An
28
+ * explicitly live RUN_ERROR clears busy without closing the connection;
29
+ * historical errors remain data. Connections without hooks retain the existing
30
+ * completion behavior. Subscriber, detach, and result contracts are preserved.
30
31
  *
31
32
  * TODO: Remove this in favour of the base implementation once AG-UI's
32
33
  * AbstractAgent supports opting out of `verifyEvents` for transports whose
33
- * connection life-cycle isn't a single run. As of `@ag-ui/client@0.0.57`
34
+ * connection life-cycle isn't a single run AND preserves the connection-local
35
+ * replay lifecycle and running-state behavior below. Skipping verification alone
36
+ * is insufficient. As of `@ag-ui/client@0.0.57`
34
37
  * `connectAgent(parameters?, subscriber?)` takes no such option.
35
38
  *
36
39
  * @param agent - The agent whose `connect()` stream should be consumed.
37
40
  * @param parameters - Run parameters, forwarded to `prepareRunAgentInput`.
38
41
  * @param subscriber - Optional one-shot subscriber for this connect call.
39
42
  */
40
- async function ɵconnectWithoutEventVerification(agent, parameters, subscriber) {
43
+ async function ɵconnectWithoutEventVerification(agent, parameters, subscriber, connect) {
41
44
  const self = agent;
42
45
  try {
43
46
  agent.isRunning = true;
@@ -45,10 +48,20 @@ async function ɵconnectWithoutEventVerification(agent, parameters, subscriber)
45
48
  const input = self.prepareRunAgentInput(parameters);
46
49
  let result;
47
50
  const previousMessageIds = new Set(agent.messages.map((m) => m.id));
51
+ let isReplaying = true;
52
+ const liveErrors = /* @__PURE__ */ new WeakSet();
48
53
  const subscribers = [
49
- { onRunFinishedEvent: (event) => {
50
- if (event.outcome === "success") result = event.result;
51
- } },
54
+ {
55
+ onRunStartedEvent: () => {
56
+ agent.isRunning = true;
57
+ },
58
+ onRunErrorEvent: ({ event }) => {
59
+ if (liveErrors.has(event)) agent.isRunning = false;
60
+ },
61
+ onRunFinishedEvent: (event) => {
62
+ if (event.outcome === "success") result = event.result;
63
+ }
64
+ },
52
65
  ...agent.subscribers,
53
66
  subscriber ?? {}
54
67
  ];
@@ -58,7 +71,18 @@ async function ɵconnectWithoutEventVerification(agent, parameters, subscriber)
58
71
  self.activeRunCompletionPromise = new Promise((resolve) => {
59
72
  resolveCompletion = resolve;
60
73
  });
61
- const source$ = defer(() => self.connect(input)).pipe(transformChunks(self.debugLogger), takeUntil(self.activeRunDetach$));
74
+ const lifecycle = {
75
+ onReplayStarted: () => {
76
+ isReplaying = true;
77
+ agent.isRunning = true;
78
+ },
79
+ onReplayFinished: () => {
80
+ isReplaying = false;
81
+ }
82
+ };
83
+ const source$ = defer(() => connect ? connect(input, lifecycle) : self.connect(input)).pipe(tap((event) => {
84
+ if (!isReplaying && event.type === EventType.RUN_ERROR) liveErrors.add(event);
85
+ }), transformChunks(self.debugLogger), takeUntil(self.activeRunDetach$));
62
86
  const applied$ = self.apply(input, source$, subscribers);
63
87
  await lastValueFrom(self.processApplyEvents(input, applied$, subscribers).pipe(catchError((error) => {
64
88
  agent.isRunning = false;
@@ -205,9 +229,11 @@ function ɵobservePhoenixSocketHealth$(socketSignals$, maxConsecutiveErrors) {
205
229
  const globalFetch = (...args) => fetch(...args);
206
230
  const CLIENT_AG_UI_EVENT = "ag_ui_event";
207
231
  const REPLAY_COMPLETE_EVENT = "replay_complete";
232
+ const REPLAY_FAILED_EVENT = "replay_failed";
208
233
  const STREAM_IDLE_EVENT = "stream_idle";
209
234
  const STOP_RUN_EVENT = "stop_run";
210
235
  const CONNECT_STREAM_IDLE_REPLAY_FALLBACK_MS = 100;
236
+ const CONNECT_IDLE_WARNING_MS = 3e4;
211
237
  const MAX_UNOPENED_CREDENTIAL_REFRESHES = 2;
212
238
  var AgentThreadLockedError = class extends Error {
213
239
  constructor(threadId) {
@@ -295,7 +321,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
295
321
  ...parameters,
296
322
  runId: this.canonicalRunId
297
323
  };
298
- return ɵconnectWithoutEventVerification(this, effectiveParameters, subscriber);
324
+ return ɵconnectWithoutEventVerification(this, effectiveParameters, subscriber, (input, lifecycle) => this.connect(input, lifecycle));
299
325
  }
300
326
  abortRun() {
301
327
  if (this.activeChannel && this.canonicalRunId) {
@@ -344,17 +370,22 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
344
370
  * gateway only streams events past it instead of replaying the
345
371
  * entire history every time the chat re-opens a socket.
346
372
  */
347
- connect(input) {
373
+ connect(input, lifecycle) {
348
374
  this.threadId = input.threadId;
349
375
  this.canonicalRunId = null;
350
376
  const replayCursor = this.getReconnectCursor(input);
351
377
  return defer(() => this.requestJoinCredentials$("connect", input, replayCursor)).pipe(switchMap((credentials) => {
352
- if (credentials === null) return EMPTY;
378
+ if (credentials === null) {
379
+ lifecycle?.onReplayStarted?.();
380
+ lifecycle?.onReplayFinished?.();
381
+ return EMPTY;
382
+ }
353
383
  const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: false });
354
384
  return this.observeThread$(canonicalInput, credentials, {
355
385
  completeOnRunError: false,
356
386
  streamMode: "connect",
357
- replayCursor
387
+ replayCursor,
388
+ lifecycle
358
389
  });
359
390
  }));
360
391
  }
@@ -454,8 +485,24 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
454
485
  }
455
486
  observeThreadSession$(input, credentials, options) {
456
487
  return defer(() => {
488
+ options.lifecycle?.onReplayStarted?.();
457
489
  let ownSocket = null;
458
490
  let ownChannel = null;
491
+ let replayFinished = false;
492
+ let warnedAboutIdle = false;
493
+ let idleWarning;
494
+ const clearIdleWarning = () => {
495
+ clearTimeout(idleWarning);
496
+ idleWarning = void 0;
497
+ };
498
+ const scheduleIdleWarning = () => {
499
+ clearIdleWarning();
500
+ if (options.streamMode !== "connect" || !replayFinished || warnedAboutIdle) return;
501
+ idleWarning = setTimeout(() => {
502
+ warnedAboutIdle = true;
503
+ this.debugLogger?.lifecycle("INTELLIGENCE", "No progress or stream_idle for 30 seconds after replay_complete; the run may still be active or its status unavailable.", { threadId: input.threadId });
504
+ }, CONNECT_IDLE_WARNING_MS);
505
+ };
459
506
  const socket$ = ɵphoenixSocket$({
460
507
  url: credentials.realtime.clientUrl,
461
508
  options: {
@@ -489,19 +536,42 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
489
536
  let latestObservedReplayCursor = null;
490
537
  const threadEvents$ = this.observeThreadEvents$(input.threadId, channel$, options).pipe(tap((payload) => {
491
538
  latestObservedReplayCursor = this.readEventId(payload) ?? latestObservedReplayCursor;
539
+ scheduleIdleWarning();
492
540
  }), share());
493
- const replayComplete$ = this.observeControlEvent$(input.threadId, channel$, REPLAY_COMPLETE_EVENT).pipe(shareReplay({
494
- bufferSize: 1,
495
- refCount: true
541
+ const controlCursor = {
542
+ applied: null,
543
+ replayFailed: false
544
+ };
545
+ const replayFailed$ = channel$.pipe(switchMap$1(({ channel }) => this.observeChannelEvent$(channel, REPLAY_FAILED_EVENT)), tap(() => {
546
+ controlCursor.replayFailed = true;
496
547
  }));
497
- const streamIdle$ = this.observeControlEvent$(input.threadId, channel$, STREAM_IDLE_EVENT).pipe(shareReplay({
548
+ const replayComplete$ = this.observeControlEvent$(input.threadId, channel$, REPLAY_COMPLETE_EVENT, controlCursor).pipe(tap(() => {
549
+ replayFinished = true;
550
+ scheduleIdleWarning();
551
+ options.lifecycle?.onReplayFinished?.();
552
+ }), share());
553
+ const streamIdle$ = this.observeControlEvent$(input.threadId, channel$, STREAM_IDLE_EVENT, controlCursor).pipe(share());
554
+ const replayRestart$ = merge(this.observeControlEvent$(input.threadId, channel$, "phx_error", controlCursor), ɵobservePhoenixSocketSignals$(socket$).pipe(filter((signal) => signal.type === "error"))).pipe(tap(() => {
555
+ replayFinished = false;
556
+ warnedAboutIdle = false;
557
+ clearIdleWarning();
558
+ options.lifecycle?.onReplayStarted?.();
559
+ }), share());
560
+ const streamIdleCompletion$ = options.streamMode === "connect" ? replayRestart$.pipe(startWith(null), switchMap(() => merge(combineLatest([replayComplete$.pipe(take(1)), streamIdle$.pipe(take(1))]), streamIdle$.pipe(take(1), filter((payload) => this.canFallbackCompleteConnect(payload, reconnectCursor, latestObservedReplayCursor)), delay(CONNECT_STREAM_IDLE_REPLAY_FALLBACK_MS), tap(() => {
561
+ this.debugLogger?.lifecycle("INTELLIGENCE", "Completing connection on stream_idle fallback without replay_complete.", {
562
+ threadId: input.threadId,
563
+ fallbackMs: CONNECT_STREAM_IDLE_REPLAY_FALLBACK_MS
564
+ });
565
+ })))), take(1), shareReplay({
498
566
  bufferSize: 1,
499
567
  refCount: true
500
- }));
501
- const streamIdleCompletion$ = options.streamMode === "connect" ? merge(combineLatest([replayComplete$.pipe(take(1)), streamIdle$.pipe(take(1))]), streamIdle$.pipe(take(1), filter((payload) => this.canFallbackCompleteConnect(payload, reconnectCursor, latestObservedReplayCursor)), delay(CONNECT_STREAM_IDLE_REPLAY_FALLBACK_MS))).pipe(take(1)) : EMPTY;
568
+ })) : EMPTY;
502
569
  const threadCompleted$ = threadEvents$.pipe(ignoreElements(), endWith(null), take(1));
503
570
  const terminal$ = merge(threadCompleted$, streamIdleCompletion$);
504
- return merge(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$, options.onSocketOpen).pipe(takeUntil(terminal$)), threadEvents$.pipe(takeUntil(streamIdleCompletion$)), replayComplete$.pipe(ignoreElements(), takeUntil(terminal$)), streamIdleCompletion$.pipe(ignoreElements(), takeUntil(threadCompleted$))).pipe(finalize(() => this.cleanupOwned(ownChannel, ownSocket)));
571
+ return merge(replayFailed$.pipe(ignoreElements(), takeUntil(terminal$)), replayComplete$.pipe(ignoreElements(), takeUntil(terminal$)), this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$, options.onSocketOpen).pipe(takeUntil(terminal$)), threadEvents$.pipe(takeUntil(streamIdleCompletion$)), streamIdleCompletion$.pipe(ignoreElements(), takeUntil(threadCompleted$))).pipe(finalize(() => {
572
+ clearIdleWarning();
573
+ this.cleanupOwned(ownChannel, ownSocket);
574
+ }));
505
575
  });
506
576
  }
507
577
  joinThreadChannel$(channel$) {
@@ -521,8 +591,8 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
521
591
  errorOnRunError: options.streamMode === "run"
522
592
  })), dematerialize());
523
593
  }
524
- observeControlEvent$(threadId, channel$, eventName) {
525
- return channel$.pipe(switchMap$1(({ channel }) => this.observeChannelEvent$(channel, eventName)), tap((payload) => this.updateLastSeenEventIdFromControl(threadId, payload)));
594
+ observeControlEvent$(threadId, channel$, eventName, controlCursor) {
595
+ return channel$.pipe(switchMap$1(({ channel }) => this.observeChannelEvent$(channel, eventName)), tap((payload) => this.updateLastSeenEventIdFromControl(threadId, payload, controlCursor)));
526
596
  }
527
597
  observeChannelEvent$(channel, eventName) {
528
598
  return ɵobservePhoenixEvent$(channel, eventName);
@@ -571,9 +641,11 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
571
641
  if (!eventId) return;
572
642
  this.advanceLastSeenEventId(threadId, eventId);
573
643
  }
574
- updateLastSeenEventIdFromControl(threadId, payload) {
644
+ updateLastSeenEventIdFromControl(threadId, payload, controlCursor) {
575
645
  const eventId = this.readControlEventId(payload);
576
646
  if (!eventId) return;
647
+ if (eventId === controlCursor.applied && !controlCursor.replayFailed) return;
648
+ controlCursor.applied = eventId;
577
649
  this.advanceLastSeenEventId(threadId, eventId);
578
650
  }
579
651
  advanceLastSeenEventId(threadId, eventId) {
@@ -981,7 +1053,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
981
1053
  });
982
1054
  }
983
1055
  async connectAgent(parameters, subscriber) {
984
- if (this.runtimeMode !== RUNTIME_MODE_INTELLIGENCE) return ɵconnectWithoutEventVerification(this, parameters, subscriber);
1056
+ if (this.runtimeMode !== RUNTIME_MODE_INTELLIGENCE) return ɵconnectWithoutEventVerification(this, parameters, subscriber, (input, lifecycle) => this.connect(input, lifecycle));
985
1057
  if (this.delegate) await this.delegate.detachActiveRun();
986
1058
  await this.resolveDelegate();
987
1059
  const delegate = this.delegate;
@@ -1002,7 +1074,10 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1002
1074
  this.isRunning = false;
1003
1075
  },
1004
1076
  onRunErrorEvent: () => {
1005
- this.isRunning = false;
1077
+ this.isRunning = delegate.isRunning;
1078
+ },
1079
+ onRunStartedEvent: () => {
1080
+ this.isRunning = delegate.isRunning;
1006
1081
  }
1007
1082
  });
1008
1083
  const forwardedSubs = this.subscribers.map((s) => delegate.subscribe(s));
@@ -1017,9 +1092,9 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1017
1092
  for (const sub of forwardedSubs) sub.unsubscribe();
1018
1093
  }
1019
1094
  }
1020
- connect(input) {
1021
- if (this.runtimeMode === "pending" || this.transport === "auto" && this.runtimeMode !== RUNTIME_MODE_INTELLIGENCE) return defer(() => from(this.ensureRuntimeConfiguration())).pipe(switchMap$1(() => this.connect(input)));
1022
- if (this.runtimeMode === RUNTIME_MODE_INTELLIGENCE) return this.#connectViaDelegate(input);
1095
+ connect(input, lifecycle) {
1096
+ if (this.runtimeMode === "pending" || this.transport === "auto" && this.runtimeMode !== RUNTIME_MODE_INTELLIGENCE) return defer(() => from(this.ensureRuntimeConfiguration())).pipe(switchMap$1(() => this.connect(input, lifecycle)));
1097
+ if (this.runtimeMode === RUNTIME_MODE_INTELLIGENCE) return this.#connectViaDelegate(input, lifecycle);
1023
1098
  return this.#connectViaHttp(input);
1024
1099
  }
1025
1100
  run(input) {
@@ -1027,8 +1102,8 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1027
1102
  if (this.runtimeMode === RUNTIME_MODE_INTELLIGENCE) return this.#runViaDelegate(input);
1028
1103
  return this.#runViaHttp(input);
1029
1104
  }
1030
- #connectViaDelegate(input) {
1031
- return defer(() => from(this.resolveDelegate())).pipe(switchMap$1((delegate) => withAbortErrorHandling(delegate.connect(input))));
1105
+ #connectViaDelegate(input, lifecycle) {
1106
+ return defer(() => from(this.resolveDelegate())).pipe(switchMap$1((delegate) => withAbortErrorHandling(delegate.connect(input, lifecycle))));
1032
1107
  }
1033
1108
  #connectViaHttp(unfiltered) {
1034
1109
  const input = this.#applyMessageFilter(unfiltered);
@@ -1222,6 +1297,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1222
1297
  delegate.agentId = this.routedAgentId();
1223
1298
  delegate.description = this.description;
1224
1299
  delegate.threadId = this.threadId;
1300
+ delegate.debugLogger = this.debugLogger;
1225
1301
  delegate.setMessages(this.messages);
1226
1302
  delegate.setState(this.state);
1227
1303
  if (hasHeaders(delegate)) delegate.headers = { ...this.headers };
@@ -5393,6 +5469,7 @@ const ALLOWED_KEYS = new Set([
5393
5469
  "onMessagesChanged",
5394
5470
  "onStateChanged",
5395
5471
  "onRunInitialized",
5472
+ "onRunStartedEvent",
5396
5473
  "onRunFinalized",
5397
5474
  "onRunFailed",
5398
5475
  "onRunErrorEvent"
@@ -5994,6 +6071,10 @@ var CopilotKitCore = class {
5994
6071
  const fn = sub.onRunInitialized;
5995
6072
  guarded.onRunInitialized = (params) => safeCall("onRunInitialized", fn, params);
5996
6073
  }
6074
+ if (sub.onRunStartedEvent) {
6075
+ const fn = sub.onRunStartedEvent;
6076
+ guarded.onRunStartedEvent = (params) => safeCall("onRunStartedEvent", fn, params);
6077
+ }
5997
6078
  if (sub.onRunFinalized) {
5998
6079
  const fn = sub.onRunFinalized;
5999
6080
  guarded.onRunFinalized = (params) => safeCall("onRunFinalized", fn, params);
@@ -6045,6 +6126,7 @@ var CopilotKitCore = class {
6045
6126
  });
6046
6127
  const lifecycleOnly = {};
6047
6128
  if (subscriber.onRunInitialized) lifecycleOnly.onRunInitialized = subscriber.onRunInitialized;
6129
+ if (subscriber.onRunStartedEvent) lifecycleOnly.onRunStartedEvent = subscriber.onRunStartedEvent;
6048
6130
  if (subscriber.onRunFinalized) lifecycleOnly.onRunFinalized = subscriber.onRunFinalized;
6049
6131
  if (subscriber.onRunFailed) lifecycleOnly.onRunFailed = subscriber.onRunFailed;
6050
6132
  if (subscriber.onRunErrorEvent) lifecycleOnly.onRunErrorEvent = subscriber.onRunErrorEvent;