@copilotkit/core 1.56.2 → 1.56.4-canary.1777529757

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.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function(global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@ag-ui/client'), require('@copilotkit/shared'), require('rxjs'), require('rxjs/operators'), require('phoenix'), require('zod-to-json-schema'), require('rxjs/fetch')) :
3
- typeof define === 'function' && define.amd ? define(['exports', '@ag-ui/client', '@copilotkit/shared', 'rxjs', 'rxjs/operators', 'phoenix', 'zod-to-json-schema', 'rxjs/fetch'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitCore = {}), global.AgUIClient,global.CopilotKitShared,global.rxjs,global.rxjs.operators,global.phoenix,global.zodToJsonSchema,global.rxjs_fetch));
5
- })(this, function(exports, _ag_ui_client, _copilotkit_shared, rxjs, rxjs_operators, phoenix, zod_to_json_schema, rxjs_fetch) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@tanstack/pacer'), require('@ag-ui/client'), require('@copilotkit/shared'), require('rxjs'), require('rxjs/operators'), require('phoenix'), require('zod-to-json-schema'), require('rxjs/fetch')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', '@tanstack/pacer', '@ag-ui/client', '@copilotkit/shared', 'rxjs', 'rxjs/operators', 'phoenix', 'zod-to-json-schema', 'rxjs/fetch'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitCore = {}), global._tanstack_pacer,global.AgUIClient,global.CopilotKitShared,global.rxjs,global.rxjs.operators,global.phoenix,global.zodToJsonSchema,global.rxjs_fetch));
5
+ })(this, function(exports, _tanstack_pacer, _ag_ui_client, _copilotkit_shared, rxjs, rxjs_operators, phoenix, zod_to_json_schema, rxjs_fetch) {
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
7
7
 
8
8
  //#region src/utils/phoenix-observable.ts
@@ -167,6 +167,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
167
167
  //#endregion
168
168
  //#region src/intelligence-agent.ts
169
169
  const CLIENT_AG_UI_EVENT = "ag_ui_event";
170
+ const REPLAY_COMPLETE_EVENT = "replay_complete";
171
+ const STREAM_IDLE_EVENT = "stream_idle";
170
172
  const STOP_RUN_EVENT = "stop_run";
171
173
  var AgentThreadLockedError = class extends Error {
172
174
  constructor(threadId) {
@@ -180,7 +182,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
180
182
  _defineProperty(this, "config", void 0);
181
183
  _defineProperty(this, "socket", null);
182
184
  _defineProperty(this, "activeChannel", null);
183
- _defineProperty(this, "runId", null);
185
+ _defineProperty(this, "canonicalRunId", null);
184
186
  _defineProperty(this, "sharedState", void 0);
185
187
  this.config = config;
186
188
  this.sharedState = sharedState;
@@ -217,7 +219,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
217
219
  var _this$agentId;
218
220
  this.isRunning = true;
219
221
  this.agentId = (_this$agentId = this.agentId) !== null && _this$agentId !== void 0 ? _this$agentId : (0, _ag_ui_client.randomUUID)();
220
- const input = this.prepareRunAgentInput(parameters);
222
+ const effectiveParameters = (parameters === null || parameters === void 0 ? void 0 : parameters.runId) || !this.canonicalRunId ? parameters : {
223
+ ...parameters,
224
+ runId: this.canonicalRunId
225
+ };
226
+ const input = this.prepareRunAgentInput(effectiveParameters);
221
227
  let result;
222
228
  const previousMessageIds = new Set(this.messages.map((m) => m.id));
223
229
  const subscribers = [
@@ -256,14 +262,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
256
262
  }
257
263
  }
258
264
  abortRun() {
259
- if (this.activeChannel && this.runId) {
265
+ if (this.activeChannel && this.canonicalRunId) {
260
266
  const fallback = setTimeout(() => clear(), 5e3);
261
267
  const clear = () => {
262
268
  clearTimeout(fallback);
263
269
  this.detachActiveRun();
264
270
  this.cleanup();
265
271
  };
266
- this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.runId }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
272
+ this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.canonicalRunId }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
267
273
  } else {
268
274
  this.detachActiveRun();
269
275
  this.cleanup();
@@ -275,11 +281,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
275
281
  */
276
282
  run(input) {
277
283
  this.threadId = input.threadId;
278
- this.runId = input.runId;
279
- return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => this.observeThread$(input, credentials, {
280
- completeOnRunError: false,
281
- streamMode: "run"
282
- })));
284
+ this.canonicalRunId = input.runId;
285
+ return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => {
286
+ if (credentials === null) return (0, rxjs.throwError)(() => /* @__PURE__ */ new Error("REST run request returned no credentials"));
287
+ const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: true });
288
+ return this.observeThread$(canonicalInput, credentials, {
289
+ completeOnRunError: false,
290
+ streamMode: "run"
291
+ });
292
+ }));
283
293
  }
