fix(labs): reserve and re-check each page of a scan, and read digits from the text layer

The route reserved one read's budget and checked the wire once, and a
20-page report then ran up to 40 calls for many minutes on that single
admission. Every read after the first now reserves its own share of the
day's budget and asks the wire again (aiEgressRefusal) before it is sent;
when either refuses, the scan stops there, keeps the pages already read
and names the rest with stoppedBy budget or unavailable. The run settles
against everything it reserved, with the provider's reported usage net of
its cached share, and a scan that fails after several calls is charged
for them instead of refunded in full.

A rendered PDF page now goes out with its own text layer, read by pdfjs
beside the image, so a digitally produced report's digits, decimal
separators and units come from its characters rather than from a 1600 px
picture. Splitting the PDF into single-page documents would need a PDF
writer the project does not ship; the text layer needs none and helps
every provider. A scanned page has no text layer and reads as before.

Codex's cut reply keeps the text it streamed (on a non-enumerable
property, so no log or event serialises it), and its closed rows are
salvaged like every other provider's. The merge folds a reading only into
one an earlier page listed, never two rows of the same page, and the
printed range is part of the key. OCR'd text the deadline cannot split
small enough for one read is refused up front as labs.ocr.tooLarge.
This commit is contained in:
Marc-André Bombeck committed 2026-10-21 11:10:02 +02:00
1 parent 6376710254
commit 79d905f089
26 files changed
+715 -126

No files matched your search

+1
View File
@@ -253,6 +253,7 @@ export const ERROR_CODE_CATALOGUE: Readonly<Record<string, readonly string[]>> =
"labs.ocr.pdfNeedsAnthropic",
"labs.ocr.providerUnsupported",
"labs.ocr.rateLimited",
"labs.ocr.tooLarge",
"labs.restore.invalid",
"labs.result.notFound",
"labs.unit.mismatch",