@copilotkit/core 1.56.2 → 1.56.4

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
@@ -2473,8 +2463,21 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2473
2463
  CopilotKitCoreErrorCode["TRANSCRIPTION_RATE_LIMITED"] = "transcription_rate_limited";
2474
2464
  CopilotKitCoreErrorCode["TRANSCRIPTION_AUTH_FAILED"] = "transcription_auth_failed";
2475
2465
  CopilotKitCoreErrorCode["TRANSCRIPTION_NETWORK_ERROR"] = "transcription_network_error";
2466
+ CopilotKitCoreErrorCode["SUBSCRIBER_CALLBACK_FAILED"] = "subscriber_callback_failed";
2476
2467
  return CopilotKitCoreErrorCode;
2477
2468
  }({});
2469
+ /**
2470
+ * Runtime allowlist derived from {@link SUBSCRIBE_TO_AGENT_KEYS}. Hoisted
2471
+ * to module scope so the Set is allocated once, not per-subscription.
2472
+ */
2473
+ const ALLOWED_KEYS = new Set([
2474
+ "onMessagesChanged",
2475
+ "onStateChanged",
2476
+ "onRunInitialized",
2477
+ "onRunFinalized",
2478
+ "onRunFailed",
2479
+ "onRunErrorEvent"
2480
+ ]);
2478
2481
  let CopilotKitCoreRuntimeConnectionStatus = /* @__PURE__ */ function(CopilotKitCoreRuntimeConnectionStatus) {
2479
2482
  CopilotKitCoreRuntimeConnectionStatus["Disconnected"] = "disconnected";
2480
2483
  CopilotKitCoreRuntimeConnectionStatus["Connected"] = "connected";
@@ -2543,6 +2546,16 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2543
2546
  }, "Subscriber onError error:");
2544
2547
  }
2545
2548
  /**
2549
+ * Log a message to the console and emit an error to subscribers.
2550
+ * Catches failures from `emitError` itself to prevent unhandled rejections.
2551
+ */
2552
+ logAndEmitError(message, params, logLevel = "error") {
2553
+ console[logLevel](message, params.error);
2554
+ this.emitError(params).catch((emitErr) => {
2555
+ console.error(message + " — emitError itself failed:", emitErr);
2556
+ });
2557
+ }
2558
+ /**
2546
2559
  * Snapshot accessors
2547
2560
  */
2548
2561
  get context() {
@@ -2579,17 +2592,30 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2579
2592
  return this._properties;
2580
2593
  }
2581
2594
  /**
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.
2595
+ * Default throttle interval (ms) used by `subscribeToAgentWithOptions()`
2596
+ * when the caller does not specify an explicit `throttleMs`.
2597
+ * `undefined` means no default is configured; `0` means no throttling.
2586
2598
  */
2587
2599
  get defaultThrottleMs() {
2588
2600
  return this._defaultThrottleMs;
2589
2601
  }
2602
+ /**
2603
+ * Set the default throttle interval (ms) for `subscribeToAgentWithOptions()`.
2604
+ *
2605
+ * Accepts a non-negative finite number or `undefined` (to clear the
2606
+ * default). Invalid values (NaN, Infinity, negative) are logged as
2607
+ * errors and ignored — the previous valid value is preserved.
2608
+ */
2590
2609
  setDefaultThrottleMs(value) {
2591
2610
  if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
2592
- this._defaultThrottleMs = void 0;
2611
+ this.logAndEmitError(`CopilotKitCore.setDefaultThrottleMs: value must be a non-negative finite number or undefined, got ${value}. Keeping current value (${this._defaultThrottleMs}).`, {
2612
+ error: /* @__PURE__ */ new Error(`setDefaultThrottleMs: invalid value (${value}), keeping current value (${this._defaultThrottleMs})`),
2613
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2614
+ context: {
2615
+ value,
2616
+ currentValue: this._defaultThrottleMs
2617
+ }
2618
+ });
2593
2619
  return;
2594
2620
  }
2595
2621
  this._defaultThrottleMs = value;
@@ -2716,6 +2742,138 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2716
2742
  } };
2717
2743
  }
