@copilotkit/core 1.57.0 → 1.57.1-canary.1778272612

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
@@ -254,10 +254,22 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
254
254
  * Reconnect to an existing thread by fetching websocket credentials and
255
255
  * joining the realtime thread channel.
256
256
  */
257
+ /**
258
+ * Reconnect to an existing thread by fetching websocket credentials
259
+ * and joining the realtime thread channel.
260
+ *
261
+ * Note: this method does NOT clear the replay cursor. Whether to
262
+ * request a full historical replay vs. resume from
263
+ * `lastSeenEventId` is a decision the caller (typically
264
+ * `RunHandler.connectAgent`) makes by calling
265
+ * {@link clearReconnectCursor} ahead of time on a fresh thread
266
+ * restore. Same-thread churn re-connects preserve the cursor so the
267
+ * gateway only streams events past it instead of replaying the
268
+ * entire history every time the chat re-opens a socket.
269
+ */
257
270
  connect(input) {
258
271
  this.threadId = input.threadId;
259
272
  this.canonicalRunId = null;
260
- this.clearReconnectCursor(input.threadId);
261
273
  return defer(() => this.requestJoinCredentials$("connect", input)).pipe(switchMap((credentials) => {
262
274
  if (credentials === null) return EMPTY;
263
275
  const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: false });
@@ -439,6 +451,14 @@ var IntelligenceAgent = class IntelligenceAgent extends AbstractAgent {
439
451
  getReconnectCursor(input) {
440
452
  return this.getLastSeenEventId(input.threadId);
441
453
  }
454
+ /**
455
+ * Drop the cached `lastSeenEventId` cursor for `threadId` so the
456
+ * next connect to that topic asks the gateway for a full historical
457
+ * replay (rather than resuming). Public because
458
+ * `RunHandler.connectAgent` calls it on a detected thread switch
459
+ * to rebuild local state from scratch, and skips it on same-thread
460
+ * churn so the gateway can resume.
461
+ */
442
462
  clearReconnectCursor(threadId) {
443
463
  this.sharedState.lastSeenEventIds.delete(threadId);
444
464
  }
@@ -501,6 +521,7 @@ function withAbortErrorHandling(observable) {
501
521
  var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAgent {
502
522
  runtimeUrl;
503
523
  credentials;
524
+ runtimeAgentId;
504
525
  transport;
505
526
  singleEndpointUrl;
506
527
  runtimeMode;
@@ -511,7 +532,8 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
511
532
  constructor(config) {
512
533
  const normalizedRuntimeUrl = config.runtimeUrl ? config.runtimeUrl.replace(/\/$/, "") : void 0;
513
534
  const transport = config.transport ?? "auto";
514
- const runUrl = transport === "single" ? normalizedRuntimeUrl ?? config.runtimeUrl ?? "" : `${normalizedRuntimeUrl ?? config.runtimeUrl}/agent/${encodeURIComponent(config.agentId ?? "")}/run`;
535
+ const routedId = config.runtimeAgentId ?? config.agentId ?? "";
536
+ const runUrl = transport === "single" ? normalizedRuntimeUrl ?? config.runtimeUrl ?? "" : `${normalizedRuntimeUrl ?? config.runtimeUrl}/agent/${encodeURIComponent(routedId)}/run`;
515
537
  if (!runUrl) throw new Error("ProxiedCopilotRuntimeAgent requires a runtimeUrl when transport is set to 'single'.");
516
538
  super({
517
539
  ...config,
@@ -519,6 +541,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
519
541
  });
520
542
  this.runtimeUrl = normalizedRuntimeUrl ?? config.runtimeUrl;
521
543
  this.credentials = config.credentials;
544
+ this.runtimeAgentId = config.runtimeAgentId;
522
545
  this.transport = transport;
523
546
  this.runtimeMode = config.runtimeMode ?? RUNTIME_MODE_SSE;
524
547
  this.intelligence = config.intelligence;
@@ -526,6 +549,21 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
526
549
  if (config.debug) this.debug = config.debug;
527
550
  if (this.transport === "single") this.singleEndpointUrl = this.runtimeUrl;
528
551
  }
552
+ /**
553
+ * The agent id used for outbound runtime requests — `runtimeAgentId` when
554
+ * set (manually-registered proxy), otherwise `agentId` (registry id
555
+ * matches runtime id). Subscriber bookkeeping keeps using `agentId`
556
+ * directly.
557
+ *
558
+ * Throws when both are unset: a proxy reaching an HTTP path with no
559
+ * routable id is a bug, and a missing id would otherwise produce a
560
+ * malformed `/agent//run` or `/agent/undefined/connect` URL silently.
561
+ */
562
+ routedAgentId() {
563
+ const id = this.runtimeAgentId ?? this.agentId;
564
+ if (!id) throw new Error("ProxiedCopilotRuntimeAgent: cannot make a runtime request without an agentId or runtimeAgentId.");
565
+ return id;
566
+ }
529
567
  get capabilities() {
530
568
  return this._capabilities;
531
569
  }
@@ -545,6 +583,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
545
583
  }
546
584
  if (!this.agentId || !this.threadId) return;
547
585
  if (typeof fetch === "undefined") return;
586
+ const routedId = this.routedAgentId();
548
587
  if (this.transport === "single") {
549
588
  if (!this.singleEndpointUrl) return;
550
589
  const headers = new Headers({
@@ -557,7 +596,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
557
596
  body: JSON.stringify({
558
597
  method: "agent/stop",
559
598
  params: {
560
- agentId: this.agentId,
599
+ agentId: routedId,
561
600
  threadId: this.threadId
562
601
  }
563
602
  }),
@@ -568,7 +607,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
568
607
  return;
569
608
  }
570
609
  if (!this.runtimeUrl) return;
571
- const stopPath = `${this.runtimeUrl}/agent/${encodeURIComponent(this.agentId)}/stop/${encodeURIComponent(this.threadId)}`;
610
+ const stopPath = `${this.runtimeUrl}/agent/${encodeURIComponent(routedId)}/stop/${encodeURIComponent(this.threadId)}`;
572
611
  const origin = typeof window !== "undefined" && window.location ? window.location.origin : "http://localhost";
573
612
  const base = new URL(this.runtimeUrl, origin);
574
613
  const stopUrl = new URL(stopPath, base);
@@ -632,12 +671,13 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
632
671
  return defer(() => from(this.resolveDelegate())).pipe(switchMap$1((delegate) => withAbortErrorHandling(delegate.connect(input))));
633
672
  }
634
673
  #connectViaHttp(input) {
674
+ const routedId = this.routedAgentId();
635
675
  if (this.transport === "single") {
636
676
  if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
637
- const requestInit = this.createSingleRouteRequestInit(input, "agent/connect", { agentId: this.agentId });
677
+ const requestInit = this.createSingleRouteRequestInit(input, "agent/connect", { agentId: routedId });
638
678
  return withAbortErrorHandling(transformHttpEventStream(runHttpRequest(this.singleEndpointUrl, requestInit)));
639
679
  }
640
- return withAbortErrorHandling(transformHttpEventStream(runHttpRequest(`${this.runtimeUrl}/agent/${this.agentId}/connect`, this.requestInit(input))));
680
+ return withAbortErrorHandling(transformHttpEventStream(runHttpRequest(`${this.runtimeUrl}/agent/${routedId}/connect`, this.requestInit(input))));
641
681
  }
642
682
  #runViaDelegate(input) {
643
683
  return defer(() => from(this.resolveDelegate())).pipe(switchMap$1((delegate) => withAbortErrorHandling(delegate.run(input))));
@@ -645,7 +685,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
645
685
  #runViaHttp(input) {
646
686
  if (this.transport === "single") {
647
687
  if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
648
- const requestInit = this.createSingleRouteRequestInit(input, "agent/run", { agentId: this.agentId });
688
+ const requestInit = this.createSingleRouteRequestInit(input, "agent/run", { agentId: this.routedAgentId() });
649
689
  return withAbortErrorHandling(transformHttpEventStream(runHttpRequest(this.singleEndpointUrl, requestInit)));
650
690
  }
651
691
  return withAbortErrorHandling(super.run(input));
@@ -654,6 +694,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
654
694
  const cloned = new ProxiedCopilotRuntimeAgent({
655
695
  runtimeUrl: this.runtimeUrl,
656
696
  agentId: this.agentId,
697
+ runtimeAgentId: this.runtimeAgentId,
657
698
  description: this.description,
658
699
  headers: { ...this.headers },
659
700
  credentials: this.credentials,
@@ -672,6 +713,22 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
672
713
  }
673
714
  return cloned;
674
715
  }
716
+ /**
717
+ * Drop the delegate's cached `lastSeenEventId` for this thread so
718
+ * the next connect requests a full historical replay from the
719
+ * gateway. Used by `RunHandler.connectAgent` on a detected thread
720
+ * switch (the chat moved between threads, so its local
721
+ * messages/state are about to be cleared and need rebuilding from
722
+ * the gateway). Skipped on same-thread churn re-connects so the
723
+ * gateway can resume from the cursor instead.
724
+ *
725
+ * No-op for non-Intelligence runtime modes — the HTTP transport
726
+ * doesn't replay.
727
+ */
728
+ clearReplayCursor(threadId) {
729
+ if (this.runtimeMode !== RUNTIME_MODE_INTELLIGENCE) return;
730
+ this.delegate?.clearReconnectCursor?.(threadId);
731
+ }
675
732
  async resolveDelegate() {
676
733
  await this.ensureRuntimeMode();
677
734
  if (!this.delegate) {
@@ -762,17 +819,18 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends HttpAg
762
819
  };
763
820
  }
764
821
  createIntelligenceDelegate() {
765
- if (!this.runtimeUrl || !this.agentId || !this.intelligence?.wsUrl) throw new Error("Intelligence mode requires runtimeUrl, agentId, and intelligence websocket metadata");
822
+ const routedId = this.routedAgentId();
823
+ if (!this.runtimeUrl || !routedId || !this.intelligence?.wsUrl) throw new Error("Intelligence mode requires runtimeUrl, agentId, and intelligence websocket metadata");
766
824
  return new IntelligenceAgent({
767
825
  url: this.intelligence.wsUrl,
768
826
  runtimeUrl: this.runtimeUrl,
769
- agentId: this.agentId,
827
+ agentId: routedId,
770
828
  headers: { ...this.headers },
771
829
  credentials: this.credentials
772
830
  });
773
831
  }
774
832
  syncDelegate(delegate) {
775
- delegate.agentId = this.agentId;
833
+ delegate.agentId = this.routedAgentId();
776
834
  delegate.description = this.description;
777
835
  delegate.threadId = this.threadId;
778
836
  delegate.setMessages(this.messages);
@@ -906,6 +964,52 @@ var AgentRegistry = class {
906
964
  this.notifyAgentsChanged();
907
965
  }
908
966
  /**
967
+ * Register a proxied agent that routes outbound runtime requests to an
968
+ * existing runtime agent (`runtimeAgentId`) while exposing a distinct local
969
+ * registry id (`agentId`). Throws if `agentId` is already taken by either a
970
+ * local or runtime-discovered agent.
971
+ *
972
+ * Use this to mount multiple frontend agents against a single runtime
973
+ * agent (e.g. a chat-1 / chat-2 pair both proxying to "default") without
974
+ * implicit per-thread cloning. The returned `unregister` removes the proxy
975
+ * from the registry and emits `onAgentsChanged`.
976
+ */
977
+ registerProxiedAgent({ agentId, runtimeAgentId }) {
978
+ if (Object.prototype.hasOwnProperty.call(this._agents, agentId)) throw new Error(`CopilotKitCore.registerProxiedAgent: agentId "${agentId}" is already registered. Pick a different agentId, or unregister the existing agent first.`);
979
+ const friends = this.core;
980
+ const debug = friends.debug;
981
+ const agent = new ProxiedCopilotRuntimeAgent({
982
+ runtimeUrl: this._runtimeUrl,
983
+ agentId,
984
+ runtimeAgentId,
985
+ transport: this._runtimeTransport,
986
+ credentials: friends.credentials,
987
+ runtimeMode: this._runtimeUrl ? this._runtimeConnectionStatus === CopilotKitCoreRuntimeConnectionStatus.Connected ? this._runtimeMode : "pending" : RUNTIME_MODE_SSE,
988
+ intelligence: this._intelligence,
989
+ debug: debug ? resolveDebugConfig(debug) : void 0
990
+ });
991
+ this.applyHeadersToAgent(agent);
992
+ this.localAgents[agentId] = agent;
993
+ this._agents = {
994
+ ...this.localAgents,
995
+ ...this.remoteAgents
996
+ };
997
+ this.notifyAgentsChanged();
998
+ return {
999
+ agent,
1000
+ unregister: () => {
1001
+ if (this.localAgents[agentId] === agent) {
1002
+ delete this.localAgents[agentId];
1003
+ this._agents = {
1004
+ ...this.localAgents,
1005
+ ...this.remoteAgents
1006
+ };
1007
+ this.notifyAgentsChanged();
1008
+ }
1009
+ }
1010
+ };
1011
+ }
1012
+ /**
909
1013
  * Get an agent by ID
910
1014
  */
911
1015
  getAgent(id) {
@@ -1185,16 +1289,10 @@ var SuggestionEngine = class {
1185
1289
  /**
1186
1290
  * Reload suggestions for a specific agent
1187
1291
  * This triggers generation of new suggestions based on current configs
1188
- *
1189
- * @param agentId - The consumer agent ID
1190
- * @param consumerAgent - Optional: the specific agent instance whose messages should be used
1191
- * for availability filtering and context. When running with per-thread clones, the thread
1192
- * clone holds the conversation messages; passing it here ensures dynamic suggestions fire
1193
- * after the first message even though the registry agent has an empty message list.
1194
1292
  */
1195
- reloadSuggestions(agentId, consumerAgent) {
1293
+ reloadSuggestions(agentId) {
1196
1294
  this.clearSuggestions(agentId);
1197
- const agent = consumerAgent ?? this.core.getAgent(agentId);
1295
+ const agent = this.core.getAgent(agentId);
1198
1296
  const messageCount = agent?.messages?.length ?? 0;
1199
1297
  let hasAnySuggestions = false;
1200
1298
  for (const config of Object.values(this._suggestionsConfig)) {
@@ -1207,7 +1305,7 @@ var SuggestionEngine = class {
1207
1305
  hasAnySuggestions = true;
1208
1306
  this.notifySuggestionsStartedLoading(agentId);
1209
1307
  }
1210
- this.generateSuggestions(suggestionId, config, agentId, agent);
1308
+ this.generateSuggestions(suggestionId, config, agentId);
1211
1309
  } else if (isStaticSuggestionsConfig(config)) this.addStaticSuggestions(suggestionId, config, agentId);
1212
1310
  }
1213
1311
  }
@@ -1235,12 +1333,12 @@ var SuggestionEngine = class {
1235
1333
  /**
1236
1334
  * Generate suggestions using a provider agent
1237
1335
  */
1238
- async generateSuggestions(suggestionId, config, consumerAgentId, consumerAgent) {
1336
+ async generateSuggestions(suggestionId, config, consumerAgentId) {
1239
1337
  let agent = void 0;
1240
1338
  try {
1241
1339
  const suggestionsProviderAgent = this.core.getAgent(config.providerAgentId ?? "default");
1242
1340
  if (!suggestionsProviderAgent) throw new Error(`Suggestions provider agent not found: ${config.providerAgentId}`);
1243
- const suggestionsConsumerAgent = consumerAgent ?? this.core.getAgent(consumerAgentId);
1341
+ const suggestionsConsumerAgent = this.core.getAgent(consumerAgentId);
1244
1342
  if (!suggestionsConsumerAgent) throw new Error(`Suggestions consumer agent not found: ${consumerAgentId}`);
1245
1343
  agent = suggestionsProviderAgent.clone();
1246
1344
  agent.threadId = suggestionId;
@@ -1468,6 +1566,23 @@ var RunHandler = class {
1468
1566
  * `processAgentResult`.
1469
1567
  */
1470
1568
  _runDepth = 0;
1569
+ /**
1570
+ * Tracks the threadId of the most recent `connectAgent` call so we
1571
+ * can distinguish a fresh thread restore (different threadId than
1572
+ * last time — chat is rebuilding state from scratch, must clear
1573
+ * messages/state and ask the gateway for a full replay) from a
1574
+ * same-thread churn re-connect (effect-dep churn or transient
1575
+ * disconnect — local messages/state are still meaningful, must
1576
+ * preserve them and let the gateway resume from
1577
+ * `lastSeenEventId`).
1578
+ *
1579
+ * Tyler's bug fired because every `connectAgent` was treated as a
1580
+ * fresh restore, which forced the gateway to replay the full
1581
+ * thread history on every churn re-connect and amplified the
1582
+ * downstream churn into duplicate `cpki_event_id` rows in the
1583
+ * inspector and intermittent "Message not found" toasts.
1584
+ */
1585
+ _lastConnectedThreadId = null;
1471
1586
  constructor(core) {
1472
1587
  this.core = core;
1473
1588
  }
@@ -1541,14 +1656,17 @@ var RunHandler = class {
1541
1656
  */
1542
1657
  async connectAgent({ agent }) {
1543
1658
  try {
1659
+ const incomingThreadId = agent.threadId ?? null;
1660
+ const isFreshRestore = incomingThreadId !== this._lastConnectedThreadId;
1661
+ this._lastConnectedThreadId = incomingThreadId;
1544
1662
  await agent.detachActiveRun();
1545
- agent.setMessages([]);
1546
- agent.setState({});
1663
+ if (isFreshRestore) {
1664
+ agent.setMessages([]);
1665
+ agent.setState({});
1666
+ const proxied = agent;
1667
+ if (incomingThreadId && typeof proxied.clearReplayCursor === "function") proxied.clearReplayCursor(incomingThreadId);
1668
+ }
1547
1669
  if (agent instanceof HttpAgent) agent.headers = { ...this._internal.headers };
1548
- await this._internal.notifySubscribers((subscriber) => subscriber.onAgentRunStarted?.({
1549
- copilotkit: this.core,
1550
- agent
1551
- }), "Subscriber onAgentRunStarted error:");
1552
1670
  const runAgentResult = await agent.connectAgent({
1553
1671
  forwardedProps: this._internal.properties,
1554
1672
  tools: this.buildFrontendTools(agent.agentId),
@@ -1579,11 +1697,6 @@ var RunHandler = class {
1579
1697
  if (agent.agentId) this._internal.suggestionEngine.clearSuggestions(agent.agentId);
1580
1698
  if (agent instanceof HttpAgent) agent.headers = { ...this._internal.headers };
1581
1699
  if (agent.detachActiveRun) await agent.detachActiveRun();
1582
- this._internal.subscribeAgentToStateManager(agent);
1583
- await this._internal.notifySubscribers((subscriber) => subscriber.onAgentRunStarted?.({
1584
- copilotkit: this.core,
1585
- agent
1586
- }), "Subscriber onAgentRunStarted error:");
1587
1700
  const isTopLevel = this._runDepth === 0;
1588
1701
  let originalAbortRun;
1589
1702
  if (isTopLevel) {
@@ -1654,7 +1767,7 @@ var RunHandler = class {
1654
1767
  await this._internal.waitForPendingFrameworkUpdates();
1655
1768
  return await this.runAgent({ agent });
1656
1769
  }
1657
- this._internal.suggestionEngine.reloadSuggestions(agentId, agent);
1770
+ this._internal.suggestionEngine.reloadSuggestions(agentId);
1658
1771
  return runAgentResult;
1659
1772
  }
1660
1773
  /**
@@ -2066,21 +2179,14 @@ var StateManager = class {
2066
2179
  initialize() {}
2067
2180
  /**
2068
2181
  * Subscribe to an agent's events to track state and messages.
2069
- *
2070
- * Registry agents (subscribed via `onAgentsChanged`) use the bare `agentId`
2071
- * key so that `unsubscribeFromAgent(agentId)` can remove them when they
2072
- * are replaced. Per-thread clones (subscribed via `subscribeAgentToStateManager`)
2073
- * pass `{ isClone: true }` to use a composite `agentId:threadId` key, keeping
2074
- * their subscription independent of the registry agent's.
2075
2182
  */
2076
- subscribeToAgent(agent, { isClone = false } = {}) {
2183
+ subscribeToAgent(agent) {
2077
2184
  if (!agent.agentId) return;
2078
2185
  const agentId = agent.agentId;
2079
- const subscriptionKey = isClone && agent.threadId ? `${agentId}:${agent.threadId}` : agentId;
2080
- const existingUnsubscribe = this.agentSubscriptions.get(subscriptionKey);
2186
+ const existingUnsubscribe = this.agentSubscriptions.get(agentId);
2081
2187
  if (existingUnsubscribe) {
2082
2188
  existingUnsubscribe();
2083
- this.agentSubscriptions.delete(subscriptionKey);
2189
+ this.agentSubscriptions.delete(agentId);
2084
2190
  }
2085
2191
  let revoked = false;
2086
2192
  let subRunId;
@@ -2124,16 +2230,13 @@ var StateManager = class {
2124
2230
  this.handleNewMessage(agent, message, input ? effectiveInput(input) : void 0);
2125
2231
  }
2126
2232
  });
2127
- this.agentSubscriptions.set(subscriptionKey, () => {
2233
+ this.agentSubscriptions.set(agentId, () => {
2128
2234
  revoked = true;
2129
2235
  unsubscribe();
2130
2236
  });
2131
2237
  }
2132
2238
  /**
2133
- * Unsubscribe a registry agent's subscription (bare `agentId` key).
2134
- * Per-thread clone subscriptions use composite `agentId:threadId` keys and
2135
- * are replaced (not removed) by subsequent subscribeToAgent(agent, { isClone: true })
2136
- * calls for the same (agentId, threadId) pair.
2239
+ * Unsubscribe an agent's subscription.
2137
2240
  */
2138
2241
  unsubscribeFromAgent(agentId) {
2139
2242
  const unsubscribe = this.agentSubscriptions.get(agentId);
@@ -2418,7 +2521,16 @@ var CopilotKitCore = class {
2418
2521
  if (agent.agentId) this.stateManager.subscribeToAgent(agent);
2419
2522
  });
2420
2523
  const currentAgentIds = new Set(Object.keys(agents));
2421
- for (const agentId of Object.keys(this.threadStoreRegistry.getAll())) if (this.previousAgentIds.has(agentId) && !currentAgentIds.has(agentId)) this.threadStoreRegistry.unregister(agentId);
2524
+ for (const agentId of Object.keys(this.threadStoreRegistry.getAll())) if (this.previousAgentIds.has(agentId) && !currentAgentIds.has(agentId)) try {
2525
+ this.threadStoreRegistry.unregister(agentId);
2526
+ } catch (err) {
2527
+ console.error(`CopilotKitCore.onAgentsChanged: threadStoreRegistry.unregister failed for "${agentId}":`, err);
2528
+ }
2529
+ for (const agentId of this.previousAgentIds) if (!currentAgentIds.has(agentId)) try {
2530
+ this.stateManager.unsubscribeFromAgent(agentId);
2531
+ } catch (err) {
2532
+ console.error(`CopilotKitCore.onAgentsChanged: stateManager.unsubscribeFromAgent failed for "${agentId}":`, err);
2533
+ }
2422
2534
  this.previousAgentIds = currentAgentIds;
2423
2535
  } });
2424
2536
  }
@@ -2581,6 +2693,28 @@ var CopilotKitCore = class {
2581
2693
  removeAgent__unsafe_dev_only(id) {
2582
2694
  this.agentRegistry.removeAgent__unsafe_dev_only(id);
2583
2695
  }
2696
+ /**
2697
+ * Register a proxied agent against an existing runtime agent. The proxy is
2698
+ * exposed under `agentId` (local registry id) and routes outbound runtime
2699
+ * requests to `runtimeAgentId`. Throws if `agentId` is already taken.
2700
+ *
2701
+ * Returns the minted proxy and an `unregister` handle for cleanup.
2702
+ *
2703
+ * Use this to mount multiple frontend agents against a single runtime
2704
+ * agent (e.g. one per chat window) without implicit per-thread cloning.
2705
+ *
2706
+ * @example
2707
+ * const { agent, unregister } = copilotkit.registerProxiedAgent({
2708
+ * agentId: "chat-1",
2709
+ * runtimeAgentId: "default",
2710
+ * });
2711
+ * // ... <CopilotChat agentId="chat-1" />
2712
+ * // on cleanup:
2713
+ * unregister();
2714
+ */
2715
+ registerProxiedAgent(params) {
2716
+ return this.agentRegistry.registerProxiedAgent(params);
2717
+ }
2584
2718
  getAgent(id) {
2585
2719
  return this.agentRegistry.getAgent(id);
2586
2720
  }
@@ -2814,9 +2948,6 @@ var CopilotKitCore = class {
2814
2948
  getRunIdsForThread(agentId, threadId) {
2815
2949
  return this.stateManager.getRunIdsForThread(agentId, threadId);
2816
2950
  }
2817
- subscribeAgentToStateManager(agent) {
2818
- this.stateManager.subscribeToAgent(agent, { isClone: true });
2819
- }
2820
2951
  /**
2821
2952
  * Internal method used by RunHandler to build frontend tools
2822
2953
  */