@heripo/pdf-parser 0.1.32 → 0.1.34

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.d.cts CHANGED
@@ -9,10 +9,20 @@ type ReviewAssistanceTaskId = 'text_ocr_hanja' | 'text_integrity' | 'text_role_f
9
9
 
10
10
  interface PDFCorrectionModelOptions {
11
11
  textCorrection: LanguageModel;
12
+ /** textCorrection 실패(예: lmstudio 400) 시 LLMCaller fallback. */
13
+ textCorrectionFallback?: LanguageModel;
12
14
  pageGate: LanguageModel;
15
+ /** pageGate(page-eligibility) 실패 시 fallback. */
16
+ pageGateFallback?: LanguageModel;
13
17
  reviewAssistance: LanguageModel;
18
+ /** review-assistance 공통 fallback (task별 미지정 시 사용). */
19
+ reviewAssistanceFallback?: LanguageModel;
14
20
  reviewAssistanceTasks?: Partial<Record<ReviewAssistanceTaskId, LanguageModel>>;
21
+ /** review-assistance task별 fallback. */
22
+ reviewAssistanceTasksFallback?: Partial<Record<ReviewAssistanceTaskId, LanguageModel>>;
15
23
  tableCorrection?: LanguageModel;
24
+ /** tableCorrection 실패 시 fallback. */
25
+ tableCorrectionFallback?: LanguageModel;
16
26
  }
17
27
  interface PDFCorrectionConcurrencyOptions {
18
28
  pages?: number;
@@ -93,6 +103,8 @@ type PDFConvertOptions = Omit<ConversionOptions, 'to_formats' | 'image_export_mo
93
103
  chunkMaxRetries?: number;
94
104
  /** Vision model for document language detection when the PDF text layer is insufficient */
95
105
  languageDetectionModel?: LanguageModel;
106
+ /** languageDetectionModel 실패 시 fallback. */
107
+ languageDetectionFallbackModel?: LanguageModel;
96
108
  /** LLM model for document type validation (opt-in: skipped when not set) */
97
109
  documentValidationModel?: LanguageModel;
98
110
  };
package/dist/index.d.ts CHANGED
@@ -9,10 +9,20 @@ type ReviewAssistanceTaskId = 'text_ocr_hanja' | 'text_integrity' | 'text_role_f
9
9
 
10
10
  interface PDFCorrectionModelOptions {
11
11
  textCorrection: LanguageModel;
12
+ /** textCorrection 실패(예: lmstudio 400) 시 LLMCaller fallback. */
13
+ textCorrectionFallback?: LanguageModel;
12
14
  pageGate: LanguageModel;
15
+ /** pageGate(page-eligibility) 실패 시 fallback. */
16
+ pageGateFallback?: LanguageModel;
13
17
  reviewAssistance: LanguageModel;
18
+ /** review-assistance 공통 fallback (task별 미지정 시 사용). */
19
+ reviewAssistanceFallback?: LanguageModel;
14
20
  reviewAssistanceTasks?: Partial<Record<ReviewAssistanceTaskId, LanguageModel>>;
21
+ /** review-assistance task별 fallback. */
22
+ reviewAssistanceTasksFallback?: Partial<Record<ReviewAssistanceTaskId, LanguageModel>>;
15
23
  tableCorrection?: LanguageModel;
24
+ /** tableCorrection 실패 시 fallback. */
25
+ tableCorrectionFallback?: LanguageModel;
16
26
  }
17
27
  interface PDFCorrectionConcurrencyOptions {
18
28
  pages?: number;
@@ -93,6 +103,8 @@ type PDFConvertOptions = Omit<ConversionOptions, 'to_formats' | 'image_export_mo
93
103
  chunkMaxRetries?: number;
94
104
  /** Vision model for document language detection when the PDF text layer is insufficient */
95
105
  languageDetectionModel?: LanguageModel;
106
+ /** languageDetectionModel 실패 시 fallback. */
107
+ languageDetectionFallbackModel?: LanguageModel;
96
108
  /** LLM model for document type validation (opt-in: skipped when not set) */
97
109
  documentValidationModel?: LanguageModel;
98
110
  };
