@copilotkit/core 1.76.0 → 1.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,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
  /**
@@ -1907,6 +1908,10 @@ var AgentRegistry = class {
1907
1908
  this.applyMessageFilterToAgent(agent);
1908
1909
  });
1909
1910
  }
1911
+ /** Preserve the exact configured URL when posting to a single endpoint. */
1912
+ get runtimeEndpointUrl() {
1913
+ return this._runtimeEndpointUrl;
1914
+ }
1910
1915
  createRuntimeFetch() {
1911
1916
  if (!this.runtimeFetch) this.runtimeFetch = (async (input, init) => {
1912
1917
  const meta = () => runtimeRequestMeta(init);
@@ -2931,6 +2936,68 @@ function normalizeToolResultContent(content) {
2931
2936
  function isForwardedToClientPlaceholder(content) {
2932
2937
  return normalizeToolResultContent(content) === FORWARDED_TO_CLIENT;
2933
2938
  }
2939
+ const MEDIA_PART_TYPES = new Set([
2940
+ "image",
2941
+ "audio",
2942
+ "video",
2943
+ "document"
2944
+ ]);
2945
+ function isOptionalString(value) {
2946
+ return value === void 0 || typeof value === "string";
2947
+ }
2948
+ function isPartSource(value) {
2949
+ if (!value || typeof value !== "object") return false;
2950
+ const source = value;
2951
+ if (typeof source.value !== "string") return false;
2952
+ switch (source.type) {
2953
+ case "data": return typeof source.mimeType === "string";
2954
+ case "url": return isOptionalString(source.mimeType);
2955
+ case "file": return isOptionalString(source.provider) && isOptionalString(source.mimeType);
2956
+ default: return false;
2957
+ }
2958
+ }
2959
+ /**
2960
+ * Mirrors `ContentPartSchema` from `@ag-ui/core/schemas`. The runtime parses
2961
+ * the request with that schema, and one invalid part there fails the whole run.
2962
+ * Core does not import the schema because the UMD build has no global for it.
2963
+ * A test checks this function against the schema.
2964
+ */
2965
+ function isContentPart(value) {
2966
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2967
+ const part = value;
2968
+ if (!isOptionalString(part.id) || part.metadata === null) return false;
2969
+ if (part.type === "text") return typeof part.text === "string";
2970
+ return typeof part.type === "string" && MEDIA_PART_TYPES.has(part.type) && isPartSource(part.source);
2971
+ }
2972
+ /**
2973
+ * Converts what a frontend tool handler returned into tool message content.
2974
+ * A non-empty array of AG-UI content parts passes through unchanged, so an
2975
+ * image reaches the model as media. Everything else becomes a string, as before.
2976
+ *
2977
+ * `text` is the string form, for surfaces typed as `result: string`
2978
+ * (`runTool`, `onToolExecutionEnd`). For content parts it is the JSON text they
2979
+ * received before. It is computed here, so a part that cannot be serialized
2980
+ * throws inside the caller's handler error boundary.
2981
+ */
2982
+ function toToolResultContent(result) {
2983
+ if (result === void 0 || result === null) return {
2984
+ content: "",
2985
+ text: ""
2986
+ };
2987
+ if (typeof result === "string") return {
2988
+ content: result,
2989
+ text: result
2990
+ };
2991
+ const text = JSON.stringify(result);
2992
+ if (Array.isArray(result) && result.length > 0 && result.every(isContentPart)) return {
2993
+ content: result,
2994
+ text
2995
+ };
2996
+ return {
2997
+ content: text,
2998
+ text
2999
+ };
3000
+ }
2934
3001
 
2935
3002
  //#endregion
2936
3003
  //#region src/core/tool-schema.ts
@@ -3644,6 +3711,7 @@ var RunHandler = class {
3644
3711
  */
3645
3712
  async executeToolHandler({ tool, toolCall, agent, agentId, handlerArgs, toolType, messageId, signal = this._runAbortController?.signal, discardOnAbort = false }) {
3646
3713
  let toolCallResult = "";
3714
+ let toolCallText = "";
3647
3715
  let errorMessage;
3648
3716
  let isArgumentError = false;
3649
3717
  let parsedArgs;
@@ -3679,9 +3747,7 @@ var RunHandler = class {
3679
3747
  agent,
3680
3748
  signal
3681
3749
  });
3682
- if (result === void 0 || result === null) toolCallResult = "";
3683
- else if (typeof result === "string") toolCallResult = result;
3684
- else toolCallResult = JSON.stringify(result);
3750
+ ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3685
3751
  } catch (error) {
3686
3752
  const handlerError = error instanceof Error ? error : new Error(String(error));
3687
3753
  errorMessage = handlerError.message;
@@ -3698,17 +3764,18 @@ var RunHandler = class {
3698
3764
  }
3699
3765
  });
3700
3766
  }
3701
- if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
3767
+ if (errorMessage) toolCallResult = toolCallText = `Error: ${errorMessage}`;
3702
3768
  await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
3703
3769
  copilotkit: this.core,
3704
3770
  toolCallId: toolCall.id,
3705
3771
  agentId,
3706
3772
  toolName: toolCall.function.name,
3707
- result: errorMessage ? "" : toolCallResult,
3773
+ result: errorMessage ? "" : toolCallText,
3708
3774
  error: errorMessage
3709
3775
  }), "Subscriber onToolExecutionEnd error:");
3710
3776
  return {
3711
- result: toolCallResult,
3777
+ content: toolCallResult,
3778
+ result: toolCallText,
3712
3779
  error: errorMessage,
3713
3780
  isArgumentError
3714
3781
  };
