Skip to content

feat(ocr): add PDF support with light-ocr 0.5.5 - #2040

Merged
yyhhyyyyyy merged 10 commits into
devfrom
feat/light-ocr-0.5.5-pdf
Jul 27, 2026
Merged

feat(ocr): add PDF support with light-ocr 0.5.5#2040
yyhhyyyyyy merged 10 commits into
devfrom
feat/light-ocr-0.5.5-pdf

Conversation

@yyhhyyyyyy

@yyhhyyyyyy yyhhyyyyyy commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Upgrade @arcships/light-ocr to 0.5.5 and package its PDFium runtime across supported platforms.
  • Add offline, streaming PDF OCR with page-aware truncation, deterministic caching, and partial-result handling.
  • Add compact PDF attachment choices for Auto, embedded text, and OCR.
  • Preserve the exact resolved PDF representation across history, retry, export, search, and provider context.

What changed

Runtime and packaging

  • Pin the facade, runtime, model, and native packages independently.
  • Package and validate PDFium assets on macOS, Linux, and Windows.
  • Advance the packaged OCR manifest to schema v3.
  • Preserve macOS signing compatibility by encoding and securely materializing PDFium Mach-O files.
  • Extend packaged smoke tests to perform real image and PDF recognition without network access.

PDF OCR pipeline

  • Extend the helper protocol with streamed document pages, completion, output-stop, cancellation, and structured errors.
  • Enforce explicit page, pixel, file-size, token, idle-timeout, and total-timeout limits.
  • Assemble PDF OCR as a page-aware prefix with accurate included-page coverage.
  • Preserve useful partial text when deterministic resource limits are reached.
  • Add exact document cache identities and coverage-aware artifact replacement.
  • Keep turn-level text packing separate from persisted OCR artifacts.

Routing and provider context

  • Add Auto, Text, and OCR representation choices for PDF attachments.
  • Route Auto to embedded text when at least 90% of pages contain substantive text; otherwise use OCR.
  • Persist resolved PDF representations so historical messages never reopen the source file.
  • Exclude embedded PDF content when OCR is selected.
  • Escape OCR text and mark it as untrusted attachment data before sending it to providers.
  • Keep cancellation as control flow without persisting a failure or partial artifact.

Summary by CodeRabbit

  • New Features
    • Added Light OCR PDF attachment support with multi-page recognition and embedded-text handling, including representation selection (auto / embedded text / OCR) and improved availability handling.
    • Improved chat UI and previews for PDFs, showing page coverage, truncation notices, and resource-limited outcomes.
  • Improvements
    • Preserved embedded PDF text coverage through chat context, session exports, and search indexing.
    • Strengthened safety handling so embedded/PDF-derived text is treated correctly as user-provided content.
  • Documentation
    • Updated Light OCR PDF support documentation (specs, plan, and implementation checklist) to match the finalized protocol and acceptance criteria.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Light OCR now supports PDF attachments through embedded-text selection or streamed document OCR, with PDFium-aware packaging, page-aware artifacts and caching, persistence, context/export handling, renderer states, localization, and expanded validation. Agent registry and model catalog metadata are also updated.

Changes

Light OCR PDF support

