test/codex-dynamic-tool-names.test.js
Rama main · réplica de lectura del repositorio original.
// Codex dynamic tool aliases against the installed app-server validation rules.
// The fake app-server below never spawns Codex or contacts a provider.
var test = require("node:test");
var assert = require("node:assert/strict");
var crypto = require("node:crypto");
var codexModule = require("../lib/yoke/adapters/codex");
var toolNames = require("../lib/yoke/codex-dynamic-tool-names");
// Port of codex-rs/app-server thread_processor.rs validate_dynamic_tools
// (rust-v0.162.1, the installed @openai/codex version) for function specs.
function validateLikeCodex(tools) {
var seen = new Set();
tools.forEach(function (tool) {
var name = tool.name.trim();
if (!name) throw new Error("dynamic tool name must not be empty");
if (name !== tool.name) throw new Error("dynamic tool name has leading/trailing whitespace: " + tool.name);
if (!/^[a-zA-Z0-9_-]+$/.test(name)) throw new Error("dynamic tool name must match ^[a-zA-Z0-9_-]+$: " + name);
if (name.length > 128) throw new Error("dynamic tool name must be at most 128 characters: " + name);
if (name === "mcp" || name.indexOf("mcp__") === 0) throw new Error("dynamic tool name is reserved: " + name);
if (seen.has(name)) throw new Error("duplicate dynamic tool name: " + name);
seen.add(name);
if (tool.type !== "function" || !tool.inputSchema || typeof tool.inputSchema !== "object") throw new Error("invalid spec: " + name);
});
}
var DISCOVER = "mcp__clay-mcp-connections__discover_mcp_connections";
var PROPOSE = "mcp__clay-mcp-connections__propose_mcp_import";
function def(name) {
return { name: name, description: "desc " + name, inputSchema: { type: "object", properties: {} } };
}
function fakeServer(script) {
var server = { calls: [], responses: [], entry: null };
server.started = true;
server.addHandler = function (fn) { server.entry = { threadId: null, fn: fn }; return server.entry; };
server.removeHandler = function () {};
server.respond = function (id, result) { server.responses.push({ id: id, result: result }); };
server.send = function (method, params) {
server.calls.push({ method: method, params: params });
if (method === "thread/start") return Promise.resolve({ thread: { id: "thread-alias" } });
if (method === "thread/resume") return Promise.resolve({ thread: { id: params.threadId } });
if (method === "turn/start") {
setImmediate(function () {
(script || []).forEach(function (message) { server.entry.fn(message); });
setImmediate(function () { server.entry.fn({ method: "turn/completed", params: { threadId: "thread-alias" } }); });
});
}
return Promise.resolve({});
};
return server;
}
async function run(server, opts) {
var permissions = [], executed = [], events = [];
var handle = codexModule.contractTestKit.createQueryHandle(server, Object.assign({
cwd: process.cwd(), model: "gpt-test", abortController: new AbortController(),
canUseTool: function (name) { permissions.push(name); return Promise.resolve({ behavior: "allow" }); },
callDynamicTool: function (name, args) { executed.push({ name: name, args: args }); return Promise.resolve({ content: [{ type: "text", text: "ok " + name }] }); },
}, opts));
handle.pushMessage("Use the tools");
for await (var event of handle) {
events.push(event);
if (event.yokeType === "result" || event.yokeType === "error") break;
}
handle.close();
await new Promise(function (resolve) { setImmediate(resolve); });
return { permissions: permissions, executed: executed, events: events };
}
function toolCall(id, tool, extra) {
return { id: id, method: "item/tool/call", params: Object.assign({ threadId: "thread-alias", callId: "call-" + id, tool: tool, arguments: { probe: id } }, extra || {}) };
}
test("fresh Codex thread/start advertises a catalog the installed validator accepts", async function () {
var longName = "mcp__" + "s".repeat(90) + "__" + "t".repeat(90);
var originals = [DISCOVER, PROPOSE, "write_note", "send_to_partner", "mcp", "mcp__my.server__tool name", longName,
toolNames.transportName(DISCOVER), "clay__plain_lookalike"];
assert.throws(function () { validateLikeCodex(originals.map(function (name) { return { type: "function", name: name, inputSchema: {} }; })); },
/dynamic tool name is reserved: mcp__clay-mcp-connections__discover_mcp_connections/, "the baseline names reproduce the reported failure");
var server = fakeServer([]);
await run(server, { dynamicTools: originals.map(def) });
var start = server.calls.find(function (call) { return call.method === "thread/start"; });
validateLikeCodex(start.params.dynamicTools);
var names = start.params.dynamicTools.map(function (tool) { return tool.name; });
assert.equal(names.length, originals.length);
assert.equal(names[2], "write_note");
assert.equal(names[3], "send_to_partner");
assert.match(names[0], /^clay__clay-mcp-connections__discover_mcp_connections__[0-9a-f]{16}$/);
assert.notEqual(names[7], names[0], "an original that spells another tool's alias is itself re-aliased");
assert.equal(start.params.dynamicTools[0].description, "desc " + DISCOVER);
});
test("Codex resolves aliases to original names for permission, dispatch and activity", async function () {
var alias = toolNames.transportName(DISCOVER);
var server = fakeServer([
{ method: "item/started", params: { item: { type: "dynamicToolCall", id: "item-1", tool: alias, arguments: { probe: 1 } } } },
toolCall(51, alias),
]);
var result = await run(server, { dynamicTools: [def(DISCOVER), def("write_note")] });
assert.deepEqual(result.permissions, [DISCOVER]);
assert.deepEqual(result.executed, [{ name: DISCOVER, args: { probe: 51 } }]);
var started = result.events.filter(function (event) { return event.yokeType === "tool_start" || event.yokeType === "tool_executing"; });
assert.deepEqual(started.map(function (event) { return event.toolName; }), [DISCOVER, DISCOVER]);
assert.deepEqual(server.responses, [{ id: 51, result: { contentItems: [{ type: "inputText", text: "ok " + DISCOVER }], success: true } }]);
});
test("Codex refuses raw reserved, spoofed, namespaced and unadvertised aliases before permission", async function () {
var forged = toolNames.transportName(DISCOVER).replace(/[0-9a-f]{16}$/, "0000000000000000");
var server = fakeServer([
toolCall(61, DISCOVER),
toolCall(62, forged),
toolCall(63, toolNames.transportName(DISCOVER), { namespace: "clay" }),
toolCall(64, toolNames.transportName("mcp__other__tool")),
]);
var result = await run(server, { dynamicTools: [def(DISCOVER)] });
assert.deepEqual(result.permissions, []);
assert.deepEqual(result.executed, []);
assert.deepEqual(server.responses.map(function (response) { return response.id; }), [61, 62, 63, 64]);
server.responses.forEach(function (response) {
assert.equal(response.result.success, false);
assert.match(response.result.contentItems[0].text, /^Error: /);
});
assert.match(server.responses[2].result.contentItems[0].text, /namespaced/);
});
test("a resumed thread's persisted aliases resolve against a rebuilt catalog and stale ones are refused", async function () {
var discoverAlias = toolNames.transportName(DISCOVER);
var proposeAlias = toolNames.transportName(PROPOSE);
var server = fakeServer([toolCall(71, discoverAlias), toolCall(72, proposeAlias), toolCall(73, "write_note")]);
// Rebuilt in a different order, with the propose tool no longer present.
var result = await run(server, { resumeSessionId: "thread-alias", dynamicTools: [def("write_note"), def(DISCOVER)] });
var resume = server.calls.find(function (call) { return call.method === "thread/resume"; });
assert.equal(Object.prototype.hasOwnProperty.call(resume.params, "dynamicTools"), false);
assert.deepEqual(result.executed.map(function (call) { return call.name; }), [DISCOVER, "write_note"]);
assert.deepEqual(result.permissions, [DISCOVER, "mcp__clay-notes__write_note"]);
var stale = server.responses.find(function (response) { return response.id === 72; });
assert.equal(stale.result.success, false);
assert.match(stale.result.contentItems[0].text, /not in this conversation's current catalog/);
});
test("identical originals are advertised once and a true alias collision fails closed before thread/start", async function () {
var catalog = toolNames.createCatalog([def(DISCOVER), def(DISCOVER), def("write_note")]);
assert.deepEqual(catalog.advertised.map(function (entry) { return entry.name; }), [toolNames.transportName(DISCOVER), "write_note"]);
var original = crypto.createHash;
crypto.createHash = function () {
return { update: function () { return { digest: function () { return "f".repeat(64); } }; } };
};
var server = fakeServer([]);
var result;
try {
result = await run(server, { dynamicTools: [def("mcp__a_b"), def("mcp__a.b")] });
} finally {
crypto.createHash = original;
}
assert.equal(server.calls.some(function (call) { return call.method === "thread/start"; }), false);
var error = result.events.find(function (event) { return event.yokeType === "error"; });
assert.match(error.text, /Codex dynamic tool names collide: mcp__a_b and mcp__a\.b/);
});