284
294
  /**
285
295
  * Reconnect to an existing thread by fetching websocket credentials and
@@ -287,21 +297,15 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
287
297
  */
288
298
  connect(input) {
289
299
  this.threadId = input.threadId;
290
- this.runId = input.runId;
291
- return (0, rxjs.defer)(() => this.requestConnectPlan$(input)).pipe((0, rxjs.switchMap)((plan) => {
292
- if (plan === null) return rxjs.EMPTY;
293
- if (plan.mode === "bootstrap") {
294
- this.setLastSeenEventId(input.threadId, plan.latestEventId);
295
- for (const event of plan.events) this.updateRunIdFromEvent(event);
296
- return (0, rxjs.from)(plan.events);
297
- }
298
- this.setLastSeenEventId(input.threadId, plan.joinFromEventId);
299
- for (const event of plan.events) this.updateRunIdFromEvent(event);
300
- return (0, rxjs.concat)((0, rxjs.from)(plan.events), this.observeThread$(input, { joinToken: plan.joinToken }, {
301
- completeOnRunError: true,
302
- streamMode: "connect",
303
- replayCursor: plan.joinFromEventId
304
- }));
300
+ this.canonicalRunId = null;
301
+ this.clearReconnectCursor(input.threadId);
302
+ return (0, rxjs.defer)(() => this.requestJoinCredentials$("connect", input)).pipe((0, rxjs.switchMap)((credentials) => {
303
+ if (credentials === null) return rxjs.EMPTY;
304
+ const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: false });
305
+ return this.observeThread$(canonicalInput, credentials, {
306
+ completeOnRunError: false,
307
+ streamMode: "connect"
308
+ });
305
309
  }));
306
310
  }
307
311
  /**
@@ -318,8 +322,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
318
322
  ownSocket.disconnect();
319
323
  if (this.socket === ownSocket) this.socket = null;
320
324
  }
321
- if (this.threadId) this.sharedState.lastSeenEventIds.delete(this.threadId);
322
- this.runId = null;
325
+ this.canonicalRunId = null;
323
326
  }
324
327
  cleanup() {
325
328
  this.cleanupOwned(this.activeChannel, this.socket);
@@ -340,80 +343,57 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
340
343
  tools: input.tools,
341
344
  context: input.context,
342
345
  state: input.state,
343
- forwardedProps: input.forwardedProps
346
+ forwardedProps: input.forwardedProps,
347
+ ...mode === "connect" ? { lastSeenEventId: this.getReconnectCursor(input) } : {}
344
348
  }),
345
349
  ...this.config.credentials ? { credentials: this.config.credentials } : {}
346
350
  });
347
- if (response.status === 409) throw new AgentThreadLockedError(input.threadId);
351
+ if (response.status === 204 && mode === "connect") return null;
352
+ if (response.status === 409 && mode === "run") throw new AgentThreadLockedError(input.threadId);
348
353
  if (!response.ok) {
349
354
  const text = await response.text().catch(() => "");
350
355
  throw new Error(text || response.statusText || String(response.status));
351
356
  }
352
- const payload = await response.json();
353
- if (!payload.joinToken) throw new Error("missing joinToken");
354
- return { joinToken: payload.joinToken };
357
+ return this.normalizeJoinCredentials(await response.json(), input);
355
358
  } catch (error) {
356
359
  if (error instanceof AgentThreadLockedError) throw error;
357
360
  throw new Error(`REST ${mode} request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
358
361
  }
359
362
  });
360
363
  }
361
- requestConnectPlan$(input) {
362
- return (0, rxjs.defer)(async () => {
363
- try {
364
- const response = await fetch(this.buildRuntimeUrl("connect"), {
365
- method: "POST",
366
- headers: {
367
- "Content-Type": "application/json",
368
- ...this.config.headers
369
- },
370
- body: JSON.stringify({
371
- threadId: input.threadId,
372
- runId: input.runId,
373
- messages: input.messages,
374
- tools: input.tools,
375
- context: input.context,
376
- state: input.state,
377
- forwardedProps: input.forwardedProps,
378
- lastSeenEventId: this.getReconnectCursor(input)
379
- }),
380
- ...this.config.credentials ? { credentials: this.config.credentials } : {}
381
- });
382
- if (response.status === 204) return null;
383
- if (!response.ok) {
384
- const text = await response.text().catch(() => "");
385
- throw new Error(text || response.statusText || String(response.status));
386
- }
387
- return this.normalizeConnectPlan(await response.json());
388
- } catch (error) {
389
- throw new Error(`REST connect request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
390
- }
391
- });
392
- }
393
- normalizeConnectPlan(payload) {
364
+ normalizeJoinCredentials(payload, input) {
394
365
  const envelope = payload && typeof payload === "object" ? payload : null;
395
- if ((envelope === null || envelope === void 0 ? void 0 : envelope.mode) === "bootstrap") return {
396
- mode: "bootstrap",
397
- latestEventId: typeof envelope.latestEventId === "string" ? envelope.latestEventId : null,
398
- events: Array.isArray(envelope.events) ? envelope.events : []
366
+ const realtime = (envelope === null || envelope === void 0 ? void 0 : envelope.realtime) && typeof envelope.realtime === "object" ? envelope.realtime : null;
367
+ if (typeof (envelope === null || envelope === void 0 ? void 0 : envelope.joinToken) !== "string" || !envelope.joinToken) throw new Error("missing joinToken");
368
+ if (typeof (realtime === null || realtime === void 0 ? void 0 : realtime.clientUrl) !== "string" || !realtime.clientUrl) throw new Error("missing realtime.clientUrl");
369
+ if (typeof realtime.topic !== "string" || !realtime.topic) throw new Error("missing realtime.topic");
370
+ return {
371
+ threadId: typeof envelope.threadId === "string" && envelope.threadId ? envelope.threadId : input.threadId,
372
+ runId: typeof envelope.runId === "string" && envelope.runId ? envelope.runId : null,
373
+ joinToken: envelope.joinToken,
374
+ realtime: {
375
+ clientUrl: realtime.clientUrl,
376
+ topic: realtime.topic
377
+ }
399
378
  };
400
- if ((envelope === null || envelope === void 0 ? void 0 : envelope.mode) === "live") {
401
- if (typeof envelope.joinToken !== "string" || envelope.joinToken.length === 0) throw new Error("missing joinToken");
402
- return {
403
- mode: "live",
404
- joinToken: envelope.joinToken,
405
- joinFromEventId: typeof envelope.joinFromEventId === "string" ? envelope.joinFromEventId : null,
406
- events: Array.isArray(envelope.events) ? envelope.events : []
407
- };
408
- }
409
- throw new Error("invalid connect plan");
410
379
  }
411
380
  observeThread$(input, credentials, options) {
381
+ return this.observeThreadSession$(input, credentials, options).pipe((0, rxjs_operators.catchError)((error) => {
382
+ if (!this.isSocketReconnectExhaustedError(error)) return (0, rxjs.throwError)(() => error);
383
+ return this.requestJoinCredentials$("connect", input).pipe((0, rxjs.switchMap)((refreshedCredentials) => refreshedCredentials === null ? rxjs.EMPTY : this.observeThread$(this.applyCanonicalRunIdentity(input, refreshedCredentials, { fallbackToInputRunId: options.streamMode === "run" }), refreshedCredentials, {
384
+ ...options,
385
+ channelMode: "connect",
386
+ replayCursor: this.getReconnectCursor(input)
387
+ })));
388
+ }));
389
+ }
390
+ observeThreadSession$(input, credentials, options) {
412
391
  return (0, rxjs.defer)(() => {
392
+ var _options$channelMode;
413
393
  let ownSocket = null;
414
394
  let ownChannel = null;
415
395
  const socket$ = ɵphoenixSocket$({
416
- url: this.config.url,
396
+ url: credentials.realtime.clientUrl,
417
397
  options: {
418
398
  params: {
419
399
  ...this.config.socketParams,
@@ -429,10 +409,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
429
409
  bufferSize: 1,
430
410
  refCount: true
431
411
  }));
432
- const { topic, params } = this.createThreadChannelDescriptor(input, options.streamMode, options.replayCursor);
412
+ const params = this.createThreadChannelParams(input, (_options$channelMode = options.channelMode) !== null && _options$channelMode !== void 0 ? _options$channelMode : options.streamMode, options.replayCursor);
433
413
  const channel$ = ɵphoenixChannel$({
434
414
  socket$,
435
- topic,
415
+ topic: credentials.realtime.topic,
436
416
  params
437
417
  }).pipe((0, rxjs_operators.tap)(({ channel }) => {
438
418
  ownChannel = channel;
@@ -442,8 +422,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
442
422
  refCount: true
443
423
  }));
444
424
  const threadEvents$ = this.observeThreadEvents$(input.threadId, channel$, options).pipe((0, rxjs_operators.share)());
445
- const threadCompleted$ = threadEvents$.pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.endWith)(null), (0, rxjs_operators.take)(1));
446
- return (0, rxjs.merge)(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$).pipe((0, rxjs_operators.takeUntil)(threadCompleted$)), threadEvents$).pipe((0, rxjs_operators.finalize)(() => this.cleanupOwned(ownChannel, ownSocket)));
425
+ const replayComplete$ = this.observeControlEvent$(input.threadId, channel$, REPLAY_COMPLETE_EVENT).pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.share)());
426
+ const streamIdle$ = this.observeControlEvent$(input.threadId, channel$, STREAM_IDLE_EVENT).pipe((0, rxjs_operators.shareReplay)({
427
+ bufferSize: 1,
428
+ refCount: true
429
+ }));
430
+ const streamIdleCompletion$ = options.streamMode === "connect" ? streamIdle$.pipe((0, rxjs_operators.take)(1)) : rxjs.EMPTY;
431
+ const terminal$ = (0, rxjs.merge)(threadEvents$.pipe((0, rxjs_operators.ignoreElements)(), (0, rxjs_operators.endWith)(null), (0, rxjs_operators.take)(1)), streamIdleCompletion$);
432
+ return (0, rxjs.merge)(this.joinThreadChannel$(channel$), this.observeSocketHealth$(socket$).pipe((0, rxjs_operators.takeUntil)(terminal$)), threadEvents$.pipe((0, rxjs_operators.takeUntil)(streamIdleCompletion$)), replayComplete$.pipe((0, rxjs_operators.takeUntil)(terminal$)), streamIdleCompletion$.pipe((0, rxjs_operators.ignoreElements)())).pipe((0, rxjs_operators.finalize)(() => this.cleanupOwned(ownChannel, ownSocket)));
447
433
  });
448
434
  }
449
435
  joinThreadChannel$(channel$) {
@@ -455,18 +441,24 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
455
441
  observeThreadEvents$(threadId, channel$, options) {
456
442
  return channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => this.observeChannelEvent$(channel, CLIENT_AG_UI_EVENT)), (0, rxjs_operators.tap)((payload) => {
457
443
  this.updateLastSeenEventId(threadId, payload);
458
- this.updateRunIdFromEvent(payload);
459
- }), (0, rxjs_operators.mergeMap)((payload) => (0, rxjs.from)(this.createThreadNotifications(payload, options.completeOnRunError))), (0, rxjs.dematerialize)());
444
+ }), (0, rxjs_operators.mergeMap)((payload) => this.createThreadNotifications(payload, {
445
+ completeOnRunError: options.completeOnRunError,
446
+ completeOnRunFinished: options.streamMode === "run",
447
+ errorOnRunError: options.streamMode === "run"
448
+ })), (0, rxjs.dematerialize)());
449
+ }
450
+ observeControlEvent$(threadId, channel$, eventName) {
451
+ return channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => this.observeChannelEvent$(channel, eventName)), (0, rxjs_operators.tap)((payload) => this.updateLastSeenEventIdFromControl(threadId, payload)));
460
452
  }
461
453
  observeChannelEvent$(channel, eventName) {
462
454
  return ɵobservePhoenixEvent$(channel, eventName);
463
455
  }
464
- createThreadNotifications(payload, completeOnRunError) {
465
- if (payload.type === _ag_ui_client.EventType.RUN_FINISHED) return [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()];
456
+ createThreadNotifications(payload, options) {
457
+ if (payload.type === _ag_ui_client.EventType.RUN_FINISHED) return options.completeOnRunFinished ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : [rxjs.Notification.createNext(payload)];
466
458
  if (payload.type === _ag_ui_client.EventType.RUN_ERROR) {
467
459
  var _message;
468
460
  const errorMessage = (_message = payload.message) !== null && _message !== void 0 ? _message : "Run error";
469
- return completeOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : [rxjs.Notification.createNext(payload), rxjs.Notification.createError(new Error(errorMessage))];
461
+ return options.completeOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createComplete()] : options.errorOnRunError ? [rxjs.Notification.createNext(payload), rxjs.Notification.createError(new Error(errorMessage))] : [rxjs.Notification.createNext(payload)];
470
462
  }
471
463
  return [rxjs.Notification.createNext(payload)];
472
464
  }
@@ -475,62 +467,60 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
475
467
  const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
476
468
  return new URL(path, new URL(this.config.runtimeUrl, origin)).toString();
477
469
  }
478
- createThreadChannelDescriptor(input, streamMode, replayCursor) {
479
- const params = streamMode === "run" ? {
470
+ createThreadChannelParams(input, streamMode, replayCursor) {
471
+ return streamMode === "run" ? {
480
472
  stream_mode: "run",
481
473
  run_id: input.runId
482
474
  } : {
483
475
  stream_mode: "connect",
484
476
  last_seen_event_id: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor
485
477
  };
486
- return {
487
- topic: `thread:${input.threadId}`,
488
- params
489
- };
490
478
  }
491
479
  getLastSeenEventId(threadId) {
492
480
  var _this$sharedState$las;
493
481
  return (_this$sharedState$las = this.sharedState.lastSeenEventIds.get(threadId)) !== null && _this$sharedState$las !== void 0 ? _this$sharedState$las : null;
494
482
  }
495
483
  getReconnectCursor(input) {
496
- return this.hasLocalThreadMessages(input) ? this.getLastSeenEventId(input.threadId) : null;
484
+ return this.getLastSeenEventId(input.threadId);
497
485
  }
498
- hasLocalThreadMessages(input) {
499
- return Array.isArray(input.messages) && input.messages.length > 0;
486
+ clearReconnectCursor(threadId) {
487
+ this.sharedState.lastSeenEventIds.delete(threadId);
500
488
  }
501
489
  updateLastSeenEventId(threadId, payload) {
502
490
  const eventId = this.readEventId(payload);
503
491
  if (!eventId) return;
504
492
  this.sharedState.lastSeenEventIds.set(threadId, eventId);
505
493
  }
506
- setLastSeenEventId(threadId, eventId) {
494
+ updateLastSeenEventIdFromControl(threadId, payload) {
495
+ const eventId = this.readControlEventId(payload);
507
496
  if (!eventId) return;
508
497
  this.sharedState.lastSeenEventIds.set(threadId, eventId);
509
498
  }
510
- /**
511
- * Keep `this.runId` in sync with the backend's actual run ID.
512
- *
513
- * During a `connect` (resume) flow the client generates a fresh `runId`
514
- * via `prepareRunAgentInput`, but the backend is running under its own
515
- * run ID. If the client later sends `STOP_RUN_EVENT` with the wrong
516
- * `runId`, the gateway's runner channel will not match it and the agent
517
- * keeps running. Extracting the run ID from live events fixes this.
518
- *
519
- * The runner normalises events to `run_id` (snake_case) before pushing
520
- * to the gateway, so we check both `runId` and `run_id`.
521
- */
522
- updateRunIdFromEvent(payload) {
523
- var _record$runId;
524
- const record = payload;
525
- const eventRunId = (_record$runId = record.runId) !== null && _record$runId !== void 0 ? _record$runId : record.run_id;
526
- if (typeof eventRunId === "string" && eventRunId.length > 0) this.runId = eventRunId;
527
- }
528
499
  readEventId(payload) {
529
500
  const metadata = payload.metadata;
530
501
  if (!metadata || typeof metadata !== "object") return null;
531
502
  const runnerEventId = metadata.cpki_event_id;
532
503
  return typeof runnerEventId === "string" ? runnerEventId : null;
533
504
  }
505
+ readControlEventId(payload) {
506
+ if (!payload || typeof payload !== "object") return null;
507
+ const latestEventId = payload.latestEventId;
508
+ return typeof latestEventId === "string" ? latestEventId : null;
509
+ }
510
+ applyCanonicalRunIdentity(input, credentials, options) {
511
+ var _credentials$runId;
512
+ this.threadId = credentials.threadId;
513
+ const runId = (_credentials$runId = credentials.runId) !== null && _credentials$runId !== void 0 ? _credentials$runId : options.fallbackToInputRunId ? input.runId : null;
514
+ this.canonicalRunId = runId;
515
+ return {
516
+ ...input,
517
+ threadId: credentials.threadId,
518
+ ...runId === null ? {} : { runId }
519
+ };
520
+ }
521
+ isSocketReconnectExhaustedError(error) {
522
+ return error instanceof Error && error.message.includes("WebSocket connection failed after");
523
+ }
534
524
  };
