diff --git a/src/tools/look-at/tools.test.ts b/src/tools/look-at/tools.test.ts index 107aff1d4..b8a44ac5c 100644 --- a/src/tools/look-at/tools.test.ts +++ b/src/tools/look-at/tools.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test" -import { normalizeArgs, validateArgs } from "./tools" +import { normalizeArgs, validateArgs, createLookAt } from "./tools" describe("look-at tool", () => { describe("normalizeArgs", () => { @@ -70,4 +70,80 @@ describe("look-at tool", () => { expect(error).toContain("file_path") }) }) + + describe("createLookAt error handling", () => { + // #given session.prompt에서 JSON parse 에러 발생 + // #when LookAt 도구 실행 + // #then 사용자 친화적 에러 메시지 반환 + test("handles JSON parse error from session.prompt gracefully", async () => { + const mockClient = { + session: { + get: async () => ({ data: { directory: "/project" } }), + create: async () => ({ data: { id: "ses_test_json_error" } }), + prompt: async () => { + throw new Error("JSON Parse error: Unexpected EOF") + }, + messages: async () => ({ data: [] }), + }, + } + + const tool = createLookAt({ + client: mockClient, + directory: "/project", + } as any) + + const toolContext = { + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + abort: new AbortController().signal, + } + + const result = await tool.execute( + { file_path: "/test/file.png", goal: "analyze image" }, + toolContext + ) + + expect(result).toContain("Error: Failed to analyze file") + expect(result).toContain("malformed response") + expect(result).toContain("multimodal-looker") + expect(result).toContain("image/png") + }) + + // #given session.prompt에서 일반 에러 발생 + // #when LookAt 도구 실행 + // #then 원본 에러 메시지 포함한 에러 반환 + test("handles generic prompt error gracefully", async () => { + const mockClient = { + session: { + get: async () => ({ data: { directory: "/project" } }), + create: async () => ({ data: { id: "ses_test_generic_error" } }), + prompt: async () => { + throw new Error("Network connection failed") + }, + messages: async () => ({ data: [] }), + }, + } + + const tool = createLookAt({ + client: mockClient, + directory: "/project", + } as any) + + const toolContext = { + sessionID: "parent-session", + messageID: "parent-message", + agent: "sisyphus", + abort: new AbortController().signal, + } + + const result = await tool.execute( + { file_path: "/test/file.pdf", goal: "extract text" }, + toolContext + ) + + expect(result).toContain("Error: Failed to send prompt") + expect(result).toContain("Network connection failed") + }) + }) }) diff --git a/src/tools/look-at/tools.ts b/src/tools/look-at/tools.ts index dea499a7f..d3176ae25 100644 --- a/src/tools/look-at/tools.ts +++ b/src/tools/look-at/tools.ts @@ -131,22 +131,49 @@ Original error: ${createResult.error}` log(`[look_at] Created session: ${sessionID}`) log(`[look_at] Sending prompt with file passthrough to session ${sessionID}`) - await ctx.client.session.prompt({ - path: { id: sessionID }, - body: { - agent: MULTIMODAL_LOOKER_AGENT, - tools: { - task: false, - call_omo_agent: false, - look_at: false, - read: false, + try { + await ctx.client.session.prompt({ + path: { id: sessionID }, + body: { + agent: MULTIMODAL_LOOKER_AGENT, + tools: { + task: false, + call_omo_agent: false, + look_at: false, + read: false, + }, + parts: [ + { type: "text", text: prompt }, + { type: "file", mime: mimeType, url: pathToFileURL(args.file_path).href, filename }, + ], }, - parts: [ - { type: "text", text: prompt }, - { type: "file", mime: mimeType, url: pathToFileURL(args.file_path).href, filename }, - ], - }, - }) + }) + } catch (promptError) { + const errorMessage = promptError instanceof Error ? promptError.message : String(promptError) + log(`[look_at] Prompt error:`, promptError) + + const isJsonParseError = errorMessage.includes("JSON") && (errorMessage.includes("EOF") || errorMessage.includes("parse")) + if (isJsonParseError) { + return `Error: Failed to analyze file - received malformed response from multimodal-looker agent. + +This typically occurs when: +1. The multimodal-looker model is not available or not connected +2. The model does not support this file type (${mimeType}) +3. The API returned an empty or truncated response + +File: ${args.file_path} +MIME type: ${mimeType} + +Try: +- Ensure a vision-capable model (e.g., gemini-3-flash, gpt-5.2) is available +- Check provider connections in opencode settings +- For text files like .md, .txt, use the Read tool instead + +Original error: ${errorMessage}` + } + + return `Error: Failed to send prompt to multimodal-looker agent: ${errorMessage}` + } log(`[look_at] Prompt sent, fetching messages...`)