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"); });