@heripo/pdf-parser 0.1.44 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,8 +37,8 @@
37
37
  ## Key Features
38
38
 
39
39
  - **Fixed ocrmac OCR**: Docling conversion always uses ocrmac / Apple Vision Framework
40
- - **Mandatory VLM Correction**: Post-Docling correction always runs with text correction, page gating, and structural review models
41
- - **Apple Silicon Optimized**: GPU acceleration on M1/M2/M3/M4/M5 chips
40
+ - **Mandatory VLM Correction**: Post-Docling text correction always runs; page gating and structural review can be disabled
41
+ - **Apple Silicon Backend**: Docling conversion requests the `mps` accelerator
42
42
  - **Automatic Environment Setup**: Automatic Python virtual environment and docling-serve installation
43
43
  - **Image Extraction**: Automatic extraction and saving of images from PDFs
44
44
  - **Document Type Validation**: Optional LLM-based validation that a PDF is an archaeological report
@@ -65,22 +65,23 @@
65
65
  brew install node
66
66
  ```
67
67
 
68
- #### 2. pnpm >= 11
68
+ #### 2. pnpm >= 12
69
69
 
70
70
  ```bash
71
- npm install -g pnpm
71
+ npm install -g pnpm@12
72
72
  ```
73
73
 
74
74
  #### 3. Python 3.9 - 3.12
75
75
 
76
- > **Important**: Python 3.13+ is not supported. Some Docling SDK dependencies are not compatible with Python 3.13.
76
+ > **Important**: The current installer accepts Python 3.9–3.12 and rejects 3.13+. This is the repository's version check, not a compatibility statement for every upstream Docling release.
77
77
 
78
78
  ```bash
79
79
  # Install Python 3.11 (recommended)
80
80
  brew install python@3.11
81
81
 
82
82
  # Verify version
83
- python3.11 --version
83
+ export PATH="$(brew --prefix python@3.11)/libexec/bin:$PATH"
84
+ python3 --version
84
85
  ```
85
86
 
86
87
  #### 4. poppler (PDF text extraction)
@@ -105,9 +106,9 @@ Installed by default on macOS. Verify:
105
106
  which lsof
106
107
  ```
107
108
 
108
- #### 7. ImageMagick + Ghostscript (optional)
109
+ #### 7. ImageMagick + Ghostscript
109
110
 
110
- Required only when using the image PDF fallback feature (`enableImagePdfFallback` or `forceImagePdf`).
111
+ Required for rendering local PDF pages used by mandatory VLM correction, as well as image-PDF fallback. Install these even when `enableImagePdfFallback` and `forceImagePdf` are disabled.
111
112
 
112
113
  ```bash
113
114
  brew install imagemagick ghostscript
@@ -121,25 +122,27 @@ When using `@heripo/pdf-parser` for the first time, it automatically:
121
122
  2. Installs `docling-serve` and dependencies
122
123
  3. Starts docling-serve process on local port
123
124
 
124
- This setup runs only once and may take 5-10 minutes depending on internet connection.
125
+ The virtual environment is reused, but local initialization runs dependency setup again. The installer pins docling-serve 1.16.1 and an explicit Docling runtime set; see [python-environment.ts](./src/environment/python-environment.ts). Ensure the `python3` command on PATH is a supported version, not just a separately installed `python3.11`.
125
126
 
126
127
  ## Installation
127
128
 
128
129
  ```bash
129
130
  # Install with npm
130
- npm install @heripo/pdf-parser @heripo/logger
131
+ npm install @heripo/pdf-parser @heripo/logger @ai-sdk/openai
131
132
 
132
133
  # Install with pnpm
133
- pnpm add @heripo/pdf-parser @heripo/logger
134
+ pnpm add @heripo/pdf-parser @heripo/logger @ai-sdk/openai
134
135
 
135
136
  # Install with yarn
136
- yarn add @heripo/pdf-parser @heripo/logger
137
+ yarn add @heripo/pdf-parser @heripo/logger @ai-sdk/openai
137
138
  ```
138
139
 
139
140
  ## Usage
140
141
 
141
142
  ### Basic Usage
142
143
 
144
+ Use an ESM project. `HERIPO_MODEL` is an example-specific environment variable for a model ID supporting image input and structured output; also set `OPENAI_API_KEY`. Other providers can be supplied through their AI SDK adapters.
145
+
143
146
  ```typescript
144
147
  import { openai } from '@ai-sdk/openai';
