Skip to content

Commit a7e4b0d

Browse files
author
Grant Burry
committed
fix: filter base64 image from screenshot result
- Filter out base64 image data from the `screenshot` result to reduce context usage. - Extract file paths from text content and attach guidance.
1 parent f62b468 commit a7e4b0d

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

src/tools/browser-eval.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,45 @@ type BrowserEvalArgs = {
149149
files?: string[]
150150
}
151151

152+
/**
153+
* Filter out base64 image data from the `screenshot` result to reduce context usage.
154+
* Extract file paths from text content and attach guidance.
155+
*/
156+
export function filterImageDataFromResult(result: unknown): unknown {
157+
if (typeof result !== "object" || result === null) {
158+
return result
159+
}
160+
161+
const typedResult = result as { content?: { type: string; text?: string; data?: string }[] }
162+
163+
if (!Array.isArray(typedResult.content)) {
164+
return result
165+
}
166+
167+
const filteredContent = typedResult.content.filter((block) => block.type !== "image")
168+
169+
const textBlock = filteredContent.find((block) => block.type === "text")
170+
let screenshotPath: string | null = null
171+
172+
if (textBlock?.text) {
173+
// Extract file path from text like "saved it as /path/to/screenshot.png"
174+
const pathMatch = textBlock.text.match(/(?:saved (?:it )?as|saved to) (.+\.png)/i)
175+
if (pathMatch) {
176+
screenshotPath = pathMatch[1]
177+
}
178+
}
179+
180+
// Add helpful message about reading the screenshot
181+
if (screenshotPath && textBlock) {
182+
filteredContent.push({
183+
type: "text",
184+
text: `\n\nTo view this screenshot, use the "read" tool with the file path: ${screenshotPath}`,
185+
})
186+
}
187+
188+
return { ...typedResult, content: filteredContent }
189+
}
190+
152191
export async function handler(args: BrowserEvalArgs): Promise<string> {
153192
try {
154193
if (args.action === "start") {
@@ -288,10 +327,13 @@ export async function handler(args: BrowserEvalArgs): Promise<string> {
288327

289328
const result = await callServerTool(connection, toolName, toolArgs)
290329

330+
const formattedResult =
331+
args.action === "screenshot" ? filterImageDataFromResult(result) : result
332+
291333
return JSON.stringify({
292334
success: true,
293335
action: args.action,
294-
result,
336+
result: formattedResult,
295337
})
296338
} catch (error) {
297339
const errorMessage = error instanceof Error ? error.message : String(error)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { describe, it, expect } from "vitest"
2+
import { filterImageDataFromResult } from "../../src/tools/browser-eval.js"
3+
4+
describe("browser-eval screenshot filter", () => {
5+
it("should filter out base64 image data from screenshot result", () => {
6+
const mockResponse = {
7+
content: [
8+
{
9+
type: "text",
10+
text: "### Result\nTook the viewport screenshot and saved it as /tmp/screenshot.png",
11+
},
12+
{
13+
type: "image",
14+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
15+
},
16+
],
17+
}
18+
19+
const formattedResult = filterImageDataFromResult(mockResponse) as {
20+
content: { type: string; text?: string }[]
21+
}
22+
23+
// Should remove the image block
24+
expect(formattedResult.content.length).toBe(2) // Original text + new guidance text
25+
expect(formattedResult.content.every((block) => block.type !== "image")).toBe(true)
26+
27+
// Should contain guidance about reading the file
28+
const guidanceBlock = formattedResult.content.find((block) =>
29+
block.text?.includes('To view this screenshot, use the "read" tool with the file path')
30+
)
31+
expect(guidanceBlock).toBeDefined()
32+
expect(guidanceBlock?.text).toContain("/tmp/screenshot.png")
33+
})
34+
35+
it("should extract file path from various text formats", () => {
36+
const formats = [
37+
"saved it as /path/to/screenshot.png",
38+
"saved as /path/to/screenshot.png",
39+
"Took the viewport screenshot and saved it as /var/folders/temp/screenshot.png",
40+
]
41+
42+
formats.forEach((text) => {
43+
const mockResult = { content: [{ type: "text", text }] }
44+
45+
const formattedResult = filterImageDataFromResult(mockResult) as {
46+
content: { type: string; text?: string }[]
47+
}
48+
49+
const guidanceBlock = formattedResult.content.find((block) =>
50+
block.text?.includes("To view this screenshot")
51+
)
52+
expect(guidanceBlock).toBeDefined()
53+
})
54+
})
55+
56+
it("should handle results without image data", () => {
57+
const mockResult = { content: [{ type: "text", text: "Some other result" }] }
58+
59+
const formattedResult = filterImageDataFromResult(mockResult) as {
60+
content: { type: string; text?: string }[]
61+
}
62+
63+
// Should not add guidance if no screenshot path found
64+
expect(formattedResult.content.length).toBe(1)
65+
})
66+
67+
it("should handle non-object results", () => {
68+
expect(filterImageDataFromResult(null)).toBe(null)
69+
expect(filterImageDataFromResult("string")).toBe("string")
70+
expect(filterImageDataFromResult(123)).toBe(123)
71+
})
72+
73+
it("should handle responses without content array", () => {
74+
const mockResult = { other: "data" }
75+
expect(filterImageDataFromResult(mockResult)).toEqual(mockResult)
76+
})
77+
})

0 commit comments

Comments
 (0)