diff --git a/src/responses/parser.ts b/src/responses/parser.ts index 72204ea4d..8c263e689 100644 --- a/src/responses/parser.ts +++ b/src/responses/parser.ts @@ -112,6 +112,11 @@ function mapToolChoice(value: unknown): OcxRequestOptions["toolChoice"] { if (t === "image_generation" || t === "image_gen") { return { name: IMAGE_GEN_TOOL_NAME }; } + // Hosted web-search choices force the synthetic routed search tool. Treating this shape as + // `auto` would also advertise unrelated client tools when the sidecar injects web_search. + if (t === "web_search" || t === "web_search_preview") { + return { name: WEB_SEARCH_TOOL_NAME }; + } if (t === "allowed_tools" && Array.isArray(value.tools)) { const names = value.tools .map(allowedToolName) diff --git a/tests/responses-parser.test.ts b/tests/responses-parser.test.ts index 5b35e8b10..ef130d20e 100644 --- a/tests/responses-parser.test.ts +++ b/tests/responses-parser.test.ts @@ -161,6 +161,20 @@ describe("Responses parser", () => { expect(parsed.options.toolChoice).toEqual({ allowedTools: ["web_search"], mode: "required" }); }); + test("maps a forced hosted web_search choice to the synthetic routed tool", () => { + const parsed = parseRequest({ + model: "umans/umans-kimi-k2.7", + input: "search", + tools: [ + { type: "web_search" }, + { type: "function", name: "run_shell", parameters: { type: "object" } }, + ], + tool_choice: { type: "web_search" }, + }); + + expect(parsed.options.toolChoice).toEqual({ name: "web_search" }); + }); + test("maps type-only hosted image_generation tool_choice to required image_gen", () => { const parsed = parseRequest({ model: "claude-opus-4-6",