@copilotkit/core 1.75.2 → 1.77.0

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
@@ -6,7 +6,25 @@ let rxjs = require("rxjs");
6
6
  let rxjs_operators = require("rxjs/operators");
7
7
  let phoenix = require("phoenix");
8
8
  let zod_to_json_schema = require("zod-to-json-schema");
9
+ let _copilotkit_learning = require("@copilotkit/learning");
9
10
 
11
+ //#region src/utils/http-agent.ts
12
+ /**
13
+ * Whether an agent is an `HttpAgent`, including one built from another copy of
14
+ * `@ag-ui/client` (an app that pins its own 0.x version next to CopilotKit's
15
+ * 1.0, for example). `instanceof` is false across package copies, which made
16
+ * CopilotKit silently skip headers and abort wiring for those agents. The
17
+ * fallback checks the public fields CopilotKit uses, which every published
18
+ * `HttpAgent` has.
19
+ */
20
+ function ɵisHttpAgent(agent) {
21
+ if (agent instanceof _ag_ui_client.HttpAgent) return true;
22
+ if (typeof agent !== "object" || agent === null) return false;
23
+ const candidate = agent;
24
+ return typeof candidate.url === "string" && typeof candidate.headers === "object" && candidate.headers !== null && candidate.abortController instanceof AbortController && typeof candidate.runAgent === "function";
25
+ }
26
+
27
+ //#endregion
10
28
  //#region src/utils/connect-replay.ts
11
29
  /**
12
30
  * Runs an agent's `connect()` stream through the AbstractAgent apply pipeline
@@ -23,18 +41,27 @@ let zod_to_json_schema = require("zod-to-json-schema");
23
41
  * Cannot send event type 'RUN_STARTED': The run has already errored with
24
42
  * 'RUN_ERROR'. No further events can be sent.
25
43
  *
26
- * `transformChunks` is still applied — message reassembly is needed either way.
44
+ * The AG-UI 1.0 compatibility boundary still runs first, so stored 0.x history
45
+ * is translated (THINKING_* events, `binary` parts, legacy nulls) exactly as in
46
+ * the base pipeline. `enforceEvents` is omitted too: a replay is stored
47
+ * history, and one event that the 1.0 schema rejects (the Intelligence gateway
48
+ * replays RUN_STARTED without `runId`, for example) must not stop the whole
49
+ * thread from hydrating. That keeps the leniency this path had before 1.0.
27
50
  *
28
51
  * Connection-local replay hooks track the phase before applying events. An
29
52
  * explicitly live RUN_ERROR clears busy without closing the connection;
30
53
  * historical errors remain data. Connections without hooks retain the existing
31
- * completion behavior. Subscriber, detach, and result contracts are preserved.
54
+ * completion behavior.
55
+ *
56
+ * Apart from the above, this mirrors the base `AbstractAgent.connectAgent`
57
+ * implementation, so callers keep the same subscriber notifications, detach
58
+ * semantics, and `{ result, newMessages }` return shape.
32
59
  *
33
60
  * TODO: Remove this in favour of the base implementation once AG-UI's
34
61
  * AbstractAgent supports opting out of `verifyEvents` for transports whose
35
62
  * connection life-cycle isn't a single run AND preserves the connection-local
36
63
  * replay lifecycle and running-state behavior below. Skipping verification alone
37
- * is insufficient. As of `@ag-ui/client@0.0.57`
64
+ * is insufficient. As of `@ag-ui/client@1.0.1`
38
65
  * `connectAgent(parameters?, subscriber?)` takes no such option.
39
66
  *
40
67
  * @param agent - The agent whose `connect()` stream should be consumed.
@@ -43,6 +70,9 @@ let zod_to_json_schema = require("zod-to-json-schema");
43
70
  */
44
71
  async function ɵconnectWithoutEventVerification(agent, parameters, subscriber, connect) {
45
72
  const self = agent;
73
+ let finalizeRun = () => {
74
+ agent.isRunning = false;
75
+ };
46
76
  try {
47
77
  agent.isRunning = true;
48
78
  agent.agentId = agent.agentId ?? (0, _ag_ui_client.randomUUID)();
@@ -66,12 +96,35 @@ async function ɵconnectWithoutEventVerification(agent, parameters, subscriber,
66
96
  ...agent.subscribers,
67
97
  subscriber ?? {}
68
98
  ];
69
- await self.onInitialize(input, subscribers);
70
- self.activeRunDetach$ = new rxjs.Subject();
99
+ const activeRunDetach$ = new rxjs.Subject();
100
+ self.activeRunDetach$ = activeRunDetach$;
71
101
  let resolveCompletion;
72
102
  self.activeRunCompletionPromise = new Promise((resolve) => {
73
103
  resolveCompletion = resolve;
74
104
  });
105
+ let detached = false;
106
+ let finalized = false;
107
+ const detachSubscription = activeRunDetach$.subscribe(() => {
108
+ detached = true;
109
+ });
110
+ finalizeRun = () => {
111
+ if (finalized) return;
112
+ finalized = true;
113
+ detachSubscription.unsubscribe();
114
+ if (self.activeRunDetach$ === activeRunDetach$) {
115
+ agent.isRunning = false;
116
+ self.activeRunCompletionPromise = void 0;
117
+ self.activeRunDetach$ = void 0;
118
+ self.onFinalize(input, subscribers);
119
+ }
120
+ resolveCompletion?.();
121
+ resolveCompletion = void 0;
122
+ };
123
+ await self.onInitialize(input, subscribers);
124
+ if (detached) return {
125
+ result: void 0,
126
+ newMessages: []
127
+ };
75
128
  const lifecycle = {
76
129
  onReplayStarted: () => {
77
130
  isReplaying = true;
@@ -81,29 +134,23 @@ async function ɵconnectWithoutEventVerification(agent, parameters, subscriber,
81
134
  isReplaying = false;
82
135
  }
83
136
  };
84
- const source$ = (0, rxjs.defer)(() => connect ? connect(input, lifecycle) : self.connect(input)).pipe((0, rxjs_operators.tap)((event) => {
137
+ const source$ = (0, rxjs.defer)(() => new _ag_ui_client.CompatibilityBoundary().run(input, { run: () => connect ? connect(input, lifecycle) : self.connect(input) })).pipe((0, rxjs_operators.tap)((event) => {
85
138
  if (!isReplaying && event.type === _ag_ui_client.EventType.RUN_ERROR) liveErrors.add(event);
86
- }), (0, _ag_ui_client.transformChunks)(self.debugLogger), (0, rxjs_operators.takeUntil)(self.activeRunDetach$));
139
+ }), (0, _ag_ui_client.transformChunks)(self.debugLogger), (0, rxjs_operators.takeUntil)(activeRunDetach$));
87
140
  const applied$ = self.apply(input, source$, subscribers);
88
141
  await (0, rxjs.lastValueFrom)(self.processApplyEvents(input, applied$, subscribers).pipe((0, rxjs_operators.catchError)((error) => {
142
+ if (self.activeRunDetach$ !== activeRunDetach$) return rxjs.EMPTY;
89
143
  agent.isRunning = false;
90
144
  if (error instanceof _ag_ui_client.AGUIConnectNotImplementedError) return rxjs.EMPTY;
91
145
  return self.onError(input, error, subscribers);
92
- }), (0, rxjs_operators.finalize)(() => {
93
- agent.isRunning = false;
94
- self.onFinalize(input, subscribers);
95
- resolveCompletion?.();
96
- resolveCompletion = void 0;
97
- self.activeRunCompletionPromise = void 0;
98
- self.activeRunDetach$ = void 0;
99
- })), { defaultValue: void 0 });
146
+ }), (0, rxjs_operators.finalize)(() => finalizeRun())), { defaultValue: void 0 });
100
147
  const newMessages = (0, _ag_ui_client.structuredClone_)(agent.messages).filter((m) => !previousMessageIds.has(m.id));
101
148
  return {
102
149
  result,
103
150
  newMessages
104
151
  };
105
152
  } finally {
106
- agent.isRunning = false;
153
+ finalizeRun();
107
154
  }
108
155
  }
109
156
 