package/dist/index.js CHANGED
@@ -138,6 +138,8 @@ function detectProvider(model) {
138
138
  if (providerId.includes("google")) return "google";
139
139
  if (providerId.includes("anthropic")) return "anthropic";
140
140
  if (providerId.includes("together")) return "togetherai";
141
+ if (providerId.includes("ollama")) return "ollama";
142
+ if (providerId.includes("lmstudio")) return "lmstudio";
141
143
  return "unknown";
142
144
  }
143
145
  var LLMCaller = class {
@@ -225,14 +227,18 @@ var LLMCaller = class {
225
227
  *
226
228
  * Strategy per provider:
227
229
  * - OpenAI / Anthropic / Google Gemini: `Output.object()` with schema retry
228
- * - Together AI / unknown: forced tool call pattern
230
+ * - Together AI / Ollama / unknown: forced tool call pattern
231
+ *
232
+ * Ollama uses tool-call because MLX backends ignore the `format` grammar
233
+ * that `Output.object()` relies on, and gguf's grammar is unstable once
234
+ * thinking is disabled. Applies to both text and vision paths.
229
235
  *
230
236
  * Retries up to MAX_STRUCTURED_OUTPUT_RETRIES times on NoObjectGeneratedError
231
237
  * (schema mismatch), re-throwing the last error if all attempts fail.
232
238
  */
233
239
  static async generateStructuredOutput(model, schema, promptParams) {
234
240
  const providerType = detectProvider(model);
235
- if (providerType === "togetherai" || providerType === "unknown") {
241
+ if (providerType === "togetherai" || providerType === "ollama" || providerType === "unknown") {
236
242
  return this.generateViaToolCall(model, schema, promptParams);
237
243
  }
238
244
  let lastError;
@@ -337,6 +343,29 @@ var LLMCaller = class {
337
343
  function formatTokens(usage) {
338
344
  return `${usage.inputTokens} input, ${usage.outputTokens} output, ${usage.totalTokens} total`;
339
345
  }
346
+ function emptyUsage() {
347
+ return { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
348
+ }
349
+ function addInto(target, src) {
350
+ target.inputTokens += src.inputTokens;
351
+ target.outputTokens += src.outputTokens;
352
+ target.totalTokens += src.totalTokens;
353
+ }
354
+ 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
+ };
360
+ }
361
+ function accumulateModel(bucket, modelName, usage) {
362
+ let entry = bucket.get(modelName);
363
+ if (!entry) {
364
+ entry = emptyUsage();
365
+ bucket.set(modelName, entry);
366
+ }
367
+ addInto(entry, usage);
368
+ }
340
369
  var LLMTokenUsageAggregator = class {
341
370
  usage = {};
342
371
  /**
@@ -349,21 +378,15 @@ var LLMTokenUsageAggregator = class {
349
378
  this.usage[usage.component] = {
350
379
  component: usage.component,
351
380
  phases: {},
352
- total: {
353
- inputTokens: 0,
354
- outputTokens: 0,
355
- totalTokens: 0
356
- }
381
+ total: emptyUsage()
357
382
  };
358
383
  }
359
384
  const component = this.usage[usage.component];
360
385
  if (!component.phases[usage.phase]) {
361
386
  component.phases[usage.phase] = {
362
- total: {
363
- inputTokens: 0,
364
- outputTokens: 0,
365
- totalTokens: 0
366
- },
387
+ primary: /* @__PURE__ */ new Map(),
388
+ fallback: /* @__PURE__ */ new Map(),
389
+ total: emptyUsage(),
367
390
  metadata: []
368
391
  };
369
392
  }
@@ -372,41 +395,17 @@ var LLMTokenUsageAggregator = class {
372
395
  phase.metadata.push(usage.metadata);
373
396
  }
374
397
  if (usage.model === "primary") {
375
- if (!phase.primary) {
376
- phase.primary = {
377
- modelName: usage.modelName,
378
- inputTokens: 0,
379
- outputTokens: 0,
380
- totalTokens: 0
381
- };
382
- }
383
- phase.primary.inputTokens += usage.inputTokens;
384
- phase.primary.outputTokens += usage.outputTokens;
385
- phase.primary.totalTokens += usage.totalTokens;
398
+ accumulateModel(phase.primary, usage.modelName, usage);
386
399
  } else if (usage.model === "fallback") {
387
- if (!phase.fallback) {
388
- phase.fallback = {
389
- modelName: usage.modelName,
390
- inputTokens: 0,
391
- outputTokens: 0,
392
- totalTokens: 0
393
- };
394
- }
395
- phase.fallback.inputTokens += usage.inputTokens;
396
- phase.fallback.outputTokens += usage.outputTokens;
397
- phase.fallback.totalTokens += usage.totalTokens;
400
+ accumulateModel(phase.fallback, usage.modelName, usage);
398
401
  }
399
- phase.total.inputTokens += usage.inputTokens;
400
- phase.total.outputTokens += usage.outputTokens;
401
- phase.total.totalTokens += usage.totalTokens;
402
- component.total.inputTokens += usage.inputTokens;
403
- component.total.outputTokens += usage.outputTokens;
404
- component.total.totalTokens += usage.totalTokens;
402
+ addInto(phase.total, usage);
403
+ addInto(component.total, usage);
405
404
  }
406
405
  /**
407
406
  * Get aggregated usage grouped by component
408
407
  *
409
- * @returns Array of component aggregates with phase breakdown
408
+ * @returns Array of component aggregates with per-model phase breakdown
410
409
  */
411
410
  getByComponent() {
412
411
  return Object.values(this.usage);
@@ -415,8 +414,16 @@ var LLMTokenUsageAggregator = class {
415
414
  * Get token usage report in structured JSON format
416
415
  *
417
416
  * Converts internal usage data to external TokenUsageReport format suitable
418
- * for serialization and reporting. The report includes component breakdown,
419
- * phase-level details, and both primary and fallback model usage.
417
+ * for serialization and reporting.
418
+ *
419
+ * Each (phase, model) pair becomes its own PhaseUsageReport entry. A phase
420
+ * that used a single primary (and/or a single fallback) model yields exactly
421
+ * one entry — identical to the legacy shape — while a phase that mixed models
422
+ * yields one entry per model, all sharing the same `phase` name. Every
423
+ * (phase, tier, modelName) combination appears in exactly one entry, so
424
+ * consumers that key by `${component}|${phase}|${tier}|${modelName}`
425
+ * (heripo-web's token-usage emitter / ledger recorder) attribute each model's
426
+ * tokens — and cost — correctly without double counting.
420
427
  *
421
428
  * @returns Structured token usage report with components and total
422
429
  */
@@ -425,34 +432,41 @@ var LLMTokenUsageAggregator = class {
425
432
  for (const component of Object.values(this.usage)) {
426
433
  const phases = [];
427
434
  for (const [phaseName, phaseData] of Object.entries(component.phases)) {
428
- const phaseReport = {
429
- phase: phaseName,
430
- total: {
431
- inputTokens: phaseData.total.inputTokens,
432
- outputTokens: phaseData.total.outputTokens,
433
- totalTokens: phaseData.total.totalTokens
434
- }
435
- };
436
- if (phaseData.metadata.length > 0) {
437
- phaseReport.metadata = [...phaseData.metadata];
438
- }
439
- if (phaseData.primary) {
440
- phaseReport.primary = {
441
- modelName: phaseData.primary.modelName,
442
- inputTokens: phaseData.primary.inputTokens,
443
- outputTokens: phaseData.primary.outputTokens,
444
- totalTokens: phaseData.primary.totalTokens
435
+ const primaryEntries = [...phaseData.primary.entries()];
436
+ const fallbackEntries = [...phaseData.fallback.entries()];
437
+ const entryCount = Math.max(
438
+ primaryEntries.length,
439
+ fallbackEntries.length
440
+ );
441
+ if (entryCount === 0) {
442
+ const phaseReport = {
443
+ phase: phaseName,
444
+ total: { ...phaseData.total }
445
445
  };
446
+ if (phaseData.metadata.length > 0) {
447
+ phaseReport.metadata = [...phaseData.metadata];
448
+ }
449
+ phases.push(phaseReport);
450
+ continue;
446
451
  }
447
- if (phaseData.fallback) {
448
- phaseReport.fallback = {
449
- modelName: phaseData.fallback.modelName,
450
- inputTokens: phaseData.fallback.inputTokens,
451
- outputTokens: phaseData.fallback.outputTokens,
452
- totalTokens: phaseData.fallback.totalTokens
452
+ for (let i = 0; i < entryCount; i++) {
453
+ const primary = primaryEntries[i];
454
+ const fallback = fallbackEntries[i];
455
+ const phaseReport = {
456
+ phase: phaseName,
457
+ total: sumUsage(primary?.[1], fallback?.[1])
453
458
  };
459
+ if (primary) {
460
+ phaseReport.primary = { modelName: primary[0], ...primary[1] };
461
+ }
462
+ if (fallback) {
463
+ phaseReport.fallback = { modelName: fallback[0], ...fallback[1] };
464
+ }
465
+ if (i === 0 && phaseData.metadata.length > 0) {
466
+ phaseReport.metadata = [...phaseData.metadata];
467
+ }
468
+ phases.push(phaseReport);
454
469
  }
455
- phases.push(phaseReport);
456
470
  }
457
471
  components.push({
458
472
  component: component.component,
@@ -497,8 +511,8 @@ var LLMTokenUsageAggregator = class {
497
511
  /**
498
512
  * Log comprehensive token usage summary
499
513
  *
500
- * Outputs usage grouped by component, with phase and model breakdown.
501
- * Shows primary and fallback token usage separately for each phase.
514
+ * Outputs usage grouped by component, with phase and per-model breakdown.
515
+ * Shows each primary and fallback model on its own line for each phase.
502
516
  * Call this once at the end of document processing.
503
517
  *
504
518
  * @param logger - Logger instance for output
@@ -514,31 +528,23 @@ var LLMTokenUsageAggregator = class {
514
528
  let grandInputTokens = 0;
515
529
  let grandOutputTokens = 0;
516
530
  let grandTotalTokens = 0;
517
- let grandPrimaryInputTokens = 0;
518
- let grandPrimaryOutputTokens = 0;
519
- let grandPrimaryTotalTokens = 0;
520
- let grandFallbackInputTokens = 0;
521
- let grandFallbackOutputTokens = 0;
522
- let grandFallbackTotalTokens = 0;
531
+ const grandPrimary = emptyUsage();
532
+ const grandFallback = emptyUsage();
523
533
  for (const component of components) {
524
534
  logger.info(`${component.component}:`);
525
- for (const [phase, phaseData] of Object.entries(component.phases)) {
526
- logger.info(` - ${phase}:`);
527
- if (phaseData.primary) {
535
+ for (const [phaseName, phaseData] of Object.entries(component.phases)) {
536
+ logger.info(` - ${phaseName}:`);
537
+ for (const [modelName, modelUsage] of phaseData.primary) {
528
538
  logger.info(
529
- ` primary (${phaseData.primary.modelName}): ${formatTokens(phaseData.primary)}`
539
+ ` primary (${modelName}): ${formatTokens(modelUsage)}`
530
540
  );
531
- grandPrimaryInputTokens += phaseData.primary.inputTokens;
532
- grandPrimaryOutputTokens += phaseData.primary.outputTokens;
533
- grandPrimaryTotalTokens += phaseData.primary.totalTokens;
541
+ addInto(grandPrimary, modelUsage);
534
542
  }
535
- if (phaseData.fallback) {
543
+ for (const [modelName, modelUsage] of phaseData.fallback) {
536
544
  logger.info(
537
- ` fallback (${phaseData.fallback.modelName}): ${formatTokens(phaseData.fallback)}`
545
+ ` fallback (${modelName}): ${formatTokens(modelUsage)}`
538
546
  );
539
- grandFallbackInputTokens += phaseData.fallback.inputTokens;
540
- grandFallbackOutputTokens += phaseData.fallback.outputTokens;
541
- grandFallbackTotalTokens += phaseData.fallback.totalTokens;
547
+ addInto(grandFallback, modelUsage);
542
548
  }
543
549
  logger.info(` subtotal: ${formatTokens(phaseData.total)}`);
544
550
  }
@@ -551,23 +557,11 @@ var LLMTokenUsageAggregator = class {
551
557
  grandTotalTokens += component.total.totalTokens;
552
558
  }
553
559
  logger.info("--- Summary ---");
554
- if (grandPrimaryTotalTokens > 0) {
555
- logger.info(
556
- `Primary total: ${formatTokens({
557
- inputTokens: grandPrimaryInputTokens,
558
- outputTokens: grandPrimaryOutputTokens,
559
- totalTokens: grandPrimaryTotalTokens
560
- })}`
561
- );
560
+ if (grandPrimary.totalTokens > 0) {
561
+ logger.info(`Primary total: ${formatTokens(grandPrimary)}`);
562
562
  }
563
- if (grandFallbackTotalTokens > 0) {
564
- logger.info(
565
- `Fallback total: ${formatTokens({
566
- inputTokens: grandFallbackInputTokens,
567
- outputTokens: grandFallbackOutputTokens,
568
- totalTokens: grandFallbackTotalTokens
569
- })}`
570
- );
563
+ if (grandFallback.totalTokens > 0) {
564
+ logger.info(`Fallback total: ${formatTokens(grandFallback)}`);
571
565
  }
572
566
  logger.info(
573
567
  `Grand total: ${formatTokens({
@@ -1334,6 +1328,7 @@ var PdfLanguageDetector = class {
1334
1328
  }
1335
1329
  ],
1336
1330
  primaryModel: options.model,
1331
+ fallbackModel: options.fallbackModel,
1337
1332
  maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
1338
1333
  temperature: options.temperature ?? 0,
1339
1334
  abortSignal: options.abortSignal,
@@ -3302,6 +3297,7 @@ var ReviewAssistancePageGate = class {
3302
3297
  }
3303
3298
  ],
3304
3299
  primaryModel: model,
3300
+ fallbackModel: options.fallbackModel,
3305
3301
  maxRetries: options.maxRetries ?? REVIEW_ASSISTANCE_PAGE_GATE_MAX_RETRIES,
3306
3302
  temperature: options.temperature ?? REVIEW_ASSISTANCE_PAGE_GATE_TEMPERATURE,
3307
3303
  abortSignal: options.abortSignal,
@@ -3573,6 +3569,7 @@ var PostDoclingPageProcessor = class {
3573
3569
  }
3574
3570
  ],
3575
3571
  primaryModel: model,
3572
+ fallbackModel: options?.fallbackModel,
3576
3573
  maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES2,
3577
3574
  temperature: options?.temperature ?? DEFAULT_TEMPERATURE,
3578
3575
  abortSignal: options?.abortSignal,
@@ -3618,6 +3615,7 @@ var PostDoclingPageProcessor = class {
3618
3615
  image,
3619
3616
  gateOptions.model,
3620
3617
  {
3618
+ fallbackModel: gateOptions.fallback,
3621
3619
  maxRetries: gateOptions.maxRetries,
3622
3620
  temperature: gateOptions.temperature,
3623
3621
  abortSignal: options?.abortSignal,
@@ -8863,6 +8861,7 @@ var ReviewAssistanceRunner = class {
8863
8861
  image,
8864
8862
  options.pageGateModel,
8865
8863
  {
8864
+ fallbackModel: options.pageGateFallback,
8866
8865
  maxRetries: options.pageGateMaxRetries,
8867
8866
  temperature: options.pageGateTemperature,
8868
8867
  abortSignal: options.abortSignal,
@@ -9141,7 +9140,7 @@ var ReviewAssistanceRunner = class {
9141
9140
  async reviewWorkItem(context, workItem, image, pageCount, modelResolver, options) {
9142
9141
  const startedAtMs = Date.now();
9143
9142
  const startedAt = new Date(startedAtMs).toISOString();
9144
- const model = modelResolver(workItem.task);
9143
+ const { model, fallback } = modelResolver(workItem.task);
9145
9144
  const modelId = this.getModelId(model);
9146
9145
  let attempts = 0;
9147
9146
  let validationFeedback = [];
@@ -9182,6 +9181,7 @@ var ReviewAssistanceRunner = class {
9182
9181
  }
9183
9182
  ],
9184
9183
  primaryModel: model,
9184
+ fallbackModel: fallback,
9185
9185
  maxRetries: this.getTaskMaxRetries(workItem.task, options),
9186
9186
  temperature: options.temperature,
9187
9187
  abortSignal: options.abortSignal,
@@ -9968,7 +9968,7 @@ var ReviewAssistanceRunner = class {
9968
9968
  */
9969
9969
  async callMergeArbiter(candidates, context, image, modelResolver, options) {
9970
9970
  const arbiterTask = this.resolveMergeTaskDefinition(candidates);
9971
- const model = modelResolver(arbiterTask);
9971
+ const { model, fallback } = modelResolver(arbiterTask);
9972
9972
  const conflictRefs = [
9973
9973
  ...new Set(
9974
9974
  candidates.flatMap(
@@ -10007,6 +10007,7 @@ var ReviewAssistanceRunner = class {
10007
10007
  }
10008
10008
  ],
10009
10009
  primaryModel: model,
10010
+ fallbackModel: fallback,
10010
10011
  maxRetries: REVIEW_ASSISTANCE_MERGE_MAX_RETRIES,
10011
10012
  temperature: options.temperature,
10012
10013
  abortSignal: options.abortSignal,
@@ -10559,6 +10560,7 @@ var PostDoclingCorrectionPipeline = class {
10559
10560
  outputDir,
10560
10561
  correction.models.textCorrection,
10561
10562
  {
10563
+ fallbackModel: correction.models.textCorrectionFallback,
10562
10564
  concurrency: correction.concurrency.pages,
10563
10565
  maxRetries: correction.maxRetries.textCorrection,
10564
10566
  temperature: correction.temperature,
@@ -10569,6 +10571,7 @@ var PostDoclingCorrectionPipeline = class {
10569
10571
  pageTexts,
10570
10572
  reviewAssistanceGate: {
10571
10573
  model: correction.models.pageGate,
10574
+ fallback: correction.models.pageGateFallback,
10572
10575
  maxRetries: correction.maxRetries.pageGate,
10573
10576
  temperature: correction.temperature,
10574
10577
  outputLanguage: correction.outputLanguage
@@ -10584,6 +10587,7 @@ var PostDoclingCorrectionPipeline = class {
10584
10587
  pdfPath,
10585
10588
  pageTexts,
10586
10589
  pageGateModel: correction.models.pageGate,
10590
+ pageGateFallback: correction.models.pageGateFallback,
10587
10591
  pageGateMaxRetries: correction.maxRetries.pageGate,
10588
10592
  pageGateTemperature: correction.temperature,
10589
10593
  pageConcurrency: correction.concurrency.pages,
@@ -10609,9 +10613,15 @@ var PostDoclingCorrectionPipeline = class {
10609
10613
  buildReviewAssistanceModelResolver(correction) {
10610
10614
  return (task) => {
10611
10615
  if (task.id === "tables") {
10612
- return correction.models.tableCorrection ?? correction.models.reviewAssistanceTasks?.tables ?? correction.models.reviewAssistance;
10616
+ return {
10617
+ model: correction.models.tableCorrection ?? correction.models.reviewAssistanceTasks?.tables ?? correction.models.reviewAssistance,
10618
+ fallback: correction.models.tableCorrectionFallback ?? correction.models.reviewAssistanceTasksFallback?.tables ?? correction.models.reviewAssistanceFallback
10619
+ };
10613
10620
  }
10614
- return correction.models.reviewAssistanceTasks?.[task.id] ?? correction.models.reviewAssistance;
10621
+ return {
10622
+ model: correction.models.reviewAssistanceTasks?.[task.id] ?? correction.models.reviewAssistance,
10623
+ fallback: correction.models.reviewAssistanceTasksFallback?.[task.id] ?? correction.models.reviewAssistanceFallback
10624
+ };
10615
10625
  };
10616
10626
  }
10617
10627
  async extractPageTexts(pdfPath, outputDir) {
@@ -10759,6 +10769,7 @@ var PDFConverter = class {
10759
10769
  try {
10760
10770
  const result = await detector.detect(pdfPath, languageDetectionDir, {
10761
10771
  model: options.languageDetectionModel,
10772
+ fallbackModel: options.languageDetectionFallbackModel,
10762
10773
  aggregator: options.aggregator,
10763
10774
  abortSignal
10764
10775
  });