@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 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
- instructions: config.cacheSystemPrompt ? {
360
- role: "system",
361
- content: config.systemPrompt,
362
- providerOptions: {
363
- anthropic: { cacheControl: { type: "ephemeral" } }
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 = ["run", "--port", this.port.toString()];
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 { httpUrl, server } = await this.resolveUrl(url);
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 task = await this.startConversionTask(httpUrl, conversionOptions);
2870
- await trackTaskProgress(
2871
- task,
2872
- this.timeout,
2873
- this.logger,
2874
- "[PDFConverter]",
2875
- {
2876
- showDetailedProgress: true
2877
- }
2878
- );
2879
- if (abortSignal?.aborted) {
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
- const error = new Error("PDF conversion was aborted");
2884
- error.name = "AbortError";
2885
- throw error;
2886
- }
2887
- const cwd2 = process.cwd();
2888
- const zipPath2 = join7(cwd2, "result.zip");
2889
- await downloadTaskResult(
2890
- this.client,
2891
- task.taskId,
2892
- zipPath2,
2893
- this.logger,
2894
- "[PDFConverter]"
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
- } else {
2916
- this.logger.warn(
2917
- "[PDFConverter] Page image rendering skipped: only supported for local files (file:// URLs)"
2918
- );
2947
+ } finally {
2948
+ if (server) {
2949
+ this.logger.info("[PDFConverter] Stopping local file server...");
2950
+ await server.stop();
2951
+ }
2919
2952
  }
2920
- if (abortSignal?.aborted) {
2921
- this.logger.info("[PDFConverter] Conversion aborted before callback");
2922
- const error = new Error("PDF conversion was aborted");
2923
- error.name = "AbortError";
2924
- throw error;
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
- if (existsSync4(zipPath)) {
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 = tmpdir();
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 {