@copilotkit/core 1.75.0 → 1.75.2

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;
@@ -209,6 +233,7 @@ const REPLAY_FAILED_EVENT = "replay_failed";
209
233
  const STREAM_IDLE_EVENT = "stream_idle";
210
234
  const STOP_RUN_EVENT = "stop_run";
211
235
  const CONNECT_STREAM_IDLE_REPLAY_FALLBACK_MS = 100;
236
+ const CONNECT_IDLE_WARNING_MS = 3e4;
212
237
  const MAX_UNOPENED_CREDENTIAL_REFRESHES = 2;
213
238
  var AgentThreadLockedError = class extends Error {
214
239
  constructor(threadId) {
@@ -296,7 +321,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
296
321
  ...parameters,
297
322
  runId: this.canonicalRunId
298
323
  };
299
- return ɵconnectWithoutEventVerification(this, effectiveParameters, subscriber);
324
+ return ɵconnectWithoutEventVerification(this, effectiveParameters, subscriber, (input, lifecycle) => this.connect(input, lifecycle));
300
325
  }
301
326
  abortRun() {
302
327
  if (this.activeChannel && this.canonicalRunId) {
@@ -345,17 +370,22 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
345
370
  * gateway only streams events past it instead of replaying the
346
371
  * entire history every time the chat re-opens a socket.
347
372
  */
348
- connect(input) {
373
+ connect(input, lifecycle) {
349
374
  this.threadId = input.threadId;
350
375
  this.canonicalRunId = null;
351
376
  const replayCursor = this.getReconnectCursor(input);
352
377
  return defer(() => this.requestJoinCredentials$("connect", input, replayCursor)).pipe(switchMap((credentials) => {
353
- if (credentials === null) return EMPTY;
378
+ if (credentials === null) {
379
+ lifecycle?.onReplayStarted?.();
380
+ lifecycle?.onReplayFinished?.();
381
+ return EMPTY;
382
+ }
354
383
  const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: false });
355
384
  return this.observeThread$(canonicalInput, credentials, {
356
385
  completeOnRunError: false,
357
386
  streamMode: "connect",
358
- replayCursor
387
+ replayCursor,
388
+ lifecycle
359
389
  });
360
390
  }));
361
391
  }
@@ -455,8 +485,24 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
455
485
  }
456
486
  observeThreadSession$(input, credentials, options) {
457
487
  return defer(() => {
488
+ options.lifecycle?.onReplayStarted?.();
458
489
  let ownSocket = null;
459
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
+ };
460
506
  const socket$ = ɵphoenixSocket$({
461
507
  url: credentials.realtime.clientUrl,
462
508
  options: {
@@ -490,6 +536,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
490
536
  let latestObservedReplayCursor = null;
491
537
  const threadEvents$ = this.observeThreadEvents$(input.threadId, channel$, options).pipe(tap((payload) => {
492
538
  latestObservedReplayCursor = this.readEventId(payload) ?? latestObservedReplayCursor;
539
+ scheduleIdleWarning();
493
540
  }), share());
494
541
  const controlCursor = {
495
542
  applied: null,
@@ -498,18 +545,33 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
498
545
  const replayFailed$ = channel$.pipe(switchMap$1(({ channel }) => this.observeChannelEvent$(channel, REPLAY_FAILED_EVENT)), tap(() => {
499
546
  controlCursor.replayFailed = true;
500
547
  }));
501
- const replayComplete$ = this.observeControlEvent$(input.threadId, channel$, REPLAY_COMPLETE_EVENT, controlCursor).pipe(shareReplay({
502
- bufferSize: 1,
503
- refCount: true
504
- }));
505
- const streamIdle$ = this.observeControlEvent$(input.threadId, channel$, STREAM_IDLE_EVENT, controlCursor).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({
506
566
  bufferSize: 1,
507
567
  refCount: true
508
- }));
509
- 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;
510
569
  const threadCompleted$ = threadEvents$.pipe(ignoreElements(), endWith(null), take(1));
511
570
  const terminal$ = merge(threadCompleted$, streamIdleCompletion$);
512
- return merge(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$, options.onSocketOpen).pipe(takeUntil(terminal$)), threadEvents$.pipe(takeUntil(streamIdleCompletion$)), replayFailed$.pipe(ignoreElements(), takeUntil(terminal$)), 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
+ }));
513
575
  });
514
576
  }