Layer / File(s) Summary
Contracts and feature specification
docs/features/light-ocr-pdf-support/*, src/shared/types/*, src/shared/contracts/*
Defines PDF representation preferences, embedded-text coverage, document OCR snapshots, protocol constraints, cache identity, and migration behavior.
Packaged runtime and document execution
package.json, resources/runtime-versions.json, scripts/*, src/main/ocr/*
Pins Light OCR 0.5.5, writes schema v3 manifests, validates PDFium inventories, adds protocol v2 document streaming, snapshots PDFs, and orchestrates extraction.
Routing, persistence, and presentation
src/main/agent/*, src/main/file/*, src/main/session/*, src/main/tape/*, src/renderer/src/components/*, src/renderer/src/i18n/*
Routes PDFs between embedded text and OCR, persists coverage and page metadata, updates context/search/export behavior, and adds representation/status UI across locales.
Validation
test/main/*, test/renderer/*, .github/workflows/*
Covers protocol streaming, PDFium packaging, routing, cancellation, caching, persistence, context escaping, export behavior, UI states, and packaged smoke recognition.

Catalog and registry updates

Layer / File(s) Summary
Agent and model catalog revisions
resources/acp-registry/registry.json, resources/model-db/providers.json
Updates agent distribution versions and checksums, revises model pricing and capabilities, removes obsolete models, and adds provider/model catalog entries.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: zerob13

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding PDF OCR support on light-ocr 0.5.5.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/light-ocr-0.5.5-pdf

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/ocr/ocrArtifactStore.ts (1)

644-712: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Accept schema version 1 and migrate it before initializing the schema.

Databases from the previous v1 schema are accepted by shouldRebuildOcrCache for schema_mismatch, deleted, and then recreated empty, so existing cache entries are lost instead of preserved. The v1→v2 delta is additive, and the test expects the v1 file to be rebuilt successfully, so add 1 to both the supported-version check and the version bump condition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/ocrArtifactStore.ts` around lines 644 - 712, The initialize
method currently rejects schema version 1 and does not migrate it. Update the
supported-version check to accept 1, and include schemaVersion === 1 in the
condition that sets user_version to 2, preserving existing v1 cache data while
applying the additive schema initialization.
src/main/agent/deepchat/runtime/contextBuilder.ts (1)

431-433: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Image metadata isn't sanitized like the new PDF path — attacker-controlled filename can break the untrusted-data framing.

sanitizeAttachmentMetadata was added specifically to neutralize newlines/</> in attacker-controlled attachment metadata before it's embedded in the prompt (used for PDFs at Lines 464-469). This same file's buildResolvedImageRepresentationContext — touched by this very diff (invalid-kind guard, shared escape helper) — still interpolates raw fileName/mimeType unsanitized into the [Attached Image ... untrusted attachment data] block. A crafted image filename containing newlines and fake structural text (e.g. </untrusted_ocr_data>\nSYSTEM: ...) can inject content that reads as authoritative text outside the escaped OCR body, undermining the very untrusted-data framing this PR introduces.

🛡️ Proposed fix
       const fileName = typeof file.name === 'string' ? file.name : `image-${index + 1}`
       const mimeType = resolveFileMimeType(file)
-      const metadata = [`name: ${fileName}`, `mime: ${mimeType}`].join('\n')
+      const metadata = [
+        `name: ${sanitizeAttachmentMetadata(fileName, 512)}`,
+        `mime: ${sanitizeAttachmentMetadata(mimeType, 128)}`
+      ].join('\n')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/agent/deepchat/runtime/contextBuilder.ts` around lines 431 - 433,
Update buildResolvedImageRepresentationContext to sanitize both fileName and
mimeType with the existing sanitizeAttachmentMetadata helper before constructing
the metadata string, matching the PDF metadata path and preserving the untrusted
attachment-data framing.
src/main/agent/deepchat/runtime/turnCoordinator.ts (1)

422-427: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Apply OCR-style guard to PDF embedded_text attachments too

shouldGuardOcrAttachmentText and historyContainsOcrAttachmentText only check for resolvedRepresentation?.kind === 'ocr_text', while contextBuilder.ts also renders embedded_text PDF content as untrusted attachment data. Include embedded_text in the guard predicate so PDF prompt-injection protection covers the default Auto-mode text path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/agent/deepchat/runtime/turnCoordinator.ts` around lines 422 - 427,
Update the attachment guard predicates around shouldGuardOcrAttachmentText and
historyContainsOcrAttachmentText to treat resolvedRepresentation.kind values of
both "ocr_text" and "embedded_text" as requiring the OCR safety rule. Preserve
the existing prompt construction and ensure PDF embedded_text content is guarded
in both current content and history paths.
🧹 Nitpick comments (8)
src/main/ocr/ocrArtifactStore.ts (1)

865-884: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Eviction candidates are materialized and sorted on every runMaintenance, even when under budget.

putDocument/put call runMaintenance() on each write, and this builds + sorts a combined array of all records before checking the byte budget. The SQLite backend guards this work behind logicalBytes > maxBytes; mirroring that here avoids the per-write allocation and sort.

♻️ Suggested change
     let logicalBytes = this.logicalBytes()
+    if (logicalBytes <= this.options.maxBytes) return
     const candidates = [
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/ocrArtifactStore.ts` around lines 865 - 884, Update
runMaintenance around the logicalBytes and candidates construction so candidate
materialization and sorting occur only when logicalBytes exceeds the configured
maxBytes budget. Preserve the existing eviction behavior when over budget, while
returning without allocating or sorting candidates when storage remains within
budget.
src/main/ocr/documentTextExtractionService.ts (2)

461-508: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Near-duplicate of readImmutableImageSnapshot.

This is essentially src/main/ocr/imagePreprocessor.ts's readImmutableImageSnapshot with a different chunk size and error type. Consider extracting a shared chunked-read+SHA-256 helper parameterized by chunk size and an error factory, so the byte-limit semantics can't drift between image and document paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentTextExtractionService.ts` around lines 461 - 508,
Extract the shared chunked file-read and SHA-256 logic from
readImmutablePdfSnapshot and readImmutableImageSnapshot into a reusable helper,
parameterized by chunk size and the caller’s error factory or error type. Update
both snapshot functions to use it while preserving their existing byte-limit,
abort, empty-input, and error semantics.

510-515: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Misleading error codes for invalid configuration inputs.

normalizeDocumentSourceByteLimit reports an invalid limit as input_too_large (which the router maps to a user-facing "document too large") and normalizeGenerationTokenLimit reports an invalid limit as runtime_identity_mismatch. invalid_input describes both cases more accurately.

Also applies to: 635-644

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentTextExtractionService.ts` around lines 510 - 515, Update
normalizeDocumentSourceByteLimit and normalizeGenerationTokenLimit to throw
DocumentTextExtractionError with the invalid_input code for invalid
configuration limits, while preserving their existing validation and messages.
src/main/ocr/documentOcrArtifact.ts (1)

20-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the tokenx component from the installed dependency version.

PDF_OCR_ARTIFACT_REVISION is used as part of cached artifact identity, but tokenx=0.4.1 is hard-coded while package.json only pins ^0.4.1. When tokenx is upgraded, stale artifacts can remain keyed by the old revision and only fail on the runtime token-count re-estimate. Use the resolved package version, or add coverage that it stays in sync with the dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentOcrArtifact.ts` around lines 20 - 27, Update
PDF_OCR_ARTIFACT_REVISION to derive the tokenx component from the installed
tokenx dependency version instead of hard-coding 0.4.1. Use the resolved package
metadata so the artifact identity changes whenever tokenx is upgraded, while
preserving the existing revision components.
scripts/smoke-light-ocr.js (1)

219-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share assertExactPackageDependency with afterPack.js instead of duplicating it.

scripts/afterPack.js (Lines 441-453) defines the identical helper; only the error message differs. Both scripts already import from ./light-ocr-artifacts.mjs, which is the natural home for this pin-verification invariant. Keeping two copies risks the packaging check and the smoke check drifting apart.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/smoke-light-ocr.js` around lines 219 - 231, Move the shared
package-version validation logic from assertExactPackageDependency in
scripts/smoke-light-ocr.js into light-ocr-artifacts.mjs, then import and reuse
it from both smoke-light-ocr.js and afterPack.js. Preserve each caller’s
existing error-message behavior while removing the duplicated helper
definitions.
src/main/session/data/transcript.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated attachment-searchable-text extraction rule across two files. Both buildSearchableAttachmentText and collectUserMessageAttachmentRefs independently re-implement the identical "include ocr_text unconditionally; include embedded_text only when isPdfAttachment" rule. Extracting this into a shared helper in @shared/utils/attachmentRepresentation (which both files already import from) would prevent the two copies from silently diverging as new representation kinds are added.

  • src/main/session/data/transcript.ts#L136-174: extract the resolved.kind === 'ocr_text' | 'embedded_text' + isPdfAttachment selection logic into a shared getAttachmentSearchableText(file)-style helper.
  • src/main/tape/application/recallProjection.ts#L147-174: consume the same shared helper instead of re-deriving attachmentText locally, keeping this file's own per-attachment/per-message character budgeting on top of it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/session/data/transcript.ts` at line 1, Extract the shared attachment
searchable-text selection logic from buildSearchableAttachmentText and
collectUserMessageAttachmentRefs into `@shared/utils/attachmentRepresentation` as
a getAttachmentSearchableText(file)-style helper. Have the helper always include
ocr_text and include embedded_text only for PDF attachments, then update both
callers to use it while preserving recallProjection’s existing per-attachment
and per-message character budgeting.
test/main/ocr/lightOcrProtocol.test.ts (1)

15-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the exported LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS instead of the literal.

maxTotalPixels hardcodes 100 * 1024 * 1024 while the sibling limits come from exported constants; the test will silently drift if the protocol limit changes (test/main/ocr/lightOcrHelper.test.ts already imports it).

♻️ Proposed change
 import {
   LIGHT_OCR_DOCUMENT_MAX_LINE_CHARACTERS,
   LIGHT_OCR_DOCUMENT_MAX_PAGE_PIXELS,
+  LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS,
   LIGHT_OCR_HELPER_MAX_INPUT_BYTES,
@@
-  maxTotalPixels: 100 * 1024 * 1024
+  maxTotalPixels: LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/ocr/lightOcrProtocol.test.ts` around lines 15 - 22, Update the
documentOptions.maxTotalPixels assignment to reuse the exported
LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS constant instead of the hardcoded 100 * 1024
* 1024 expression, matching the sibling OCR limit fields and existing test
usage.
src/main/ocr/attachmentCapabilityRouter.ts (1)

360-378: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider running image and document OCR resolution concurrently.

resolveImageOcrCandidates and resolveDocumentOcrCandidates operate on disjoint candidate lists and only append to shared issues/routingDiagnostics/ocrDiagnostics collections, which is safe to do from concurrently-scheduled async calls in JS (no true parallel mutation). Awaiting them sequentially adds the document OCR latency on top of the image-batch latency whenever both are present in a turn.

♻️ Proposed refactor
-    await this.resolveImageOcrCandidates(
-      processableImages,
-      backend,
-      maxFileSize,
-      issues,
-      routingDiagnostics,
-      ocrDiagnostics,
-      signal
-    )
-    await this.resolveDocumentOcrCandidates(
-      processableDocuments,
-      backend,
-      maxFileSize,
-      issues,
-      routingDiagnostics,
-      ocrDiagnostics,
-      signal
-    )
+    await Promise.all([
+      this.resolveImageOcrCandidates(
+        processableImages,
+        backend,
+        maxFileSize,
+        issues,
+        routingDiagnostics,
+        ocrDiagnostics,
+        signal
+      ),
+      this.resolveDocumentOcrCandidates(
+        processableDocuments,
+        backend,
+        maxFileSize,
+        issues,
+        routingDiagnostics,
+        ocrDiagnostics,
+        signal
+      )
+    ])

Note: the underlying OCR process host may still serialize IPC internally, so real-world gain depends on that layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/attachmentCapabilityRouter.ts` around lines 360 - 378, Update
the orchestration around resolveImageOcrCandidates and
resolveDocumentOcrCandidates to start both async resolutions concurrently and
await their combined completion, while preserving their existing arguments and
shared diagnostic/issue collection behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 112: Add the required Light OCR transitive dependencies to the
package.json dependency declarations: `@arcships/light-ocr-runtime` at 0.1.5,
`@arcships/light-ocr-model-ppocrv6-small` at 0.3.4, and each `@arcships/light-ocr-`*
native package at 0.5.5, alongside the existing `@arcships/light-ocr` entry. Keep
versions aligned with resources/runtime-versions.json and update the lockfile
consistently.

In `@resources/model-db/providers.json`:
- Around line 96770-96779: The openai/gpt-5-chat entry is incomplete and lacks
capability and pricing metadata. Update the model definition with the
appropriate modalities, tool_call, reasoning, and cost fields consistent with
neighbouring entries, and verify that retaining this stub is intentional after
its removal from the inference provider.
- Around line 90958-90964: Correct the model type from imageGeneration to chat
for google/gemini-2.5-flash, google/gemini-2.5-pro,
google/gemini-2.5-flash-lite, google/gemini-2.0-flash,
google/gemini-2.0-flash-lite-001, and qwen/qwen3-vl-plus. Apply this change at
resources/model-db/providers.json lines 90958-90964, 91209-91215, 91265-91271,
96451-96456, 96476-96481, and 97044-97055 respectively, preserving all other
model metadata.

In `@src/main/file/adapters/PdfFileAdapter.ts`:
- Around line 30-40: Update readPdfData in the PdfFileAdapter so filesystem
failures from fs.stat and fs.readFile are handled by the existing catch path and
resolve to undefined after logging. Widen the try boundary to cover the complete
file-loading and parsing flow, ensuring loadPdfData does not cache a permanently
rejected promise while preserving the existing successful and oversized-file
behavior.

In `@src/main/ocr/attachmentCapabilityRouter.ts`:
- Around line 749-792: Thread the routing issues collection into
applyTurnOcrTextBudget and update its call site so the budget-driven OCR-empty
downgrade uses the existing markUnavailable/appendIssue path instead of
assigning resolvedRepresentation directly. Preserve attachment indexing when
recording the issue, ensuring buildPreparationSummary surfaces the unavailable
document rather than returning an empty ready summary.

In `@src/main/ocr/documentOcrArtifact.ts`:
- Around line 116-129: Update LightOcrDocumentArtifact.append to avoid calling
fitDocumentOcrPages on every page: maintain the accumulated text and estimated
token count incrementally, compute only the new page’s contribution while within
both budgets, and append directly when it fits. When the new page exceeds a
budget, use the existing fitTruncatedPrefix path once to produce the truncated
result and mark the artifact as truncated, preserving page ordering and return
statuses.

In `@src/main/ocr/documentTextExtractionService.ts`:
- Around line 285-301: When the cached artifact becomes invalid after
truncateDocumentOcrArtifact and withSourcePageCountHint in the cache-hit path,
treat it as a cache miss instead of throwing DocumentTextExtractionError. Only
return the cached result when isValidDocumentOcrArtifact succeeds; otherwise
continue into the existing fresh extraction flow.

In `@src/main/ocr/lightOcrProcessHost.ts`:
- Around line 1070-1115: Update sendDocumentStop to use a dedicated, longer
document-stop timeout, preferably via a documentStopTimeoutMs option with a
default above a single page’s render and OCR duration, instead of cancelGraceMs.
Keep the existing acknowledgement cleanup and process-disposal behavior for
genuine stop timeouts, while ensuring output-limit truncation can allow the
helper to finish and preserve the partial document result.

In `@src/main/ocr/ocrRuntimeAssetResolver.ts`:
- Around line 434-440: Replace JSON.stringify-based inventory equality in
matchesArtifactInventory with explicit comparisons across the fixed groups
nativeCode, pdfiumCode, pdfiumLoader, and other, checking each group’s length
and entries element by element. Apply the same order-independent comparison
against expectedInventory in scripts/smoke-light-ocr.js at lines 468-470; both
sites must avoid relying on object key order.

---

Outside diff comments:
In `@src/main/agent/deepchat/runtime/contextBuilder.ts`:
- Around line 431-433: Update buildResolvedImageRepresentationContext to
sanitize both fileName and mimeType with the existing sanitizeAttachmentMetadata
helper before constructing the metadata string, matching the PDF metadata path
and preserving the untrusted attachment-data framing.

In `@src/main/agent/deepchat/runtime/turnCoordinator.ts`:
- Around line 422-427: Update the attachment guard predicates around
shouldGuardOcrAttachmentText and historyContainsOcrAttachmentText to treat
resolvedRepresentation.kind values of both "ocr_text" and "embedded_text" as
requiring the OCR safety rule. Preserve the existing prompt construction and
ensure PDF embedded_text content is guarded in both current content and history
paths.

In `@src/main/ocr/ocrArtifactStore.ts`:
- Around line 644-712: The initialize method currently rejects schema version 1
and does not migrate it. Update the supported-version check to accept 1, and
include schemaVersion === 1 in the condition that sets user_version to 2,
preserving existing v1 cache data while applying the additive schema
initialization.

---

Nitpick comments:
In `@scripts/smoke-light-ocr.js`:
- Around line 219-231: Move the shared package-version validation logic from
assertExactPackageDependency in scripts/smoke-light-ocr.js into
light-ocr-artifacts.mjs, then import and reuse it from both smoke-light-ocr.js
and afterPack.js. Preserve each caller’s existing error-message behavior while
removing the duplicated helper definitions.

In `@src/main/ocr/attachmentCapabilityRouter.ts`:
- Around line 360-378: Update the orchestration around resolveImageOcrCandidates
and resolveDocumentOcrCandidates to start both async resolutions concurrently
and await their combined completion, while preserving their existing arguments
and shared diagnostic/issue collection behavior.

In `@src/main/ocr/documentOcrArtifact.ts`:
- Around line 20-27: Update PDF_OCR_ARTIFACT_REVISION to derive the tokenx
component from the installed tokenx dependency version instead of hard-coding
0.4.1. Use the resolved package metadata so the artifact identity changes
whenever tokenx is upgraded, while preserving the existing revision components.

In `@src/main/ocr/documentTextExtractionService.ts`:
- Around line 461-508: Extract the shared chunked file-read and SHA-256 logic
from readImmutablePdfSnapshot and readImmutableImageSnapshot into a reusable
helper, parameterized by chunk size and the caller’s error factory or error
type. Update both snapshot functions to use it while preserving their existing
byte-limit, abort, empty-input, and error semantics.
- Around line 510-515: Update normalizeDocumentSourceByteLimit and
normalizeGenerationTokenLimit to throw DocumentTextExtractionError with the
invalid_input code for invalid configuration limits, while preserving their
existing validation and messages.

In `@src/main/ocr/ocrArtifactStore.ts`:
- Around line 865-884: Update runMaintenance around the logicalBytes and
candidates construction so candidate materialization and sorting occur only when
logicalBytes exceeds the configured maxBytes budget. Preserve the existing
eviction behavior when over budget, while returning without allocating or
sorting candidates when storage remains within budget.

In `@src/main/session/data/transcript.ts`:
- Line 1: Extract the shared attachment searchable-text selection logic from
buildSearchableAttachmentText and collectUserMessageAttachmentRefs into
`@shared/utils/attachmentRepresentation` as a
getAttachmentSearchableText(file)-style helper. Have the helper always include
ocr_text and include embedded_text only for PDF attachments, then update both
callers to use it while preserving recallProjection’s existing per-attachment
and per-message character budgeting.

In `@test/main/ocr/lightOcrProtocol.test.ts`:
- Around line 15-22: Update the documentOptions.maxTotalPixels assignment to
reuse the exported LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS constant instead of the
hardcoded 100 * 1024 * 1024 expression, matching the sibling OCR limit fields
and existing test usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 26bedcb9-fa3d-4b41-9296-a7b0a26a4349

📥 Commits

Reviewing files that changed from the base of the PR and between 1b40063 and 1921303.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/renderer/src/lib/icons/icon-collections.generated.ts is excluded by !**/*.generated.*
  • src/renderer/src/lib/icons/icon-whitelist.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (99)
  • docs/features/light-ocr-integration/spec.md
  • docs/features/light-ocr-pdf-support/plan.md
  • docs/features/light-ocr-pdf-support/spec.md
  • docs/features/light-ocr-pdf-support/tasks.md
  • package.json
  • resources/acp-registry/registry.json
  • resources/model-db/providers.json
  • resources/runtime-versions.json
  • scripts/afterPack.js
  • scripts/install-runtime.mjs
  • scripts/light-ocr-artifacts.mjs
  • scripts/smoke-light-ocr.js
  • src/main/agent/deepchat/runtime/contextBuilder.ts
  • src/main/agent/deepchat/runtime/turnCoordinator.ts
  • src/main/exporter/agentSessionExporter.ts
  • src/main/exporter/formats/userMessageText.ts
  • src/main/file/adapters/PdfFileAdapter.ts
  • src/main/file/index.ts
  • src/main/lightOcrHelperEntry.ts
  • src/main/ocr/attachmentCapabilityRouter.ts
  • src/main/ocr/documentOcrArtifact.ts
  • src/main/ocr/documentTextExtractionService.ts
  • src/main/ocr/imageTextExtractionService.ts
  • src/main/ocr/lightOcrHelper.ts
  • src/main/ocr/lightOcrNativePayload.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • src/main/ocr/lightOcrProtocol.ts
  • src/main/ocr/ocrArtifactStore.ts
  • src/main/ocr/ocrRuntimeAssetResolver.ts
  • src/main/ocr/ocrRuntimeService.ts
  • src/main/ocr/ocrSourceSnapshotBudget.ts
  • src/main/session/data/transcript.ts
  • src/main/tape/application/recallProjection.ts
  • src/main/tape/infrastructure/sqlite/tapeSearchProjectionStore.ts
  • src/renderer/src/components/chat/AttachmentPreparationDialog.vue
  • src/renderer/src/components/chat/ChatAttachmentItem.vue
  • src/renderer/src/components/chat/PendingInputLane.vue
  • src/renderer/src/components/chat/nodes/FileAttachmentView.vue
  • src/renderer/src/features/chat-page/composables/useComposerSubmit.ts
  • src/renderer/src/features/chat-page/model/composerDraftState.ts
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/de-DE/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/es-ES/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/id-ID/chat.json
  • src/renderer/src/i18n/it-IT/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/ms-MY/chat.json
  • src/renderer/src/i18n/pl-PL/chat.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/tr-TR/chat.json
  • src/renderer/src/i18n/vi-VN/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/renderer/src/pages/NewThreadPage.vue
  • src/shared/chat.d.ts
  • src/shared/contracts/common.ts
  • src/shared/contracts/domainSchemas.ts
  • src/shared/types/agent-interface.d.ts
  • src/shared/types/attachment.ts
  • src/shared/types/core/chat.ts
  • src/shared/utils/attachmentRepresentation.ts
  • test/fixtures/light-ocr/fake-helper.mjs
  • test/main/agent/deepchat/runtime/contextBuilder.test.ts
  • test/main/exporter/agentSessionExporter.test.ts
  • test/main/exporter/userMessageText.test.ts
  • test/main/file/pdfFileAdapter.test.ts
  • test/main/ocr/attachmentCapabilityRouter.test.ts
  • test/main/ocr/documentOcrArtifact.test.ts
  • test/main/ocr/documentOcrArtifactStore.test.ts
  • test/main/ocr/documentTextExtractionService.test.ts
  • test/main/ocr/lightOcrHelper.test.ts
  • test/main/ocr/lightOcrNativePayload.test.ts
  • test/main/ocr/lightOcrProcessHost.test.ts
  • test/main/ocr/lightOcrProtocol.test.ts
  • test/main/ocr/ocrRuntimeAssetResolver.test.ts
  • test/main/ocr/ocrRuntimeService.test.ts
  • test/main/ocr/ocrSourceSnapshotBudget.test.ts
  • test/main/ocr/routes.test.ts
  • test/main/scripts/afterPack.test.ts
  • test/main/scripts/installRuntime.test.ts
  • test/main/scripts/lightOcrArtifacts.test.ts
  • test/main/scripts/smokeLightOcr.test.ts
  • test/main/session/data/tables/deepchatPendingInputsTable.test.ts
  • test/main/session/data/tapeRecall.test.ts
  • test/main/session/data/transcript.test.ts
  • test/main/shared/attachmentRepresentation.test.ts
  • test/renderer/components/ChatAttachmentItem.test.ts
  • test/renderer/components/FileAttachmentView.test.ts
  • test/renderer/components/NewThreadPage.test.ts
  • test/renderer/components/fileAttachmentNode.test.ts
  • test/renderer/features/chat-page/composables/useComposerSubmit.test.ts
  • test/renderer/features/chat-page/model/composerDraftState.test.ts

