feat(browser-tools): configurable screenshot resolution, format, and quality (#1152)
Add environment variable overrides for screenshot capture settings so users can opt into full-resolution output for human review while keeping the Anthropic vision-optimized defaults: - SCREENSHOT_MAX_WIDTH (default 1568, set 0 to uncap) - SCREENSHOT_MAX_HEIGHT (default 8000, set 0 to uncap) - SCREENSHOT_FORMAT (default jpeg for viewport / png for crops) - SCREENSHOT_QUALITY (default 80, range 1-100) Also fixes: - Integration test viewport/scale mismatch: was 1280x720 scale 1, now 1280x800 scale 2 to match production browser context - Unit test height-limit assertion: test expected <= 1568 but MAX_SCREENSHOT_HEIGHT is 8000 — corrected test image and assertions
This commit is contained in:
parent
d24095971c
commit
45af9f7f9d
4 changed files with 66 additions and 16 deletions
|
|
@ -13,8 +13,40 @@ import { formatCompactStateSummary } from "./utils.js";
|
|||
// Anthropic vision: 1568px is the recommended optimal width. Height is capped
|
||||
// generously at 8000px so tall full-page screenshots remain readable rather
|
||||
// than being squished into a square constraint.
|
||||
const MAX_SCREENSHOT_WIDTH = 1568;
|
||||
const MAX_SCREENSHOT_HEIGHT = 8000;
|
||||
//
|
||||
// Override via environment variables:
|
||||
// SCREENSHOT_MAX_WIDTH=0 → uncap width (use raw resolution)
|
||||
// SCREENSHOT_MAX_HEIGHT=0 → uncap height
|
||||
// SCREENSHOT_FORMAT=png → lossless PNG for all viewport/fullpage screenshots
|
||||
// SCREENSHOT_QUALITY=100 → max JPEG quality (1-100, default 80)
|
||||
const MAX_SCREENSHOT_WIDTH = parseScreenshotDimension(process.env.SCREENSHOT_MAX_WIDTH, 1568);
|
||||
const MAX_SCREENSHOT_HEIGHT = parseScreenshotDimension(process.env.SCREENSHOT_MAX_HEIGHT, 8000);
|
||||
|
||||
/** Parse a dimension env var: positive int = that value, 0 = Infinity (uncapped), absent/invalid = default. */
|
||||
function parseScreenshotDimension(value: string | undefined, fallback: number): number {
|
||||
if (value === undefined || value === "") return fallback;
|
||||
const n = parseInt(value, 10);
|
||||
if (isNaN(n) || n < 0) return fallback;
|
||||
if (n === 0) return Infinity;
|
||||
return n;
|
||||
}
|
||||
|
||||
/** Return the user-configured screenshot format override, or null for default behavior. */
|
||||
export function getScreenshotFormatOverride(): "png" | "jpeg" | null {
|
||||
const fmt = process.env.SCREENSHOT_FORMAT?.toLowerCase();
|
||||
if (fmt === "png") return "png";
|
||||
if (fmt === "jpeg" || fmt === "jpg") return "jpeg";
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Return the user-configured default JPEG quality, or the provided fallback. */
|
||||
export function getScreenshotQualityDefault(fallback: number): number {
|
||||
const q = process.env.SCREENSHOT_QUALITY;
|
||||
if (q === undefined || q === "") return fallback;
|
||||
const n = parseInt(q, 10);
|
||||
if (isNaN(n) || n < 1 || n > 100) return fallback;
|
||||
return n;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Compact page state capture
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ let page;
|
|||
|
||||
before(async () => {
|
||||
browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 }, deviceScaleFactor: 2 });
|
||||
page = await context.newPage();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -602,14 +602,14 @@ describe("constrainScreenshot", () => {
|
|||
});
|
||||
|
||||
it("handles an image where only height exceeds the limit", async () => {
|
||||
const buf = await createTestJpeg(1000, 2000);
|
||||
const buf = await createTestJpeg(1000, 9000);
|
||||
const result = await constrainScreenshot(null, buf, "image/jpeg", 80);
|
||||
const sharp = require("sharp");
|
||||
const meta = await sharp(result).metadata();
|
||||
assert.ok(meta.width <= 1568);
|
||||
assert.ok(meta.height <= 1568);
|
||||
assert.ok(meta.height <= 8000);
|
||||
// Height was the constraining dimension
|
||||
assert.equal(meta.height, 1568);
|
||||
assert.equal(meta.height, 8000);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { ToolDeps } from "../state.js";
|
||||
import { getScreenshotFormatOverride, getScreenshotQualityDefault } from "../capture.js";
|
||||
|
||||
export function registerScreenshotTools(pi: ExtensionAPI, deps: ToolDeps): void {
|
||||
pi.registerTool({
|
||||
|
|
@ -32,20 +33,37 @@ export function registerScreenshotTools(pi: ExtensionAPI, deps: ToolDeps): void
|
|||
|
||||
let screenshotBuffer: Buffer;
|
||||
let mimeType: string;
|
||||
const quality = params.quality ?? 80;
|
||||
const formatOverride = getScreenshotFormatOverride();
|
||||
const quality = params.quality ?? getScreenshotQualityDefault(80);
|
||||
|
||||
if (params.selector) {
|
||||
const fmt = formatOverride ?? "png";
|
||||
const locator = p.locator(params.selector).first();
|
||||
screenshotBuffer = await locator.screenshot({ type: "png", scale: "css" });
|
||||
mimeType = "image/png";
|
||||
if (fmt === "jpeg") {
|
||||
screenshotBuffer = await locator.screenshot({ type: "jpeg", quality, scale: "css" });
|
||||
mimeType = "image/jpeg";
|
||||
} else {
|
||||
screenshotBuffer = await locator.screenshot({ type: "png", scale: "css" });
|
||||
mimeType = "image/png";
|
||||
}
|
||||
} else {
|
||||
screenshotBuffer = await p.screenshot({
|
||||
fullPage: params.fullPage ?? false,
|
||||
type: "jpeg",
|
||||
quality,
|
||||
scale: "css",
|
||||
});
|
||||
mimeType = "image/jpeg";
|
||||
const fmt = formatOverride ?? "jpeg";
|
||||
if (fmt === "png") {
|
||||
screenshotBuffer = await p.screenshot({
|
||||
fullPage: params.fullPage ?? false,
|
||||
type: "png",
|
||||
scale: "css",
|
||||
});
|
||||
mimeType = "image/png";
|
||||
} else {
|
||||
screenshotBuffer = await p.screenshot({
|
||||
fullPage: params.fullPage ?? false,
|
||||
type: "jpeg",
|
||||
quality,
|
||||
scale: "css",
|
||||
});
|
||||
mimeType = "image/jpeg";
|
||||
}
|
||||
}
|
||||
|
||||
screenshotBuffer = await deps.constrainScreenshot(p, screenshotBuffer, mimeType, quality);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue