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