145
148
  import { Logger } from '@heripo/logger';
@@ -158,33 +161,37 @@ const pdfParser = new PDFParser({
158
161
  logger,
159
162
  });
160
163
 
161
- const correctionModel = openai('gpt-5.1');
164
+ const correctionModel = openai(process.env.HERIPO_MODEL!);
162
165
 
163
166
  // Initialize (environment setup and start docling-serve)
164
- await pdfParser.init();
165
-
166
- // Parse PDF
167
- const tokenUsageReport = await pdfParser.parse(
168
- 'file:///path/to/report.pdf', // PDF URL (file:// or http://)
169
- 'report-001', // Report ID
170
- async (outputPath) => {
171
- // Conversion complete callback
172
- console.log('PDF conversion complete:', outputPath);
173
- },
174
- false, // cleanupAfterCallback
175
- {
176
- correction: {
177
- models: {
178
- textCorrection: correctionModel,
179
- pageGate: correctionModel,
180
- reviewAssistance: correctionModel,
181
- },
167
+ try {
168
+ await pdfParser.init();
169
+
170
+ // Parse PDF
171
+ const tokenUsageReport = await pdfParser.parse(
172
+ 'file:///path/to/report.pdf', // PDF URL (file:// or http://)
173
+ 'report-001', // Report ID
174
+ async (outputPath) => {
175
+ // Conversion complete callback
176
+ console.log('PDF conversion complete:', outputPath);
182
177
  },
183
- }, // PDFConvertOptions
184
- );
178
+ false, // cleanupAfterCallback
179
+ {
180
+ correction: {
181
+ models: {
182
+ textCorrection: correctionModel,
183
+ pageGate: correctionModel,
184
+ reviewAssistance: correctionModel,
185
+ },
186
+ },
187
+ }, // PDFConvertOptions
188
+ );
185
189
 
186
- // Token usage report (null when no LLM usage)
187
- console.log('Token usage:', tokenUsageReport);
190
+ // Token usage report (null when no LLM usage)
191
+ console.log('Token usage:', tokenUsageReport);
192
+ } finally {
193
+ await pdfParser.dispose();
194
+ }
188
195
  ```
189
196
 
190
197
  ### Advanced Options
@@ -193,7 +200,7 @@ console.log('Token usage:', tokenUsageReport);
193
200
  // Option A: Use local server with port
194
201
  const pdfParser = new PDFParser({
195
202
  logger,
196
- port: 5001, // Port to use (default: 5001)
203
+ port: 5001, // Set the local port explicitly
197
204
  timeout: 10000000, // Timeout (milliseconds)
198
205
  venvPath: '/custom/path/.venv', // Custom venv path (default: CWD/.venv)
199
206
  killExistingProcess: true, // Kill existing process on port (default: false)
@@ -216,13 +223,13 @@ const tokenUsageReport = await pdfParser.parse(
216
223
  // Mandatory post-Docling correction
217
224
  correction: {
218
225
  models: {
219
- textCorrection: openai('gpt-5.1'),
220
- pageGate: openai('gpt-5.1'),
221
- reviewAssistance: openai('gpt-5.1'),
222
- tableCorrection: openai('gpt-5.1'),
226
+ textCorrection: openai(process.env.HERIPO_MODEL!),
227
+ pageGate: openai(process.env.HERIPO_MODEL!),
228
+ reviewAssistance: openai(process.env.HERIPO_MODEL!),
229
+ tableCorrection: openai(process.env.HERIPO_MODEL!),
223
230
  reviewAssistanceTasks: {
224
- text_ocr_hanja: openai('gpt-5.1'),
225
- tables: openai('gpt-5.1'),
231
+ text_ocr_hanja: openai(process.env.HERIPO_MODEL!),
232
+ tables: openai(process.env.HERIPO_MODEL!),
226
233
  },
227
234
  },
228
235
  concurrency: {
@@ -246,7 +253,7 @@ const tokenUsageReport = await pdfParser.parse(
246
253
  onReviewAssistanceProgress: (event) => console.log(event),
247
254
 
248
255
  // Document validation
249
- documentValidationModel: openai('gpt-5.1'),
256
+ documentValidationModel: openai(process.env.HERIPO_MODEL!),
250
257
 
251
258
  // Chunked conversion for large PDFs
252
259
  chunkedConversion: true,
@@ -265,6 +272,12 @@ const tokenUsageReport = await pdfParser.parse(
265
272
  );
266
273
  ```
267
274
 
275
+ ### Artifacts
276
+
277
+ The callback receives the absolute `output/<reportId>/` path. `result.json` is the corrected document and `result_ocr_origin.json` is the initial OCR snapshot. `images/` and `pages/` hold extracted and page images. Structural review also produces `result_review_origin.json`, `review_assistance_page_gate.json`, `review_assistance_checkpoint.json` and `review_assistance.json`. Check page status, issues and call traces for failed review work.
278
+
279
+ With `cleanupAfterCallback: true`, the artifact directory is removed after the callback. Copy required files elsewhere inside it or use `false` to preserve them. The parser does not create `result-processed.json` or a handoff manifest; the demo worker saves those separately.
280
+
268
281
  ### Resource Cleanup
269
282
 
270
283
  Clean up resources after work is complete:
@@ -278,15 +291,15 @@ await pdfParser.dispose();
278
291
 
279
292
  ### Why ocrmac Is Fixed
280
293
 
281
- **ocrmac (Apple Vision Framework) is an excellent OCR engine** -- it's free, GPU-accelerated, and delivers high-quality results. For processing thousands to millions of archaeological reports, there's no better solution.
294
+ ocrmac is the fixed OCR backend, using Apple Vision on macOS.
282
295
 
283
- `@heripo/pdf-parser` no longer samples OCR strategies or switches to a VLM OCR path. Docling conversion always uses ocrmac. VLMs are used only after Docling conversion as a mandatory correction stage.
296
+ `@heripo/pdf-parser` no longer samples OCR strategies or switches to a VLM OCR path. Docling conversion always uses ocrmac. VLM correction runs after Docling conversion. Optional language detection and document validation can also call a model before conversion.
284
297
 
285
298
  ### Required Correction Contract
286
299
 
287
300
  Every `parse()` call must provide `correction.models.textCorrection`, `correction.models.pageGate`, and `correction.models.reviewAssistance`. If any required correction model is missing, parsing fails before conversion callback wrapping.
288
301
 
289
- The correction stage runs in this order:
302
+ With the default review controls enabled, the correction stage runs as follows:
290
303
 
291
304
  1. Save `result_ocr_origin.json` before mutations.
292
305
  2. Run page-level text and table-cell OCR correction with `textCorrection`.
@@ -299,21 +312,39 @@ Text correction applies to every page with text or table content. The page gate
299
312
 
300
313
  ### Local Model Execution
301
314
 
302
- The correction pipeline is optimized for local VLMs: small contexts, many calls, deterministic validation, retry loops, bounded concurrency, and resumable checkpoints. For local models, start with `concurrency.pages: 1`, `concurrency.tables: 1`, `modelConcurrency: 1`, `temperature: 0`, and a generous `workItemTimeoutMs`. Increase concurrency only after the model is stable.
315
+ The correction pipeline is optimized for local VLMs: small contexts, many calls, deterministic validation, retry loops, bounded concurrency, and resumable checkpoints. For local models, start with `concurrency.pages: 1`, `concurrency.reviewTasks: 1`, `modelConcurrency: 1`, `temperature: 0`, and a generous `workItemTimeoutMs`. Increase concurrency only after the model is stable.
303
316
 
304
- ### Rollout Smoke Test
317
+ ### Validation
305
318
 
306
- Repository contributors can run the correction rollout smoke test against two local demo archaeological report artifacts:
319
+ Run the defined package check with `pnpm --filter @heripo/pdf-parser test:coverage`. Tests mock external model calls; correction quality on real documents requires separate evaluation.
307
320
 
308
- ```bash
309
- pnpm --filter @heripo/pdf-parser smoke:correction
310
- ```
321
+ ### Correction Controls and Defaults
311
322
 
312
- The smoke test copies existing demo artifacts into `/private/tmp/heripo-pdf-parser-correction-smoke`, runs correction with a deterministic local fake model, verifies `review_assistance.json`, table work-item traces, validation status, and checkpoint resume behavior. It exercises pipeline mechanics; semantic table quality still depends on the configured real local VLM.
323
+ All three required models must still be supplied when structural review is disabled. `reviewAssistanceEnabled: false` runs only text/table-cell OCR correction, skipping the page gate and structural review. `tableCorrectionEnabled: false` skips structural table work items, but does not disable initial table-cell OCR correction.
324
+
325
+ `forceAutoApply` defaults to `false`. When true, validated commands are applied without routing to manual review based on confidence thresholds or structural block reasons. Command validation still runs.
326
+
327
+ | Option | Default |
328
+ | ---------------------------------------------------- | ---------------------- |
329
+ | `concurrency.pages` | `1` |
330
+ | `concurrency.reviewTasks` | `6` |
331
+ | `modelConcurrency` | `1` |
332
+ | `maxRetries.*` | `3` |
333
+ | `workItemTimeoutMs` | `1800000` (30 minutes) |
334
+ | `outputLanguage` | `en-US` |
335
+ | `autoApplyThreshold` / `proposalThreshold` | `0.85` / `0.5` |
336
+ | `reviewAssistanceEnabled` / `tableCorrectionEnabled` | `true` / `true` |
337
+ | `forceAutoApply` / `temperature` | `false` / `0` |
338
+
339
+ Defaults are also exported as `PDF_CORRECTION_DEFAULTS`. `concurrency.tables` and `pageGate.structuralNoiseThreshold` exist in the type but are not forwarded as runner controls by the current top-level pipeline. Use `reviewTasks` and `modelConcurrency` to bound table work.
340
+
341
+ ### Language Detection
342
+
343
+ Explicit `ocr_lang` values take precedence. Otherwise local PDF text is inspected, with optional vision-based detection through `languageDetectionModel` and `languageDetectionFallbackModel` when needed. Remote inputs and undetected inputs use `ko-KR`, `en-US` as defaults. This does not select an OCR engine: OCR remains ocrmac.
313
344
 
314
345
  ## Review Assistance
315
346
 
316
- Review Assistance always runs after text correction, but it does not process every page with the same intensity. The page gate marks covers, chapter covers, barcode/ISBN pages, and decorative pages as low-value for structural review. Skipped pages remain observable in `review_assistance.json` with an info issue and skip reason.
347
+ Review Assistance runs after text correction when `correction.reviewAssistanceEnabled` is true (library default), but it does not process every page with the same intensity. The page gate marks covers, chapter covers, barcode/ISBN pages, and decorative pages as low-value for structural review. Skipped pages remain observable in `review_assistance.json` with an info issue and skip reason.
317
348
 
318
349
  Eligible pages are split into small work items for text OCR/Hanja review, text integrity, text role/footnote review, tables, pictures/captions, layout/bbox/order, and table-specific correction. Each call records timing, model id, attempts, target refs, and deterministic validation status in `review_assistance.json`.
319
350
 
@@ -330,10 +361,10 @@ const tokenUsageReport = await pdfParser.parse(
330
361
  {
331
362
  correction: {
332
363
  models: {
333
- textCorrection: openai('gpt-5.1'),
334
- pageGate: openai('gpt-5.1-mini'),
335
- reviewAssistance: openai('gpt-5.1'),
336
- tableCorrection: openai('gpt-5.1'),
364
+ textCorrection: openai(process.env.HERIPO_MODEL!),
365
+ pageGate: openai(process.env.HERIPO_MODEL!),
366
+ reviewAssistance: openai(process.env.HERIPO_MODEL!),
367
+ tableCorrection: openai(process.env.HERIPO_MODEL!),
337
368
  },
338
369
  autoApplyThreshold: 0.85,
339
370
  proposalThreshold: 0.5,
@@ -353,7 +384,7 @@ const tokenUsageReport = await pdfParser.parse(
353
384
  );
354
385
  ```
355
386
 
356
- Review Assistance requires a local `file://` PDF for page image and text-layer references. It updates `result.json` with auto-applied fixes, keeps snapshots in `result_review_origin.json` and `result_ocr_origin.json`, writes `review_assistance_page_gate.json`, and writes `review_assistance.json` containing per-page decisions, issues, proposals, call traces, validation status, and summary counts.
387
+ Use a local `file://` PDF for the complete pipeline. HTTP inputs are accepted by Docling, but local page rendering, language detection and document validation are skipped. Missing `pages/page_<index>.png` files cause text correction to be skipped; a successful callback does not prove every page was corrected. Failed correction calls retain the original OCR text and emit warnings. It updates `result.json` with auto-applied fixes, keeps snapshots in `result_review_origin.json` and `result_ocr_origin.json`, writes `review_assistance_page_gate.json`, and writes `review_assistance.json` containing per-page decisions, issues, proposals, call traces, validation status, and summary counts.
357
388
 
358
389
  ## Document Type Validation
359
390
 
@@ -371,12 +402,12 @@ try {
371
402
  {
372
403
  correction: {
373
404
  models: {
374
- textCorrection: openai('gpt-5.1'),
375
- pageGate: openai('gpt-5.1'),
376
- reviewAssistance: openai('gpt-5.1'),
405
+ textCorrection: openai(process.env.HERIPO_MODEL!),
406
+ pageGate: openai(process.env.HERIPO_MODEL!),
407
+ reviewAssistance: openai(process.env.HERIPO_MODEL!),
377
408
  },
378
409
  },
379
- documentValidationModel: openai('gpt-5.1'),
410
+ documentValidationModel: openai(process.env.HERIPO_MODEL!),
380
411
  },
381
412
  );
382
413
  } catch (error) {
@@ -399,18 +430,20 @@ const tokenUsageReport = await pdfParser.parse(
399
430
  {
400
431
  correction: {
401
432
  models: {
402
- textCorrection: openai('gpt-5.1'),
403
- pageGate: openai('gpt-5.1'),
404
- reviewAssistance: openai('gpt-5.1'),
433
+ textCorrection: openai(process.env.HERIPO_MODEL!),
434
+ pageGate: openai(process.env.HERIPO_MODEL!),
435
+ reviewAssistance: openai(process.env.HERIPO_MODEL!),
405
436
  },
406
437
  },
407
438
  chunkedConversion: true,
408
- chunkSize: 50, // Pages per chunk (default: configured in constants)
409
- chunkMaxRetries: 3, // Max retry attempts per failed chunk (default: configured in constants)
439
+ chunkSize: 50, // Pages per chunk (default: 10)
440
+ chunkMaxRetries: 3, // Retries per failed chunk (default: 2)
410
441
  },
411
442
  );
412
443
  ```
413
444
 
445
+ Correction runs after chunk results are merged. The current chunked failure path attempts image-PDF conversion independently of `enableImagePdfFallback`, so ImageMagick/Ghostscript are needed for that recovery path too. For local inputs, the chunked branch takes precedence when `chunkedConversion` and `forceImagePdf` are both set.
446
+
414
447
  ## Image PDF Fallback
415
448
 
416
449
  When conversion fails, the parser can automatically fall back to converting the PDF to an image-based PDF first, then retrying conversion. This is useful for PDFs with complex or corrupt structures. Requires ImageMagick and Ghostscript.
@@ -440,9 +473,9 @@ const tokenUsageReport = await pdfParser.parse(
440
473
  {
441
474
  correction: {
442
475
  models: {
443
- textCorrection: openai('gpt-5.1'),
444
- pageGate: openai('gpt-5.1'),
445
- reviewAssistance: openai('gpt-5.1'),
476
+ textCorrection: openai(process.env.HERIPO_MODEL!),
477
+ pageGate: openai(process.env.HERIPO_MODEL!),
478
+ reviewAssistance: openai(process.env.HERIPO_MODEL!),
446
479
  },
447
480
  },
448
481
  forceImagePdf: true, // Always convert to image PDF first
@@ -471,16 +504,16 @@ try {
471
504
  {
472
505
  correction: {
473
506
  models: {
474
- textCorrection: openai('gpt-5.1'),
475
- pageGate: openai('gpt-5.1'),
476
- reviewAssistance: openai('gpt-5.1'),
507
+ textCorrection: openai(process.env.HERIPO_MODEL!),
508
+ pageGate: openai(process.env.HERIPO_MODEL!),
509
+ reviewAssistance: openai(process.env.HERIPO_MODEL!),
477
510
  },
478
511
  },
479
512
  },
480
513
  controller.signal, // AbortSignal
481
514
  );
482
515
  } catch (error) {
483
- if (error.name === 'AbortError') {
516
+ if (error instanceof Error && error.name === 'AbortError') {
484
517
  console.log('Parsing was cancelled');
485
518
  }
486
519
  }
@@ -494,35 +527,9 @@ When using a local docling-serve instance (port mode), the parser automatically
494
527
 
495
528
  ## Why macOS Only?
496
529
 
497
- `@heripo/pdf-parser` **intentionally relies heavily on macOS**. The key reason for this decision is **Docling SDK's local OCR performance**.
498
-
499
- ### OCR Selection Background
500
-
501
- Archaeological excavation report PDFs have the following characteristics:
502
-
503
- - Scanned documents spanning hundreds of pages
504
- - Layouts containing complex tables, diagrams, photographs
505
- - Precise text extraction is essential
506
-
507
- ### OCR Option Comparison
508
-
509
- | Method | Performance | Cost | Description |
510
- | ----------------------- | ----------- | ---- | ---------------------------------------------------------- |
511
- | **Docling (Local)** | ★★★★★ | Free | Overwhelming performance on Apple Silicon, GPU utilization |
512
- | Cloud OCR (Google, AWS) | ★★★★ | $$$ | Tens of dollars per hundreds of pages |
513
- | Tesseract (Local) | ★★ | Free | Low Korean recognition rate, lacking layout analysis |
514
-
515
- ### Key Advantages
530
+ The package declares `os: ["darwin"]` and checks macOS and local tools in `init()`. Connecting to an external Docling server through `baseUrl` does not remove this client-side restriction. ocrmac uses Apple Vision on macOS.
516
531
 
517
- - **Cost**: 100x+ cheaper than cloud OCR (free)
518
- - **Performance**: Fast processing with GPU acceleration on Apple Silicon M1/M2/M3/M4/M5
519
- - **Quality**: Accurate recognition even for complex documents
520
- - **Privacy**: Documents are not sent to external servers
521
-
522
- ### Trade-off
523
-
524
- - Optimal performance only in macOS + Apple Silicon environment
525
- - No current plans for Linux/Windows support (see "Linux Support Status" below)
532
+ Docling OCR can run locally, but VLM correction calls the configured models. Cloud models can receive document text and page images and incur API charges. To keep the full pipeline local, configure local models for language detection, document validation, correction, fallbacks and downstream document processing.
526
533
 
527
534
  ## System Dependencies Details
528
535
 
@@ -534,17 +541,18 @@ Archaeological excavation report PDFs have the following characteristics:
534
541
  | poppler | Any | `brew install poppler` | PDF page counting (pdfinfo) and text layer extraction (pdftotext) |
535
542
  | jq | Any | `brew install jq` | JSON processing (conversion result parsing) |
536
543
  | lsof | Any | Included with macOS | docling-serve port management |
537
- | ImageMagick | Any (optional) | `brew install imagemagick` | Image PDF fallback and page rendering |
538
- | Ghostscript | Any (optional) | `brew install ghostscript` | Image PDF fallback (PDF to image conversion) |
544
+ | ImageMagick | Any | `brew install imagemagick` | Image PDF fallback and page rendering |
545
+ | Ghostscript | Any | `brew install ghostscript` | Image PDF fallback (PDF to image conversion) |
539
546
 
540
- > **Python 3.13+ is not supported.** Some Docling SDK dependencies are not compatible with Python 3.13.
547
+ > **Python 3.13+ is rejected by the current installer.** See the version check in [python-version.ts](./src/utils/python-version.ts).
541
548
 
542
549
  ### Checking Python Version
543
550
 
544
551
  ```bash
545
552
  # Check installed Python version
546
553
  python3 --version
547
- python3.11 --version
554
+ export PATH="$(brew --prefix python@3.11)/libexec/bin:$PATH"
555
+ python3 --version
548
556
 
549
557
  # When multiple versions are installed
550
558
  ls -la /usr/local/bin/python*
@@ -569,12 +577,12 @@ which jq
569
577
  ```typescript
570
578
  type Options = {
571
579
  logger: LoggerMethods; // Logger instance (REQUIRED)
572
- timeout?: number; // Timeout in milliseconds (default: 10000000)
580
+ timeout?: number; // Timeout in milliseconds (default: 100000)
573
581
  venvPath?: string; // Python venv path (default: CWD/.venv)
574
582
  killExistingProcess?: boolean; // Kill existing process on port (default: false)
575
583
  enableImagePdfFallback?: boolean; // Enable image PDF fallback (default: false, requires ImageMagick + Ghostscript)
576
584
  } & (
577
- | { port?: number } // Local server mode (default port: 5001)
585
+ | { port?: number } // Local mode; explicitly provide port (no runtime default)
578
586
  | { baseUrl: string } // External server mode
579
587
  );
580
588
  ```
@@ -614,6 +622,10 @@ Disposes the parser instance, kills the local docling-serve process (if started)
614
622
  await pdfParser.dispose();
615
623
  ```
616
624
 
625
+ ##### `isReady(): Promise<boolean>` / `ensureReady(): Promise<void>`
626
+
627
+ `isReady()` checks health without recovery. `ensureReady()` requires initialization and attempts recovery for a local server. External server failures are propagated. `dispose()` also terminates a process on the configured local port, including a reused server; use `baseUrl` for an externally managed lifecycle.
628
+
617
629
  ### PDFConvertOptions
618
630
 
619
631
  ```typescript
@@ -630,6 +642,8 @@ type PDFConvertOptions = {
630
642
 
631
643
  // Document processing
632
644
  document_timeout?: number; // Document processing timeout in seconds
645
+ languageDetectionModel?: LanguageModel;
646
+ languageDetectionFallbackModel?: LanguageModel;
633
647
  documentValidationModel?: LanguageModel; // LLM for document type validation
634
648
 
635
649
  // Correction progress
@@ -686,7 +700,7 @@ interface PDFCorrectionOptions {
686
700
  concurrency?: {
687
701
  pages?: number; // Page-level text correction and page gate concurrency
688
702
  reviewTasks?: number; // Structural Review Assistance work-item concurrency
689
- tables?: number; // Table-specific correction concurrency
703
+ tables?: number; // Accepted but not forwarded by the top-level pipeline
690
704
  };
691
705
  maxRetries?: {
692
706
  textCorrection?: number;
@@ -800,31 +814,12 @@ brew install imagemagick ghostscript
800
814
 
801
815
  ## Linux Support Status
802
816
 
803
- Currently **macOS only**. Linux support is **not entirely ruled out**, but due to OCR performance and cost efficiency issues, **there are no specific plans at this time**.
804
-
805
- | Platform | Status | Notes |
806
- | --------------------- | --------- | ----------------------------------------------- |
807
- | macOS + Apple Silicon | Supported | Optimal performance, GPU acceleration |
808
- | macOS + Intel | Supported | No GPU acceleration |
809
- | Linux | TBD | No current plans due to performance/cost issues |
810
- | Windows | TBD | WSL2 Linux approach possible |
811
-
812
- ### Reason for No Linux Support
813
-
814
- Docling SDK's local OCR achieves both performance and cost efficiency by utilizing Apple Metal GPU acceleration on macOS. We have not yet found an OCR solution on Linux that provides equivalent performance and cost efficiency.
815
-
816
- ### Ideas Welcome
817
-
818
- If you have ideas for supporting Linux while maintaining both performance and cost efficiency, please suggest them via [GitHub Discussions](https://github.com/heripo-lab/heripo-engine/discussions) or Issues. The following information is particularly helpful:
819
-
820
- - Experience with Korean document OCR on Linux
821
- - OCR solutions capable of handling complex layouts (tables, diagrams)
822
- - Cost estimates for processing hundreds of pages
817
+ The current `@heripo/pdf-parser` package supports macOS and does not provide a Linux/Windows runtime path. Platform proposals are welcome in [GitHub Discussions](https://github.com/heripo-lab/heripo-engine/discussions).
823
818
 
824
819
  ## Related Packages
825
820
 
826
- - [@heripo/document-processor](../document-processor) - Document structure analysis and LLM processing
827
- - [@heripo/model](../model) - Data models and type definitions
821
+ - [@heripo/document-processor](../document-processor/README.md) - Document structure analysis and LLM processing
822
+ - [@heripo/model](../model/README.md) - Data models and type definitions
828
823
 
829
824
  ## Sponsor
830
825
 
package/dist/index.js CHANGED
@@ -142,6 +142,15 @@ function detectProvider(model) {
142
142
  if (providerId.includes("lmstudio")) return "lmstudio";
143
143
  return "unknown";
144
144
  }
145
+ function cacheWrite1hTokens(steps) {
146
+ let total;
147
+ for (const step of steps ?? []) {
148
+ const raw = step.usage.raw;
149
+ const value = raw?.cache_creation?.ephemeral_1h_input_tokens;
150
+ if (value !== void 0) total = (total ?? 0) + value;
151
+ }
152
+ return total;
153
+ }
145
154
  var LLMCaller = class {
146
155
  /**
147
156
  * Extract model name from LanguageModel object
@@ -160,6 +169,9 @@ var LLMCaller = class {
160
169
  * Build usage information from response
161
170
  */
162
171
  static buildUsage(config, modelName, response, usedFallback) {
172
+ const usage = response.usage;
173
+ const write1h = cacheWrite1hTokens(response.steps);
174
+ const allWrites = usage?.inputTokenDetails?.cacheWriteTokens;
163
175
  return {
164
176
  component: config.component,
165
177
  phase: config.phase,
@@ -168,6 +180,13 @@ var LLMCaller = class {
168
180
  inputTokens: response.usage?.inputTokens ?? 0,
169
181
  outputTokens: response.usage?.outputTokens ?? 0,
170
182
  totalTokens: response.usage?.totalTokens ?? 0,
183
+ ...usage?.inputTokenDetails?.cacheReadTokens != null && {
184
+ cachedInputTokens: usage.inputTokenDetails.cacheReadTokens
185
+ },
186
+ ...allWrites != null && {
187
+ cacheWriteTokens: allWrites - (write1h ?? 0)
188
+ },
189
+ ...write1h !== void 0 && { cacheWrite1hTokens: write1h },
171
190
  metadata: config.metadata
172
191
  };
173
192
  }
@@ -210,7 +229,8 @@ var LLMCaller = class {
210
229
  if (toolCall) {
211
230
  return {
212
231
  output: toolCall.input,
213
- usage: lastResult.usage
232
+ usage: lastResult.usage,
233
+ steps: lastResult.steps
214
234
  };
215
235
  }
216
236
  }
@@ -347,16 +367,32 @@ function emptyUsage() {
347
367
  return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
348
368
  }
349
369
  function addInto(target, src) {
370
+ const hadUsage = target.inputTokens !== 0 || target.outputTokens !== 0 || target.totalTokens !== 0;
350
371
  target.inputTokens += src.inputTokens;
351
372
  target.outputTokens += src.outputTokens;
352
373
  target.totalTokens += src.totalTokens;
374
+ for (const key of CACHE_KEYS) {
375
+ const next = src[key];
376
+ const current = target[key];
377
+ if (next === void 0) {
378
+ if (current !== void 0) target[key] = null;
379
+ } else if (current === void 0) {
380
+ target[key] = hadUsage ? null : next;
381
+ } else {
382
+ target[key] = current === null || next === null ? null : current + next;
383
+ }
384
+ }
353
385
  }
386
+ var CACHE_KEYS = [
387
+ "cachedInputTokens",
388
+ "cacheWriteTokens",
389
+ "cacheWrite1hTokens"
390
+ ];
354
391
  function sumUsage(a, b) {
355
- return {
356
- inputTokens: (a?.inputTokens ?? 0) + (b?.inputTokens ?? 0),
357
- outputTokens: (a?.outputTokens ?? 0) + (b?.outputTokens ?? 0),
358
- totalTokens: (a?.totalTokens ?? 0) + (b?.totalTokens ?? 0)
359
- };
392
+ const total = emptyUsage();
393
+ if (a) addInto(total, a);
394
+ if (b) addInto(total, b);
395
+ return total;
360
396
  }
361
397
  function accumulateModel(bucket, modelName, usage) {
362
398
  let entry = bucket.get(modelName);
@@ -471,21 +507,13 @@ var LLMTokenUsageAggregator = class {
471
507
  components.push({
472
508
  component: component.component,
473
509
  phases,
474
- total: {
475
- inputTokens: component.total.inputTokens,
476
- outputTokens: component.total.outputTokens,
477
- totalTokens: component.total.totalTokens
478
- }
510
+ total: { ...component.total }
479
511
  });
480
512
  }
481
513
  const totalUsage = this.getTotalUsage();
482
514
  return {
483
515
  components,
484
- total: {
485
- inputTokens: totalUsage.inputTokens,
486
- outputTokens: totalUsage.outputTokens,
487
- totalTokens: totalUsage.totalTokens
488
- }
516
+ total: totalUsage
489
517
  };
490
518
  }
491
519
  /**
@@ -494,19 +522,11 @@ var LLMTokenUsageAggregator = class {
494
522
  * @returns Aggregated token usage totals
495
523
  */
496
524
  getTotalUsage() {
497
- let totalInput = 0;
498
- let totalOutput = 0;
499
- let totalTokens = 0;
525
+ const total = emptyUsage();
500
526
  for (const component of Object.values(this.usage)) {
501
- totalInput += component.total.inputTokens;
502
- totalOutput += component.total.outputTokens;
503
- totalTokens += component.total.totalTokens;
527
+ addInto(total, component.total);
504
528
  }
505
- return {
506
- inputTokens: totalInput,
507
- outputTokens: totalOutput,
508
- totalTokens
509
- };
529
+ return total;
510
530
  }
511
531
  /**
512
532
  * Log comprehensive token usage summary