2718
2744
  /**
2745
+ * Subscribe to an agent's notification and lifecycle events with
2746
+ * optional configuration (e.g. throttling).
2747
+ *
2748
+ * Wraps every callback with error protection (`safeCall`) and applies
2749
+ * the options before delegating to `agent.subscribe()`.
2750
+ *
2751
+ * See {@link SubscribeToAgentSubscriber} for the accepted callback subset
2752
+ * and the rationale for excluding AG-UI event handlers.
2753
+ */
2754
+ subscribeToAgentWithOptions(agent, subscriber, options) {
2755
+ var _ref, _options$throttleMs;
2756
+ 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;
2757
+ let effectiveMs = 0;
2758
+ if (!Number.isFinite(resolved) || resolved < 0) {
2759
+ const source = (options === null || options === void 0 ? void 0 : options.throttleMs) !== void 0 ? "throttleMs" : "defaultThrottleMs";
2760
+ this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`, {
2761
+ error: /* @__PURE__ */ new Error(`subscribeToAgentWithOptions: invalid ${source} (${resolved}), falling back to unthrottled`),
2762
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2763
+ context: {
2764
+ agentId: agent.agentId,
2765
+ source,
2766
+ value: resolved
2767
+ }
2768
+ });
2769
+ } else effectiveMs = resolved;
2770
+ const agentLabel = agent.agentId || "(unknown agent)";
2771
+ const safeCall = (label, fn, ...args) => {
2772
+ const reportError = (err, verb) => {
2773
+ this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions[${agentLabel}]: ${label} callback ${verb}:`, {
2774
+ error: err instanceof Error ? err : new Error(String(err)),
2775
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2776
+ context: {
2777
+ agentId: agent.agentId,
2778
+ callback: label
2779
+ }
2780
+ });
2781
+ };
2782
+ try {
2783
+ const result = fn(...args);
2784
+ if (result instanceof Promise) return result.catch((err) => {
2785
+ reportError(err, "rejected");
2786
+ });
2787
+ return result;
2788
+ } catch (err) {
2789
+ reportError(err, "threw");
2790
+ }
2791
+ };
2792
+ const guardAll = (sub) => {
2793
+ const guarded = {};
2794
+ if (sub.onMessagesChanged) {
2795
+ const fn = sub.onMessagesChanged;
2796
+ guarded.onMessagesChanged = (params) => safeCall("onMessagesChanged", fn, params);
2797
+ }
2798
+ if (sub.onStateChanged) {
2799
+ const fn = sub.onStateChanged;
2800
+ guarded.onStateChanged = (params) => safeCall("onStateChanged", fn, params);
2801
+ }
2802
+ if (sub.onRunInitialized) {
2803
+ const fn = sub.onRunInitialized;
2804
+ guarded.onRunInitialized = (params) => safeCall("onRunInitialized", fn, params);
2805
+ }
2806
+ if (sub.onRunFinalized) {
2807
+ const fn = sub.onRunFinalized;
2808
+ guarded.onRunFinalized = (params) => safeCall("onRunFinalized", fn, params);
2809
+ }
2810
+ if (sub.onRunFailed) {
2811
+ const fn = sub.onRunFailed;
2812
+ guarded.onRunFailed = (params) => safeCall("onRunFailed", fn, params);
2813
+ }
2814
+ if (sub.onRunErrorEvent) {
2815
+ const fn = sub.onRunErrorEvent;
2816
+ guarded.onRunErrorEvent = (params) => safeCall("onRunErrorEvent", fn, params);
2817
+ }
2818
+ return guarded;
2819
+ };
2820
+ for (const key of Object.keys(subscriber)) if (typeof subscriber[key] === "function" && !ALLOWED_KEYS.has(key)) {
2821
+ 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.`;
2822
+ this.logAndEmitError(message, {
2823
+ error: new Error(message),
2824
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2825
+ context: {
2826
+ agentId: agent.agentId,
2827
+ droppedCallback: key
2828
+ }
2829
+ }, "warn");
2830
+ }
2831
+ if (effectiveMs <= 0) {
2832
+ const subscription = agent.subscribe(guardAll(subscriber));
2833
+ return { unsubscribe: () => subscription.unsubscribe() };
2834
+ }
2835
+ let active = true;
2836
+ let latestMessagesParams = null;
2837
+ let latestStateParams = null;
2838
+ const flushPending = () => {
2839
+ if (active && subscriber.onMessagesChanged && latestMessagesParams) {
2840
+ const params = latestMessagesParams;
2841
+ latestMessagesParams = null;
2842
+ safeCall("onMessagesChanged", subscriber.onMessagesChanged, params);
2843
+ }
2844
+ if (active && subscriber.onStateChanged && latestStateParams) {
2845
+ const params = latestStateParams;
2846
+ latestStateParams = null;
2847
+ safeCall("onStateChanged", subscriber.onStateChanged, params);
2848
+ }
2849
+ };
2850
+ const throttler = new _tanstack_pacer.Throttler(flushPending, {
2851
+ wait: effectiveMs,
2852
+ leading: true,
2853
+ trailing: true
2854
+ });
2855
+ const lifecycleOnly = {};
2856
+ if (subscriber.onRunInitialized) lifecycleOnly.onRunInitialized = subscriber.onRunInitialized;
2857
+ if (subscriber.onRunFinalized) lifecycleOnly.onRunFinalized = subscriber.onRunFinalized;
2858
+ if (subscriber.onRunFailed) lifecycleOnly.onRunFailed = subscriber.onRunFailed;
2859
+ if (subscriber.onRunErrorEvent) lifecycleOnly.onRunErrorEvent = subscriber.onRunErrorEvent;
2860
+ const wrappedSubscriber = guardAll(lifecycleOnly);
2861
+ if (subscriber.onMessagesChanged) wrappedSubscriber.onMessagesChanged = (params) => {
2862
+ latestMessagesParams = params;
2863
+ throttler.maybeExecute();
2864
+ };
2865
+ if (subscriber.onStateChanged) wrappedSubscriber.onStateChanged = (params) => {
2866
+ latestStateParams = params;
2867
+ throttler.maybeExecute();
2868
+ };
2869
+ const subscription = agent.subscribe(wrappedSubscriber);
2870
+ return { unsubscribe: () => {
2871
+ active = false;
2872
+ throttler.cancel();
2873
+ subscription.unsubscribe();
2874
+ } };
2875
+ }
2876
+ /**
2719
2877
  * Agent connectivity (delegated to RunHandler)
2720
2878
  */
2721
2879
  async connectAgent(params) {
@@ -3264,15 +3422,19 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3264
3422
  threadUpserted: props(),
3265
3423
  threadDeleted: props()
3266
3424
  });
3267
- function sortThreadsByUpdatedAt(threads) {
3268
- return [...threads].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
3425
+ function sortThreadsByRecency(threads) {
3426
+ return [...threads].sort((left, right) => {
3427
+ var _ref, _left$lastRunAt, _ref2, _right$lastRunAt;
3428
+ const leftKey = (_ref = (_left$lastRunAt = left.lastRunAt) !== null && _left$lastRunAt !== void 0 ? _left$lastRunAt : left.updatedAt) !== null && _ref !== void 0 ? _ref : left.createdAt;
3429
+ return ((_ref2 = (_right$lastRunAt = right.lastRunAt) !== null && _right$lastRunAt !== void 0 ? _right$lastRunAt : right.updatedAt) !== null && _ref2 !== void 0 ? _ref2 : right.createdAt).localeCompare(leftKey);
3430
+ });
3269
3431
  }
3270
3432
  function upsertThread(threads, thread) {
3271
3433
  const existingIndex = threads.findIndex((item) => item.id === thread.id);
3272
- if (existingIndex === -1) return sortThreadsByUpdatedAt([...threads, thread]);
3434
+ if (existingIndex === -1) return sortThreadsByRecency([...threads, thread]);
3273
3435
  const next = [...threads];
3274
3436
  next[existingIndex] = thread;
3275
- return sortThreadsByUpdatedAt(next);
3437
+ return sortThreadsByRecency(next);
3276
3438
  }
3277
3439
  const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.contextChanged, (state, { context }) => ({
3278
3440
  ...state,
@@ -3305,7 +3467,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
3305
3467
  if (sessionId !== state.sessionId) return state;
3306
3468
  return {
3307
3469
  ...state,
3308
- threads: sortThreadsByUpdatedAt(threads),
3470
+ threads: sortThreadsByRecency(threads),
3309
3471
  isLoading: false,
3310
3472
  error: null,
3311
3473
  metadataJoinCode: joinCode,