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