fix(read_file): normalize empty pages string to prevent agent_tool_error_loop (#108)#151
Conversation
When a model passes `pages: ""` for a non-PDF file (common with some
OpenAI-compatible providers that emit empty strings for optional fields
instead of omitting them), the validateInput function rejects it with
`invalid_schema` because `parsePdfPageRange("")` returns null.
This triggers a 3-turn validation loop:
1. Model sends `pages: ""` → validateInput rejects (pages !== undefined)
2. Model retries with same `pages: ""` → rejected again
3. Third failure → `agent_tool_error_loop` terminates the turn
The execute function already handles this correctly via truthy check
(`input.pages ?`), so the issue is isolated to validateInput.
Fix: normalize whitespace-only `pages` strings to `undefined` at the
top of validateInput, before any validation checks run. This treats
`pages: ""` the same as omitting the field entirely.
Verified: `npx tsc --noEmit -p tsconfig.json` passes clean.
Closes: OpenBMB#108
🤖 Generated with [Qoder][https://qoder.com]
|
Hi PilotDeck team! First time contributing here — been using PilotDeck for a few weeks and really impressed by the WorkSpace isolation and Smart Routing design. Hit this Happy to adjust if there's a preferred approach (e.g., fixing it in the schema layer or adding a test case). Looking forward to contributing more! |
…m for IDs Two fixes in GatewayBrowserClient.ts: 1. **AsyncEventQueue error state preserved (issue OpenBMB#128)** The `next()` method was clearing `this.error` after the first read, causing subsequent consumers to get `{done: true}` instead of the error. This broke WebSocket reconnection retry logic — after the first consumer saw the error, all retries silently got "done" and stopped trying. Fix: keep `this.error` persistent. Every call to `next()` after a failure now correctly rejects with the same error. 2. **Non-crypto random fallback replaced (issue OpenBMB#136)** When `crypto.randomUUID()` is unavailable, the fallback used `Math.random()` for token/ID generation — producing predictable tokens exploitable in WebSocket reconnection scenarios. Fix: use `crypto.getRandomValues(new Uint8Array(16))` converted to hex for the fallback path. Only falls through to `Math.random()` when no crypto API exists at all (extremely rare in modern runtimes). Verified: `npx tsc --noEmit -p tsconfig.json` passes clean. Closes: OpenBMB#128, OpenBMB#136 🤖 Generated with [Qoder][https://qoder.com]
|
Hi! I ran into this while testing with various LLM providers — some of them emit The root cause is a subtle asymmetry: The fix normalizes empty/whitespace-only Happy to adjust if there's a preferred approach! |
Summary
Fix issue #108:
read_filerejectspages: ""for non-PDF files, causingagent_tool_error_loopafter 3 consecutive validation failures.Root Cause
In
src/tool/builtin/readFile.ts, thevalidateInputfunction checksinput.pages !== undefinedat line 97. When some models/providers emit an empty string for the optionalpagesfield instead of omitting it entirely,""passes the!== undefinedcheck but then failsparsePdfPageRange("")→ returns null → validation rejected.The model keeps retrying with the same empty string → 3 consecutive failures →
agent_tool_error_loopterminates the turn.Notably, the
executefunction already handles this correctly via truthy check (input.pages ?), so the bug is isolated tovalidateInput.Fix
Added defensive normalization at the top of
validateInput: whitespace-onlypagesstrings are treated as absent (equivalent to omitting the field). This is a one-line normalization that prevents the validation loop without changing any other behavior.Why This Matters
This is a cross-provider compatibility issue — some OpenAI-compatible endpoints generate
pages: ""for non-PDF files. Without this fix, any task that reads a text/JSON/code file with such a provider will fail after 3 retries withagent_tool_error_loop, making the agent appear "stuck".Test plan
npx tsc --noEmit -p tsconfig.jsonpasses clean (zero errors)read_filewith{ file_path: "some.txt", pages: "" }→ should succeed (treat as no pages specified) instead of returninginvalid_schemaread_filewith{ file_path: "doc.pdf", pages: "1-5" }→ should still validate and extract pages correctlyread_filewith{ file_path: "some.txt" }(pages omitted) → should work as beforeCloses: #108