@copilotkit/core 1.70.0 → 1.70.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2343,6 +2343,137 @@ function isForwardedToClientPlaceholder(content) {
2343
2343
  return normalizeToolResultContent(content) === FORWARDED_TO_CLIENT;
2344
2344
  }
2345
2345
 
2346
+ //#endregion
2347
+ //#region src/core/tool-schema.ts
2348
+ /**
2349
+ * Empty tool schema constant
2350
+ */
2351
+ const EMPTY_TOOL_SCHEMA = {
2352
+ type: "object",
2353
+ properties: {}
2354
+ };
2355
+ /**
2356
+ * Create a JSON schema from a tool's parameters
2357
+ */
2358
+ function createToolSchema(tool) {
2359
+ if (!tool.parameters) return { ...EMPTY_TOOL_SCHEMA };
2360
+ const rawSchema = (0, _copilotkit_shared.schemaToJsonSchema)(tool.parameters, { zodToJsonSchema: (schema, options) => (0, zod_to_json_schema.zodToJsonSchema)(schema, options) });
2361
+ if (!rawSchema || typeof rawSchema !== "object") return { ...EMPTY_TOOL_SCHEMA };
2362
+ const { $schema: _$schema, ...schema } = rawSchema;
2363
+ if (typeof schema.type !== "string") schema.type = "object";
2364
+ if (typeof schema.properties !== "object" || schema.properties === null) schema.properties = {};
2365
+ stripAdditionalProperties(schema);
2366
+ return schema;
2367
+ }
2368
+ /**
2369
+ * Remove every `additionalProperties` key from a JSON schema tree, in place.
2370
+ * Some LLM providers reject schemas that carry it.
2371
+ */
2372
+ function stripAdditionalProperties(schema) {
2373
+ if (!schema || typeof schema !== "object") return;
2374
+ if (Array.isArray(schema)) {
2375
+ schema.forEach(stripAdditionalProperties);
2376
+ return;
2377
+ }
2378
+ const record = schema;
2379
+ if (record.additionalProperties !== void 0) delete record.additionalProperties;
2380
+ for (const value of Object.values(record)) stripAdditionalProperties(value);
2381
+ }
2382
+
2383
+ //#endregion
2384
+ //#region src/core/webmcp.ts
2385
+ /**
2386
+ * Return the page's `document.modelContext`, or null when WebMCP is not
2387
+ * available (SSR, React Native, browsers without the API enabled).
2388
+ */
2389
+ function getWebMCPModelContext() {
2390
+ if (typeof document === "undefined") return null;
2391
+ return document.modelContext ?? null;
2392
+ }
2393
+ /**
2394
+ * Keeps a set of frontend tools registered on the page's WebMCP model context.
2395
+ *
2396
+ * `sync(desired)` reconciles the registrations with the desired set: tools that
2397
+ * are gone (or were re-registered with a new tool object) are unregistered by
2398
+ * aborting their registration signal, and new tools are registered with an
2399
+ * `execute` that delegates to the frontend tool's own handler.
2400
+ *
2401
+ * All WebMCP-API constraints live here so callers only supply the desired set:
2402
+ * a tool without a description is skipped with a warning (the spec rejects
2403
+ * empty descriptions), and failures from `registerTool` (duplicate name,
2404
+ * unsupported context, ...) are warned rather than thrown.
2405
+ */
2406
+ var WebMCPRegistry = class {
2407
+ entries = /* @__PURE__ */ new Map();
2408
+ warnedNames = /* @__PURE__ */ new Set();
2409
+ /** Names currently kept registered on the model context. */
2410
+ get registeredNames() {
2411
+ return [...this.entries.keys()];
2412
+ }
2413
+ /** Reconcile the registered tools with the desired set. */
2414
+ sync(desired) {
2415
+ const modelContext = getWebMCPModelContext();
2416
+ if (!modelContext) return;
2417
+ for (const [name, entry] of [...this.entries]) if (desired.get(name) !== entry.tool) {
2418
+ entry.controller.abort();
2419
+ this.entries.delete(name);
2420
+ }
2421
+ for (const [name, tool] of desired) {
2422
+ if (this.entries.has(name)) continue;
2423
+ if (!tool.description) {
2424
+ this.warnOnce(name, `Skipping WebMCP registration for tool '${name}': WebMCP requires a description.`);
2425
+ continue;
2426
+ }
2427
+ const controller = new AbortController();
2428
+ this.entries.set(name, {
2429
+ tool,
2430
+ controller
2431
+ });
2432
+ modelContext.registerTool(this.buildModelContextTool(tool), { signal: controller.signal }).catch((error) => {
2433
+ if (this.entries.get(name)?.controller !== controller) return;
2434
+ this.entries.delete(name);
2435
+ const message = error instanceof Error ? error.message : String(error);
2436
+ this.warnOnce(name, `WebMCP registration failed for tool '${name}': ${message}`);
2437
+ });
2438
+ }
2439
+ }
2440
+ /**
2441
+ * Build the WebMCP model-context tool for a frontend tool. The `execute`
2442
+ * callback delegates to the frontend tool's own handler, so a browser agent
2443
+ * call runs the same application code as an agent call.
2444
+ */
2445
+ buildModelContextTool(tool) {
2446
+ const annotations = typeof tool.webmcp === "object" ? tool.webmcp.annotations : void 0;
2447
+ return {
2448
+ name: tool.name,
2449
+ description: tool.description,
2450
+ inputSchema: createToolSchema(tool),
2451
+ execute: async (args, options) => {
2452
+ if (!tool.handler) return "";
2453
+ const toolCall = {
2454
+ id: (0, _copilotkit_shared.randomUUID)(),
2455
+ type: "function",
2456
+ function: {
2457
+ name: tool.name,
2458
+ arguments: JSON.stringify(args ?? {})
2459
+ }
2460
+ };
2461
+ return await tool.handler(args, {
2462
+ toolCall,
2463
+ signal: options?.signal
2464
+ });
2465
+ },
2466
+ ...annotations ? { annotations } : {}
2467
+ };
2468
+ }
2469
+ /** Log `message` once per tool name. Repeated syncs must not spam the log. */
2470
+ warnOnce(name, message) {
2471
+ if (this.warnedNames.has(name)) return;
2472
+ this.warnedNames.add(name);
2473
+ _copilotkit_shared.logger.warn(`[CopilotKit] ${message}`);
2474
+ }
2475
+ };
2476
+
2346
2477
  //#endregion