@@ -3720,6 +3787,7 @@ var RunHandler = class {
3720
3787
  const threadId = agent.threadId;
3721
3788
  if (tool?.agentId && tool.agentId !== agent.agentId) return false;
3722
3789
  let handlerResult = {
3790
+ content: "",
3723
3791
  result: "",
3724
3792
  error: void 0,
3725
3793
  isArgumentError: false
@@ -3744,7 +3812,7 @@ var RunHandler = class {
3744
3812
  id: (0, _copilotkit_shared.randomUUID)(),
3745
3813
  role: "tool",
3746
3814
  toolCallId: toolCall.id,
3747
- content: handlerResult.result
3815
+ content: handlerResult.content
3748
3816
  };
3749
3817
  agent.messages.splice(insertAt, 0, toolMessage);
3750
3818
  if (!handlerResult.error && tool?.followUp !== false) return true;
@@ -3761,6 +3829,7 @@ var RunHandler = class {
3761
3829
  const threadId = agent.threadId;
3762
3830
  if (wildcardTool?.agentId && wildcardTool.agentId !== agent.agentId) return false;
3763
3831
  let toolCallResult = "";
3832
+ let toolCallText = "";
3764
3833
  let errorMessage;
3765
3834
  if (wildcardTool?.handler) {
3766
3835
  let parsedArgs;
@@ -3799,9 +3868,7 @@ var RunHandler = class {
3799
3868
  agent,
3800
3869
  signal
3801
3870
  });
3802
- if (result === void 0 || result === null) toolCallResult = "";
3803
- else if (typeof result === "string") toolCallResult = result;
3804
- else toolCallResult = JSON.stringify(result);
3871
+ ({content: toolCallResult, text: toolCallText} = toToolResultContent(result));
3805
3872
  } catch (error) {
3806
3873
  const handlerError = error instanceof Error ? error : new Error(String(error));
3807
3874
  errorMessage = handlerError.message;
@@ -3818,13 +3885,13 @@ var RunHandler = class {
3818
3885
  }
3819
3886
  });
3820
3887
  }
3821
- if (errorMessage) toolCallResult = `Error: ${errorMessage}`;
3888
+ if (errorMessage) toolCallResult = toolCallText = `Error: ${errorMessage}`;
3822
3889
  await this._internal.notifySubscribers((subscriber) => subscriber.onToolExecutionEnd?.({
3823
3890
  copilotkit: this.core,
3824
3891
  toolCallId: toolCall.id,
3825
3892
  agentId,
3826
3893
  toolName: toolCall.function.name,
3827
- result: errorMessage ? "" : toolCallResult,
3894
+ result: errorMessage ? "" : toolCallText,
3828
3895
  error: errorMessage
3829
3896
  }), "Subscriber onToolExecutionEnd error:");
3830
3897
  }
@@ -3895,6 +3962,7 @@ var RunHandler = class {
3895
3962
  };
3896
3963
  agent.messages.push(assistantMessage);
3897
3964
  let handlerResult = {
3965
+ content: "",
3898
3966
  result: "",
3899
3967
  error: void 0,
3900
3968
  isArgumentError: false
@@ -3911,7 +3979,7 @@ var RunHandler = class {
3911
3979
  id: (0, _copilotkit_shared.randomUUID)(),
3912
3980
  role: "tool",
3913
3981
  toolCallId,
3914
- content: handlerResult.result
3982
+ content: handlerResult.content
3915
3983
  };
3916
3984
  const assistantIndex = agent.messages.findIndex((m) => m.id === assistantMessage.id);
3917
3985
  if (assistantIndex !== -1) agent.messages.splice(assistantIndex + 1, 0, toolResultMessage);
@@ -4512,6 +4580,720 @@ var ThreadStoreRegistry = class {
4512
4580
  }
4513
4581
  };
4514
4582
 
