@copilotkit/core 1.76.0 → 1.77.1

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,6 +6,7 @@ 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
 
10
11
  //#region src/utils/http-agent.ts
11
12
  /**
@@ -195,10 +196,17 @@ function ɵcreatePhoenixJoinOutcome$(channel) {
195
196
  *
196
197
  * The socket is constructed and connected on subscription, and disconnected on
197
198
  * teardown. Each subscription creates an isolated socket instance.
199
+ *
200
+ * Heartbeats default to every 15s: Phoenix's own 30s default matches common
201
+ * reverse-proxy WebSocket idle timeouts (e.g. Azure Application Gateway), which
202
+ * would drop the socket during a quiet run. Callers may override it.
198
203
  */
199
204
  function ɵphoenixSocket$(options) {
200
205
  return (0, rxjs.defer)(() => {
201
- const socket = new phoenix.Socket(options.url, options.options);
206
+ const socket = new phoenix.Socket(options.url, {
207
+ heartbeatIntervalMs: 15e3,
208
+ ...options.options
209
+ });
202
210
  const signals$ = ɵcreatePhoenixSocketSignals$(socket).pipe((0, rxjs_operators.shareReplay)({
203
211
  bufferSize: 1,
204
212
  refCount: true
@@ -282,6 +290,11 @@ const STOP_RUN_EVENT = "stop_run";
282
290
  const CONNECT_STREAM_IDLE_REPLAY_FALLBACK_MS = 100;
283
291
  const CONNECT_IDLE_WARNING_MS = 3e4;
284
292
  const MAX_UNOPENED_CREDENTIAL_REFRESHES = 2;
293
+ function isMcpResourceRead(input) {
294
+ const forwardedProps = input.forwardedProps;
295
+ const request = forwardedProps?.__proxiedMCPRequest;
296
+ return forwardedProps?.__copilotkitMcpResourceReadOnly === true && typeof request === "object" && request !== null && request.method === "resources/read";
297
+ }
285
298
  var AgentThreadLockedError = class extends Error {
286
299
  constructor(threadId) {
287
300
  super(threadId ? `Thread ${threadId} is locked` : "Thread is locked");
@@ -389,10 +402,22 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
389
402
  * server-pushed AG-UI events to the Observable subscriber.
390
403
  */
391
404
  run(input) {
392
- this.threadId = input.threadId;
393
- this.canonicalRunId = input.runId;
405
+ if (!isMcpResourceRead(input)) {
406
+ this.threadId = input.threadId;
407
+ this.canonicalRunId = input.runId;
408
+ }
394
409
  return (0, rxjs.defer)(() => this.requestJoinCredentials$("run", input)).pipe((0, rxjs.switchMap)((credentials) => {
395
410
  if (credentials === null) return (0, rxjs.throwError)(() => /* @__PURE__ */ new Error("REST run request returned no credentials"));
411
+ if ("kind" in credentials) return (0, rxjs.of)({
412
+ type: _ag_ui_client.EventType.RUN_STARTED,
413
+ threadId: credentials.threadId,
414
+ runId: credentials.runId
415
+ }, {
416
+ type: _ag_ui_client.EventType.RUN_FINISHED,
417
+ threadId: credentials.threadId,
418
+ runId: credentials.runId,
419
+ result: credentials.result
420
+ });
396
421
  const canonicalInput = this.applyCanonicalRunIdentity(input, credentials, { fallbackToInputRunId: true });
397
422
  return this.observeThread$(canonicalInput, credentials, {
398
423
  completeOnRunError: false,
@@ -484,7 +509,18 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
484
509
  const text = await response.text().catch(() => "");
485
510
  throw new Error(text || response.statusText || String(response.status));
486
511
  }
487
- return this.normalizeJoinCredentials(await response.json(), input);
512
+ const payload = await response.json();
513
+ if (mode === "run" && isMcpResourceRead(input) && typeof payload === "object" && payload !== null && payload.kind === "mcp-resource-read") {
514
+ const resource = payload;
515
+ if (typeof resource.threadId !== "string" || !resource.threadId || typeof resource.runId !== "string" || !resource.runId) throw new Error("invalid MCP resource response identity");
516
+ return {
517
+ kind: "mcp-resource-read",
518
+ threadId: resource.threadId,
519
+ runId: resource.runId,
520
+ result: resource.result
521
+ };
522
+ }
523
+ return this.normalizeJoinCredentials(payload, input);
488
524
  } catch (error) {
489
525
  if (error instanceof AgentThreadLockedError) throw error;
490
526
  throw new Error(`REST ${mode} request failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
@@ -1267,6 +1303,21 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
1267
1303
  }
1268
1304
  return cloned;
1269
1305
  }
1306
+ /** Run presentation-only MCP reads on a separate client agent so their
1307
+ * lifecycle cannot replace an in-flight approval's detach/completion state. */
1308
+ async ɵrunMcpResourceRead(parameters) {
1309
+ const request = parameters.forwardedProps?.__proxiedMCPRequest;
1310
+ if (typeof request !== "object" || request === null || request.method !== "resources/read") throw new Error("Only MCP resources/read can use the read-only path");
1311
+ await this.ensureRuntimeConfiguration();
1312
+ const resourceParameters = {
1313
+ ...parameters,
1314
+ forwardedProps: {
1315
+ ...parameters.forwardedProps,
1316
+ __copilotkitMcpResourceReadOnly: true
1317
+ }
1318
+ };
1319
+ return this.runtimeMode === _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE ? this.clone().runAgent(resourceParameters) : this.runAgent(resourceParameters);
1320
+ }
1270
1321
  /**
1271
1322
  * Drop the delegate's cached `lastSeenEventId` for this thread so
1272
1323
  * the next connect requests a full historical replay from the
@@ -1907,6 +1958,10 @@ var AgentRegistry = class {
1907
1958
  this.applyMessageFilterToAgent(agent);
1908
1959
  });
1909
1960
  }
1961
+ /** Preserve the exact configured URL when posting to a single endpoint. */
1962
+ get runtimeEndpointUrl() {
1963
+ return this._runtimeEndpointUrl;
1964
+ }
1910
1965
  createRuntimeFetch() {
1911
1966
  if (!this.runtimeFetch) this.runtimeFetch = (async (input, init) => {
1912
1967
  const meta = () => runtimeRequestMeta(init);
@@ -2931,6 +2986,68 @@ function normalizeToolResultContent(content) {
2931
2986
  function isForwardedToClientPlaceholder(content) {
2932
2987
  return normalizeToolResultContent(content) === FORWARDED_TO_CLIENT;
2933
2988
  }
2989
+ const MEDIA_PART_TYPES = new Set([
2990
+ "image",
2991
+ "audio",
2992
+ "video",
2993
+ "document"
2994
+ ]);
2995
+ function isOptionalString(value) {
2996
+ return value === void 0 || typeof value === "string";
2997
+ }
2998
+ function isPartSource(value) {
2999
+ if (!value || typeof value !== "object") return false;
3000
+ const source = value;
3001
+ if (typeof source.value !== "string") return false;
3002
+ switch (source.type) {
3003
+ case "data": return typeof source.mimeType === "string";
3004
+ case "url": return isOptionalString(source.mimeType);
3005
+ case "file": return isOptionalString(source.provider) && isOptionalString(source.mimeType);
3006
+ default: return false;
3007
+ }
3008
+ }
3009
+ /**
3010
+ * Mirrors `ContentPartSchema` from `@ag-ui/core/schemas`. The runtime parses
3011
+ * the request with that schema, and one invalid part there fails the whole run.
3012
+ * Core does not import the schema because the UMD build has no global for it.
3013
+ * A test checks this function against the schema.
3014
+ */
3015
+ function isContentPart(value) {
3016
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3017
+ const part = value;
3018
+ if (!isOptionalString(part.id) || part.metadata === null) return false;
3019
+ if (part.type === "text") return typeof part.text === "string";
3020
+ return typeof part.type === "string" && MEDIA_PART_TYPES.has(part.type) && isPartSource(part.source);
3021
+ }
3022
+ /**
3023
+ * Converts what a frontend tool handler returned into tool message content.
3024
+ * A non-empty array of AG-UI content parts passes through unchanged, so an
3025
+ * image reaches the model as media. Everything else becomes a string, as before.
3026
+ *
3027
+ * `text` is the string form, for surfaces typed as `result: string`
3028
+ * (`runTool`, `onToolExecutionEnd`). For content parts it is the JSON text they
3029
+ * received before. It is computed here, so a part that cannot be serialized
3030
+ * throws inside the caller's handler error boundary.
3031
+ */
3032
+ function toToolResultContent(result) {
3033
+ if (result === void 0 || result === null) return {
3034
+ content: "",
3035
+ text: ""
3036
+ };
3037
+ if (typeof result === "string") return {
3038
+ content: result,
3039
+ text: result
3040
+ };
3041
+ const text = JSON.stringify(result);
3042
+ if (Array.isArray(result) && result.length > 0 && result.every(isContentPart)) return {
3043
+ content: result,
3044
+ text
3045
+ };
3046
+ return {
3047
+ content: text,
3048
+ text
3049
+ };
3050
+ }
2934
3051
 
2935
3052
  //#endregion
2936
3053
  //#region src/core/tool-schema.ts
@@ -3422,7 +3539,7 @@ var RunHandler = class {
3422
3539
  this.processAgentResult({
3423
3540
  runAgentResult,
3424
3541
  agent,
3425
- toolExecutionMode: "human-in-the-loop",
3542
+ toolExecutionMode: "restore",
3426
3543
  signal: controller.signal
3427
3544
  }).catch(async (error) => {
3428
3545
  await this._internal.emitError({
@@ -3553,8 +3670,8 @@ var RunHandler = class {
3553
3670
  const threadId = agent.threadId;
3554
3671
  const executing = this._executingToolCalls.get(agent) ?? /* @__PURE__ */ new Set();
3555
3672
  this._executingToolCalls.set(agent, executing);
3556
- if (toolExecutionMode === "human-in-the-loop" && agent.messages.some((message) => message.role === "assistant" && message.toolCalls?.some((call) => executing.has(JSON.stringify([threadId, call.id])) && !agent.messages.some((result) => result.role === "tool" && result.toolCallId === call.id && !this.isFrontendPlaceholderResult(result))))) return runAgentResult;
3557
- const messagesToProcess = toolExecutionMode === "human-in-the-loop" ? [...agent.messages] : newMessages;
3673
+ if (toolExecutionMode === "restore" && agent.messages.some((message) => message.role === "assistant" && message.toolCalls?.some((call) => executing.has(JSON.stringify([threadId, call.id])) && !agent.messages.some((result) => result.role === "tool" && result.toolCallId === call.id && !this.isFrontendPlaceholderResult(result))))) return runAgentResult;
3674
+ const messagesToProcess = toolExecutionMode === "restore" ? [...agent.messages] : newMessages;
3558
3675
  for (const message of messagesToProcess) if (message.role === "assistant") for (const toolCall of message.toolCalls || []) {
3559
3676
  const tool = this.getTool({
3560
3677
  toolName: toolCall.function.name,
@@ -3570,7 +3687,7 @@ var RunHandler = class {
3570
3687
  return wildcardTool;
3571
3688
  };
3572
3689
  const executableTool = tool ?? getWildcardTool();
3573
- if (toolExecutionMode === "human-in-the-loop" && (executableTool?.type !== "human-in-the-loop" || !executableTool.handler)) continue;
3690
+ if (toolExecutionMode === "restore" && (!executableTool?.handler || executableTool.type !== "human-in-the-loop" && executableTool.reconnectBehavior !== "resume-pending")) continue;
3574
3691
  const executionKey = JSON.stringify([threadId, toolCall.id]);
3575
3692
  if (signal?.aborted || agent.threadId !== threadId || executing.has(executionKey)) continue;
3576
3693
  const existingResultIndex = newMessages.findIndex((m) => m.role === "tool" && m.toolCallId === toolCall.id);
@@ -3601,14 +3718,14 @@ var RunHandler = class {
3601
3718
  if (signal?.aborted) abortInteraction();
3602
3719
  }
3603
3720
  const executionSignal = interactionController?.signal ?? signal;
3604
- const discardOnAbort = toolExecutionMode === "human-in-the-loop" || !!interactionController;
3721
+ const discardOnAbort = toolExecutionMode === "restore" || !!interactionController;
3605
3722
  try {
3606
3723
  if (tool) {
3607
- if (await this.executeSpecificTool(tool, toolCall, message, agent, agentId, executionSignal, discardOnAbort)) needsFollowUp = true;
3724
+ if (await this.executeSpecificTool(tool, toolCall, message, agent, agentId, executionSignal, discardOnAbort, toolExecutionMode === "restore")) needsFollowUp = true;
3608
3725
  } else {
3609
3726
  const fallbackTool = getWildcardTool();
3610
3727
  if (fallbackTool) {
3611
- if (await this.executeWildcardTool(fallbackTool, toolCall, message, agent, agentId, executionSignal, discardOnAbort)) needsFollowUp = true;
3728
+ if (await this.executeWildcardTool(fallbackTool, toolCall, message, agent, agentId, executionSignal, discardOnAbort, toolExecutionMode === "restore")) needsFollowUp = true;
3612
3729
  }
3613
3730
  }
3614
3731
  } finally {
@@ -3642,8 +3759,9 @@ var RunHandler = class {
3642
3759
  * Handles arg parsing, subscriber notifications, handler invocation, result stringification,
3643
3760
  * and error handling.
3644
3761
  */
3645
- async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId, signal = this._runAbortController?.signal, discardOnAbort = false }) {
3762
+ async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId, signal = this._runAbortController?.signal, discardOnAbort = false, isReplay = false }) {
3646
3763
  let toolCallResult = "";
3764
+ let toolCallText = "";
3647
3765
  let errorMessage;
3648
3766
  let isArgumentError = false;
3649
3767
  let parsedArgs;
@@ -3677,11 +3795,10 @@ var RunHandler = class {
3677
3795
  const result = await tool.handler(parsedArgs, {
3678
3796
  toolCall,
3679
3797
  agent,
3680
- signal
3798
+ signal,
3799
+ ...isReplay ? { isReplay } : {}
3681
3800
  });
3682
- if (result === void 0 || result === null) toolCallResult = "";
3683
- else if (typeof result === "string") toolCallResult = result;
3684
- else toolCallResult = JSON.stringify(result);
3801
+ ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3685
3802
  } catch (error) {
3686
3803
  const handlerError = error instanceof Error ? error : new Error(String(error));
3687
3804
  errorMessage = handlerError.message;
@@ -3698,17 +3815,18 @@ var RunHandler = class {
3698
3815
  }
3699
3816
  });
3700
3817
  }
3701
- if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
3818
+ if (errorMessage) toolCallResult = toolCallText = `Error: ${errorMessage}`;
3702
3819
  await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
3703
3820
  copilotkit: this.core,
3704
3821
  toolCallId: toolCall.id,
3705
3822
  agentId,
3706
3823
  toolName: toolCall.function.name,
3707
- result: errorMessage ? "" : toolCallResult,
3824
+ result: errorMessage ? "" : toolCallText,
3708
3825
  error: errorMessage
3709
3826
  }), "Subscriber onToolExecutionEnd error:");
3710
3827
  return {
3711
- result: toolCallResult,
3828
+ content: toolCallResult,
3829
+ result: toolCallText,
3712
3830
  error: errorMessage,
3713
3831
  isArgumentError
3714
3832
  };
@@ -3716,10 +3834,11 @@ var RunHandler = class {
3716
3834
  /**
3717
3835
  * Execute a specific tool
3718
3836
  */
3719
- async executeSpecificTool(tool, toolCall, message, agent, agentId, signal, discardOnAbort = false) {
3837
+ async executeSpecificTool(tool, toolCall, message, agent, agentId, signal, discardOnAbort = false, isReplay = false) {
3720
3838
  const threadId = agent.threadId;
3721
3839
  if (tool?.agentId && tool.agentId !== agent.agentId) return false;
3722
3840
  let handlerResult = {
3841
+ content: "",
3723
3842
  result: "",
3724
3843
  error: void 0,
3725
3844
  isArgumentError: false
@@ -3733,7 +3852,8 @@ var RunHandler = class {
3733
3852
  toolType: "specific",
3734
3853
  messageId: message.id,
3735
3854
  signal,
3736
- discardOnAbort
3855
+ discardOnAbort,
3856
+ isReplay
3737
3857
  });
3738
3858
  {
3739
3859
  const messageIndex = agent.messages.findIndex((m) => m.id === message.id);
@@ -3744,7 +3864,7 @@ var RunHandler = class {
3744
3864
  id: (0, _copilotkit_shared.randomUUID)(),
3745
3865
  role: "tool",
3746
3866
  toolCallId: toolCall.id,
3747
- content: handlerResult.result
3867
+ content: handlerResult.content
3748
3868
  };
3749
3869
  agent.messages.splice(insertAt, 0, toolMessage);
3750
3870
  if (!handlerResult.error && tool?.followUp !== false) return true;
@@ -3757,10 +3877,11 @@ var RunHandler = class {
3757
3877
  * specific tools, so this method keeps its own arg-wrapping logic rather than
3758
3878
  * delegating to `executeToolHandler`.
3759
3879
  */
3760
- async executeWildcardTool(wildcardTool, toolCall, message, agent, agentId, signal, discardOnAbort = false) {
3880
+ async executeWildcardTool(wildcardTool, toolCall, message, agent, agentId, signal, discardOnAbort = false, isReplay = false) {
3761
3881
  const threadId = agent.threadId;
3762
3882
  if (wildcardTool?.agentId && wildcardTool.agentId !== agent.agentId) return false;
3763
3883
  let toolCallResult = "";
3884
+ let toolCallText = "";
3764
3885
  let errorMessage;
3765
3886
  if (wildcardTool?.handler) {
3766
3887
  let parsedArgs;
@@ -3797,11 +3918,10 @@ var RunHandler = class {
3797
3918
  const result = await wildcardTool.handler(wildcardArgs, {
3798
3919
  toolCall,
3799
3920
  agent,
3800
- signal
3921
+ signal,
3922
+ ...isReplay ? { isReplay } : {}
3801
3923
  });
3802
- if (result === void 0 || result === null) toolCallResult = "";
3803
- else if (typeof result === "string") toolCallResult = result;
3804
- else toolCallResult = JSON.stringify(result);
3924
+ ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3805
3925
  } catch (error) {
3806
3926
  const handlerError = error instanceof Error ? error : new Error(String(error));
3807
3927
  errorMessage = handlerError.message;
@@ -3818,13 +3938,13 @@ var RunHandler = class {
3818
3938
  }
3819
3939
  });
3820
3940
  }
3821
- if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
3941
+ if (errorMessage) toolCallResult = toolCallText = `Error: ${errorMessage}`;
3822
3942
  await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
3823
3943
  copilotkit: this.core,
3824
3944
  toolCallId: toolCall.id,
3825
3945
  agentId,
3826
3946
  toolName: toolCall.function.name,
3827
- result: errorMessage ? "" : toolCallResult,
3947
+ result: errorMessage ? "" : toolCallText,
3828
3948
  error: errorMessage
3829
3949
  }), "Subscriber onToolExecutionEnd error:");
3830
3950
  }
@@ -3895,6 +4015,7 @@ var RunHandler = class {
3895
4015
  };
3896
4016
  agent.messages.push(assistantMessage);
3897
4017
  let handlerResult = {
4018
+ content: "",
3898
4019
  result: "",
3899
4020
  error: void 0,
3900
4021
  isArgumentError: false
@@ -3911,7 +4032,7 @@ var RunHandler = class {
3911
4032
  id: (0, _copilotkit_shared.randomUUID)(),
3912
4033
  role: "tool",
3913
4034
  toolCallId,
3914
- content: handlerResult.result
4035
+ content: handlerResult.content
3915
4036
  };
3916
4037
  const assistantIndex = agent.messages.findIndex((m) => m.id === assistantMessage.id);
3917
4038
  if (assistantIndex !== -1) agent.messages.splice(assistantIndex + 1, 0, toolResultMessage);
@@ -4512,6 +4633,750 @@ var ThreadStoreRegistry = class {
4512
4633
  }
4513
4634
  };
4514
4635
 
4636
+ //#endregion
4637
+ //#region src/core/trajectory-connection.ts
4638
+ const TIMEOUT_MS = 1e4;
4639
+ const BATCH_INTERVAL_MS = 2e3;
4640
+ const MAX_BATCH_EVENTS = 50;
4641
+ const MAX_BATCH_BYTES = 64 * 1024;
4642
+ const MAX_EVENT_BYTES = 16 * 1024;
4643
+ const byteLength = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
4644
+ const reconnectDelay = (0, _copilotkit_shared.phoenixExponentialBackoff)(1e3, 1e4);
4645
+ function isObject(value) {
4646
+ return typeof value === "object" && value !== null;
4647
+ }
4648
+ function isNonNegativeInteger(value) {
4649
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
4650
+ }
4651
+ function isBatchAcknowledgement(value, eventCount) {
4652
+ 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);
4653
+ }
4654
+ function isGrant(value) {
4655
+ 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;
4656
+ }
4657
+ function errorCode(value, fallback) {
4658
+ return isObject(value) && typeof value.code === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(value.code) ? value.code : fallback;
4659
+ }
4660
+ /** Owns bounded, connected-only capture batches. Failed batches are never resent. */
4661
+ var TrajectoryConnection = class {
4662
+ session;
4663
+ nextSeq = Math.floor(Date.now()) * 1e3;
4664
+ constructor(core, onChange) {
4665
+ this.core = core;
4666
+ this.onChange = onChange;
4667
+ }
4668
+ get trajectoryId() {
4669
+ return this.session?.ready ? this.session.trajectoryId : null;
4670
+ }
4671
+ start(trajectoryId, config) {
4672
+ if (this.session && (trajectoryId === void 0 || trajectoryId === this.session.trajectoryId)) return this.session.promise;
4673
+ this.stop();
4674
+ const id = trajectoryId ?? crypto.randomUUID();
4675
+ let resolveStart;
4676
+ const promise = new Promise((resolve) => {
4677
+ resolveStart = resolve;
4678
+ });
4679
+ const session = {
4680
+ trajectoryId: id,
4681
+ config,
4682
+ promise,
4683
+ resolve: resolveStart,
4684
+ retries: 0,
4685
+ queue: [],
4686
+ dropped: 0,
4687
+ ready: false,
4688
+ started: false,
4689
+ offline: typeof navigator !== "undefined" && navigator.onLine === false,
4690
+ onOffline: () => {
4691
+ if (this.session !== session) return;
4692
+ session.offline = true;
4693
+ if (session.connection) this.fail(session, session.connection, "CONNECTION_LOST");
4694
+ clearTimeout(session.retry);
4695
+ session.retry = void 0;
4696
+ },
4697
+ onOnline: () => {
4698
+ if (this.session !== session) return;
4699
+ session.offline = false;
4700
+ if (!session.connection) {
4701
+ clearTimeout(session.retry);
4702
+ session.retry = void 0;
4703
+ this.connect(session);
4704
+ }
4705
+ },
4706
+ linkedThreads: /* @__PURE__ */ new Set(),
4707
+ agentWatches: /* @__PURE__ */ new Map()
4708
+ };
4709
+ this.session = session;
4710
+ session.runWatch = this.core.subscribe({ onAgentRunStarted: ({ agent }) => this.watchAgent(session, agent) });
4711
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
4712
+ window.addEventListener("offline", session.onOffline);
4713
+ window.addEventListener("online", session.onOnline);
4714
+ }
4715
+ if (session.offline) {
4716
+ this.report(session, "CONNECTION_LOST");
4717
+ this.end(session, {
4718
+ status: "error",
4719
+ code: "CONNECTION_LOST"
4720
+ });
4721
+ } else this.connect(session);
4722
+ return promise;
4723
+ }
4724
+ stop() {
4725
+ const session = this.session;
4726
+ if (!session) return;
4727
+ if (session.ready && session.connection) this.flush(session, session.connection);
4728
+ this.end(session, {
4729
+ status: "error",
4730
+ code: "CANCELLED"
4731
+ });
4732
+ }
4733
+ emit(name, value) {
4734
+ const session = this.session;
4735
+ if (session?.ready) session.collector?.emit(name, value);
4736
+ else if (session?.started) this.addDropped(session, 1, false);
4737
+ }
4738
+ watchAgent(session, agent) {
4739
+ if (this.session !== session || session.agentWatches.has(agent)) return;
4740
+ session.agentWatches.set(agent, agent.subscribe({ onRunStartedEvent: ({ event }) => this.linkThread(session, event) }));
4741
+ }
4742
+ /** Sends one `thread.linked` per Thread. A link that cannot be sent waits for the next run. */
4743
+ linkThread(session, { threadId }) {
4744
+ const connection = session.connection;
4745
+ if (!session.ready || !connection || !threadId || session.linkedThreads.has(threadId)) return;
4746
+ session.linkedThreads.add(threadId);
4747
+ const event = {
4748
+ type: "CUSTOM",
4749
+ name: "thread.linked",
4750
+ timestamp: Date.now(),
4751
+ value: { threadId }
4752
+ };
4753
+ try {
4754
+ const { beforeSend } = session.config;
4755
+ const kept = beforeSend === void 0 ? event : beforeSend(event);
4756
+ if (kept !== null) this.send(session, connection, kept);
4757
+ } catch {
4758
+ this.report(session, "INVALID_EVENT");
4759
+ }
4760
+ }
4761
+ report(session, code) {
4762
+ try {
4763
+ session.config.onError?.({
4764
+ code,
4765
+ message: `Trajectory capture: ${code}.`
4766
+ });
4767
+ } catch {}
4768
+ }
4769
+ current(session, connection) {
4770
+ return this.session === session && session.connection === connection;
4771
+ }
4772
+ end(session, result) {
4773
+ if (this.session !== session) return;
4774
+ this.session = void 0;
4775
+ clearTimeout(session.retry);
4776
+ session.runWatch?.unsubscribe();
4777
+ for (const watch of session.agentWatches.values()) watch.unsubscribe();
4778
+ session.agentWatches.clear();
4779
+ session.collector?.stop();
4780
+ session.ready = false;
4781
+ if (session.queue.length > 0) this.report(session, "EVENTS_DROPPED");
4782
+ this.discardQueue(session);
4783
+ if (session.connection) {
4784
+ for (const pending of session.connection.pending) {
4785
+ pending.finish("PERSISTENCE_UNKNOWN");
4786
+ this.report(session, "PERSISTENCE_UNKNOWN");
4787
+ }
4788
+ this.cleanup(session, session.connection);
4789
+ }
4790
+ if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
4791
+ window.removeEventListener("offline", session.onOffline);
4792
+ window.removeEventListener("online", session.onOnline);
4793
+ }
4794
+ session.resolve(result);
4795
+ this.onChange();
4796
+ }
4797
+ cleanup(session, connection) {
4798
+ session.connection = void 0;
4799
+ clearTimeout(connection.timer);
4800
+ connection.abort.abort();
4801
+ for (const pending of connection.pending) clearTimeout(pending.timer);
4802
+ connection.pending.clear();
4803
+ const { socket, channel } = connection;
4804
+ try {
4805
+ if (channel) {
4806
+ for (const [event, ref] of connection.channelRefs) channel.off(event, ref);
4807
+ channel.leave();
4808
+ }
4809
+ } finally {
4810
+ if (socket) {
4811
+ socket.off(connection.socketRefs);
4812
+ socket.disconnect();
4813
+ }
4814
+ }
4815
+ }
4816
+ fail(session, connection, code, retryable = true) {
4817
+ if (!this.current(session, connection)) return;
4818
+ session.collector?.stop();
4819
+ session.collector = void 0;
4820
+ if (session.ready && session.started) session.promise = new Promise((resolve) => {
4821
+ session.resolve = resolve;
4822
+ });
4823
+ session.ready = false;
4824
+ this.discardQueue(session);
4825
+ session.linkedThreads.clear();
4826
+ for (const pending of connection.pending) pending.finish("PERSISTENCE_UNKNOWN");
4827
+ if (!this.current(session, connection)) return;
4828
+ this.cleanup(session, connection);
4829
+ this.onChange();
4830
+ this.report(session, code);
4831
+ if (this.session !== session) return;
4832
+ if (!session.started || !retryable) {
4833
+ this.end(session, {
4834
+ status: "error",
4835
+ code
4836
+ });
4837
+ return;
4838
+ }
4839
+ if (session.offline || this.session !== session) return;
4840
+ session.retry = setTimeout(() => {
4841
+ session.retry = void 0;
4842
+ if (this.session === session && !session.offline) this.connect(session);
4843
+ }, reconnectDelay(++session.retries));
4844
+ }
4845
+ async connect(session) {
4846
+ if (this.session !== session || session.offline || session.connection) return;
4847
+ const connection = {
4848
+ abort: new AbortController(),
4849
+ socketRefs: [],
4850
+ channelRefs: [],
4851
+ pending: /* @__PURE__ */ new Set()
4852
+ };
4853
+ session.connection = connection;
4854
+ connection.timer = setTimeout(() => this.fail(session, connection, "CONNECTION_TIMEOUT"), TIMEOUT_MS);
4855
+ try {
4856
+ const runtimeUrl = this.core.runtimeUrl;
4857
+ if (!runtimeUrl) {
4858
+ this.fail(session, connection, "RUNTIME_REQUIRED", false);
4859
+ return;
4860
+ }
4861
+ let transport = this.core.runtimeTransport;
4862
+ if (transport === "auto") {
4863
+ transport = await this.detectedTransport(connection.abort.signal);
4864
+ if (!this.current(session, connection)) return;
4865
+ }
4866
+ const rest = transport === "rest";
4867
+ const response = await fetch(rest ? `${runtimeUrl}/trajectory/${encodeURIComponent(session.trajectoryId)}/connect` : this.core.ɵruntimeEndpointUrl ?? runtimeUrl, {
4868
+ method: "POST",
4869
+ redirect: "error",
4870
+ signal: connection.abort.signal,
4871
+ headers: {
4872
+ ...this.core.headers,
4873
+ "Content-Type": "application/json"
4874
+ },
4875
+ credentials: this.core.credentials,
4876
+ body: JSON.stringify(rest ? {} : {
4877
+ method: "trajectory/connect",
4878
+ params: { trajectoryId: session.trajectoryId },
4879
+ body: {}
4880
+ })
4881
+ });
4882
+ if (!this.current(session, connection)) return;
4883
+ const payload = await response.json().catch(() => void 0);
4884
+ if (!this.current(session, connection)) return;
4885
+ if (!response.ok) {
4886
+ this.fail(session, connection, errorCode(payload, "CONNECTION_FAILED"), response.status >= 500 || response.status === 408 || response.status === 429);
4887
+ return;
4888
+ }
4889
+ if (!isGrant(payload)) {
4890
+ this.fail(session, connection, "INVALID_GRANT", false);
4891
+ return;
4892
+ }
4893
+ const socket = new phoenix.Socket(payload.realtime.clientUrl, {
4894
+ params: { join_token: payload.joinToken },
4895
+ timeout: TIMEOUT_MS
4896
+ });
4897
+ connection.socket = socket;
4898
+ const lost = () => this.fail(session, connection, "CONNECTION_LOST");
4899
+ connection.socketRefs.push(socket.onError(lost), socket.onClose(lost));
4900
+ const channel = socket.channel(payload.realtime.topic, {});
4901
+ connection.channel = channel;
4902
+ connection.channelRefs.push(["phx_error", channel.onError(lost)], ["phx_close", channel.onClose(lost)]);
4903
+ socket.connect();
4904
+ if (!this.current(session, connection)) return;
4905
+ channel.join(TIMEOUT_MS).receive("ok", () => {
4906
+ if (!this.current(session, connection)) return;
4907
+ try {
4908
+ clearTimeout(connection.timer);
4909
+ session.ready = true;
4910
+ session.retries = 0;
4911
+ session.collector = (0, _copilotkit_learning.createTrajectoryCollector)({
4912
+ ...session.config,
4913
+ ignoreUrls: [
4914
+ ...session.config.ignoreUrls ?? [],
4915
+ ...this.core.runtimeUrl ? [this.core.runtimeUrl] : [],
4916
+ ...this.core.ɵruntimeEndpointUrl ? [this.core.ɵruntimeEndpointUrl] : [],
4917
+ "https://telemetry.copilotkit.ai/",
4918
+ "https://cdn.copilotkit.ai/"
4919
+ ],
4920
+ onError: (error) => {
4921
+ if (error.code === "CAPTURE_FAILED") this.fail(session, connection, error.code, false);
4922
+ else {
4923
+ if (error.code === "INVALID_EVENT" || error.code === "EVENT_TOO_LARGE") this.addDropped(session, 1);
4924
+ session.config.onError?.(error);
4925
+ }
4926
+ },
4927
+ send: (event) => this.send(session, connection, event)
4928
+ });
4929
+ session.collector.start();
4930
+ if (!this.current(session, connection)) return;
4931
+ if (session.dropped > 0) this.scheduleFlush(session, connection);
4932
+ session.started = true;
4933
+ session.resolve({
4934
+ status: "started",
4935
+ trajectoryId: session.trajectoryId
4936
+ });
4937
+ this.onChange();
4938
+ } catch {
4939
+ this.fail(session, connection, "CAPTURE_FAILED", false);
4940
+ }
4941
+ }).receive("error", (joinError) => this.fail(session, connection, errorCode(joinError, "JOIN_FAILED"))).receive("timeout", () => this.fail(session, connection, "CONNECTION_TIMEOUT"));
4942
+ } catch {
4943
+ this.fail(session, connection, "CONNECTION_FAILED");
4944
+ }
4945
+ }
4946
+ /** Resolves once auto-detection picks a transport, or with "auto" when the attempt is aborted. */
4947
+ detectedTransport(signal) {
4948
+ return new Promise((resolve) => {
4949
+ const finish = () => {
4950
+ subscription.unsubscribe();
4951
+ signal.removeEventListener("abort", finish);
4952
+ resolve(this.core.runtimeTransport);
4953
+ };
4954
+ const subscription = this.core.subscribe({ onRuntimeConnectionStatusChanged: () => {
4955
+ if (this.core.runtimeTransport !== "auto") finish();
4956
+ } });
4957
+ signal.addEventListener("abort", finish, { once: true });
4958
+ });
4959
+ }
4960
+ addDropped(session, count, schedule = true) {
4961
+ session.dropped = Math.min(Number.MAX_SAFE_INTEGER, session.dropped + count);
4962
+ if (schedule && count > 0 && session.ready && session.connection) this.scheduleFlush(session, session.connection);
4963
+ }
4964
+ scheduleFlush(session, connection) {
4965
+ if (session.flushTimer !== void 0 || !this.current(session, connection)) return;
4966
+ session.flushTimer = setTimeout(() => {
4967
+ session.flushTimer = void 0;
4968
+ this.flush(session, connection);
4969
+ }, BATCH_INTERVAL_MS);
4970
+ }
4971
+ discardQueue(session) {
4972
+ clearTimeout(session.flushTimer);
4973
+ session.flushTimer = void 0;
4974
+ this.addDropped(session, session.queue.length, false);
4975
+ session.queue = [];
4976
+ }
4977
+ connected(connection) {
4978
+ return connection.socket?.isConnected() === true && connection.channel?.state === "joined";
4979
+ }
4980
+ send(session, connection, event) {
4981
+ if (!this.current(session, connection) || !session.ready) return;
4982
+ if (!this.connected(connection)) {
4983
+ this.addDropped(session, 1);
4984
+ this.fail(session, connection, "CONNECTION_LOST");
4985
+ return;
4986
+ }
4987
+ if (!Number.isSafeInteger(this.nextSeq)) {
4988
+ this.addDropped(session, 1);
4989
+ this.report(session, "SEQUENCE_EXHAUSTED");
4990
+ return;
4991
+ }
4992
+ const original = event.value;
4993
+ const fields = isObject(original) && !Array.isArray(original) && !Object.hasOwn(original, "seq") ? original : { data: original };
4994
+ const wire = {
4995
+ ...event,
4996
+ value: {
4997
+ ...fields,
4998
+ seq: this.nextSeq++
4999
+ }
5000
+ };
5001
+ if (byteLength(wire) > MAX_EVENT_BYTES) {
5002
+ this.addDropped(session, 1);
5003
+ this.report(session, "EVENT_TOO_LARGE");
5004
+ return;
5005
+ }
5006
+ const fits = () => session.queue.length < MAX_BATCH_EVENTS && byteLength({
5007
+ events: [...session.queue, wire],
5008
+ dropped: Number.MAX_SAFE_INTEGER
5009
+ }) <= MAX_BATCH_BYTES;
5010
+ if (!fits()) this.flush(session, connection);
5011
+ if (!this.current(session, connection) || !session.ready) return;
5012
+ if (!fits()) {
5013
+ this.addDropped(session, 1);
5014
+ this.report(session, "EVENTS_DROPPED");
5015
+ return;
5016
+ }
5017
+ session.queue.push(wire);
5018
+ if (session.queue.length === MAX_BATCH_EVENTS) this.flush(session, connection);
5019
+ else this.scheduleFlush(session, connection);
5020
+ }
5021
+ flush(session, connection) {
5022
+ if (!this.current(session, connection) || !session.ready || session.queue.length === 0 && session.dropped === 0) return;
5023
+ if (!this.connected(connection)) {
5024
+ this.fail(session, connection, "CONNECTION_LOST");
5025
+ return;
5026
+ }
5027
+ if (connection.pending.size > 0) return;
5028
+ clearTimeout(session.flushTimer);
5029
+ session.flushTimer = void 0;
5030
+ const batch = {
5031
+ events: session.queue,
5032
+ dropped: session.dropped
5033
+ };
5034
+ session.queue = [];
5035
+ session.dropped = 0;
5036
+ let finished = false;
5037
+ const pending = {
5038
+ timer: setTimeout(() => pending.finish("PERSISTENCE_UNKNOWN"), TIMEOUT_MS),
5039
+ finish: (code, knownRollback = false, flushNext = true) => {
5040
+ if (finished) return;
5041
+ finished = true;
5042
+ clearTimeout(pending.timer);
5043
+ connection.pending.delete(pending);
5044
+ if (knownRollback) this.addDropped(session, batch.events.length + batch.dropped, false);
5045
+ if (code && this.current(session, connection)) this.report(session, code);
5046
+ if (flushNext && this.current(session, connection) && session.ready && (session.queue.length > 0 || !knownRollback && session.dropped > 0)) this.flush(session, connection);
5047
+ }
5048
+ };
5049
+ connection.pending.add(pending);
5050
+ try {
5051
+ connection.channel.push("events", batch, TIMEOUT_MS).receive("ok", (payload) => {
5052
+ pending.finish(!isBatchAcknowledgement(payload, batch.events.length) ? "PERSISTENCE_UNKNOWN" : payload.rejected > 0 ? "EVENTS_REJECTED" : void 0);
5053
+ }).receive("error", (payload) => {
5054
+ if (!this.current(session, connection)) return;
5055
+ const reason = isObject(payload) ? payload.reason : void 0;
5056
+ if (reason === "unauthorized" || reason === "trajectory_mismatch") {
5057
+ pending.finish(void 0, true, false);
5058
+ this.fail(session, connection, reason.toUpperCase(), false);
5059
+ return;
5060
+ }
5061
+ const rollback = typeof reason === "string" && [
5062
+ "invalid_batch",
5063
+ "batch_too_large",
5064
+ "batch_too_many_events",
5065
+ "trajectory_over_share",
5066
+ "trajectory_outbox_full",
5067
+ "unsupported_event"
5068
+ ].includes(reason);
5069
+ pending.finish(rollback ? reason.toUpperCase() : "PERSISTENCE_UNKNOWN", rollback);
5070
+ }).receive("timeout", () => pending.finish("PERSISTENCE_UNKNOWN"));
5071
+ } catch {
5072
+ pending.finish("PERSISTENCE_UNKNOWN");
5073
+ }
5074
+ }
5075
+ };
5076
+
5077
+ //#endregion
5078
+ //#region src/core/learning-bridge.ts
5079
+ const COPILOTKIT_OWN_URLS = ["https://telemetry.copilotkit.ai/", "https://cdn.copilotkit.ai/"];
5080
+ /**
5081
+ * Wires `@copilotkit/learning` into Core. Default capture uses Runtime and Phoenix;
5082
+ * explicit legacy sinks also use agent events and open-Thread attribution.
5083
+ */
5084
+ var LearningBridge = class {
5085
+ collector = null;
5086
+ agents = /* @__PURE__ */ new Map();
5087
+ openThreads = /* @__PURE__ */ new Set();
5088
+ toolCalls = /* @__PURE__ */ new Map();
5089
+ executingTools = /* @__PURE__ */ new Map();
5090
+ lastRunIds = /* @__PURE__ */ new WeakMap();
5091
+ lastLinkedThreadId = null;
5092
+ coreSubscription = null;
5093
+ seq = 0;
5094
+ activeConfig;
5095
+ connection;
5096
+ constructor(core, config, onChange = () => {}) {
5097
+ this.core = core;
5098
+ this.config = config;
5099
+ this.connection = new TrajectoryConnection(core, onChange);
5100
+ this.setConfig(config);
5101
+ }
5102
+ setConfig(config) {
5103
+ this.config = config;
5104
+ if (config === void 0) this.stop();
5105
+ }
5106
+ get isConfigured() {
5107
+ return this.config !== void 0;
5108
+ }
5109
+ get isLegacyCaptureActive() {
5110
+ return this.collector !== null && this.collector.trajectoryId !== null;
5111
+ }
5112
+ startTracking() {
5113
+ if (this.coreSubscription !== null || !this.isLegacyCaptureActive) return;
5114
+ this.coreSubscription = this.core.subscribe({
5115
+ onAgentsChanged: ({ agents }) => {
5116
+ for (const agent of Object.values(agents)) this.track(agent);
5117
+ },
5118
+ onAgentRunStarted: ({ agent }) => this.track(agent),
5119
+ onToolExecutionStart: ({ toolCallId, agentId, toolName }) => {
5120
+ if (!this.isLegacyCaptureActive) return;
5121
+ this.executingTools.set(toolCallId, Date.now());
5122
+ this.emitToolCall("executing", toolCallId, agentId, toolName, {});
5123
+ },
5124
+ onToolExecutionEnd: ({ toolCallId, agentId, toolName, error }) => {
5125
+ if (!this.isLegacyCaptureActive) return;
5126
+ const startedAt = this.executingTools.get(toolCallId);
5127
+ this.executingTools.delete(toolCallId);
5128
+ this.emitToolCall("completed", toolCallId, agentId, toolName, {
5129
+ durationMs: startedAt === void 0 ? void 0 : Date.now() - startedAt,
5130
+ outcome: error === void 0 ? "ok" : "error"
5131
+ });
5132
+ this.toolCalls.delete(toolCallId);
5133
+ }
5134
+ });
5135
+ for (const agent of Object.values(this.core.agents)) this.track(agent);
5136
+ }
5137
+ get trajectoryId() {
5138
+ return this.connection.trajectoryId ?? this.collector?.trajectoryId ?? null;
5139
+ }
5140
+ start(options = {}) {
5141
+ const config = this.config;
5142
+ if (config === void 0) {
5143
+ console.warn("[CopilotKit] startTrajectory() needs the `learning` option on CopilotKitCore (or the CopilotKitProvider `learning` prop).");
5144
+ return Promise.resolve({
5145
+ status: "error",
5146
+ code: "LEARNING_DISABLED"
5147
+ });
5148
+ }
5149
+ if (config.sink === void 0) {
5150
+ 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.");
5151
+ this.stopLegacy();
5152
+ return this.connection.start(options.trajectoryId, config);
5153
+ }
5154
+ this.connection.stop();
5155
+ const trajectoryId = options.trajectoryId ?? this.collector?.trajectoryId ?? crypto.randomUUID();
5156
+ if (this.collector === null) {
5157
+ this.activeConfig = config;
5158
+ const runtimeUrl = this.core.runtimeUrl;
5159
+ this.collector = (0, _copilotkit_learning.createCollector)({
5160
+ ...config,
5161
+ beforeSend: (event) => {
5162
+ const sequenced = {
5163
+ ...event,
5164
+ value: {
5165
+ ...event.value,
5166
+ seq: ++this.seq
5167
+ }
5168
+ };
5169
+ return config.beforeSend === void 0 ? sequenced : config.beforeSend(sequenced);
5170
+ },
5171
+ ignoreUrls: [
5172
+ ...config.ignoreUrls ?? [],
5173
+ ...COPILOTKIT_OWN_URLS,
5174
+ ...runtimeUrl === void 0 ? [] : [runtimeUrl]
5175
+ ],
5176
+ enrich: (target) => this.enrich(target)
5177
+ });
5178
+ }
5179
+ const wasActive = this.collector.trajectoryId !== null;
5180
+ this.collector.start({
5181
+ ...options,
5182
+ trajectoryId
5183
+ });
5184
+ if (wasActive) return Promise.resolve(this.collector.trajectoryId === trajectoryId ? {
5185
+ status: "started",
5186
+ trajectoryId
5187
+ } : {
5188
+ status: "error",
5189
+ code: "TRAJECTORY_ACTIVE"
5190
+ });
5191
+ this.startTracking();
5192
+ this.lastLinkedThreadId = null;
5193
+ for (const threadId of this.distinctOpenThreadIds()) this.emitThreadLinked(threadId, "start");
5194
+ return Promise.resolve({
5195
+ status: "started",
5196
+ trajectoryId
5197
+ });
5198
+ }
5199
+ stop() {
5200
+ this.connection.stop();
5201
+ this.stopLegacy();
5202
+ }
5203
+ stopLegacy() {
5204
+ this.collector?.stop();
5205
+ this.collector = null;
5206
+ this.activeConfig = void 0;
5207
+ this.coreSubscription?.unsubscribe();
5208
+ this.coreSubscription = null;
5209
+ for (const subscription of this.agents.values()) subscription.unsubscribe();
5210
+ this.agents.clear();
5211
+ this.toolCalls.clear();
5212
+ this.executingTools.clear();
5213
+ }
5214
+ /** A developer event. Only an explicit legacy sink adds open-Thread context. */
5215
+ emit(name, value) {
5216
+ this.connection.emit(name, value);
5217
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) this.collector?.emit(name, {
5218
+ ...this.openThreadContext(),
5219
+ ...value
5220
+ });
5221
+ }
5222
+ registerOpenThread(params) {
5223
+ const entry = { ...params };
5224
+ const isNew = !this.distinctOpenThreadIds().includes(entry.threadId);
5225
+ this.openThreads.add(entry);
5226
+ if (isNew) this.emitThreadLinked(entry.threadId, "open");
5227
+ return {
5228
+ update: (threadId) => {
5229
+ if (threadId === entry.threadId) return;
5230
+ entry.threadId = threadId;
5231
+ if (![...this.openThreads].filter((other) => other !== entry).some((other) => other.threadId === threadId)) this.emitThreadLinked(threadId, "switch");
5232
+ },
5233
+ unregister: () => {
5234
+ this.openThreads.delete(entry);
5235
+ }
5236
+ };
5237
+ }
5238
+ distinctOpenThreadIds() {
5239
+ return [...new Set([...this.openThreads].map((open) => open.threadId))];
5240
+ }
5241
+ emitThreadLinked(threadId, reason) {
5242
+ if (!(this.collector !== null && this.collector.trajectoryId !== null) || threadId === this.lastLinkedThreadId) return;
5243
+ this.lastLinkedThreadId = threadId;
5244
+ const owner = [...this.openThreads].find((open) => open.threadId === threadId);
5245
+ const agent = this.findAgentForThread(threadId);
5246
+ this.collector?.ɵemit("thread.linked", {
5247
+ threadId,
5248
+ agentId: owner?.agentId ?? agent?.agentId ?? null,
5249
+ hasMessages: (agent?.messages.length ?? 0) > 0,
5250
+ reason
5251
+ });
5252
+ }
5253
+ findAgentForThread(threadId) {
5254
+ return [...this.agents.keys()].find((agent) => agent.threadId === threadId);
5255
+ }
5256
+ track(agent) {
5257
+ const agentId = agent.agentId;
5258
+ if (!this.isLegacyCaptureActive || this.agents.has(agent) || agentId === void 0) return;
5259
+ this.agents.set(agent, agent.subscribe(this.agentSubscriber(agent, agentId)));
5260
+ }
5261
+ agentSubscriber(agent, agentId) {
5262
+ const runIdFor = () => this.lastRunIds.get(agent);
5263
+ return {
5264
+ onRunStartedEvent: ({ event }) => {
5265
+ this.lastRunIds.set(agent, event.runId);
5266
+ this.emitRun("started", agent, event.threadId, event.runId);
5267
+ },
5268
+ onRunFinishedEvent: ({ event }) => {
5269
+ this.emitRun("finished", agent, event.threadId, event.runId);
5270
+ },
5271
+ onRunErrorEvent: () => {
5272
+ this.emitRun("error", agent, agent.threadId, runIdFor() ?? null);
5273
+ },
5274
+ onToolCallStartEvent: ({ event }) => {
5275
+ if (!this.isLegacyCaptureActive) return;
5276
+ const origin = {
5277
+ agentId,
5278
+ threadId: agent.threadId,
5279
+ messageId: event.parentMessageId,
5280
+ runId: runIdFor()
5281
+ };
5282
+ this.toolCalls.set(event.toolCallId, origin);
5283
+ this.emitToolCall("started", event.toolCallId, origin.agentId, event.toolCallName, {});
5284
+ },
5285
+ onToolCallResultEvent: ({ event }) => {
5286
+ if (!this.executingTools.has(event.toolCallId)) this.toolCalls.delete(event.toolCallId);
5287
+ },
5288
+ onNewMessage: ({ message }) => this.emitMessage(agent, message)
5289
+ };
5290
+ }
5291
+ emitRun(phase, agent, threadId, runId) {
5292
+ this.collector?.ɵemit("agent.run", {
5293
+ phase,
5294
+ runId,
5295
+ threadId,
5296
+ agentId: agent.agentId
5297
+ });
5298
+ }
5299
+ emitMessage(agent, message) {
5300
+ const content = typeof message.content === "string" ? message.content : "";
5301
+ const includeText = this.activeConfig?.capture?.agentText !== false && typeof message.content === "string";
5302
+ this.collector?.ɵemit("agent.message", {
5303
+ messageId: message.id,
5304
+ runId: this.runIdForMessage(agent, message.id),
5305
+ threadId: agent.threadId,
5306
+ agentId: agent.agentId,
5307
+ role: message.role,
5308
+ textLength: content.length,
5309
+ ...includeText ? { text: content } : {}
5310
+ });
5311
+ }
5312
+ emitToolCall(phase, toolCallId, agentId, toolName, extra) {
5313
+ const origin = this.toolCalls.get(toolCallId);
5314
+ this.collector?.ɵemit("tool.call", {
5315
+ phase,
5316
+ toolCallId,
5317
+ toolName,
5318
+ agentId,
5319
+ threadId: origin?.threadId ?? null,
5320
+ messageId: origin?.messageId,
5321
+ runId: origin?.runId,
5322
+ ...extra
5323
+ });
5324
+ }
5325
+ runIdForMessage(agent, messageId) {
5326
+ const agentId = agent.agentId;
5327
+ if (agentId === void 0) return null;
5328
+ return this.core.getRunIdForMessage(agentId, agent.threadId, messageId) ?? this.lastRunIds.get(agent) ?? null;
5329
+ }
5330
+ findMessage(messageId) {
5331
+ for (const agent of this.agents.keys()) {
5332
+ const message = agent.messages.find((candidate) => candidate.id === messageId);
5333
+ if (message !== void 0) return {
5334
+ agent,
5335
+ message
5336
+ };
5337
+ }
5338
+ return null;
5339
+ }
5340
+ toolStatus(agent, toolCallId) {
5341
+ if (this.executingTools.has(toolCallId)) return "executing";
5342
+ return agent.messages.some((message) => message.role === "tool" && message.toolCallId === toolCallId) ? "complete" : "inProgress";
5343
+ }
5344
+ /** Adds agent context to a click: exact message first, then the single open Thread. */
5345
+ enrich(target) {
5346
+ const messageId = target.closest("[data-message-id]")?.getAttribute("data-message-id") ?? null;
5347
+ const toolCallId = target.closest("[data-tool-call-id]")?.getAttribute("data-tool-call-id") ?? null;
5348
+ const found = messageId === null ? null : this.findMessage(messageId);
5349
+ if (found !== null) {
5350
+ const { agent, message } = found;
5351
+ const toolCall = toolCallId !== null && message.role === "assistant" ? message.toolCalls?.find((call) => call.id === toolCallId) : void 0;
5352
+ return {
5353
+ threadId: agent.threadId,
5354
+ agentId: agent.agentId,
5355
+ messageId: message.id,
5356
+ runId: this.runIdForMessage(agent, message.id),
5357
+ ...toolCall === void 0 ? {} : {
5358
+ toolCallId: toolCall.id,
5359
+ toolName: toolCall.function.name,
5360
+ toolStatus: this.toolStatus(agent, toolCall.id)
5361
+ }
5362
+ };
5363
+ }
5364
+ return this.openThreadContext();
5365
+ }
5366
+ /** The single open Thread, or `null` with the candidates when several are open. */
5367
+ openThreadContext() {
5368
+ const openIds = this.distinctOpenThreadIds();
5369
+ if (openIds.length > 1) return {
5370
+ threadId: null,
5371
+ threadAmbiguity: {
5372
+ reason: "multiple-open",
5373
+ candidates: openIds
5374
+ }
5375
+ };
5376
+ return { threadId: openIds[0] ?? null };
5377
+ }
5378
+ };
5379
+
4515
5380
  //#endregion
4516
5381
  //#region src/utils/micro-redux.ts
4517
5382
  const INTERNAL_ACTION_TYPES = {
@@ -5664,6 +6529,9 @@ var CopilotKitCore = class {
5664
6529
  runHandler;
5665
6530
  stateManager;
5666
6531
  threadStoreRegistry;
6532
+ learningBridge;
6533
+ notifiedTrajectoryId = null;
6534
+ learningConfiguredListeners = /* @__PURE__ */ new Set();
5667
6535
  /**
5668
6536
  * The single core-owned memory store, created lazily on first
5669
6537
  * `getMemoryStore()` and kept user-scoped for the lifetime of the core.
@@ -5678,7 +6546,7 @@ var CopilotKitCore = class {
5678
6546
  * store that was registered prior to that initial notification.
5679
6547
  */
5680
6548
  previousAgentIds = /* @__PURE__ */ new Set();
5681
- constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, messageFilter, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug }) {
6549
+ constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, messageFilter, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug, learning }) {
5682
6550
  this._headers = normalizeHeaders(headers);
5683
6551
  this._credentials = credentials;
5684
6552
  this._messageFilter = messageFilter;
@@ -5694,6 +6562,7 @@ var CopilotKitCore = class {
5694
6562
  this.runHandler.initialize(tools);
5695
6563
  this.suggestionEngine.initialize(suggestionsConfig);
5696
6564
  this.stateManager.initialize();
6565
+ this.learningBridge = new LearningBridge(this, learning, () => this.notifyTrajectoryChanged());
5697
6566
  this.agentRegistry.setRuntimeTransport(runtimeTransport);
5698
6567
  this.agentRegistry.setRuntimeUrl(runtimeUrl, { deferConnection: deferInitialConnection });
5699
6568
  this.previousAgentIds = new Set(Object.keys(agents__unsafe_dev_only));
@@ -5845,6 +6714,10 @@ var CopilotKitCore = class {
5845
6714
  get runtimeConnectionStatus() {
5846
6715
  return this.agentRegistry.runtimeConnectionStatus;
5847
6716
  }
6717
+ /** @internal The verbatim single-endpoint URL, including any trailing slash. */
6718
+ get ɵruntimeEndpointUrl() {
6719
+ return this.agentRegistry.runtimeEndpointUrl;
6720
+ }
5848
6721
  get ɵruntimeFetch() {
5849
6722
  return this.agentRegistry.createRuntimeFetch();
5850
6723
  }
@@ -6004,6 +6877,83 @@ var CopilotKitCore = class {
6004
6877
  getAgent(id) {
6005
6878
  return this.agentRegistry.getAgent(id);
6006
6879
  }
6880
+ /** Updates settings for the next capture. Removing the config stops capture. */
6881
+ setLearningConfig(config) {
6882
+ const wasConfigured = this.ɵlearningConfigured;
6883
+ this.learningBridge.setConfig(config);
6884
+ this.notifyTrajectoryChanged();
6885
+ if (this.ɵlearningConfigured === wasConfigured) return;
6886
+ for (const listener of this.learningConfiguredListeners) try {
6887
+ listener();
6888
+ } catch (error) {
6889
+ console.error("Learning configured listener error:", error);
6890
+ }
6891
+ }
6892
+ /**
6893
+ * @internal Whether a `learning` config is set. Unlike {@link trajectoryId},
6894
+ * it does not change with capture start, connection failures or reconnects.
6895
+ */
6896
+ get ɵlearningConfigured() {
6897
+ return this.learningBridge.isConfigured;
6898
+ }
6899
+ /** @internal Calls `listener` when {@link ɵlearningConfigured} changes. */
6900
+ ɵsubscribeToLearningConfigured(listener) {
6901
+ this.learningConfiguredListeners.add(listener);
6902
+ return () => {
6903
+ this.learningConfiguredListeners.delete(listener);
6904
+ };
6905
+ }
6906
+ /** The active capture's Trajectory ID, or null while capture is stopped. */
6907
+ get trajectoryId() {
6908
+ return this.learningBridge.trajectoryId;
6909
+ }
6910
+ notifyTrajectoryChanged() {
6911
+ const trajectoryId = this.trajectoryId;
6912
+ if (trajectoryId === this.notifiedTrajectoryId) return;
6913
+ this.notifiedTrajectoryId = trajectoryId;
6914
+ this.notifySubscribers((subscriber) => subscriber.onTrajectoryChanged?.({
6915
+ copilotkit: this,
6916
+ trajectoryId
6917
+ }), "Subscriber onTrajectoryChanged error:");
6918
+ }
6919
+ /**
6920
+ * Starts one Trajectory after Runtime authorization and the Gateway join.
6921
+ * Omit the ID to create one. Repeated starts await the active connection;
6922
+ * a different ID stops the previous capture. Explicit sinks retain the
6923
+ * prototype's synchronous capture and require a stop before changing IDs.
6924
+ *
6925
+ * @example copilotkit.startTrajectory({ trajectoryId: crypto.randomUUID() })
6926
+ */
6927
+ startTrajectory(options = {}) {
6928
+ const result = this.learningBridge.start(options);
6929
+ this.notifyTrajectoryChanged();
6930
+ return result;
6931
+ }
6932
+ /** Cancels pending work and stops capture, attempting one final batch without waiting for its ACK. */
6933
+ stopTrajectory() {
6934
+ this.learningBridge.stop();
6935
+ this.notifyTrajectoryChanged();
6936
+ }
6937
+ /**
6938
+ * Records an outcome that clicks cannot show, such as a saved report or an
6939
+ * approved deal. The wire adds `value.seq`; arrays, primitives, and objects
6940
+ * already containing `seq` are preserved under `value.data`. Explicit legacy
6941
+ * sinks retain object-only events with open-Thread enrichment.
6942
+ * No-op while no Trajectory runs. Events emitted during connection recovery
6943
+ * count as dropped. Built-in names such as `click` are rejected.
6944
+ *
6945
+ * @example copilotkit.emitTrajectoryEvent("deal.approved", { dealId: "deal-1" })
6946
+ */
6947
+ emitTrajectoryEvent(name, value = {}) {
6948
+ this.learningBridge.emit(name, value);
6949
+ }
6950
+ /**
6951
+ * Tells Core that a view shows this Thread, so captured interactions can link to it.
6952
+ * Framework bindings call this; call it yourself only without a CopilotKit provider.
6953
+ */
6954
+ registerOpenThread(params) {
6955
+ return this.learningBridge.registerOpenThread(params);
6956
+ }
6007
6957
  /**
6008
6958
  * Re-apply the current headers to a single agent (delegated to
6009
6959
  * AgentRegistry). Core headers are merged on top of the agent's own