2347
2478
  //#region src/core/run-handler.ts
2348
2479
  /**
@@ -2425,6 +2556,12 @@ var RunHandler = class {
2425
2556
  */
2426
2557
  _runDepth = 0;
2427
2558
  /**
2559
+ * Keeps tools that opt in via `webmcp` registered on the page's WebMCP model
2560
+ * context (`document.modelContext`), in addition to the normal agent
2561
+ * registration. Reconciled on every tool registry mutation.
2562
+ */
2563
+ _webmcpRegistry = new WebMCPRegistry();
2564
+ /**
2428
2565
  * Tracks the threadId of the most recent `connectAgent` call so we
2429
2566
  * can distinguish a fresh thread restore (different threadId than
2430
2567
  * last time — chat is rebuilding state from scratch, must clear
@@ -2495,6 +2632,7 @@ var RunHandler = class {
2495
2632
  initialize(tools) {
2496
2633
  this._propTools = [...tools];
2497
2634
  this._cachedMergedTools = null;
2635
+ this.syncWebMCP();
2498
2636
  }
2499
2637
  /**
2500
2638
  * Add a tool to the registry. Survives provider re-syncs ({@link setTools}),
@@ -2508,6 +2646,7 @@ var RunHandler = class {
2508
2646
  }
2509
2647
  this._hookTools.set(key, tool);
2510
2648
  this._cachedMergedTools = null;
2649
+ this.syncWebMCP();
2511
2650
  }
2512
2651
  /**
2513
2652
  * Remove a tool by name and optionally by agentId
@@ -2519,6 +2658,7 @@ var RunHandler = class {
2519
2658
  return !(tool.name === id && !tool.agentId);
2520
2659
  });
2521
2660
  this._cachedMergedTools = null;
2661
+ this.syncWebMCP();
2522
2662
  }
2523
2663
  /**
2524
2664
  * Get a tool by name and optionally by agentId.
@@ -2541,6 +2681,7 @@ var RunHandler = class {
2541
2681
  setTools(tools) {
2542
2682
  this._propTools = [...tools];
2543
2683
  this._cachedMergedTools = null;
2684
+ this.syncWebMCP();
2544
2685
  }
2545
2686
  /**
2546
2687
  * Return the registered A2UI catalog components (readonly).
@@ -3062,6 +3203,7 @@ var RunHandler = class {
3062
3203
  const key = this.capabilityKey(name, agentId);
3063
3204
  if (enabled) this._disabledToolKeys.delete(key);
3064
3205
  else this._disabledToolKeys.add(key);
3206
+ this.syncWebMCP();
3065
3207
  }
3066
3208
  /** Whether a tool is currently enabled (not overridden off). Defaults true. */
3067
3209
  isToolEnabled(name, agentId) {
@@ -3078,6 +3220,32 @@ var RunHandler = class {
3078
3220
  }));
3079
3221
  }
3080
3222
  /**
3223
+ * Reconcile the WebMCP registrations with the current tool registry. Tools
3224
+ * opt in via `webmcp: true` or `webmcp: { annotations }`; the same
3225
+ * availability rules as `buildFrontendTools` apply. WebMCP tools are
3226
+ * page-level, so unlike the agent tool list they are not filtered by
3227
+ * agentId — a name collision across agentIds keeps the first registration.
3228
+ */
3229
+ syncWebMCP() {
3230
+ const desired = /* @__PURE__ */ new Map();
3231
+ const warnedCollisions = /* @__PURE__ */ new Set();
3232
+ for (const tool of this.tools) {
3233
+ if (!tool.webmcp) continue;
3234
+ if (tool.name === WILDCARD_TOOL_NAME) continue;
3235
+ if (tool.available === false || tool.available === "disabled") continue;
3236
+ if (!this.isToolEnabled(tool.name, tool.agentId)) continue;
3237
+ if (desired.has(tool.name)) {
3238
+ if (!warnedCollisions.has(tool.name)) {
3239
+ warnedCollisions.add(tool.name);
3240
+ _copilotkit_shared.logger.warn(`[CopilotKit] Multiple WebMCP tools share the name '${tool.name}'. Only the first registration is exposed to browser agents.`);
3241
+ }
3242
+ continue;
3243
+ }
3244
+ desired.set(tool.name, tool);
3245
+ }
3246
+ this._webmcpRegistry.sync(desired);
3247
+ }
3248
+ /**
3081
3249
  * Create an agent error subscriber
3082
3250
  */
3083
3251
  createAgentErrorSubscriber(agent) {
@@ -3110,36 +3278,6 @@ var RunHandler = class {
3110
3278
  }
3111
3279
  };
3112
3280
  /**
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
3281
  * Ensures parsed tool arguments are a plain object.
3144
3282
  * Throws for non-object values so the caller's catch block can emit
3145
3283
  * a structured TOOL_ARGUMENT_PARSE_FAILED error.
@@ -6568,6 +6706,7 @@ exports.RunHandler = RunHandler;
6568
6706
  exports.StateManager = StateManager;
6569
6707
  exports.SuggestionEngine = SuggestionEngine;
6570
6708
  exports.ToolCallStatus = ToolCallStatus;
6709
+ exports.WebMCPRegistry = WebMCPRegistry;
6571
6710
  exports.completePartialMarkdown = completePartialMarkdown;
6572
6711
  exports.createActionGroup = createActionGroup;
6573
6712
  exports.createEffect = createEffect;
@@ -6581,6 +6720,7 @@ exports.emitInspectorViewThread = emitInspectorViewThread;
6581
6720
  exports.emitInspectorViewThreadResult = emitInspectorViewThreadResult;
6582
6721
  exports.empty = empty;
6583
6722
  exports.ensureObjectArgs = ensureObjectArgs;
6723
+ exports.getWebMCPModelContext = getWebMCPModelContext;
6584
6724
  exports.isAbortError = isAbortError;
6585
6725
  exports.isInspectorThreadBridgeEnabled = isInspectorThreadBridgeEnabled;
6586
6726
  exports.isRunCompletionAware = isRunCompletionAware;