535
525
 
536
526
  //#endregion
@@ -1356,6 +1346,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1356
1346
  this.notifySuggestionsChanged(agentId, []);
1357
1347
  }
1358
1348
  /**
1349
+ * Clear suggestions at the start of a run, but immediately restore static
1350
+ * suggestions that have `available: "always"` so they remain visible
1351
+ * throughout the run.
1352
+ */
1353
+ clearSuggestionsForRun(agentId) {
1354
+ this.clearSuggestions(agentId);
1355
+ for (const config of Object.values(this._suggestionsConfig)) {
1356
+ if (config.consumerAgentId !== void 0 && config.consumerAgentId !== "*" && config.consumerAgentId !== agentId) continue;
1357
+ if (config.available !== "always") continue;
1358
+ if (isStaticSuggestionsConfig(config)) this.addStaticSuggestions((0, _copilotkit_shared.randomUUID)(), config, agentId);
1359
+ }
1360
+ }
1361
+ /**
1359
1362
  * Get current suggestions for an agent
1360
1363
  */
1361
1364
  getSuggestions(agentId) {
@@ -1721,7 +1724,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1721
1724
  * Run an agent
1722
1725
  */
1723
1726
  async runAgent({ agent, forwardedProps }) {
1724
- if (agent.agentId) this._internal.suggestionEngine.clearSuggestions(agent.agentId);
1727
+ if (agent.agentId) this._internal.suggestionEngine.clearSuggestionsForRun(agent.agentId);
1725
1728
  if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...this._internal.headers };
1726
1729
  if (agent.detachActiveRun) await agent.detachActiveRun();
1727
1730
  this._internal.subscribeAgentToStateManager(agent);
@@ -2473,8 +2476,21 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2473
2476
  CopilotKitCoreErrorCode["TRANSCRIPTION_RATE_LIMITED"] = "transcription_rate_limited";
2474
2477
  CopilotKitCoreErrorCode["TRANSCRIPTION_AUTH_FAILED"] = "transcription_auth_failed";
2475
2478
  CopilotKitCoreErrorCode["TRANSCRIPTION_NETWORK_ERROR"] = "transcription_network_error";
2479
+ CopilotKitCoreErrorCode["SUBSCRIBER_CALLBACK_FAILED"] = "subscriber_callback_failed";
2476
2480
  return CopilotKitCoreErrorCode;
2477
2481
  }({});
