@copilotkit/core 1.72.1-canary.sp-longthread-perf → 1.73.2-canary.1790068297

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
@@ -380,16 +380,24 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
380
380
  requestJoinCredentials$(mode, input, replayCursor) {
381
381
  return (0, rxjs.defer)(async () => {
382
382
  try {
383
- const response = await (this.config.fetch ?? globalFetch)(this.buildRuntimeUrl(mode), {
383
+ const requestFetch = this.config.fetch ?? globalFetch;
384
+ const body = {
385
+ ...input,
386
+ ...mode === "connect" ? { lastSeenEventId: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor } : {}
387
+ };
388
+ const single = this.config.transport === "single";
389
+ const response = await requestFetch(single ? this.config.runtimeUrl : this.buildRuntimeUrl(mode), {
384
390
  method: "POST",
391
+ redirect: "error",
385
392
  headers: {
386
393
  "Content-Type": "application/json",
387
394
  ...this.headers
388
395
  },
389
- body: JSON.stringify({
390
- ...input,
391
- ...mode === "connect" ? { lastSeenEventId: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor } : {}
392
- }),
396
+ body: JSON.stringify(single ? {
397
+ method: `agent/${mode}`,
398
+ params: { agentId: this.config.agentId },
399
+ body
400
+ } : body),
393
401
  ...this.credentials ? { credentials: this.credentials } : {}
394
402
  });
395
403
  if (response.status === 204 && mode === "connect") return null;
@@ -623,6 +631,90 @@ function isRuntimeInfoRequestError(error) {
623
631
  return typeof error === "object" && error !== null && typeof error.runtimeInfoStatus === "number";
624
632
  }
625
633
 
634
+ //#endregion
635
+ //#region src/core/message-filter.ts
636
+ function toolCallIdsOf(message) {
637
+ const calls = message.toolCalls;
638
+ if (!Array.isArray(calls)) return [];
639
+ return calls.map((call) => call.id).filter((id) => Boolean(id));
640
+ }
641
+ function resultCallIdOf(message) {
642
+ if (message.role !== "tool") return void 0;
643
+ return message.toolCallId;
644
+ }
645
+ /**
646
+ * Repair the tool-call pairs a filter broke.
647
+ *
648
+ * Every provider rejects a half pair. A tool result whose call is gone reads as
649
+ * a reply to nothing, and an assistant tool call whose result is gone leaves
650
+ * the turn unanswered — the failure reporters hit on #1482 when a trimmed
651
+ * history landed mid-HITL and Anthropic answered "a tool result is expected".
652
+ * A filter written as `messages.slice(-1)` cannot know this, so the repair runs
653
+ * for every filter rather than being something each caller opts into.
654
+ *
655
+ * Both repairs restore, never discard, and both are anchored on the untrimmed
656
+ * thread:
657
+ *
658
+ * - A kept tool result whose assistant call was trimmed away gets that call put
659
+ * back in front of it. Dropping the result instead would be protocol-safe and
660
+ * semantically wrong: on a human-in-the-loop turn the result IS the new
661
+ * information, and a backend that already holds the call would be asked to
662
+ * resume with nothing.
663
+ * - A kept assistant call whose result was trimmed gets that result restored,
664
+ * placed directly after the call.
665
+ *
666
+ * Restoring a call pulls in every result for that call, not only the one the
667
+ * filter kept. Providers reject an assistant turn with a parallel tool call
668
+ * left unanswered, so a half-answered set is as invalid as a missing one.
669
+ *
670
+ * A call with no result anywhere in `full` is left alone. That is an open call
671
+ * (a pending frontend tool, an unanswered interrupt), not a broken pair. A
672
+ * result whose call is nowhere in `full` is left alone too: it was already
673
+ * unpaired before the filter ran, so it is not this function's to fix.
674
+ *
675
+ * The filter's own ordering is preserved. Restorations are the only insertions,
676
+ * and messages the filter synthesized (absent from `full`) pass through
677
+ * untouched.
678
+ */
679
+ function ɵrepairToolCallPairs(kept, full) {
680
+ const callIdToIssuer = /* @__PURE__ */ new Map();
681
+ const callIdToResult = /* @__PURE__ */ new Map();
682
+ for (const message of full) {
683
+ for (const callId of toolCallIdsOf(message)) callIdToIssuer.set(callId, message);
684
+ const resultCallId = resultCallIdOf(message);
685
+ if (resultCallId !== void 0) callIdToResult.set(resultCallId, message);
686
+ }
687
+ const repaired = [];
688
+ const emitted = /* @__PURE__ */ new Set();
689
+ const emit = (message) => {
690
+ if (emitted.has(message.id)) return;
691
+ emitted.add(message.id);
692
+ repaired.push(message);
693
+ };
694
+ /** Emit an assistant turn together with every result it is owed. */
695
+ const emitWithResults = (message) => {
696
+ emit(message);
697
+ for (const callId of toolCallIdsOf(message)) {
698
+ const result = callIdToResult.get(callId);
699
+ if (result) emit(result);
700
+ }
701
+ };
702
+ for (const message of kept) {
703
+ const resultCallId = resultCallIdOf(message);
704
+ if (resultCallId === void 0) {
705
+ emitWithResults(message);
706
+ continue;
707
+ }
708
+ const issuer = callIdToIssuer.get(resultCallId);
709
+ if (issuer) {
710
+ emitWithResults(issuer);
711
+ continue;
712
+ }
713
+ emit(message);
714
+ }
715
+ return repaired;
716
+ }
717
+
626
718
  //#endregion
627
719
  //#region src/agent.ts
628
720
  function hasHeaders(agent) {
@@ -661,6 +753,18 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
661
753
  _capabilities;
662
754
  delegate;
663
755
  runtimeInfoPromise;
756
+ _messageFilter;
757
+ /**
758
+ * The HTTP `run` this agent started and has not yet seen finish, as the
759
+ * exact `{ threadId, runId }` it POSTed. `abortRun` narrows `/stop` to this
760
+ * run only while `threadId` still matches: `threadId` is a public field the
761
+ * host may reassign under a live run, and a runId from another thread must
762
+ * not be sent as if it were this thread's. Released when the run stream
763
+ * finalizes, cleared on `connect` (a reconnected thread may run under a
764
+ * runId this client never saw) and never copied by `clone`. Without a
765
+ * provable active run the stop stays thread-wide.
766
+ */
767
+ activeRun;
664
768
  constructor(config) {
665
769
  const normalizedRuntimeUrl = config.runtimeUrl ? config.runtimeUrl.replace(/\/$/, "") : void 0;
666
770
  const transport = config.transport ?? "auto";
@@ -679,10 +783,47 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
679
783
  this.runtimeMode = config.runtimeMode ?? _copilotkit_shared.RUNTIME_MODE_SSE;
680
784
  this.intelligence = config.intelligence;
681
785
  this._capabilities = config.capabilities;
786
+ this._messageFilter = config.messageFilter;
682
787
  if (config.debug) this.debug = config.debug;
683
788
  if (this.transport === "single") this.singleEndpointUrl = this.runtimeEndpointUrl;
684
789
  }
685
790
  /**
791
+ * Adopt the runtime mode a fresh `/info` just reported.
792
+ *
793
+ * A live proxy outlives the runtime configuration that minted it: a
794
+ * redeploy, an env-var change or a rollback can flip `intelligence` → `sse`
795
+ * under a page that is already open. The registry preserves the proxy across
796
+ * that re-sync on purpose — it is backing an open conversation — so the new
797
+ * mode is pushed onto the instance here instead of replacing it.
798
+ *
799
+ * Without this the mode was decided once, at construction, and `run()` kept
800
+ * taking the delegate path against a runtime serving plain SSE, where
801
+ * `response.json()` on a `text/event-stream` body throws for the rest of the
802
+ * page's life. See #7130.
803
+ *
804
+ * A delegate built for the old mode is torn down: it speaks a protocol the
805
+ * runtime no longer serves, and it may be holding a websocket open. The
806
+ * memoized `/info` promise goes with it — it answered for the old
807
+ * configuration, so a later `ensureRuntimeConfiguration()` must re-ask
808
+ * rather than treat that answer as current.
809
+ *
810
+ * Only `wsUrl` is compared on the Intelligence metadata, because that is the
811
+ * one field the delegate bakes in at construction.
812
+ */
813
+ adoptRuntimeMode(runtimeMode, intelligence) {
814
+ const unchanged = this.runtimeMode === runtimeMode && this.intelligence?.wsUrl === intelligence?.wsUrl;
815
+ this.runtimeMode = runtimeMode;
816
+ this.intelligence = intelligence;
817
+ if (unchanged) return;
818
+ this.runtimeInfoPromise = void 0;
819
+ const staleDelegate = this.delegate;
820
+ this.delegate = void 0;
821
+ if (staleDelegate) {
822
+ staleDelegate.abortRun();
823
+ this.detachActiveRun();
824
+ }
825
+ }
826
+ /**
686
827
  * The agent id used for outbound runtime requests — `runtimeAgentId` when
687
828
  * set (manually-registered proxy), otherwise `agentId` (registry id
688
829
  * matches runtime id). Subscriber bookkeeping keeps using `agentId`
@@ -700,6 +841,67 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
700
841
  get capabilities() {
701
842
  return this._capabilities;
702
843
  }
844
+ /**
845
+ * The filter applied to the outbound message list on every run.
846
+ *
847
+ * Registry-owned. `AgentRegistry` writes it whenever the core-level filter
848
+ * changes, so an agent discovered before the app configured one still picks
849
+ * it up — and so a value written here by hand is replaced on the registry's
850
+ * next sweep. Configure it through `CopilotKitCore` instead.
851
+ */
852
+ get messageFilter() {
853
+ return this._messageFilter;
854
+ }
855
+ set messageFilter(filter) {
856
+ this._messageFilter = filter;
857
+ }
858
+ /**
859
+ * Narrow the outbound payload with the configured message filter.
860
+ *
861
+ * **Called from the HTTP paths only, and that placement is the Intelligence
862
+ * exemption.** An earlier revision applied the filter in
863
+ * `prepareRunAgentInput`, which runs before the runtime mode is resolved:
864
+ * with the default `"auto"` transport, or on a proxy still `"pending"` its
865
+ * first `/info`, `run()` prepared the input, *then* resolved the mode, then
866
+ * handed that already-filtered input to `#runViaDelegate` — so a managed
867
+ * runtime received a truncated thread despite the exemption. Filtering here
868
+ * makes that unreachable by construction: `#runViaHttp` and
869
+ * `#connectViaHttp` are only entered once the mode is known not to be
870
+ * Intelligence. Do not move this back up the call chain.
871
+ *
872
+ * The managed runtime is the store of record for the thread — the threads
873
+ * drawer and the Slack transcript read from it — so a client-side truncation
874
+ * there has a blast radius nobody asked for. Every reporter on #1482 is
875
+ * self-hosted.
876
+ *
877
+ * `prepareRunAgentInput` has already deep-cloned the thread and stripped
878
+ * `activity` messages by the time the input reaches here, so the filter
879
+ * cannot reach the messages the UI renders no matter what it does with the
880
+ * array it is handed. The filter gets a *second* deep clone on top of that,
881
+ * because `input.messages` is also the repair baseline and the untrimmed
882
+ * fallback: sharing the message objects let a filter that mutates a kept
883
+ * message's `toolCallId` corrupt the baseline, and an orphaned tool result
884
+ * then went out on the wire — a payload the provider rejects, which is
885
+ * exactly the failure this whole mechanism exists to prevent.
886
+ */
887
+ #applyMessageFilter(input) {
888
+ const filter = this._messageFilter;
889
+ if (!filter) return input;
890
+ try {
891
+ const kept = filter((0, _ag_ui_client.structuredClone_)(input.messages), { agentId: this.agentId ?? "" });
892
+ if (!Array.isArray(kept)) {
893
+ console.warn("ProxiedCopilotRuntimeAgent: messageFilter returned a non-array value; sending the full message history instead.");
894
+ return input;
895
+ }
896
+ return {
897
+ ...input,
898
+ messages: ɵrepairToolCallPairs(kept, input.messages)
899
+ };
900
+ } catch (error) {
901
+ console.warn("ProxiedCopilotRuntimeAgent: messageFilter failed; sending the full message history instead.", error);
902
+ return input;
903
+ }
904
+ }
703
905
  requestInit(input) {
704
906
  return {
705
907
  ...super.requestInit(input),
@@ -723,6 +925,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
723
925
  if (!this.agentId || !this.threadId) return;
724
926
  if (typeof fetch === "undefined") return;
725
927
  const routedId = this.routedAgentId();
928
+ const runId = this.activeRun?.threadId === this.threadId ? this.activeRun.runId : void 0;
726
929
  if (this.transport === "single") {
727
930
  if (!this.singleEndpointUrl) return;
728
931
  const headers = new Headers({
@@ -737,7 +940,8 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
737
940
  params: {
738
941
  agentId: routedId,
739
942
  threadId: this.threadId
740
- }
943
+ },
944
+ ...runId === void 0 ? {} : { body: { runId } }
741
945
  }),
742
946
  ...this.credentials ? { credentials: this.credentials } : {}
743
947
  }).catch((error) => {
@@ -756,6 +960,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
756
960
  "Content-Type": "application/json",
757
961
  ...this.headers
758
962
  },
963
+ ...runId === void 0 ? {} : { body: JSON.stringify({ runId }) },
759
964
  ...this.credentials ? { credentials: this.credentials } : {}
760
965
  }).catch((error) => {
761
966
  console.error("ProxiedCopilotRuntimeAgent: stop request failed", error);
@@ -811,7 +1016,9 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
811
1016
  #connectViaDelegate(input) {
812
1017
  return (0, rxjs.defer)(() => (0, rxjs.from)(this.resolveDelegate())).pipe((0, rxjs_operators.switchMap)((delegate) => withAbortErrorHandling(delegate.connect(input))));
813
1018
  }
814
- #connectViaHttp(input) {
1019
+ #connectViaHttp(unfiltered) {
1020
+ const input = this.#applyMessageFilter(unfiltered);
1021
+ this.activeRun = void 0;
815
1022
  const routedId = this.routedAgentId();
816
1023
  if (this.transport === "single") {
817
1024
  if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
@@ -825,13 +1032,24 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
825
1032
  #runViaDelegate(input) {
826
1033
  return (0, rxjs.defer)(() => (0, rxjs.from)(this.resolveDelegate())).pipe((0, rxjs_operators.switchMap)((delegate) => withAbortErrorHandling(delegate.run(input))));
827
1034
  }
828
- #runViaHttp(input) {
1035
+ #runViaHttp(unfiltered) {
1036
+ const input = this.#applyMessageFilter(unfiltered);
1037
+ const activeRun = {
1038
+ threadId: input.threadId,
1039
+ runId: input.runId
1040
+ };
1041
+ const trackActiveRun = (source) => {
1042
+ this.activeRun = activeRun;
1043
+ return source.pipe((0, rxjs_operators.finalize)(() => {
1044
+ if (this.activeRun === activeRun) this.activeRun = void 0;
1045
+ }));
1046
+ };
829
1047
  if (this.transport === "single") {
830
1048
  if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
831
1049
  const requestInit = this.createSingleRouteRequestInit(input, "agent/run", { agentId: this.routedAgentId() });
832
- return withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(() => this.fetch(this.singleEndpointUrl, requestInit))));
1050
+ return trackActiveRun(withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(() => this.fetch(this.singleEndpointUrl, requestInit)))));
833
1051
  }
834
- return withAbortErrorHandling(super.run(input));
1052
+ return trackActiveRun(withAbortErrorHandling(super.run(input)));
835
1053
  }
836
1054
  clone() {
837
1055
  const cloned = new ProxiedCopilotRuntimeAgent({
@@ -846,7 +1064,8 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
846
1064
  intelligence: this.intelligence,
847
1065
  capabilities: this._capabilities,
848
1066
  debug: this.debug,
849
- fetch: this.fetch
1067
+ fetch: this.fetch,
1068
+ messageFilter: this._messageFilter
850
1069
  });
851
1070
  cloned.threadId = this.threadId;
852
1071
  cloned.setState(this.state);
@@ -974,9 +1193,11 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
974
1193
  createIntelligenceDelegate() {
975
1194
  const routedId = this.routedAgentId();
976
1195
  if (!this.runtimeUrl || !routedId || !this.intelligence?.wsUrl) throw new Error("Intelligence mode requires runtimeUrl, agentId, and intelligence websocket metadata");
1196
+ const single = this.transport === "single";
977
1197
  return new IntelligenceAgent({
978
1198
  url: this.intelligence.wsUrl,
979
- runtimeUrl: this.runtimeUrl,
1199
+ runtimeUrl: single ? this.runtimeEndpointUrl ?? this.runtimeUrl : this.runtimeUrl,
1200
+ transport: single ? "single" : "rest",
980
1201
  agentId: routedId,
981
1202
  headers: { ...this.headers },
982
1203
  credentials: this.credentials,
@@ -1239,6 +1460,7 @@ var AgentRegistry = class {
1239
1460
  this.localAgents = this.assignAgentIds(agents);
1240
1461
  this.applyHeadersToAgents(this.localAgents);
1241
1462
  this.applyCredentialsToAgents(this.localAgents);
1463
+ this.applyMessageFilterToAgents(this.localAgents);
1242
1464
  this.applyRuntimeFetchToAgents(this.localAgents);
1243
1465
  this._agents = this.localAgents;
1244
1466
  }
@@ -1330,6 +1552,7 @@ var AgentRegistry = class {
1330
1552
  };
1331
1553
  this.applyHeadersToAgents(this._agents);
1332
1554
  this.applyCredentialsToAgents(this._agents);
1555
+ this.applyMessageFilterToAgents(this._agents);
1333
1556
  this.applyRuntimeFetchToAgents(this._agents);
1334
1557
  this.notifyAgentsChanged();
1335
1558
  }
@@ -1382,7 +1605,8 @@ var AgentRegistry = class {
1382
1605
  credentials: friends.credentials,
1383
1606
  runtimeMode: this._runtimeUrl ? this._runtimeConnectionStatus === CopilotKitCoreRuntimeConnectionStatus.Connected ? this._runtimeMode : "pending" : _copilotkit_shared.RUNTIME_MODE_SSE,
1384
1607
  intelligence: this._intelligence,
1385
- debug: debug ? (0, _copilotkit_shared.resolveDebugConfig)(debug) : void 0
1608
+ debug: debug ? (0, _copilotkit_shared.resolveDebugConfig)(debug) : void 0,
1609
+ messageFilter: friends.messageFilter
1386
1610
  });
1387
1611
  this.applyHeadersToAgent(agent);
1388
1612
  this.applyRuntimeFetchToAgent(agent);
@@ -1451,6 +1675,45 @@ var AgentRegistry = class {
1451
1675
  this.applyCredentialsToAgent(agent);
1452
1676
  });
1453
1677
  }
1678
+ /**
1679
+ * Carry the mode a fresh `/info` reported onto a proxy this re-sync kept.
1680
+ *
1681
+ * `canReuseRuntimeAgent` deliberately ignores the mode: a preserved proxy is
1682
+ * backing an open conversation, and replacing the instance to change one
1683
+ * field would drop it. The mode is therefore pushed onto the survivor here,
1684
+ * the same way headers and credentials are. Without it the mode was fixed at
1685
+ * construction, so a runtime that flipped `intelligence` → `sse` under an
1686
+ * open page left every later run on the delegate path. See #7130.
1687
+ */
1688
+ applyRuntimeModeToAgent(agent, runtimeInfo) {
1689
+ if (agent instanceof ProxiedCopilotRuntimeAgent) agent.adoptRuntimeMode(runtimeInfo.mode ?? _copilotkit_shared.RUNTIME_MODE_SSE, runtimeInfo.intelligence);
1690
+ }
1691
+ /**
1692
+ * Apply the core's message filter to an agent.
1693
+ *
1694
+ * The test is the class, not which bucket the agent is registered in: every
1695
+ * `ProxiedCopilotRuntimeAgent` gets the core filter, whether it came from
1696
+ * `/info` or from `registerProxiedAgent` (which files its proxy under
1697
+ * `localAgents`). Agents of any other class are left untouched — an
1698
+ * `AbstractAgent` the app built has no such hook, and its owner already has
1699
+ * the AG-UI middleware seam.
1700
+ *
1701
+ * This is the sole writer of `agent.messageFilter` in normal operation. The
1702
+ * public setter exists for the registry and for tests; a value written
1703
+ * directly onto an agent is replaced the next time the registry sweeps, the
1704
+ * same way per-agent credentials are.
1705
+ */
1706
+ applyMessageFilterToAgent(agent) {
1707
+ if (agent instanceof ProxiedCopilotRuntimeAgent) agent.messageFilter = this.core.messageFilter;
1708
+ }
1709
+ /**
1710
+ * Apply the core's message filter to all agents
1711
+ */
1712
+ applyMessageFilterToAgents(agents) {
1713
+ Object.values(agents).forEach((agent) => {
1714
+ this.applyMessageFilterToAgent(agent);
1715
+ });
1716
+ }
1454
1717
  createRuntimeFetch() {
1455
1718
  if (!this.runtimeFetch) this.runtimeFetch = (async (input, init) => {
1456
1719
  const meta = () => runtimeRequestMeta(init);
@@ -1838,7 +2101,9 @@ var AgentRegistry = class {
1838
2101
  if (existing instanceof ProxiedCopilotRuntimeAgent && this.canReuseRuntimeAgent(existing, runtimeUrl, this._runtimeTransport)) {
1839
2102
  this.applyHeadersToAgent(existing);
1840
2103
  this.applyCredentialsToAgent(existing);
2104
+ this.applyMessageFilterToAgent(existing);
1841
2105
  this.applyRuntimeFetchToAgent(existing);
2106
+ this.applyRuntimeModeToAgent(existing, runtimeInfoResponse);
1842
2107
  return [id, existing];
1843
2108
  }
1844
2109
  const agent = new ProxiedCopilotRuntimeAgent({
@@ -1850,7 +2115,8 @@ var AgentRegistry = class {
1850
2115
  runtimeMode: runtimeInfoResponse.mode ?? _copilotkit_shared.RUNTIME_MODE_SSE,
1851
2116
  intelligence: runtimeInfoResponse.intelligence,
1852
2117
  capabilities,
1853
- debug: rawDebug ? (0, _copilotkit_shared.resolveDebugConfig)(rawDebug) : void 0
2118
+ debug: rawDebug ? (0, _copilotkit_shared.resolveDebugConfig)(rawDebug) : void 0,
2119
+ messageFilter: this.core.messageFilter
1854
2120
  });
1855
2121
  this.applyHeadersToAgent(agent);
1856
2122
  this.applyRuntimeFetchToAgent(agent);
@@ -2242,7 +2508,10 @@ var SuggestionEngine = class {
2242
2508
  credentials
2243
2509
  }) } : {}
2244
2510
  });
2245
- } else suggestionAgent = suggestionsProviderAgent.clone();
2511
+ } else {
2512
+ suggestionAgent = suggestionsProviderAgent.clone();
2513
+ if (suggestionAgent instanceof ProxiedCopilotRuntimeAgent) suggestionAgent.messageFilter = void 0;
2514
+ }
2246
2515
  suggestionAgent.threadId = suggestionId;
