From 484a33f34831bdebeabebc393da69ab7ce2a0f0b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 12 Jan 2026 07:37:14 +0000 Subject: [PATCH] fix: cap ai snapshots for tool calls (#763) (thanks @thesash) --- CHANGELOG.md | 1 + README.md | 31 ++++++++++++++++--------------- scripts/clawtributors-map.json | 3 ++- src/agents/tools/browser-tool.ts | 13 ++++++++++--- src/browser/pw-tools-core.ts | 13 +++++-------- src/browser/routes/agent.ts | 15 +++++++-------- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5313350d..aeff73191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ ### Fixes - Models/Onboarding: configure MiniMax (minimax.io) via Anthropic-compatible `/anthropic` endpoint by default (keep `minimax-api` as a legacy alias). +- Agents/Browser: cap Playwright AI snapshots for tool calls (maxChars); CLI snapshots remain full. (#763) — thanks @thesash. - Models: normalize Gemini 3 Pro/Flash IDs to preview names for live model lookups. (#769) — thanks @steipete. - CLI: fix guardCancel typing for configure prompts. (#769) — thanks @steipete. - Gateway/WebChat: include handshake validation details in the WebSocket close reason for easier debugging; preserve close codes. diff --git a/README.md b/README.md index 0d9c7f10f..365df020f 100644 --- a/README.md +++ b/README.md @@ -458,19 +458,20 @@ Special thanks to @andrewting19 for the Anthropic OAuth tool-name fix. Thanks to all clawtributors:

- steipete joaohlisboa mneves75 rahthakor joshp123 mukhtharcm maxsumrall xadenryan Tobias Bischoff hsrvc - magimetal jamesgroat NicholasSpisak dantelex daveonkels radek-paclt Eng. Juan Combetto Mariano Belinky julianengel claude - jeffersonwarrior sreekaransrinath dbhurley gupsammy nachoiacovino Vasanth Rao Naik Sabavat lc0rp scald andranik-sahakyan nachx639 - sircrumpet rafaelreis-r meaningfool ratulsarna lutr0 abhisekbasu1 emanuelst thewilloftheshadow KristijanJovanovski osolmaz - kiranjd sebslight onutc CashWilliams sheeek manuelhettich minghinmatthewlam buddyh mcinteerj timkrase - azade-c Yurii Chukhlib austinm911 blacksmith-sh[bot] imfing jarvis-medmatic mahmoudashraf93 petter-b RandyVentures jalehman - jonasjancarik obviyus dan-dr iamadig koala73 manmal neist ogulcancelik pasogott VACInc - zats antons Django Navarro L36 Server pcty-nextgen-service-account Syhids erik-agens erikpr1994 fcatuhe HeimdallStrategy - henrino3 jayhickey Jonathan D. Rhyne (DJ-D) jverdi Keith the Silly Goose Kit mitschabaude-bot ngutman oswalpalash p6l-richard - philipp-spiess pkrmf Sash Catanzarite VAC adam91holt alejandro maza andrewting19 Asleep123 bjesuiter bolismauro - cash-echo-bot Clawd conhecendocontato gtsifrikas HazAT hrdwdmrbl hugobarauna Jarvis kitze kkarimi - levifig Lloyd loukotal martinpucik mickahouan Miles mrdbstn MSch nexty5870 prathamdby - reeltimeapps RLTCmpe Rolf Fredheim rubyrunsstuff Samrat Jha snopoke wes-davis wstock YuriNachos Zach Knickerbocker - zknicker Azade ddyo Erik latitudeki5223 Manuel Maly Mourad Boustani pcty-nextgen-ios-builder Quentin Randy Torres - William Stock + steipete joaohlisboa mneves75 rahthakor joshp123 mukhtharcm maxsumrall xadenryan Tobias Bischoff magimetal + NicholasSpisak hsrvc claude jamesgroat dantelex daveonkels radek-paclt jeffersonwarrior mteam88 Eng. Juan Combetto + Mariano Belinky julianengel benithors sreekaransrinath dbhurley gupsammy cristip73 nachoiacovino Vasanth Rao Naik Sabavat lc0rp + scald andranik-sahakyan nachx639 sircrumpet peschee rafaelreis-r meaningfool ratulsarna lutr0 abhisekbasu1 + thewilloftheshadow emanuelst KristijanJovanovski osolmaz kiranjd Sebastian Barrios sheeek onutc CashWilliams manuelhettich + minghinmatthewlam myfunc buddyh mcinteerj timkrase azade-c obviyus bjesuiter superman32432432 Yurii Chukhlib + antons austinm911 blacksmith-sh[bot] imfing jarvis-medmatic mahmoudashraf93 petter-b pkrmf RandyVentures dan-dr + HeimdallStrategy jalehman jonasjancarik neist iamadig koala73 manmal ogulcancelik pasogott petradonka + VACInc zats Chris Taylor Django Navarro gabriel-trigo Kit L36 Server ngutman pcty-nextgen-service-account rubyrunsstuff + Syhids danielz1z erik-agens erikpr1994 fcatuhe henrino3 jayhickey Jonathan D. Rhyne (DJ-D) juanpablodlc jverdi + Keith the Silly Goose mitschabaude-bot mjrussell oswalpalash p6l-richard philipp-spiess Sash Catanzarite VAC adam91holt alejandro maza + andrewting19 Asleep123 bolismauro cash-echo-bot Clawd conhecendocontato evalexpr gtsifrikas HazAT hrdwdmrbl + hugobarauna Jarvis kitze kkarimi levifig Lloyd loukotal martinpucik mickahouan Miles + mrdbstn MSch nexty5870 prathamdby reeltimeapps RLTCmpe Rolf Fredheim roshanasingh4 Samrat Jha snopoke + The Admiral wes-davis wstock YuriNachos Zach Knickerbocker zknicker Azade ddyo Erik latitudeki5223 + Manuel Maly Mourad Boustani pcty-nextgen-ios-builder Quentin Randy Torres thesash William Stock

diff --git a/scripts/clawtributors-map.json b/scripts/clawtributors-map.json index 5327ebbff..9dc6bb837 100644 --- a/scripts/clawtributors-map.json +++ b/scripts/clawtributors-map.json @@ -2,7 +2,8 @@ "ensureLogins": [ "jdrhyne", "latitudeki5223", - "manmal" + "manmal", + "thesash" ], "seedCommit": "d6863f87", "placeholderAvatar": "assets/avatar-placeholder.svg", diff --git a/src/agents/tools/browser-tool.ts b/src/agents/tools/browser-tool.ts index a003d90fa..5ec8e84ac 100644 --- a/src/agents/tools/browser-tool.ts +++ b/src/agents/tools/browser-tool.ts @@ -44,6 +44,8 @@ const BROWSER_ACT_KINDS = [ type BrowserActKind = (typeof BROWSER_ACT_KINDS)[number]; +const DEFAULT_AI_SNAPSHOT_MAX_CHARS = 80_000; + // NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...]) // because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema. // The discriminator (kind) determines which properties are relevant; runtime validates. @@ -326,14 +328,19 @@ export function createBrowserTool(opts?: { : undefined; const maxChars = typeof params.maxChars === "number" && - Number.isFinite(params.maxChars) - ? params.maxChars + Number.isFinite(params.maxChars) && + params.maxChars > 0 + ? Math.floor(params.maxChars) + : undefined; + const resolvedMaxChars = + format === "ai" + ? (maxChars ?? DEFAULT_AI_SNAPSHOT_MAX_CHARS) : undefined; const snapshot = await browserSnapshot(baseUrl, { format, targetId, limit, - maxChars, + ...(resolvedMaxChars ? { maxChars: resolvedMaxChars } : {}), profile, }); if (snapshot.format === "ai") { diff --git a/src/browser/pw-tools-core.ts b/src/browser/pw-tools-core.ts index 5fb917ed8..2f22d1380 100644 --- a/src/browser/pw-tools-core.ts +++ b/src/browser/pw-tools-core.ts @@ -10,8 +10,6 @@ import { let nextUploadArmId = 0; let nextDialogArmId = 0; -const MAX_SNAPSHOT_CHARS = 80_000; - function requireRef(value: unknown): string { const ref = typeof value === "string" ? value.trim() : ""; if (!ref) throw new Error("ref is required"); @@ -44,18 +42,17 @@ export async function snapshotAiViaPlaywright(opts: { ), track: "response", }); + let snapshot = String(result?.full ?? ""); const maxChars = opts.maxChars; const limit = typeof maxChars === "number" && Number.isFinite(maxChars) && maxChars > 0 ? Math.floor(maxChars) - : MAX_SNAPSHOT_CHARS; - let snapshot = String(result?.full ?? ""); - let truncated = false; - if (snapshot.length > limit) { + : undefined; + if (limit && snapshot.length > limit) { snapshot = `${snapshot.slice(0, limit)}\n\n[...TRUNCATED - page too large]`; - truncated = true; + return { snapshot, truncated: true }; } - return truncated ? { snapshot, truncated } : { snapshot }; + return { snapshot }; } export async function clickViaPlaywright(opts: { diff --git a/src/browser/routes/agent.ts b/src/browser/routes/agent.ts index 052d9eab6..d5b8a675e 100644 --- a/src/browser/routes/agent.ts +++ b/src/browser/routes/agent.ts @@ -567,23 +567,22 @@ export function registerBrowserAgentRoutes( ? Number(req.query.maxChars) : undefined; const limit = Number.isFinite(limitRaw) ? limitRaw : undefined; - const maxChars = Number.isFinite(maxCharsRaw) ? maxCharsRaw : undefined; + const maxChars = + typeof maxCharsRaw === "number" && + Number.isFinite(maxCharsRaw) && + maxCharsRaw > 0 + ? Math.floor(maxCharsRaw) + : undefined; try { const tab = await profileCtx.ensureTabAvailable(targetId || undefined); if (format === "ai") { const pw = await requirePwAi(res, "ai snapshot"); if (!pw) return; - const resolvedMaxChars = - typeof maxChars === "number" && maxChars > 0 - ? maxChars - : typeof limit === "number" && limit > 0 - ? limit - : undefined; const snap = await pw.snapshotAiViaPlaywright({ cdpUrl: profileCtx.profile.cdpUrl, targetId: tab.targetId, - ...(resolvedMaxChars ? { maxChars: resolvedMaxChars } : {}), + ...(maxChars ? { maxChars } : {}), }); return res.json({ ok: true,