4583
+ //#endregion
4584
+ //#region src/core/trajectory-connection.ts
4585
+ const TIMEOUT_MS = 1e4;
4586
+ const BATCH_INTERVAL_MS = 2e3;
4587
+ const MAX_BATCH_EVENTS = 50;
4588
+ const MAX_BATCH_BYTES = 64 * 1024;
4589
+ const MAX_EVENT_BYTES = 16 * 1024;
4590
+ const byteLength = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
4591
+ const reconnectDelay = (0, _copilotkit_shared.phoenixExponentialBackoff)(1e3, 1e4);
4592
+ function isObject(value) {
4593
+ return typeof value === "object" && value !== null;
4594
+ }
4595
+ function isNonNegativeInteger(value) {
4596
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
4597
+ }
4598
+ function isBatchAcknowledgement(value, eventCount) {
4599
+ return isObject(value) && isNonNegativeInteger(value.accepted) && isNonNegativeInteger(value.rejected) && value.accepted + value.rejected === eventCount && (value.highestSeq === null || isNonNegativeInteger(value.highestSeq)) && (value.accepted === 0 || value.highestSeq !== null);
4600
+ }
4601
+ function isGrant(value) {
4602
+ return isObject(value) && typeof value.joinToken === "string" && value.joinToken.length > 0 && isObject(value.realtime) && typeof value.realtime.clientUrl === "string" && value.realtime.clientUrl.length > 0 && typeof value.realtime.topic === "string" && value.realtime.topic.length > 0;
4603
+ }
4604
+ function errorCode(value, fallback) {
4605
+ return isObject(value) && typeof value.code === "string" && /^[A-Z][A-Z0-9_]{0,63}$/.test(value.code) ? value.code : fallback;
4606
+ }
4607
+ /** Owns bounded, connected-only capture batches. Failed batches are never resent. */
4608
+ var TrajectoryConnection = class {
4609
+ session;
4610
+ nextSeq = Math.floor(Date.now()) * 1e3;
4611
+ constructor(core, onChange) {
4612
+ this.core = core;
4613
+ this.onChange = onChange;
4614
+ }
4615
+ get trajectoryId() {
4616
+ return this.session?.ready ? this.session.trajectoryId : null;
4617
+ }
4618
+ start(trajectoryId, config) {
4619
+ if (this.session && (trajectoryId === void 0 || trajectoryId === this.session.trajectoryId)) return this.session.promise;
4620
+ this.stop();
4621
+ const id = trajectoryId ?? crypto.randomUUID();
4622
+ let resolveStart;
4623
+ const promise = new Promise((resolve) => {
4624
+ resolveStart = resolve;
4625
+ });
4626
+ const session = {
4627
+ trajectoryId: id,
4628
+ config,
4629
+ promise,
4630
+ resolve: resolveStart,
4631
+ retries: 0,
4632
+ queue: [],
4633
+ dropped: 0,
4634
+ ready: false,
4635
+ started: false,
4636
+ offline: typeof navigator !== "undefined" && navigator.onLine === false,
4637
+ onOffline: () => {
4638
+ if (this.session !== session) return;
4639
+ session.offline = true;
4640
+ if (session.connection) this.fail(session, session.connection, "CONNECTION_LOST");
4641
+ clearTimeout(session.retry);
4642
+ session.retry = void 0;
4643
+ },
4644
+ onOnline: () => {
4645
+ if (this.session !== session) return;
4646
+ session.offline = false;
4647
+ if (!session.connection) {
4648
+ clearTimeout(session.retry);
4649
+ session.retry = void 0;
4650
+ this.connect(session);
4651
+ }
4652
+ }
4653
+ };
4654
+ this.session = session;
4655
+ if (typeof window !== "undefined" && typeof window.addEventListener === "function") {
4656
+ window.addEventListener("offline", session.onOffline);
4657
+ window.addEventListener("online", session.onOnline);
4658
+ }
4659
+ if (session.offline) {
4660
+ this.report(session, "CONNECTION_LOST");
4661
+ this.end(session, {
4662
+ status: "error",
4663
+ code: "CONNECTION_LOST"
4664
+ });
4665
+ } else this.connect(session);
4666
+ return promise;
4667
+ }
4668
+ stop() {
4669
+ const session = this.session;
4670
+ if (!session) return;
4671
+ if (session.ready && session.connection) this.flush(session, session.connection);
4672
+ this.end(session, {
4673
+ status: "error",
4674
+ code: "CANCELLED"
4675
+ });
4676
+ }
4677
+ emit(name, value) {
4678
+ const session = this.session;
4679
+ if (session?.ready) session.collector?.emit(name, value);
4680
+ else if (session?.started) this.addDropped(session, 1, false);
4681
+ }
4682
+ report(session, code) {
4683
+ try {
4684
+ session.config.onError?.({
4685
+ code,
4686
+ message: `Trajectory capture: ${code}.`
4687
+ });
4688
+ } catch {}
4689
+ }
4690
+ current(session, connection) {
4691
+ return this.session === session && session.connection === connection;
4692
+ }
4693
+ end(session, result) {
4694
+ if (this.session !== session) return;
4695
+ this.session = void 0;
4696
+ clearTimeout(session.retry);
4697
+ session.collector?.stop();
4698
+ session.ready = false;
4699
+ if (session.queue.length > 0) this.report(session, "EVENTS_DROPPED");
4700
+ this.discardQueue(session);
4701
+ if (session.connection) {
4702
+ for (const pending of session.connection.pending) {
4703
+ pending.finish("PERSISTENCE_UNKNOWN");
4704
+ this.report(session, "PERSISTENCE_UNKNOWN");
4705
+ }
4706
+ this.cleanup(session, session.connection);
4707
+ }
4708
+ if (typeof window !== "undefined" && typeof window.removeEventListener === "function") {
4709
+ window.removeEventListener("offline", session.onOffline);
4710
+ window.removeEventListener("online", session.onOnline);
4711
+ }
4712
+ session.resolve(result);
4713
+ this.onChange();
4714
+ }
4715
+ cleanup(session, connection) {
4716
+ session.connection = void 0;
4717
+ clearTimeout(connection.timer);
4718
+ connection.abort.abort();
4719
+ for (const pending of connection.pending) clearTimeout(pending.timer);
4720
+ connection.pending.clear();
4721
+ const { socket, channel } = connection;
4722
+ try {
4723
+ if (channel) {
4724
+ for (const [event, ref] of connection.channelRefs) channel.off(event, ref);
4725
+ channel.leave();
4726
+ }
4727
+ } finally {
4728
+ if (socket) {
4729
+ socket.off(connection.socketRefs);
4730
+ socket.disconnect();
4731
+ }
4732
+ }
4733
+ }
4734
+ fail(session, connection, code, retryable = true) {
4735
+ if (!this.current(session, connection)) return;
4736
+ session.collector?.stop();
4737
+ session.collector = void 0;
4738
+ if (session.ready && session.started) session.promise = new Promise((resolve) => {
4739
+ session.resolve = resolve;
4740
+ });
4741
+ session.ready = false;
4742
+ this.discardQueue(session);
4743
+ for (const pending of connection.pending) pending.finish("PERSISTENCE_UNKNOWN");
4744
+ if (!this.current(session, connection)) return;
4745
+ this.cleanup(session, connection);
4746
+ this.onChange();
4747
+ this.report(session, code);
4748
+ if (this.session !== session) return;
4749
+ if (!session.started || !retryable) {
4750
+ this.end(session, {
4751
+ status: "error",
4752
+ code
4753
+ });
4754
+ return;
4755
+ }
4756
+ if (session.offline || this.session !== session) return;
4757
+ session.retry = setTimeout(() => {
4758
+ session.retry = void 0;
4759
+ if (this.session === session && !session.offline) this.connect(session);
4760
+ }, reconnectDelay(++session.retries));
4761
+ }
4762
+ async connect(session) {
4763
+ if (this.session !== session || session.offline || session.connection) return;
4764
+ const connection = {
4765
+ abort: new AbortController(),
4766
+ socketRefs: [],
4767
+ channelRefs: [],
4768
+ pending: /* @__PURE__ */ new Set()
4769
+ };
4770
+ session.connection = connection;
4771
+ connection.timer = setTimeout(() => this.fail(session, connection, "CONNECTION_TIMEOUT"), TIMEOUT_MS);
4772
+ try {
4773
+ const runtimeUrl = this.core.runtimeUrl;
4774
+ if (!runtimeUrl) {
4775
+ this.fail(session, connection, "RUNTIME_REQUIRED", false);
4776
+ return;
4777
+ }
4778
+ let transport = this.core.runtimeTransport;
4779
+ if (transport === "auto") {
4780
+ transport = await this.detectedTransport(connection.abort.signal);
4781
+ if (!this.current(session, connection)) return;
4782
+ }
4783
+ const rest = transport === "rest";
4784
+ const response = await fetch(rest ? `${runtimeUrl}/trajectory/${encodeURIComponent(session.trajectoryId)}/connect` : this.core.ɵruntimeEndpointUrl ?? runtimeUrl, {
4785
+ method: "POST",
4786
+ redirect: "error",
4787
+ signal: connection.abort.signal,
4788
+ headers: {
4789
+ ...this.core.headers,
4790
+ "Content-Type": "application/json"
4791
+ },
4792
+ credentials: this.core.credentials,
4793
+ body: JSON.stringify(rest ? {} : {
4794
+ method: "trajectory/connect",
4795
+ params: { trajectoryId: session.trajectoryId },
4796
+ body: {}
4797
+ })
4798
+ });
4799
+ if (!this.current(session, connection)) return;
4800
+ const payload = await response.json().catch(() => void 0);
4801
+ if (!this.current(session, connection)) return;
4802
+ if (!response.ok) {
4803
+ this.fail(session, connection, errorCode(payload, "CONNECTION_FAILED"), response.status >= 500 || response.status === 408 || response.status === 429);
4804
+ return;
4805
+ }
4806
+ if (!isGrant(payload)) {
4807
+ this.fail(session, connection, "INVALID_GRANT", false);
4808
+ return;
4809
+ }
4810
+ const socket = new phoenix.Socket(payload.realtime.clientUrl, {
4811
+ params: { join_token: payload.joinToken },
4812
+ timeout: TIMEOUT_MS
4813
+ });
4814
+ connection.socket = socket;
4815
+ const lost = () => this.fail(session, connection, "CONNECTION_LOST");
4816
+ connection.socketRefs.push(socket.onError(lost), socket.onClose(lost));
4817
+ const channel = socket.channel(payload.realtime.topic, {});
4818
+ connection.channel = channel;
4819
+ connection.channelRefs.push(["phx_error", channel.onError(lost)], ["phx_close", channel.onClose(lost)]);
4820
+ socket.connect();
4821
+ if (!this.current(session, connection)) return;
4822
+ channel.join(TIMEOUT_MS).receive("ok", () => {
4823
+ if (!this.current(session, connection)) return;
4824
+ try {
4825
+ clearTimeout(connection.timer);
4826
+ session.ready = true;
4827
+ session.retries = 0;
4828
+ session.collector = (0, _copilotkit_learning.createTrajectoryCollector)({
4829
+ ...session.config,
4830
+ ignoreUrls: [
4831
+ ...session.config.ignoreUrls ?? [],
4832
+ ...this.core.runtimeUrl ? [this.core.runtimeUrl] : [],
4833
+ ...this.core.ɵruntimeEndpointUrl ? [this.core.ɵruntimeEndpointUrl] : [],
4834
+ "https://telemetry.copilotkit.ai/",
4835
+ "https://cdn.copilotkit.ai/"
4836
+ ],
4837
+ onError: (error) => {
4838
+ if (error.code === "CAPTURE_FAILED") this.fail(session, connection, error.code, false);
4839
+ else {
4840
+ if (error.code === "INVALID_EVENT" || error.code === "EVENT_TOO_LARGE") this.addDropped(session, 1);
4841
+ session.config.onError?.(error);
4842
+ }
4843
+ },
4844
+ send: (event) => this.send(session, connection, event)
4845
+ });
4846
+ session.collector.start();
4847
+ if (!this.current(session, connection)) return;
4848
+ if (session.dropped > 0) this.scheduleFlush(session, connection);
4849
+ session.started = true;
4850
+ session.resolve({
4851
+ status: "started",
4852
+ trajectoryId: session.trajectoryId
4853
+ });
4854
+ this.onChange();
4855
+ } catch {
4856
+ this.fail(session, connection, "CAPTURE_FAILED", false);
4857
+ }
4858
+ }).receive("error", (joinError) => this.fail(session, connection, errorCode(joinError, "JOIN_FAILED"))).receive("timeout", () => this.fail(session, connection, "CONNECTION_TIMEOUT"));
4859
+ } catch {
4860
+ this.fail(session, connection, "CONNECTION_FAILED");
4861
+ }
4862
+ }
4863
+ /** Resolves once auto-detection picks a transport, or with "auto" when the attempt is aborted. */
4864
+ detectedTransport(signal) {
4865
+ return new Promise((resolve) => {
4866
+ const finish = () => {
4867
+ subscription.unsubscribe();
4868
+ signal.removeEventListener("abort", finish);
4869
+ resolve(this.core.runtimeTransport);
4870
+ };
4871
+ const subscription = this.core.subscribe({ onRuntimeConnectionStatusChanged: () => {
4872
+ if (this.core.runtimeTransport !== "auto") finish();
4873
+ } });
4874
+ signal.addEventListener("abort", finish, { once: true });
4875
+ });
4876
+ }
4877
+ addDropped(session, count, schedule = true) {
4878
+ session.dropped = Math.min(Number.MAX_SAFE_INTEGER, session.dropped + count);
4879
+ if (schedule && count > 0 && session.ready && session.connection) this.scheduleFlush(session, session.connection);
4880
+ }
4881
+ scheduleFlush(session, connection) {
4882
+ if (session.flushTimer !== void 0 || !this.current(session, connection)) return;
4883
+ session.flushTimer = setTimeout(() => {
4884
+ session.flushTimer = void 0;
4885
+ this.flush(session, connection);
4886
+ }, BATCH_INTERVAL_MS);
4887
+ }
4888
+ discardQueue(session) {
4889
+ clearTimeout(session.flushTimer);
4890
+ session.flushTimer = void 0;
4891
+ this.addDropped(session, session.queue.length, false);
4892
+ session.queue = [];
4893
+ }
4894
+ connected(connection) {
4895
+ return connection.socket?.isConnected() === true && connection.channel?.state === "joined";
4896
+ }
4897
+ send(session, connection, event) {
4898
+ if (!this.current(session, connection) || !session.ready) return;
4899
+ if (!this.connected(connection)) {
4900
+ this.addDropped(session, 1);
4901
+ this.fail(session, connection, "CONNECTION_LOST");
4902
+ return;
4903
+ }
4904
+ if (!Number.isSafeInteger(this.nextSeq)) {
4905
+ this.addDropped(session, 1);
4906
+ this.report(session, "SEQUENCE_EXHAUSTED");
4907
+ return;
4908
+ }
4909
+ const original = event.value;
4910
+ const fields = isObject(original) && !Array.isArray(original) && !Object.hasOwn(original, "seq") ? original : { data: original };
4911
+ const wire = {
4912
+ ...event,
4913
+ value: {
4914
+ ...fields,
4915
+ seq: this.nextSeq++
4916
+ }
4917
+ };
4918
+ if (byteLength(wire) > MAX_EVENT_BYTES) {
4919
+ this.addDropped(session, 1);
4920
+ this.report(session, "EVENT_TOO_LARGE");
4921
+ return;
4922
+ }
4923
+ const fits = () => session.queue.length < MAX_BATCH_EVENTS && byteLength({
4924
+ events: [...session.queue, wire],
4925
+ dropped: Number.MAX_SAFE_INTEGER
4926
+ }) <= MAX_BATCH_BYTES;
4927
+ if (!fits()) this.flush(session, connection);
4928
+ if (!this.current(session, connection) || !session.ready) return;
4929
+ if (!fits()) {
4930
+ this.addDropped(session, 1);
4931
+ this.report(session, "EVENTS_DROPPED");
4932
+ return;
4933
+ }
4934
+ session.queue.push(wire);
4935
+ if (session.queue.length === MAX_BATCH_EVENTS) this.flush(session, connection);
4936
+ else this.scheduleFlush(session, connection);
4937
+ }
4938
+ flush(session, connection) {
4939
+ if (!this.current(session, connection) || !session.ready || session.queue.length === 0 && session.dropped === 0) return;
4940
+ if (!this.connected(connection)) {
4941
+ this.fail(session, connection, "CONNECTION_LOST");
4942
+ return;
4943
+ }
4944
+ if (connection.pending.size > 0) return;
4945
+ clearTimeout(session.flushTimer);
4946
+ session.flushTimer = void 0;
4947
+ const batch = {
4948
+ events: session.queue,
4949
+ dropped: session.dropped
4950
+ };
4951
+ session.queue = [];
4952
+ session.dropped = 0;
4953
+ let finished = false;
4954
+ const pending = {
4955
+ timer: setTimeout(() => pending.finish("PERSISTENCE_UNKNOWN"), TIMEOUT_MS),
4956
+ finish: (code, knownRollback = false, flushNext = true) => {
4957
+ if (finished) return;
4958
+ finished = true;
4959
+ clearTimeout(pending.timer);
4960
+ connection.pending.delete(pending);
4961
+ if (knownRollback) this.addDropped(session, batch.events.length + batch.dropped, false);
4962
+ if (code && this.current(session, connection)) this.report(session, code);
4963
+ if (flushNext && this.current(session, connection) && session.ready && (session.queue.length > 0 || !knownRollback && session.dropped > 0)) this.flush(session, connection);
4964
+ }
4965
+ };
4966
+ connection.pending.add(pending);
4967
+ try {
4968
+ connection.channel.push("events", batch, TIMEOUT_MS).receive("ok", (payload) => {
4969
+ pending.finish(!isBatchAcknowledgement(payload, batch.events.length) ? "PERSISTENCE_UNKNOWN" : payload.rejected > 0 ? "EVENTS_REJECTED" : void 0);
4970
+ }).receive("error", (payload) => {
4971
+ if (!this.current(session, connection)) return;
4972
+ const reason = isObject(payload) ? payload.reason : void 0;
4973
+ if (reason === "unauthorized" || reason === "trajectory_mismatch") {
4974
+ pending.finish(void 0, true, false);
4975
+ this.fail(session, connection, reason.toUpperCase(), false);
4976
+ return;
4977
+ }
4978
+ const rollback = typeof reason === "string" && [
4979
+ "invalid_batch",
4980
+ "batch_too_large",
4981
+ "batch_too_many_events",
4982
+ "trajectory_over_share",
4983
+ "trajectory_outbox_full",
4984
+ "unsupported_event"
4985
+ ].includes(reason);
4986
+ pending.finish(rollback ? reason.toUpperCase() : "PERSISTENCE_UNKNOWN", rollback);
4987
+ }).receive("timeout", () => pending.finish("PERSISTENCE_UNKNOWN"));
4988
+ } catch {
4989
+ pending.finish("PERSISTENCE_UNKNOWN");
4990
+ }
4991
+ }
4992
+ };
4993
+
4994
+ //#endregion
4995
+ //#region src/core/learning-bridge.ts
4996
+ const COPILOTKIT_OWN_URLS = ["https://telemetry.copilotkit.ai/", "https://cdn.copilotkit.ai/"];
4997
+ /**
4998
+ * Wires `@copilotkit/learning` into Core. Default capture uses Runtime and Phoenix;
4999
+ * explicit legacy sinks also use agent events and open-Thread attribution.
5000
+ */
5001
+ var LearningBridge = class {
5002
+ collector = null;
5003
+ agents = /* @__PURE__ */ new Map();
5004
+ openThreads = /* @__PURE__ */ new Set();
5005
+ toolCalls = /* @__PURE__ */ new Map();
5006
+ executingTools = /* @__PURE__ */ new Map();
5007
+ lastRunIds = /* @__PURE__ */ new WeakMap();
5008
+ lastLinkedThreadId = null;
5009
+ coreSubscription = null;
5010
+ seq = 0;
5011
+ activeConfig;
5012
+ connection;
5013
+ constructor(core, config, onChange = () => {}) {
5014
+ this.core = core;
5015
+ this.config = config;
5016
+ this.connection = new TrajectoryConnection(core, onChange);
5017
+ this.setConfig(config);
5018
+ }
5019
+ setConfig(config) {
5020
+ this.config = config;
5021
+ if (config === void 0) this.stop();
5022
+ }
5023
+ get isConfigured() {
5024
+ return this.config !== void 0;
5025
+ }
5026
+ get isLegacyCaptureActive() {
5027
+ return this.collector !== null && this.collector.trajectoryId !== null;
5028
+ }
5029
+ startTracking() {
5030
+ if (this.coreSubscription !== null || !this.isLegacyCaptureActive) return;
5031
+ this.coreSubscription = this.core.subscribe({
5032
+ onAgentsChanged: ({ agents }) => {
5033
+ for (const agent of Object.values(agents)) this.track(agent);
5034
+ },
5035
+ onAgentRunStarted: ({ agent }) => this.track(agent),
5036
+ onToolExecutionStart: ({ toolCallId, agentId, toolName }) => {
5037
+ if (!this.isLegacyCaptureActive) return;
5038
+ this.executingTools.set(toolCallId, Date.now());
5039
+ this.emitToolCall("executing", toolCallId, agentId, toolName, {});
5040
+ },
5041
+ onToolExecutionEnd: ({ toolCallId, agentId, toolName, error }) => {
5042
+ if (!this.isLegacyCaptureActive) return;
5043
+ const startedAt = this.executingTools.get(toolCallId);
5044
+ this.executingTools.delete(toolCallId);
5045
+ this.emitToolCall("completed", toolCallId, agentId, toolName, {
5046
+ durationMs: startedAt === void 0 ? void 0 : Date.now() - startedAt,
5047
+ outcome: error === void 0 ? "ok" : "error"
5048
+ });
5049
+ this.toolCalls.delete(toolCallId);
5050
+ }
5051
+ });
5052
+ for (const agent of Object.values(this.core.agents)) this.track(agent);
5053
+ }
5054
+ get trajectoryId() {
5055
+ return this.connection.trajectoryId ?? this.collector?.trajectoryId ?? null;
5056
+ }
5057
+ start(options = {}) {
5058
+ const config = this.config;
5059
+ if (config === void 0) {
5060
+ console.warn("[CopilotKit] startTrajectory() needs the `learning` option on CopilotKitCore (or the CopilotKitProvider `learning` prop).");
5061
+ return Promise.resolve({
5062
+ status: "error",
5063
+ code: "LEARNING_DISABLED"
5064
+ });
5065
+ }
5066
+ if (config.sink === void 0) {
5067
+ if (options.learningContainerIds !== void 0) console.warn("[CopilotKit] learningContainerIds is supported only with a custom sink. Authenticated Trajectory capture does not assign Learning Containers; remove learningContainerIds from the capture options.");
5068
+ this.stopLegacy();
5069
+ return this.connection.start(options.trajectoryId, config);
5070
+ }
5071
+ this.connection.stop();
5072
+ const trajectoryId = options.trajectoryId ?? this.collector?.trajectoryId ?? crypto.randomUUID();
5073
+ if (this.collector === null) {
5074
+ this.activeConfig = config;
5075
+ const runtimeUrl = this.core.runtimeUrl;
5076
+ this.collector = (0, _copilotkit_learning.createCollector)({
5077
+ ...config,
5078
+ beforeSend: (event) => {
5079
+ const sequenced = {
5080
+ ...event,
5081
+ value: {
5082
+ ...event.value,
5083
+ seq: ++this.seq
5084
+ }
5085
+ };
5086
+ return config.beforeSend === void 0 ? sequenced : config.beforeSend(sequenced);
5087
+ },
5088
+ ignoreUrls: [
5089
+ ...config.ignoreUrls ?? [],
5090
+ ...COPILOTKIT_OWN_URLS,
5091
+ ...runtimeUrl === void 0 ? [] : [runtimeUrl]
5092
+ ],
5093
+ enrich: (target) => this.enrich(target)
5094
+ });
5095
+ }
5096
+ const wasActive = this.collector.trajectoryId !== null;
5097
+ this.collector.start({
5098
+ ...options,
5099
+ trajectoryId
5100
+ });
5101
+ if (wasActive) return Promise.resolve(this.collector.trajectoryId === trajectoryId ? {
5102
+ status: "started",
5103
+ trajectoryId
5104
+ } : {
5105
+ status: "error",
5106
+ code: "TRAJECTORY_ACTIVE"
5107
+ });
5108
+ this.startTracking();
5109
+ this.lastLinkedThreadId = null;
5110
+ for (const threadId of this.distinctOpenThreadIds()) this.emitThreadLinked(threadId, "start");
5111
+ return Promise.resolve({
5112
+ status: "started",
5113
+ trajectoryId
5114
+ });
5115
+ }
5116
+ stop() {
5117
+ this.connection.stop();
5118
+ this.stopLegacy();
5119
+ }
5120
+ stopLegacy() {
5121
+ this.collector?.stop();
5122
+ this.collector = null;
5123
+ this.activeConfig = void 0;
5124
+ this.coreSubscription?.unsubscribe();
5125
+ this.coreSubscription = null;
5126
+ for (const subscription of this.agents.values()) subscription.unsubscribe();
5127
+ this.agents.clear();
5128
+ this.toolCalls.clear();
5129
+ this.executingTools.clear();
5130
+ }
5131
+ /** A developer event. Only an explicit legacy sink adds open-Thread context. */
5132
+ emit(name, value) {
5133
+ this.connection.emit(name, value);
5134
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) this.collector?.emit(name, {
5135
+ ...this.openThreadContext(),
5136
+ ...value
5137
+ });
5138
+ }
5139
+ registerOpenThread(params) {
5140
+ const entry = { ...params };
5141
+ const isNew = !this.distinctOpenThreadIds().includes(entry.threadId);
5142
+ this.openThreads.add(entry);
5143
+ if (isNew) this.emitThreadLinked(entry.threadId, "open");
5144
+ return {
5145
+ update: (threadId) => {
5146
+ if (threadId === entry.threadId) return;
5147
+ entry.threadId = threadId;
5148
+ if (![...this.openThreads].filter((other) => other !== entry).some((other) => other.threadId === threadId)) this.emitThreadLinked(threadId, "switch");
5149
+ },
5150
+ unregister: () => {
5151
+ this.openThreads.delete(entry);
5152
+ }
5153
+ };
5154
+ }
5155
+ distinctOpenThreadIds() {
5156
+ return [...new Set([...this.openThreads].map((open) => open.threadId))];
5157
+ }
5158
+ emitThreadLinked(threadId, reason) {
5159
+ if (!(this.collector !== null && this.collector.trajectoryId !== null) || threadId === this.lastLinkedThreadId) return;
5160
+ this.lastLinkedThreadId = threadId;
5161
+ const owner = [...this.openThreads].find((open) => open.threadId === threadId);
5162
+ const agent = this.findAgentForThread(threadId);
5163
+ this.collector?.ɵemit("thread.linked", {
5164
+ threadId,
5165
+ agentId: owner?.agentId ?? agent?.agentId ?? null,
5166
+ hasMessages: (agent?.messages.length ?? 0) > 0,
5167
+ reason
5168
+ });
5169
+ }
5170
+ findAgentForThread(threadId) {
5171
+ return [...this.agents.keys()].find((agent) => agent.threadId === threadId);
5172
+ }
5173
+ track(agent) {
5174
+ const agentId = agent.agentId;
5175
+ if (!this.isLegacyCaptureActive || this.agents.has(agent) || agentId === void 0) return;
5176
+ this.agents.set(agent, agent.subscribe(this.agentSubscriber(agent, agentId)));
5177
+ }
5178
+ agentSubscriber(agent, agentId) {
5179
+ const runIdFor = () => this.lastRunIds.get(agent);
5180
+ return {
5181
+ onRunStartedEvent: ({ event }) => {
5182
+ this.lastRunIds.set(agent, event.runId);
5183
+ this.emitRun("started", agent, event.threadId, event.runId);
5184
+ },
5185
+ onRunFinishedEvent: ({ event }) => {
5186
+ this.emitRun("finished", agent, event.threadId, event.runId);
5187
+ },
5188
+ onRunErrorEvent: () => {
5189
+ this.emitRun("error", agent, agent.threadId, runIdFor() ?? null);
5190
+ },
5191
+ onToolCallStartEvent: ({ event }) => {
5192
+ if (!this.isLegacyCaptureActive) return;
5193
+ const origin = {
5194
+ agentId,
5195
+ threadId: agent.threadId,
5196
+ messageId: event.parentMessageId,
5197
+ runId: runIdFor()
5198
+ };
5199
+ this.toolCalls.set(event.toolCallId, origin);
5200
+ this.emitToolCall("started", event.toolCallId, origin.agentId, event.toolCallName, {});
5201
+ },
5202
+ onToolCallResultEvent: ({ event }) => {
5203
+ if (!this.executingTools.has(event.toolCallId)) this.toolCalls.delete(event.toolCallId);
5204
+ },
5205
+ onNewMessage: ({ message }) => this.emitMessage(agent, message)
5206
+ };
5207
+ }
5208
+ emitRun(phase, agent, threadId, runId) {
5209
+ this.collector?.ɵemit("agent.run", {
5210
+ phase,
5211
+ runId,
5212
+ threadId,
5213
+ agentId: agent.agentId
5214
+ });
5215
+ }
5216
+ emitMessage(agent, message) {
5217
+ const content = typeof message.content === "string" ? message.content : "";
5218
+ const includeText = this.activeConfig?.capture?.agentText !== false && typeof message.content === "string";
5219
+ this.collector?.ɵemit("agent.message", {
5220
+ messageId: message.id,
5221
+ runId: this.runIdForMessage(agent, message.id),
5222
+ threadId: agent.threadId,
5223
+ agentId: agent.agentId,
5224
+ role: message.role,
5225
+ textLength: content.length,
5226
+ ...includeText ? { text: content } : {}
5227
+ });
5228
+ }
5229
+ emitToolCall(phase, toolCallId, agentId, toolName, extra) {
5230
+ const origin = this.toolCalls.get(toolCallId);
5231
+ this.collector?.ɵemit("tool.call", {
5232
+ phase,
5233
+ toolCallId,
5234
+ toolName,
5235
+ agentId,
5236
+ threadId: origin?.threadId ?? null,
5237
+ messageId: origin?.messageId,
5238
+ runId: origin?.runId,
5239
+ ...extra
5240
+ });
5241
+ }
5242
+ runIdForMessage(agent, messageId) {
5243
+ const agentId = agent.agentId;
5244
+ if (agentId === void 0) return null;
5245
+ return this.core.getRunIdForMessage(agentId, agent.threadId, messageId) ?? this.lastRunIds.get(agent) ?? null;
5246
+ }
5247
+ findMessage(messageId) {
5248
+ for (const agent of this.agents.keys()) {
5249
+ const message = agent.messages.find((candidate) => candidate.id === messageId);
5250
+ if (message !== void 0) return {
5251
+ agent,
5252
+ message
5253
+ };
5254
+ }
5255
+ return null;
5256
+ }
5257
+ toolStatus(agent, toolCallId) {
5258
+ if (this.executingTools.has(toolCallId)) return "executing";
5259
+ return agent.messages.some((message) => message.role === "tool" && message.toolCallId === toolCallId) ? "complete" : "inProgress";
5260
+ }
5261
+ /** Adds agent context to a click: exact message first, then the single open Thread. */
5262
+ enrich(target) {
5263
+ const messageId = target.closest("[data-message-id]")?.getAttribute("data-message-id") ?? null;
5264
+ const toolCallId = target.closest("[data-tool-call-id]")?.getAttribute("data-tool-call-id") ?? null;
5265
+ const found = messageId === null ? null : this.findMessage(messageId);
5266
+ if (found !== null) {
5267
+ const { agent, message } = found;
5268
+ const toolCall = toolCallId !== null && message.role === "assistant" ? message.toolCalls?.find((call) => call.id === toolCallId) : void 0;
5269
+ return {
5270
+ threadId: agent.threadId,
5271
+ agentId: agent.agentId,
5272
+ messageId: message.id,
5273
+ runId: this.runIdForMessage(agent, message.id),
5274
+ ...toolCall === void 0 ? {} : {
5275
+ toolCallId: toolCall.id,
5276
+ toolName: toolCall.function.name,
5277
+ toolStatus: this.toolStatus(agent, toolCall.id)
5278
+ }
5279
+ };
5280
+ }
5281
+ return this.openThreadContext();
5282
+ }
5283
+ /** The single open Thread, or `null` with the candidates when several are open. */
5284
+ openThreadContext() {
5285
+ const openIds = this.distinctOpenThreadIds();
5286
+ if (openIds.length > 1) return {
5287
+ threadId: null,
5288
+ threadAmbiguity: {
5289
+ reason: "multiple-open",
5290
+ candidates: openIds
5291
+ }
5292
+ };
5293
+ return { threadId: openIds[0] ?? null };
5294
+ }
5295
+ };
5296
+
4515
5297
  //#endregion