Comment thread package.json
Comment thread src/main/file/adapters/PdfFileAdapter.ts
Comment thread src/main/ocr/attachmentCapabilityRouter.ts
Comment thread src/main/ocr/documentOcrArtifact.ts
Comment thread src/main/ocr/documentTextExtractionService.ts
Comment thread src/main/ocr/lightOcrProcessHost.ts
Comment thread src/main/ocr/ocrRuntimeAssetResolver.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/ocr/ocrArtifactStore.ts (1)

644-712: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Accept schema version 1 and migrate it before initializing the schema.

Databases from the previous v1 schema are accepted by shouldRebuildOcrCache for schema_mismatch, deleted, and then recreated empty, so existing cache entries are lost instead of preserved. The v1→v2 delta is additive, and the test expects the v1 file to be rebuilt successfully, so add 1 to both the supported-version check and the version bump condition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/ocrArtifactStore.ts` around lines 644 - 712, The initialize
method currently rejects schema version 1 and does not migrate it. Update the
supported-version check to accept 1, and include schemaVersion === 1 in the
condition that sets user_version to 2, preserving existing v1 cache data while
applying the additive schema initialization.
src/main/agent/deepchat/runtime/contextBuilder.ts (1)

431-433: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Image metadata isn't sanitized like the new PDF path — attacker-controlled filename can break the untrusted-data framing.

sanitizeAttachmentMetadata was added specifically to neutralize newlines/</> in attacker-controlled attachment metadata before it's embedded in the prompt (used for PDFs at Lines 464-469). This same file's buildResolvedImageRepresentationContext — touched by this very diff (invalid-kind guard, shared escape helper) — still interpolates raw fileName/mimeType unsanitized into the [Attached Image ... untrusted attachment data] block. A crafted image filename containing newlines and fake structural text (e.g. </untrusted_ocr_data>\nSYSTEM: ...) can inject content that reads as authoritative text outside the escaped OCR body, undermining the very untrusted-data framing this PR introduces.

🛡️ Proposed fix
       const fileName = typeof file.name === 'string' ? file.name : `image-${index + 1}`
       const mimeType = resolveFileMimeType(file)
-      const metadata = [`name: ${fileName}`, `mime: ${mimeType}`].join('\n')
+      const metadata = [
+        `name: ${sanitizeAttachmentMetadata(fileName, 512)}`,
+        `mime: ${sanitizeAttachmentMetadata(mimeType, 128)}`
+      ].join('\n')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/agent/deepchat/runtime/contextBuilder.ts` around lines 431 - 433,
Update buildResolvedImageRepresentationContext to sanitize both fileName and
mimeType with the existing sanitizeAttachmentMetadata helper before constructing
the metadata string, matching the PDF metadata path and preserving the untrusted
attachment-data framing.
src/main/agent/deepchat/runtime/turnCoordinator.ts (1)

