@claritylabs/cl-sdk 1.3.1 → 1.3.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/README.md CHANGED
@@ -137,7 +137,7 @@ If your callback ignores those fields, the model will only see the text prompt.
137
137
 
138
138
  ## Model routing metadata
139
139
 
140
- Every SDK model callback may receive `taskKind`, `budgetDiagnostics`, and `trace`. Hosts can use these provider-agnostic fields for cheap-first routing, fallback, and telemetry without the SDK hardcoding model names. Example task kinds include `extraction_classify`, `extraction_focused`, `extraction_review`, `query_reason`, `application_extract_fields`, and `pce_impact_analysis`. `budgetDiagnostics` includes the resolved max-token budget and truncation-risk warnings for the current subtask. `trace` identifies the current extractor, page range, format batch, or source-backed call so host logs can show what was being generated instead of a generic model-call label.
140
+ Every SDK model callback may receive `taskKind`, `budgetDiagnostics`, and `trace`. Hosts can use these provider-agnostic fields for cheap-first routing, fallback, and telemetry without the SDK hardcoding model names. Example task kinds include `extraction_classify`, `extraction_focused`, `extraction_review`, `query_reason`, `application_extract_fields`, and `pce_impact_analysis`. `budgetDiagnostics` includes the resolved output-token cap, the lower preferred task budget, and truncation-risk warnings for the current subtask. When model capabilities include `maxOutputTokens`, the SDK uses that model maximum as the request cap instead of treating low task preferences as hard limits. `trace` identifies the current extractor, page range, format batch, or source-backed call so host logs can show what was being generated instead of a generic model-call label.
141
141
 
142
142
  ## Bounded Agentic Workflows
143
143
 
package/dist/index.d.mts CHANGED
@@ -41,6 +41,7 @@ interface ModelBudgetResolution {
41
41
  taskKind: ModelTaskKind;
42
42
  maxTokens: number;
43
43
  hintTokens: number;
44
+ preferredOutputTokens?: number;
44
45
  modelMaxOutputTokens?: number;
45
46
  hardMaxOutputTokens?: number;
46
47
  estimatedInputTokens?: number;
package/dist/index.d.ts CHANGED
@@ -41,6 +41,7 @@ interface ModelBudgetResolution {
41
41
  taskKind: ModelTaskKind;
42
42
  maxTokens: number;
43
43
  hintTokens: number;
44
+ preferredOutputTokens?: number;
44
45
  modelMaxOutputTokens?: number;
45
46
  hardMaxOutputTokens?: number;
46
47
  estimatedInputTokens?: number;
package/dist/index.js CHANGED
@@ -559,7 +559,8 @@ function resolveModelBudget(params) {
559
559
  const schemaTokens = estimateTokens(params.schemaSizeBytes) ?? 0;
560
560
  const expectedListLength = positiveInteger(params.expectedListLength) ?? 0;
561
561
  const warnings = [];
562
- let maxTokens = constrainedPreference ?? taskCapability ?? longListCapability ?? defaultCapability ?? hintTokens;
562
+ const preferredOutputTokens = constrainedPreference ?? taskCapability ?? longListCapability ?? defaultCapability ?? hintTokens;
563
+ let maxTokens = hardMaxOutputTokens ?? modelMaxOutputTokens ?? preferredOutputTokens;
563
564
  if (minOutputTokens) {
564
565
  maxTokens = Math.max(maxTokens, minOutputTokens);
565
566
  }
@@ -588,6 +589,7 @@ function resolveModelBudget(params) {
588
589
  taskKind,
589
590
  maxTokens,
590
591
  hintTokens,
592
+ preferredOutputTokens,
591
593
  modelMaxOutputTokens,
592
594
  hardMaxOutputTokens,
593
595
  estimatedInputTokens,
@@ -8183,7 +8185,7 @@ function normalizePageAssignments(pageAssignments, formInventory) {
8183
8185
  }
8184
8186
  return pageAssignments.map((assignment) => {
8185
8187
  let extractorNames = [...new Set(
8186
- (assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"]).filter(Boolean)
8188
+ assignment.extractorNames.filter(Boolean)
8187
8189
  )];
8188
8190
  const hasDeclarations = extractorNames.includes("declarations");
8189
8191
  const hasConditions = extractorNames.includes("conditions");
@@ -8202,9 +8204,6 @@ function normalizePageAssignments(pageAssignments, formInventory) {
8202
8204
  if (inventoryTypes?.has("endorsement") && !extractorNames.includes("endorsements")) {
8203
8205
  extractorNames = [...extractorNames, "endorsements"];
8204
8206
  }
8205
- if (extractorNames.length === 0) {
8206
- extractorNames = ["sections"];
8207
- }
8208
8207
  return {
8209
8208
  ...assignment,
8210
8209
  extractorNames
@@ -8241,20 +8240,12 @@ function groupContiguousPages(pages) {
8241
8240
  function buildPlanFromPageAssignments(pageAssignments, pageCount, formInventory) {
8242
8241
  const extractorPages = /* @__PURE__ */ new Map();
8243
8242
  for (const assignment of pageAssignments) {
8244
- const extractors = assignment.extractorNames.length > 0 ? assignment.extractorNames : ["sections"];
8243
+ const focusedExtractors = assignment.extractorNames.filter((name) => name !== "sections");
8244
+ const extractors = focusedExtractors.length > 0 ? focusedExtractors : assignment.extractorNames;
8245
8245
  for (const extractorName of extractors) {
8246
8246
  extractorPages.set(extractorName, [...extractorPages.get(extractorName) ?? [], assignment.localPageNumber]);
8247
8247
  }
8248
8248
  }
8249
- const coveredPages = /* @__PURE__ */ new Set();
8250
- for (const pages of extractorPages.values()) {
8251
- for (const page of pages) coveredPages.add(page);
8252
- }
8253
- for (let page = 1; page <= pageCount; page += 1) {
8254
- if (!coveredPages.has(page)) {
8255
- extractorPages.set("sections", [...extractorPages.get("sections") ?? [], page]);
8256
- }
8257
- }
8258
8249
  const contextualExtractors = /* @__PURE__ */ new Set(["conditions", "covered_reasons", "definitions", "exclusions", "endorsements"]);
8259
8250
  const contextualForms = (formInventory?.forms ?? []).filter(
8260
8251
  (form) => form.pageStart != null && (form.pageEnd ?? form.pageStart) != null
@@ -8541,6 +8532,139 @@ function createExtractor(config) {
8541
8532
  ranges.push({ startPage, endPage: previousPage });
8542
8533
  return ranges;
8543
8534
  }
8535
+ function pageNumberForSpan(span) {
8536
+ return span.pageStart ?? span.location?.startPage ?? span.location?.page;
8537
+ }
8538
+ function spansForPageRange(spans, startPage, endPage) {
8539
+ return spans.filter((span) => {
8540
+ const start = span.pageStart ?? span.location?.startPage ?? span.location?.page;
8541
+ const end = span.pageEnd ?? span.location?.endPage ?? start;
8542
+ return typeof start === "number" && typeof end === "number" && start <= endPage && end >= startPage;
8543
+ });
8544
+ }
8545
+ function inferSectionType(title, text) {
8546
+ const value = `${title} ${text.slice(0, 500)}`.toLowerCase();
8547
+ if (/\bdefinition|defined terms?\b/.test(value)) return "definition";
8548
+ if (/\bexclusion|not covered|does not apply\b/.test(value)) return "exclusion";
8549
+ if (/\bcondition|duties|loss condition|general condition\b/.test(value)) return "condition";
8550
+ if (/\bendorsement|amend|additional insured\b/.test(value)) return "endorsement";
8551
+ if (/\bcovered cause|covered reason|covered peril|cause of loss|perils insured\b/.test(value)) return "covered_reason";
8552
+ if (/\bdeclaration|schedule\b/.test(value)) return "declarations";
8553
+ return "policy_form";
8554
+ }
8555
+ function buildSourceBackedSectionIndex(spans, startPage, endPage) {
8556
+ const candidateSpans = spansForPageRange(spans, startPage, endPage).filter((span) => span.text.trim().length > 0).filter((span) => span.metadata?.sourceUnit === "section_candidate" || span.sectionId || span.text.length >= 160);
8557
+ return {
8558
+ sections: candidateSpans.map((span, index) => {
8559
+ const pageStart = span.pageStart ?? span.location?.startPage ?? span.location?.page ?? startPage;
8560
+ const pageEnd = span.pageEnd ?? span.location?.endPage ?? pageStart;
8561
+ const title = span.sectionId ?? span.formNumber ?? firstHeadingLine(span.text) ?? `Policy text page ${pageStart}`;
8562
+ return {
8563
+ title,
8564
+ sectionNumber: span.formNumber,
8565
+ pageStart,
8566
+ pageEnd,
8567
+ type: inferSectionType(title, span.text),
8568
+ excerpt: span.text.slice(0, 240),
8569
+ recordId: `section_index_${pageStart}_${index}`,
8570
+ sourceSpanIds: [span.id],
8571
+ sourceTextHash: span.textHash ?? sourceSpanTextHash(span.text)
8572
+ };
8573
+ })
8574
+ };
8575
+ }
8576
+ function firstHeadingLine(text) {
8577
+ const line = text.split(/\r?\n/).map((item) => item.trim()).find((item) => item.length > 0);
8578
+ if (!line) return void 0;
8579
+ return line.slice(0, 100);
8580
+ }
8581
+ function buildDeterministicFormInventory(spans, pageCount) {
8582
+ if (spans.length === 0) return void 0;
8583
+ const formPattern = /\b([A-Z]{1,4}\s?\d{2,5}(?:\s?\d{2,4})?|[A-Z]{2,8}-\d{2,8})\b/g;
8584
+ const seen = /* @__PURE__ */ new Map();
8585
+ for (const span of spans) {
8586
+ const page = pageNumberForSpan(span);
8587
+ if (!page) continue;
8588
+ const title = firstHeadingLine(span.text) ?? span.sectionId;
8589
+ const matches = /* @__PURE__ */ new Set([
8590
+ ...span.formNumber ? [span.formNumber] : [],
8591
+ ...Array.from(span.text.slice(0, 1200).matchAll(formPattern), (match) => match[1].trim())
8592
+ ]);
8593
+ for (const formNumber of matches) {
8594
+ const key = `${formNumber}:${title ?? ""}`;
8595
+ const existing = seen.get(key);
8596
+ const formType = inferFormType(title ?? "", span.text);
8597
+ if (existing) {
8598
+ existing.pageStart = Math.min(existing.pageStart ?? page, page);
8599
+ existing.pageEnd = Math.max(existing.pageEnd ?? page, page);
8600
+ continue;
8601
+ }
8602
+ seen.set(key, {
8603
+ formNumber,
8604
+ title,
8605
+ formType,
8606
+ pageStart: Math.min(page, pageCount),
8607
+ pageEnd: Math.min(page, pageCount)
8608
+ });
8609
+ }
8610
+ }
8611
+ const forms = [...seen.values()].sort((a, b) => (a.pageStart ?? 0) - (b.pageStart ?? 0));
8612
+ return forms.length > 0 ? { forms } : void 0;
8613
+ }
8614
+ function inferFormType(title, text) {
8615
+ const value = `${title} ${text.slice(0, 700)}`.toLowerCase();
8616
+ if (/\bdeclarations?|schedule\b/.test(value)) return "declarations";
8617
+ if (/\bendorsement|amendatory|additional insured\b/.test(value)) return "endorsement";
8618
+ if (/\bnotice|department of insurance|complaint|privacy\b/.test(value)) return "notice";
8619
+ if (/\bapplication|questionnaire\b/.test(value)) return "application";
8620
+ if (/\bcoverage|policy form|conditions|exclusions|definitions|causes of loss\b/.test(value)) return "coverage";
8621
+ return "other";
8622
+ }
8623
+ function buildDeterministicPageAssignments(spans, pageCount) {
8624
+ if (spans.length === 0) return [];
8625
+ const pageTexts = /* @__PURE__ */ new Map();
8626
+ for (const span of spans) {
8627
+ const page = pageNumberForSpan(span);
8628
+ if (!page) continue;
8629
+ pageTexts.set(page, [pageTexts.get(page), span.sectionId, span.text].filter(Boolean).join("\n"));
8630
+ }
8631
+ return Array.from({ length: pageCount }, (_, index) => {
8632
+ const page = index + 1;
8633
+ const text = pageTexts.get(page) ?? "";
8634
+ const lower = text.toLowerCase();
8635
+ const extractorNames = [];
8636
+ if (/\bdeclarations?|policy period|named insured|producer|schedule\b/.test(lower)) {
8637
+ extractorNames.push("carrier_info", "named_insured", "declarations");
8638
+ }
8639
+ if (/\blimit|deductible|premium|coinsurance|scheduled amount|blanket\b/.test(lower) && /\$|\b\d{2,}(?:,\d{3})*\b/.test(lower)) {
8640
+ extractorNames.push("coverage_limits");
8641
+ }
8642
+ if (/\bpremium|tax|fee|surcharge\b/.test(lower) && /\$/.test(lower)) extractorNames.push("premium_breakdown");
8643
+ if (/\bendorsement|additional insured|amendatory\b/.test(lower)) extractorNames.push("endorsements");
8644
+ if (/\bexclusion|not covered|does not apply\b/.test(lower)) extractorNames.push("exclusions");
8645
+ if (/\bcondition|duties in the event|loss condition|general condition\b/.test(lower)) extractorNames.push("conditions");
8646
+ if (/\bdefinition|defined terms?\b/.test(lower)) extractorNames.push("definitions");
8647
+ if (/\bcovered cause|covered peril|cause of loss|perils insured\b/.test(lower)) extractorNames.push("covered_reasons");
8648
+ if (textIncludesSupplementarySignal(text)) extractorNames.push("supplementary");
8649
+ if (extractorNames.length === 0 && text.trim().length > 180) extractorNames.push("sections");
8650
+ return {
8651
+ localPageNumber: page,
8652
+ extractorNames: [...new Set(extractorNames)],
8653
+ pageRole: inferPageRole(lower),
8654
+ hasScheduleValues: /\$|\b\d{2,}(?:,\d{3})*\b/.test(lower) && /\blimit|deductible|premium|schedule|class|location\b/.test(lower),
8655
+ confidence: text.trim().length > 0 ? 0.8 : 0,
8656
+ notes: "Deterministic source-span page assignment"
8657
+ };
8658
+ });
8659
+ }
8660
+ function inferPageRole(lowerText) {
8661
+ if (/\bdeclarations?|schedule\b/.test(lowerText)) return "declarations_schedule";
8662
+ if (/\bendorsement\b/.test(lowerText)) return "endorsement_form";
8663
+ if (/\bexclusion|condition\b/.test(lowerText)) return "condition_exclusion_form";
8664
+ if (/\bnotice|department of insurance|complaint\b/.test(lowerText)) return "supplementary";
8665
+ if (lowerText.trim()) return "policy_form";
8666
+ return "other";
8667
+ }
8544
8668
  function shouldRunLlmReview(mode, report, sourceSpansAvailable) {
8545
8669
  if (mode === "skip" || maxReviewRounds <= 0) return false;
8546
8670
  if (mode === "always") return true;
@@ -8576,7 +8700,14 @@ function createExtractor(config) {
8576
8700
  }
8577
8701
  return lines.length > 0 ? lines.join("\n") : "";
8578
8702
  }
8579
- async function runFocusedExtractorTask(task, pdfInput, memory, pageRangeCache, getPageRangePdf, getPageImages, getPageRangeText) {
8703
+ async function runFocusedExtractorTask(task, pdfInput, memory, sourceSpansForSections, pageRangeCache, getPageRangePdf, getPageImages, getPageRangeText) {
8704
+ if (task.extractorName === "sections" && sourceSpansForSections.length > 0) {
8705
+ return {
8706
+ name: "sections",
8707
+ data: buildSourceBackedSectionIndex(sourceSpansForSections, task.startPage, task.endPage),
8708
+ usage: void 0
8709
+ };
8710
+ }
8580
8711
  if (task.extractorName === "supplementary") {
8581
8712
  const alreadyExtractedSummary = buildAlreadyExtractedSummary(memory);
8582
8713
  const budget = resolveBudget("extraction_focused", 4096);
@@ -8825,6 +8956,17 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8825
8956
  formInventory = resumed.formInventory;
8826
8957
  memory.set("form_inventory", formInventory);
8827
8958
  onProgress?.("Resuming from checkpoint (form inventory complete)...");
8959
+ } else if (sourceSpans.length > 0) {
8960
+ formInventory = buildDeterministicFormInventory(sourceSpans, pageCount) ?? { forms: [] };
8961
+ onProgress?.(`Built deterministic form inventory for ${primaryType} ${documentType}.`);
8962
+ memory.set("form_inventory", formInventory);
8963
+ await pipelineCtx.save("form_inventory", {
8964
+ id,
8965
+ pageCount,
8966
+ classifyResult,
8967
+ formInventory,
8968
+ memory: Object.fromEntries(memory)
8969
+ });
8828
8970
  } else {
8829
8971
  onProgress?.(`Building form inventory for ${primaryType} ${documentType}...`);
8830
8972
  const budget = resolveBudget("extraction_form_inventory", 2048);
@@ -8865,6 +9007,20 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8865
9007
  if (resumed?.pageAssignments && pipelineCtx.isPhaseComplete("page_map")) {
8866
9008
  pageAssignments = resumed.pageAssignments;
8867
9009
  onProgress?.("Resuming from checkpoint (page map complete)...");
9010
+ } else if (sourceSpans.length > 0) {
9011
+ onProgress?.(`Mapping document pages from source spans for ${primaryType} ${documentType}...`);
9012
+ pageAssignments = normalizePageAssignments(
9013
+ buildDeterministicPageAssignments(sourceSpans, pageCount),
9014
+ formInventory
9015
+ );
9016
+ await pipelineCtx.save("page_map", {
9017
+ id,
9018
+ pageCount,
9019
+ classifyResult,
9020
+ formInventory,
9021
+ pageAssignments,
9022
+ memory: Object.fromEntries(memory)
9023
+ });
8868
9024
  } else {
8869
9025
  onProgress?.(`Mapping document pages for ${primaryType} ${documentType}...`);
8870
9026
  const chunkSize = 8;
@@ -8984,6 +9140,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
8984
9140
  task,
8985
9141
  extractionPdfInput,
8986
9142
  memory,
9143
+ sourceSpans,
8987
9144
  completedPageRangePdfCache,
8988
9145
  getPageRangePdf,
8989
9146
  convertPdfToImages ? getPageImages : void 0,
@@ -9148,6 +9305,7 @@ ${pageText || "(No Docling text was available for this page range.)"}`;
9148
9305
  task,
9149
9306
  extractionPdfInput,
9150
9307
  memory,
9308
+ sourceSpans,
9151
9309
  completedPageRangePdfCache,
9152
9310
  getPageRangePdf,
9153
9311
  convertPdfToImages ? getPageImages : void 0,