4516
5298
  //#region src/utils/micro-redux.ts
4517
5299
  const INTERNAL_ACTION_TYPES = {
@@ -5664,6 +6446,9 @@ var CopilotKitCore = class {
5664
6446
  runHandler;
5665
6447
  stateManager;
5666
6448
  threadStoreRegistry;
6449
+ learningBridge;
6450
+ notifiedTrajectoryId = null;
6451
+ learningConfiguredListeners = /* @__PURE__ */ new Set();
5667
6452
  /**
5668
6453
  * The single core-owned memory store, created lazily on first
5669
6454
  * `getMemoryStore()` and kept user-scoped for the lifetime of the core.
@@ -5678,7 +6463,7 @@ var CopilotKitCore = class {
5678
6463
  * store that was registered prior to that initial notification.
5679
6464
  */
5680
6465
  previousAgentIds = /* @__PURE__ */ new Set();
5681
- constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, messageFilter, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug }) {
6466
+ constructor({ runtimeUrl, runtimeTransport = "auto", deferInitialConnection = false, headers = {}, credentials, messageFilter, properties = {}, agents__unsafe_dev_only = {}, tools = [], suggestionsConfig = [], debug, learning }) {
5682
6467
  this._headers = normalizeHeaders(headers);
5683
6468
  this._credentials = credentials;
5684
6469
  this._messageFilter = messageFilter;
@@ -5694,6 +6479,7 @@ var CopilotKitCore = class {
5694
6479
  this.runHandler.initialize(tools);
5695
6480
  this.suggestionEngine.initialize(suggestionsConfig);
5696
6481
  this.stateManager.initialize();
6482
+ this.learningBridge = new LearningBridge(this, learning, () => this.notifyTrajectoryChanged());
5697
6483
  this.agentRegistry.setRuntimeTransport(runtimeTransport);
5698
6484
  this.agentRegistry.setRuntimeUrl(runtimeUrl, { deferConnection: deferInitialConnection });
5699
6485
  this.previousAgentIds = new Set(Object.keys(agents__unsafe_dev_only));
@@ -5845,6 +6631,10 @@ var CopilotKitCore = class {
5845
6631
  get runtimeConnectionStatus() {
5846
6632
  return this.agentRegistry.runtimeConnectionStatus;
5847
6633
  }
6634
+ /** @internal The verbatim single-endpoint URL, including any trailing slash. */
6635
+ get ɵruntimeEndpointUrl() {
6636
+ return this.agentRegistry.runtimeEndpointUrl;
6637
+ }
5848
6638
  get ɵruntimeFetch() {
5849
6639
  return this.agentRegistry.createRuntimeFetch();
5850
6640
  }
@@ -6004,6 +6794,83 @@ var CopilotKitCore = class {
6004
6794
  getAgent(id) {
6005
6795
  return this.agentRegistry.getAgent(id);
6006
6796
  }
6797
+ /** Updates settings for the next capture. Removing the config stops capture. */
6798
+ setLearningConfig(config) {
6799
+ const wasConfigured = this.ɵlearningConfigured;
6800
+ this.learningBridge.setConfig(config);
6801
+ this.notifyTrajectoryChanged();
6802
+ if (this.ɵlearningConfigured === wasConfigured) return;
6803
+ for (const listener of this.learningConfiguredListeners) try {
6804
+ listener();
6805
+ } catch (error) {
6806
+ console.error("Learning configured listener error:", error);
6807
+ }
6808
+ }
6809
+ /**
6810
+ * @internal Whether a `learning` config is set. Unlike {@link trajectoryId},
6811
+ * it does not change with capture start, connection failures or reconnects.
6812
+ */
6813
+ get ɵlearningConfigured() {
6814
+ return this.learningBridge.isConfigured;
6815
+ }
6816
+ /** @internal Calls `listener` when {@link ɵlearningConfigured} changes. */
6817
+ ɵsubscribeToLearningConfigured(listener) {
6818
+ this.learningConfiguredListeners.add(listener);
6819
+ return () => {
6820
+ this.learningConfiguredListeners.delete(listener);
6821
+ };
6822
+ }
6823
+ /** The active capture's Trajectory ID, or null while capture is stopped. */
6824
+ get trajectoryId() {
6825
+ return this.learningBridge.trajectoryId;
6826
+ }
6827
+ notifyTrajectoryChanged() {
6828
+ const trajectoryId = this.trajectoryId;
6829
+ if (trajectoryId === this.notifiedTrajectoryId) return;
6830
+ this.notifiedTrajectoryId = trajectoryId;
6831
+ this.notifySubscribers((subscriber) => subscriber.onTrajectoryChanged?.({
6832
+ copilotkit: this,
6833
+ trajectoryId
6834
+ }), "Subscriber onTrajectoryChanged error:");
6835
+ }
6836
+ /**
6837
+ * Starts one Trajectory after Runtime authorization and the Gateway join.
6838
+ * Omit the ID to create one. Repeated starts await the active connection;
6839
+ * a different ID stops the previous capture. Explicit sinks retain the
6840
+ * prototype's synchronous capture and require a stop before changing IDs.
6841
+ *
6842
+ * @example copilotkit.startTrajectory({ trajectoryId: crypto.randomUUID() })
6843
+ */
6844
+ startTrajectory(options = {}) {
6845
+ const result = this.learningBridge.start(options);
6846
+ this.notifyTrajectoryChanged();
6847
+ return result;
6848
+ }
6849
+ /** Cancels pending work and stops capture, attempting one final batch without waiting for its ACK. */
6850
+ stopTrajectory() {
6851
+ this.learningBridge.stop();
6852
+ this.notifyTrajectoryChanged();
6853
+ }
6854
+ /**
6855
+ * Records an outcome that clicks cannot show, such as a saved report or an
6856
+ * approved deal. The wire adds `value.seq`; arrays, primitives, and objects
6857
+ * already containing `seq` are preserved under `value.data`. Explicit legacy
6858
+ * sinks retain object-only events with open-Thread enrichment.
6859
+ * No-op while no Trajectory runs. Events emitted during connection recovery
6860
+ * count as dropped. Built-in names such as `click` are rejected.
6861
+ *
6862
+ * @example copilotkit.emitTrajectoryEvent("deal.approved", { dealId: "deal-1" })
6863
+ */
6864
+ emitTrajectoryEvent(name, value = {}) {
6865
+ this.learningBridge.emit(name, value);
6866
+ }
6867
+ /**
6868
+ * Tells Core that a view shows this Thread, so captured interactions can link to it.
6869
+ * Framework bindings call this; call it yourself only without a CopilotKit provider.
6870
+ */
6871
+ registerOpenThread(params) {
6872
+ return this.learningBridge.registerOpenThread(params);
6873
+ }
6007
6874
  /**
6008
6875
  * Re-apply the current headers to a single agent (delegated to
6009
6876
  * AgentRegistry). Core headers are merged on top of the agent's own