diff --git a/src/responses/code-mode-helper-compat.ts b/src/responses/code-mode-helper-compat.ts index 726c5dcf19..f0c86f1e07 100644 --- a/src/responses/code-mode-helper-compat.ts +++ b/src/responses/code-mode-helper-compat.ts @@ -54,6 +54,16 @@ export function compileCodeModeHelperInput(argumentsText: unknown, toolName: str if (toolName === "write_stdin") { return `const result = await tools.write_stdin(${JSON.stringify(args)});\ntext(result);`; } + if (toolName === "view_image") { + let target = ""; + if (isPlainObject(args)) { + if (typeof args.path === "string") target = ` for ${JSON.stringify(args.path)}`; + else if (typeof args.file_path === "string") target = ` for ${JSON.stringify(args.file_path)}`; + else if (typeof args.file === "string") target = ` for ${JSON.stringify(args.file)}`; + } + const msg = `Error: 'view_image' is not available in Code Mode${target}. Inspect files or visual assets programmatically using tools.exec_command or code.`; + return `text(${JSON.stringify(msg)});\n`; + } return `const result = await tools.exec_command(${JSON.stringify(args)});\ntext(result);`; } diff --git a/src/types/tools.ts b/src/types/tools.ts index bb91ebe63f..3678bcbc98 100644 --- a/src/types/tools.ts +++ b/src/types/tools.ts @@ -57,6 +57,7 @@ const CODE_MODE_HELPER_TOOL_NAMES = [ ...LEGACY_SHELL_BRIDGE_TOOL_NAMES, "write_stdin", "apply_patch", + "view_image", ] as const; /** diff --git a/tests/responses/legacy-shell-compat.test.ts b/tests/responses/legacy-shell-compat.test.ts index 6503c3e6dd..0598bced7c 100644 --- a/tests/responses/legacy-shell-compat.test.ts +++ b/tests/responses/legacy-shell-compat.test.ts @@ -112,4 +112,16 @@ describe("code-mode helper compatibility", () => { expect(received).toEqual(input === "[]" ? [] : input); } }); + + test("view_image emits a friendly code-mode error message via text()", async () => { + const source = compileCodeModeHelperInput( + JSON.stringify({ path: "/root/banner.png" }), + "view_image", + ); + let output: unknown; + const run = new AsyncFunction("tools", "text", source); + await run({}, (value: unknown) => { output = value; }); + expect(output).toContain("Error: 'view_image' is not available in Code Mode for \"/root/banner.png\""); + expect(output).toContain("tools.exec_command"); + }); }); diff --git a/tests/responses/responses-undeclared-tool-guard.test.ts b/tests/responses/responses-undeclared-tool-guard.test.ts index 41d8383a6c..27c577b118 100644 --- a/tests/responses/responses-undeclared-tool-guard.test.ts +++ b/tests/responses/responses-undeclared-tool-guard.test.ts @@ -1640,6 +1640,16 @@ describe("undeclaredToolCallNameInResponse", () => { expect(undeclaredToolCallNameInResponse(response, new Set())).toBe("write_stdin"); }); + test("accepts view_image through a bare unified exec declaration", () => { + const response = { + output: [{ type: "function_call", name: "view_image" }], + }; + + expect(undeclaredToolCallNameInResponse(response, new Set(["exec"]))).toBeUndefined(); + expect(undeclaredToolCallNameInResponse(response, new Set(["view_image"]))).toBeUndefined(); + expect(undeclaredToolCallNameInResponse(response, new Set())).toBe("view_image"); + }); + test("never legacy-normalizes a namespaced shell bridge call", () => { // A namespaced call (e.g. an MCP server advertising its own exec_command) must be // matched by its full wire name only — never normalized to bare `exec`.