test/codex-dynamic-tools.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert");
var codexModule = require("../lib/yoke/adapters/codex");
test("Codex canonicalizes every note operation to the exact Clay Notes namespace", function () {
var canonical = codexModule.contractTestKit.canonicalDynamicPermissionToolName;
var tools = ["list_notes", "write_note", "close_note", "reopen_note", "remove_note"];
for (var i = 0; i < tools.length; i++) {
assert.strictEqual(canonical(tools[i]), "mcp__clay-notes__" + tools[i]);
}
assert.strictEqual(canonical("close_note_extra"), "close_note_extra");
assert.strictEqual(canonical("unrelated_tool"), "unrelated_tool");
assert.strictEqual(canonical("constructor"), "constructor");
assert.strictEqual(canonical("toString"), "toString");
assert.strictEqual(canonical("__proto__"), "__proto__");
});
test("Codex canonicalizes exact document presentation tools without trusting lookalikes", function () {
var canonical = codexModule.contractTestKit.canonicalDynamicPermissionToolName;
assert.strictEqual(canonical("present_markdown_edit"), "mcp__clay-documents__present_markdown_edit");
assert.strictEqual(canonical("present_wireframe"), "mcp__clay-documents__present_wireframe");
assert.strictEqual(canonical("present_wireframe_extra"), "present_wireframe_extra");
assert.strictEqual(canonical("other_present_wireframe"), "other_present_wireframe");
});
test("Codex executes present_wireframe after requesting its canonical permission identity", async function () {
var handlerEntry = null;
var responses = [];
var permissionNames = [];
var executed = [];
var server = {
started: true,
addHandler: function (fn) { handlerEntry = { threadId: null, fn: fn }; return handlerEntry; },
removeHandler: function () {},
respond: function (id, result) { responses.push({ id: id, result: result }); },
send: function (method) {
if (method === "thread/start") return Promise.resolve({ thread: { id: "thread-wireframe" } });
if (method === "turn/start") {
setImmediate(function () {
handlerEntry.fn({
id: 42,
method: "item/tool/call",
params: { threadId: "thread-wireframe", callId: "call-wireframe", tool: "present_wireframe", arguments: { id: "settings", source: "@startsalt\n{ [Save] }\n@endsalt" } },
});
setImmediate(function () {
handlerEntry.fn({ method: "turn/completed", params: { threadId: "thread-wireframe" } });
});
});
}
return Promise.resolve({});
},
};
var handle = codexModule.contractTestKit.createQueryHandle(server, {
cwd: process.cwd(),
model: "gpt-test",
dynamicTools: [{ name: "present_wireframe", description: "Present", inputSchema: { type: "object" } }],
canUseTool: function (name) {
permissionNames.push(name);
return Promise.resolve({ behavior: name === "mcp__clay-documents__present_wireframe" ? "allow" : "deny" });
},
callDynamicTool: function (name, args) {
executed.push({ name: name, args: args });
return Promise.resolve({ content: [{ type: "text", text: "Presented" }] });
},
abortController: new AbortController(),
});
handle.pushMessage("Show the wireframe");
for await (var event of handle) {
if (event.yokeType === "result") break;
}
handle.close();
await new Promise(function (resolve) { setImmediate(resolve); });
assert.deepStrictEqual(permissionNames, ["mcp__clay-documents__present_wireframe"]);
assert.strictEqual(executed.length, 1);
assert.strictEqual(executed[0].name, "present_wireframe");
assert.strictEqual(executed[0].args.id, "settings");
assert.deepStrictEqual(responses, [{
id: 42,
result: { contentItems: [{ type: "inputText", text: "Presented" }], success: true },
}]);
});
test("Codex registers and executes session-bound dynamic tools", async function () {
var calls = [];
var responses = [];
var handlerEntry = null;
var server = {
started: true,
addHandler: function (fn) {
handlerEntry = { threadId: null, fn: fn };
return handlerEntry;
},
removeHandler: function () {},
respond: function (id, result) { responses.push({ id: id, result: result }); },
send: function (method, params) {
calls.push({ method: method, params: params });
if (method === "thread/start") return Promise.resolve({ thread: { id: "thread-pair" } });
if (method === "turn/start") {
setImmediate(function () {
handlerEntry.fn({
id: 41,
method: "item/tool/call",
params: { threadId: "thread-pair", callId: "call-1", tool: "send_to_partner", arguments: { message: "Build it" } },
});
setImmediate(function () {
handlerEntry.fn({ method: "turn/completed", params: { threadId: "thread-pair" } });
});
});
return Promise.resolve({});
}
return Promise.resolve({});
},
};
var receivedArgs = null;
var handle = codexModule.contractTestKit.createQueryHandle(server, {
cwd: process.cwd(),
model: "gpt-test",
systemPrompt: "Base instructions",
appendSystemPrompt: "You are the Driver",
dynamicTools: [{
name: "send_to_partner",
description: "Delegate work",
inputSchema: { type: "object", properties: { message: { type: "string" } } },
}],
callDynamicTool: function (name, args) {
assert.strictEqual(name, "send_to_partner");
receivedArgs = args;
return Promise.resolve({ content: [{ type: "text", text: "Worker complete" }] });
},
abortController: new AbortController(),
});
await handle.setModel("gpt-5.6-sol");
handle.pushMessage("Implement the feature");
var events = [];
for await (var event of handle) {
events.push(event);
if (event.yokeType === "result") break;
}
handle.close();
var startCall = calls.find(function (call) { return call.method === "thread/start"; });
assert.strictEqual(startCall.params.dynamicTools[0].name, "send_to_partner");
var turnCall = calls.find(function (call) { return call.method === "turn/start"; });
assert.strictEqual(turnCall.params.model, "gpt-5.6-sol");
assert.match(turnCall.params.input[0].text, /Base instructions/);
assert.match(turnCall.params.input[0].text, /You are the Driver/);
assert.deepStrictEqual(receivedArgs, { message: "Build it" });
assert.deepStrictEqual(events[0], { yokeType: "session_started", sessionId: "thread-pair" });
assert.deepStrictEqual(responses, [{
id: 41,
result: { contentItems: [{ type: "inputText", text: "Worker complete" }], success: true },
}]);
});
test("Codex does not send unsupported dynamicTools on thread/resume", async function () {
var calls = [];
var server = {
started: true,
addHandler: function () { return { threadId: null, fn: function () {} }; },
removeHandler: function () {},
send: function (method, params) {
calls.push({ method: method, params: params });
if (method === "thread/resume") return Promise.resolve({ thread: { id: "resumed" } });
if (method === "turn/start") return Promise.resolve({});
return Promise.resolve({});
},
};
var handle = codexModule.contractTestKit.createQueryHandle(server, {
cwd: process.cwd(), model: "gpt-test", resumeSessionId: "resumed",
dynamicTools: [{ name: "new_tool", inputSchema: { type: "object" } }],
abortController: new AbortController(),
});
handle.pushMessage("Continue");
await new Promise(function (resolve) { setImmediate(resolve); });
handle.close();
var resume = calls.filter(function (call) { return call.method === "thread/resume"; })[0];
assert.ok(resume);
assert.equal(Object.prototype.hasOwnProperty.call(resume.params, "dynamicTools"), false,
"the installed protocol does not define dynamicTools for thread/resume");
});
test("Codex recovers a stale resumed catalog through supported query MCP config", async function () {
var calls = [];
var server = {
started: true,
addHandler: function () { return { threadId: null, fn: function () {} }; },
removeHandler: function () {},
send: function (method, params) {
calls.push({ method: method, params: params });
if (method === "thread/resume") return Promise.resolve({ thread: { id: "resumed" } });
return Promise.resolve({});
},
};
var handle = codexModule.contractTestKit.createQueryHandle(server, {
cwd: process.cwd(), model: "gpt-test", resumeSessionId: "resumed",
sessionMcpServer: { name: "clay-session-tools", command: process.execPath, args: ["bridge.js", "--session", "7", "--query-generation", "3"], env: { CLAY_AUTH_TOKEN: "scoped" } },
abortController: new AbortController(),
});
handle.pushMessage("Continue");
await new Promise(function (resolve) { setImmediate(resolve); });
handle.close();
var resume = calls.filter(function (call) { return call.method === "thread/resume"; })[0];
assert.deepEqual(resume.params.config, { mcp_servers: {
"clay-session-tools": { command: process.execPath, args: ["bridge.js", "--session", "7", "--query-generation", "3"], env: { CLAY_AUTH_TOKEN: "scoped" } },
} });
assert.equal(Object.prototype.hasOwnProperty.call(resume.params, "dynamicTools"), false);
});
test("Codex resumes stale session tools from a warmed idle runtime and reaps it after abort", async function () {
var servers = [];
function fakeServer(serverOpts) {
var server = { started: false, stopped: 0, calls: [], handler: null, options: serverOpts };
server.start = function() { server.started = true; return Promise.resolve(); };
server.stop = function() { server.stopped++; server.started = false; };
server.addHandler = function(fn) { server.handler = fn; return { threadId: null, fn: fn }; };
server.removeHandler = function() {};
server.notify = function() {};
server.respond = function() {};
server.send = function(method, params) {
server.calls.push({ method: method, params: params });
if (method === "thread/resume") return Promise.resolve({ thread: { id: "saved-thread" } });
if (method === "thread/start") return Promise.resolve({ thread: { id: "fresh-thread" } });
return Promise.resolve({ data: [] });
};
servers.push(server);
return server;
}
var adapter = codexModule.createCodexAdapter({
cwd: process.cwd(), createAppServer: fakeServer,
adapterOptions: { CODEX: { config: { mcp_servers: {
"external-server": { command: "external-mcp", args: ["--preserve"] },
} } } },
});
await adapter.init({});
await Promise.resolve();
var handle = await adapter.createQuery({
cwd: process.cwd(), resumeSessionId: "saved-thread", model: "gpt-test",
sessionMcpServer: { name: "clay-session-tools", command: process.execPath, args: ["bridge.js", "--session", "7", "--query-generation", "3"] },
});
handle.pushMessage("Continue");
await new Promise(function(resolve) { setImmediate(resolve); });
assert.equal(servers.length, 2, "recovery starts a second, isolated app-server only after releasing the shared one");
assert.equal(servers[0].stopped, 1, "the idle shared runtime is released before resume");
assert.equal(servers[0].calls.some(function(call) { return call.method === "thread/resume"; }), false);
assert.deepEqual(servers[1].options.config.mcp_servers["external-server"], { command: "external-mcp", args: ["--preserve"] }, "configured external MCP servers survive recovery initialization");
assert.ok(servers[1].options.config.mcp_servers["clay-tools"], "the regular Clay bridge survives recovery initialization");
assert.deepEqual(servers[1].options.config.mcp_servers["clay-session-tools"], {
command: process.execPath, args: ["bridge.js", "--session", "7", "--query-generation", "3"], env: {},
}, "the exact recovery bridge is added alongside existing servers");
assert.equal(servers[1].calls.some(function(call) { return call.method === "thread/resume" && call.params.config; }), false, "isolated resume does not replace its initialized MCP server map");
handle.abort();
await new Promise(function(resolve) { setImmediate(resolve); });
assert.equal(servers[1].stopped, 1, "the isolated recovery runtime is reaped after abort");
});
test("Codex waits for isolated recovery cleanup before resuming the same thread again", async function () {
var EventEmitter = require("node:events").EventEmitter;
var servers = [];
function fakeServer() {
var isRecoveryRuntime = servers.length > 0;
var server = { started: false, stopped: 0, calls: [], handler: null };
if (isRecoveryRuntime) {
server.proc = new EventEmitter();
server.proc.exitCode = null;
server.proc.signalCode = null;
server.proc.kill = function() {};
}
server.start = function() { server.started = true; return Promise.resolve(); };
server.stop = function() {
server.stopped++;
server.started = false;
if (server.proc) {
setTimeout(function() {
server.proc.exitCode = 0;
server.proc.emit("exit");
}, 30);
}
};
server.addHandler = function(fn) { server.handler = fn; return { threadId: null, fn: fn }; };
server.removeHandler = function() {}; server.notify = function() {}; server.respond = function() {};
server.send = function(method, params) {
server.calls.push({ method: method, params: params });
if (method === "thread/resume") return Promise.resolve({ thread: { id: "saved-thread" } });
return Promise.resolve({ data: [] });
};
servers.push(server);
return server;
}
var adapter = codexModule.createCodexAdapter({ cwd: process.cwd(), createAppServer: fakeServer });
await adapter.init({});
var first = await adapter.createQuery({
cwd: process.cwd(), resumeSessionId: "saved-thread", model: "gpt-test",
sessionMcpServer: { name: "clay-session-tools", command: process.execPath, args: ["bridge.js"] },
});
first.abort();
var secondPromise = adapter.createQuery({
cwd: process.cwd(), resumeSessionId: "saved-thread", model: "gpt-test",
sessionMcpServer: { name: "clay-session-tools", command: process.execPath, args: ["bridge.js"] },
});
await new Promise(function(resolve) { setTimeout(resolve, 10); });
assert.equal(servers.length, 2, "the next recovery does not start while the prior writer is still exiting");
var second = await secondPromise;
assert.equal(servers.length, 3, "the next recovery starts only after prior cleanup completes");
second.abort();
await adapter.shutdown();
assert.equal(servers[2].stopped, 1, "parent shutdown awaits and reaps the retained recovery runtime once");
});
test("Codex permits recovery for a confirmed unloaded thread beside an active sibling", async function () {
var servers = [];
function fakeServer() {
var server = { started: false, stopped: 0, calls: [], handler: null };
server.start = function() { server.started = true; return Promise.resolve(); };
server.stop = function() { server.stopped++; server.started = false; };
server.addHandler = function(fn) { server.handler = fn; return { threadId: null, fn: fn }; };
server.removeHandler = function() {}; server.notify = function() {}; server.respond = function() {};
server.send = function(method, params) {
server.calls.push({ method: method, params: params });
if (method === "thread/start") return Promise.resolve({ thread: { id: "fresh-thread" } });
return Promise.resolve({ data: [] });
};
servers.push(server);
return server;
}
var adapter = codexModule.createCodexAdapter({ cwd: process.cwd(), createAppServer: fakeServer });
var fresh = await adapter.createQuery({ cwd: process.cwd(), model: "gpt-test" });
fresh.pushMessage("Keep the shared runtime active");
await new Promise(function(resolve) { setImmediate(resolve); });
var recovery = await adapter.createQuery({
cwd: process.cwd(), resumeSessionId: "saved-thread",
sessionMcpServer: { name: "clay-session-tools", command: process.execPath, args: ["bridge.js"] },
});
assert.equal(servers.length, 2, "an isolated writer starts only for the confirmed absent target");
assert.equal(servers[0].stopped, 0, "the active sibling is never terminated for recovery");
fresh.abort();
recovery.abort();
await adapter.shutdown();
});
test("Codex reports that resumed query-bound tools depend on the persisted thread catalog", function () {
var adapter = codexModule.createCodexAdapter({ cwd: process.cwd() });
assert.deepStrictEqual(adapter.queryBoundToolCapability, {
mode: "dynamic",
resume: "session-mcp-recovery",
});
});
test("Codex context accounting separates verified last-turn input from cumulative and cache totals", async function () {
var kit = codexModule.contractTestKit;
var state = kit.createEventState("gpt-test");
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
tokenUsage: { lastTurn: { inputTokens: 100, cachedInputTokens: 900, outputTokens: 5 }, total: { inputTokens: 99999 }, modelContextWindow: 1000 },
} }, state);
assert.equal(state.lastInputTokens, 100);
assert.deepEqual(state.currentContextUsage, { input_tokens: 100, contextWindow: 1000 });
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
tokenUsage: { total: { inputTokens: 99999 }, modelContextWindow: 1000 },
} }, state);
assert.equal(state.lastInputTokens, null, "a total-only update clears stale turn usage");
assert.equal(state.currentContextUsage, null);
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
tokenUsage: { lastTurn: { inputTokens: 5000 }, modelContextWindow: 1000 },
} }, state);
assert.equal(state.lastInputTokens, null, "an impossible snapshot is not trusted");
assert.equal(state.currentContextUsage, null, "an impossible snapshot hides current context");
});
test("Codex requires a valid current-turn window and preserves zero occupancy", function () {
var kit = codexModule.contractTestKit;
var state = kit.createEventState("gpt-test");
kit.normalizeEvent({ method: "turn/started", params: {} }, state);
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
tokenUsage: { lastTurn: { inputTokens: 0 }, modelContextWindow: 1000 },
} }, state);
assert.deepStrictEqual(state.currentContextUsage, { input_tokens: 0, contextWindow: 1000 });
kit.normalizeEvent({ method: "turn/started", params: {} }, state);
assert.equal(state.currentContextUsage, null, "a new turn cannot reuse the previous snapshot");
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
tokenUsage: { lastTurn: { inputTokens: 10 } },
} }, state);
assert.equal(state.currentContextUsage, null, "a snapshot without a valid window is unavailable");
});
test("Codex completion keeps billing usage separate from context occupancy", function () {
var kit = codexModule.contractTestKit;
var state = kit.createEventState("gpt-test");
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
tokenUsage: { lastTurn: { inputTokens: 100, cachedInputTokens: 900 }, modelContextWindow: 1000 },
} }, state);
var events = kit.normalizeEvent({ method: "turn/completed", params: {
usage: { input_tokens: 739000, output_tokens: 12, cached_input_tokens: 258000 },
} }, state);
var result = events.filter(function (event) { return event.yokeType === "result"; })[0];
assert.equal(result.usage.input_tokens, 739000, "generic accounting keeps reported billing usage");
assert.equal(result.usage.cache_read_input_tokens, 258000);
assert.equal(state.currentContextUsage.input_tokens, 100, "context occupancy stays on the verified snapshot");
assert.deepStrictEqual(result.modelUsage["gpt-test"].contextSnapshot, {
valid: true,
inputTokens: 100,
contextWindow: 1000,
});
});
test("Codex completion falls back to snapshot accounting without reporting occupancy as billing", function () {
var kit = codexModule.contractTestKit;
var state = kit.createEventState("gpt-test");
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
tokenUsage: { lastTurn: { inputTokens: 42, cachedInputTokens: 7, outputTokens: 3 }, modelContextWindow: 1000 },
} }, state);
var events = kit.normalizeEvent({ method: "turn/completed", params: {} }, state);
var result = events.filter(function (event) { return event.yokeType === "result"; })[0];
assert.equal(result.usage.input_tokens, 42);
assert.equal(result.usage.cache_read_input_tokens, 7);
assert.equal(result.usage.output_tokens, 3);
assert.equal(result.modelUsage["gpt-test"].contextSnapshot.valid, true);
});
test("Codex completion persists an invalid snapshot marker when occupancy is unavailable", function () {
var kit = codexModule.contractTestKit;
var state = kit.createEventState("gpt-test");
var events = kit.normalizeEvent({ method: "turn/completed", params: {
usage: { input_tokens: 739000, output_tokens: 1, cached_input_tokens: 258000 },
} }, state);
var result = events.filter(function (event) { return event.yokeType === "result"; })[0];
assert.deepStrictEqual(result.modelUsage["gpt-test"].contextSnapshot, { valid: false });
});
test("Codex ignores token snapshots from a different current turn", function () {
var kit = codexModule.contractTestKit;
var state = kit.createEventState("gpt-test");
kit.normalizeEvent({ method: "turn/started", params: { turnId: "turn-current" } }, state);
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
turnId: "turn-old", tokenUsage: { lastTurn: { inputTokens: 10 }, modelContextWindow: 1000 },
} }, state);
assert.equal(state.currentContextUsage, null);
kit.normalizeEvent({ method: "thread/tokenUsage/updated", params: {
turnId: "turn-current", tokenUsage: { lastTurn: { inputTokens: 0 }, modelContextWindow: 1000 },
} }, state);
assert.equal(state.currentContextUsage.input_tokens, 0);
assert.equal(state.currentContextUsage.turnId, "turn-current");
});