@@ -586,7 +633,7 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
586
633
  observeThreadEvents$(threadId, channel$, options) {
587
634
  return channel$.pipe((0, rxjs_operators.switchMap)(({ channel }) => this.observeChannelEvent$(channel, CLIENT_AG_UI_EVENT)), (0, rxjs_operators.tap)((payload) => {
588
635
  this.updateLastSeenEventId(threadId, payload);
589
- }), (0, rxjs_operators.mergeMap)((payload) => this.createThreadNotifications(payload, {
636
+ }), (0, rxjs_operators.map)((payload) => options.streamMode === "run" ? (0, _copilotkit_shared.stripIntelligenceRoutingFields)(payload) : payload), (0, rxjs_operators.mergeMap)((payload) => this.createThreadNotifications(payload, {
590
637
  completeOnRunError: options.completeOnRunError,
591
638
  completeOnRunFinished: options.streamMode === "run",
592
639
  errorOnRunError: options.streamMode === "run"
@@ -840,6 +887,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
840
887
  _capabilities;
841
888
  delegate;
842
889
  runtimeInfoPromise;
890
+ connectionAttempt;
843
891
  _messageFilter;
844
892
  /**
845
893
  * The HTTP `run` this agent started and has not yet seen finish, as the
@@ -902,13 +950,12 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
902
950
  this.runtimeMode = runtimeMode;
903
951
  this.intelligence = intelligence;
904
952
  if (unchanged) return;
953
+ this.connectionAttempt = void 0;
905
954
  this.runtimeInfoPromise = void 0;
906
955
  const staleDelegate = this.delegate;
907
956
  this.delegate = void 0;
908
- if (staleDelegate) {
909
- staleDelegate.abortRun();
910
- this.detachActiveRun();
911
- }
957
+ if (staleDelegate) staleDelegate.abortRun();
958
+ this.detachActiveRun();
912
959
  }
913
960
  /**
914
961
  * The agent id used for outbound runtime requests — `runtimeAgentId` when
@@ -999,8 +1046,11 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
999
1046
  return this._capabilities ?? {};
1000
1047
  }
1001
1048
  async detachActiveRun() {
1002
- if (this.delegate) await this.delegate.detachActiveRun();
1003
- await super.detachActiveRun();
1049
+ this.connectionAttempt = void 0;
1050
+ this.isRunning = false;
1051
+ const delegateDetach = this.delegate?.detachActiveRun();
1052
+ const proxyDetach = super.detachActiveRun();
1053
+ await Promise.all([delegateDetach, proxyDetach]);
1004
1054
  }
1005
1055
  abortRun() {
1006
1056
  if (this.delegate) {
@@ -1054,41 +1104,74 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
1054
1104
  });
1055
1105
  }
1056
1106
  async connectAgent(parameters, subscriber) {
1107
+ const detached = this.detachActiveRun();
1108
+ const connection = {};
1109
+ const threadId = this.threadId;
1110
+ this.connectionAttempt = connection;
1111
+ const isCurrentConnection = () => this.connectionAttempt === connection && this.threadId === threadId;
1112
+ await detached;
1113
+ if (!isCurrentConnection()) return {
1114
+ result: void 0,
1115
+ newMessages: []
1116
+ };
1117
+ await this.ensureRuntimeConfiguration();
1118
+ if (!isCurrentConnection()) return {
1119
+ result: void 0,
1120
+ newMessages: []
1121
+ };
1057
1122
  if (this.runtimeMode !== _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE) return ɵconnectWithoutEventVerification(this, parameters, subscriber, (input, lifecycle) => this.connect(input, lifecycle));
1058
- if (this.delegate) await this.delegate.detachActiveRun();
1059
1123
  await this.resolveDelegate();
1124
+ if (!isCurrentConnection()) return {
1125
+ result: void 0,
1126
+ newMessages: []
1127
+ };
1060
1128
  const delegate = this.delegate;
1061
1129
  const bridgeSub = delegate.subscribe({
1062
1130
  onMessagesChanged: () => {
1131
+ if (!isCurrentConnection()) return;
1063
1132
  this.setMessages([...delegate.messages]);
1064
1133
  },
1065
1134
  onStateChanged: () => {
1135
+ if (!isCurrentConnection()) return;
1066
1136
  this.setState({ ...delegate.state });
1067
1137
  },
1068
1138
  onRunInitialized: () => {
1139
+ if (!isCurrentConnection()) return;
1069
1140
  this.isRunning = true;
1070
1141
  },
1071
1142
  onRunFinalized: () => {
1143
+ if (!isCurrentConnection()) return;
1144
+ this.pendingInterrupts = (0, _ag_ui_client.structuredClone_)(delegate.pendingInterrupts);
1072
1145
  this.isRunning = false;
1073
1146
  },
1074
1147
  onRunFailed: () => {
1148
+ if (!isCurrentConnection()) return;
1075
1149
  this.isRunning = false;
1076
1150
  },
1077
1151
  onRunErrorEvent: () => {
1152
+ if (!isCurrentConnection()) return;
1078
1153
  this.isRunning = delegate.isRunning;
1079
1154
  },
1080
1155
  onRunStartedEvent: () => {
1156
+ if (!isCurrentConnection()) return;
1081
1157
  this.isRunning = delegate.isRunning;
1082
1158
  }
1083
1159
  });
1084
- const forwardedSubs = this.subscribers.map((s) => delegate.subscribe(s));
1160
+ const forwardedSubs = [...this.subscribers, ...subscriber ? [subscriber] : []].map((subscriber) => delegate.subscribe(new Proxy(subscriber, { get(target, property, receiver) {
1161
+ const callback = Reflect.get(target, property, receiver);
1162
+ if (typeof callback !== "function") return callback;
1163
+ return (...args) => isCurrentConnection() ? Reflect.apply(callback, target, args) : void 0;
1164
+ } })));
1085
1165
  try {
1086
- const result = await delegate.connectAgent(parameters, subscriber);
1087
- this.setMessages([...delegate.messages]);
1088
- this.setState({ ...delegate.state });
1166
+ const result = await delegate.connectAgent(parameters);
1167
+ if (isCurrentConnection()) {
1168
+ this.setMessages([...delegate.messages]);
1169
+ this.setState({ ...delegate.state });
1170
+ this.pendingInterrupts = (0, _ag_ui_client.structuredClone_)(delegate.pendingInterrupts);
1171
+ }
1089
1172
  return result;
1090
1173
  } finally {
1091
- this.isRunning = false;
1174
+ if (isCurrentConnection()) this.isRunning = false;
1092
1175
  bridgeSub.unsubscribe();
1093
1176
  for (const sub of forwardedSubs) sub.unsubscribe();
1094
1177
  }
@@ -1110,14 +1193,31 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
1110
1193
  const input = this.#applyMessageFilter(unfiltered);
1111
1194
  this.activeRun = void 0;
1112
1195
  const routedId = this.routedAgentId();
1196
+ let httpEvents;
1113
1197
  if (this.transport === "single") {
1114
1198
  if (!this.singleEndpointUrl) throw new Error("Single endpoint transport requires a runtimeUrl");
1115
1199
  const requestInit = this.createSingleRouteRequestInit(input, "agent/connect", { agentId: routedId });
1116
- return withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(() => this.fetch(this.singleEndpointUrl, requestInit))));
1200
+ httpEvents = (0, _ag_ui_client.runHttpRequest)(() => this.fetch(this.singleEndpointUrl, requestInit));
1201
+ } else {
1202
+ const connectUrl = `${this.runtimeUrl}/agent/${routedId}/connect`;
1203
+ const connectRequestInit = this.requestInit(input);
1204
+ httpEvents = (0, _ag_ui_client.runHttpRequest)(() => this.fetch(connectUrl, connectRequestInit));
1117
1205
  }
1118
- const connectUrl = `${this.runtimeUrl}/agent/${routedId}/connect`;
1119
- const connectRequestInit = this.requestInit(input);
1120
- return withAbortErrorHandling((0, _ag_ui_client.transformHttpEventStream)((0, _ag_ui_client.runHttpRequest)(() => this.fetch(connectUrl, connectRequestInit))));
1206
+ return withAbortErrorHandling((0, rxjs.defer)(() => {
1207
+ let replayStarted = false;
1208
+ const emptyHistory = [{
1209
+ type: _ag_ui_client.EventType.MESSAGES_SNAPSHOT,
1210
+ messages: []
1211
+ }, {
1212
+ type: _ag_ui_client.EventType.STATE_SNAPSHOT,
1213
+ snapshot: {}
1214
+ }];
1215
+ return (0, _ag_ui_client.transformHttpEventStream)(httpEvents).pipe((0, rxjs_operators.concatMap)((event) => {
1216
+ if (replayStarted) return (0, rxjs.of)(event);
1217
+ replayStarted = true;
1218
+ return (0, rxjs.from)([...emptyHistory, event]);
1219
+ }), (0, rxjs_operators.concatWith)((0, rxjs.defer)(() => replayStarted ? rxjs.EMPTY : (0, rxjs.from)(emptyHistory))));
1220
+ }));
1121
1221
  }
1122
1222
  #runViaDelegate(input) {
1123
1223
  return (0, rxjs.defer)(() => (0, rxjs.from)(this.resolveDelegate())).pipe((0, rxjs_operators.switchMap)((delegate) => withAbortErrorHandling(delegate.run(input))));
@@ -1160,6 +1260,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
1160
1260
  cloned.threadId = this.threadId;
1161
1261
  cloned.setState(this.state);
1162
1262
  cloned.setMessages(this.messages);
1263
+ cloned.pendingInterrupts = (0, _ag_ui_client.structuredClone_)(this.pendingInterrupts);
1163
1264
  if (this.delegate) {
1164
1265
  const clonedDelegate = this.delegate.clone();
1165
1266
  cloned.delegate = clonedDelegate;
@@ -1197,6 +1298,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
1197
1298
  if (this.runtimeMode === _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE || this.runtimeMode !== "pending" && this.transport !== "auto") return;
1198
1299
  if (!this.runtimeUrl) throw new Error("Runtime URL is not set");
1199
1300
  const runtimeInfoPromise = this.runtimeInfoPromise ?? this.fetchRuntimeInfo().then((runtimeInfo) => {
1301
+ if (this.runtimeInfoPromise !== runtimeInfoPromise) return;
1200
1302
  this.runtimeMode = runtimeInfo.mode ?? _copilotkit_shared.RUNTIME_MODE_SSE;
1201
1303
  this.intelligence = runtimeInfo.intelligence;
1202
1304
  });
@@ -1301,6 +1403,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
1301
1403
  delegate.debugLogger = this.debugLogger;
1302
1404
  delegate.setMessages(this.messages);
1303
1405
  delegate.setState(this.state);
1406
+ delegate.pendingInterrupts = (0, _ag_ui_client.structuredClone_)(this.pendingInterrupts);
1304
1407
  if (hasHeaders(delegate)) delegate.headers = { ...this.headers };
1305
1408
  if (hasCredentials(delegate)) delegate.credentials = this.credentials;
1306
1409
  }
@@ -1736,7 +1839,7 @@ var AgentRegistry = class {
1736
1839
  * because only `HttpAgent` carries a `headers` field. See #5635.
1737
1840
  */
1738
1841
  applyHeadersToAgent(agent) {
1739
- if (agent instanceof _ag_ui_client.HttpAgent) {
1842
+ if (ɵisHttpAgent(agent)) {
1740
1843
  if (!this.agentOwnHeaders.has(agent)) this.agentOwnHeaders.set(agent, { ...agent.headers });
1741
1844
  agent.headers = {
1742
1845
  ...this.agentOwnHeaders.get(agent),
@@ -1805,6 +1908,10 @@ var AgentRegistry = class {
1805
1908
  this.applyMessageFilterToAgent(agent);
1806
1909
  });
1807
1910
  }
1911
+ /** Preserve the exact configured URL when posting to a single endpoint. */
1912
+ get runtimeEndpointUrl() {
1913
+ return this._runtimeEndpointUrl;
1914
+ }
1808
1915
  createRuntimeFetch() {
1809
1916
  if (!this.runtimeFetch) this.runtimeFetch = (async (input, init) => {
1810
1917
  const meta = () => runtimeRequestMeta(init);
@@ -2829,6 +2936,68 @@ function normalizeToolResultContent(content) {
2829
2936
  function isForwardedToClientPlaceholder(content) {
2830
2937
  return normalizeToolResultContent(content) === FORWARDED_TO_CLIENT;
2831
2938
  }
2939
+ const MEDIA_PART_TYPES = new Set([
2940
+ "image",
2941
+ "audio",
2942
+ "video",
2943
+ "document"
2944
+ ]);
2945
+ function isOptionalString(value) {
2946
+ return value === void 0 || typeof value === "string";
2947
+ }
2948
+ function isPartSource(value) {
2949
+ if (!value || typeof value !== "object") return false;
2950
+ const source = value;
2951
+ if (typeof source.value !== "string") return false;
2952
+ switch (source.type) {
2953
+ case "data": return typeof source.mimeType === "string";
2954
+ case "url": return isOptionalString(source.mimeType);
2955
+ case "file": return isOptionalString(source.provider) && isOptionalString(source.mimeType);
2956
+ default: return false;
2957
+ }
2958
+ }
2959
+ /**
2960
+ * Mirrors `ContentPartSchema` from `@ag-ui/core/schemas`. The runtime parses
2961
+ * the request with that schema, and one invalid part there fails the whole run.
2962
+ * Core does not import the schema because the UMD build has no global for it.
2963
+ * A test checks this function against the schema.
2964
+ */
2965
+ function isContentPart(value) {
2966
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2967
+ const part = value;
2968
+ if (!isOptionalString(part.id) || part.metadata === null) return false;
2969
+ if (part.type === "text") return typeof part.text === "string";
2970
+ return typeof part.type === "string" && MEDIA_PART_TYPES.has(part.type) && isPartSource(part.source);
2971
+ }
2972
+ /**
2973
+ * Converts what a frontend tool handler returned into tool message content.
2974
+ * A non-empty array of AG-UI content parts passes through unchanged, so an
2975
+ * image reaches the model as media. Everything else becomes a string, as before.
2976
+ *
2977
+ * `text` is the string form, for surfaces typed as `result: string`
2978
+ * (`runTool`, `onToolExecutionEnd`). For content parts it is the JSON text they
2979
+ * received before. It is computed here, so a part that cannot be serialized
2980
+ * throws inside the caller's handler error boundary.
2981
+ */
2982
+ function toToolResultContent(result) {
2983
+ if (result === void 0 || result === null) return {
2984
+ content: "",
2985
+ text: ""
2986
+ };
2987
+ if (typeof result === "string") return {
2988
+ content: result,
2989
+ text: result
2990
+ };
2991
+ const text = JSON.stringify(result);
2992
+ if (Array.isArray(result) && result.length > 0 && result.every(isContentPart)) return {
2993
+ content: result,
2994
+ text
2995
+ };
2996
+ return {
2997
+ content: text,
2998
+ text
2999
+ };
3000
+ }
2832
3001
 
2833
3002
  //#endregion
2834
3003
  //#region src/core/tool-schema.ts
@@ -3087,6 +3256,7 @@ var RunHandler = class {
3087
3256
  * inspector and intermittent "Message not found" toasts.
3088
3257
  */
3089
3258
  _lastConnectedThreadIdsByAgent = /* @__PURE__ */ new Map();
3259
+ _lastActiveThreadIdsByAgent = /* @__PURE__ */ new Map();
3090
3260
  _anonymousAgentIds = /* @__PURE__ */ new WeakMap();
3091
3261
  _nextAnonymousAgentId = 0;
3092
3262
  /** Prevent reconnects from starting an interaction already executing locally. */
@@ -3264,7 +3434,8 @@ var RunHandler = class {
3264
3434
  * Connect an agent (establish initial connection)
3265
3435
  */
3266
3436
  async connectAgent({ agent }) {
3267
- this._connectionGenerations.set(agent, {});
3437
+ const connection = {};
3438
+ this._connectionGenerations.set(agent, connection);
3268
3439
  const incomingThreadId = agent.threadId ?? null;
3269
3440
  const previousReplay = this._interactionAbortControllers.get(agent);
3270
3441
  if (previousReplay && previousReplay.threadId !== incomingThreadId) {
@@ -3274,12 +3445,19 @@ var RunHandler = class {
3274
3445
  }
3275
3446
  try {
3276
3447
  const restoreKey = this.getConnectRestoreKey(agent);
3277
- const isFreshRestore = incomingThreadId !== (this._lastConnectedThreadIdsByAgent.get(restoreKey) ?? null);
3278
- this._lastConnectedThreadIdsByAgent.set(restoreKey, incomingThreadId);
3448
+ const previousThreadId = this._lastActiveThreadIdsByAgent.get(restoreKey);
3449
+ const isFreshRestore = incomingThreadId !== (previousThreadId ?? this._lastConnectedThreadIdsByAgent.get(restoreKey) ?? null);
3279
3450
  await agent.detachActiveRun();
3451
+ if (this._connectionGenerations.get(agent) !== connection || (agent.threadId ?? null) !== incomingThreadId) return {
3452
+ result: void 0,
3453
+ newMessages: []
3454
+ };
3455
+ this._lastConnectedThreadIdsByAgent.set(restoreKey, incomingThreadId);
3456
+ this._lastActiveThreadIdsByAgent.set(restoreKey, incomingThreadId);
3280
3457
  if (isFreshRestore) {
3281
3458
  agent.setMessages([]);
3282
3459
  agent.setState({});
3460
+ if (previousThreadId !== void 0 && previousThreadId !== incomingThreadId) agent.pendingInterrupts = [];
3283
3461
  const cursorAware = agent;
3284
3462
  if (incomingThreadId) {
3285
3463
  if (typeof cursorAware.clearReplayCursor === "function") cursorAware.clearReplayCursor(incomingThreadId);
@@ -3291,12 +3469,16 @@ var RunHandler = class {
3291
3469
  copilotkit: this.core,
3292
3470
  agent
3293
3471
  }), "Subscriber onAgentRunStarted error:");
3472
+ if (this._connectionGenerations.get(agent) !== connection || (agent.threadId ?? null) !== incomingThreadId) return {
3473
+ result: void 0,
3474
+ newMessages: []
3475
+ };
3294
3476
  const runAgentResult = await agent.connectAgent({
3295
3477
  forwardedProps: this._internal.properties,
3296
3478
  tools: this.buildFrontendTools(agent.agentId),
3297
3479
  context: this._internal.getContextForAgent(agent.agentId)
3298
3480
  }, this.createAgentErrorSubscriber(agent));
3299
- if ((agent.threadId ?? null) !== incomingThreadId) return runAgentResult;
3481
+ if (this._connectionGenerations.get(agent) !== connection || (agent.threadId ?? null) !== incomingThreadId) return runAgentResult;
3300
3482
  const controller = new AbortController();
3301
3483
  const replay = this._interactionAbortControllers.get(agent) ?? {
3302
3484
  threadId: incomingThreadId,
@@ -3353,6 +3535,12 @@ var RunHandler = class {
3353
3535
  continuationHandoff?.cancel();
3354
3536
  throw error;
3355
3537
  }
3538
+ const resumedRunId = this._internal.stateManager.getContinuationRunId(agent, {
3539
+ threadId: agent.threadId,
3540
+ resume,
3541
+ forwardedProps
3542
+ });
3543
+ const logicalHandoff = continuationHandoff ?? (resumedRunId ? this._internal.stateManager.markNextRunAsContinuation(agent, resumedRunId) : void 0);
3356
3544
  const isTopLevel = this._runDepth === 0;
3357
3545
  let originalAbortRun;
3358
3546
  if (isTopLevel) {
@@ -3370,18 +3558,19 @@ var RunHandler = class {
3370
3558
  }
3371
3559
  this._runDepth++;
3372
3560
  try {
3373
- let logicalRunId = runId;
3561
+ let logicalRunId = resumedRunId ?? runId;
3374
3562
  const agentSubscriber = this.createAgentErrorSubscriber(agent);
3375
3563
  let started = false;
3376
3564
  const onRunStartedEvent = agentSubscriber.onRunStartedEvent;
3377
3565
  const onRunInitialized = agentSubscriber.onRunInitialized;
3378
3566
  agentSubscriber.onRunInitialized = async (params) => {
3379
- continuationHandoff?.bind(params.input);
3567
+ this._lastActiveThreadIdsByAgent.set(this.getConnectRestoreKey(agent), params.input.threadId);
3568
+ logicalHandoff?.bind(params.input);
3380
3569
  return onRunInitialized?.(params);
3381
3570
  };
3382
3571
  agentSubscriber.onRunStartedEvent = async (params) => {
3383
3572
  started = true;
3384
- if (!continuationHandoff) logicalRunId = params.input.runId;
3573
+ if (!logicalHandoff) logicalRunId = params.input.runId;
3385
3574
  return onRunStartedEvent?.(params);
3386
3575
  };
3387
3576
  const pinRunIdOnWire = runId !== void 0 && !continuationHandoff;
@@ -3396,14 +3585,14 @@ var RunHandler = class {
3396
3585
  context: this._internal.getContextForAgent(agent.agentId)
3397
3586
  };
3398
3587
  const runAgentResult = await agent.runAgent(agentRunInput, agentSubscriber);
3399
- if (!started) continuationHandoff?.cancel();
3588
+ if (!started) logicalHandoff?.cancel();
3400
3589
  return await this.processAgentResult({
3401
3590
  runAgentResult,
3402
3591
  agent,
3403
3592
  runId: logicalRunId
3404
3593
  });
3405
3594
  } catch (error) {
3406
- continuationHandoff?.cancel();
3595
+ logicalHandoff?.cancel();
3407
3596
  const runError = error instanceof Error ? error : new Error(String(error));
3408
3597
  const context = {};
3409
3598
  if (agent.agentId) context.agentId = agent.agentId;
@@ -3522,6 +3711,7 @@ var RunHandler = class {
3522
3711
  */
3523
3712
  async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId, signal = this._runAbortController?.signal, discardOnAbort = false }) {
3524
3713
  let toolCallResult = "";
3714
+ let toolCallText = "";
3525
3715
  let errorMessage;
3526
3716
  let isArgumentError = false;
3527
3717
  let parsedArgs;
@@ -3557,9 +3747,7 @@ var RunHandler = class {
3557
3747
  agent,
3558
3748
  signal
3559
3749
  });
3560
- if (result === void 0 || result === null) toolCallResult = "";
3561
- else if (typeof result === "string") toolCallResult = result;
3562
- else toolCallResult = JSON.stringify(result);
3750
+ ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3563
3751
  } catch (error) {
3564
3752
  const handlerError = error instanceof Error ? error : new Error(String(error));
3565
3753
  errorMessage = handlerError.message;
@@ -3576,17 +3764,18 @@ var RunHandler = class {
3576
3764
  }
3577
3765
  });
3578
3766
  }
3579
- if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
3767
+ if (errorMessage) toolCallResult = toolCallText = `Error: ${errorMessage}`;
3580
3768
  await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
3581
3769
  copilotkit: this.core,
3582
3770
  toolCallId: toolCall.id,
3583
3771
  agentId,
3584
3772
  toolName: toolCall.function.name,
3585
- result: errorMessage ? "" : toolCallResult,
3773
+ result: errorMessage ? "" : toolCallText,
3586
3774
  error: errorMessage
3587
3775
  }), "Subscriber onToolExecutionEnd error:");
3588
3776
  return {
3589
- result: toolCallResult,
3777
+ content: toolCallResult,
3778
+ result: toolCallText,
3590
3779
  error: errorMessage,
3591
3780
  isArgumentError
3592
3781
  };
@@ -3598,6 +3787,7 @@ var RunHandler = class {
3598
3787
  const threadId = agent.threadId;
3599
3788
  if (tool?.agentId && tool.agentId !== agent.agentId) return false;
3600
3789
  let handlerResult = {
3790
+ content: "",
3601
3791
  result: "",
3602
3792
  error: void 0,
3603
3793
  isArgumentError: false
@@ -3622,7 +3812,7 @@ var RunHandler = class {
3622
3812
  id: (0, _copilotkit_shared.randomUUID)(),
3623
3813
  role: "tool",
3624
3814
  toolCallId: toolCall.id,
3625
- content: handlerResult.result
3815
+ content: handlerResult.content
3626
3816
  };
3627
3817
  agent.messages.splice(insertAt, 0, toolMessage);
3628
3818
  if (!handlerResult.error && tool?.followUp !== false) return true;
@@ -3639,6 +3829,7 @@ var RunHandler = class {
3639
3829
  const threadId = agent.threadId;
3640
3830
  if (wildcardTool?.agentId && wildcardTool.agentId !== agent.agentId) return false;
3641
3831
  let toolCallResult = "";
3832
+ let toolCallText = "";
3642
3833
  let errorMessage;
3643
3834
  if (wildcardTool?.handler) {
3644
3835
  let parsedArgs;
@@ -3677,9 +3868,7 @@ var RunHandler = class {
3677
3868
  agent,
3678
3869
  signal
3679
3870
  });
3680
- if (result === void 0 || result === null) toolCallResult = "";
3681
- else if (typeof result === "string") toolCallResult = result;
3682
- else toolCallResult = JSON.stringify(result);
3871
+ ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3683
3872
  } catch (error) {
3684
3873
  const handlerError = error instanceof Error ? error : new Error(String(error));
3685
3874
  errorMessage = handlerError.message;
@@ -3696,13 +3885,13 @@ var RunHandler = class {
3696
3885
  }
3697
3886
  });
3698
3887
  }
3699
- if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
3888
+ if (errorMessage) toolCallResult = toolCallText = `Error: ${errorMessage}`;
3700
3889
  await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
3701
3890
  copilotkit: this.core,
3702
3891
  toolCallId: toolCall.id,
3703
3892
  agentId,
3704
3893
  toolName: toolCall.function.name,
3705
- result: errorMessage ? "" : toolCallResult,
3894
+ result: errorMessage ? "" : toolCallText,
3706
3895
  error: errorMessage
3707
3896
  }), "Subscriber onToolExecutionEnd error:");
3708
3897
  }
@@ -3773,6 +3962,7 @@ var RunHandler = class {
3773
3962
  };
3774
3963
  agent.messages.push(assistantMessage);
3775
3964
  let handlerResult = {
3965
+ content: "",
3776
3966
  result: "",
3777
3967
  error: void 0,
3778
3968
  isArgumentError: false
@@ -3789,7 +3979,7 @@ var RunHandler = class {
3789
3979
  id: (0, _copilotkit_shared.randomUUID)(),
3790
3980
  role: "tool",
3791
3981
  toolCallId,
3792
- content: handlerResult.result
3982
+ content: handlerResult.content
3793
3983
  };
3794
3984
  const assistantIndex = agent.messages.findIndex((m) => m.id === assistantMessage.id);
3795
3985
  if (assistantIndex !== -1) agent.messages.splice(assistantIndex + 1, 0, toolResultMessage);
@@ -3945,6 +4135,15 @@ var StateManager = class {
3945
4135
  activeRun = /* @__PURE__ */ new Map();
3946
4136
  agentSubscriptions = /* @__PURE__ */ new Map();
3947
4137
  pendingContinuations = /* @__PURE__ */ new WeakMap();
4138
+ interruptedRuns = /* @__PURE__ */ new WeakMap();
4139
+ /** Find the interrupted logical run without consuming its pending answers. */
4140
+ getContinuationRunId(agent, input) {
4141
+ const pending = this.interruptedRuns.get(agent)?.get(input.threadId);
4142
+ if (!pending) return void 0;
4143
+ if (pending.interruptIds === null) return input.resume === void 0 && isContinuation(input) ? pending.runId : void 0;
4144
+ const submitted = new Set(input.resume?.map((entry) => entry.interruptId));
4145
+ return submitted.size > 0 && submitted.size === input.resume?.length && submitted.size === pending.interruptIds.length && pending.interruptIds.every((id) => submitted.has(id)) ? pending.runId : void 0;
4146
+ }
3948
4147
  constructor(core) {
3949
4148
  this.core = core;
3950
4149
  }
@@ -3988,6 +4187,7 @@ var StateManager = class {
3988
4187
  }
3989
4188
  let revoked = false;
3990
4189
  let subRunId;
4190
+ let legacyInterrupted = false;
3991
4191
  let runFinished = false;
3992
4192
  const pendingResults = /* @__PURE__ */ new WeakMap();
3993
4193
  const reconcilePendingResults = (historyMessages, input) => {
@@ -4053,21 +4253,43 @@ var StateManager = class {
4053
4253
  if (revoked) return;
4054
4254
  const internalContinuation = [...this.pendingContinuations.get(agent) ?? []].find((pending) => pending.expectedInput === input);
4055
4255
  internalContinuation?.cancel();
4256
+ const startedInput = event.input ?? {
4257
+ ...input,
4258
+ threadId: event.threadId
4259
+ };
4260
+ const resumedRunId = this.getContinuationRunId(agent, startedInput);
4261
+ this.interruptedRuns.get(agent)?.delete(startedInput.threadId);
4262
+ legacyInterrupted = false;
4056
4263
  if (internalContinuation) subRunId = internalContinuation.expectedRunId ?? event.runId ?? input.runId;
4264
+ else if (resumedRunId) subRunId = resumedRunId;
4057
4265
  else if (runFinished && input.runId === subRunId && !isContinuation(input) && (event.runId == null || event.runId === subRunId)) subRunId = (0, _ag_ui_client.randomUUID)();
4058
4266
  else subRunId = event.runId || input.runId;
4059
4267
  runFinished = false;
4060
4268
  this.handleRunStarted(agent, effectiveInput(input), state);
4061
4269
  },
4062
- onRunFinishedEvent: ({ input, state, messages }) => {
4270
+ onRunFinishedEvent: ({ event, input, state, messages }) => {
4063
4271
  if (revoked) return;
4064
4272
  runFinished = true;
4065
4273
  const effective = effectiveInput(input);
4274
+ let interrupted = this.interruptedRuns.get(agent);
4275
+ if (event.outcome?.type === "interrupt" || legacyInterrupted) {
4276
+ if (!interrupted) {
4277
+ interrupted = /* @__PURE__ */ new Map();
4278
+ this.interruptedRuns.set(agent, interrupted);
4279
+ }
4280
+ interrupted.set(event.threadId, {
4281
+ runId: effective.runId,
4282
+ interruptIds: event.outcome?.type === "interrupt" ? event.outcome.interrupts.map((interrupt) => interrupt.id) : null
4283
+ });
4284
+ } else interrupted?.delete(event.threadId);
4066
4285
  const mutation = reconcilePendingResults(messages, input);
4067
4286
  clearPendingResults(input);
4068
4287
  this.handleRunFinished(agent, effective, state);
4069
4288
  return mutation;
4070
4289
  },
4290
+ onCustomEvent: ({ event }) => {
4291
+ if (!revoked && event.name === "on_interrupt") legacyInterrupted = true;
4292
+ },
4071
4293
  onRunErrorEvent: ({ input, state, messages }) => {
4072
4294
  if (revoked) return;
4073
4295
  runFinished = true;
@@ -4124,6 +4346,7 @@ var StateManager = class {
4124
4346
  unsubscribe: () => {
4125
4347
  revoked = true;
4126
4348
  this.pendingContinuations.delete(agent);
4349
+ this.interruptedRuns.delete(agent);
4127
4350
  unsubscribe();
4128
4351
  }
4129
4352
  });
@@ -4284,6 +4507,8 @@ var StateManager = class {
4284
4507
  * Clear all state for an agent
4285
4508
  */
4286
4509
  clearAgentState(agentId) {
4510
+ const agent = this.agentSubscriptions.get(agentId)?.agent;
4511
+ if (agent) this.interruptedRuns.delete(agent);
4287
4512
  this.stateByRun.delete(agentId);
4288
4513
  this.messageToRun.delete(agentId);
4289
4514
  this.rawEventByMessage.delete(agentId);
@@ -4292,6 +4517,8 @@ var StateManager = class {
4292
4517
  * Clear all state for a thread
4293
4518
  */
4294
4519
  clearThreadState(agentId, threadId) {
4520
+ const agent = this.agentSubscriptions.get(agentId)?.agent;
4521
+ if (agent) this.interruptedRuns.get(agent)?.delete(threadId);
4295
4522
  this.stateByRun.get(agentId)?.delete(threadId);
4296
4523
  this.messageToRun.get(agentId)?.delete(threadId);
4297
4524
  this.rawEventByMessage.get(agentId)?.delete(threadId);
@@ -4353,6 +4580,720 @@ var ThreadStoreRegistry = class {
4353
4580
  }
4354
4581
  };
4355
4582
 
4583
+ //#endregion
4584
+ //#region src/core/trajectory-connection.ts
4585
+ const TIMEOUT_MS = 1e4;
4586
+ const BATCH_INTERVAL_MS = 2e3;
4587
+ const MAX_BATCH_EVENTS = 50;
4588
+ const MAX_BATCH_BYTES = 64 * 1024;
4589
+ const MAX_EVENT_BYTES = 16 * 1024;
4590
+ const byteLength = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
4591
+ const reconnectDelay = (0, _copilotkit_shared.phoenixExponentialBackoff)(1e3, 1e4);
4592
+ function isObject(value) {
4593
+ return typeof value === "object" && value !== null;
4594
+ }
4595
+ function isNonNegativeInteger(value) {
4596
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
4597
+ }
4598
+ function isBatchAcknowledgement(value, eventCount) {
4599
+ return isObject(value) && isNonNegativeInteger(value.accepted) && isNonNegativeInteger(value.rejected) && value.accepted + value.rejected === eventCount && (value.highestSeq === null || isNonNegativeInteger(value.highestSeq)) && (value.accepted === 0 || value.highestSeq !== null);
4600
+ }
4601
+ function isGrant(value) {
4602
+ return isObject(value) && typeof value.joinToken === "string" && value.joinToken.length > 0 && isObject(value.realtime) && typeof value.realtime.clientUrl === "string" && value.realtime.clientUrl.length > 0 && typeof value.realtime.topic === "string" && value.realtime.topic.length > 0;
4603
+ }
4604
+ function errorCode(value, fallback) {
4605
+ return isObject(value) && typeof value.code === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(value.code) ? value.code : fallback;
4606
+ }
4607
+ /** Owns bounded, connected-only capture batches. Failed batches are never resent. */
4608
+ var TrajectoryConnection = class {
4609
+ session;
4610
+ nextSeq = Math.floor(Date.now()) * 1e3;
4611
+ constructor(core, onChange) {
4612
+ this.core = core;
4613
+ this.onChange = onChange;
4614
+ }
4615
+ get trajectoryId() {
4616
+ return this.session?.ready ? this.session.trajectoryId : null;
4617
+ }
4618
+ start(trajectoryId, config) {
4619
+ if (this.session && (trajectoryId === void 0 || trajectoryId === this.session.trajectoryId)) return this.session.promise;
4620
+ this.stop();
4621
+ const id = trajectoryId ?? crypto.randomUUID();
4622
+ let resolveStart;
4623
+ const promise = new Promise((resolve) => {
4624
+ resolveStart = resolve;
4625
+ });
4626
+ const session = {
4627
+ trajectoryId: id,
4628
+ config,
4629
+ promise,
4630
+ resolve: resolveStart,
4631
+ retries: 0,
4632
+ queue: [],
4633
+ dropped: 0,
4634
+ ready: false,
4635
+ started: false,
4636
+ offline: typeof navigator !== "undefined" && navigator.onLine === false,
4637
+ onOffline: () => {
4638
+ if (this.session !== session) return;
4639
+ session.offline = true;
4640
+ if (session.connection) this.fail(session, session.connection, "CONNECTION_LOST");
4641
+ clearTimeout(session.retry);
4642
+ session.retry = void 0;
4643
+ },
4644
+ onOnline: () => {
4645
+ if (this.session !== session) return;
4646
+ session.offline = false;
4647
+ if (!session.connection) {
4648
+ clearTimeout(session.retry);
4649
+ session.retry = void 0;
4650
+ this.connect(session);
4651
+ }
4652
+ }
4653
+ };
4654
+ this.session = session;
4655
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
4656
+ window.addEventListener("offline", session.onOffline);
4657
+ window.addEventListener("online", session.onOnline);
4658
+ }
4659
+ if (session.offline) {
4660
+ this.report(session, "CONNECTION_LOST");
4661
+ this.end(session, {
4662
+ status: "error",
4663
+ code: "CONNECTION_LOST"
4664
+ });
4665
+ } else this.connect(session);
4666
+ return promise;
4667
+ }
4668
+ stop() {
4669
+ const session = this.session;
4670
+ if (!session) return;
4671
+ if (session.ready && session.connection) this.flush(session, session.connection);
4672
+ this.end(session, {
4673
+ status: "error",
4674
+ code: "CANCELLED"
4675
+ });
4676
+ }
4677
+ emit(name, value) {
4678
+ const session = this.session;
4679
+ if (session?.ready) session.collector?.emit(name, value);
4680
+ else if (session?.started) this.addDropped(session, 1, false);
4681
+ }
4682
+ report(session, code) {
4683
+ try {
4684
+ session.config.onError?.({
4685
+ code,
4686
+ message: `Trajectory capture: ${code}.`
4687
+ });
4688
+ } catch {}
4689
+ }
4690
+ current(session, connection) {
4691
+ return this.session === session && session.connection === connection;
4692
+ }
4693
+ end(session, result) {
4694
+ if (this.session !== session) return;
4695
+ this.session = void 0;
4696
+ clearTimeout(session.retry);
4697
+ session.collector?.stop();
4698
+ session.ready = false;
4699
+ if (session.queue.length > 0) this.report(session, "EVENTS_DROPPED");
4700
+ this.discardQueue(session);
4701
+ if (session.connection) {
4702
+ for (const pending of session.connection.pending) {
4703
+ pending.finish("PERSISTENCE_UNKNOWN");
4704
+ this.report(session, "PERSISTENCE_UNKNOWN");
4705
+ }
4706
+ this.cleanup(session, session.connection);
4707
+ }
4708
+ if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
4709
+ window.removeEventListener("offline", session.onOffline);
4710
+ window.removeEventListener("online", session.onOnline);
4711
+ }
4712
+ session.resolve(result);
4713
+ this.onChange();
4714
+ }
4715
+ cleanup(session, connection) {
4716
+ session.connection = void 0;
4717
+ clearTimeout(connection.timer);
4718
+ connection.abort.abort();
4719
+ for (const pending of connection.pending) clearTimeout(pending.timer);
4720
+ connection.pending.clear();
4721
+ const { socket, channel } = connection;
4722
+ try {
4723
+ if (channel) {
4724
+ for (const [event, ref] of connection.channelRefs) channel.off(event, ref);
4725
+ channel.leave();
4726
+ }
4727
+ } finally {
4728
+ if (socket) {
4729
+ socket.off(connection.socketRefs);
4730
+ socket.disconnect();
4731
+ }
4732
+ }
4733
+ }
4734
+ fail(session, connection, code, retryable = true) {
4735
+ if (!this.current(session, connection)) return;
4736
+ session.collector?.stop();
4737
+ session.collector = void 0;
4738
+ if (session.ready && session.started) session.promise = new Promise((resolve) => {
4739
+ session.resolve = resolve;
4740
+ });
4741
+ session.ready = false;
4742
+ this.discardQueue(session);
4743
+ for (const pending of connection.pending) pending.finish("PERSISTENCE_UNKNOWN");
4744
+ if (!this.current(session, connection)) return;
4745
+ this.cleanup(session, connection);
4746
+ this.onChange();
4747
+ this.report(session, code);
4748
+ if (this.session !== session) return;
4749
+ if (!session.started || !retryable) {
4750
+ this.end(session, {
4751
+ status: "error",
4752
+ code
4753
+ });
4754
+ return;
4755
+ }
4756
+ if (session.offline || this.session !== session) return;
4757
+ session.retry = setTimeout(() => {
4758
+ session.retry = void 0;
4759
+ if (this.session === session && !session.offline) this.connect(session);
4760
+ }, reconnectDelay(++session.retries));
4761
+ }
4762
+ async connect(session) {
4763
+ if (this.session !== session || session.offline || session.connection) return;
4764
+ const connection = {
4765
+ abort: new AbortController(),
4766
+ socketRefs: [],
4767
+ channelRefs: [],
4768
+ pending: /* @__PURE__ */ new Set()
4769
+ };
4770
+ session.connection = connection;
4771
+ connection.timer = setTimeout(() => this.fail(session, connection, "CONNECTION_TIMEOUT"), TIMEOUT_MS);
4772
+ try {
4773
+ const runtimeUrl = this.core.runtimeUrl;
4774
+ if (!runtimeUrl) {
4775
+ this.fail(session, connection, "RUNTIME_REQUIRED", false);
4776
+ return;
4777
+ }
4778
+ let transport = this.core.runtimeTransport;
4779
+ if (transport === "auto") {
4780
+ transport = await this.detectedTransport(connection.abort.signal);
4781
+ if (!this.current(session, connection)) return;
4782
+ }
4783
+ const rest = transport === "rest";
4784
+ const response = await fetch(rest ? `${runtimeUrl}/trajectory/${encodeURIComponent(session.trajectoryId)}/connect` : this.core.ɵruntimeEndpointUrl ?? runtimeUrl, {
4785
+ method: "POST",
4786
+ redirect: "error",
4787
+ signal: connection.abort.signal,
4788
+ headers: {
4789
+ ...this.core.headers,
4790
+ "Content-Type": "application/json"
4791
+ },
4792
+ credentials: this.core.credentials,
4793
+ body: JSON.stringify(rest ? {} : {
4794
+ method: "trajectory/connect",
4795
+ params: { trajectoryId: session.trajectoryId },
4796
+ body: {}
4797
+ })
4798
+ });
4799
+ if (!this.current(session, connection)) return;
4800
+ const payload = await response.json().catch(() => void 0);
4801
+ if (!this.current(session, connection)) return;
4802
+ if (!response.ok) {
4803
+ this.fail(session, connection, errorCode(payload, "CONNECTION_FAILED"), response.status >= 500 || response.status === 408 || response.status === 429);
4804
+ return;
4805
+ }
4806
+ if (!isGrant(payload)) {
4807
+ this.fail(session, connection, "INVALID_GRANT", false);
4808
+ return;
4809
+ }
4810
+ const socket = new phoenix.Socket(payload.realtime.clientUrl, {
4811
+ params: { join_token: payload.joinToken },
4812
+ timeout: TIMEOUT_MS
4813
+ });
4814
+ connection.socket = socket;
4815
+ const lost = () => this.fail(session, connection, "CONNECTION_LOST");
4816
+ connection.socketRefs.push(socket.onError(lost), socket.onClose(lost));
4817
+ const channel = socket.channel(payload.realtime.topic, {});
4818
+ connection.channel = channel;
4819
+ connection.channelRefs.push(["phx_error", channel.onError(lost)], ["phx_close", channel.onClose(lost)]);
4820
+ socket.connect();
4821
+ if (!this.current(session, connection)) return;
4822
+ channel.join(TIMEOUT_MS).receive("ok", () => {
4823
+ if (!this.current(session, connection)) return;
4824
+ try {
4825
+ clearTimeout(connection.timer);
4826
+ session.ready = true;
4827
+ session.retries = 0;
4828
+ session.collector = (0, _copilotkit_learning.createTrajectoryCollector)({
4829
+ ...session.config,
4830
+ ignoreUrls: [
4831
+ ...session.config.ignoreUrls ?? [],
4832
+ ...this.core.runtimeUrl ? [this.core.runtimeUrl] : [],
4833
+ ...this.core.ɵruntimeEndpointUrl ? [this.core.ɵruntimeEndpointUrl] : [],
4834
+ "https://telemetry.copilotkit.ai/",
4835
+ "https://cdn.copilotkit.ai/"
4836
+ ],
4837
+ onError: (error) => {
4838
+ if (error.code === "CAPTURE_FAILED") this.fail(session, connection, error.code, false);
4839
+ else {
4840
+ if (error.code === "INVALID_EVENT" || error.code === "EVENT_TOO_LARGE") this.addDropped(session, 1);
4841
+ session.config.onError?.(error);
4842
+ }
4843
+ },
4844
+ send: (event) => this.send(session, connection, event)
4845
+ });
4846
+ session.collector.start();
4847
+ if (!this.current(session, connection)) return;
4848
+ if (session.dropped > 0) this.scheduleFlush(session, connection);
4849
+ session.started = true;
4850
+ session.resolve({
4851
+ status: "started",
4852
+ trajectoryId: session.trajectoryId
4853
+ });
4854
+ this.onChange();
4855
+ } catch {
4856
+ this.fail(session, connection, "CAPTURE_FAILED", false);
4857
+ }
4858
+ }).receive("error", (joinError) => this.fail(session, connection, errorCode(joinError, "JOIN_FAILED"))).receive("timeout", () => this.fail(session, connection, "CONNECTION_TIMEOUT"));
4859
+ } catch {
4860
+ this.fail(session, connection, "CONNECTION_FAILED");
4861
+ }
4862
+ }
4863
+ /** Resolves once auto-detection picks a transport, or with "auto" when the attempt is aborted. */
4864
+ detectedTransport(signal) {
4865
+ return new Promise((resolve) => {
4866
+ const finish = () => {
4867
+ subscription.unsubscribe();
4868
+ signal.removeEventListener("abort", finish);
4869
+ resolve(this.core.runtimeTransport);
4870
+ };
4871
+ const subscription = this.core.subscribe({ onRuntimeConnectionStatusChanged: () => {
4872
+ if (this.core.runtimeTransport !== "auto") finish();
4873
+ } });
4874
+ signal.addEventListener("abort", finish, { once: true });
4875
+ });
4876
+ }
4877
+ addDropped(session, count, schedule = true) {
4878
+ session.dropped = Math.min(Number.MAX_SAFE_INTEGER, session.dropped + count);
4879
+ if (schedule && count > 0 && session.ready && session.connection) this.scheduleFlush(session, session.connection);
4880
+ }
4881
+ scheduleFlush(session, connection) {
4882
+ if (session.flushTimer !== void 0 || !this.current(session, connection)) return;
4883
+ session.flushTimer = setTimeout(() => {
4884
+ session.flushTimer = void 0;
4885
+ this.flush(session, connection);
4886
+ }, BATCH_INTERVAL_MS);
4887
+ }
4888
+ discardQueue(session) {
4889
+ clearTimeout(session.flushTimer);
4890
+ session.flushTimer = void 0;
4891
+ this.addDropped(session, session.queue.length, false);
4892
+ session.queue = [];
4893
+ }
4894
+ connected(connection) {
4895
+ return connection.socket?.isConnected() === true && connection.channel?.state === "joined";
4896
+ }
4897
+ send(session, connection, event) {
4898
+ if (!this.current(session, connection) || !session.ready) return;
4899
+ if (!this.connected(connection)) {
4900
+ this.addDropped(session, 1);
4901
+ this.fail(session, connection, "CONNECTION_LOST");
4902
+ return;
4903
+ }
4904
+ if (!Number.isSafeInteger(this.nextSeq)) {
4905
+ this.addDropped(session, 1);
4906
+ this.report(session, "SEQUENCE_EXHAUSTED");
4907
+ return;
4908
+ }
4909
+ const original = event.value;
4910
+ const fields = isObject(original) && !Array.isArray(original) && !Object.hasOwn(original, "seq") ? original : { data: original };
4911
+ const wire = {
4912
+ ...event,
4913
+ value: {
4914
+ ...fields,
4915
+ seq: this.nextSeq++
4916
+ }
4917
+ };
4918
+ if (byteLength(wire) > MAX_EVENT_BYTES) {
4919
+ this.addDropped(session, 1);
4920
+ this.report(session, "EVENT_TOO_LARGE");
4921
+ return;
4922
+ }
4923
+ const fits = () => session.queue.length < MAX_BATCH_EVENTS && byteLength({
4924
+ events: [...session.queue, wire],
4925
+ dropped: Number.MAX_SAFE_INTEGER
4926
+ }) <= MAX_BATCH_BYTES;
4927
+ if (!fits()) this.flush(session, connection);
4928
+ if (!this.current(session, connection) || !session.ready) return;
4929
+ if (!fits()) {
4930
+ this.addDropped(session, 1);
4931
+ this.report(session, "EVENTS_DROPPED");
4932
+ return;
4933
+ }
4934
+ session.queue.push(wire);
4935
+ if (session.queue.length === MAX_BATCH_EVENTS) this.flush(session, connection);
4936
+ else this.scheduleFlush(session, connection);
4937
+ }
4938
+ flush(session, connection) {
4939
+ if (!this.current(session, connection) || !session.ready || session.queue.length === 0 && session.dropped === 0) return;
4940
+ if (!this.connected(connection)) {
4941
+ this.fail(session, connection, "CONNECTION_LOST");
4942
+ return;
4943
+ }
4944
+ if (connection.pending.size > 0) return;
4945
+ clearTimeout(session.flushTimer);
4946
+ session.flushTimer = void 0;
4947
+ const batch = {
4948
+ events: session.queue,
4949
+ dropped: session.dropped
4950
+ };
4951
+ session.queue = [];
4952
+ session.dropped = 0;
4953
+ let finished = false;
4954
+ const pending = {
4955
+ timer: setTimeout(() => pending.finish("PERSISTENCE_UNKNOWN"), TIMEOUT_MS),
4956
+ finish: (code, knownRollback = false, flushNext = true) => {
4957
+ if (finished) return;
4958
+ finished = true;
4959
+ clearTimeout(pending.timer);
4960
+ connection.pending.delete(pending);
4961
+ if (knownRollback) this.addDropped(session, batch.events.length + batch.dropped, false);
4962
+ if (code && this.current(session, connection)) this.report(session, code);
4963
+ if (flushNext && this.current(session, connection) && session.ready && (session.queue.length > 0 || !knownRollback && session.dropped > 0)) this.flush(session, connection);
4964
+ }
4965
+ };
4966
+ connection.pending.add(pending);
4967
+ try {
4968
+ connection.channel.push("events", batch, TIMEOUT_MS).receive("ok", (payload) => {
4969
+ pending.finish(!isBatchAcknowledgement(payload, batch.events.length) ? "PERSISTENCE_UNKNOWN" : payload.rejected > 0 ? "EVENTS_REJECTED" : void 0);
4970
+ }).receive("error", (payload) => {
4971
+ if (!this.current(session, connection)) return;
4972
+ const reason = isObject(payload) ? payload.reason : void 0;
4973
+ if (reason === "unauthorized" || reason === "trajectory_mismatch") {
4974
+ pending.finish(void 0, true, false);
4975
+ this.fail(session, connection, reason.toUpperCase(), false);
4976
+ return;
4977
+ }
4978
+ const rollback = typeof reason === "string" && [
4979
+ "invalid_batch",
4980
+ "batch_too_large",
4981
+ "batch_too_many_events",
4982
+ "trajectory_over_share",
4983
+ "trajectory_outbox_full",
4984
+ "unsupported_event"
4985
+ ].includes(reason);
4986
+ pending.finish(rollback ? reason.toUpperCase() : "PERSISTENCE_UNKNOWN", rollback);
4987
+ }).receive("timeout", () => pending.finish("PERSISTENCE_UNKNOWN"));
4988
+ } catch {
4989
+ pending.finish("PERSISTENCE_UNKNOWN");
4990
+ }
4991
+ }
4992
+ };
4993
+
4994
+ //#endregion
4995
+ //#region src/core/learning-bridge.ts
4996
+ const COPILOTKIT_OWN_URLS = ["https://telemetry.copilotkit.ai/", "https://cdn.copilotkit.ai/"];
4997
+ /**
4998
+ * Wires `@copilotkit/learning` into Core. Default capture uses Runtime and Phoenix;
4999
+ * explicit legacy sinks also use agent events and open-Thread attribution.
5000
+ */
5001
+ var LearningBridge = class {
5002
+ collector = null;
5003
+ agents = /* @__PURE__ */ new Map();
5004
+ openThreads = /* @__PURE__ */ new Set();
5005
+ toolCalls = /* @__PURE__ */ new Map();
5006
+ executingTools = /* @__PURE__ */ new Map();
5007
+ lastRunIds = /* @__PURE__ */ new WeakMap();
5008
+ lastLinkedThreadId = null;
5009
+ coreSubscription = null;
5010
+ seq = 0;
5011
+ activeConfig;
5012
+ connection;
5013
+ constructor(core, config, onChange = () => {}) {
5014
+ this.core = core;
5015
+ this.config = config;
5016
+ this.connection = new TrajectoryConnection(core, onChange);
5017
+ this.setConfig(config);
5018
+ }
5019
+ setConfig(config) {
5020
+ this.config = config;
5021
+ if (config === void 0) this.stop();
5022
+ }
5023
+ get isConfigured() {
5024
+ return this.config !== void 0;
5025
+ }
5026
+ get isLegacyCaptureActive() {
5027
+ return this.collector !== null && this.collector.trajectoryId !== null;
5028
+ }
5029
+ startTracking() {
5030
+ if (this.coreSubscription !== null || !this.isLegacyCaptureActive) return;
5031
+ this.coreSubscription = this.core.subscribe({
5032
+ onAgentsChanged: ({ agents }) => {
5033
+ for (const agent of Object.values(agents)) this.track(agent);
5034
+ },
5035
+ onAgentRunStarted: ({ agent }) => this.track(agent),
5036
+ onToolExecutionStart: ({ toolCallId, agentId, toolName }) => {
5037
+ if (!this.isLegacyCaptureActive) return;
5038
+ this.executingTools.set(toolCallId, Date.now());
5039
+ this.emitToolCall("executing", toolCallId, agentId, toolName, {});
5040
+ },
5041
+ onToolExecutionEnd: ({ toolCallId, agentId, toolName, error }) => {
5042
+ if (!this.isLegacyCaptureActive) return;
5043
+ const startedAt = this.executingTools.get(toolCallId);
5044
+ this.executingTools.delete(toolCallId);
5045
+ this.emitToolCall("completed", toolCallId, agentId, toolName, {
5046
+ durationMs: startedAt === void 0 ? void 0 : Date.now() - startedAt,
5047
+ outcome: error === void 0 ? "ok" : "error"
5048
+ });
5049
+ this.toolCalls.delete(toolCallId);
5050
+ }
5051
+ });
5052
+ for (const agent of Object.values(this.core.agents)) this.track(agent);
5053
+ }
5054
+ get trajectoryId() {
5055
+ return this.connection.trajectoryId ?? this.collector?.trajectoryId ?? null;
5056
+ }
5057
+ start(options = {}) {
5058
+ const config = this.config;
5059
+ if (config === void 0) {
5060
+ console.warn("[CopilotKit] startTrajectory() needs the `learning` option on CopilotKitCore (or the CopilotKitProvider `learning` prop).");
5061
+ return Promise.resolve({
5062
+ status: "error",
5063
+ code: "LEARNING_DISABLED"
5064
+ });
5065
+ }
5066
+ if (config.sink === void 0) {
5067
+ if (options.learningContainerIds !== void 0) console.warn("[CopilotKit] learningContainerIds is supported only with a custom sink. Authenticated Trajectory capture does not assign Learning Containers; remove learningContainerIds from the capture options.");
5068
+ this.stopLegacy();
5069
+ return this.connection.start(options.trajectoryId, config);
5070
+ }
5071
+ this.connection.stop();
5072
+ const trajectoryId = options.trajectoryId ?? this.collector?.trajectoryId ?? crypto.randomUUID();
5073
+ if (this.collector === null) {
5074
+ this.activeConfig = config;
5075
+ const runtimeUrl = this.core.runtimeUrl;
5076
+ this.collector = (0, _copilotkit_learning.createCollector)({
5077
+ ...config,
5078
+ beforeSend: (event) => {
5079
+ const sequenced = {
5080
+ ...event,
5081
+ value: {
5082
+ ...event.value,
5083
+ seq: ++this.seq
5084
+ }
5085
+ };
5086
+ return config.beforeSend === void 0 ? sequenced : config.beforeSend(sequenced);
5087
+ },
5088
+ ignoreUrls: [
5089
+ ...config.ignoreUrls ?? [],
5090
+ ...COPILOTKIT_OWN_URLS,
5091
+ ...runtimeUrl === void 0 ? [] : [runtimeUrl]
5092
+ ],
5093
+ enrich: (target) => this.enrich(target)
5094
+ });
5095
+ }
5096
+ const wasActive = this.collector.trajectoryId !== null;
5097
+ this.collector.start({
5098
+ ...options,
5099
+ trajectoryId
5100
+ });
5101
+ if (wasActive) return Promise.resolve(this.collector.trajectoryId === trajectoryId ? {
5102
+ status: "started",
5103
+ trajectoryId
5104
+ } : {
5105
+ status: "error",
5106
+ code: "TRAJECTORY_ACTIVE"
5107
+ });
5108
+ this.startTracking();
5109
+ this.lastLinkedThreadId = null;
5110
+ for (const threadId of this.distinctOpenThreadIds()) this.emitThreadLinked(threadId, "start");
5111
+ return Promise.resolve({
5112
+ status: "started",
5113
+ trajectoryId
5114
+ });
5115
+ }
5116
+ stop() {
5117
+ this.connection.stop();
5118
+ this.stopLegacy();
5119
+ }
5120
+ stopLegacy() {
5121
+ this.collector?.stop();
5122
+ this.collector = null;
5123
+ this.activeConfig = void 0;
5124
+ this.coreSubscription?.unsubscribe();
5125
+ this.coreSubscription = null;
5126
+ for (const subscription of this.agents.values()) subscription.unsubscribe();
5127
+ this.agents.clear();
5128
+ this.toolCalls.clear();
5129
+ this.executingTools.clear();
5130
+ }
5131
+ /** A developer event. Only an explicit legacy sink adds open-Thread context. */
5132
+ emit(name, value) {
5133
+ this.connection.emit(name, value);
5134
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) this.collector?.emit(name, {
5135
+ ...this.openThreadContext(),
5136
+ ...value
5137
+ });
5138
+ }
5139
+ registerOpenThread(params) {
5140
+ const entry = { ...params };
5141
+ const isNew = !this.distinctOpenThreadIds().includes(entry.threadId);
5142
+ this.openThreads.add(entry);
5143
+ if (isNew) this.emitThreadLinked(entry.threadId, "open");
5144
+ return {
5145
+ update: (threadId) => {
5146
+ if (threadId === entry.threadId) return;
5147
+ entry.threadId = threadId;
5148
+ if (![...this.openThreads].filter((other) => other !== entry).some((other) => other.threadId === threadId)) this.emitThreadLinked(threadId, "switch");
5149
+ },
5150
+ unregister: () => {
5151
+ this.openThreads.delete(entry);
5152
+ }
5153
+ };
5154
+ }
5155
+ distinctOpenThreadIds() {
5156
+ return [...new Set([...this.openThreads].map((open) => open.threadId))];
5157
+ }
5158
+ emitThreadLinked(threadId, reason) {
5159
+ if (!(this.collector !== null && this.collector.trajectoryId !== null) || threadId === this.lastLinkedThreadId) return;
5160
+ this.lastLinkedThreadId = threadId;
5161
+ const owner = [...this.openThreads].find((open) => open.threadId === threadId);
5162
+ const agent = this.findAgentForThread(threadId);
5163
+ this.collector?.ɵemit("thread.linked", {
5164
+ threadId,
5165
+ agentId: owner?.agentId ?? agent?.agentId ?? null,
5166
+ hasMessages: (agent?.messages.length ?? 0) > 0,
5167
+ reason
5168
+ });
5169
+ }
5170
+ findAgentForThread(threadId) {
5171
+ return [...this.agents.keys()].find((agent) => agent.threadId === threadId);
5172
+ }
5173
+ track(agent) {
5174
+ const agentId = agent.agentId;
5175
+ if (!this.isLegacyCaptureActive || this.agents.has(agent) || agentId === void 0) return;
5176
+ this.agents.set(agent, agent.subscribe(this.agentSubscriber(agent, agentId)));
5177
+ }
5178
+ agentSubscriber(agent, agentId) {
5179
+ const runIdFor = () => this.lastRunIds.get(agent);
5180
+ return {
5181
+ onRunStartedEvent: ({ event }) => {
5182
+ this.lastRunIds.set(agent, event.runId);
5183
+ this.emitRun("started", agent, event.threadId, event.runId);
5184
+ },
5185
+ onRunFinishedEvent: ({ event }) => {
5186
+ this.emitRun("finished", agent, event.threadId, event.runId);
5187
+ },
5188
+ onRunErrorEvent: () => {
5189
+ this.emitRun("error", agent, agent.threadId, runIdFor() ?? null);
5190
+ },
5191
+ onToolCallStartEvent: ({ event }) => {
5192
+ if (!this.isLegacyCaptureActive) return;
5193
+ const origin = {
5194
+ agentId,
5195
+ threadId: agent.threadId,
5196
+ messageId: event.parentMessageId,
5197
+ runId: runIdFor()
5198
+ };
5199
+ this.toolCalls.set(event.toolCallId, origin);
5200
+ this.emitToolCall("started", event.toolCallId, origin.agentId, event.toolCallName, {});
5201
+ },
5202
+ onToolCallResultEvent: ({ event }) => {
5203
+ if (!this.executingTools.has(event.toolCallId)) this.toolCalls.delete(event.toolCallId);
5204
+ },
5205
+ onNewMessage: ({ message }) => this.emitMessage(agent, message)
5206
+ };
5207
+ }
5208
+ emitRun(phase, agent, threadId, runId) {
5209
+ this.collector?.ɵemit("agent.run", {
5210
+ phase,
5211
+ runId,
5212
+ threadId,
5213
+ agentId: agent.agentId
5214
+ });
5215
+ }
5216
+ emitMessage(agent, message) {
5217
+ const content = typeof message.content === "string" ? message.content : "";
5218
+ const includeText = this.activeConfig?.capture?.agentText !== false && typeof message.content === "string";
5219
+ this.collector?.ɵemit("agent.message", {
5220
+ messageId: message.id,
5221
+ runId: this.runIdForMessage(agent, message.id),
5222
+ threadId: agent.threadId,
5223
+ agentId: agent.agentId,
5224
+ role: message.role,
5225
+ textLength: content.length,
5226
+ ...includeText ? { text: content } : {}
5227
+ });
5228
+ }
5229
+ emitToolCall(phase, toolCallId, agentId, toolName, extra) {
5230
+ const origin = this.toolCalls.get(toolCallId);
5231
+ this.collector?.ɵemit("tool.call", {
5232
+ phase,
5233
+ toolCallId,
5234
+ toolName,
5235
+ agentId,
5236
+ threadId: origin?.threadId ?? null,
5237
+ messageId: origin?.messageId,
5238
+ runId: origin?.runId,
5239
+ ...extra
5240
+ });
5241
+ }
5242
+ runIdForMessage(agent, messageId) {
5243
+ const agentId = agent.agentId;
5244
+ if (agentId === void 0) return null;
5245
+ return this.core.getRunIdForMessage(agentId, agent.threadId, messageId) ?? this.lastRunIds.get(agent) ?? null;
5246
+ }
5247
+ findMessage(messageId) {
5248
+ for (const agent of this.agents.keys()) {
5249
+ const message = agent.messages.find((candidate) => candidate.id === messageId);
5250
+ if (message !== void 0) return {
5251
+ agent,
5252
+ message
5253
+ };
5254
+ }
5255
+ return null;
5256
+ }
5257
+ toolStatus(agent, toolCallId) {
5258
+ if (this.executingTools.has(toolCallId)) return "executing";
5259
+ return agent.messages.some((message) => message.role === "tool" && message.toolCallId === toolCallId) ? "complete" : "inProgress";
5260
+ }
5261
+ /** Adds agent context to a click: exact message first, then the single open Thread. */
5262
+ enrich(target) {
5263
+ const messageId = target.closest("[data-message-id]")?.getAttribute("data-message-id") ?? null;
5264
+ const toolCallId = target.closest("[data-tool-call-id]")?.getAttribute("data-tool-call-id") ?? null;
5265
+ const found = messageId === null ? null : this.findMessage(messageId);
5266
+ if (found !== null) {
5267
+ const { agent, message } = found;
5268
+ const toolCall = toolCallId !== null && message.role === "assistant" ? message.toolCalls?.find((call) => call.id === toolCallId) : void 0;
5269
+ return {
5270
+ threadId: agent.threadId,
5271
+ agentId: agent.agentId,
5272
+ messageId: message.id,
5273
+ runId: this.runIdForMessage(agent, message.id),
5274
+ ...toolCall === void 0 ? {} : {
5275
+ toolCallId: toolCall.id,
5276
+ toolName: toolCall.function.name,
5277
+ toolStatus: this.toolStatus(agent, toolCall.id)
5278
+ }
5279
+ };
5280
+ }
5281
+ return this.openThreadContext();
5282
+ }
5283
+ /** The single open Thread, or `null` with the candidates when several are open. */
5284
+ openThreadContext() {
5285
+ const openIds = this.distinctOpenThreadIds();
5286
+ if (openIds.length > 1) return {
5287
+ threadId: null,
5288
+ threadAmbiguity: {
5289
+ reason: "multiple-open",
5290
+ candidates: openIds
5291
+ }
5292
+ };
5293
+ return { threadId: openIds[0] ?? null };
5294
+ }
5295
+ };
5296
+
4356
5297
  //#endregion
4357
5298
  //#region src/utils/micro-redux.ts
4358
5299
  const INTERNAL_ACTION_TYPES = {
@@ -5505,6 +6446,9 @@ var CopilotKitCore = class {
5505
6446
  runHandler;
5506
6447
  stateManager;
5507
6448
  threadStoreRegistry;
6449
+ learningBridge;
6450
+ notifiedTrajectoryId = null;
6451
+ learningConfiguredListeners = /* @__PURE__ */ new Set();
5508
6452
  /**
5509
6453
  * The single core-owned memory store, created lazily on first
5510
6454
  * `getMemoryStore()` and kept user-scoped for the lifetime of the core.
@@ -5519,7 +6463,7 @@ var CopilotKitCore = class {
5519
6463
  * store that was registered prior to that initial notification.
5520
6464
  */
5521
6465
  previousAgentIds = /* @__PURE__ */ new Set();
5522
- constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, messageFilter, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug }) {
6466
+ constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, messageFilter, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug, learning }) {
5523
6467
  this._headers = normalizeHeaders(headers);
5524
6468
  this._credentials = credentials;
5525
6469
  this._messageFilter = messageFilter;
@@ -5535,6 +6479,7 @@ var CopilotKitCore = class {
5535
6479
  this.runHandler.initialize(tools);
5536
6480
  this.suggestionEngine.initialize(suggestionsConfig);
5537
6481
  this.stateManager.initialize();
6482
+ this.learningBridge = new LearningBridge(this, learning, () => this.notifyTrajectoryChanged());
5538
6483
  this.agentRegistry.setRuntimeTransport(runtimeTransport);
5539
6484
  this.agentRegistry.setRuntimeUrl(runtimeUrl, { deferConnection: deferInitialConnection });
5540
6485
  this.previousAgentIds = new Set(Object.keys(agents__unsafe_dev_only));
@@ -5686,6 +6631,10 @@ var CopilotKitCore = class {
5686
6631
  get runtimeConnectionStatus() {
5687
6632
  return this.agentRegistry.runtimeConnectionStatus;
5688
6633
  }
6634
+ /** @internal The verbatim single-endpoint URL, including any trailing slash. */
6635
+ get ɵruntimeEndpointUrl() {
6636
+ return this.agentRegistry.runtimeEndpointUrl;
6637
+ }
5689
6638
  get ɵruntimeFetch() {
5690
6639
  return this.agentRegistry.createRuntimeFetch();
5691
6640
  }
@@ -5845,6 +6794,83 @@ var CopilotKitCore = class {
5845
6794
  getAgent(id) {
5846
6795
  return this.agentRegistry.getAgent(id);
5847
6796
  }
6797
+ /** Updates settings for the next capture. Removing the config stops capture. */
6798
+ setLearningConfig(config) {
6799
+ const wasConfigured = this.ɵlearningConfigured;
6800
+ this.learningBridge.setConfig(config);
6801
+ this.notifyTrajectoryChanged();
6802
+ if (this.ɵlearningConfigured === wasConfigured) return;
6803
+ for (const listener of this.learningConfiguredListeners) try {
6804
+ listener();
6805
+ } catch (error) {
6806
+ console.error("Learning configured listener error:", error);
6807
+ }
6808
+ }
6809
+ /**
6810
+ * @internal Whether a `learning` config is set. Unlike {@link trajectoryId},
6811
+ * it does not change with capture start, connection failures or reconnects.
6812
+ */
6813
+ get ɵlearningConfigured() {
6814
+ return this.learningBridge.isConfigured;
6815
+ }
6816
+ /** @internal Calls `listener` when {@link ɵlearningConfigured} changes. */
6817
+ ɵsubscribeToLearningConfigured(listener) {
6818
+ this.learningConfiguredListeners.add(listener);
6819
+ return () => {
6820
+ this.learningConfiguredListeners.delete(listener);
6821
+ };
6822
+ }
6823
+ /** The active capture's Trajectory ID, or null while capture is stopped. */
6824
+ get trajectoryId() {
6825
+ return this.learningBridge.trajectoryId;
6826
+ }
6827
+ notifyTrajectoryChanged() {
6828
+ const trajectoryId = this.trajectoryId;
6829
+ if (trajectoryId === this.notifiedTrajectoryId) return;
6830
+ this.notifiedTrajectoryId = trajectoryId;
6831
+ this.notifySubscribers((subscriber) => subscriber.onTrajectoryChanged?.({
6832
+ copilotkit: this,
6833
+ trajectoryId
6834
+ }), "Subscriber onTrajectoryChanged error:");
6835
+ }
6836
+ /**
6837
+ * Starts one Trajectory after Runtime authorization and the Gateway join.
6838
+ * Omit the ID to create one. Repeated starts await the active connection;
6839
+ * a different ID stops the previous capture. Explicit sinks retain the
6840
+ * prototype's synchronous capture and require a stop before changing IDs.
6841
+ *
6842
+ * @example copilotkit.startTrajectory({ trajectoryId: crypto.randomUUID() })
6843
+ */
6844
+ startTrajectory(options = {}) {
6845
+ const result = this.learningBridge.start(options);
6846
+ this.notifyTrajectoryChanged();
6847
+ return result;
6848
+ }
6849
+ /** Cancels pending work and stops capture, attempting one final batch without waiting for its ACK. */
6850
+ stopTrajectory() {
6851
+ this.learningBridge.stop();
6852
+ this.notifyTrajectoryChanged();
6853
+ }
6854
+ /**
6855
+ * Records an outcome that clicks cannot show, such as a saved report or an
6856
+ * approved deal. The wire adds `value.seq`; arrays, primitives, and objects
6857
+ * already containing `seq` are preserved under `value.data`. Explicit legacy
6858
+ * sinks retain object-only events with open-Thread enrichment.
6859
+ * No-op while no Trajectory runs. Events emitted during connection recovery
6860
+ * count as dropped. Built-in names such as `click` are rejected.
6861
+ *
6862
+ * @example copilotkit.emitTrajectoryEvent("deal.approved", { dealId: "deal-1" })
6863
+ */
6864
+ emitTrajectoryEvent(name, value = {}) {
6865
+ this.learningBridge.emit(name, value);
6866
+ }
6867
+ /**
6868
+ * Tells Core that a view shows this Thread, so captured interactions can link to it.
6869
+ * Framework bindings call this; call it yourself only without a CopilotKit provider.
6870
+ */
6871
+ registerOpenThread(params) {
6872
+ return this.learningBridge.registerOpenThread(params);
6873
+ }
5848
6874
  /**
5849
6875
  * Re-apply the current headers to a single agent (delegated to
5850
6876
  * AgentRegistry). Core headers are merged on top of the agent's own
@@ -7407,6 +8433,7 @@ exports.ɵcreateMemoryStore = createMemoryStore;
7407
8433
  exports.ɵcreateThreadSelectors = ɵcreateThreadSelectors;
7408
8434
  exports.ɵcreateThreadStore = createThreadStore;
7409
8435
  exports.ɵisCopilotKitFeature = ɵisCopilotKitFeature;
8436
+ exports.ɵisHttpAgent = ɵisHttpAgent;
7410
8437
  exports.ɵisRetryableMemoryStatus = isRetryableStatus;
7411
8438
  exports.ɵjoinPhoenixChannel$ = ɵjoinPhoenixChannel$;
7412
8439
  exports.ɵmapMemoryMetadataEvent = ɵmapMemoryMetadataEvent;