@copilotkit/core 1.56.0 → 1.56.2-canary.pin-to-send

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { Throttler } from "@tanstack/pacer";
1
2
  import { AbstractAgent, EventType, HttpAgent, randomUUID, runHttpRequest, structuredClone_, transformChunks, transformHttpEventStream } from "@ag-ui/client";
2
3
  import { RUNTIME_MODE_INTELLIGENCE, RUNTIME_MODE_SSE, logger, partialJSONParse, phoenixExponentialBackoff, randomUUID as randomUUID$1, resolveDebugConfig, schemaToJsonSchema } from "@copilotkit/shared";
3
4
  import { BehaviorSubject, EMPTY, NEVER, Notification, Observable, Subject, Subscription, asapScheduler, concat, defer, dematerialize, firstValueFrom, from, lastValueFrom, merge, of, switchMap, throwError } from "rxjs";
@@ -137,7 +138,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
137
138
  config;
138
139
  socket = null;
139
140
  activeChannel = null;
140
- runId = null;
141
+ canonicalRunId = null;
141
142
  sharedState;
142
143
  constructor(config, sharedState = { lastSeenEventIds: /* @__PURE__ */ new Map() }) {
143
144
  super();
@@ -175,7 +176,11 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
175
176
  try {
176
177
  this.isRunning = true;
177
178
  this.agentId = this.agentId ?? randomUUID();
178
- const input = this.prepareRunAgentInput(parameters);
179
+ const effectiveParameters = parameters?.runId || !this.canonicalRunId ? parameters : {
180
+ ...parameters,
181
+ runId: this.canonicalRunId
182
+ };
183
+ const input = this.prepareRunAgentInput(effectiveParameters);
179
184
  let result;
180
185
  const previousMessageIds = new Set(this.messages.map((m) => m.id));
181
186
  const subscribers = [
@@ -214,14 +219,14 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
214
219
  }
215
220
  }
216
221
  abortRun() {
217
- if (this.activeChannel && this.runId) {
222
+ if (this.activeChannel && this.canonicalRunId) {
218
223
  const fallback = setTimeout(() => clear(), 5e3);
219
224
  const clear = () => {
220
225
  clearTimeout(fallback);
221
226
  this.detachActiveRun();
222
227
  this.cleanup();
223
228
  };
224
- this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.runId }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
229
+ this.activeChannel.push(STOP_RUN_EVENT, { run_id: this.canonicalRunId }).receive("ok", clear).receive("error", clear).receive("timeout", clear);
225
230
  } else {
226
231
  this.detachActiveRun();
227
232
  this.cleanup();
@@ -233,7 +238,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
233
238
  */
234
239
  run(input) {
235
240
  this.threadId = input.threadId;
236
- this.runId = input.runId;
241
+ this.canonicalRunId = input.runId;
237
242
  return defer(() => this.requestJoinCredentials$("run", input)).pipe(switchMap((credentials) => this.observeThread$(input, credentials, {
238
243
  completeOnRunError: false,
239
244
  streamMode: "run"
@@ -245,16 +250,14 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
245
250
  */
246
251
  connect(input) {
247
252
  this.threadId = input.threadId;
248
- this.runId = input.runId;
253
+ this.canonicalRunId = input.runId;
249
254
  return defer(() => this.requestConnectPlan$(input)).pipe(switchMap((plan) => {
250
255
  if (plan === null) return EMPTY;
251
256
  if (plan.mode === "bootstrap") {
252
257
  this.setLastSeenEventId(input.threadId, plan.latestEventId);
253
- for (const event of plan.events) this.updateRunIdFromEvent(event);
254
258
  return from(plan.events);
255
259
  }
256
260
  this.setLastSeenEventId(input.threadId, plan.joinFromEventId);
257
- for (const event of plan.events) this.updateRunIdFromEvent(event);
258
261
  return concat(from(plan.events), this.observeThread$(input, { joinToken: plan.joinToken }, {
259
262
  completeOnRunError: true,
260
263
  streamMode: "connect",
@@ -277,7 +280,7 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
277
280
  if (this.socket === ownSocket) this.socket = null;
278
281
  }
279
282
  if (this.threadId) this.sharedState.lastSeenEventIds.delete(this.threadId);
280
- this.runId = null;
283
+ this.canonicalRunId = null;
281
284
  }
282
285
  cleanup() {
283
286
  this.cleanupOwned(this.activeChannel, this.socket);
@@ -413,7 +416,6 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
413
416
  observeThreadEvents$(threadId, channel$, options) {
414
417
  return channel$.pipe(switchMap$1(({ channel }) => this.observeChannelEvent$(channel, CLIENT_AG_UI_EVENT)), tap((payload) => {
415
418
  this.updateLastSeenEventId(threadId, payload);
416
- this.updateRunIdFromEvent(payload);
417
419
  }), mergeMap((payload) => from(this.createThreadNotifications(payload, options.completeOnRunError))), dematerialize());
418
420
  }
419
421
  observeChannelEvent$(channel, eventName) {
@@ -463,23 +465,6 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
463
465
  if (!eventId) return;
464
466
  this.sharedState.lastSeenEventIds.set(threadId, eventId);
465
467
  }
466
- /**
467
- * Keep `this.runId` in sync with the backend's actual run ID.
468
- *
469
- * During a `connect` (resume) flow the client generates a fresh `runId`
470
- * via `prepareRunAgentInput`, but the backend is running under its own
471
- * run ID. If the client later sends `STOP_RUN_EVENT` with the wrong
472
- * `runId`, the gateway's runner channel will not match it and the agent
473
- * keeps running. Extracting the run ID from live events fixes this.
474
- *
475
- * The runner normalises events to `run_id` (snake_case) before pushing
476
- * to the gateway, so we check both `runId` and `run_id`.
477
- */
478
- updateRunIdFromEvent(payload) {
479
- const record = payload;
480
- const eventRunId = record.runId ?? record.run_id;
481
- if (typeof eventRunId === "string" && eventRunId.length > 0) this.runId = eventRunId;
482
- }
483
468
  readEventId(payload) {
484
469
  const metadata = payload.metadata;
485
470
  if (!metadata || typeof metadata !== "object") return null;
@@ -857,6 +842,7 @@ var AgentRegistry = class {
857
842
  initialize(agents) {
858
843
  this.localAgents = this.assignAgentIds(agents);
859
844
  this.applyHeadersToAgents(this.localAgents);
845
+ this.applyCredentialsToAgents(this.localAgents);
860
846
  this._agents = this.localAgents;
861
847
  }
862
848
  /**
@@ -886,6 +872,7 @@ var AgentRegistry = class {
886
872
  ...this.remoteAgents
887
873
  };
888
874
  this.applyHeadersToAgents(this._agents);
875
+ this.applyCredentialsToAgents(this._agents);
889
876
  this.notifyAgentsChanged();
890
877
  }
891
878
  /**
@@ -895,6 +882,7 @@ var AgentRegistry = class {
895
882
  this.validateAndAssignAgentId(id, agent);
896
883
  this.localAgents[id] = agent;
897
884
  this.applyHeadersToAgent(agent);
885
+ this.applyCredentialsToAgent(agent);
898
886
  this._agents = {
899
887
  ...this.localAgents,
900
888
  ...this.remoteAgents
@@ -1955,7 +1943,7 @@ var RunHandler = class {
1955
1943
  * Build frontend tools for an agent
1956
1944
  */
1957
1945
  buildFrontendTools(agentId) {
1958
- return this._tools.filter((tool) => tool.available !== false && (!tool.agentId || tool.agentId === agentId)).map((tool) => ({
1946
+ return this._tools.filter((tool) => tool.available !== false && tool.available !== "disabled" && (!tool.agentId || tool.agentId === agentId)).map((tool) => ({
1959
1947
  name: tool.name,
1960
1948
  description: tool.description ?? "",
1961
1949
  parameters: createToolSchema(tool)
@@ -2305,8 +2293,21 @@ let CopilotKitCoreErrorCode = /* @__PURE__ */ function(CopilotKitCoreErrorCode)
2305
2293
  CopilotKitCoreErrorCode["TRANSCRIPTION_RATE_LIMITED"] = "transcription_rate_limited";
2306
2294
  CopilotKitCoreErrorCode["TRANSCRIPTION_AUTH_FAILED"] = "transcription_auth_failed";
2307
2295
  CopilotKitCoreErrorCode["TRANSCRIPTION_NETWORK_ERROR"] = "transcription_network_error";
2296
+ CopilotKitCoreErrorCode["SUBSCRIBER_CALLBACK_FAILED"] = "subscriber_callback_failed";
2308
2297
  return CopilotKitCoreErrorCode;
2309
2298
  }({});
2299
+ /**
2300
+ * Runtime allowlist derived from {@link SUBSCRIBE_TO_AGENT_KEYS}. Hoisted
2301
+ * to module scope so the Set is allocated once, not per-subscription.
2302
+ */
2303
+ const ALLOWED_KEYS = new Set([
2304
+ "onMessagesChanged",
2305
+ "onStateChanged",
2306
+ "onRunInitialized",
2307
+ "onRunFinalized",
2308
+ "onRunFailed",
2309
+ "onRunErrorEvent"
2310
+ ]);
2310
2311
  let CopilotKitCoreRuntimeConnectionStatus = /* @__PURE__ */ function(CopilotKitCoreRuntimeConnectionStatus) {
2311
2312
  CopilotKitCoreRuntimeConnectionStatus["Disconnected"] = "disconnected";
2312
2313
  CopilotKitCoreRuntimeConnectionStatus["Connected"] = "connected";
@@ -2372,6 +2373,16 @@ var CopilotKitCore = class {
2372
2373
  }), "Subscriber onError error:");
2373
2374
  }
2374
2375
  /**
2376
+ * Log a message to the console and emit an error to subscribers.
2377
+ * Catches failures from `emitError` itself to prevent unhandled rejections.
2378
+ */
2379
+ logAndEmitError(message, params, logLevel = "error") {
2380
+ console[logLevel](message, params.error);
2381
+ this.emitError(params).catch((emitErr) => {
2382
+ console.error(message + " — emitError itself failed:", emitErr);
2383
+ });
2384
+ }
2385
+ /**
2375
2386
  * Snapshot accessors
2376
2387
  */
2377
2388
  get context() {
@@ -2408,17 +2419,30 @@ var CopilotKitCore = class {
2408
2419
  return this._properties;
2409
2420
  }
2410
2421
  /**
2411
- * Default throttle interval (ms) applied by framework hooks (e.g.
2412
- * `useAgent()`) when the hook/component does not specify an explicit
2413
- * `throttleMs`. An explicit `0` passed as `throttleMs` to `useAgent()`
2414
- * or `<CopilotChat>` overrides this default and disables throttling.
2422
+ * Default throttle interval (ms) used by `subscribeToAgentWithOptions()`
2423
+ * when the caller does not specify an explicit `throttleMs`.
2424
+ * `undefined` means no default is configured; `0` means no throttling.
2415
2425
  */
2416
2426
  get defaultThrottleMs() {
2417
2427
  return this._defaultThrottleMs;
2418
2428
  }
2429
+ /**
2430
+ * Set the default throttle interval (ms) for `subscribeToAgentWithOptions()`.
2431
+ *
2432
+ * Accepts a non-negative finite number or `undefined` (to clear the
2433
+ * default). Invalid values (NaN, Infinity, negative) are logged as
2434
+ * errors and ignored — the previous valid value is preserved.
2435
+ */
2419
2436
  setDefaultThrottleMs(value) {
2420
2437
  if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
2421
- this._defaultThrottleMs = void 0;
2438
+ this.logAndEmitError(`CopilotKitCore.setDefaultThrottleMs: value must be a non-negative finite number or undefined, got ${value}. Keeping current value (${this._defaultThrottleMs}).`, {
2439
+ error: /* @__PURE__ */ new Error(`setDefaultThrottleMs: invalid value (${value}), keeping current value (${this._defaultThrottleMs})`),
2440
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2441
+ context: {
2442
+ value,
2443
+ currentValue: this._defaultThrottleMs
2444
+ }
2445
+ });
2422
2446
  return;
2423
2447
  }
2424
2448
  this._defaultThrottleMs = value;
@@ -2539,6 +2563,137 @@ var CopilotKitCore = class {
2539
2563
  } };
2540
2564
  }
2541
2565
  /**
2566
+ * Subscribe to an agent's notification and lifecycle events with
2567
+ * optional configuration (e.g. throttling).
2568
+ *
2569
+ * Wraps every callback with error protection (`safeCall`) and applies
2570
+ * the options before delegating to `agent.subscribe()`.
2571
+ *
2572
+ * See {@link SubscribeToAgentSubscriber} for the accepted callback subset
2573
+ * and the rationale for excluding AG-UI event handlers.
2574
+ */
2575
+ subscribeToAgentWithOptions(agent, subscriber, options) {
2576
+ const resolved = options?.throttleMs ?? this._defaultThrottleMs ?? 0;
2577
+ let effectiveMs = 0;
2578
+ if (!Number.isFinite(resolved) || resolved < 0) {
2579
+ const source = options?.throttleMs !== void 0 ? "throttleMs" : "defaultThrottleMs";
2580
+ this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`, {
2581
+ error: /* @__PURE__ */ new Error(`subscribeToAgentWithOptions: invalid ${source} (${resolved}), falling back to unthrottled`),
2582
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2583
+ context: {
2584
+ agentId: agent.agentId,
2585
+ source,
2586
+ value: resolved
2587
+ }
2588
+ });
2589
+ } else effectiveMs = resolved;
2590
+ const agentLabel = agent.agentId || "(unknown agent)";
2591
+ const safeCall = (label, fn, ...args) => {
2592
+ const reportError = (err, verb) => {
2593
+ this.logAndEmitError(`CopilotKitCore.subscribeToAgentWithOptions[${agentLabel}]: ${label} callback ${verb}:`, {
2594
+ error: err instanceof Error ? err : new Error(String(err)),
2595
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2596
+ context: {
2597
+ agentId: agent.agentId,
2598
+ callback: label
2599
+ }
2600
+ });
2601
+ };
2602
+ try {
2603
+ const result = fn(...args);
2604
+ if (result instanceof Promise) return result.catch((err) => {
2605
+ reportError(err, "rejected");
2606
+ });
2607
+ return result;
2608
+ } catch (err) {
2609
+ reportError(err, "threw");
2610
+ }
2611
+ };
2612
+ const guardAll = (sub) => {
2613
+ const guarded = {};
2614
+ if (sub.onMessagesChanged) {
2615
+ const fn = sub.onMessagesChanged;
2616
+ guarded.onMessagesChanged = (params) => safeCall("onMessagesChanged", fn, params);
2617
+ }
2618
+ if (sub.onStateChanged) {
2619
+ const fn = sub.onStateChanged;
2620
+ guarded.onStateChanged = (params) => safeCall("onStateChanged", fn, params);
2621
+ }
2622
+ if (sub.onRunInitialized) {
2623
+ const fn = sub.onRunInitialized;
2624
+ guarded.onRunInitialized = (params) => safeCall("onRunInitialized", fn, params);
2625
+ }
2626
+ if (sub.onRunFinalized) {
2627
+ const fn = sub.onRunFinalized;
2628
+ guarded.onRunFinalized = (params) => safeCall("onRunFinalized", fn, params);
2629
+ }
2630
+ if (sub.onRunFailed) {
2631
+ const fn = sub.onRunFailed;
2632
+ guarded.onRunFailed = (params) => safeCall("onRunFailed", fn, params);
2633
+ }
2634
+ if (sub.onRunErrorEvent) {
2635
+ const fn = sub.onRunErrorEvent;
2636
+ guarded.onRunErrorEvent = (params) => safeCall("onRunErrorEvent", fn, params);
2637
+ }
2638
+ return guarded;
2639
+ };
2640
+ for (const key of Object.keys(subscriber)) if (typeof subscriber[key] === "function" && !ALLOWED_KEYS.has(key)) {
2641
+ 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.`;
2642
+ this.logAndEmitError(message, {
2643
+ error: new Error(message),
2644
+ code: CopilotKitCoreErrorCode.SUBSCRIBER_CALLBACK_FAILED,
2645
+ context: {
2646
+ agentId: agent.agentId,
2647
+ droppedCallback: key
2648
+ }
2649
+ }, "warn");
2650
+ }
2651
+ if (effectiveMs <= 0) {
2652
+ const subscription = agent.subscribe(guardAll(subscriber));
2653
+ return { unsubscribe: () => subscription.unsubscribe() };
2654
+ }
2655
+ let active = true;
2656
+ let latestMessagesParams = null;
2657
+ let latestStateParams = null;
2658
+ const flushPending = () => {
2659
+ if (active && subscriber.onMessagesChanged && latestMessagesParams) {
2660
+ const params = latestMessagesParams;
2661
+ latestMessagesParams = null;
2662
+ safeCall("onMessagesChanged", subscriber.onMessagesChanged, params);
2663
+ }
2664
+ if (active && subscriber.onStateChanged && latestStateParams) {
2665
+ const params = latestStateParams;
2666
+ latestStateParams = null;
2667
+ safeCall("onStateChanged", subscriber.onStateChanged, params);
2668
+ }
2669
+ };
2670
+ const throttler = new Throttler(flushPending, {
2671
+ wait: effectiveMs,
2672
+ leading: true,
2673
+ trailing: true
2674
+ });
2675
+ const lifecycleOnly = {};
2676
+ if (subscriber.onRunInitialized) lifecycleOnly.onRunInitialized = subscriber.onRunInitialized;
2677
+ if (subscriber.onRunFinalized) lifecycleOnly.onRunFinalized = subscriber.onRunFinalized;
2678
+ if (subscriber.onRunFailed) lifecycleOnly.onRunFailed = subscriber.onRunFailed;
2679
+ if (subscriber.onRunErrorEvent) lifecycleOnly.onRunErrorEvent = subscriber.onRunErrorEvent;
2680
+ const wrappedSubscriber = guardAll(lifecycleOnly);
2681
+ if (subscriber.onMessagesChanged) wrappedSubscriber.onMessagesChanged = (params) => {
2682
+ latestMessagesParams = params;
2683
+ throttler.maybeExecute();
2684
+ };
2685
+ if (subscriber.onStateChanged) wrappedSubscriber.onStateChanged = (params) => {
2686
+ latestStateParams = params;
2687
+ throttler.maybeExecute();
2688
+ };
2689
+ const subscription = agent.subscribe(wrappedSubscriber);
2690
+ return { unsubscribe: () => {
2691
+ active = false;
2692
+ throttler.cancel();
2693
+ subscription.unsubscribe();
2694
+ } };
2695
+ }
2696
+ /**
2542
2697
  * Agent connectivity (delegated to RunHandler)
2543
2698
  */
2544
2699
  async connectAgent(params) {
@@ -3085,15 +3240,18 @@ const threadDomainEvents = createActionGroup("Thread Domain", {
3085
3240
  threadUpserted: props(),
3086
3241
  threadDeleted: props()
3087
3242
  });
3088
- function sortThreadsByUpdatedAt(threads) {
3089
- return [...threads].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
3243
+ function sortThreadsByRecency(threads) {
3244
+ return [...threads].sort((left, right) => {
3245
+ const leftKey = left.lastRunAt ?? left.updatedAt ?? left.createdAt;
3246
+ return (right.lastRunAt ?? right.updatedAt ?? right.createdAt).localeCompare(leftKey);
3247
+ });
3090
3248
  }
3091
3249
  function upsertThread(threads, thread) {
3092
3250
  const existingIndex = threads.findIndex((item) => item.id === thread.id);
3093
- if (existingIndex === -1) return sortThreadsByUpdatedAt([...threads, thread]);
3251
+ if (existingIndex === -1) return sortThreadsByRecency([...threads, thread]);
3094
3252
  const next = [...threads];
3095
3253
  next[existingIndex] = thread;
3096
- return sortThreadsByUpdatedAt(next);
3254
+ return sortThreadsByRecency(next);
3097
3255
  }
3098
3256
  const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.contextChanged, (state, { context }) => ({
3099
3257
  ...state,
@@ -3126,7 +3284,7 @@ const threadReducer = createReducer(initialThreadState, on(threadAdapterEvents.c
3126
3284
  if (sessionId !== state.sessionId) return state;
3127
3285
  return {
3128
3286
  ...state,
3129
- threads: sortThreadsByUpdatedAt(threads),
3287
+ threads: sortThreadsByRecency(threads),
3130
3288
  isLoading: false,
3131
3289
  error: null,
3132
3290
  metadataJoinCode: joinCode,