2482
+ /**
2483
+ * Runtime allowlist derived from {@link SUBSCRIBE_TO_AGENT_KEYS}. Hoisted
2484
+ * to module scope so the Set is allocated once, not per-subscription.
2485
+ */
2486
+ const ALLOWED_KEYS = new Set([
2487
+ "onMessagesChanged",
2488
+ "onStateChanged",
2489
+ "onRunInitialized",
2490
+ "onRunFinalized",
2491
+ "onRunFailed",
2492
+ "onRunErrorEvent"
2493
+ ]);
2478
2494
  let CopilotKitCoreRuntimeConnectionStatus = /* @__PURE__ */ function(CopilotKitCoreRuntimeConnectionStatus) {
2479
2495
  CopilotKitCoreRuntimeConnectionStatus["Disconnected"] = "disconnected";
2480
2496
  CopilotKitCoreRuntimeConnectionStatus["Connected"] = "connected";
@@ -2543,6 +2559,16 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2543
2559
  }, "Subscriber onError error:");
2544
2560
  }
2545
2561
  /**
2562
+ * Log a message to the console and emit an error to subscribers.
2563
+ * Catches failures from `emitError` itself to prevent unhandled rejections.
2564
+ */
2565
+ logAndEmitError(message, params, logLevel = "error") {
2566
+ console[logLevel](message, params.error);
2567
+ this.emitError(params).catch((emitErr) => {
2568
+ console.error(message + " — emitError itself failed:", emitErr);
2569
+ });
2570
+ }
2571
+ /**
2546
2572
  * Snapshot accessors
2547
2573
  */
