@semiont/jobs 0.5.25 → 0.5.26

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.
@@ -1,4 +1,4 @@
1
- import { createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, busRequest, resourceId, getPrimaryMediaType, assembleAnnotation, findClaimSpan, textExtractionOf, reconcileSelector, GENERATABLE_MEDIA_TYPES, locate, createFragmentSelector, getLocaleEnglishName, estimateTokens, chunkText, isArray, isObject, isString, deriveViews } from '@semiont/core';
1
+ import { createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, busRequest, resourceId, getPrimaryMediaType, assembleAnnotation, findClaimSpan, textExtractionOf, reconcileSelector, GENERATABLE_MEDIA_TYPES, locate, createFragmentSelector, getLocaleEnglishName, estimateTokens, chunkText, isObject, isString, deriveViews } from '@semiont/core';
2
2
  import { anchoredTextStoreOverTransport, deriveStorageUri, extractPdfTextLayer, EXTRACTORS, calculateChecksum, withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';
3
3
  import { execFileSync } from 'child_process';
4
4
  import { existsSync, readFileSync, mkdtempSync, writeFileSync, rmSync } from 'fs';
@@ -3766,9 +3766,9 @@ var require_mapOneOrManyArgs = __commonJS({
3766
3766
  Object.defineProperty(exports, "__esModule", { value: true });
3767
3767
  exports.mapOneOrManyArgs = void 0;
3768
3768
  var map_1 = require_map();
3769
- var isArray2 = Array.isArray;
3769
+ var isArray = Array.isArray;
3770
3770
  function callOrApply(fn, args) {
3771
- return isArray2(args) ? fn.apply(void 0, __spreadArray([], __read(args))) : fn(args);
3771
+ return isArray(args) ? fn.apply(void 0, __spreadArray([], __read(args))) : fn(args);
3772
3772
  }
3773
3773
  function mapOneOrManyArgs(fn) {
3774
3774
  return map_1.map(function(args) {
@@ -3913,14 +3913,14 @@ var require_argsArgArrayOrObject = __commonJS({
3913
3913
  "../../node_modules/rxjs/dist/cjs/internal/util/argsArgArrayOrObject.js"(exports) {
3914
3914
  Object.defineProperty(exports, "__esModule", { value: true });
3915
3915
  exports.argsArgArrayOrObject = void 0;
3916
- var isArray2 = Array.isArray;
3916
+ var isArray = Array.isArray;
3917
3917
  var getPrototypeOf = Object.getPrototypeOf;
3918
3918
  var objectProto = Object.prototype;
3919
3919
  var getKeys = Object.keys;
3920
3920
  function argsArgArrayOrObject(args) {
3921
3921
  if (args.length === 1) {
3922
3922
  var first_1 = args[0];
3923
- if (isArray2(first_1)) {
3923
+ if (isArray(first_1)) {
3924
3924
  return { args: first_1, keys: null };
3925
3925
  }
3926
3926
  if (isPOJO(first_1)) {
@@ -4668,9 +4668,9 @@ var require_argsOrArgArray = __commonJS({
4668
4668
  "../../node_modules/rxjs/dist/cjs/internal/util/argsOrArgArray.js"(exports) {
4669
4669
  Object.defineProperty(exports, "__esModule", { value: true });
4670
4670
  exports.argsOrArgArray = void 0;
4671
- var isArray2 = Array.isArray;
4671
+ var isArray = Array.isArray;
4672
4672
  function argsOrArgArray(args) {
4673
- return args.length === 1 && isArray2(args[0]) ? args[0] : args;
4673
+ return args.length === 1 && isArray(args[0]) ? args[0] : args;
4674
4674
  }
4675
4675
  exports.argsOrArgArray = argsOrArgArray;
4676
4676
  }
@@ -9371,15 +9371,15 @@ async function withTimeout(work, label) {
9371
9371
  clearTimeout(timer);
9372
9372
  }
9373
9373
  }
9374
- function boundedGenerate(client, prompt, maxTokens, temperature, options) {
9374
+ function boundedGenerate(client, prompt, maxTokens, temperature) {
9375
9375
  return withTimeout(
9376
- client.generateText(prompt, maxTokens, temperature, options),
9376
+ client.generateText(prompt, maxTokens, temperature),
9377
9377
  `${client.type}:${client.modelId}`
9378
9378
  );
9379
9379
  }
9380
- function boundedGenerateWithMetadata(client, prompt, maxTokens, temperature, options) {
9380
+ function boundedGenerateStructured(client, prompt, maxTokens, temperature, elementSchema) {
9381
9381
  return withTimeout(
9382
- client.generateTextWithMetadata(prompt, maxTokens, temperature, options),
9382
+ client.generateStructured(prompt, maxTokens, temperature, elementSchema),
9383
9383
  `${client.type}:${client.modelId}`
9384
9384
  );
9385
9385
  }
@@ -9713,32 +9713,57 @@ Example format:
9713
9713
  return prompt;
9714
9714
  }
9715
9715
  };
9716
- function parseJsonArray(response, motivation) {
9717
- let parsed;
9718
- try {
9719
- parsed = JSON.parse(response.trim());
9720
- } catch (error) {
9721
- console.error(`[MotivationParsers] Failed to parse AI ${motivation} response:`, error);
9722
- console.error("Raw response:", response);
9723
- throw error instanceof Error ? error : new Error(String(error));
9724
- }
9725
- if (!Array.isArray(parsed)) {
9726
- console.error(`[MotivationParsers] Expected a JSON array for ${motivation} detection, got ${typeof parsed}:`, response);
9727
- throw new Error(`Expected a JSON array for ${motivation} detection, got ${typeof parsed}`);
9728
- }
9729
- return parsed;
9730
- }
9716
+ var COMMENT_ELEMENT_SCHEMA = {
9717
+ type: "object",
9718
+ properties: {
9719
+ exact: { type: "string" },
9720
+ prefix: { type: "string" },
9721
+ suffix: { type: "string" },
9722
+ comment: { type: "string" }
9723
+ },
9724
+ required: ["exact", "comment"],
9725
+ additionalProperties: false
9726
+ };
9727
+ var HIGHLIGHT_ELEMENT_SCHEMA = {
9728
+ type: "object",
9729
+ properties: {
9730
+ exact: { type: "string" },
9731
+ prefix: { type: "string" },
9732
+ suffix: { type: "string" }
9733
+ },
9734
+ required: ["exact"],
9735
+ additionalProperties: false
9736
+ };
9737
+ var ASSESSMENT_ELEMENT_SCHEMA = {
9738
+ type: "object",
9739
+ properties: {
9740
+ exact: { type: "string" },
9741
+ prefix: { type: "string" },
9742
+ suffix: { type: "string" },
9743
+ assessment: { type: "string" }
9744
+ },
9745
+ required: ["exact", "assessment"],
9746
+ additionalProperties: false
9747
+ };
9748
+ var TAG_ELEMENT_SCHEMA = {
9749
+ type: "object",
9750
+ properties: {
9751
+ exact: { type: "string" },
9752
+ prefix: { type: "string" },
9753
+ suffix: { type: "string" }
9754
+ },
9755
+ required: ["exact"],
9756
+ additionalProperties: false
9757
+ };
9731
9758
  var MotivationParsers = class {
9732
9759
  /**
9733
- * Parse and validate AI response for comment detection
9760
+ * Validate and reconcile structured comment elements.
9734
9761
  *
9735
- * @param response - Raw AI response text (a JSON array)
9762
+ * @param parsed - Already-parsed elements from the structured surface
9736
9763
  * @param content - Original content to validate offsets against
9737
9764
  * @returns Array of validated comment matches
9738
- * @throws if the response is not a parseable JSON array
9739
9765
  */
9740
- static parseComments(response, content) {
9741
- const parsed = parseJsonArray(response, "comment");
9766
+ static parseComments(parsed, content) {
9742
9767
  const valid = parsed.filter(
9743
9768
  (c) => isObject(c) && isString(c.exact) && isString(c.comment) && c.comment.trim().length > 0
9744
9769
  );
@@ -9767,15 +9792,13 @@ var MotivationParsers = class {
9767
9792
  return validatedComments;
9768
9793
  }
9769
9794
  /**
9770
- * Parse and validate AI response for highlight detection
9795
+ * Validate and reconcile structured highlight elements.
9771
9796
  *
9772
- * @param response - Raw AI response text (a JSON array)
9797
+ * @param parsed - Already-parsed elements from the structured surface
9773
9798
  * @param content - Original content to validate offsets against
9774
9799
  * @returns Array of validated highlight matches
9775
- * @throws if the response is not a parseable JSON array
9776
9800
  */
9777
- static parseHighlights(response, content) {
9778
- const parsed = parseJsonArray(response, "highlight");
9801
+ static parseHighlights(parsed, content) {
9779
9802
  const highlights = parsed.filter(
9780
9803
  (h) => isObject(h) && isString(h.exact)
9781
9804
  );
@@ -9802,15 +9825,13 @@ var MotivationParsers = class {
9802
9825
  return validatedHighlights;
9803
9826
  }
9804
9827
  /**
9805
- * Parse and validate AI response for assessment detection
9828
+ * Validate and reconcile structured assessment elements.
9806
9829
  *
9807
- * @param response - Raw AI response text (a JSON array)
9830
+ * @param parsed - Already-parsed elements from the structured surface
9808
9831
  * @param content - Original content to validate offsets against
9809
9832
  * @returns Array of validated assessment matches
9810
- * @throws if the response is not a parseable JSON array
9811
9833
  */
9812
- static parseAssessments(response, content) {
9813
- const parsed = parseJsonArray(response, "assessment");
9834
+ static parseAssessments(parsed, content) {
9814
9835
  const assessments = parsed.filter(
9815
9836
  (a) => isObject(a) && isString(a.exact) && isString(a.assessment)
9816
9837
  );
@@ -9838,14 +9859,13 @@ var MotivationParsers = class {
9838
9859
  return validatedAssessments;
9839
9860
  }
9840
9861
  /**
9841
- * Parse the LLM's tag response into raw, pre-reconciliation tag inputs.
9862
+ * Validate structured tag elements into raw, pre-reconciliation tag inputs.
9842
9863
  * Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`
9843
9864
  * by anchoring `exact` against the source content.
9844
9865
  *
9845
- * @throws if the response is not a parseable JSON array
9866
+ * @param parsed - Already-parsed elements from the structured surface
9846
9867
  */
9847
- static parseTags(response) {
9848
- const parsed = parseJsonArray(response, "tag");
9868
+ static parseTags(parsed) {
9849
9869
  const valid = parsed.filter(
9850
9870
  (t) => isObject(t) && isString(t.exact) && t.exact.trim().length > 0
9851
9871
  );
@@ -9892,22 +9912,22 @@ function assertNotTruncated(response, motivation, chunk, totalChunks, outputBudg
9892
9912
  throw new Error(`${motivation} detection response truncated (max_tokens) on chunk ${chunk}/${totalChunks} despite the derived output budget of ${outputBudget} tokens \u2014 failing the job rather than under-reporting annotations.`);
9893
9913
  }
9894
9914
  }
9895
- async function detectInChunks(client, content, buildPrompt, temperature, motivation, parse, onChunk) {
9915
+ async function detectInChunks(client, content, buildPrompt, temperature, motivation, elementSchema, parse, onChunk) {
9896
9916
  const limits = await client.limits();
9897
9917
  const scaffoldTokens = estimateTokens(buildPrompt(""));
9898
9918
  const { chunking, outputBudget } = deriveDetectionBudget(limits, scaffoldTokens);
9899
9919
  const chunks = chunkText(content, chunking);
9900
9920
  const collected = [];
9901
9921
  for (let i = 0; i < chunks.length; i++) {
9902
- const response = await boundedGenerateWithMetadata(
9922
+ const response = await boundedGenerateStructured(
9903
9923
  client,
9904
9924
  buildPrompt(chunks[i]),
9905
9925
  outputBudget,
9906
9926
  temperature,
9907
- { format: "json" }
9927
+ elementSchema
9908
9928
  );
9909
9929
  assertNotTruncated(response, motivation, i + 1, chunks.length, outputBudget);
9910
- collected.push(...parse(response.text));
9930
+ collected.push(...parse(response.items));
9911
9931
  if (i < chunks.length - 1) {
9912
9932
  onChunk?.(i + 1, chunks.length);
9913
9933
  }
@@ -9930,7 +9950,8 @@ var AnnotationDetection = class {
9930
9950
  (chunk) => MotivationPrompts.buildCommentPrompt(chunk, instructions, tone, density, language, sourceLanguage),
9931
9951
  0.4,
9932
9952
  "comment",
9933
- (text) => MotivationParsers.parseComments(text, content),
9953
+ COMMENT_ELEMENT_SCHEMA,
9954
+ (items) => MotivationParsers.parseComments(items, content),
9934
9955
  onChunk
9935
9956
  );
9936
9957
  }
@@ -9948,7 +9969,8 @@ var AnnotationDetection = class {
9948
9969
  (chunk) => MotivationPrompts.buildHighlightPrompt(chunk, instructions, density, sourceLanguage),
9949
9970
  0.3,
9950
9971
  "highlight",
9951
- (text) => MotivationParsers.parseHighlights(text, content),
9972
+ HIGHLIGHT_ELEMENT_SCHEMA,
9973
+ (items) => MotivationParsers.parseHighlights(items, content),
9952
9974
  onChunk
9953
9975
  );
9954
9976
  }
@@ -9966,7 +9988,8 @@ var AnnotationDetection = class {
9966
9988
  (chunk) => MotivationPrompts.buildAssessmentPrompt(chunk, instructions, tone, density, language, sourceLanguage),
9967
9989
  0.3,
9968
9990
  "assessment",
9969
- (text) => MotivationParsers.parseAssessments(text, content),
9991
+ ASSESSMENT_ELEMENT_SCHEMA,
9992
+ (items) => MotivationParsers.parseAssessments(items, content),
9970
9993
  onChunk
9971
9994
  );
9972
9995
  }
@@ -10002,12 +10025,24 @@ var AnnotationDetection = class {
10002
10025
  ),
10003
10026
  0.2,
10004
10027
  "tag",
10005
- (text) => MotivationParsers.parseTags(text),
10028
+ TAG_ELEMENT_SCHEMA,
10029
+ (items) => MotivationParsers.parseTags(items),
10006
10030
  onChunk
10007
10031
  );
10008
10032
  return MotivationParsers.validateTagOffsets(parsedTags, content, category);
10009
10033
  }
10010
10034
  };
10035
+ var ENTITY_ELEMENT_SCHEMA = {
10036
+ type: "object",
10037
+ properties: {
10038
+ exact: { type: "string" },
10039
+ entityType: { type: "string" },
10040
+ prefix: { type: "string" },
10041
+ suffix: { type: "string" }
10042
+ },
10043
+ required: ["exact", "entityType"],
10044
+ additionalProperties: false
10045
+ };
10011
10046
  async function extractEntities(exact, entityTypes, client, includeDescriptiveReferences, logger2, sourceLanguage, onChunk) {
10012
10047
  const entityTypesDescription = entityTypes.map((et) => {
10013
10048
  if (typeof et === "string") {
@@ -10069,50 +10104,25 @@ Example output:
10069
10104
  });
10070
10105
  const collected = [];
10071
10106
  for (let i = 0; i < chunks.length; i++) {
10072
- const response = await boundedGenerateWithMetadata(
10107
+ const response = await boundedGenerateStructured(
10073
10108
  client,
10074
10109
  buildPrompt(chunks[i]),
10075
10110
  outputBudget,
10076
10111
  0.3,
10077
10112
  // Lower temperature for more consistent extraction
10078
- // Force grammar-constrained JSON output. Without this, Ollama models
10079
- // periodically emit malformed JSON (truncated brackets, mid-token
10080
- // breaks at higher token counts) which silently parse-fails into
10081
- // [] downstream. The prompt's schema (which keys, what types) still
10082
- // governs *what* the JSON contains; `format: 'json'` governs that
10083
- // it's syntactically valid.
10084
- { format: "json" }
10113
+ ENTITY_ELEMENT_SCHEMA
10085
10114
  );
10086
10115
  logger2.debug("Got entity extraction response", {
10087
10116
  chunk: i + 1,
10088
10117
  chunks: chunks.length,
10089
- responseLength: response.text.length
10118
+ items: response.items.length
10090
10119
  });
10091
10120
  if (response.stopReason === "max_tokens") {
10092
10121
  const errorMsg = `Entity extraction response truncated (max_tokens) on chunk ${i + 1}/${chunks.length} despite the derived output budget of ${outputBudget} tokens \u2014 failing the job rather than dropping annotations.`;
10093
- logger2.error(errorMsg, { responseLength: response.text.length });
10122
+ logger2.error(errorMsg, { items: response.items.length });
10094
10123
  throw new Error(errorMsg);
10095
10124
  }
10096
- let entities;
10097
- try {
10098
- entities = JSON.parse(response.text.trim());
10099
- } catch (error) {
10100
- logger2.error("Failed to parse entity extraction response", {
10101
- error: error instanceof Error ? error.message : String(error),
10102
- response: response.text.slice(0, 500)
10103
- });
10104
- throw new Error("Failed to parse entity extraction response", {
10105
- cause: error instanceof Error ? error : new Error(String(error))
10106
- });
10107
- }
10108
- if (!isArray(entities)) {
10109
- logger2.error("Failed to parse entity extraction response: expected a JSON array", {
10110
- response: response.text.slice(0, 500)
10111
- });
10112
- throw new Error("Failed to parse entity extraction response: expected a JSON array");
10113
- }
10114
- logger2.debug("Parsed entities from AI response", { chunk: i + 1, count: entities.length });
10115
- for (const e of entities) {
10125
+ for (const e of response.items) {
10116
10126
  if (isObject(e) && isString(e.exact) && isString(e.entityType)) {
10117
10127
  collected.push({
10118
10128
  exact: e.exact,