@skydiveai/pi-extensions 0.1.251-beta.5 → 0.1.251-beta.7
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 +43 -3
- 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
|
-
|
|
1271
|
-
|
|
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);
|