2548
2574
  get context() {
@@ -2579,17 +2605,30 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2579
2605
  return this._properties;
2580
2606
  }
2581
2607
  /**
2582
- * Default throttle interval (ms) applied by framework hooks (e.g.
2583
- * `useAgent()`) when the hook/component does not specify an explicit
2584
- * `throttleMs`. An explicit `0` passed as `throttleMs` to `useAgent()`
2585
- * or `<CopilotChat>` overrides this default and disables throttling.
2608
+ * Default throttle interval (ms) used by `subscribeToAgentWithOptions()`
2609
+ * when the caller does not specify an explicit `throttleMs`.
2610
+ * `undefined` means no default is configured; `0` means no throttling.
2586
2611
  */
2587
2612
  get defaultThrottleMs() {
2588
2613
  return this._defaultThrottleMs;
2589
2614
  }
2615
+ /**
2616
+ * Set the default throttle interval (ms) for `subscribeToAgentWithOptions()`.
2617
+ *
2618
+ * Accepts a non-negative finite number or `undefined` (to clear the
2619
+ * default). Invalid values (NaN, Infinity, negative) are logged as
2620
+ * errors and ignored — the previous valid value is preserved.
2621
+ */
2590
2622
  setDefaultThrottleMs(value) {
2591
2623
  if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
2592
- this._defaultThrottleMs = void 0;
2624
+ this.logAndEmitError(`CopilotKitCore.setDefaultThrottleMs: value must be a non-negative finite number or undefined, got ${value}. Keeping current value (${this._defaultThrottleMs}).`, {
2625
+ error: /* @__PURE__ */ new Error(`setDefaultThrottleMs: invalid value (${value}), keeping current value (${this._defaultThrottleMs})`),
2626
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2627
+ context: {
2628
+ value,
2629
+ currentValue: this._defaultThrottleMs
2630
+ }
2631
+ });
2593
2632
  return;
2594
2633
  }