422-427: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Apply OCR-style guard to PDF embedded_text attachments too

shouldGuardOcrAttachmentText and historyContainsOcrAttachmentText only check for resolvedRepresentation?.kind === 'ocr_text', while contextBuilder.ts also renders embedded_text PDF content as untrusted attachment data. Include embedded_text in the guard predicate so PDF prompt-injection protection covers the default Auto-mode text path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/agent/deepchat/runtime/turnCoordinator.ts` around lines 422 - 427,
Update the attachment guard predicates around shouldGuardOcrAttachmentText and
historyContainsOcrAttachmentText to treat resolvedRepresentation.kind values of
both "ocr_text" and "embedded_text" as requiring the OCR safety rule. Preserve
the existing prompt construction and ensure PDF embedded_text content is guarded
in both current content and history paths.
🧹 Nitpick comments (8)
src/main/ocr/ocrArtifactStore.ts (1)

865-884: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Eviction candidates are materialized and sorted on every runMaintenance, even when under budget.

putDocument/put call runMaintenance() on each write, and this builds + sorts a combined array of all records before checking the byte budget. The SQLite backend guards this work behind logicalBytes > maxBytes; mirroring that here avoids the per-write allocation and sort.

♻️ Suggested change
     let logicalBytes = this.logicalBytes()
+    if (logicalBytes <= this.options.maxBytes) return
     const candidates = [
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/ocrArtifactStore.ts` around lines 865 - 884, Update
runMaintenance around the logicalBytes and candidates construction so candidate
materialization and sorting occur only when logicalBytes exceeds the configured
maxBytes budget. Preserve the existing eviction behavior when over budget, while
returning without allocating or sorting candidates when storage remains within
budget.
src/main/ocr/documentTextExtractionService.ts (2)

461-508: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Near-duplicate of readImmutableImageSnapshot.

This is essentially src/main/ocr/imagePreprocessor.ts's readImmutableImageSnapshot with a different chunk size and error type. Consider extracting a shared chunked-read+SHA-256 helper parameterized by chunk size and an error factory, so the byte-limit semantics can't drift between image and document paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentTextExtractionService.ts` around lines 461 - 508,
Extract the shared chunked file-read and SHA-256 logic from
readImmutablePdfSnapshot and readImmutableImageSnapshot into a reusable helper,
parameterized by chunk size and the caller’s error factory or error type. Update
both snapshot functions to use it while preserving their existing byte-limit,
abort, empty-input, and error semantics.

510-515: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Misleading error codes for invalid configuration inputs.

normalizeDocumentSourceByteLimit reports an invalid limit as input_too_large (which the router maps to a user-facing "document too large") and normalizeGenerationTokenLimit reports an invalid limit as runtime_identity_mismatch. invalid_input describes both cases more accurately.

Also applies to: 635-644

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentTextExtractionService.ts` around lines 510 - 515, Update
normalizeDocumentSourceByteLimit and normalizeGenerationTokenLimit to throw
DocumentTextExtractionError with the invalid_input code for invalid
configuration limits, while preserving their existing validation and messages.
src/main/ocr/documentOcrArtifact.ts (1)

20-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the tokenx component from the installed dependency version.

