@copilotkit/core 1.70.0 → 1.70.2

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,8 +6,84 @@ 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 rxjs_fetch = require("rxjs/fetch");
10
9
 
10
+ //#region src/utils/connect-replay.ts
11
+ /**
12
+ * Runs an agent's `connect()` stream through the AbstractAgent apply pipeline
13
+ * with the `verifyEvents` step omitted.
14
+ *
15
+ * `verifyEvents` enforces AG-UI's *single run* lifecycle rules: exactly one
16
+ * RUN_STARTED opening the stream, no events after a terminal RUN_FINISHED /
17
+ * RUN_ERROR. Those rules are correct for `/run`, but a `/connect` response is a
18
+ * replay of a thread's history and can legitimately carry several past runs
19
+ * back to back — including a run that ended in RUN_ERROR followed by a later
20
+ * RUN_STARTED. Verifying a replay against single-run rules makes hydration of
21
+ * an existing thread fail outright:
22
+ *
23
+ * Cannot send event type 'RUN_STARTED': The run has already errored with
24
+ * 'RUN_ERROR'. No further events can be sent.
25
+ *
26
+ * `transformChunks` is still applied — message reassembly is needed either way.
27
+ *
28
+ * This mirrors the base `AbstractAgent.connectAgent` implementation exactly
29
+ * apart from that omission, so callers keep the same subscriber notifications,
30
+ * detach semantics, and `{ result, newMessages }` return shape.
31
+ *
32
+ * TODO: Remove this in favour of the base implementation once AG-UI's
33
+ * AbstractAgent supports opting out of `verifyEvents` for transports whose
34
+ * connection life-cycle isn't a single run. As of `@ag-ui/client@0.0.57`
35
+ * `connectAgent(parameters?, subscriber?)` takes no such option.
36
+ *
37
+ * @param agent - The agent whose `connect()` stream should be consumed.
38
+ * @param parameters - Run parameters, forwarded to `prepareRunAgentInput`.
39
+ * @param subscriber - Optional one-shot subscriber for this connect call.
40
+ */
41
+ async function ɵconnectWithoutEventVerification(agent, parameters, subscriber) {
42
+ const self = agent;
43
+ try {
44
+ agent.isRunning = true;
45
+ agent.agentId = agent.agentId ?? (0, _ag_ui_client.randomUUID)();
46
+ const input = self.prepareRunAgentInput(parameters);
47
+ let result;
48
+ const previousMessageIds = new Set(agent.messages.map((m) => m.id));
49
+ const subscribers = [
50
+ { onRunFinishedEvent: (event) => {
51
+ if (event.outcome === "success") result = event.result;
52
+ } },
53
+ ...agent.subscribers,
54
+ subscriber ?? {}
55
+ ];
56
+ await self.onInitialize(input, subscribers);
57
+ self.activeRunDetach$ = new rxjs.Subject();
58
+ let resolveCompletion;
59
+ self.activeRunCompletionPromise = new Promise((resolve) => {
60
+ resolveCompletion = resolve;
61
+ });
62
+ const source$ = (0, rxjs.defer)(() => self.connect(input)).pipe((0, _ag_ui_client.transformChunks)(self.debugLogger), (0, rxjs_operators.takeUntil)(self.activeRunDetach$));
63
+ const applied$ = self.apply(input, source$, subscribers);
64
+ await (0, rxjs.lastValueFrom)(self.processApplyEvents(input, applied$, subscribers).pipe((0, rxjs_operators.catchError)((error) => {
65
+ agent.isRunning = false;
66
+ if (error instanceof _ag_ui_client.AGUIConnectNotImplementedError) return rxjs.EMPTY;
67
+ return self.onError(input, error, subscribers);
68
+ }), (0, rxjs_operators.finalize)(() => {
69
+ agent.isRunning = false;
70
+ self.onFinalize(input, subscribers);
71
+ resolveCompletion?.();
72
+ resolveCompletion = void 0;
73
+ self.activeRunCompletionPromise = void 0;
74
+ self.activeRunDetach$ = void 0;
75
+ })), { defaultValue: void 0 });
76
+ const newMessages = (0, _ag_ui_client.structuredClone_)(agent.messages).filter((m) => !previousMessageIds.has(m.id));
77
+ return {
78
+ result,
79
+ newMessages
80
+ };
81
+ } finally {
82
+ agent.isRunning = false;
83
+ }
84
+ }
85
+
86
+ //#endregion
11
87
  //#region src/utils/phoenix-observable.ts
