@semiont/jobs 0.5.8 → 0.5.9

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, softwareToAgent, baseUrl, getPrimaryMediaType, textExtractionOf, reconcileSelector, didToAgent, getLocaleEnglishName } from '@semiont/core';
1
+ import { createTomlConfigLoader, softwareToAgent, baseUrl, getPrimaryMediaType, textExtractionOf, reconcileSelector, didToAgent, getLocaleEnglishName, isArray, isObject, isString } from '@semiont/core';
2
2
  import { deriveStorageUri } from '@semiont/content';
3
3
  import { withSpan, SpanKind, recordJobOutcome } from '@semiont/observability';
4
4
  import { generateAnnotationId } from '@semiont/event-sourcing';
@@ -3765,9 +3765,9 @@ var require_mapOneOrManyArgs = __commonJS({
3765
3765
  Object.defineProperty(exports, "__esModule", { value: true });
3766
3766
  exports.mapOneOrManyArgs = void 0;
3767
3767
  var map_1 = require_map();
3768
- var isArray = Array.isArray;
3768
+ var isArray2 = Array.isArray;
3769
3769
  function callOrApply(fn, args) {
3770
- return isArray(args) ? fn.apply(void 0, __spreadArray([], __read(args))) : fn(args);
3770
+ return isArray2(args) ? fn.apply(void 0, __spreadArray([], __read(args))) : fn(args);
3771
3771
  }
3772
3772
  function mapOneOrManyArgs(fn) {
3773
3773
  return map_1.map(function(args) {
@@ -3912,14 +3912,14 @@ var require_argsArgArrayOrObject = __commonJS({
3912
3912
  "../../node_modules/rxjs/dist/cjs/internal/util/argsArgArrayOrObject.js"(exports) {
3913
3913
  Object.defineProperty(exports, "__esModule", { value: true });
3914
3914
  exports.argsArgArrayOrObject = void 0;
3915
- var isArray = Array.isArray;
3915
+ var isArray2 = Array.isArray;
3916
3916
  var getPrototypeOf = Object.getPrototypeOf;
3917
3917
  var objectProto = Object.prototype;
3918
3918
  var getKeys = Object.keys;
3919
3919
  function argsArgArrayOrObject(args) {
3920
3920
  if (args.length === 1) {
3921
3921
  var first_1 = args[0];
3922
- if (isArray(first_1)) {
3922
+ if (isArray2(first_1)) {
3923
3923
  return { args: first_1, keys: null };
3924
3924
  }
3925
3925
  if (isPOJO(first_1)) {
@@ -4667,9 +4667,9 @@ var require_argsOrArgArray = __commonJS({
4667
4667
  "../../node_modules/rxjs/dist/cjs/internal/util/argsOrArgArray.js"(exports) {
4668
4668
  Object.defineProperty(exports, "__esModule", { value: true });
4669
4669
  exports.argsOrArgArray = void 0;
4670
- var isArray = Array.isArray;
4670
+ var isArray2 = Array.isArray;
4671
4671
  function argsOrArgArray(args) {
4672
- return args.length === 1 && isArray(args[0]) ? args[0] : args;
4672
+ return args.length === 1 && isArray2(args[0]) ? args[0] : args;
4673
4673
  }
4674
4674
  exports.argsOrArgArray = argsOrArgArray;
4675
4675
  }
@@ -9587,197 +9587,144 @@ Example format:
9587
9587
  return prompt;
9588
9588
  }
9589
9589
  };
9590
- function extractObjectsFromArray(response) {
9591
- let cleaned = response.trim();
9592
- if (cleaned.startsWith("```")) {
9593
- cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
9594
- }
9590
+ function parseJsonArray(response, motivation) {
9591
+ let parsed;
9595
9592
  try {
9596
- const parsed = JSON.parse(cleaned);
9597
- return Array.isArray(parsed) ? parsed : [];
9598
- } catch {
9599
- }
9600
- const start = cleaned.indexOf("[");
9601
- if (start === -1) return [];
9602
- const endBracket = cleaned.lastIndexOf("]");
9603
- const end = endBracket > start ? endBracket : cleaned.length;
9604
- const inner = cleaned.slice(start + 1, end);
9605
- const objects = [];
9606
- let depth = 0;
9607
- let objStart = -1;
9608
- let inString = false;
9609
- let escape = false;
9610
- for (let i = 0; i < inner.length; i++) {
9611
- const ch = inner[i];
9612
- if (escape) {
9613
- escape = false;
9614
- continue;
9615
- }
9616
- if (ch === "\\") {
9617
- escape = true;
9618
- continue;
9619
- }
9620
- if (ch === '"') {
9621
- inString = !inString;
9622
- continue;
9623
- }
9624
- if (inString) continue;
9625
- if (ch === "{") {
9626
- if (depth === 0) objStart = i;
9627
- depth++;
9628
- } else if (ch === "}") {
9629
- depth--;
9630
- if (depth === 0 && objStart !== -1) {
9631
- try {
9632
- objects.push(JSON.parse(inner.slice(objStart, i + 1)));
9633
- } catch {
9634
- }
9635
- objStart = -1;
9636
- }
9637
- }
9593
+ parsed = JSON.parse(response.trim());
9594
+ } catch (error) {
9595
+ console.error(`[MotivationParsers] Failed to parse AI ${motivation} response:`, error);
9596
+ console.error("Raw response:", response);
9597
+ throw error instanceof Error ? error : new Error(String(error));
9638
9598
  }
9639
- return objects;
9599
+ if (!Array.isArray(parsed)) {
9600
+ console.error(`[MotivationParsers] Expected a JSON array for ${motivation} detection, got ${typeof parsed}:`, response);
9601
+ throw new Error(`Expected a JSON array for ${motivation} detection, got ${typeof parsed}`);
9602
+ }
9603
+ return parsed;
9640
9604
  }
9641
9605
  var MotivationParsers = class {
9642
9606
  /**
9643
9607
  * Parse and validate AI response for comment detection
9644
9608
  *
9645
- * @param response - Raw AI response string (may include markdown code fences)
9609
+ * @param response - Raw AI response text (a JSON array)
9646
9610
  * @param content - Original content to validate offsets against
9647
9611
  * @returns Array of validated comment matches
9612
+ * @throws if the response is not a parseable JSON array
9648
9613
  */
9649
9614
  static parseComments(response, content) {
9650
- try {
9651
- const parsed = extractObjectsFromArray(response);
9652
- const valid = parsed.filter(
9653
- (c) => !!c && typeof c === "object" && typeof c.exact === "string" && typeof c.comment === "string" && c.comment.trim().length > 0
9654
- );
9655
- console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);
9656
- const validatedComments = [];
9657
- for (const comment of valid) {
9658
- const reconciled = reconcileSelector(content, {
9659
- exact: comment.exact,
9660
- ...typeof comment.prefix === "string" ? { prefix: comment.prefix } : {},
9661
- ...typeof comment.suffix === "string" ? { suffix: comment.suffix } : {}
9662
- });
9663
- if (!reconciled) {
9664
- console.warn(`[MotivationParsers] Dropped hallucinated comment "${comment.exact}"`);
9665
- continue;
9666
- }
9667
- logAnchorMethod("comment", comment.exact, reconciled.anchorMethod);
9668
- validatedComments.push({
9669
- comment: comment.comment,
9670
- exact: reconciled.exact,
9671
- start: reconciled.start,
9672
- end: reconciled.end,
9673
- ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9674
- ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9675
- });
9615
+ const parsed = parseJsonArray(response, "comment");
9616
+ const valid = parsed.filter(
9617
+ (c) => isObject(c) && isString(c.exact) && isString(c.comment) && c.comment.trim().length > 0
9618
+ );
9619
+ console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);
9620
+ const validatedComments = [];
9621
+ for (const comment of valid) {
9622
+ const reconciled = reconcileSelector(content, {
9623
+ exact: comment.exact,
9624
+ ...typeof comment.prefix === "string" ? { prefix: comment.prefix } : {},
9625
+ ...typeof comment.suffix === "string" ? { suffix: comment.suffix } : {}
9626
+ });
9627
+ if (!reconciled) {
9628
+ console.warn(`[MotivationParsers] Dropped hallucinated comment "${comment.exact}"`);
9629
+ continue;
9676
9630
  }
9677
- return validatedComments;
9678
- } catch (error) {
9679
- console.error("[MotivationParsers] Failed to parse AI comment response:", error);
9680
- return [];
9631
+ logAnchorMethod("comment", comment.exact, reconciled.anchorMethod);
9632
+ validatedComments.push({
9633
+ comment: comment.comment,
9634
+ exact: reconciled.exact,
9635
+ start: reconciled.start,
9636
+ end: reconciled.end,
9637
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9638
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9639
+ });
9681
9640
  }
9641
+ return validatedComments;
9682
9642
  }
9683
9643
  /**
9684
9644
  * Parse and validate AI response for highlight detection
9685
9645
  *
9686
- * @param response - Raw AI response string (may include markdown code fences)
9646
+ * @param response - Raw AI response text (a JSON array)
9687
9647
  * @param content - Original content to validate offsets against
9688
9648
  * @returns Array of validated highlight matches
9649
+ * @throws if the response is not a parseable JSON array
9689
9650
  */
9690
9651
  static parseHighlights(response, content) {
9691
- try {
9692
- const parsed = extractObjectsFromArray(response);
9693
- const highlights = parsed.filter(
9694
- (h) => !!h && typeof h === "object" && typeof h.exact === "string"
9695
- );
9696
- const validatedHighlights = [];
9697
- for (const highlight of highlights) {
9698
- const reconciled = reconcileSelector(content, {
9699
- exact: highlight.exact,
9700
- ...typeof highlight.prefix === "string" ? { prefix: highlight.prefix } : {},
9701
- ...typeof highlight.suffix === "string" ? { suffix: highlight.suffix } : {}
9702
- });
9703
- if (!reconciled) {
9704
- console.warn(`[MotivationParsers] Dropped hallucinated highlight "${highlight.exact}"`);
9705
- continue;
9706
- }
9707
- logAnchorMethod("highlight", highlight.exact, reconciled.anchorMethod);
9708
- validatedHighlights.push({
9709
- exact: reconciled.exact,
9710
- start: reconciled.start,
9711
- end: reconciled.end,
9712
- ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9713
- ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9714
- });
9652
+ const parsed = parseJsonArray(response, "highlight");
9653
+ const highlights = parsed.filter(
9654
+ (h) => isObject(h) && isString(h.exact)
9655
+ );
9656
+ const validatedHighlights = [];
9657
+ for (const highlight of highlights) {
9658
+ const reconciled = reconcileSelector(content, {
9659
+ exact: highlight.exact,
9660
+ ...typeof highlight.prefix === "string" ? { prefix: highlight.prefix } : {},
9661
+ ...typeof highlight.suffix === "string" ? { suffix: highlight.suffix } : {}
9662
+ });
9663
+ if (!reconciled) {
9664
+ console.warn(`[MotivationParsers] Dropped hallucinated highlight "${highlight.exact}"`);
9665
+ continue;
9715
9666
  }
9716
- return validatedHighlights;
9717
- } catch (error) {
9718
- console.error("[MotivationParsers] Failed to parse AI highlight response:", error);
9719
- console.error("Raw response:", response);
9720
- return [];
9667
+ logAnchorMethod("highlight", highlight.exact, reconciled.anchorMethod);
9668
+ validatedHighlights.push({
9669
+ exact: reconciled.exact,
9670
+ start: reconciled.start,
9671
+ end: reconciled.end,
9672
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9673
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9674
+ });
9721
9675
  }
9676
+ return validatedHighlights;
9722
9677
  }
9723
9678
  /**
9724
9679
  * Parse and validate AI response for assessment detection
9725
9680
  *
9726
- * @param response - Raw AI response string (may include markdown code fences)
9681
+ * @param response - Raw AI response text (a JSON array)
9727
9682
  * @param content - Original content to validate offsets against
9728
9683
  * @returns Array of validated assessment matches
9684
+ * @throws if the response is not a parseable JSON array
9729
9685
  */
9730
9686
  static parseAssessments(response, content) {
9731
- try {
9732
- const parsed = extractObjectsFromArray(response);
9733
- const assessments = parsed.filter(
9734
- (a) => !!a && typeof a === "object" && typeof a.exact === "string" && typeof a.assessment === "string"
9735
- );
9736
- const validatedAssessments = [];
9737
- for (const assessment of assessments) {
9738
- const reconciled = reconcileSelector(content, {
9739
- exact: assessment.exact,
9740
- ...typeof assessment.prefix === "string" ? { prefix: assessment.prefix } : {},
9741
- ...typeof assessment.suffix === "string" ? { suffix: assessment.suffix } : {}
9742
- });
9743
- if (!reconciled) {
9744
- console.warn(`[MotivationParsers] Dropped hallucinated assessment "${assessment.exact}"`);
9745
- continue;
9746
- }
9747
- logAnchorMethod("assessment", assessment.exact, reconciled.anchorMethod);
9748
- validatedAssessments.push({
9749
- assessment: assessment.assessment,
9750
- exact: reconciled.exact,
9751
- start: reconciled.start,
9752
- end: reconciled.end,
9753
- ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9754
- ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9755
- });
9687
+ const parsed = parseJsonArray(response, "assessment");
9688
+ const assessments = parsed.filter(
9689
+ (a) => isObject(a) && isString(a.exact) && isString(a.assessment)
9690
+ );
9691
+ const validatedAssessments = [];
9692
+ for (const assessment of assessments) {
9693
+ const reconciled = reconcileSelector(content, {
9694
+ exact: assessment.exact,
9695
+ ...typeof assessment.prefix === "string" ? { prefix: assessment.prefix } : {},
9696
+ ...typeof assessment.suffix === "string" ? { suffix: assessment.suffix } : {}
9697
+ });
9698
+ if (!reconciled) {
9699
+ console.warn(`[MotivationParsers] Dropped hallucinated assessment "${assessment.exact}"`);
9700
+ continue;
9756
9701
  }
9757
- return validatedAssessments;
9758
- } catch (error) {
9759
- console.error("[MotivationParsers] Failed to parse AI assessment response:", error);
9760
- console.error("Raw response:", response);
9761
- return [];
9702
+ logAnchorMethod("assessment", assessment.exact, reconciled.anchorMethod);
9703
+ validatedAssessments.push({
9704
+ assessment: assessment.assessment,
9705
+ exact: reconciled.exact,
9706
+ start: reconciled.start,
9707
+ end: reconciled.end,
9708
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9709
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9710
+ });
9762
9711
  }
9712
+ return validatedAssessments;
9763
9713
  }
9764
9714
  /**
9765
9715
  * Parse the LLM's tag response into raw, pre-reconciliation tag inputs.
9766
9716
  * Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`
9767
9717
  * by anchoring `exact` against the source content.
9718
+ *
9719
+ * @throws if the response is not a parseable JSON array
9768
9720
  */
9769
9721
  static parseTags(response) {
9770
- try {
9771
- const parsed = extractObjectsFromArray(response);
9772
- const valid = parsed.filter(
9773
- (t) => !!t && typeof t === "object" && typeof t.exact === "string" && t.exact.trim().length > 0
9774
- );
9775
- console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);
9776
- return valid;
9777
- } catch (error) {
9778
- console.error("[MotivationParsers] Failed to parse AI tag response:", error);
9779
- return [];
9780
- }
9722
+ const parsed = parseJsonArray(response, "tag");
9723
+ const valid = parsed.filter(
9724
+ (t) => isObject(t) && isString(t.exact) && t.exact.trim().length > 0
9725
+ );
9726
+ console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);
9727
+ return valid;
9781
9728
  }
9782
9729
  /**
9783
9730
  * Anchor raw tag inputs against source content and add category.
@@ -9814,6 +9761,11 @@ function logAnchorMethod(motivation, exact, anchorMethod) {
9814
9761
  }
9815
9762
 
9816
9763
  // src/workers/annotation-detection.ts
9764
+ function assertNotTruncated(response, motivation) {
9765
+ if (response.stopReason === "max_tokens") {
9766
+ throw new Error(`${motivation} detection response truncated (max_tokens) \u2014 increase max_tokens or reduce resource size; failing the job rather than under-reporting annotations.`);
9767
+ }
9768
+ }
9817
9769
  var AnnotationDetection = class {
9818
9770
  /**
9819
9771
  * Detect comments in content.
@@ -9825,8 +9777,9 @@ var AnnotationDetection = class {
9825
9777
  */
9826
9778
  static async detectComments(content, client, instructions, tone, density, language, sourceLanguage) {
9827
9779
  const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density, language, sourceLanguage);
9828
- const response = await client.generateText(prompt, 3e3, 0.4, { format: "json" });
9829
- return MotivationParsers.parseComments(response, content);
9780
+ const response = await client.generateTextWithMetadata(prompt, 3e3, 0.4, { format: "json" });
9781
+ assertNotTruncated(response, "comment");
9782
+ return MotivationParsers.parseComments(response.text, content);
9830
9783
  }
9831
9784
  /**
9832
9785
  * Detect highlights in content.
@@ -9837,8 +9790,9 @@ var AnnotationDetection = class {
9837
9790
  */
9838
9791
  static async detectHighlights(content, client, instructions, density, sourceLanguage) {
9839
9792
  const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density, sourceLanguage);
9840
- const response = await client.generateText(prompt, 2e3, 0.3, { format: "json" });
9841
- return MotivationParsers.parseHighlights(response, content);
9793
+ const response = await client.generateTextWithMetadata(prompt, 2e3, 0.3, { format: "json" });
9794
+ assertNotTruncated(response, "highlight");
9795
+ return MotivationParsers.parseHighlights(response.text, content);
9842
9796
  }
9843
9797
  /**
9844
9798
  * Detect assessments in content.
@@ -9849,8 +9803,9 @@ var AnnotationDetection = class {
9849
9803
  */
9850
9804
  static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage) {
9851
9805
  const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density, language, sourceLanguage);
9852
- const response = await client.generateText(prompt, 3e3, 0.3, { format: "json" });
9853
- return MotivationParsers.parseAssessments(response, content);
9806
+ const response = await client.generateTextWithMetadata(prompt, 3e3, 0.3, { format: "json" });
9807
+ assertNotTruncated(response, "assessment");
9808
+ return MotivationParsers.parseAssessments(response.text, content);
9854
9809
  }
9855
9810
  /**
9856
9811
  * Detect tags in content for a specific category.
@@ -9879,8 +9834,9 @@ var AnnotationDetection = class {
9879
9834
  categoryInfo.examples,
9880
9835
  sourceLanguage
9881
9836
  );
9882
- const response = await client.generateText(prompt, 4e3, 0.2, { format: "json" });
9883
- const parsedTags = MotivationParsers.parseTags(response);
9837
+ const response = await client.generateTextWithMetadata(prompt, 4e3, 0.2, { format: "json" });
9838
+ assertNotTruncated(response, "tag");
9839
+ const parsedTags = MotivationParsers.parseTags(response.text);
9884
9840
  return MotivationParsers.validateTagOffsets(parsedTags, content, category);
9885
9841
  }
9886
9842
  };
@@ -9949,36 +9905,42 @@ Example output:
9949
9905
  { format: "json" }
9950
9906
  );
9951
9907
  logger2.debug("Got entity extraction response", { responseLength: response.text.length });
9908
+ if (response.stopReason === "max_tokens") {
9909
+ const errorMsg = "Entity extraction response truncated (max_tokens) \u2014 increase max_tokens or reduce resource size; failing the job rather than dropping annotations.";
9910
+ logger2.error(errorMsg, { responseLength: response.text.length });
9911
+ throw new Error(errorMsg);
9912
+ }
9913
+ let entities;
9952
9914
  try {
9953
- let jsonStr = response.text.trim();
9954
- if (jsonStr.startsWith("```")) {
9955
- jsonStr = jsonStr.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "");
9956
- }
9957
- const entities = JSON.parse(jsonStr);
9958
- logger2.debug("Parsed entities from AI response", { count: entities.length });
9959
- if (response.stopReason === "max_tokens") {
9960
- const errorMsg = `AI response truncated: Found ${entities.length} entities but response hit max_tokens limit. Increase max_tokens or reduce resource size.`;
9961
- logger2.error(errorMsg);
9962
- throw new Error(errorMsg);
9963
- }
9964
- return entities.filter((e) => {
9965
- const ok = e && typeof e === "object" && typeof e.exact === "string" && typeof e.entityType === "string";
9966
- if (!ok) {
9967
- logger2.debug("Dropped malformed LLM entity", { entity: e });
9968
- }
9969
- return ok;
9970
- }).map((entity) => ({
9971
- exact: entity.exact,
9972
- entityType: entity.entityType,
9973
- ...typeof entity.prefix === "string" ? { prefix: entity.prefix } : {},
9974
- ...typeof entity.suffix === "string" ? { suffix: entity.suffix } : {}
9975
- }));
9915
+ entities = JSON.parse(response.text.trim());
9976
9916
  } catch (error) {
9977
9917
  logger2.error("Failed to parse entity extraction response", {
9978
- error: error instanceof Error ? error.message : String(error)
9918
+ error: error instanceof Error ? error.message : String(error),
9919
+ response: response.text.slice(0, 500)
9920
+ });
9921
+ throw new Error("Failed to parse entity extraction response", {
9922
+ cause: error instanceof Error ? error : new Error(String(error))
9979
9923
  });
9980
- return [];
9981
9924
  }
9925
+ if (!isArray(entities)) {
9926
+ logger2.error("Failed to parse entity extraction response: expected a JSON array", {
9927
+ response: response.text.slice(0, 500)
9928
+ });
9929
+ throw new Error("Failed to parse entity extraction response: expected a JSON array");
9930
+ }
9931
+ logger2.debug("Parsed entities from AI response", { count: entities.length });
9932
+ return entities.filter((e) => {
9933
+ const ok = isObject(e) && isString(e.exact) && isString(e.entityType);
9934
+ if (!ok) {
9935
+ logger2.debug("Dropped malformed LLM entity", { entity: e });
9936
+ }
9937
+ return ok;
9938
+ }).map((entity) => ({
9939
+ exact: entity.exact,
9940
+ entityType: entity.entityType,
9941
+ ...isString(entity.prefix) ? { prefix: entity.prefix } : {},
9942
+ ...isString(entity.suffix) ? { suffix: entity.suffix } : {}
9943
+ }));
9982
9944
  }
9983
9945
  function getLanguageName(locale) {
9984
9946
  return getLocaleEnglishName(locale) || locale;