2247
2516
  suggestionAgent.setMessages(seededMessages);
2248
2517
  suggestionAgent.setState(seededState);
@@ -2604,6 +2873,19 @@ var WebMCPRegistry = class {
2604
2873
  //#endregion
2605
2874
  //#region src/core/run-handler.ts
2606
2875
  /**
2876
+ * Live approvals cancelled by stopping their own run, keyed by the handler's
2877
+ * signal. The value reports whether that stop is still current: no reconnect
2878
+ * has started and the thread is unchanged, so nothing will restore the call.
2879
+ *
2880
+ * Kept here rather than in `AbortSignal.reason`: React Native's global
2881
+ * `AbortController` is the `abort-controller@3` polyfill, whose `abort()`
2882
+ * takes no reason and whose signals never expose one.
2883
+ */
2884
+ const liveHITLStops = /* @__PURE__ */ new WeakMap();
2885
+ function discardAbortedResult(signal, discardOnAbort, errorMessage) {
2886
+ return !!(discardOnAbort && signal?.aborted && !(liveHITLStops.get(signal)?.() && errorMessage));
2887
+ }
2888
+ /**
2607
2889
  * Absolute safety cap on the depth of recursive follow-up runs triggered by
2608
2890
  * `processAgentResult`. Without this, any scenario that keeps
2609
2891
  * `needsFollowUp = true` (e.g. the LLM repeatedly calling the same tool, a
@@ -2716,6 +2998,10 @@ var RunHandler = class {
2716
2998
  _lastConnectedThreadIdsByAgent = /* @__PURE__ */ new Map();
2717
2999
  _anonymousAgentIds = /* @__PURE__ */ new WeakMap();
2718
3000
  _nextAnonymousAgentId = 0;
3001
+ /** Prevent reconnects from starting an interaction already executing locally. */
3002
+ _executingToolCalls = /* @__PURE__ */ new WeakMap();
3003
+ _connectionGenerations = /* @__PURE__ */ new WeakMap();
3004
+ _interactionAbortControllers = /* @__PURE__ */ new WeakMap();
2719
3005
  constructor(core) {
2720
3006
  this.core = core;
2721
3007
  }
@@ -2724,8 +3010,14 @@ var RunHandler = class {
2724
3010
  * that in-flight tool handlers should stop and `processAgentResult` should
2725
3011
  * not start a follow-up run.
2726
3012
  */
2727
- abortCurrentRun() {
3013
+ abortCurrentRun(agent) {
2728
3014
  this._runAbortController?.abort();
3015
+ const replay = this._interactionAbortControllers.get(agent);
3016
+ if (replay) {
3017
+ for (const controller of replay.controllers) controller.abort();
3018
+ this._interactionAbortControllers.delete(agent);
3019
+ this._executingToolCalls.delete(agent);
3020
+ }
2729
3021
  }
2730
3022
  /**
2731
3023
  * Typed access to CopilotKitCore's internal ("friend") methods.
@@ -2881,7 +3173,14 @@ var RunHandler = class {
2881
3173
  * Connect an agent (establish initial connection)
2882
3174
  */
2883
3175
  async connectAgent({ agent }) {
3176
+ this._connectionGenerations.set(agent, {});
2884
3177
  const incomingThreadId = agent.threadId ?? null;
3178
+ const previousReplay = this._interactionAbortControllers.get(agent);
3179
+ if (previousReplay && previousReplay.threadId !== incomingThreadId) {
3180
+ for (const controller of previousReplay.controllers) controller.abort();
3181
+ this._interactionAbortControllers.delete(agent);
3182
+ this._executingToolCalls.delete(agent);
3183
+ }
2885
3184
  try {
2886
3185
  const restoreKey = this.getConnectRestoreKey(agent);
2887
3186
  const isFreshRestore = incomingThreadId !== (this._lastConnectedThreadIdsByAgent.get(restoreKey) ?? null);
@@ -2906,11 +3205,33 @@ var RunHandler = class {
2906
3205
  tools: this.buildFrontendTools(agent.agentId),
2907
3206
  context: this._internal.getContextForAgent(agent.agentId)
2908
3207
  }, this.createAgentErrorSubscriber(agent));
2909
- return this.processAgentResult({
3208
+ if ((agent.threadId ?? null) !== incomingThreadId) return runAgentResult;
3209
+ const controller = new AbortController();
3210
+ const replay = this._interactionAbortControllers.get(agent) ?? {
3211
+ threadId: incomingThreadId,
3212
+ controllers: /* @__PURE__ */ new Set()
3213
+ };
3214
+ replay.controllers.add(controller);
3215
+ this._interactionAbortControllers.set(agent, replay);
3216
+ this.processAgentResult({
2910
3217
  runAgentResult,
2911
3218
  agent,
2912
- executeFrontendTools: false
3219
+ toolExecutionMode: "human-in-the-loop",
3220
+ signal: controller.signal
3221
+ }).catch(async (error) => {
3222
+ await this._internal.emitError({
3223
+ error: error instanceof Error ? error : new Error(String(error)),
3224
+ code: CopilotKitCoreErrorCode.AGENT_CONNECT_FAILED,
3225
+ context: {
3226
+ agentId: agent.agentId,
3227
+ threadId: incomingThreadId
3228
+ }
3229
+ });
3230
+ }).finally(() => {
3231
+ replay.controllers.delete(controller);
3232
+ if (replay.controllers.size === 0 && this._interactionAbortControllers.get(agent) === replay) this._interactionAbortControllers.delete(agent);
2913
3233
  });
3234
+ return runAgentResult;
2914
3235
  } catch (error) {
2915
3236
  const connectError = error instanceof Error ? error : new Error(String(error));
2916
3237
  if (!isAbortError$1(connectError)) {
@@ -3012,45 +3333,83 @@ var RunHandler = class {
3012
3333
  /**
3013
3334
  * Process agent result and execute tools
3014
3335
  */
3015
- async processAgentResult({ runAgentResult, agent, runId, executeFrontendTools = true }) {
3336
+ async processAgentResult({ runAgentResult, agent, runId, toolExecutionMode = "all", signal = this._runAbortController?.signal }) {
3016
3337
  const { newMessages } = runAgentResult;
3017
3338
  const agentId = agent.agentId;
3018
3339
  let needsFollowUp = false;
3019
- if (executeFrontendTools) {
3020
- for (const message of newMessages) if (message.role === "assistant") for (const toolCall of message.toolCalls || []) {
3021
- const tool = this.getTool({
3022
- toolName: toolCall.function.name,
3340
+ const threadId = agent.threadId;
3341
+ const executing = this._executingToolCalls.get(agent) ?? /* @__PURE__ */ new Set();
3342
+ this._executingToolCalls.set(agent, executing);
3343
+ if (toolExecutionMode === "human-in-the-loop" && agent.messages.some((message) => message.role === "assistant" && message.toolCalls?.some((call) => executing.has(JSON.stringify([threadId, call.id])) && !agent.messages.some((result) => result.role === "tool" && result.toolCallId === call.id && !this.isFrontendPlaceholderResult(result))))) return runAgentResult;
3344
+ const messagesToProcess = toolExecutionMode === "human-in-the-loop" ? [...agent.messages] : newMessages;
3345
+ for (const message of messagesToProcess) if (message.role === "assistant") for (const toolCall of message.toolCalls || []) {
3346
+ const tool = this.getTool({
3347
+ toolName: toolCall.function.name,
3348
+ agentId: agent.agentId
3349
+ });
3350
+ let wildcardTool;
3351
+ const getWildcardTool = () => {
3352
+ if (tool || wildcardTool) return wildcardTool;
3353
+ wildcardTool = this.getTool({
3354
+ toolName: WILDCARD_TOOL_NAME,
3023
3355
  agentId: agent.agentId
3024
3356
  });
3025
- let wildcardTool;
3026
- const getWildcardTool = () => {
3027
- if (tool || wildcardTool) return wildcardTool;
3028
- wildcardTool = this.getTool({
3029
- toolName: WILDCARD_TOOL_NAME,
3030
- agentId: agent.agentId
3031
- });
3032
- return wildcardTool;
3357
+ return wildcardTool;
3358
+ };
3359
+ const executableTool = tool ?? getWildcardTool();
3360
+ if (toolExecutionMode === "human-in-the-loop" && (executableTool?.type !== "human-in-the-loop" || !executableTool.handler)) continue;
3361
+ const executionKey = JSON.stringify([threadId, toolCall.id]);
3362
+ if (signal?.aborted || agent.threadId !== threadId || executing.has(executionKey)) continue;
3363
+ const existingResultIndex = newMessages.findIndex((m) => m.role === "tool" && m.toolCallId === toolCall.id);
3364
+ let existingResult = agent.messages.find((m) => m.role === "tool" && m.toolCallId === toolCall.id) ?? newMessages[existingResultIndex];
3365
+ if (existingResult && executableTool?.handler && this.isFrontendPlaceholderResult(existingResult)) {
3366
+ if (existingResultIndex !== -1) newMessages.splice(existingResultIndex, 1);
3367
+ existingResult = void 0;
3368
+ const agentMsgIdx = agent.messages.findIndex((m) => m.role === "tool" && m.toolCallId === toolCall.id);
3369
+ if (agentMsgIdx !== -1) agent.messages.splice(agentMsgIdx, 1);
3370
+ }
3371
+ if (!existingResult) {
3372
+ executing.add(executionKey);
3373
+ const interactionController = toolExecutionMode === "all" && executableTool?.type === "human-in-the-loop" ? new AbortController() : void 0;
3374
+ const interactions = this._interactionAbortControllers.get(agent) ?? {
3375
+ threadId: threadId ?? null,
3376
+ controllers: /* @__PURE__ */ new Set()
3377
+ };
3378
+ const abortInteraction = () => {
3379
+ if (!interactionController) return;
3380
+ const connectionGeneration = this._connectionGenerations.get(agent);
3381
+ liveHITLStops.set(interactionController.signal, () => this._connectionGenerations.get(agent) === connectionGeneration && agent.threadId === threadId);
3382
+ interactionController.abort();
3033
3383
  };
3034
- let existingResultIndex = newMessages.findIndex((m) => m.role === "tool" && m.toolCallId === toolCall.id);
3035
- const existingResult = existingResultIndex === -1 ? void 0 : newMessages[existingResultIndex];
3036
- const executableTool = tool ?? getWildcardTool();
3037
- if (existingResult && executableTool?.handler && this.isFrontendPlaceholderResult(existingResult)) {
3038
- newMessages.splice(existingResultIndex, 1);
3039
- existingResultIndex = -1;
3040
- const agentMsgIdx = agent.messages.findIndex((m) => m.role === "tool" && m.toolCallId === toolCall.id);
3041
- if (agentMsgIdx !== -1) agent.messages.splice(agentMsgIdx, 1);
3384
+ if (interactionController) {
3385
+ interactions.controllers.add(interactionController);
3386
+ this._interactionAbortControllers.set(agent, interactions);
3387
+ signal?.addEventListener("abort", abortInteraction, { once: true });
3388
+ if (signal?.aborted) abortInteraction();
3042
3389
  }
3043
- if (existingResultIndex === -1) if (tool) {
3044
- if (await this.executeSpecificTool(tool, toolCall, message, agent, agentId)) needsFollowUp = true;
3045
- } else {
3046
- const fallbackTool = getWildcardTool();
3047
- if (fallbackTool) {
3048
- if (await this.executeWildcardTool(fallbackTool, toolCall, message, agent, agentId)) needsFollowUp = true;
3390
+ const executionSignal = interactionController?.signal ?? signal;
3391
+ const discardOnAbort = toolExecutionMode === "human-in-the-loop" || !!interactionController;
3392
+ try {
3393
+ if (tool) {
3394
+ if (await this.executeSpecificTool(tool, toolCall, message, agent, agentId, executionSignal, discardOnAbort)) needsFollowUp = true;
3395
+ } else {
3396
+ const fallbackTool = getWildcardTool();
3397
+ if (fallbackTool) {
3398
+ if (await this.executeWildcardTool(fallbackTool, toolCall, message, agent, agentId, executionSignal, discardOnAbort)) needsFollowUp = true;
3399
+ }
3400
+ }
3401
+ } finally {
3402
+ executing.delete(executionKey);
3403
+ if (interactionController) {
3404
+ signal?.removeEventListener("abort", abortInteraction);
3405
+ interactions.controllers.delete(interactionController);
3406
+ if (interactions.controllers.size === 0 && this._interactionAbortControllers.get(agent) === interactions) this._interactionAbortControllers.delete(agent);
3049
3407
  }
3050
3408
  }
3409
+ if (interactionController?.signal.aborted) return runAgentResult;
3051
3410
  }
3052
3411
  }
3053
- if (needsFollowUp && !this._runAbortController?.signal.aborted) if (this._runDepth >= MAX_FOLLOW_UP_DEPTH) _copilotkit_shared.logger.warn(`[CopilotKit] Follow-up depth limit (${MAX_FOLLOW_UP_DEPTH}) reached for agent "${agentId}". Stopping recursive follow-up runs to prevent an infinite loop. This usually indicates a tool that keeps requesting a follow-up (e.g. the LLM repeatedly calling the same tool). Consider setting "followUp: false" on the offending tool.`);
3412
+ if (needsFollowUp && agent.threadId === threadId && !signal?.aborted) if (this._runDepth >= MAX_FOLLOW_UP_DEPTH) _copilotkit_shared.logger.warn(`[CopilotKit] Follow-up depth limit (${MAX_FOLLOW_UP_DEPTH}) reached for agent "${agentId}". Stopping recursive follow-up runs to prevent an infinite loop. This usually indicates a tool that keeps requesting a follow-up (e.g. the LLM repeatedly calling the same tool). Consider setting "followUp: false" on the offending tool.`);
3054
3413
  else {
3055
3414
  await this._internal.waitForPendingFrameworkUpdates();
3056
3415
  const continuationHandoff = this._internal.stateManager.markNextRunAsContinuation(agent, runId);
@@ -3070,7 +3429,7 @@ var RunHandler = class {
3070
3429
  * Handles arg parsing, subscriber notifications, handler invocation, result stringification,
3071
3430
  * and error handling.
3072
3431
  */
3073
- async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId }) {
3432
+ async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId, signal = this._runAbortController?.signal, discardOnAbort = false }) {
3074
3433
  let toolCallResult = "";
3075
3434
  let errorMessage;
3076
3435
  let isArgumentError = false;
@@ -3105,7 +3464,7 @@ var RunHandler = class {
3105
3464
  const result = await tool.handler(parsedArgs, {
3106
3465
  toolCall,
3107
3466
  agent,
3108
- signal: this._runAbortController?.signal
3467
+ signal
3109
3468
  });
3110
3469
  if (result === void 0 || result === null) toolCallResult = "";
3111
3470
  else if (typeof result === "string") toolCallResult = result;
@@ -3113,7 +3472,7 @@ var RunHandler = class {
3113
3472
  } catch (error) {
3114
3473
  const handlerError = error instanceof Error ? error : new Error(String(error));
3115
3474
  errorMessage = handlerError.message;
3116
- await this._internal.emitError({
3475
+ if (!discardAbortedResult(signal, discardOnAbort, errorMessage)) await this._internal.emitError({
3117
3476
  error: handlerError,
3118
3477
  code: CopilotKitCoreErrorCode.TOOL_HANDLER_FAILED,
3119
3478
  context: {
@@ -3144,7 +3503,8 @@ var RunHandler = class {
3144
3503
  /**
3145
3504
  * Execute a specific tool
3146
3505
  */
3147
- async executeSpecificTool(tool, toolCall, message, agent, agentId) {
3506
+ async executeSpecificTool(tool, toolCall, message, agent, agentId, signal, discardOnAbort = false) {
3507
+ const threadId = agent.threadId;
3148
3508
  if (tool?.agentId && tool.agentId !== agent.agentId) return false;
3149
3509
  let handlerResult = {
3150
3510
  result: "",
@@ -3158,11 +3518,13 @@ var RunHandler = class {
3158
3518
  agentId,
3159
3519
  handlerArgs: toolCall.function.arguments,
3160
3520
  toolType: "specific",
3161
- messageId: message.id
3521
+ messageId: message.id,
3522
+ signal,
3523
+ discardOnAbort
3162
3524
  });
3163
3525
  {
3164
3526
  const messageIndex = agent.messages.findIndex((m) => m.id === message.id);
3165
- if (messageIndex === -1) return false;
3527
+ if (discardAbortedResult(signal, discardOnAbort, handlerResult.error) || agent.threadId !== threadId || messageIndex === -1 || agent.messages.some((m) => m.role === "tool" && m.toolCallId === toolCall.id && !this.isFrontendPlaceholderResult(m))) return false;
3166
3528
  let insertAt = messageIndex + 1;
3167
3529
  while (insertAt < agent.messages.length && agent.messages[insertAt]?.role === "tool") insertAt++;
3168
3530
  const toolMessage = {
@@ -3182,7 +3544,8 @@ var RunHandler = class {
3182
3544
  * specific tools, so this method keeps its own arg-wrapping logic rather than
3183
3545
  * delegating to `executeToolHandler`.
3184
3546
  */
3185
- async executeWildcardTool(wildcardTool, toolCall, message, agent, agentId) {
3547
+ async executeWildcardTool(wildcardTool, toolCall, message, agent, agentId, signal, discardOnAbort = false) {
3548
+ const threadId = agent.threadId;
3186
3549
  if (wildcardTool?.agentId && wildcardTool.agentId !== agent.agentId) return false;
3187
3550
  let toolCallResult = "";
3188
3551
  let errorMessage;
@@ -3220,7 +3583,8 @@ var RunHandler = class {
3220
3583
  if (!errorMessage) try {
3221
3584
  const result = await wildcardTool.handler(wildcardArgs, {
3222
3585
  toolCall,
3223
- agent
3586
+ agent,
3587
+ signal
3224
3588
  });
3225
3589
  if (result === void 0 || result === null) toolCallResult = "";
3226
3590
  else if (typeof result === "string") toolCallResult = result;
@@ -3228,7 +3592,7 @@ var RunHandler = class {
3228
3592
  } catch (error) {
3229
3593
  const handlerError = error instanceof Error ? error : new Error(String(error));
3230
3594
  errorMessage = handlerError.message;
3231
- await this._internal.emitError({
3595
+ if (!discardAbortedResult(signal, discardOnAbort, errorMessage)) await this._internal.emitError({
3232
3596
  error: handlerError,
3233
3597
  code: CopilotKitCoreErrorCode.TOOL_HANDLER_FAILED,
3234
3598
  context: {
@@ -3253,7 +3617,7 @@ var RunHandler = class {
3253
3617
  }
3254
3618
  {
3255
3619
  const messageIndex = agent.messages.findIndex((m) => m.id === message.id);
3256
- if (messageIndex === -1) return false;
3620
+ if (discardAbortedResult(signal, discardOnAbort, errorMessage) || agent.threadId !== threadId || messageIndex === -1 || agent.messages.some((m) => m.role === "tool" && m.toolCallId === toolCall.id && !this.isFrontendPlaceholderResult(m))) return false;
3257
3621
  let insertAt = messageIndex + 1;
3258
3622
  while (insertAt < agent.messages.length && agent.messages[insertAt]?.role === "tool") insertAt++;
3259
3623
  const toolMessage = {
@@ -5038,6 +5402,7 @@ function normalizeHeaders(headers) {
5038
5402
  var CopilotKitCore = class {
5039
5403
  _headers;
5040
5404
  _credentials;
5405
+ _messageFilter;
5041
5406
  _properties;
5042
5407
  _defaultThrottleMs;
5043
5408
  _debug;
@@ -5062,9 +5427,10 @@ var CopilotKitCore = class {
5062
5427
  * store that was registered prior to that initial notification.
5063
5428
  */
5064
5429
  previousAgentIds = /* @__PURE__ */ new Set();
5065
- constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug }) {
5430
+ constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, messageFilter, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug }) {
5066
5431
  this._headers = normalizeHeaders(headers);
5067
5432
  this._credentials = credentials;
5433
+ this._messageFilter = messageFilter;
5068
5434
  this._properties = properties;
5069
5435
  this._debug = debug;
5070
5436
  this.agentRegistry = new AgentRegistry(this);
@@ -5184,6 +5550,9 @@ var CopilotKitCore = class {
5184
5550
  get credentials() {
5185
5551
  return this._credentials;
5186
5552
  }
5553
+ get messageFilter() {
5554
+ return this._messageFilter;
5555
+ }
5187
5556
  get properties() {
5188
5557
  return this._properties;
5189
5558
  }
@@ -5329,6 +5698,17 @@ var CopilotKitCore = class {
5329
5698
  this.agentRegistry.applyCredentialsToAgents(this.agentRegistry.agents);
5330
5699
  this.agentRegistry.handleCredentialsChanged();
5331
5700
  }
5701
+ /**
5702
+ * Replace the message filter applied to every runtime agent.
5703
+ *
5704
+ * Applies to agents already discovered as well as ones discovered later, so
5705
+ * a filter set before `/info` lands is not lost. Pass `undefined` to go back
5706
+ * to sending the full thread.
5707
+ */
5708
+ setMessageFilter(messageFilter) {
5709
+ this._messageFilter = messageFilter;
5710
+ this.agentRegistry.applyMessageFilterToAgents(this.agentRegistry.agents);
5711
+ }
5332
5712
  setProperties(properties) {
5333
5713
  this._properties = properties;
5334
5714
  this.notifySubscribers((subscriber) => subscriber.onPropertiesChanged?.({
@@ -5677,7 +6057,7 @@ var CopilotKitCore = class {
5677
6057
  return this.runHandler.connectAgent(params);
5678
6058
  }
5679
6059
  stopAgent(params) {
5680
- this.runHandler.abortCurrentRun();
6060
+ this.runHandler.abortCurrentRun(params.agent);
5681
6061
  params.agent.abortRun();
5682
6062
  }
5683
6063
  async runAgent(params) {