12
88
  /**
13
89
  * Adapt Phoenix socket open/error callbacks into an observable signal stream.
@@ -204,72 +280,22 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
204
280
  /**
205
281
  * Override of AbstractAgent.connectAgent that removes the `verifyEvents` step.
206
282
  *
207
- * Background: AbstractAgent's connectAgent pipeline runs events through
208
- * `verifyEvents`, which validates that the stream follows the AG-UI protocol
209
- * lifecycle — specifically, it expects a RUN_STARTED event before any content
210
- * events and a RUN_FINISHED/RUN_ERROR event to complete the stream.
211
- *
212
283
  * IntelligenceAgent uses long-lived WebSocket connections rather than
213
284
  * request-scoped SSE streams. When connecting to replay historical messages
214
285
  * for an existing thread, the connection semantics don't map to a single
215
- * agent run start/stop cycle. The replayed events may not include
216
- * RUN_STARTED/RUN_FINISHED bookends (or may contain events from multiple
217
- * past runs), which causes verifyEvents to either never complete or to
218
- * error out.
286
+ * agent run start/stop cycle: the replayed events may omit the
287
+ * RUN_STARTED/RUN_FINISHED bookends, or carry events from several past runs,
288
+ * either of which makes `verifyEvents` stall or error out.
219
289
  *
220
- * This override replicates the base connectAgent implementation exactly,
221
- * substituting only `transformChunks` (which is still needed for message
222
- * reassembly) and omitting `verifyEvents`.
223
- *
224
- * TODO: Remove this override once AG-UI's AbstractAgent supports opting out
225
- * of verifyEvents for transports with different connection life-cycles.
290
+ * See {@link ɵconnectWithoutEventVerification} for the pipeline itself, which
291
+ * the self-hosted `/connect` path shares for the same reason.
226
292
  */
227
293
  async connectAgent(parameters, subscriber) {
228
- const self = this;
229
- try {
230
- this.isRunning = true;
231
- this.agentId = this.agentId ?? (0, _ag_ui_client.randomUUID)();
232
- const effectiveParameters = parameters?.runId || !this.canonicalRunId ? parameters : {
233
- ...parameters,
234
- runId: this.canonicalRunId
235
- };
236
- const input = this.prepareRunAgentInput(effectiveParameters);
237
- let result;
238
- const previousMessageIds = new Set(this.messages.map((m) => m.id));
239
- const subscribers = [
240
- { onRunFinishedEvent: (event) => {
241
- if (event.outcome === "success") result = event.result;
242
- } },
243
- ...this.subscribers,
244
- subscriber ?? {}
245
- ];
246
- await this.onInitialize(input, subscribers);
247
- self.activeRunDetach$ = new rxjs.Subject();
248
- let resolveCompletion;
249
- self.activeRunCompletionPromise = new Promise((resolve) => {
250
- resolveCompletion = resolve;
251
- });
252
- const source$ = (0, rxjs.defer)(() => this.connect(input)).pipe((0, _ag_ui_client.transformChunks)(this.debugLogger), (0, rxjs_operators.takeUntil)(self.activeRunDetach$));
253
- const applied$ = this.apply(input, source$, subscribers);
254
- await (0, rxjs.lastValueFrom)(this.processApplyEvents(input, applied$, subscribers).pipe((0, rxjs_operators.catchError)((error) => {
255
- this.isRunning = false;
256
- return this.onError(input, error, subscribers);
257
- }), (0, rxjs_operators.finalize)(() => {
258
- this.isRunning = false;
259
- this.onFinalize(input, subscribers);
260
- resolveCompletion?.();
261
- resolveCompletion = void 0;
262
- self.activeRunCompletionPromise = void 0;
263
- self.activeRunDetach$ = void 0;
264
- })), { defaultValue: void 0 });
265
- const newMessages = (0, _ag_ui_client.structuredClone_)(this.messages).filter((m) => !previousMessageIds.has(m.id));
266
- return {
267
- result,
268
- newMessages
269
- };
270
- } finally {
271
- this.isRunning = false;
272
- }
294
+ const effectiveParameters = parameters?.runId || !this.canonicalRunId ? parameters : {
295
+ ...parameters,
296
+ runId: this.canonicalRunId
297
+ };
298
+ return ɵconnectWithoutEventVerification(this, effectiveParameters, subscriber);
273
299
  }
