@semiont/jobs 0.5.8 → 0.5.10

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, busRequest, getPrimaryMediaType, textExtractionOf, assembleAnnotation, reconcileSelector, didToAgent, getLocaleEnglishName, isArray, isObject, isString, deriveViews } 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';
@@ -326,16 +326,16 @@ var require_timeoutProvider = __commonJS({
326
326
  Object.defineProperty(exports, "__esModule", { value: true });
327
327
  exports.timeoutProvider = void 0;
328
328
  exports.timeoutProvider = {
329
- setTimeout: function(handler, timeout2) {
329
+ setTimeout: function(handler, timeout) {
330
330
  var args = [];
331
331
  for (var _i = 2; _i < arguments.length; _i++) {
332
332
  args[_i - 2] = arguments[_i];
333
333
  }
334
334
  var delegate = exports.timeoutProvider.delegate;
335
335
  if (delegate === null || delegate === void 0 ? void 0 : delegate.setTimeout) {
336
- return delegate.setTimeout.apply(delegate, __spreadArray([handler, timeout2], __read(args)));
336
+ return delegate.setTimeout.apply(delegate, __spreadArray([handler, timeout], __read(args)));
337
337
  }
338
- return setTimeout.apply(void 0, __spreadArray([handler, timeout2], __read(args)));
338
+ return setTimeout.apply(void 0, __spreadArray([handler, timeout], __read(args)));
339
339
  },
340
340
  clearTimeout: function(handle) {
341
341
  var delegate = exports.timeoutProvider.delegate;
@@ -1659,16 +1659,16 @@ var require_intervalProvider = __commonJS({
1659
1659
  Object.defineProperty(exports, "__esModule", { value: true });
1660
1660
  exports.intervalProvider = void 0;
1661
1661
  exports.intervalProvider = {
1662
- setInterval: function(handler, timeout2) {
1662
+ setInterval: function(handler, timeout) {
1663
1663
  var args = [];
1664
1664
  for (var _i = 2; _i < arguments.length; _i++) {
1665
1665
  args[_i - 2] = arguments[_i];
1666
1666
  }
1667
1667
  var delegate = exports.intervalProvider.delegate;
1668
1668
  if (delegate === null || delegate === void 0 ? void 0 : delegate.setInterval) {
1669
- return delegate.setInterval.apply(delegate, __spreadArray([handler, timeout2], __read(args)));
1669
+ return delegate.setInterval.apply(delegate, __spreadArray([handler, timeout], __read(args)));
1670
1670
  }
1671
- return setInterval.apply(void 0, __spreadArray([handler, timeout2], __read(args)));
1671
+ return setInterval.apply(void 0, __spreadArray([handler, timeout], __read(args)));
1672
1672
  },
1673
1673
  clearInterval: function(handle) {
1674
1674
  var delegate = exports.intervalProvider.delegate;
@@ -3566,7 +3566,7 @@ var require_firstValueFrom = __commonJS({
3566
3566
  exports.firstValueFrom = void 0;
3567
3567
  var EmptyError_1 = require_EmptyError();
3568
3568
  var Subscriber_1 = require_Subscriber();
3569
- function firstValueFrom2(source, config) {
3569
+ function firstValueFrom(source, config) {
3570
3570
  var hasConfig = typeof config === "object";
3571
3571
  return new Promise(function(resolve, reject) {
3572
3572
  var subscriber = new Subscriber_1.SafeSubscriber({
@@ -3586,7 +3586,7 @@ var require_firstValueFrom = __commonJS({
3586
3586
  source.subscribe(subscriber);
3587
3587
  });
3588
3588
  }
3589
- exports.firstValueFrom = firstValueFrom2;
3589
+ exports.firstValueFrom = firstValueFrom;
3590
3590
  }
3591
3591
  });
3592
3592
 
@@ -3673,7 +3673,7 @@ var require_timeout = __commonJS({
3673
3673
  this.info = info;
3674
3674
  };
3675
3675
  });
3676
- function timeout2(config, schedulerArg) {
3676
+ function timeout(config, schedulerArg) {
3677
3677
  var _a = isDate_1.isValidDate(config) ? { first: config } : typeof config === "number" ? { each: config } : config, first = _a.first, each = _a.each, _b = _a.with, _with = _b === void 0 ? timeoutErrorFactory : _b, _c = _a.scheduler, scheduler = _c === void 0 ? schedulerArg !== null && schedulerArg !== void 0 ? schedulerArg : async_1.asyncScheduler : _c, _d = _a.meta, meta = _d === void 0 ? null : _d;
3678
3678
  if (first == null && each == null) {
3679
3679
  throw new TypeError("No timeout provided.");
@@ -3711,7 +3711,7 @@ var require_timeout = __commonJS({
3711
3711
  !seen && startTimer(first != null ? typeof first === "number" ? first : +first - scheduler.now() : each);
3712
3712
  });
3713
3713
  }
3714
- exports.timeout = timeout2;
3714
+ exports.timeout = timeout;
3715
3715
  function timeoutErrorFactory(info) {
3716
3716
  throw new exports.TimeoutError(info);
3717
3717
  }
@@ -3725,7 +3725,7 @@ var require_map = __commonJS({
3725
3725
  exports.map = void 0;
3726
3726
  var lift_1 = require_lift();
3727
3727
  var OperatorSubscriber_1 = require_OperatorSubscriber();
3728
- function map2(project, thisArg) {
3728
+ function map(project, thisArg) {
3729
3729
  return lift_1.operate(function(source, subscriber) {
3730
3730
  var index = 0;
3731
3731
  source.subscribe(OperatorSubscriber_1.createOperatorSubscriber(subscriber, function(value) {
@@ -3733,7 +3733,7 @@ var require_map = __commonJS({
3733
3733
  }));
3734
3734
  });
3735
3735
  }
3736
- exports.map = map2;
3736
+ exports.map = map;
3737
3737
  }
3738
3738
  });
3739
3739
 
@@ -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)) {
@@ -4633,7 +4633,7 @@ var require_merge = __commonJS({
4633
4633
  var empty_1 = require_empty();
4634
4634
  var args_1 = require_args();
4635
4635
  var from_1 = require_from();
4636
- function merge2() {
4636
+ function merge() {
4637
4637
  var args = [];
4638
4638
  for (var _i = 0; _i < arguments.length; _i++) {
4639
4639
  args[_i] = arguments[_i];
@@ -4643,7 +4643,7 @@ var require_merge = __commonJS({
4643
4643
  var sources = args;
4644
4644
  return !sources.length ? empty_1.EMPTY : sources.length === 1 ? innerFrom_1.innerFrom(sources[0]) : mergeAll_1.mergeAll(concurrent)(from_1.from(sources, scheduler));
4645
4645
  }
4646
- exports.merge = merge2;
4646
+ exports.merge = merge;
4647
4647
  }
4648
4648
  });
4649
4649
 
@@ -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
  }
@@ -4750,7 +4750,7 @@ var require_filter = __commonJS({
4750
4750
  exports.filter = void 0;
4751
4751
  var lift_1 = require_lift();
4752
4752
  var OperatorSubscriber_1 = require_OperatorSubscriber();
4753
- function filter2(predicate, thisArg) {
4753
+ function filter(predicate, thisArg) {
4754
4754
  return lift_1.operate(function(source, subscriber) {
4755
4755
  var index = 0;
4756
4756
  source.subscribe(OperatorSubscriber_1.createOperatorSubscriber(subscriber, function(value) {
@@ -4758,7 +4758,7 @@ var require_filter = __commonJS({
4758
4758
  }));
4759
4759
  });
4760
4760
  }
4761
- exports.filter = filter2;
4761
+ exports.filter = filter;
4762
4762
  }
4763
4763
  });
4764
4764
 
@@ -5885,7 +5885,7 @@ var require_take = __commonJS({
5885
5885
  var empty_1 = require_empty();
5886
5886
  var lift_1 = require_lift();
5887
5887
  var OperatorSubscriber_1 = require_OperatorSubscriber();
5888
- function take2(count) {
5888
+ function take(count) {
5889
5889
  return count <= 0 ? function() {
5890
5890
  return empty_1.EMPTY;
5891
5891
  } : lift_1.operate(function(source, subscriber) {
@@ -5900,7 +5900,7 @@ var require_take = __commonJS({
5900
5900
  }));
5901
5901
  });
5902
5902
  }
5903
- exports.take = take2;
5903
+ exports.take = take;
5904
5904
  }
5905
5905
  });
5906
5906
 
@@ -6692,7 +6692,7 @@ var require_merge2 = __commonJS({
6692
6692
  var mergeAll_1 = require_mergeAll();
6693
6693
  var args_1 = require_args();
6694
6694
  var from_1 = require_from();
6695
- function merge2() {
6695
+ function merge() {
6696
6696
  var args = [];
6697
6697
  for (var _i = 0; _i < arguments.length; _i++) {
6698
6698
  args[_i] = arguments[_i];
@@ -6703,7 +6703,7 @@ var require_merge2 = __commonJS({
6703
6703
  mergeAll_1.mergeAll(concurrent)(from_1.from(__spreadArray([source], __read(args)), scheduler)).subscribe(subscriber);
6704
6704
  });
6705
6705
  }
6706
- exports.merge = merge2;
6706
+ exports.merge = merge;
6707
6707
  }
6708
6708
  });
6709
6709
 
@@ -9207,9 +9207,19 @@ var require_cjs = __commonJS({
9207
9207
 
9208
9208
  // src/job-claim-adapter.ts
9209
9209
  var import_rxjs = __toESM(require_cjs());
9210
- var import_rxjs2 = __toESM(require_cjs());
9210
+ function workerBusAsPrimitive(bus) {
9211
+ return {
9212
+ emit(channel, payload) {
9213
+ return bus.emit(channel, payload);
9214
+ },
9215
+ stream(channel) {
9216
+ return bus.on$(channel);
9217
+ }
9218
+ };
9219
+ }
9211
9220
  function createJobClaimAdapter(options) {
9212
9221
  const { bus, jobTypes } = options;
9222
+ const requestBus = workerBusAsPrimitive(bus);
9213
9223
  const activeJob$ = new import_rxjs.BehaviorSubject(null);
9214
9224
  const isProcessing$ = new import_rxjs.BehaviorSubject(false);
9215
9225
  const jobsCompleted$ = new import_rxjs.BehaviorSubject(0);
@@ -9218,22 +9228,7 @@ function createJobClaimAdapter(options) {
9218
9228
  let started = false;
9219
9229
  const claimJob = async (assignment) => {
9220
9230
  try {
9221
- const correlationId = crypto.randomUUID();
9222
- const result$ = (0, import_rxjs2.merge)(
9223
- bus.on$("job:claimed").pipe(
9224
- (0, import_rxjs2.filter)((e) => e.correlationId === correlationId),
9225
- (0, import_rxjs2.map)((e) => ({ ok: true, response: e.response }))
9226
- ),
9227
- bus.on$("job:claim-failed").pipe(
9228
- (0, import_rxjs2.filter)((e) => e.correlationId === correlationId),
9229
- (0, import_rxjs2.map)(() => ({ ok: false }))
9230
- )
9231
- ).pipe((0, import_rxjs2.take)(1), (0, import_rxjs2.timeout)(1e4));
9232
- const resultPromise = (0, import_rxjs2.firstValueFrom)(result$);
9233
- await bus.emit("job:claim", { correlationId, jobId: assignment.jobId });
9234
- const result = await resultPromise;
9235
- if (!result.ok) return null;
9236
- const job = result.response;
9231
+ const job = await busRequest(requestBus, "job:claim", { jobId: assignment.jobId }, 1e4);
9237
9232
  return {
9238
9233
  jobId: assignment.jobId,
9239
9234
  type: assignment.type,
@@ -9587,197 +9582,144 @@ Example format:
9587
9582
  return prompt;
9588
9583
  }
9589
9584
  };
9590
- function extractObjectsFromArray(response) {
9591
- let cleaned = response.trim();
9592
- if (cleaned.startsWith("```")) {
9593
- cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
9594
- }
9585
+ function parseJsonArray(response, motivation) {
9586
+ let parsed;
9595
9587
  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
- }
9588
+ parsed = JSON.parse(response.trim());
9589
+ } catch (error) {
9590
+ console.error(`[MotivationParsers] Failed to parse AI ${motivation} response:`, error);
9591
+ console.error("Raw response:", response);
9592
+ throw error instanceof Error ? error : new Error(String(error));
9593
+ }
9594
+ if (!Array.isArray(parsed)) {
9595
+ console.error(`[MotivationParsers] Expected a JSON array for ${motivation} detection, got ${typeof parsed}:`, response);
9596
+ throw new Error(`Expected a JSON array for ${motivation} detection, got ${typeof parsed}`);
9638
9597
  }
9639
- return objects;
9598
+ return parsed;
9640
9599
  }
9641
9600
  var MotivationParsers = class {
9642
9601
  /**
9643
9602
  * Parse and validate AI response for comment detection
9644
9603
  *
9645
- * @param response - Raw AI response string (may include markdown code fences)
9604
+ * @param response - Raw AI response text (a JSON array)
9646
9605
  * @param content - Original content to validate offsets against
9647
9606
  * @returns Array of validated comment matches
9607
+ * @throws if the response is not a parseable JSON array
9648
9608
  */
9649
9609
  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
- });
9610
+ const parsed = parseJsonArray(response, "comment");
9611
+ const valid = parsed.filter(
9612
+ (c) => isObject(c) && isString(c.exact) && isString(c.comment) && c.comment.trim().length > 0
9613
+ );
9614
+ console.log(`[MotivationParsers] Parsed ${valid.length} valid comments from ${parsed.length} total`);
9615
+ const validatedComments = [];
9616
+ for (const comment of valid) {
9617
+ const reconciled = reconcileSelector(content, {
9618
+ exact: comment.exact,
9619
+ ...typeof comment.prefix === "string" ? { prefix: comment.prefix } : {},
9620
+ ...typeof comment.suffix === "string" ? { suffix: comment.suffix } : {}
9621
+ });
9622
+ if (!reconciled) {
9623
+ console.warn(`[MotivationParsers] Dropped hallucinated comment "${comment.exact}"`);
9624
+ continue;
9676
9625
  }
9677
- return validatedComments;
9678
- } catch (error) {
9679
- console.error("[MotivationParsers] Failed to parse AI comment response:", error);
9680
- return [];
9626
+ logAnchorMethod("comment", comment.exact, reconciled.anchorMethod);
9627
+ validatedComments.push({
9628
+ comment: comment.comment,
9629
+ exact: reconciled.exact,
9630
+ start: reconciled.start,
9631
+ end: reconciled.end,
9632
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9633
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9634
+ });
9681
9635
  }
9636
+ return validatedComments;
9682
9637
  }
9683
9638
  /**
9684
9639
  * Parse and validate AI response for highlight detection
9685
9640
  *
9686
- * @param response - Raw AI response string (may include markdown code fences)
9641
+ * @param response - Raw AI response text (a JSON array)
9687
9642
  * @param content - Original content to validate offsets against
9688
9643
  * @returns Array of validated highlight matches
9644
+ * @throws if the response is not a parseable JSON array
9689
9645
  */
9690
9646
  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
- });
9647
+ const parsed = parseJsonArray(response, "highlight");
9648
+ const highlights = parsed.filter(
9649
+ (h) => isObject(h) && isString(h.exact)
9650
+ );
9651
+ const validatedHighlights = [];
9652
+ for (const highlight of highlights) {
9653
+ const reconciled = reconcileSelector(content, {
9654
+ exact: highlight.exact,
9655
+ ...typeof highlight.prefix === "string" ? { prefix: highlight.prefix } : {},
9656
+ ...typeof highlight.suffix === "string" ? { suffix: highlight.suffix } : {}
9657
+ });
9658
+ if (!reconciled) {
9659
+ console.warn(`[MotivationParsers] Dropped hallucinated highlight "${highlight.exact}"`);
9660
+ continue;
9715
9661
  }
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 [];
9662
+ logAnchorMethod("highlight", highlight.exact, reconciled.anchorMethod);
9663
+ validatedHighlights.push({
9664
+ exact: reconciled.exact,
9665
+ start: reconciled.start,
9666
+ end: reconciled.end,
9667
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9668
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9669
+ });
9721
9670
  }
9671
+ return validatedHighlights;
9722
9672
  }
9723
9673
  /**
9724
9674
  * Parse and validate AI response for assessment detection
9725
9675
  *
9726
- * @param response - Raw AI response string (may include markdown code fences)
9676
+ * @param response - Raw AI response text (a JSON array)
9727
9677
  * @param content - Original content to validate offsets against
9728
9678
  * @returns Array of validated assessment matches
9679
+ * @throws if the response is not a parseable JSON array
9729
9680
  */
9730
9681
  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
- });
9682
+ const parsed = parseJsonArray(response, "assessment");
9683
+ const assessments = parsed.filter(
9684
+ (a) => isObject(a) && isString(a.exact) && isString(a.assessment)
9685
+ );
9686
+ const validatedAssessments = [];
9687
+ for (const assessment of assessments) {
9688
+ const reconciled = reconcileSelector(content, {
9689
+ exact: assessment.exact,
9690
+ ...typeof assessment.prefix === "string" ? { prefix: assessment.prefix } : {},
9691
+ ...typeof assessment.suffix === "string" ? { suffix: assessment.suffix } : {}
9692
+ });
9693
+ if (!reconciled) {
9694
+ console.warn(`[MotivationParsers] Dropped hallucinated assessment "${assessment.exact}"`);
9695
+ continue;
9756
9696
  }
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 [];
9697
+ logAnchorMethod("assessment", assessment.exact, reconciled.anchorMethod);
9698
+ validatedAssessments.push({
9699
+ assessment: assessment.assessment,
9700
+ exact: reconciled.exact,
9701
+ start: reconciled.start,
9702
+ end: reconciled.end,
9703
+ ...reconciled.prefix !== void 0 ? { prefix: reconciled.prefix } : {},
9704
+ ...reconciled.suffix !== void 0 ? { suffix: reconciled.suffix } : {}
9705
+ });
9762
9706
  }
9707
+ return validatedAssessments;
9763
9708
  }
9764
9709
  /**
9765
9710
  * Parse the LLM's tag response into raw, pre-reconciliation tag inputs.
9766
9711
  * Reconciliation happens in `validateTagOffsets`, which adds `start`/`end`
9767
9712
  * by anchoring `exact` against the source content.
9713
+ *
9714
+ * @throws if the response is not a parseable JSON array
9768
9715
  */
9769
9716
  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
- }
9717
+ const parsed = parseJsonArray(response, "tag");
9718
+ const valid = parsed.filter(
9719
+ (t) => isObject(t) && isString(t.exact) && t.exact.trim().length > 0
9720
+ );
9721
+ console.log(`[MotivationParsers] Parsed ${valid.length} valid tags from ${parsed.length} total`);
9722
+ return valid;
9781
9723
  }
9782
9724
  /**
9783
9725
  * Anchor raw tag inputs against source content and add category.
@@ -9814,6 +9756,11 @@ function logAnchorMethod(motivation, exact, anchorMethod) {
9814
9756
  }
9815
9757
 
9816
9758
  // src/workers/annotation-detection.ts
9759
+ function assertNotTruncated(response, motivation) {
9760
+ if (response.stopReason === "max_tokens") {
9761
+ 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.`);
9762
+ }
9763
+ }
9817
9764
  var AnnotationDetection = class {
9818
9765
  /**
9819
9766
  * Detect comments in content.
@@ -9825,8 +9772,9 @@ var AnnotationDetection = class {
9825
9772
  */
9826
9773
  static async detectComments(content, client, instructions, tone, density, language, sourceLanguage) {
9827
9774
  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);
9775
+ const response = await client.generateTextWithMetadata(prompt, 3e3, 0.4, { format: "json" });
9776
+ assertNotTruncated(response, "comment");
9777
+ return MotivationParsers.parseComments(response.text, content);
9830
9778
  }
9831
9779
  /**
9832
9780
  * Detect highlights in content.
@@ -9837,8 +9785,9 @@ var AnnotationDetection = class {
9837
9785
  */
9838
9786
  static async detectHighlights(content, client, instructions, density, sourceLanguage) {
9839
9787
  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);
9788
+ const response = await client.generateTextWithMetadata(prompt, 2e3, 0.3, { format: "json" });
9789
+ assertNotTruncated(response, "highlight");
9790
+ return MotivationParsers.parseHighlights(response.text, content);
9842
9791
  }
9843
9792
  /**
9844
9793
  * Detect assessments in content.
@@ -9849,8 +9798,9 @@ var AnnotationDetection = class {
9849
9798
  */
9850
9799
  static async detectAssessments(content, client, instructions, tone, density, language, sourceLanguage) {
9851
9800
  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);
9801
+ const response = await client.generateTextWithMetadata(prompt, 3e3, 0.3, { format: "json" });
9802
+ assertNotTruncated(response, "assessment");
9803
+ return MotivationParsers.parseAssessments(response.text, content);
9854
9804
  }
9855
9805
  /**
9856
9806
  * Detect tags in content for a specific category.
@@ -9879,8 +9829,9 @@ var AnnotationDetection = class {
9879
9829
  categoryInfo.examples,
9880
9830
  sourceLanguage
9881
9831
  );
9882
- const response = await client.generateText(prompt, 4e3, 0.2, { format: "json" });
9883
- const parsedTags = MotivationParsers.parseTags(response);
9832
+ const response = await client.generateTextWithMetadata(prompt, 4e3, 0.2, { format: "json" });
9833
+ assertNotTruncated(response, "tag");
9834
+ const parsedTags = MotivationParsers.parseTags(response.text);
9884
9835
  return MotivationParsers.validateTagOffsets(parsedTags, content, category);
9885
9836
  }
9886
9837
  };
@@ -9949,41 +9900,47 @@ Example output:
9949
9900
  { format: "json" }
9950
9901
  );
9951
9902
  logger2.debug("Got entity extraction response", { responseLength: response.text.length });
9903
+ if (response.stopReason === "max_tokens") {
9904
+ const errorMsg = "Entity extraction response truncated (max_tokens) \u2014 increase max_tokens or reduce resource size; failing the job rather than dropping annotations.";
9905
+ logger2.error(errorMsg, { responseLength: response.text.length });
9906
+ throw new Error(errorMsg);
9907
+ }
9908
+ let entities;
9952
9909
  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
- }));
9910
+ entities = JSON.parse(response.text.trim());
9976
9911
  } catch (error) {
9977
9912
  logger2.error("Failed to parse entity extraction response", {
9978
- error: error instanceof Error ? error.message : String(error)
9913
+ error: error instanceof Error ? error.message : String(error),
9914
+ response: response.text.slice(0, 500)
9915
+ });
9916
+ throw new Error("Failed to parse entity extraction response", {
9917
+ cause: error instanceof Error ? error : new Error(String(error))
9979
9918
  });
9980
- return [];
9981
9919
  }
9920
+ if (!isArray(entities)) {
9921
+ logger2.error("Failed to parse entity extraction response: expected a JSON array", {
9922
+ response: response.text.slice(0, 500)
9923
+ });
9924
+ throw new Error("Failed to parse entity extraction response: expected a JSON array");
9925
+ }
9926
+ logger2.debug("Parsed entities from AI response", { count: entities.length });
9927
+ return entities.filter((e) => {
9928
+ const ok = isObject(e) && isString(e.exact) && isString(e.entityType);
9929
+ if (!ok) {
9930
+ logger2.debug("Dropped malformed LLM entity", { entity: e });
9931
+ }
9932
+ return ok;
9933
+ }).map((entity) => ({
9934
+ exact: entity.exact,
9935
+ entityType: entity.entityType,
9936
+ ...isString(entity.prefix) ? { prefix: entity.prefix } : {},
9937
+ ...isString(entity.suffix) ? { suffix: entity.suffix } : {}
9938
+ }));
9982
9939
  }
9983
9940
  function getLanguageName(locale) {
9984
9941
  return getLocaleEnglishName(locale) || locale;
9985
9942
  }
9986
- async function generateResourceFromTopic(topic, entityTypes, client, logger2, userPrompt, locale, context, temperature, maxTokens, sourceLanguage) {
9943
+ async function generateResourceFromTopic(topic, entityTypes, client, logger2, userPrompt, locale, context, temperature, maxTokens, sourceLanguage, outputMediaType = "text/markdown") {
9987
9944
  logger2.debug("Generating resource from topic", {
9988
9945
  topicPreview: topic.substring(0, 100),
9989
9946
  entityTypes,
@@ -10003,61 +9960,95 @@ IMPORTANT: Write the entire resource in ${getLanguageName(locale)}.` : "";
10003
9960
 
10004
9961
  The source resource and embedded context are in ${getLanguageName(sourceLanguage)}.` : "";
10005
9962
  let annotationSection = "";
9963
+ let contextSection = "";
9964
+ let resourceSection = "";
9965
+ let graphSection = "";
10006
9966
  if (context) {
10007
- const parts = [];
10008
- parts.push(`- Annotation motivation: ${context.annotation.motivation}`);
10009
- parts.push(`- Source resource: ${context.sourceResource.name}`);
10010
- const { motivation, body } = context.annotation;
10011
- if (motivation === "commenting" || motivation === "assessing") {
10012
- const bodyItem = Array.isArray(body) ? body[0] : body;
10013
- if (bodyItem && "value" in bodyItem && bodyItem.value) {
10014
- const label = motivation === "commenting" ? "Comment" : "Assessment";
10015
- parts.push(`- ${label}: ${bodyItem.value}`);
10016
- }
10017
- }
10018
- annotationSection = `
9967
+ const { focus } = context;
9968
+ const mainResourceId = focus.kind === "annotation" ? focus.sourceResource["@id"] : focus.resource["@id"];
9969
+ const focalAnnotationId = focus.kind === "annotation" ? focus.annotation.id : void 0;
9970
+ if (focus.kind === "annotation") {
9971
+ const parts = [];
9972
+ parts.push(`- Annotation motivation: ${focus.annotation.motivation}`);
9973
+ parts.push(`- Source resource: ${focus.sourceResource.name}`);
9974
+ const { motivation, body } = focus.annotation;
9975
+ if (motivation === "commenting" || motivation === "assessing") {
9976
+ const bodyItem = Array.isArray(body) ? body[0] : body;
9977
+ if (bodyItem && "value" in bodyItem && bodyItem.value) {
9978
+ const label = motivation === "commenting" ? "Comment" : "Assessment";
9979
+ parts.push(`- ${label}: ${bodyItem.value}`);
9980
+ }
9981
+ }
9982
+ annotationSection = `
10019
9983
 
10020
9984
  Annotation context:
10021
9985
  ${parts.join("\n")}`;
10022
- }
10023
- let contextSection = "";
10024
- if (context?.sourceContext) {
10025
- const { before, selected, after } = context.sourceContext;
10026
- contextSection = `
9986
+ if (focus.selected) {
9987
+ const { before, text, after } = focus.selected;
9988
+ contextSection = `
10027
9989
 
10028
9990
  Source document context:
10029
9991
  ---
10030
9992
  ${before ? `...${before}` : ""}
10031
- **[${selected}]**
9993
+ **[${text}]**
10032
9994
  ${after ? `${after}...` : ""}
10033
9995
  ---
10034
9996
  `;
10035
- }
10036
- let graphContextSection = "";
10037
- if (context?.graphContext) {
10038
- const gc = context.graphContext;
10039
- const connections = gc.connections ?? [];
10040
- const citedBy = gc.citedBy ?? [];
10041
- const parts = [];
10042
- if (connections.length > 0) {
10043
- const connList = connections.map((c) => `${c.resourceName}${c.entityTypes?.length ? ` (${c.entityTypes.join(", ")})` : ""}`).join(", ");
10044
- parts.push(`- Connected resources: ${connList}`);
10045
- }
10046
- if (gc.citedByCount && gc.citedByCount > 0) {
10047
- const citedNames = citedBy.map((c) => c.resourceName).join(", ");
10048
- parts.push(`- This resource is cited by ${gc.citedByCount} other resource${gc.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
10049
- }
10050
- if (gc.siblingEntityTypes && gc.siblingEntityTypes.length > 0) {
10051
- parts.push(`- Related entity types in this document: ${gc.siblingEntityTypes.join(", ")}`);
10052
- }
10053
- if (gc.inferredRelationshipSummary) {
10054
- parts.push(`- Relationship summary: ${gc.inferredRelationshipSummary}`);
9997
+ }
9998
+ } else {
9999
+ const RESOURCE_CONTENT_CAP = 4e3;
10000
+ const parts = [`- Resource: ${focus.resource.name}`];
10001
+ if (focus.summary) parts.push(`- Summary: ${focus.summary}`);
10002
+ if (focus.suggestedReferences && focus.suggestedReferences.length > 0) {
10003
+ parts.push(`- Suggested references: ${focus.suggestedReferences.join(", ")}`);
10004
+ }
10005
+ resourceSection = `
10006
+
10007
+ Resource context:
10008
+ ${parts.join("\n")}`;
10009
+ if (focus.content?.main) {
10010
+ resourceSection += `
10011
+
10012
+ Resource content:
10013
+ ---
10014
+ ${focus.content.main.slice(0, RESOURCE_CONTENT_CAP)}
10015
+ ---`;
10016
+ }
10017
+ const related = Object.entries(focus.content?.related ?? {});
10018
+ if (related.length > 0) {
10019
+ const blocks = related.map(([id, text]) => `[${id}]
10020
+ ${text.slice(0, RESOURCE_CONTENT_CAP)}`).join("\n\n");
10021
+ resourceSection += `
10022
+
10023
+ Related resource content:
10024
+ ---
10025
+ ${blocks}
10026
+ ---`;
10027
+ }
10055
10028
  }
10056
- if (parts.length > 0) {
10057
- graphContextSection = `
10029
+ if (mainResourceId) {
10030
+ const views = deriveViews(context.graph, mainResourceId, focalAnnotationId);
10031
+ const parts = [];
10032
+ if (views.connections.length > 0) {
10033
+ const connList = views.connections.map((c) => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(", ")})` : ""}`).join(", ");
10034
+ parts.push(`- Connected resources: ${connList}`);
10035
+ }
10036
+ if (views.citedByCount > 0) {
10037
+ const citedNames = views.citedBy.map((c) => c.resourceName).join(", ");
10038
+ parts.push(`- This resource is cited by ${views.citedByCount} other resource${views.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
10039
+ }
10040
+ if (views.siblingEntityTypes.length > 0) {
10041
+ parts.push(`- Related entity types in this document: ${views.siblingEntityTypes.join(", ")}`);
10042
+ }
10043
+ if (context.inferredRelationshipSummary) {
10044
+ parts.push(`- Relationship summary: ${context.inferredRelationshipSummary}`);
10045
+ }
10046
+ if (parts.length > 0) {
10047
+ graphSection = `
10058
10048
 
10059
10049
  Knowledge graph context:
10060
10050
  ${parts.join("\n")}`;
10051
+ }
10061
10052
  }
10062
10053
  }
10063
10054
  let semanticContextSection = "";
@@ -10069,17 +10060,20 @@ ${parts.join("\n")}`;
10069
10060
  Related passages from the knowledge base:
10070
10061
  ${lines.join("\n")}`;
10071
10062
  }
10072
- const structureGuidance = finalMaxTokens >= 1e3 ? "organized into titled sections (## Section) with well-structured paragraphs" : "organized into well-structured paragraphs";
10063
+ const isPlainText = outputMediaType === "text/plain";
10064
+ const structureGuidance = !isPlainText && finalMaxTokens >= 1e3 ? "organized into titled sections (## Section) with well-structured paragraphs" : "organized into well-structured paragraphs";
10065
+ const formatRequirements = isPlainText ? `- Write the response as plain text \u2014 no formatting markup (no #, *, backticks, headings, or links)
10066
+ - Begin with the title on its own first line` : `- Start with a clear heading (# Title)
10067
+ - Use markdown formatting
10068
+ - Write the response as markdown`;
10073
10069
  const prompt = `Generate a concise, informative resource about "${topic}".
10074
10070
  ${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}
10075
- ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${graphContextSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
10071
+ ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${resourceSection}${graphSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
10076
10072
 
10077
10073
  Requirements:
10078
- - Start with a clear heading (# Title)
10079
10074
  - Aim for approximately ${finalMaxTokens} tokens of content, ${structureGuidance}
10080
10075
  - Be factual and informative
10081
- - Use markdown formatting
10082
- - Write the response as markdown`;
10076
+ ${formatRequirements}`;
10083
10077
  const parseResponse = (response2) => {
10084
10078
  let content = response2.trim();
10085
10079
  if (content.startsWith("```markdown") || content.startsWith("```md")) {
@@ -10398,6 +10392,13 @@ async function processTagJob(content, inferenceClient, params, userId, generator
10398
10392
  };
10399
10393
  }
10400
10394
  async function processGenerationJob(inferenceClient, params, onProgress, logger2) {
10395
+ const GENERATABLE_MEDIA_TYPES = ["text/markdown", "text/plain"];
10396
+ const outputMediaType = params.outputMediaType ?? "text/markdown";
10397
+ if (!GENERATABLE_MEDIA_TYPES.includes(outputMediaType)) {
10398
+ throw new Error(
10399
+ `Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(" or ")}.`
10400
+ );
10401
+ }
10401
10402
  onProgress(20, "Fetching context...", "fetching");
10402
10403
  const title = params.title ?? "Untitled";
10403
10404
  const entityTypes = (params.entityTypes ?? []).map(String);
@@ -10412,13 +10413,14 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger2
10412
10413
  params.context,
10413
10414
  params.temperature,
10414
10415
  params.maxTokens,
10415
- params.sourceLanguage
10416
+ params.sourceLanguage,
10417
+ outputMediaType
10416
10418
  );
10417
10419
  onProgress(85, "Creating resource...", "creating");
10418
10420
  return {
10419
10421
  content: generated.content,
10420
10422
  title: generated.title ?? title,
10421
- format: "text/markdown",
10423
+ format: outputMediaType,
10422
10424
  result: {
10423
10425
  resourceId: "",
10424
10426
  resourceName: generated.title ?? title
@@ -10638,6 +10640,17 @@ async function handleJobInner(adapter, config, job) {
10638
10640
  ...genParams.entityTypes && genParams.entityTypes.length > 0 ? { entityTypes: genParams.entityTypes } : {},
10639
10641
  generator
10640
10642
  });
10643
+ if (!genParams.referenceId) {
10644
+ const { annotation: provenanceRef } = assembleAnnotation(
10645
+ {
10646
+ motivation: "linking",
10647
+ target: { source: String(resourceId) },
10648
+ body: { type: "SpecificResource", source: String(newResourceId), purpose: "linking" }
10649
+ },
10650
+ generator
10651
+ );
10652
+ await emitEvent(session, "mark:create", { annotation: provenanceRef, userId, resourceId });
10653
+ }
10641
10654
  await emitEvent(session, "job:complete", {
10642
10655
  ...lifecycleBase,
10643
10656
  result: { resourceId: newResourceId, resourceName: genResult.title }
@@ -10649,7 +10662,7 @@ async function handleJobInner(adapter, config, job) {
10649
10662
  }
10650
10663
 
10651
10664
  // src/worker-main.ts
10652
- var import_rxjs3 = __toESM(require_cjs());
10665
+ var import_rxjs2 = __toESM(require_cjs());
10653
10666
  var ALL_JOB_TYPES = [
10654
10667
  "reference-annotation",
10655
10668
  "generation",
@@ -10757,7 +10770,7 @@ async function startAgentWorker(group) {
10757
10770
  };
10758
10771
  const storage = new InMemorySessionStorage();
10759
10772
  setStoredSession(storage, kbId, { access: initialToken, refresh: "" });
10760
- const token$ = new import_rxjs3.BehaviorSubject(null);
10773
+ const token$ = new import_rxjs2.BehaviorSubject(null);
10761
10774
  let session;
10762
10775
  const transport = new HttpTransport({
10763
10776
  baseUrl: baseUrl(kbBackendUrl(endpoint)),