2595
2634
  this._defaultThrottleMs = value;
@@ -2716,6 +2755,138 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2716
2755
  } };
2717
2756
  }
2718
2757
  /**
2758
+ * Subscribe to an agent's notification and lifecycle events with
2759
+ * optional configuration (e.g. throttling).
2760
+ *
2761
+ * Wraps every callback with error protection (`safeCall`) and applies
2762
+ * the options before delegating to `agent.subscribe()`.
2763
+ *
2764
+ * See {@link SubscribeToAgentSubscriber} for the accepted callback subset
2765
+ * and the rationale for excluding AG-UI event handlers.
2766
+ */
2767
+ subscribeToAgentWithOptions(agent, subscriber, options) {
2768
+ var _ref, _options$throttleMs;
2769
+ const resolved = (_ref = (_options$throttleMs = options === null || options === void 0 ? void 0 : options.throttleMs) !== null && _options$throttleMs !== void 0 ? _options$throttleMs : this._defaultThrottleMs) !== null && _ref !== void 0 ? _ref : 0;
2770
+ let effectiveMs = 0;
2771
+ if (!Number.isFinite(resolved) || resolved < 0) {
2772
+ const source = (options === null || options === void 0 ? void 0 : options.throttleMs) !== void 0 ? "throttleMs" : "defaultThrottleMs";
2773
+ this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`, {
2774
+ error: /* @__PURE__ */ new Error(`subscribeToAgentWithOptions: invalid ${source} (${resolved}), falling back to unthrottled`),
2775
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2776
+ context: {
2777
+ agentId: agent.agentId,
2778
+ source,
2779
+ value: resolved
2780
+ }
2781
+ });
2782
+ } else effectiveMs = resolved;
2783
+ const agentLabel = agent.agentId || "(unknown agent)";
2784
+ const safeCall = (label, fn, ...args) => {
2785
+ const reportError = (err, verb) => {
2786
+ this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions[${agentLabel}]: ${label} callback ${verb}:`, {
2787
+ error: err instanceof Error ? err : new Error(String(err)),
2788
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2789
+ context: {
2790
+ agentId: agent.agentId,
2791
+ callback: label
2792
+ }
2793
+ });
2794
+ };
2795
+ try {
2796
+ const result = fn(...args);
2797
+ if (result instanceof Promise) return result.catch((err) => {
2798
+ reportError(err, "rejected");
2799
+ });
2800
+ return result;
2801
+ } catch (err) {
2802
+ reportError(err, "threw");
2803
+ }
2804
+ };
2805
+ const guardAll = (sub) => {
2806
+ const guarded = {};
2807
+ if (sub.onMessagesChanged) {
2808
+ const fn = sub.onMessagesChanged;
2809
+ guarded.onMessagesChanged = (params) => safeCall("onMessagesChanged", fn, params);
2810
+ }
2811
+ if (sub.onStateChanged) {
2812
+ const fn = sub.onStateChanged;
2813
+ guarded.onStateChanged = (params) => safeCall("onStateChanged", fn, params);
2814
+ }
2815
+ if (sub.onRunInitialized) {
2816
+ const fn = sub.onRunInitialized;
2817
+ guarded.onRunInitialized = (params) => safeCall("onRunInitialized", fn, params);
2818
+ }
2819
+ if (sub.onRunFinalized) {
2820
+ const fn = sub.onRunFinalized;
2821
+ guarded.onRunFinalized = (params) => safeCall("onRunFinalized", fn, params);
2822
+ }
2823
+ if (sub.onRunFailed) {
2824
+ const fn = sub.onRunFailed;
2825
+ guarded.onRunFailed = (params) => safeCall("onRunFailed", fn, params);
2826
+ }
2827
+ if (sub.onRunErrorEvent) {
2828
+ const fn = sub.onRunErrorEvent;
2829
+ guarded.onRunErrorEvent = (params) => safeCall("onRunErrorEvent", fn, params);
2830
+ }
2831
+ return guarded;
2832
+ };
2833
+ for (const key of Object.keys(subscriber)) if (typeof subscriber[key] === "function" && !ALLOWED_KEYS.has(key)) {
2834
+ const message = `CopilotKitCore.subscribeToAgentWithOptions[${agentLabel}]: callback "${key}" is not supported and was dropped. Supported callbacks: ${Array.from(ALLOWED_KEYS).join(", ")}. Use agent.subscribe() directly for event handlers and per-item notifications.`;
2835
+ this.logAndEmitError(message, {
2836
+ error: new Error(message),
2837
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2838
+ context: {
2839
+ agentId: agent.agentId,
2840
+ droppedCallback: key
2841
+ }
2842
+ }, "warn");
2843
+ }
2844
+ if (effectiveMs <= 0) {
2845
+ const subscription = agent.subscribe(guardAll(subscriber));
2846
+ return { unsubscribe: () => subscription.unsubscribe() };
2847
+ }
2848
+ let active = true;
2849
+ let latestMessagesParams = null;
2850
+ let latestStateParams = null;
2851
+ const flushPending = () => {
2852
+ if (active && subscriber.onMessagesChanged && latestMessagesParams) {
2853
+ const params = latestMessagesParams;
2854
+ latestMessagesParams = null;
2855
+ safeCall("onMessagesChanged", subscriber.onMessagesChanged, params);
2856
+ }
2857
+ if (active && subscriber.onStateChanged && latestStateParams) {
2858
+ const params = latestStateParams;
2859
+ latestStateParams = null;
2860
+ safeCall("onStateChanged", subscriber.onStateChanged, params);
2861
+ }
2862
+ };
2863
+ const throttler = new _tanstack_pacer.Throttler(flushPending, {
2864
+ wait: effectiveMs,
2865
+ leading: true,
2866
+ trailing: true
2867
+ });
2868
+ const lifecycleOnly = {};
2869
+ if (subscriber.onRunInitialized) lifecycleOnly.onRunInitialized = subscriber.onRunInitialized;
2870
+ if (subscriber.onRunFinalized) lifecycleOnly.onRunFinalized = subscriber.onRunFinalized;
2871
+ if (subscriber.onRunFailed) lifecycleOnly.onRunFailed = subscriber.onRunFailed;
2872
+ if (subscriber.onRunErrorEvent) lifecycleOnly.onRunErrorEvent = subscriber.onRunErrorEvent;
2873
+ const wrappedSubscriber = guardAll(lifecycleOnly);
2874
+ if (subscriber.onMessagesChanged) wrappedSubscriber.onMessagesChanged = (params) => {
2875
+ latestMessagesParams = params;
2876
+ throttler.maybeExecute();
2877
+ };
2878
+ if (subscriber.onStateChanged) wrappedSubscriber.onStateChanged = (params) => {
2879
+ latestStateParams = params;
2880
+ throttler.maybeExecute();
2881
+ };
2882
+ const subscription = agent.subscribe(wrappedSubscriber);
2883
+ return { unsubscribe: () => {
2884
+ active = false;
2885
+ throttler.cancel();
2886
+ subscription.unsubscribe();
2887
+ } };
2888
+ }
2889
+ /**
2719
2890
  * Agent connectivity (delegated to RunHandler)
2720
2891
  */
