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