274
300
  abortRun() {
275
301
  if (this.activeChannel && this.canonicalRunId) {
@@ -361,13 +387,7 @@ var IntelligenceAgent = class IntelligenceAgent extends _ag_ui_client.AbstractAg
361
387
  ...this.headers
362
388
  },
363
389
  body: JSON.stringify({
364
- threadId: input.threadId,
365
- runId: input.runId,
366
- messages: input.messages,
367
- tools: input.tools,
368
- context: input.context,
369
- state: input.state,
370
- forwardedProps: input.forwardedProps,
390
+ ...input,
371
391
  ...mode === "connect" ? { lastSeenEventId: replayCursor === void 0 ? this.getReconnectCursor(input) : replayCursor } : {}
372
392
  }),
373
393
  ...this.credentials ? { credentials: this.credentials } : {}
@@ -734,7 +754,7 @@ var ProxiedCopilotRuntimeAgent = class ProxiedCopilotRuntimeAgent extends _ag_ui
734
754
  });
735
755
  }
736
756
  async connectAgent(parameters, subscriber) {
737
- if (this.runtimeMode !== _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE) return super.connectAgent(parameters, subscriber);
757
+ if (this.runtimeMode !== _copilotkit_shared.RUNTIME_MODE_INTELLIGENCE) return ɵconnectWithoutEventVerification(this, parameters, subscriber);
738
758
  if (this.delegate) await this.delegate.detachActiveRun();
739
759
  await this.resolveDelegate();
740
760
  const delegate = this.delegate;
@@ -986,6 +1006,74 @@ function runtimeRequestMeta(init) {
986
1006
  return init?.ɵruntimeRequest;
987
1007
  }
988
1008
 
1009
+ //#endregion
1010
+ //#region src/utils/single-route-resource-request.ts
1011
+ /** Returns true when a path belongs to an Intelligence resource API. */
1012
+ function isIntelligenceResourcePath(path) {
1013
+ return path === "/threads" || path.startsWith("/threads/") || path === "/memories" || path.startsWith("/memories/") || path === "/annotate";
1014
+ }
1015
+ /** Reads a JSON resource body without changing invalid JSON error behavior. */
1016
+ async function readResourceBody(input, init, headers, method) {
1017
+ if (method === "GET" || method === "HEAD") return void 0;
1018
+ let text;
1019
+ if (init?.body != null) text = await new Response(init.body).text();
1020
+ else if (input instanceof Request) text = await input.clone().text();
1021
+ if (!text) return void 0;
1022
+ if (headers.get("content-type")?.toLowerCase().includes("application/json")) try {
1023
+ return JSON.parse(text);
1024
+ } catch {
1025
+ return text;
1026
+ }
1027
+ return text;
1028
+ }
1029
+ /**
1030
+ * Converts one Runtime resource fetch into a single-route JSON envelope.
1031
+ * Requests outside the mounted Runtime or outside its resource APIs pass
1032
+ * through unchanged.
1033
+ */
1034
+ async function createSingleRouteResourceRequest(input, init, runtimeUrl) {
1035
+ const runtime = new URL(runtimeUrl);
1036
+ const inputUrl = input instanceof Request ? input.url : input instanceof URL ? input.href : input;
1037
+ const target = new URL(inputUrl, runtime);
1038
+ const runtimePath = runtime.pathname.replace(/\/$/, "");
1039
+ if (target.origin !== runtime.origin) return null;
1040
+ if (!target.pathname.startsWith(`${runtimePath}/`)) return null;
1041
+ const resourcePath = target.pathname.slice(runtimePath.length);
1042
+ if (!isIntelligenceResourcePath(resourcePath)) return null;
1043
+ const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
1044
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : void 0));
1045
+ const body = await readResourceBody(input, init, headers, method);
1046
+ headers.set("content-type", "application/json");
1047
+ headers.delete("content-length");
1048
+ return {
1049
+ input: runtimeUrl,
1050
+ init: {
1051
+ ...input instanceof Request ? {
1052
+ cache: input.cache,
1053
+ credentials: input.credentials,
1054
+ integrity: input.integrity,
1055
+ keepalive: input.keepalive,
1056
+ mode: input.mode,
1057
+ redirect: input.redirect,
1058
+ referrer: input.referrer,
1059
+ referrerPolicy: input.referrerPolicy,
1060
+ signal: input.signal
1061
+ } : {},
1062
+ ...init,
1063
+ method: "POST",
1064
+ headers,
1065
+ body: JSON.stringify({
1066
+ method: "resource/request",
1067
+ params: {
1068
+ path: `${resourcePath}${target.search}`,
1069
+ httpMethod: method
1070
+ },
1071
+ ...body === void 0 ? {} : { body }
1072
+ })
1073
+ }
1074
+ };
1075
+ }
1076
+
989
1077
  //#endregion
990
1078
  //#region src/core/agent-registry.ts
991
1079
  /** Maximum wait for optional Inspector metadata before degrading to absence. */
@@ -1027,7 +1115,9 @@ var AgentRegistry = class {
1027
1115
  _runtimeMode = _copilotkit_shared.RUNTIME_MODE_SSE;
1028
1116
  _intelligence;
1029
1117
  _threadEndpoints;
1118
+ _singleRouteResourceOperations = false;
1030
1119
  _suggestions;
1120
+ _inspectorLearning = false;
1031
1121
  _inspectorMetadata;
1032
1122
  _inspectorMetadataSupported = false;
1033
1123
  inspectorMetadataRefreshReady = false;
@@ -1098,6 +1188,9 @@ var AgentRegistry = class {
1098
1188
  get suggestions() {
1099
1189
  return this._suggestions;
1100
1190
  }
1191
+ get inspectorLearning() {
1192
+ return this._inspectorLearning;
1193
+ }
1101
1194
  get inspectorMetadata() {
1102
1195
  return this._inspectorMetadata;
1103
1196
  }
@@ -1148,6 +1241,7 @@ var AgentRegistry = class {
1148
1241
  this.resetRuntimeEntitlementRetry();
1149
1242
  this._licenseStatus = void 0;
1150
1243
  this._runtimeEntitlements = void 0;
1244
+ this._singleRouteResourceOperations = false;
1151
1245
  this._runtimeUrl = normalizedRuntimeUrl;
1152
1246
  if (options?.deferConnection) return;
1153
1247
  this.updateRuntimeConnection({ preserveOnFailure: this.hasLiveRuntimeKnowledgeToProtect() });
@@ -1176,6 +1270,7 @@ var AgentRegistry = class {
1176
1270
  this.resetRuntimeEntitlementRetry();
1177
1271
  this._requestedTransport = runtimeTransport;
1178
1272
  this._runtimeTransport = runtimeTransport;
1273
+ this._singleRouteResourceOperations = false;
1179
1274
  this.updateRuntimeConnection({ preserveOnFailure: this.hasLiveRuntimeKnowledgeToProtect() });
1180
1275
  }
1181
1276
  hasLiveRuntimeKnowledgeToProtect() {
@@ -1347,7 +1442,8 @@ var AgentRegistry = class {
1347
1442
  const meta = () => runtimeRequestMeta(init);
1348
1443
  const watchdog = this.armRuntimeRequestWatchdog(meta());
1349
1444
  try {
1350
- const response = await fetch(input, init);
1445
+ const singleRouteRequest = this._runtimeTransport === "single" && this._singleRouteResourceOperations && this._runtimeUrl ? await createSingleRouteResourceRequest(input, init, this._runtimeUrl) : null;
1446
+ const response = singleRouteRequest ? await fetch(singleRouteRequest.input, singleRouteRequest.init) : await fetch(input, init);
1351
1447
  watchdog.clear();
1352
1448
  this.handleRuntimeRequestOutcome(response.ok ? "ok" : meta()?.nonCritical ? "ignored" : "failed", watchdog.checked);
1353
1449
  return response;
@@ -1496,6 +1592,7 @@ var AgentRegistry = class {
1496
1592
  }
1497
1593
  invalidateInspectorMetadataConnection() {
1498
1594
  this._inspectorMetadataSupported = false;
1595
+ this._inspectorLearning = false;
1499
1596
  this.inspectorMetadataRefreshReady = false;
1500
1597
  this.inspectorMetadataConnectionGeneration += 1;
1501
1598
  this.inspectorMetadataGeneration += 1;
@@ -1694,7 +1791,9 @@ var AgentRegistry = class {
1694
1791
  this._runtimeMode = _copilotkit_shared.RUNTIME_MODE_SSE;
1695
1792
  this._intelligence = void 0;
1696
1793
  this._threadEndpoints = void 0;
1794
+ this._singleRouteResourceOperations = false;
1697
1795
  this._suggestions = void 0;
1796
+ this._inspectorLearning = false;
1698
1797
  this._a2uiEnabled = false;
1699
1798
  this._a2uiAgents = void 0;
1700
1799
  this._openGenerativeUIEnabled = false;
@@ -1763,8 +1862,10 @@ var AgentRegistry = class {
1763
1862
  this._audioFileTranscriptionEnabled = runtimeInfoResponse.audioFileTranscriptionEnabled ?? false;
1764
1863
  this._runtimeMode = runtimeInfoResponse.mode ?? _copilotkit_shared.RUNTIME_MODE_SSE;
1765
1864
  this._intelligence = runtimeInfoResponse.intelligence;
1766
- this._threadEndpoints = runtimeInfoResponse.threadEndpoints;
1865
+ this._singleRouteResourceOperations = resolvedTransport === "single" && runtimeInfoResponse.singleRoute?.resourceOperations === true;
1866
+ this._threadEndpoints = this._singleRouteResourceOperations ? runtimeInfoResponse.singleRoute?.threadEndpoints : runtimeInfoResponse.threadEndpoints;
1767
1867
  this._suggestions = runtimeInfoResponse.suggestions;
1868
+ this._inspectorLearning = runtimeInfoResponse.inspectorLearning === true;
1768
1869
  this._inspectorMetadataSupported = runtimeInfoResponse.inspectorMetadata === true;
1769
1870
  this.inspectorMetadataRefreshReady = false;
1770
1871
  if (!this._inspectorMetadataSupported) this.setInspectorMetadata(void 0);
@@ -1798,7 +1899,9 @@ var AgentRegistry = class {
1798
1899
  this._runtimeMode = _copilotkit_shared.RUNTIME_MODE_SSE;
1799
1900
  this._intelligence = void 0;
1800
1901
  this._threadEndpoints = void 0;
1902
+ this._singleRouteResourceOperations = false;
1801
1903
  this._suggestions = void 0;
1904
+ this._inspectorLearning = false;
1802
1905
  this._a2uiEnabled = false;
1803
1906
  this._a2uiAgents = void 0;
1804
1907
  this._openGenerativeUIEnabled = false;
@@ -2343,6 +2446,137 @@ function isForwardedToClientPlaceholder(content) {
2343
2446
  return normalizeToolResultContent(content) === FORWARDED_TO_CLIENT;
2344
2447
  }
2345
2448
 
2449
+ //#endregion
2450
+ //#region src/core/tool-schema.ts
2451
+ /**
2452
+ * Empty tool schema constant
2453
+ */
2454
+ const EMPTY_TOOL_SCHEMA = {
2455
+ type: "object",
2456
+ properties: {}
2457
+ };
2458
+ /**
2459
+ * Create a JSON schema from a tool's parameters
2460
+ */
2461
+ function createToolSchema(tool) {
2462
+ if (!tool.parameters) return { ...EMPTY_TOOL_SCHEMA };
2463
+ const rawSchema = (0, _copilotkit_shared.schemaToJsonSchema)(tool.parameters, { zodToJsonSchema: (schema, options) => (0, zod_to_json_schema.zodToJsonSchema)(schema, options) });
2464
+ if (!rawSchema || typeof rawSchema !== "object") return { ...EMPTY_TOOL_SCHEMA };
2465
+ const { $schema: _$schema, ...schema } = rawSchema;
2466
+ if (typeof schema.type !== "string") schema.type = "object";
2467
+ if (typeof schema.properties !== "object" || schema.properties === null) schema.properties = {};
2468
+ stripAdditionalProperties(schema);
2469
+ return schema;
2470
+ }
2471
+ /**
2472
+ * Remove every `additionalProperties` key from a JSON schema tree, in place.
2473
+ * Some LLM providers reject schemas that carry it.
2474
+ */
2475
+ function stripAdditionalProperties(schema) {
2476
+ if (!schema || typeof schema !== "object") return;
2477
+ if (Array.isArray(schema)) {
2478
+ schema.forEach(stripAdditionalProperties);
2479
+ return;
2480
+ }
2481
+ const record = schema;
2482
+ if (record.additionalProperties !== void 0) delete record.additionalProperties;
2483
+ for (const value of Object.values(record)) stripAdditionalProperties(value);
2484
+ }
2485
+
2486
+ //#endregion
2487
+ //#region src/core/webmcp.ts
2488
+ /**
2489
+ * Return the page's `document.modelContext`, or null when WebMCP is not
2490
+ * available (SSR, React Native, browsers without the API enabled).
2491
+ */
2492
+ function getWebMCPModelContext() {
2493
+ if (typeof document === "undefined") return null;
2494
+ return document.modelContext ?? null;
2495
+ }
2496
+ /**
2497
+ * Keeps a set of frontend tools registered on the page's WebMCP model context.
2498
+ *
2499
+ * `sync(desired)` reconciles the registrations with the desired set: tools that
2500
+ * are gone (or were re-registered with a new tool object) are unregistered by
2501
+ * aborting their registration signal, and new tools are registered with an
2502
+ * `execute` that delegates to the frontend tool's own handler.
2503
+ *
2504
+ * All WebMCP-API constraints live here so callers only supply the desired set:
2505
+ * a tool without a description is skipped with a warning (the spec rejects
2506
+ * empty descriptions), and failures from `registerTool` (duplicate name,
2507
+ * unsupported context, ...) are warned rather than thrown.
2508
+ */
2509
+ var WebMCPRegistry = class {
2510
+ entries = /* @__PURE__ */ new Map();
2511
+ warnedNames = /* @__PURE__ */ new Set();
2512
+ /** Names currently kept registered on the model context. */
2513
+ get registeredNames() {
2514
+ return [...this.entries.keys()];
2515
+ }
2516
+ /** Reconcile the registered tools with the desired set. */
2517
+ sync(desired) {
2518
+ const modelContext = getWebMCPModelContext();
2519
+ if (!modelContext) return;
2520
+ for (const [name, entry] of [...this.entries]) if (desired.get(name) !== entry.tool) {
2521
+ entry.controller.abort();
2522
+ this.entries.delete(name);
2523
+ }
2524
+ for (const [name, tool] of desired) {
2525
+ if (this.entries.has(name)) continue;
2526
+ if (!tool.description) {
2527
+ this.warnOnce(name, `Skipping WebMCP registration for tool '${name}': WebMCP requires a description.`);
2528
+ continue;
2529
+ }
2530
+ const controller = new AbortController();
2531
+ this.entries.set(name, {
2532
+ tool,
2533
+ controller
2534
+ });
2535
+ modelContext.registerTool(this.buildModelContextTool(tool), { signal: controller.signal }).catch((error) => {
2536
+ if (this.entries.get(name)?.controller !== controller) return;
2537
+ this.entries.delete(name);
2538
+ const message = error instanceof Error ? error.message : String(error);
2539
+ this.warnOnce(name, `WebMCP registration failed for tool '${name}': ${message}`);
2540
+ });
2541
+ }
2542
+ }
2543
+ /**
2544
+ * Build the WebMCP model-context tool for a frontend tool. The `execute`
2545
+ * callback delegates to the frontend tool's own handler, so a browser agent
2546
+ * call runs the same application code as an agent call.
2547
+ */
2548
+ buildModelContextTool(tool) {
2549
+ const annotations = typeof tool.webmcp === "object" ? tool.webmcp.annotations : void 0;
2550
+ return {
2551
+ name: tool.name,
2552
+ description: tool.description,
2553
+ inputSchema: createToolSchema(tool),
2554
+ execute: async (args, options) => {
2555
+ if (!tool.handler) return "";
2556
+ const toolCall = {
2557
+ id: (0, _copilotkit_shared.randomUUID)(),
2558
+ type: "function",
2559
+ function: {
2560
+ name: tool.name,
2561
+ arguments: JSON.stringify(args ?? {})
2562
+ }
2563
+ };
2564
+ return await tool.handler(args, {
2565
+ toolCall,
2566
+ signal: options?.signal
2567
+ });
2568
+ },
2569
+ ...annotations ? { annotations } : {}
2570
+ };
2571
+ }
2572
+ /** Log `message` once per tool name. Repeated syncs must not spam the log. */
2573
+ warnOnce(name, message) {
2574
+ if (this.warnedNames.has(name)) return;
2575
+ this.warnedNames.add(name);
2576
+ _copilotkit_shared.logger.warn(`[CopilotKit] ${message}`);
2577
+ }
2578
+ };
2579
+
2346
2580
  //#endregion
2347
2581
  //#region src/core/run-handler.ts
2348
2582
  /**
@@ -2425,6 +2659,12 @@ var RunHandler = class {
2425
2659
  */
2426
2660
  _runDepth = 0;
2427
2661
  /**
2662
+ * Keeps tools that opt in via `webmcp` registered on the page's WebMCP model
2663
+ * context (`document.modelContext`), in addition to the normal agent
2664
+ * registration. Reconciled on every tool registry mutation.
2665
+ */
2666
+ _webmcpRegistry = new WebMCPRegistry();
2667
+ /**
2428
2668
  * Tracks the threadId of the most recent `connectAgent` call so we
2429
2669
  * can distinguish a fresh thread restore (different threadId than
2430
2670
  * last time — chat is rebuilding state from scratch, must clear
@@ -2495,6 +2735,7 @@ var RunHandler = class {
2495
2735
  initialize(tools) {
2496
2736
  this._propTools = [...tools];
2497
2737
  this._cachedMergedTools = null;
2738
+ this.syncWebMCP();
2498
2739
  }
2499
2740
  /**
2500
2741
  * Add a tool to the registry. Survives provider re-syncs ({@link setTools}),
@@ -2508,6 +2749,7 @@ var RunHandler = class {
2508
2749
  }
2509
2750
  this._hookTools.set(key, tool);
2510
2751
  this._cachedMergedTools = null;
2752
+ this.syncWebMCP();
2511
2753
  }
2512
2754
  /**
2513
2755
  * Remove a tool by name and optionally by agentId
@@ -2519,6 +2761,7 @@ var RunHandler = class {
2519
2761
  return !(tool.name === id && !tool.agentId);
2520
2762
  });
2521
2763
  this._cachedMergedTools = null;
2764
+ this.syncWebMCP();
2522
2765
  }
2523
2766
  /**
2524
2767
  * Get a tool by name and optionally by agentId.
@@ -2541,6 +2784,7 @@ var RunHandler = class {
2541
2784
  setTools(tools) {
2542
2785
  this._propTools = [...tools];
2543
2786
  this._cachedMergedTools = null;
2787
+ this.syncWebMCP();
2544
2788
  }
2545
2789
  /**
2546
2790
  * Return the registered A2UI catalog components (readonly).
@@ -3062,6 +3306,7 @@ var RunHandler = class {
3062
3306
  const key = this.capabilityKey(name, agentId);
3063
3307
  if (enabled) this._disabledToolKeys.delete(key);
3064
3308
  else this._disabledToolKeys.add(key);
3309
+ this.syncWebMCP();
3065
3310
  }
3066
3311
  /** Whether a tool is currently enabled (not overridden off). Defaults true. */
3067
3312
  isToolEnabled(name, agentId) {
@@ -3078,6 +3323,32 @@ var RunHandler = class {
3078
3323
  }));
3079
3324
  }
3080
3325
  /**
3326
+ * Reconcile the WebMCP registrations with the current tool registry. Tools
3327
+ * opt in via `webmcp: true` or `webmcp: { annotations }`; the same
3328
+ * availability rules as `buildFrontendTools` apply. WebMCP tools are
3329
+ * page-level, so unlike the agent tool list they are not filtered by
3330
+ * agentId — a name collision across agentIds keeps the first registration.
3331
+ */
3332
+ syncWebMCP() {
3333
+ const desired = /* @__PURE__ */ new Map();
3334
+ const warnedCollisions = /* @__PURE__ */ new Set();
3335
+ for (const tool of this.tools) {
3336
+ if (!tool.webmcp) continue;
3337
+ if (tool.name === WILDCARD_TOOL_NAME) continue;
3338
+ if (tool.available === false || tool.available === "disabled") continue;
3339
+ if (!this.isToolEnabled(tool.name, tool.agentId)) continue;
3340
+ if (desired.has(tool.name)) {
3341
+ if (!warnedCollisions.has(tool.name)) {
3342
+ warnedCollisions.add(tool.name);
3343
+ _copilotkit_shared.logger.warn(`[CopilotKit] Multiple WebMCP tools share the name '${tool.name}'. Only the first registration is exposed to browser agents.`);
3344
+ }
3345
+ continue;
3346
+ }
3347
+ desired.set(tool.name, tool);
3348
+ }
3349
+ this._webmcpRegistry.sync(desired);
3350
+ }
3351
+ /**
3081
3352
  * Create an agent error subscriber
3082
3353
  */
3083
3354
  createAgentErrorSubscriber(agent) {
@@ -3110,36 +3381,6 @@ var RunHandler = class {
3110
3381
  }
3111
3382
  };
3112
3383
  /**
3113
- * Empty tool schema constant
3114
- */
3115
- const EMPTY_TOOL_SCHEMA = {
3116
- type: "object",
3117
- properties: {}
3118
- };
3119
- /**
3120
- * Create a JSON schema from a tool's parameters
3121
- */
3122
- function createToolSchema(tool) {
3123
- if (!tool.parameters) return { ...EMPTY_TOOL_SCHEMA };
3124
- const rawSchema = (0, _copilotkit_shared.schemaToJsonSchema)(tool.parameters, { zodToJsonSchema: (schema, options) => (0, zod_to_json_schema.zodToJsonSchema)(schema, options) });
3125
- if (!rawSchema || typeof rawSchema !== "object") return { ...EMPTY_TOOL_SCHEMA };
3126
- const { $schema: _$schema, ...schema } = rawSchema;
3127
- if (typeof schema.type !== "string") schema.type = "object";
3128
- if (typeof schema.properties !== "object" || schema.properties === null) schema.properties = {};
3129
- stripAdditionalProperties(schema);
3130
- return schema;
3131
- }
3132
- function stripAdditionalProperties(schema) {
3133
- if (!schema || typeof schema !== "object") return;
3134
- if (Array.isArray(schema)) {
3135
- schema.forEach(stripAdditionalProperties);
3136
- return;
3137
- }
3138
- const record = schema;
3139
- if (record.additionalProperties !== void 0) delete record.additionalProperties;
3140
- for (const value of Object.values(record)) stripAdditionalProperties(value);
3141
- }
3142
- /**
3143
3384
  * Ensures parsed tool arguments are a plain object.
3144
3385
  * Throws for non-object values so the caller's catch block can emit
3145
3386
  * a structured TOOL_ARGUMENT_PARSE_FAILED error.
@@ -4162,8 +4403,24 @@ const selectMemoriesAvailable = createSelector((state) => state.available);
4162
4403
  */
4163
4404
  const selectMemoriesRealtimeStatus = createSelector((state) => state.realtimeStatus);
4164
4405
  const MEMORY_METADATA_EVENT = "memory_metadata";
4406
+ /** Runs a memory request through the injected fetch and aborts on unsubscribe. */
4165
4407
  function memoryFromFetch(input, init) {
4166
- return (0, rxjs_fetch.fromFetch)(input, init);
4408
+ const { selector, fetch: fetchImplementation, signal: outerSignal, ...requestInit } = init;
4409
+ return new rxjs.Observable((subscriber) => {
4410
+ const controller = new AbortController();
4411
+ const abort = () => controller.abort();
4412
+ if (outerSignal?.aborted) controller.abort();
4413
+ else outerSignal?.addEventListener("abort", abort);
4414
+ const subscription = (0, rxjs.from)(fetchImplementation(input, {
4415
+ ...requestInit,
4416
+ signal: controller.signal
4417
+ })).pipe((0, rxjs_operators.mergeMap)((response) => (0, rxjs.from)(selector(response)))).subscribe(subscriber);
4418
+ return () => {
4419
+ outerSignal?.removeEventListener("abort", abort);
4420
+ controller.abort();
4421
+ subscription.unsubscribe();
4422
+ };
4423
+ });
4167
4424
  }
4168
4425
  /**
4169
4426
  * Fetches the memory snapshot and maps it to a success/failure action. Both
@@ -4921,6 +5178,10 @@ var CopilotKitCore = class {
4921
5178
  get suggestions() {
4922
5179
  return this.agentRegistry.suggestions;
4923
5180
  }
5181
+ /** Whether the connected Runtime exposes debug-authorized Learning data. */
5182
+ get inspectorLearning() {
5183
+ return this.agentRegistry.inspectorLearning;
5184
+ }
4924
5185
  /** Trusted, optional metadata advertised by the connected runtime. */
4925
5186
  get inspectorMetadata() {
4926
5187
  return this.agentRegistry.inspectorMetadata;
@@ -5110,12 +5371,12 @@ var CopilotKitCore = class {
5110
5371
  /**
5111
5372
  * Lazily creates, starts, and context-syncs the core-owned memory store on
5112
5373
  * first access, then returns it. Subsequent calls return the existing store.
5113
- * The store is constructed with a bound `globalThis.fetch` and immediately
5114
- * has its runtime context synced from the current connection state.
5374
+ * The store uses the Core Runtime fetch so REST and single-route transports
5375
+ * share the same resource behavior. Its Runtime context is synced at once.
5115
5376
  */
5116
5377
  ensureMemoryStore() {
5117
5378
  if (!this._memoryStore) {
5118
- this._memoryStore = createMemoryStore({ fetch: globalThis.fetch.bind(globalThis) });
5379
+ this._memoryStore = createMemoryStore({ fetch: this.ɵruntimeFetch });
5119
5380
  this._memoryStore.start();
5120
5381
  this.syncMemoryContext();
5121
5382
  }
@@ -6568,6 +6829,7 @@ exports.RunHandler = RunHandler;
6568
6829
  exports.StateManager = StateManager;
6569
6830
  exports.SuggestionEngine = SuggestionEngine;
6570
6831
  exports.ToolCallStatus = ToolCallStatus;
6832
+ exports.WebMCPRegistry = WebMCPRegistry;
6571
6833
  exports.completePartialMarkdown = completePartialMarkdown;
6572
6834
  exports.createActionGroup = createActionGroup;
6573
6835
  exports.createEffect = createEffect;
@@ -6581,6 +6843,7 @@ exports.emitInspectorViewThread = emitInspectorViewThread;
6581
6843
  exports.emitInspectorViewThreadResult = emitInspectorViewThreadResult;
6582
6844
  exports.empty = empty;
6583
6845
  exports.ensureObjectArgs = ensureObjectArgs;
6846
+ exports.getWebMCPModelContext = getWebMCPModelContext;
6584
6847
  exports.isAbortError = isAbortError;
6585
6848
  exports.isInspectorThreadBridgeEnabled = isInspectorThreadBridgeEnabled;
6586
6849
  exports.isRunCompletionAware = isRunCompletionAware;