515
577
  joinThreadChannel$(channel$) {
@@ -991,7 +1053,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
991
1053
  });
992
1054
  }
993
1055
  async connectAgent(parameters, subscriber) {
994
- 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));
995
1057
  if (this.delegate) await this.delegate.detachActiveRun();
996
1058
  await this.resolveDelegate();
997
1059
  const delegate = this.delegate;
@@ -1012,7 +1074,10 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1012
1074
  this.isRunning = false;
1013
1075
  },
1014
1076
  onRunErrorEvent: () => {
1015
- this.isRunning = false;
1077
+ this.isRunning = delegate.isRunning;
1078
+ },
1079
+ onRunStartedEvent: () => {
1080
+ this.isRunning = delegate.isRunning;
1016
1081
  }
1017
1082
  });
1018
1083
  const forwardedSubs = this.subscribers.map((s) => delegate.subscribe(s));
@@ -1027,9 +1092,9 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1027
1092
  for (const sub of forwardedSubs) sub.unsubscribe();
1028
1093
  }
1029
1094
  }
1030
- connect(input) {
1031
- if (this.runtimeMode === "pending" || this.transport === "auto" && this.runtimeMode !== RUNTIME_MODE_INTELLIGENCE) return defer(() => from(this.ensureRuntimeConfiguration())).pipe(switchMap$1(() => this.connect(input)));
1032
- 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);
1033
1098
  return this.#connectViaHttp(input);
1034
1099
  }
1035
1100
  run(input) {
@@ -1037,8 +1102,8 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1037
1102
  if (this.runtimeMode === RUNTIME_MODE_INTELLIGENCE) return this.#runViaDelegate(input);
1038
1103
  return this.#runViaHttp(input);
1039
1104
  }
1040
- #connectViaDelegate(input) {
1041
- 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))));
1042
1107
  }
1043
1108
  #connectViaHttp(unfiltered) {
1044
1109
  const input = this.#applyMessageFilter(unfiltered);
@@ -1232,6 +1297,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
1232
1297
  delegate.agentId = this.routedAgentId();
1233
1298
  delegate.description = this.description;
1234
1299
  delegate.threadId = this.threadId;
1300
+ delegate.debugLogger = this.debugLogger;
1235
1301
  delegate.setMessages(this.messages);
1236
1302
  delegate.setState(this.state);
1237
1303
  if (hasHeaders(delegate)) delegate.headers = { ...this.headers };
@@ -5403,6 +5469,7 @@ const ALLOWED_KEYS = new Set([
5403
5469
  "onMessagesChanged",
5404
5470
  "onStateChanged",
5405
5471
  "onRunInitialized",
5472
+ "onRunStartedEvent",
5406
5473
  "onRunFinalized",
5407
5474
  "onRunFailed",
5408
5475
  "onRunErrorEvent"
@@ -6004,6 +6071,10 @@ var CopilotKitCore = class {
6004
6071
  const fn = sub.onRunInitialized;
6005
6072
  guarded.onRunInitialized = (params) => safeCall("onRunInitialized", fn, params);
6006
6073
  }
6074
+ if (sub.onRunStartedEvent) {
6075
+ const fn = sub.onRunStartedEvent;
6076
+ guarded.onRunStartedEvent = (params) => safeCall("onRunStartedEvent", fn, params);
6077
+ }
6007
6078
  if (sub.onRunFinalized) {
6008
6079
  const fn = sub.onRunFinalized;
6009
6080
  guarded.onRunFinalized = (params) => safeCall("onRunFinalized", fn, params);
@@ -6055,6 +6126,7 @@ var CopilotKitCore = class {
6055
6126
  });
6056
6127
  const lifecycleOnly = {};
6057
6128
  if (subscriber.onRunInitialized) lifecycleOnly.onRunInitialized = subscriber.onRunInitialized;
6129
+ if (subscriber.onRunStartedEvent) lifecycleOnly.onRunStartedEvent = subscriber.onRunStartedEvent;
6058
6130
  if (subscriber.onRunFinalized) lifecycleOnly.onRunFinalized = subscriber.onRunFinalized;
6059
6131
  if (subscriber.onRunFailed) lifecycleOnly.onRunFailed = subscriber.onRunFailed;
6060
6132
  if (subscriber.onRunErrorEvent) lifecycleOnly.onRunErrorEvent = subscriber.onRunErrorEvent;