Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/lib/tool-argument-integers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ReadonlySet<string>>([
["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
Expand Down
49 changes: 34 additions & 15 deletions tests/tool-argument-integers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,15 +325,34 @@ 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}');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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" } },
};
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 () => {
Expand Down Expand Up @@ -367,7 +386,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" },
},
};
Expand All @@ -384,26 +403,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 () => {
Expand Down
Loading