PDF_OCR_ARTIFACT_REVISION is used as part of cached artifact identity, but tokenx=0.4.1 is hard-coded while package.json only pins ^0.4.1. When tokenx is upgraded, stale artifacts can remain keyed by the old revision and only fail on the runtime token-count re-estimate. Use the resolved package version, or add coverage that it stays in sync with the dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentOcrArtifact.ts` around lines 20 - 27, Update
PDF_OCR_ARTIFACT_REVISION to derive the tokenx component from the installed
tokenx dependency version instead of hard-coding 0.4.1. Use the resolved package
metadata so the artifact identity changes whenever tokenx is upgraded, while
preserving the existing revision components.
scripts/smoke-light-ocr.js (1)

219-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share assertExactPackageDependency with afterPack.js instead of duplicating it.

scripts/afterPack.js (Lines 441-453) defines the identical helper; only the error message differs. Both scripts already import from ./light-ocr-artifacts.mjs, which is the natural home for this pin-verification invariant. Keeping two copies risks the packaging check and the smoke check drifting apart.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/smoke-light-ocr.js` around lines 219 - 231, Move the shared
package-version validation logic from assertExactPackageDependency in
scripts/smoke-light-ocr.js into light-ocr-artifacts.mjs, then import and reuse
it from both smoke-light-ocr.js and afterPack.js. Preserve each caller’s
existing error-message behavior while removing the duplicated helper
definitions.
src/main/session/data/transcript.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated attachment-searchable-text extraction rule across two files. Both buildSearchableAttachmentText and collectUserMessageAttachmentRefs independently re-implement the identical "include ocr_text unconditionally; include embedded_text only when isPdfAttachment" rule. Extracting this into a shared helper in @shared/utils/attachmentRepresentation (which both files already import from) would prevent the two copies from silently diverging as new representation kinds are added.

  • src/main/session/data/transcript.ts#L136-174: extract the resolved.kind === 'ocr_text' | 'embedded_text' + isPdfAttachment selection logic into a shared getAttachmentSearchableText(file)-style helper.
  • src/main/tape/application/recallProjection.ts#L147-174: consume the same shared helper instead of re-deriving attachmentText locally, keeping this file's own per-attachment/per-message character budgeting on top of it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/session/data/transcript.ts` at line 1, Extract the shared attachment
searchable-text selection logic from buildSearchableAttachmentText and
collectUserMessageAttachmentRefs into `@shared/utils/attachmentRepresentation` as
a getAttachmentSearchableText(file)-style helper. Have the helper always include
ocr_text and include embedded_text only for PDF attachments, then update both
callers to use it while preserving recallProjection’s existing per-attachment
and per-message character budgeting.
test/main/ocr/lightOcrProtocol.test.ts (1)

15-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the exported LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS instead of the literal.

maxTotalPixels hardcodes 100 * 1024 * 1024 while the sibling limits come from exported constants; the test will silently drift if the protocol limit changes (test/main/ocr/lightOcrHelper.test.ts already imports it).

