From dfb62f9a4c1965c7d42a463178265a86bec0528e Mon Sep 17 00:00:00 2001 From: jeongjin Date: Mon, 24 Aug 2026 00:39:19 +0900 Subject: [PATCH 1/2] fix(tools): coerce wait.yield_time_ms as an integral float #2448 scoped the wait repair correctly but allowlisted the hyphenated yield-time_ms name. Live Grok 4.6 Codex Desktop calls still emit yield_time_ms: 20000.0 and Codex rejects them as u64 before wait runs. Keep the repair wait-scoped so namespaced Cursor calls stay untouched. --- src/lib/tool-argument-integers.ts | 10 +++++-- tests/tool-argument-integers.test.ts | 43 ++++++++++++++++++---------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/lib/tool-argument-integers.ts b/src/lib/tool-argument-integers.ts index 7b3204d40c..171bc241a2 100644 --- a/src/lib/tool-argument-integers.ts +++ b/src/lib/tool-argument-integers.ts @@ -77,11 +77,15 @@ function declaresString(schema: SchemaNode): boolean { // against it. It gets its own change when it gets its own reproduction. const U64_NUMBER_FIELDS = new Set(["timeout_ms"]); -// Issue #2443: Codex Desktop's bare `wait` tool has the same schema/runtime split -// for `yield-time_ms` and `max_tokens`. Scope these names to that bare tool so a +// Issue #2443 / #2451: Codex Desktop's bare `wait` tool has the same schema/runtime +// split for `yield_time_ms` and `max_tokens`. Scope these names to that bare tool so a // third-party or namespaced tool can still use fractional values legitimately. +// #2448 allowlisted the hyphenated `yield-time_ms`; live Grok 4.6 calls and the +// advertised wait schema both use the underscore form, so that name is the one +// with a captured u64 rejection. Cursor still uses the same underscore name on a +// namespaced tool; the wait-only map keeps that path byte-identical. const U64_NUMBER_FIELDS_BY_TOOL = new Map>([ - ["wait", new Set(["yield-time_ms", "max_tokens"])], + ["wait", new Set(["yield_time_ms", "max_tokens"])], ]); /** True when the node accepts a JSON number (`integer` or `number`), so a numeric diff --git a/tests/tool-argument-integers.test.ts b/tests/tool-argument-integers.test.ts index 9ed89da444..3acbb45001 100644 --- a/tests/tool-argument-integers.test.ts +++ b/tests/tool-argument-integers.test.ts @@ -325,15 +325,28 @@ describe("native u64 fields advertised as number (#2316)", () => { .toBe('{"timeout_ms":"120000"}'); }); - test("Cursor's sibling field stays unchanged even with wait identity", () => { - // Cursor uses yield_time_ms (underscore), not wait's yield-time_ms (hyphen). - // Keep this explicit scope proof from #2316: field names are never broadened globally. - const others = { + test("bare wait repairs yield_time_ms and leaves unrelated number fields alone", () => { + // Live Codex Desktop wait uses the underscore form (#2451). Wait identity + // repairs that field and max_tokens, but never a generic number field. + const schema = { + type: "object", + properties: { + yield_time_ms: { type: "number" }, + max_tokens: { type: "number" }, + priority: { type: "number" }, + }, + }; + expect(coerceIntegerToolArguments( + '{"yield_time_ms":20000.0,"max_tokens":5000.0,"priority":2.0}', + schema, + "wait", + )).toBe('{"yield_time_ms":20000,"max_tokens":5000,"priority":2}'); + const other = { type: "object", properties: { yield_time_ms: { type: "number" }, priority: { type: "number" } }, }; const raw = '{"yield_time_ms":60000.0,"priority":2.0}'; - expect(coerceIntegerToolArguments(raw, others, "wait")).toBe(raw); + expect(coerceIntegerToolArguments(raw, other, "other_tool")).toBe(raw); }); test("the namespaced wait_agent call is repaired through the real bridge", async () => { @@ -367,7 +380,7 @@ describe("native u64 fields advertised as number (#2316)", () => { const CODEX_DESKTOP_WAIT_SCHEMA = { type: "object", properties: { - "yield-time_ms": { type: "number" }, + "yield_time_ms": { type: "number" }, max_tokens: { type: "number" }, }, }; @@ -384,26 +397,26 @@ const WAIT_SCOPE_NAMESPACE_MAP = new Map([ const WAIT_SCOPE_EVENTS: AdapterEvent[] = [ { type: "tool_call_start", id: "call_wait", name: "wait" }, - { type: "tool_call_delta", arguments: '{"yield-time_ms":120000.0,"max_tokens":8000.0}' }, + { type: "tool_call_delta", arguments: '{"yield_time_ms":120000.0,"max_tokens":8000.0}' }, { type: "tool_call_end", id: "call_wait" }, { type: "tool_call_start", id: "call_fractional", name: "wait" }, - { type: "tool_call_delta", arguments: '{"yield-time_ms":1.5,"max_tokens":1.5}' }, + { type: "tool_call_delta", arguments: '{"yield_time_ms":1.5,"max_tokens":1.5}' }, { type: "tool_call_end", id: "call_fractional" }, { type: "tool_call_start", id: "call_other", name: "other_tool" }, - { type: "tool_call_delta", arguments: '{"yield-time_ms":120000.0,"max_tokens":8000.0}' }, + { type: "tool_call_delta", arguments: '{"yield_time_ms":120000.0,"max_tokens":8000.0}' }, { type: "tool_call_end", id: "call_other" }, { type: "tool_call_start", id: "call_namespaced", name: "cursor_wait" }, - { type: "tool_call_delta", arguments: '{"yield-time_ms":120000.0,"max_tokens":8000.0}' }, + { type: "tool_call_delta", arguments: '{"yield_time_ms":120000.0,"max_tokens":8000.0}' }, { type: "tool_call_end", id: "call_namespaced" }, { type: "done" }, ]; -describe("Codex Desktop wait native integers (#2443)", () => { +describe("Codex Desktop wait native integers (#2443 / #2451)", () => { const expectedCalls = [ - { name: "wait", namespace: undefined, arguments: '{"yield-time_ms":120000,"max_tokens":8000}' }, - { name: "wait", namespace: undefined, arguments: '{"yield-time_ms":1.5,"max_tokens":1.5}' }, - { name: "other_tool", namespace: undefined, arguments: '{"yield-time_ms":120000.0,"max_tokens":8000.0}' }, - { name: "wait", namespace: "cursor", arguments: '{"yield-time_ms":120000.0,"max_tokens":8000.0}' }, + { name: "wait", namespace: undefined, arguments: '{"yield_time_ms":120000,"max_tokens":8000}' }, + { name: "wait", namespace: undefined, arguments: '{"yield_time_ms":1.5,"max_tokens":1.5}' }, + { name: "other_tool", namespace: undefined, arguments: '{"yield_time_ms":120000.0,"max_tokens":8000.0}' }, + { name: "wait", namespace: "cursor", arguments: '{"yield_time_ms":120000.0,"max_tokens":8000.0}' }, ]; test("streaming bridge scopes the repair to the bare wait tool", async () => { From 95a3f6766a83a7c049139c0c7c509e40dd1edf97 Mon Sep 17 00:00:00 2001 From: jeongjin Date: Mon, 24 Aug 2026 00:43:32 +0900 Subject: [PATCH 2/2] test(tools): keep wait.priority bytes when it is the only field A combined payload re-stringifies ignored number fields, so an isolated priority:2.0 case is the actual guard that wait did not absorb it. --- tests/tool-argument-integers.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/tool-argument-integers.test.ts b/tests/tool-argument-integers.test.ts index 3acbb45001..590be64194 100644 --- a/tests/tool-argument-integers.test.ts +++ b/tests/tool-argument-integers.test.ts @@ -341,6 +341,12 @@ describe("native u64 fields advertised as number (#2316)", () => { schema, "wait", )).toBe('{"yield_time_ms":20000,"max_tokens":5000,"priority":2}'); + const priorityOnly = '{"priority":2.0}'; + expect(coerceIntegerToolArguments( + priorityOnly, + schema, + "wait", + )).toBe(priorityOnly); const other = { type: "object", properties: { yield_time_ms: { type: "number" }, priority: { type: "number" } },