@heripo/pdf-parser 0.2.1 → 0.2.3
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/dist/index.js +133 -89
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -144,6 +144,50 @@ function detectProvider(model) {
|
|
|
144
144
|
if (providerId.includes("lmstudio")) return "lmstudio";
|
|
145
145
|
return "unknown";
|
|
146
146
|
}
|
|
147
|
+
function withSystemPromptCache(model, systemPrompt, providerOptions) {
|
|
148
|
+
const unchanged = {
|
|
149
|
+
...systemPrompt === void 0 ? {} : { instructions: systemPrompt },
|
|
150
|
+
...providerOptions === void 0 ? {} : { providerOptions }
|
|
151
|
+
};
|
|
152
|
+
const provider = detectProvider(model);
|
|
153
|
+
if (provider === "anthropic")
|
|
154
|
+
return systemPrompt ? {
|
|
155
|
+
...unchanged,
|
|
156
|
+
instructions: cachedSystem(systemPrompt, {
|
|
157
|
+
anthropic: { cacheControl: { type: "ephemeral" } }
|
|
158
|
+
})
|
|
159
|
+
} : unchanged;
|
|
160
|
+
if (provider !== "openai" || !supportsOpenAICacheBreakpoints(modelIdOf(model)) || providerOptions?.openai?.promptCacheOptions !== void 0)
|
|
161
|
+
return unchanged;
|
|
162
|
+
return {
|
|
163
|
+
...systemPrompt ? {
|
|
164
|
+
instructions: cachedSystem(systemPrompt, {
|
|
165
|
+
openai: { promptCacheBreakpoint: { mode: "explicit" } }
|
|
166
|
+
})
|
|
167
|
+
} : unchanged,
|
|
168
|
+
providerOptions: {
|
|
169
|
+
...providerOptions,
|
|
170
|
+
openai: {
|
|
171
|
+
...providerOptions?.openai,
|
|
172
|
+
promptCacheOptions: { mode: "explicit" }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function cachedSystem(content, providerOptions) {
|
|
178
|
+
return { role: "system", content, providerOptions };
|
|
179
|
+
}
|
|
180
|
+
function modelIdOf(model) {
|
|
181
|
+
const modelId = model.modelId;
|
|
182
|
+
return typeof modelId === "string" ? modelId : "";
|
|
183
|
+
}
|
|
184
|
+
function supportsOpenAICacheBreakpoints(modelId) {
|
|
185
|
+
const version = /^(?:openai\/)?gpt-(\d+)(?:\.(\d+))?(?:-.+)?$/u.exec(modelId);
|
|
186
|
+
if (!version) return false;
|
|
187
|
+
const major = Number(version[1]);
|
|
188
|
+
const minor = Number(version[2] ?? 0);
|
|
189
|
+
return major > 5 || major === 5 && minor >= 6;
|
|
190
|
+
}
|
|
147
191
|
function cacheWrite1hTokens(steps) {
|
|
148
192
|
let total;
|
|
149
193
|
for (const step of steps ?? []) {
|
|
@@ -345,7 +389,8 @@ var LLMCaller = class {
|
|
|
345
389
|
* 2. If all fail and fallbackModel provided, try fallback up to maxRetries times
|
|
346
390
|
* 3. Throw error if all attempts exhausted
|
|
347
391
|
*
|
|
348
|
-
* Provider-aware strategy is automatically applied based on the model's provider field
|
|
392
|
+
* Provider-aware strategy is automatically applied based on the model's provider field, and the prompt cache
|
|
393
|
+
* breakpoint is placed only at the end of the system prompt (see `withSystemPromptCache`).
|
|
349
394
|
*
|
|
350
395
|
* @template TOutput - Output type from schema validation
|
|
351
396
|
* @param config - LLM call configuration
|
|
@@ -356,17 +401,12 @@ var LLMCaller = class {
|
|
|
356
401
|
return this.executeWithFallback(
|
|
357
402
|
config,
|
|
358
403
|
(model) => this.generateStructuredOutput(model, config.schema, {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
providerOptions
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
} : config.systemPrompt,
|
|
404
|
+
...withSystemPromptCache(
|
|
405
|
+
model,
|
|
406
|
+
config.systemPrompt,
|
|
407
|
+
config.providerOptions
|
|
408
|
+
),
|
|
366
409
|
prompt: config.userPrompt,
|
|
367
|
-
...config.providerOptions && {
|
|
368
|
-
providerOptions: config.providerOptions
|
|
369
|
-
},
|
|
370
410
|
temperature: config.temperature,
|
|
371
411
|
maxRetries: config.maxRetries,
|
|
372
412
|
abortSignal: config.abortSignal
|
|
@@ -389,6 +429,7 @@ var LLMCaller = class {
|
|
|
389
429
|
config,
|
|
390
430
|
(model) => this.generateStructuredOutput(model, config.schema, {
|
|
391
431
|
messages: config.messages,
|
|
432
|
+
...withSystemPromptCache(model, void 0, void 0),
|
|
392
433
|
temperature: config.temperature,
|
|
393
434
|
maxRetries: config.maxRetries,
|
|
394
435
|
abortSignal: config.abortSignal
|
|
@@ -664,7 +705,13 @@ var DoclingServer = class _DoclingServer {
|
|
|
664
705
|
await _DoclingServer.killProcessOnPort(this.logger, this.port);
|
|
665
706
|
}
|
|
666
707
|
const doclingServePath = join(this.venvPath, "bin", "docling-serve");
|
|
667
|
-
const args = [
|
|
708
|
+
const args = [
|
|
709
|
+
"run",
|
|
710
|
+
"--host",
|
|
711
|
+
"127.0.0.1",
|
|
712
|
+
"--port",
|
|
713
|
+
this.port.toString()
|
|
714
|
+
];
|
|
668
715
|
this.logger.info(
|
|
669
716
|
"[DoclingEnvironment] Starting docling-serve on port",
|
|
670
717
|
this.port
|
|
@@ -2834,7 +2881,8 @@ var ChunkedPDFConverter = class {
|
|
|
2834
2881
|
};
|
|
2835
2882
|
|
|
2836
2883
|
// src/core/docling-conversion-executor.ts
|
|
2837
|
-
import { existsSync as existsSync4, rmSync as rmSync3 } from "fs";
|
|
2884
|
+
import { existsSync as existsSync4, mkdtempSync, rmSync as rmSync3 } from "fs";
|
|
2885
|
+
import { tmpdir } from "os";
|
|
2838
2886
|
import { join as join7 } from "path";
|
|
2839
2887
|
var DoclingConversionExecutor = class {
|
|
2840
2888
|
constructor(logger, client, timeout) {
|
|
@@ -2864,89 +2912,85 @@ var DoclingConversionExecutor = class {
|
|
|
2864
2912
|
this.logger.info(
|
|
2865
2913
|
"[PDFConverter] Results will be returned as ZIP to avoid memory limits"
|
|
2866
2914
|
);
|
|
2867
|
-
const
|
|
2915
|
+
const outputDir = join7(process.cwd(), "output", reportId);
|
|
2916
|
+
const workDir = mkdtempSync(join7(tmpdir(), "heripo-docling-"));
|
|
2917
|
+
const zipPath = join7(workDir, "result.zip");
|
|
2918
|
+
const extractDir = join7(workDir, "result_extracted");
|
|
2868
2919
|
try {
|
|
2869
|
-
const
|
|
2870
|
-
|
|
2871
|
-
task,
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
this.logger.info(
|
|
2881
|
-
"[PDFConverter] Conversion aborted after docling completion"
|
|
2920
|
+
const { httpUrl, server } = await this.resolveUrl(url);
|
|
2921
|
+
try {
|
|
2922
|
+
const task = await this.startConversionTask(httpUrl, conversionOptions);
|
|
2923
|
+
await trackTaskProgress(
|
|
2924
|
+
task,
|
|
2925
|
+
this.timeout,
|
|
2926
|
+
this.logger,
|
|
2927
|
+
"[PDFConverter]",
|
|
2928
|
+
{
|
|
2929
|
+
showDetailedProgress: true
|
|
2930
|
+
}
|
|
2882
2931
|
);
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
);
|
|
2896
|
-
} finally {
|
|
2897
|
-
if (server) {
|
|
2898
|
-
this.logger.info("[PDFConverter] Stopping local file server...");
|
|
2899
|
-
await server.stop();
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
|
-
const cwd = process.cwd();
|
|
2903
|
-
const zipPath = join7(cwd, "result.zip");
|
|
2904
|
-
const extractDir = join7(cwd, "result_extracted");
|
|
2905
|
-
const outputDir = join7(cwd, "output", reportId);
|
|
2906
|
-
try {
|
|
2907
|
-
await this.processConvertedFiles(zipPath, extractDir, outputDir);
|
|
2908
|
-
if (url.startsWith("file://")) {
|
|
2909
|
-
await renderAndUpdatePageImages(
|
|
2910
|
-
url.slice(7),
|
|
2911
|
-
outputDir,
|
|
2932
|
+
if (abortSignal?.aborted) {
|
|
2933
|
+
this.logger.info(
|
|
2934
|
+
"[PDFConverter] Conversion aborted after docling completion"
|
|
2935
|
+
);
|
|
2936
|
+
const error = new Error("PDF conversion was aborted");
|
|
2937
|
+
error.name = "AbortError";
|
|
2938
|
+
throw error;
|
|
2939
|
+
}
|
|
2940
|
+
await downloadTaskResult(
|
|
2941
|
+
this.client,
|
|
2942
|
+
task.taskId,
|
|
2943
|
+
zipPath,
|
|
2912
2944
|
this.logger,
|
|
2913
2945
|
"[PDFConverter]"
|
|
2914
2946
|
);
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
"[PDFConverter]
|
|
2918
|
-
|
|
2947
|
+
} finally {
|
|
2948
|
+
if (server) {
|
|
2949
|
+
this.logger.info("[PDFConverter] Stopping local file server...");
|
|
2950
|
+
await server.stop();
|
|
2951
|
+
}
|
|
2919
2952
|
}
|
|
2920
|
-
|
|
2921
|
-
this.
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2953
|
+
try {
|
|
2954
|
+
await this.processConvertedFiles(zipPath, extractDir, outputDir);
|
|
2955
|
+
if (url.startsWith("file://")) {
|
|
2956
|
+
await renderAndUpdatePageImages(
|
|
2957
|
+
url.slice(7),
|
|
2958
|
+
outputDir,
|
|
2959
|
+
this.logger,
|
|
2960
|
+
"[PDFConverter]"
|
|
2961
|
+
);
|
|
2962
|
+
} else {
|
|
2963
|
+
this.logger.warn(
|
|
2964
|
+
"[PDFConverter] Page image rendering skipped: only supported for local files (file:// URLs)"
|
|
2965
|
+
);
|
|
2966
|
+
}
|
|
2967
|
+
if (abortSignal?.aborted) {
|
|
2968
|
+
this.logger.info("[PDFConverter] Conversion aborted before callback");
|
|
2969
|
+
const error = new Error("PDF conversion was aborted");
|
|
2970
|
+
error.name = "AbortError";
|
|
2971
|
+
throw error;
|
|
2972
|
+
}
|
|
2973
|
+
this.logger.info("[PDFConverter] Executing completion callback...");
|
|
2974
|
+
await onComplete(outputDir);
|
|
2975
|
+
const duration = Date.now() - startTime;
|
|
2976
|
+
this.logger.info("[PDFConverter] Conversion completed successfully!");
|
|
2977
|
+
this.logger.info("[PDFConverter] Total time:", duration, "ms");
|
|
2978
|
+
} finally {
|
|
2979
|
+
if (cleanupAfterCallback) {
|
|
2980
|
+
this.logger.info(
|
|
2981
|
+
"[PDFConverter] Cleaning up output directory:",
|
|
2982
|
+
outputDir
|
|
2983
|
+
);
|
|
2984
|
+
if (existsSync4(outputDir)) {
|
|
2985
|
+
rmSync3(outputDir, { recursive: true, force: true });
|
|
2986
|
+
}
|
|
2987
|
+
} else {
|
|
2988
|
+
this.logger.info("[PDFConverter] Output preserved at:", outputDir);
|
|
2989
|
+
}
|
|
2925
2990
|
}
|
|
2926
|
-
this.logger.info("[PDFConverter] Executing completion callback...");
|
|
2927
|
-
await onComplete(outputDir);
|
|
2928
|
-
const duration = Date.now() - startTime;
|
|
2929
|
-
this.logger.info("[PDFConverter] Conversion completed successfully!");
|
|
2930
|
-
this.logger.info("[PDFConverter] Total time:", duration, "ms");
|
|
2931
2991
|
} finally {
|
|
2932
2992
|
this.logger.info("[PDFConverter] Cleaning up temporary files...");
|
|
2933
|
-
|
|
2934
|
-
rmSync3(zipPath, { force: true });
|
|
2935
|
-
}
|
|
2936
|
-
if (existsSync4(extractDir)) {
|
|
2937
|
-
rmSync3(extractDir, { recursive: true, force: true });
|
|
2938
|
-
}
|
|
2939
|
-
if (cleanupAfterCallback) {
|
|
2940
|
-
this.logger.info(
|
|
2941
|
-
"[PDFConverter] Cleaning up output directory:",
|
|
2942
|
-
outputDir
|
|
2943
|
-
);
|
|
2944
|
-
if (existsSync4(outputDir)) {
|
|
2945
|
-
rmSync3(outputDir, { recursive: true, force: true });
|
|
2946
|
-
}
|
|
2947
|
-
} else {
|
|
2948
|
-
this.logger.info("[PDFConverter] Output preserved at:", outputDir);
|
|
2949
|
-
}
|
|
2993
|
+
rmSync3(workDir, { recursive: true, force: true });
|
|
2950
2994
|
}
|
|
2951
2995
|
return null;
|
|
2952
2996
|
}
|
|
@@ -3001,7 +3045,7 @@ var DoclingConversionExecutor = class {
|
|
|
3001
3045
|
|
|
3002
3046
|
// src/core/image-pdf-converter.ts
|
|
3003
3047
|
import { existsSync as existsSync5, rmSync as rmSync4 } from "fs";
|
|
3004
|
-
import { tmpdir } from "os";
|
|
3048
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
3005
3049
|
import { join as join8 } from "path";
|
|
3006
3050
|
var ImagePdfConverter = class {
|
|
3007
3051
|
constructor(logger) {
|
|
@@ -3018,7 +3062,7 @@ var ImagePdfConverter = class {
|
|
|
3018
3062
|
*/
|
|
3019
3063
|
async convert(pdfUrl, reportId) {
|
|
3020
3064
|
const timestamp = Date.now();
|
|
3021
|
-
const tempDir =
|
|
3065
|
+
const tempDir = tmpdir2();
|
|
3022
3066
|
const inputPath = join8(tempDir, `${reportId}-${timestamp}-input.pdf`);
|
|
3023
3067
|
const outputPath = join8(tempDir, `${reportId}-${timestamp}-image.pdf`);
|
|
3024
3068
|
try {
|