♻️ Proposed change
 import {
   LIGHT_OCR_DOCUMENT_MAX_LINE_CHARACTERS,
   LIGHT_OCR_DOCUMENT_MAX_PAGE_PIXELS,
+  LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS,
   LIGHT_OCR_HELPER_MAX_INPUT_BYTES,
@@
-  maxTotalPixels: 100 * 1024 * 1024
+  maxTotalPixels: LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/ocr/lightOcrProtocol.test.ts` around lines 15 - 22, Update the
documentOptions.maxTotalPixels assignment to reuse the exported
LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS constant instead of the hardcoded 100 * 1024
* 1024 expression, matching the sibling OCR limit fields and existing test
usage.
src/main/ocr/attachmentCapabilityRouter.ts (1)

360-378: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider running image and document OCR resolution concurrently.

resolveImageOcrCandidates and resolveDocumentOcrCandidates operate on disjoint candidate lists and only append to shared issues/routingDiagnostics/ocrDiagnostics collections, which is safe to do from concurrently-scheduled async calls in JS (no true parallel mutation). Awaiting them sequentially adds the document OCR latency on top of the image-batch latency whenever both are present in a turn.

♻️ Proposed refactor
-    await this.resolveImageOcrCandidates(
-      processableImages,
-      backend,
-      maxFileSize,
-      issues,
-      routingDiagnostics,
-      ocrDiagnostics,
-      signal
-    )
-    await this.resolveDocumentOcrCandidates(
-      processableDocuments,
-      backend,
-      maxFileSize,
-      issues,
-      routingDiagnostics,
-      ocrDiagnostics,
-      signal
-    )
+    await Promise.all([
+      this.resolveImageOcrCandidates(
+        processableImages,
+        backend,
+        maxFileSize,
+        issues,
+        routingDiagnostics,
+        ocrDiagnostics,
+        signal
+      ),
+      this.resolveDocumentOcrCandidates(
+        processableDocuments,
+        backend,
+        maxFileSize,
+        issues,
+        routingDiagnostics,
+        ocrDiagnostics,
+        signal
+      )
+    ])

Note: the underlying OCR process host may still serialize IPC internally, so real-world gain depends on that layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/attachmentCapabilityRouter.ts` around lines 360 - 378, Update
the orchestration around resolveImageOcrCandidates and
resolveDocumentOcrCandidates to start both async resolutions concurrently and
await their combined completion, while preserving their existing arguments and
shared diagnostic/issue collection behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@package.json`:
- Line 112: Add the required Light OCR transitive dependencies to the
package.json dependency declarations: `@arcships/light-ocr-runtime` at 0.1.5,
`@arcships/light-ocr-model-ppocrv6-small` at 0.3.4, and each `@arcships/light-ocr-`*
native package at 0.5.5, alongside the existing `@arcships/light-ocr` entry. Keep
versions aligned with resources/runtime-versions.json and update the lockfile
consistently.

In `@resources/model-db/providers.json`:
- Around line 96770-96779: The openai/gpt-5-chat entry is incomplete and lacks
capability and pricing metadata. Update the model definition with the
appropriate modalities, tool_call, reasoning, and cost fields consistent with
neighbouring entries, and verify that retaining this stub is intentional after
its removal from the inference provider.
- Around line 90958-90964: Correct the model type from imageGeneration to chat
for google/gemini-2.5-flash, google/gemini-2.5-pro,
google/gemini-2.5-flash-lite, google/gemini-2.0-flash,
google/gemini-2.0-flash-lite-001, and qwen/qwen3-vl-plus. Apply this change at
resources/model-db/providers.json lines 90958-90964, 91209-91215, 91265-91271,
96451-96456, 96476-96481, and 97044-97055 respectively, preserving all other
model metadata.

In `@src/main/file/adapters/PdfFileAdapter.ts`:
- Around line 30-40: Update readPdfData in the PdfFileAdapter so filesystem
failures from fs.stat and fs.readFile are handled by the existing catch path and
resolve to undefined after logging. Widen the try boundary to cover the complete
file-loading and parsing flow, ensuring loadPdfData does not cache a permanently
rejected promise while preserving the existing successful and oversized-file
behavior.

In `@src/main/ocr/attachmentCapabilityRouter.ts`:
- Around line 749-792: Thread the routing issues collection into
applyTurnOcrTextBudget and update its call site so the budget-driven OCR-empty
downgrade uses the existing markUnavailable/appendIssue path instead of
assigning resolvedRepresentation directly. Preserve attachment indexing when
recording the issue, ensuring buildPreparationSummary surfaces the unavailable
document rather than returning an empty ready summary.

In `@src/main/ocr/documentOcrArtifact.ts`:
- Around line 116-129: Update LightOcrDocumentArtifact.append to avoid calling
fitDocumentOcrPages on every page: maintain the accumulated text and estimated
token count incrementally, compute only the new page’s contribution while within
both budgets, and append directly when it fits. When the new page exceeds a
budget, use the existing fitTruncatedPrefix path once to produce the truncated
result and mark the artifact as truncated, preserving page ordering and return
statuses.

In `@src/main/ocr/documentTextExtractionService.ts`:
- Around line 285-301: When the cached artifact becomes invalid after
truncateDocumentOcrArtifact and withSourcePageCountHint in the cache-hit path,
treat it as a cache miss instead of throwing DocumentTextExtractionError. Only
return the cached result when isValidDocumentOcrArtifact succeeds; otherwise
continue into the existing fresh extraction flow.

In `@src/main/ocr/lightOcrProcessHost.ts`:
- Around line 1070-1115: Update sendDocumentStop to use a dedicated, longer
document-stop timeout, preferably via a documentStopTimeoutMs option with a
default above a single page’s render and OCR duration, instead of cancelGraceMs.
Keep the existing acknowledgement cleanup and process-disposal behavior for
genuine stop timeouts, while ensuring output-limit truncation can allow the
helper to finish and preserve the partial document result.

In `@src/main/ocr/ocrRuntimeAssetResolver.ts`:
- Around line 434-440: Replace JSON.stringify-based inventory equality in
matchesArtifactInventory with explicit comparisons across the fixed groups
nativeCode, pdfiumCode, pdfiumLoader, and other, checking each group’s length
and entries element by element. Apply the same order-independent comparison
against expectedInventory in scripts/smoke-light-ocr.js at lines 468-470; both
sites must avoid relying on object key order.

---

Outside diff comments:
In `@src/main/agent/deepchat/runtime/contextBuilder.ts`:
- Around line 431-433: Update buildResolvedImageRepresentationContext to
sanitize both fileName and mimeType with the existing sanitizeAttachmentMetadata
helper before constructing the metadata string, matching the PDF metadata path
and preserving the untrusted attachment-data framing.

In `@src/main/agent/deepchat/runtime/turnCoordinator.ts`:
- Around line 422-427: Update the attachment guard predicates around
shouldGuardOcrAttachmentText and historyContainsOcrAttachmentText to treat
resolvedRepresentation.kind values of both "ocr_text" and "embedded_text" as
requiring the OCR safety rule. Preserve the existing prompt construction and
ensure PDF embedded_text content is guarded in both current content and history
paths.

In `@src/main/ocr/ocrArtifactStore.ts`:
- Around line 644-712: The initialize method currently rejects schema version 1
and does not migrate it. Update the supported-version check to accept 1, and
include schemaVersion === 1 in the condition that sets user_version to 2,
preserving existing v1 cache data while applying the additive schema
initialization.

---

Nitpick comments:
In `@scripts/smoke-light-ocr.js`:
- Around line 219-231: Move the shared package-version validation logic from
assertExactPackageDependency in scripts/smoke-light-ocr.js into
light-ocr-artifacts.mjs, then import and reuse it from both smoke-light-ocr.js
and afterPack.js. Preserve each caller’s existing error-message behavior while
removing the duplicated helper definitions.

In `@src/main/ocr/attachmentCapabilityRouter.ts`:
- Around line 360-378: Update the orchestration around resolveImageOcrCandidates
and resolveDocumentOcrCandidates to start both async resolutions concurrently
and await their combined completion, while preserving their existing arguments
and shared diagnostic/issue collection behavior.

In `@src/main/ocr/documentOcrArtifact.ts`:
- Around line 20-27: Update PDF_OCR_ARTIFACT_REVISION to derive the tokenx
component from the installed tokenx dependency version instead of hard-coding
0.4.1. Use the resolved package metadata so the artifact identity changes
whenever tokenx is upgraded, while preserving the existing revision components.

In `@src/main/ocr/documentTextExtractionService.ts`:
- Around line 461-508: Extract the shared chunked file-read and SHA-256 logic
from readImmutablePdfSnapshot and readImmutableImageSnapshot into a reusable
helper, parameterized by chunk size and the caller’s error factory or error
type. Update both snapshot functions to use it while preserving their existing
byte-limit, abort, empty-input, and error semantics.
- Around line 510-515: Update normalizeDocumentSourceByteLimit and
normalizeGenerationTokenLimit to throw DocumentTextExtractionError with the
invalid_input code for invalid configuration limits, while preserving their
existing validation and messages.

In `@src/main/ocr/ocrArtifactStore.ts`:
- Around line 865-884: Update runMaintenance around the logicalBytes and
candidates construction so candidate materialization and sorting occur only when
logicalBytes exceeds the configured maxBytes budget. Preserve the existing
eviction behavior when over budget, while returning without allocating or
sorting candidates when storage remains within budget.

In `@src/main/session/data/transcript.ts`:
- Line 1: Extract the shared attachment searchable-text selection logic from
buildSearchableAttachmentText and collectUserMessageAttachmentRefs into
`@shared/utils/attachmentRepresentation` as a
getAttachmentSearchableText(file)-style helper. Have the helper always include
ocr_text and include embedded_text only for PDF attachments, then update both
callers to use it while preserving recallProjection’s existing per-attachment
and per-message character budgeting.

In `@test/main/ocr/lightOcrProtocol.test.ts`:
- Around line 15-22: Update the documentOptions.maxTotalPixels assignment to
reuse the exported LIGHT_OCR_DOCUMENT_MAX_TOTAL_PIXELS constant instead of the
hardcoded 100 * 1024 * 1024 expression, matching the sibling OCR limit fields
and existing test usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 26bedcb9-fa3d-4b41-9296-a7b0a26a4349

📥 Commits

Reviewing files that changed from the base of the PR and between 1b40063 and 1921303.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src/renderer/src/lib/icons/icon-collections.generated.ts is excluded by !**/*.generated.*
  • src/renderer/src/lib/icons/icon-whitelist.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (99)
  • docs/features/light-ocr-integration/spec.md
  • docs/features/light-ocr-pdf-support/plan.md
  • docs/features/light-ocr-pdf-support/spec.md
  • docs/features/light-ocr-pdf-support/tasks.md
  • package.json
  • resources/acp-registry/registry.json
  • resources/model-db/providers.json
  • resources/runtime-versions.json
  • scripts/afterPack.js
  • scripts/install-runtime.mjs
  • scripts/light-ocr-artifacts.mjs
  • scripts/smoke-light-ocr.js
  • src/main/agent/deepchat/runtime/contextBuilder.ts
  • src/main/agent/deepchat/runtime/turnCoordinator.ts
  • src/main/exporter/agentSessionExporter.ts
  • src/main/exporter/formats/userMessageText.ts
  • src/main/file/adapters/PdfFileAdapter.ts
  • src/main/file/index.ts
  • src/main/lightOcrHelperEntry.ts
  • src/main/ocr/attachmentCapabilityRouter.ts
  • src/main/ocr/documentOcrArtifact.ts
  • src/main/ocr/documentTextExtractionService.ts
  • src/main/ocr/imageTextExtractionService.ts
  • src/main/ocr/lightOcrHelper.ts
  • src/main/ocr/lightOcrNativePayload.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • src/main/ocr/lightOcrProtocol.ts
  • src/main/ocr/ocrArtifactStore.ts
  • src/main/ocr/ocrRuntimeAssetResolver.ts
  • src/main/ocr/ocrRuntimeService.ts
  • src/main/ocr/ocrSourceSnapshotBudget.ts
  • src/main/session/data/transcript.ts
  • src/main/tape/application/recallProjection.ts
  • src/main/tape/infrastructure/sqlite/tapeSearchProjectionStore.ts
  • src/renderer/src/components/chat/AttachmentPreparationDialog.vue
  • src/renderer/src/components/chat/ChatAttachmentItem.vue
  • src/renderer/src/components/chat/PendingInputLane.vue
  • src/renderer/src/components/chat/nodes/FileAttachmentView.vue
  • src/renderer/src/features/chat-page/composables/useComposerSubmit.ts
  • src/renderer/src/features/chat-page/model/composerDraftState.ts
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/de-DE/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/es-ES/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/id-ID/chat.json
  • src/renderer/src/i18n/it-IT/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/ms-MY/chat.json
  • src/renderer/src/i18n/pl-PL/chat.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/tr-TR/chat.json
  • src/renderer/src/i18n/vi-VN/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/renderer/src/pages/NewThreadPage.vue
  • src/shared/chat.d.ts
  • src/shared/contracts/common.ts
  • src/shared/contracts/domainSchemas.ts
  • src/shared/types/agent-interface.d.ts
  • src/shared/types/attachment.ts
  • src/shared/types/core/chat.ts
  • src/shared/utils/attachmentRepresentation.ts
  • test/fixtures/light-ocr/fake-helper.mjs
  • test/main/agent/deepchat/runtime/contextBuilder.test.ts
  • test/main/exporter/agentSessionExporter.test.ts
  • test/main/exporter/userMessageText.test.ts
  • test/main/file/pdfFileAdapter.test.ts
  • test/main/ocr/attachmentCapabilityRouter.test.ts
  • test/main/ocr/documentOcrArtifact.test.ts
  • test/main/ocr/documentOcrArtifactStore.test.ts
  • test/main/ocr/documentTextExtractionService.test.ts
  • test/main/ocr/lightOcrHelper.test.ts
  • test/main/ocr/lightOcrNativePayload.test.ts
  • test/main/ocr/lightOcrProcessHost.test.ts
  • test/main/ocr/lightOcrProtocol.test.ts
  • test/main/ocr/ocrRuntimeAssetResolver.test.ts
  • test/main/ocr/ocrRuntimeService.test.ts
  • test/main/ocr/ocrSourceSnapshotBudget.test.ts
  • test/main/ocr/routes.test.ts
  • test/main/scripts/afterPack.test.ts
  • test/main/scripts/installRuntime.test.ts
  • test/main/scripts/lightOcrArtifacts.test.ts
  • test/main/scripts/smokeLightOcr.test.ts
  • test/main/session/data/tables/deepchatPendingInputsTable.test.ts
  • test/main/session/data/tapeRecall.test.ts
  • test/main/session/data/transcript.test.ts
  • test/main/shared/attachmentRepresentation.test.ts
  • test/renderer/components/ChatAttachmentItem.test.ts
  • test/renderer/components/FileAttachmentView.test.ts
  • test/renderer/components/NewThreadPage.test.ts
  • test/renderer/components/fileAttachmentNode.test.ts
  • test/renderer/features/chat-page/composables/useComposerSubmit.test.ts
  • test/renderer/features/chat-page/model/composerDraftState.test.ts
🛑 Comments failed to post (2)
resources/model-db/providers.json (2)

90958-90964: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Six chat models in the new zenmux provider are typed imageGeneration. All declare output: ["text"] (or no image output modality), so the shared root cause is an incorrect type value that will route text models through image-generation categorization.

  • resources/model-db/providers.json#L90958-L90964: set "type": "chat" for google/gemini-2.5-flash.
  • resources/model-db/providers.json#L91209-L91215: set "type": "chat" for google/gemini-2.5-pro.
  • resources/model-db/providers.json#L91265-L91271: set "type": "chat" for google/gemini-2.5-flash-lite.
  • resources/model-db/providers.json#L96451-L96456: set "type": "chat" for google/gemini-2.0-flash.
  • resources/model-db/providers.json#L96476-L96481: set "type": "chat" for google/gemini-2.0-flash-lite-001.
  • resources/model-db/providers.json#L97044-L97055: set "type": "chat" for qwen/qwen3-vl-plus.
📍 Affects 1 file
  • resources/model-db/providers.json#L90958-L90964 (this comment)
  • resources/model-db/providers.json#L91209-L91215
  • resources/model-db/providers.json#L91265-L91271
  • resources/model-db/providers.json#L96451-L96456
  • resources/model-db/providers.json#L96476-L96481
  • resources/model-db/providers.json#L97044-L97055
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/model-db/providers.json` around lines 90958 - 90964, Correct the
model type from imageGeneration to chat for google/gemini-2.5-flash,
google/gemini-2.5-pro, google/gemini-2.5-flash-lite, google/gemini-2.0-flash,
google/gemini-2.0-flash-lite-001, and qwen/qwen3-vl-plus. Apply this change at
resources/model-db/providers.json lines 90958-90964, 91209-91215, 91265-91271,
96451-96456, 96476-96481, and 97044-97055 respectively, preserving all other
model metadata.

96770-96779: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

openai/gpt-5-chat entry is missing modalities, tool_call, reasoning, and cost.

Every neighbouring entry declares at least modalities and tool_call; this stub will lose capability gating and cost estimation. Note the same model id is being removed from the inference provider in this PR — confirm the stub here is intentional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/model-db/providers.json` around lines 96770 - 96779, The
openai/gpt-5-chat entry is incomplete and lacks capability and pricing metadata.
Update the model definition with the appropriate modalities, tool_call,
reasoning, and cost fields consistent with neighbouring entries, and verify that
retaining this stub is intentional after its removal from the inference
provider.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/ocr/documentTextExtractionService.ts (1)

619-639: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Normalize snapshot_io_failed and page_handler_failed before throwing.

In normalizeRuntimeError, these LightOcrProcessHostError codes fall through and are re-thrown as LightOcrProcessHostError, so extractDocument() rejects with a non-DocumentTextExtractionError; mapDocumentExtractionFailure() already maps them to generic ocr_failed, losing the specific failure reason. Construct DocumentTextExtractionError for these codes or align the router mapping with the host code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentTextExtractionService.ts` around lines 619 - 639, The
normalizeRuntimeError function must convert LightOcrProcessHostError codes
snapshot_io_failed and page_handler_failed into DocumentTextExtractionError
before they are thrown. Add both codes to the existing host-error normalization
branch, preserving each original code, message, and cause so extractDocument
returns the specific failure reason instead of a generic ocr_failed mapping.
🧹 Nitpick comments (5)
src/main/ocr/lightOcrProcessHost.ts (1)

1014-1026: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Compute renderedPixels after shape validation.

page.width * page.height runs before isLightOcrDocumentPage(page), so it can be NaN for a malformed payload. The || chain short-circuits so behavior is correct today, but the ordering is fragile if the conditions are ever reordered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/lightOcrProcessHost.ts` around lines 1014 - 1026, Move the
renderedPixels calculation in the page-validation flow to occur only after
isLightOcrDocumentPage(page) has confirmed the payload shape. Preserve the
existing invalid-sequence checks and rendered-pixel validation in the
surrounding protocol handling.
src/main/ocr/attachmentCapabilityRouter.ts (1)

817-832: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

recordTurnBudgetIssue overwrites the pre-existing reason for that attachment.

If the document already recorded ocr_resource_limited (Line 517), it is replaced rather than kept alongside. That is defensible since the terminal state is now turn_ocr_budget_exhausted, but the resource-limit signal is lost from the summary — worth a short comment so the intent is explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/attachmentCapabilityRouter.ts` around lines 817 - 832, Clarify
the intentional overwrite in recordTurnBudgetIssue by adding a short comment
where an existing issue is replaced, noting that turn_ocr_budget_exhausted is
the terminal reason and supersedes the prior reason such as
ocr_resource_limited. Keep the current replacement and duplicate-removal
behavior unchanged.
src/shared/utils/documentOcrText.ts (1)

47-61: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Zero-length complete spans bypass the body check.

When start === end the if (chunk) branch is skipped, so a complete span with no ## Page N header and no body validates, while a complete span that has the header but an empty body is rejected at Line 52. If a fully empty page span is legal, consider making it explicit; otherwise reject it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/utils/documentOcrText.ts` around lines 47 - 61, Update the span
validation logic around the chunk handling in document OCR validation so
zero-length complete spans are handled explicitly. Reject a complete span when
start equals end unless fully empty pages are an intended valid case; preserve
the existing header and body validation for non-empty chunks and the
incomplete-span behavior.
src/main/ocr/documentTextExtractionService.ts (1)

250-278: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead if (!flight) guard.

The preceding if (flight) { ... return ... } already returns, so this check is always true and the non-null assertions below only exist to satisfy it.

♻️ Suggested simplification
-      let flight = this.flights.get(flightKey)
-      if (flight) {
+      const existing = this.flights.get(flightKey)
+      if (existing) {
         releaseUnusedSnapshot = false
         await this.releaseSnapshot(snapshot)
-        return await this.joinFlight(flightKey, flight, input.signal)
+        return await this.joinFlight(flightKey, existing, input.signal)
       }
-      if (!flight) {
-        const controller = new AbortController()
-        ...
-      }
+      const controller = new AbortController()
+      ...
+      const flight: SharedDocumentExtractionFlight = { controller, promise, snapshot, owners: 0, settled: false }
+      releaseUnusedSnapshot = false
+      this.flights.set(flightKey, flight)
+      promise.then(
+        () => this.finishFlight(flightKey, flight),
+        () => this.finishFlight(flightKey, flight)
+      )
       return await this.joinFlight(flightKey, flight, input.signal)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ocr/documentTextExtractionService.ts` around lines 250 - 278, Remove
the redundant if (!flight) guard after the early-returning if (flight) block.
Keep the flight creation, registration, and completion handling logic in the
unconditional path, and remove the unnecessary non-null assertions when passing
flight to finishFlight.
test/main/ocr/documentTextExtractionService.test.ts (1)

52-85: 📐 Maintainability & Code Quality | 🔵 Trivial

Default snapshotReader fallback path is never exercised.

createProcessHost's createDocumentSourceSnapshot stub (Lines 76-81) is unused by every test in this suite because createService (Lines 104-125) and the direct-construction test (Lines 388-398) always pass an explicit snapshotReader. The production fallback (options.snapshotReader ?? processHost.createDocumentSourceSnapshot) is therefore untested here.

Consider adding one test that constructs the service without snapshotReader to confirm it correctly falls back to processHost.createDocumentSourceSnapshot.

Also applies to: 104-125

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/main/ocr/documentTextExtractionService.test.ts` around lines 52 - 85,
Add a test covering the default snapshotReader fallback by constructing the
service through createService without passing snapshotReader, then trigger
document text extraction and verify processHost.createDocumentSourceSnapshot is
called and its snapshot is used. Keep existing explicit-snapshotReader tests
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/main/ocr/documentTextExtractionService.ts`:
- Around line 619-639: The normalizeRuntimeError function must convert
LightOcrProcessHostError codes snapshot_io_failed and page_handler_failed into
DocumentTextExtractionError before they are thrown. Add both codes to the
existing host-error normalization branch, preserving each original code,
message, and cause so extractDocument returns the specific failure reason
instead of a generic ocr_failed mapping.

---

Nitpick comments:
In `@src/main/ocr/attachmentCapabilityRouter.ts`:
- Around line 817-832: Clarify the intentional overwrite in
recordTurnBudgetIssue by adding a short comment where an existing issue is
replaced, noting that turn_ocr_budget_exhausted is the terminal reason and
supersedes the prior reason such as ocr_resource_limited. Keep the current
replacement and duplicate-removal behavior unchanged.

In `@src/main/ocr/documentTextExtractionService.ts`:
- Around line 250-278: Remove the redundant if (!flight) guard after the
early-returning if (flight) block. Keep the flight creation, registration, and
completion handling logic in the unconditional path, and remove the unnecessary
non-null assertions when passing flight to finishFlight.

In `@src/main/ocr/lightOcrProcessHost.ts`:
- Around line 1014-1026: Move the renderedPixels calculation in the
page-validation flow to occur only after isLightOcrDocumentPage(page) has
confirmed the payload shape. Preserve the existing invalid-sequence checks and
rendered-pixel validation in the surrounding protocol handling.

In `@src/shared/utils/documentOcrText.ts`:
- Around line 47-61: Update the span validation logic around the chunk handling
in document OCR validation so zero-length complete spans are handled explicitly.
Reject a complete span when start equals end unless fully empty pages are an
intended valid case; preserve the existing header and body validation for
non-empty chunks and the incomplete-span behavior.

In `@test/main/ocr/documentTextExtractionService.test.ts`:
- Around line 52-85: Add a test covering the default snapshotReader fallback by
constructing the service through createService without passing snapshotReader,
then trigger document text extraction and verify
processHost.createDocumentSourceSnapshot is called and its snapshot is used.
Keep existing explicit-snapshotReader tests unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d5bda813-8f7d-4f7c-b535-4dbf6208fec8

📥 Commits

Reviewing files that changed from the base of the PR and between 1921303 and fdf2dd8.

📒 Files selected for processing (43)
  • .github/workflows/prcheck.yml
  • docs/features/light-ocr-pdf-support/plan.md
  • docs/features/light-ocr-pdf-support/spec.md
  • docs/features/light-ocr-pdf-support/tasks.md
  • src/main/agent/deepchat/runtime/contextBuilder.ts
  • src/main/file/adapters/PdfFileAdapter.ts
  • src/main/ocr/attachmentCapabilityRouter.ts
  • src/main/ocr/documentOcrArtifact.ts
  • src/main/ocr/documentTextExtractionService.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • src/main/ocr/lightOcrProtocol.ts
  • src/renderer/src/i18n/da-DK/chat.json
  • src/renderer/src/i18n/de-DE/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/es-ES/chat.json
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/fr-FR/chat.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/id-ID/chat.json
  • src/renderer/src/i18n/it-IT/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/renderer/src/i18n/ms-MY/chat.json
  • src/renderer/src/i18n/pl-PL/chat.json
  • src/renderer/src/i18n/pt-BR/chat.json
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/tr-TR/chat.json
  • src/renderer/src/i18n/vi-VN/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/shared/contracts/common.ts
  • src/shared/types/attachment.ts
  • src/shared/utils/attachmentRepresentation.ts
  • src/shared/utils/documentOcrText.ts
  • test/main/agent/deepchat/runtime/contextBuilder.test.ts
  • test/main/ocr/attachmentCapabilityRouter.test.ts
  • test/main/ocr/documentOcrArtifact.test.ts
  • test/main/ocr/documentOcrArtifactStore.test.ts
  • test/main/ocr/documentTextExtractionService.test.ts
  • test/main/ocr/lightOcrProcessHost.test.ts
  • test/main/scripts/prcheckWorkflow.test.ts
  • test/main/shared/attachmentRepresentation.test.ts
🚧 Files skipped from review as they are similar to previous changes (28)
  • docs/features/light-ocr-pdf-support/tasks.md
  • src/renderer/src/i18n/id-ID/chat.json
  • src/renderer/src/i18n/en-US/chat.json
  • src/renderer/src/i18n/pl-PL/chat.json
  • src/renderer/src/i18n/tr-TR/chat.json
  • src/renderer/src/i18n/es-ES/chat.json
  • src/renderer/src/i18n/zh-CN/chat.json
  • src/renderer/src/i18n/ja-JP/chat.json
  • src/renderer/src/i18n/ko-KR/chat.json
  • src/main/file/adapters/PdfFileAdapter.ts
  • src/renderer/src/i18n/fa-IR/chat.json
  • src/renderer/src/i18n/da-DK/chat.json
  • test/main/ocr/documentOcrArtifactStore.test.ts
  • src/renderer/src/i18n/zh-TW/chat.json
  • src/renderer/src/i18n/ms-MY/chat.json
  • src/renderer/src/i18n/zh-HK/chat.json
  • src/renderer/src/i18n/vi-VN/chat.json
  • src/main/agent/deepchat/runtime/contextBuilder.ts
  • src/renderer/src/i18n/fr-FR/chat.json
  • test/main/ocr/attachmentCapabilityRouter.test.ts
  • test/main/agent/deepchat/runtime/contextBuilder.test.ts
  • src/renderer/src/i18n/ru-RU/chat.json
  • src/renderer/src/i18n/he-IL/chat.json
  • src/renderer/src/i18n/de-DE/chat.json
  • src/renderer/src/i18n/it-IT/chat.json
  • docs/features/light-ocr-pdf-support/plan.md
  • src/shared/utils/attachmentRepresentation.ts
  • src/main/ocr/lightOcrProtocol.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@resources/model-db/providers.json`:
- Around line 269259-269286: Add the missing cost object to the
zhipuai/openrouter claude-opus-5 entry, matching the provider-specific Claude
Opus 5 input, output, and cache pricing used by the corresponding provider
section. Keep the existing model capabilities unchanged and ensure the entry no
longer defaults to unknown or zero pricing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d10eee1-7c8d-46a9-a1f3-8072d4049a9d

📥 Commits

Reviewing files that changed from the base of the PR and between 08dca52 and 6cedc2d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • package.json
  • resources/acp-registry/registry.json
  • resources/model-db/providers.json
  • scripts/light-ocr-artifacts.mjs
  • scripts/smoke-light-ocr.js
  • src/main/agent/deepchat/runtime/contextBuilder.ts
  • src/main/agent/deepchat/runtime/turnCoordinator.ts
  • src/main/file/adapters/PdfFileAdapter.ts
  • src/main/ocr/documentOcrArtifact.ts
  • src/main/ocr/documentTextExtractionService.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • src/main/ocr/ocrArtifactStore.ts
  • src/main/ocr/ocrRuntimeAssetResolver.ts
  • src/main/session/data/transcript.ts
  • src/main/tape/application/recallProjection.ts
  • src/shared/utils/attachmentRepresentation.ts
  • test/fixtures/light-ocr/fake-helper.mjs
  • test/main/agent/deepchat/harness/deepChatAgentHarness.test.ts
  • test/main/agent/deepchat/runtime/contextBuilder.test.ts
  • test/main/file/pdfFileAdapter.test.ts
  • test/main/ocr/documentOcrArtifact.test.ts
  • test/main/ocr/documentTextExtractionService.test.ts
  • test/main/ocr/lightOcrProcessHost.test.ts
  • test/main/ocr/lightOcrProtocol.test.ts
  • test/main/ocr/ocrRuntimeAssetResolver.test.ts
  • test/main/scripts/lightOcrArtifacts.test.ts
  • test/main/shared/attachmentRepresentation.test.ts
🚧 Files skipped from review as they are similar to previous changes (17)
  • test/main/scripts/lightOcrArtifacts.test.ts
  • package.json
  • test/main/ocr/lightOcrProtocol.test.ts
  • src/main/session/data/transcript.ts
  • test/main/shared/attachmentRepresentation.test.ts
  • test/main/ocr/documentTextExtractionService.test.ts
  • test/main/ocr/documentOcrArtifact.test.ts
  • test/fixtures/light-ocr/fake-helper.mjs
  • src/main/ocr/ocrRuntimeAssetResolver.ts
  • test/main/ocr/ocrRuntimeAssetResolver.test.ts
  • src/shared/utils/attachmentRepresentation.ts
  • src/main/agent/deepchat/runtime/contextBuilder.ts
  • src/main/file/adapters/PdfFileAdapter.ts
  • src/main/ocr/documentTextExtractionService.ts
  • src/main/ocr/ocrArtifactStore.ts
  • src/main/ocr/lightOcrProcessHost.ts
  • scripts/smoke-light-ocr.js

Comment thread resources/model-db/providers.json
@yyhhyyyyyy
yyhhyyyyyy merged commit 13f0c6c into dev Jul 27, 2026
21 checks passed
@zhangmo8
zhangmo8 deleted the feat/light-ocr-0.5.5-pdf branch July 28, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant