@skydiveai/pi-extensions 0.1.251-beta.4 → 0.1.251-beta.6

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.
Files changed (2) hide show
  1. package/dist/index.mjs +43 -3
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1169,6 +1169,34 @@ function estimateToolTokens(tool) {
1169
1169
  return Math.ceil(chars / 4);
1170
1170
  }
1171
1171
  //#endregion
1172
+ //#region src/extensions/mcp/tool-name.ts
1173
+ const MAX_LENGTH = 128;
1174
+ /**
1175
+ * Deterministically coerce `raw` into the Anthropic tool-name alphabet.
1176
+ * Same input always yields the same output, so a tool keeps ONE stable
1177
+ * registered name across turns and reconciles (history stays valid).
1178
+ *
1179
+ * - every invalid char run collapses to a single `_`
1180
+ * - the result trims to 128 chars
1181
+ * - a degenerate all-underscore result becomes `tool`
1182
+ */
1183
+ function sanitizeToolName(raw) {
1184
+ const stripped = raw.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, MAX_LENGTH).replace(/_+$/, "");
1185
+ return stripped === "" ? "tool" : stripped;
1186
+ }
1187
+ /**
1188
+ * First free `name`, `name_2`, `name_3`, ... within the 128-char budget.
1189
+ * Suffixes come out of the kept base so the whole string stays <= 128.
1190
+ */
1191
+ function uniqueSanitizedToolName(base, used) {
1192
+ if (!used.has(base)) return base;
1193
+ for (let n = 2;; n++) {
1194
+ const suffix = `_${n}`;
1195
+ const candidate = base.slice(0, MAX_LENGTH - suffix.length) + suffix;
1196
+ if (!used.has(candidate)) return candidate;
1197
+ }
1198
+ }
1199
+ //#endregion
1172
1200
  //#region src/extensions/mcp/mcp-config.ts
1173
1201
  /**
1174
1202
  * mcp.config.json schema + loader. Split out from the extension so it has
@@ -1267,8 +1295,17 @@ async function readConfigMtimeMs(configPath) {
1267
1295
  throw err;
1268
1296
  }
1269
1297
  }
1270
- function makeToolName(serverId, toolName) {
1271
- return `mcp_${serverId}_${toolName}`;
1298
+ /**
1299
+ * The registered tool name must satisfy Anthropic's tools[].name pattern
1300
+ * (`^[a-zA-Z0-9_-]{1,128}$`) or the WHOLE Messages request hard-400s — real
1301
+ * servers emit names that fail it (e.g. granola's `granola.meetings.list
1302
+ * (v2)`). Sanitize here at registration time; execution still calls the
1303
+ * server with the ORIGINAL name, and model tool_use round-trips on the
1304
+ * sanitized one, so no reverse mapping is needed. `used` dedupes collisions
1305
+ * (two distinct raw names can sanitize to the same string).
1306
+ */
1307
+ function makeToolName(serverId, toolName, used) {
1308
+ return uniqueSanitizedToolName(sanitizeToolName(`mcp_${serverId}_${toolName}`), used);
1272
1309
  }
1273
1310
  function toMcpArguments(params) {
1274
1311
  const out = {};
@@ -1372,6 +1409,7 @@ function buildToolPromptSnippet({ description, inputSchema, serverId }) {
1372
1409
  var McpExtension = class {
1373
1410
  mcpClients = /* @__PURE__ */ new Map();
1374
1411
  registeredMcpToolNames = /* @__PURE__ */ new Set();
1412
+ usedSanitizedToolNames = /* @__PURE__ */ new Set();
1375
1413
  lastConfigMtimeMs = 0;
1376
1414
  pendingMcpUpdate = null;
1377
1415
  /** Non-consuming peek used by `shouldStopAfterTurn`. */
@@ -1385,7 +1423,7 @@ var McpExtension = class {
1385
1423
  return update;
1386
1424
  }
1387
1425
  registerMcpTool({ pi, serverId, client, tool }) {
1388
- const name = makeToolName(serverId, tool.name);
1426
+ const name = makeToolName(serverId, tool.name, this.usedSanitizedToolNames);
1389
1427
  const parameters = Type.Unsafe(tool.inputSchema);
1390
1428
  const description = tool.description?.trim() ?? "";
1391
1429
  const promptSnippet = buildToolPromptSnippet({
@@ -1429,6 +1467,7 @@ var McpExtension = class {
1429
1467
  }
1430
1468
  }));
1431
1469
  this.registeredMcpToolNames.add(name);
1470
+ this.usedSanitizedToolNames.add(name);
1432
1471
  }
1433
1472
  async reconcile({ pi, configPath, connectTimeoutMs, contextWindow }) {
1434
1473
  let config;
@@ -1454,6 +1493,7 @@ var McpExtension = class {
1454
1493
  };
1455
1494
  const desiredIds = new Set(Object.keys(config.servers));
1456
1495
  this.registeredMcpToolNames.clear();
1496
+ this.usedSanitizedToolNames.clear();
1457
1497
  for (const [id, existing] of [...this.mcpClients.entries()]) if (!desiredIds.has(id)) {
1458
1498
  await closeConnected(existing);
1459
1499
  this.mcpClients.delete(id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skydiveai/pi-extensions",
3
- "version": "0.1.251-beta.4",
3
+ "version": "0.1.251-beta.6",
4
4
  "homepage": "https://skydive.com",
5
5
  "license": "MIT",
6
6
  "author": "Create, Inc.",