@semiont/jobs 0.5.18 → 0.5.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -535,7 +535,22 @@ interface GenerationCitation {
535
535
  * jobs over SSE and dispatches by jobType to these functions.
536
536
  */
537
537
 
538
- type Agent = components['schemas']['Agent'];
538
+ /** A detected span — offsets into the extracted `.text`, plus optional context. */
539
+ type SpanMatch = {
540
+ exact: string;
541
+ start: number;
542
+ end: number;
543
+ prefix?: string;
544
+ suffix?: string;
545
+ };
546
+ /**
547
+ * Turn a detected span into a stored annotation. The media type, resource, and
548
+ * attribution context are closed over by the caller (see `prepareDetection`);
549
+ * the detection processor supplies only the motivation, the span, and any
550
+ * motivation-specific body. This is the single axis that varies by media type,
551
+ * so the detection processors themselves stay media-agnostic.
552
+ */
553
+ type BuildAnnotation = (motivation: string, match: SpanMatch, body?: Record<string, unknown> | Record<string, unknown>[]) => Record<string, unknown>;
539
554
  /**
540
555
  * Progress callback. The three positional args satisfy the minimum
541
556
  * `JobProgress` required fields (`percentage`, `message`, `stage`).
@@ -549,11 +564,11 @@ interface ProcessorResult<R> {
549
564
  annotations: Record<string, unknown>[];
550
565
  result: R;
551
566
  }
552
- declare function processHighlightJob(content: string, inferenceClient: InferenceClient, params: HighlightDetectionParams, userId: string, generator: Agent, onProgress: OnProgress): Promise<ProcessorResult<HighlightDetectionResult>>;
553
- declare function processCommentJob(content: string, inferenceClient: InferenceClient, params: CommentDetectionParams, userId: string, generator: Agent, onProgress: OnProgress): Promise<ProcessorResult<CommentDetectionResult>>;
554
- declare function processAssessmentJob(content: string, inferenceClient: InferenceClient, params: AssessmentDetectionParams, userId: string, generator: Agent, onProgress: OnProgress): Promise<ProcessorResult<AssessmentDetectionResult>>;
555
- declare function processReferenceJob(content: string, inferenceClient: InferenceClient, params: DetectionParams, userId: string, generator: Agent, onProgress: OnProgress, logger: Logger): Promise<ProcessorResult<DetectionResult>>;
556
- declare function processTagJob(content: string, inferenceClient: InferenceClient, params: TagDetectionParams, userId: string, generator: Agent, onProgress: OnProgress): Promise<ProcessorResult<TagDetectionResult>>;
567
+ declare function processHighlightJob(content: string, inferenceClient: InferenceClient, params: HighlightDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress): Promise<ProcessorResult<HighlightDetectionResult>>;
568
+ declare function processCommentJob(content: string, inferenceClient: InferenceClient, params: CommentDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress): Promise<ProcessorResult<CommentDetectionResult>>;
569
+ declare function processAssessmentJob(content: string, inferenceClient: InferenceClient, params: AssessmentDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress): Promise<ProcessorResult<AssessmentDetectionResult>>;
570
+ declare function processReferenceJob(content: string, inferenceClient: InferenceClient, params: DetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress, logger: Logger): Promise<ProcessorResult<DetectionResult>>;
571
+ declare function processTagJob(content: string, inferenceClient: InferenceClient, params: TagDetectionParams, buildAnnotation: BuildAnnotation, onProgress: OnProgress): Promise<ProcessorResult<TagDetectionResult>>;
557
572
  declare function processGenerationJob(inferenceClient: InferenceClient, params: GenerationParams, onProgress: OnProgress, logger: Logger): Promise<{
558
573
  content: string;
559
574
  title: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { promises } from 'fs';
2
2
  import * as path from 'path';
3
- import { jobId, deriveViews, reconcileSelector, isObject, isString, getLocaleEnglishName, didToAgent, isArray } from '@semiont/core';
4
- import { generateAnnotationId } from '@semiont/event-sourcing';
3
+ import { jobId, deriveViews, reconcileSelector, isObject, isString, getLocaleEnglishName, isArray } from '@semiont/core';
4
+ import '@semiont/event-sourcing';
5
5
  import '@semiont/content';
6
6
  import '@semiont/observability';
7
7
  import '@semiont/sdk';
@@ -1388,12 +1388,15 @@ function annotationDedupeKey(ann) {
1388
1388
  const target = ann.target;
1389
1389
  const selectors = Array.isArray(target?.selector) ? target.selector : [];
1390
1390
  const pos = selectors.find((s) => s.type === "TextPositionSelector");
1391
- return [
1392
- ann.motivation,
1393
- pos?.start ?? "?",
1394
- pos?.end ?? "?",
1395
- JSON.stringify(ann.body ?? null)
1396
- ].join("|");
1391
+ let anchor;
1392
+ if (pos) {
1393
+ anchor = `pos:${pos.start ?? "?"}:${pos.end ?? "?"}`;
1394
+ } else {
1395
+ const frags = selectors.filter((s) => s.type === "FragmentSelector").map((s) => s.value ?? "").join(",");
1396
+ const quote = selectors.find((s) => s.type === "TextQuoteSelector");
1397
+ anchor = `frag:${frags}|quote:${quote?.exact ?? ""}:${quote?.prefix ?? ""}:${quote?.suffix ?? ""}`;
1398
+ }
1399
+ return [ann.motivation, anchor, JSON.stringify(ann.body ?? null)].join("|");
1397
1400
  }
1398
1401
  function dedupeAnnotations(annotations) {
1399
1402
  const seen = /* @__PURE__ */ new Set();
@@ -1406,56 +1409,7 @@ function dedupeAnnotations(annotations) {
1406
1409
  }
1407
1410
  return out;
1408
1411
  }
