diff --git a/src/adapters/cursor/tool-definitions.ts b/src/adapters/cursor/tool-definitions.ts index 31d34ee5e7..d8fe23e4fe 100644 --- a/src/adapters/cursor/tool-definitions.ts +++ b/src/adapters/cursor/tool-definitions.ts @@ -651,7 +651,7 @@ export function buildCursorToolGuidanceSystemNote( // Code mode: shell/edit/MCP live inside freeform `exec` as nested helpers. Without this the // model probes for a top-level shell tool that is not there. codeMode - ? `\`${CODEX_UNIFIED_EXEC_TOOL}\` is Codex code mode: its body is JavaScript evaluated in a V8 isolate, not a shell command and not Node. Shell, file edits, and MCP are nested helpers called INSIDE that body as \`await tools.(...)\`, for example \`await tools.exec_command({cmd: \"ls\"})\`. Read the tool description and the isolate global \`ALL_TOOLS\` (not \`tools.ALL_TOOLS\`) for helpers this turn provides; absence from the top-level catalog or from \`exec\`'s description is not absence. Those nested helpers are not themselves top-level tools, so do not call \`exec_command\` or \`shell_command\` at the top level here${codeModeOtherTopLevelNames.length > 0 ? `; every other tool this turn lists, including ${quotedNames(codeModeOtherTopLevelNames)}, remains callable at the top level as usual` : ""}.` + ? `\`${CODEX_UNIFIED_EXEC_TOOL}\` is Codex code mode: its body is JavaScript evaluated in a V8 isolate, not a shell command and not Node. Shell, file edits, and MCP are nested helpers called INSIDE that body as \`await tools.(...)\`, for example \`await tools.exec_command({cmd: \"ls\"})\`. Read the tool description and the isolate global \`ALL_TOOLS\` (not \`tools.ALL_TOOLS\`) for helpers this turn provides; absence from the top-level catalog or from \`exec\`'s description is not absence. Those nested helpers are not themselves top-level tools, so do not call \`exec_command\` or \`shell_command\` at the top level here${codeModeOtherTopLevelNames.length > 0 ? `; every other tool this turn lists, including ${quotedNames(codeModeOtherTopLevelNames)}, remains callable at the top level as usual` : ""}. Nested \`tools.apply_patch(input)\` is host-executed: the string must begin exactly with \`*** Begin Patch\` and end with \`*** End Patch\` (no trailing \`***\` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated \`*** Begin Patch ***\` envelope is rejected by Codex before the file is touched.` : undefined, codeMode ? "In code mode the isolate returns nothing on its own: call `text(...)` (or `notify(...)`) on any value you need to see, or the call completes with empty output. There is no `require`, no `module`, and no filesystem or network globals; reach the host only through the nested helpers." diff --git a/src/adapters/tool-catalog-nudge.ts b/src/adapters/tool-catalog-nudge.ts index 626bd93d8d..b03a3b6a41 100644 --- a/src/adapters/tool-catalog-nudge.ts +++ b/src/adapters/tool-catalog-nudge.ts @@ -120,7 +120,7 @@ export function buildNonOpenAIToolCatalogNudgeFromNames( "Call only listed names with their listed argument keys; do not invent, translate, or rename tools.", "Names mentioned only in instructions, tool descriptions, argument descriptions, or nested helper APIs are not additional top-level tools.", verifiedCodeModeExecName - ? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names." + ? "`" + verifiedCodeModeExecName + "` is Codex code mode: its body is JavaScript evaluated in a V8 isolate. Nested helpers are called INSIDE that body as `await tools.(...)`, for example `await tools.exec_command({cmd: \"ls\"})` or `await tools.codex_app__list_threads({})`. Absence from the top-level catalog or from `" + verifiedCodeModeExecName + "`'s description is not absence: deferred helpers stay callable on `tools.`. Discover them from the isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`. Do not skip an available nested helper because it is omitted from the listed top-level names. Nested `tools.apply_patch(input)` is host-executed: the string must begin exactly with `*** Begin Patch` and end with `*** End Patch` (no trailing `***` on those lines). OpenCodex does not rewrite JavaScript inside exec, so a decorated `*** Begin Patch ***` envelope is rejected by Codex before the file is touched." : "If a listed tool exposes nested helpers such as a tools.* API, call the listed parent tool and use those helpers only inside that tool's input.", unavailableNeighborNames.length > 0 ? "Do not use neighboring-agent tool names " + quoteNames(unavailableNeighborNames) + " unless this turn's catalog lists those exact names." diff --git a/tests/cursor-tool-definitions.test.ts b/tests/cursor-tool-definitions.test.ts index 8eb0becf18..54467e41ce 100644 --- a/tests/cursor-tool-definitions.test.ts +++ b/tests/cursor-tool-definitions.test.ts @@ -483,6 +483,10 @@ describe("Cursor code mode tool guidance", () => { expect(note).toContain("isolate global `ALL_TOOLS`"); expect(note).toContain("not `tools.ALL_TOOLS`"); expect(note).toContain("absence from the top-level catalog"); + expect(note).toContain("`*** Begin Patch`"); + expect(note).toContain("`*** End Patch`"); + expect(note).toContain("no trailing `***`"); + expect(note).toContain("OpenCodex does not rewrite JavaScript inside exec"); // The flat-catalog shell-bridge guidance must NOT appear: naming a top-level // `exec_command` in code mode sends the model after a tool that does not exist. diff --git a/tests/request-pacing.test.ts b/tests/request-pacing.test.ts index c2be3e5b8e..bf8cfbb106 100644 --- a/tests/request-pacing.test.ts +++ b/tests/request-pacing.test.ts @@ -85,9 +85,11 @@ describe("requestPacingIntervalMs", () => { describe("provider request pacing queue", () => { test("spaces concurrent starts in one provider FIFO and exposes queue state", async () => { - const starts: number[] = []; - const fetchImpl = Object.assign(async () => { - starts.push(Date.now()); + const clock = fakePacingClock(); + setProviderRequestPacingRuntimeForTest(clock.runtime); + const started: Array<{ url: string; at: number }> = []; + const fetchImpl = Object.assign(async (input: Parameters[0]) => { + started.push({ url: String(input), at: clock.now() }); return new Response("ok"); }, { preconnect() {} }) as typeof globalThis.fetch; const configured = { @@ -95,13 +97,25 @@ describe("provider request pacing queue", () => { fetch: fetchImpl, } as OcxProviderConfig & { fetch: typeof globalThis.fetch }; const send = providerFetch(configured, undefined, { providerName: "demo", modelId: "model-a" }); - const pending = [send("https://example.test/v1/chat/completions"), send("https://example.test/v1/chat/completions"), send("https://example.test/v1/chat/completions")]; - await Bun.sleep(10); + const first = send("https://example.test/v1/first"); + const second = send("https://example.test/v1/second"); + const third = send("https://example.test/v1/third"); + await first; + expect(started).toEqual([{ url: "https://example.test/v1/first", at: 0 }]); expect(providerRequestPacingStatus("demo", configured).queued).toBe(2); - await Promise.all(pending); - expect(starts).toHaveLength(3); - expect(starts[1] - starts[0]).toBeGreaterThanOrEqual(85); - expect(starts[2] - starts[1]).toBeGreaterThanOrEqual(85); + clock.advanceBy(100); + await second; + expect(started).toEqual([ + { url: "https://example.test/v1/first", at: 0 }, + { url: "https://example.test/v1/second", at: 100 }, + ]); + clock.advanceBy(100); + await third; + expect(started).toEqual([ + { url: "https://example.test/v1/first", at: 0 }, + { url: "https://example.test/v1/second", at: 100 }, + { url: "https://example.test/v1/third", at: 200 }, + ]); const status = providerRequestPacingStatus("demo", configured); expect(status.queued).toBe(0); expect(status.lastModelId).toBe("model-a"); diff --git a/tests/tool-catalog-nudge.test.ts b/tests/tool-catalog-nudge.test.ts index 6316892034..702d0f3e1d 100644 --- a/tests/tool-catalog-nudge.test.ts +++ b/tests/tool-catalog-nudge.test.ts @@ -71,8 +71,12 @@ describe("non-OpenAI tool catalog nudge", () => { expect(note).toContain("await tools.codex_app__list_threads({})"); expect(note).toContain("isolate global `ALL_TOOLS`, not `tools.ALL_TOOLS`"); expect(note).toContain("Do not skip an available nested helper"); + expect(note).toContain("`*** Begin Patch`"); + expect(note).toContain("`*** End Patch`"); + expect(note).toContain("no trailing `***`"); + expect(note).toContain("OpenCodex does not rewrite JavaScript inside exec"); + expect(note).toContain("Nested `tools.apply_patch(input)` is host-executed"); expect(note).not.toContain("call the listed parent tool and use those helpers only inside that tool's input"); - expect(note).not.toContain("apply_patch"); }); test("keeps the generic nested-helper parent-tool rule when exec is not listed", () => {