2721
2892
  async connectAgent(params) {
@@ -3264,15 +3435,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3264
3435
  threadUpserted: props(),
3265
3436
  threadDeleted: props()
3266
3437
  });
3267
- function sortThreadsByUpdatedAt(threads) {
3268
- return [...threads].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
3438
+ function sortThreadsByRecency(threads) {
3439
+ return [...threads].sort((left, right) => {
3440
+ var _ref, _left$lastRunAt, _ref2, _right$lastRunAt;
3441
+ const leftKey = (_ref = (_left$lastRunAt = left.lastRunAt) !== null && _left$lastRunAt !== void 0 ? _left$lastRunAt : left.updatedAt) !== null && _ref !== void 0 ? _ref : left.createdAt;
3442
+ return ((_ref2 = (_right$lastRunAt = right.lastRunAt) !== null && _right$lastRunAt !== void 0 ? _right$lastRunAt : right.updatedAt) !== null && _ref2 !== void 0 ? _ref2 : right.createdAt).localeCompare(leftKey);
3443
+ });
3269
3444
  }
3270
3445
  function upsertThread(threads, thread) {
3271
3446
  const existingIndex = threads.findIndex((item) => item.id === thread.id);
3272
- if (existingIndex === -1) return sortThreadsByUpdatedAt([...threads, thread]);
3447
+ if (existingIndex === -1) return sortThreadsByRecency([...threads, thread]);
3273
3448
  const next = [...threads];
3274
3449
  next[existingIndex] = thread;
3275
- return sortThreadsByUpdatedAt(next);
3450
+ return sortThreadsByRecency(next);
3276
3451
  }
3277
3452
  const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.contextChanged, (state, { context }) => ({
3278
3453
  ...state,
@@ -3305,7 +3480,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3305
3480
  if (sessionId !== state.sessionId) return state;
3306
3481
  return {
3307
3482
  ...state,
3308
- threads: sortThreadsByUpdatedAt(threads),
3483
+ threads: sortThreadsByRecency(threads),
3309
3484
  isLoading: false,
3310
3485
  error: null,
3311
3486
  metadataJoinCode: joinCode,