fix(look-at): handle JSON parse errors from session.prompt gracefully (#1216)
When multimodal-looker agent returns empty/malformed response, the SDK throws 'JSON Parse error: Unexpected EOF'. This commit adds try-catch around session.prompt() to provide user-friendly error message with troubleshooting guidance. - Add error handling for JSON parse errors with detailed guidance - Add error handling for generic prompt failures - Add test cases for both error scenarios
This commit is contained in:
parent
9d3e152b19
commit
3ab4529bc7
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test"
|
import { describe, expect, test } from "bun:test"
|
||||||
import { normalizeArgs, validateArgs } from "./tools"
|
import { normalizeArgs, validateArgs, createLookAt } from "./tools"
|
||||||
|
|
||||||
describe("look-at tool", () => {
|
describe("look-at tool", () => {
|
||||||
describe("normalizeArgs", () => {
|
describe("normalizeArgs", () => {
|
||||||
@ -70,4 +70,80 @@ describe("look-at tool", () => {
|
|||||||
expect(error).toContain("file_path")
|
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")
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -131,6 +131,7 @@ Original error: ${createResult.error}`
|
|||||||
log(`[look_at] Created session: ${sessionID}`)
|
log(`[look_at] Created session: ${sessionID}`)
|
||||||
|
|
||||||
log(`[look_at] Sending prompt with file passthrough to session ${sessionID}`)
|
log(`[look_at] Sending prompt with file passthrough to session ${sessionID}`)
|
||||||
|
try {
|
||||||
await ctx.client.session.prompt({
|
await ctx.client.session.prompt({
|
||||||
path: { id: sessionID },
|
path: { id: sessionID },
|
||||||
body: {
|
body: {
|
||||||
@ -147,6 +148,32 @@ Original error: ${createResult.error}`
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
} 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...`)
|
log(`[look_at] Prompt sent, fetching messages...`)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user