1409
- function buildTextAnnotation(content, resourceId, userId, generator, motivation, match, body) {
1410
- if (content.substring(match.start, match.end) !== match.exact) {
1411
- throw new Error(
1412
- `buildTextAnnotation invariant: content.substring(${match.start}, ${match.end}) !== exact for resource ${resourceId}, motivation ${motivation}`
1413
- );
1414
- }
1415
- if (match.prefix !== void 0) {
1416
- const actualPrefix = content.substring(Math.max(0, match.start - match.prefix.length), match.start);
1417
- if (actualPrefix !== match.prefix) {
1418
- throw new Error(
1419
- `buildTextAnnotation invariant: content prefix-slice !== prefix for resource ${resourceId}, motivation ${motivation}`
1420
- );
1421
- }
1422
- }
1423
- if (match.suffix !== void 0) {
1424
- const actualSuffix = content.substring(match.end, Math.min(content.length, match.end + match.suffix.length));
1425
- if (actualSuffix !== match.suffix) {
1426
- throw new Error(
1427
- `buildTextAnnotation invariant: content suffix-slice !== suffix for resource ${resourceId}, motivation ${motivation}`
1428
- );
1429
- }
1430
- }
1431
- const creator = didToAgent(userId);
1432
- const wasAttributedTo = creator["@id"] === generator["@id"] ? [generator] : [creator, generator];
1433
- return {
1434
- "@context": "http://www.w3.org/ns/anno.jsonld",
1435
- "type": "Annotation",
1436
- "id": generateAnnotationId(),
1437
- motivation,
1438
- creator,
1439
- generator,
1440
- wasAttributedTo,
1441
- created: (/* @__PURE__ */ new Date()).toISOString(),
1442
- target: {
1443
- type: "SpecificResource",
1444
- source: resourceId,
1445
- selector: [
1446
- { type: "TextPositionSelector", start: match.start, end: match.end },
1447
- {
1448
- type: "TextQuoteSelector",
1449
- exact: match.exact,
1450
- ...match.prefix && { prefix: match.prefix },
1451
- ...match.suffix && { suffix: match.suffix }
1452
- }
1453
- ]
1454
- },
1455
- ...body !== void 0 ? { body } : {}
1456
- };
1457
- }
1458
- async function processHighlightJob(content, inferenceClient, params, userId, generator, onProgress) {
1412
+ async function processHighlightJob(content, inferenceClient, params, buildAnnotation, onProgress) {
1459
1413
  onProgress(10, "Loading resource...", "analyzing");
1460
1414
  onProgress(30, "Analyzing text...", "analyzing");
1461
1415
  const highlights = await AnnotationDetection.detectHighlights(
@@ -1467,7 +1421,7 @@ async function processHighlightJob(content, inferenceClient, params, userId, gen
1467
1421
  );
1468
1422
  onProgress(60, `Creating ${highlights.length} annotations...`, "creating");
1469
1423
  const annotations = dedupeAnnotations(highlights.map(
1470
- (h) => buildTextAnnotation(content, params.resourceId, userId, generator, "highlighting", h)
1424
+ (h) => buildAnnotation("highlighting", h)
1471
1425
  ));
1472
1426
  onProgress(100, `Complete! Created ${annotations.length} highlights`, "creating");
1473
1427
  return {
@@ -1475,7 +1429,7 @@ async function processHighlightJob(content, inferenceClient, params, userId, gen
1475
1429
  result: { highlightsFound: highlights.length, highlightsCreated: annotations.length }
1476
1430
  };
1477
1431
  }
1478
- async function processCommentJob(content, inferenceClient, params, userId, generator, onProgress) {
1432
+ async function processCommentJob(content, inferenceClient, params, buildAnnotation, onProgress) {
1479
1433
  onProgress(10, "Loading resource...", "analyzing");
1480
1434
  onProgress(30, "Analyzing text...", "analyzing");
1481
1435
  const comments = await AnnotationDetection.detectComments(
@@ -1494,7 +1448,7 @@ async function processCommentJob(content, inferenceClient, params, userId, gener
1494
1448
  // Match the pre-#651 CommentAnnotationWorker: include format and
1495
1449
  // language on the body TextualBody. Optional in the schema, but
1496
1450
  // consumers that do language-aware rendering rely on them.
1497
- buildTextAnnotation(content, params.resourceId, userId, generator, "commenting", c, [
1451
+ buildAnnotation("commenting", c, [
1498
1452
  { type: "TextualBody", value: c.comment, purpose: "commenting", format: "text/plain", language: bodyLanguage }
1499
1453
  ])
1500
1454
  )
@@ -1505,7 +1459,7 @@ async function processCommentJob(content, inferenceClient, params, userId, gener
1505
1459
  result: { commentsFound: comments.length, commentsCreated: annotations.length }
1506
1460
  };
1507
1461
  }
1508
- async function processAssessmentJob(content, inferenceClient, params, userId, generator, onProgress) {
1462
+ async function processAssessmentJob(content, inferenceClient, params, buildAnnotation, onProgress) {
1509
1463
  onProgress(10, "Loading resource...", "analyzing");
1510
1464
  onProgress(30, "Analyzing text...", "analyzing");
1511
1465
  const assessments = await AnnotationDetection.detectAssessments(
@@ -1527,7 +1481,7 @@ async function processAssessmentJob(content, inferenceClient, params, userId, ge
1527
1481
  // purpose='describing' — that loses the "this is an assessment, not
1528
1482
  // a description" signal and breaks existing readers that access
1529
1483
  // `body.value` directly on the object.
1530
- buildTextAnnotation(content, params.resourceId, userId, generator, "assessing", a, {
1484
+ buildAnnotation("assessing", a, {
1531
1485
  type: "TextualBody",
1532
1486
  value: a.assessment,
1533
1487
  purpose: "assessing",
@@ -1542,7 +1496,7 @@ async function processAssessmentJob(content, inferenceClient, params, userId, ge
1542
1496
  result: { assessmentsFound: assessments.length, assessmentsCreated: annotations.length }
1543
1497
  };
1544
1498
  }
1545
- async function processReferenceJob(content, inferenceClient, params, userId, generator, onProgress, logger) {
1499
+ async function processReferenceJob(content, inferenceClient, params, buildAnnotation, onProgress, logger) {
1546
1500
  const entityTypeNames = params.entityTypes.map(String);
1547
1501
  const requestParams = [{ label: "Entity types", value: entityTypeNames.join(", ") }];
1548
1502
  const completedEntityTypes = [];
@@ -1599,15 +1553,7 @@ async function processReferenceJob(content, inferenceClient, params, userId, gen
1599
1553
  anchorMethod: reconciled.anchorMethod
1600
1554
  });
1601
1555
  }
1602
- const ann = buildTextAnnotation(
1603
- content,
1604
- params.resourceId,
1605
- userId,
1606
- generator,
1607
- "linking",
1608
- toMatch(reconciled),
1609
- unresolvedBody
1610
- );
1556
+ const ann = buildAnnotation("linking", toMatch(reconciled), unresolvedBody);
1611
1557
  allAnnotations.push(ann);
1612
1558
  totalEmitted++;
1613
1559
  }
@@ -1619,7 +1565,7 @@ async function processReferenceJob(content, inferenceClient, params, userId, gen
1619
1565
  result: { totalFound, totalEmitted: annotations.length, errors }
1620
1566
  };
1621
1567
  }
1622
- async function processTagJob(content, inferenceClient, params, userId, generator, onProgress) {
1568
+ async function processTagJob(content, inferenceClient, params, buildAnnotation, onProgress) {
1623
1569
  onProgress(10, "Loading resource...", "analyzing");
1624
1570
  onProgress(30, "Analyzing text for tags...", "analyzing");
1625
1571
  const allTags = [];
@@ -1638,7 +1584,7 @@ async function processTagJob(content, inferenceClient, params, userId, generator
1638
1584
  const bodyLanguage = params.language ?? "en";
1639
1585
  const annotations = dedupeAnnotations(tags.map((t) => {
1640
1586
  const category = t.category ?? "unknown";
1641
- return buildTextAnnotation(content, params.resourceId, userId, generator, "tagging", t, [
1587
+ return buildAnnotation("tagging", t, [
1642
1588
  { type: "TextualBody", value: category, purpose: "tagging", format: "text/plain", language: bodyLanguage },
1643
1589
  { type: "TextualBody", value: params.schema.id, purpose: "classifying", format: "text/plain" }
1644
1590
  ]);