@semiont/jobs 0.5.9 → 0.5.11

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,14 +1,14 @@
1
- import { createTomlConfigLoader, softwareToAgent, baseUrl, getPrimaryMediaType, textExtractionOf, reconcileSelector, didToAgent, getLocaleEnglishName, isArray, isObject, isString } from '@semiont/core';
1
+ import { createTomlConfigLoader, didToAgent, baseUrl, busRequest, getPrimaryMediaType, textExtractionOf, assembleAnnotation, reconcileSelector, 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';
5
+ import { homedir, hostname } from 'os';
6
+ import { InMemorySessionStorage, setStoredSession, kbBackendUrl, SemiontClient, SemiontSession } from '@semiont/sdk';
7
+ import { HttpTransport, HttpContentTransport } from '@semiont/http-transport';
5
8
  import { createInferenceClient } from '@semiont/inference';
6
9
  import { createServer } from 'http';
7
10
  import { existsSync, readFileSync } from 'fs';
8
- import { homedir, hostname } from 'os';
9
11
  import { join } from 'path';
10
- import { InMemorySessionStorage, setStoredSession, kbBackendUrl, SemiontClient, SemiontSession } from '@semiont/sdk';
11
- import { HttpTransport, HttpContentTransport } from '@semiont/http-transport';
12
12
  import { createProcessLogger } from '@semiont/observability/process-logger';
13
13
 
14
14
  var __create = Object.create;
@@ -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
 
@@ -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
 
@@ -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,
@@ -9945,7 +9940,13 @@ Example output:
9945
9940
  function getLanguageName(locale) {
9946
9941
  return getLocaleEnglishName(locale) || locale;
9947
9942
  }
9948
- async function generateResourceFromTopic(topic, entityTypes, client, logger2, userPrompt, locale, context, temperature, maxTokens, sourceLanguage) {
9943
+ var RESOURCE_CONTENT_CAP = 4e3;
9944
+ var SEMANTIC_MATCH_LIMIT = 3;
9945
+ var SEMANTIC_MATCH_CHARS = 240;
9946
+ function idLabel(resourceId, annotationId) {
9947
+ return `[${resourceId}${annotationId ? `/${annotationId}` : ""}]`;
9948
+ }
9949
+ async function generateResourceFromTopic(topic, entityTypes, client, logger2, userPrompt, locale, context, temperature, maxTokens, sourceLanguage, outputMediaType = "text/markdown", task = "resource", structure, cite = false) {
9949
9950
  logger2.debug("Generating resource from topic", {
9950
9951
  topicPreview: topic.substring(0, 100),
9951
9952
  entityTypes,
@@ -9954,7 +9955,10 @@ async function generateResourceFromTopic(topic, entityTypes, client, logger2, us
9954
9955
  sourceLanguage,
9955
9956
  hasContext: !!context,
9956
9957
  temperature,
9957
- maxTokens
9958
+ maxTokens,
9959
+ outputMediaType,
9960
+ task,
9961
+ structure
9958
9962
  });
9959
9963
  const finalTemperature = temperature ?? 0.7;
9960
9964
  const finalMaxTokens = maxTokens ?? 500;
@@ -9965,83 +9969,146 @@ IMPORTANT: Write the entire resource in ${getLanguageName(locale)}.` : "";
9965
9969
 
9966
9970
  The source resource and embedded context are in ${getLanguageName(sourceLanguage)}.` : "";
9967
9971
  let annotationSection = "";
9972
+ let contextSection = "";
9973
+ let resourceSection = "";
9974
+ let graphSection = "";
9968
9975
  if (context) {
9969
- const parts = [];
9970
- parts.push(`- Annotation motivation: ${context.annotation.motivation}`);
9971
- parts.push(`- Source resource: ${context.sourceResource.name}`);
9972
- const { motivation, body } = context.annotation;
9973
- if (motivation === "commenting" || motivation === "assessing") {
9974
- const bodyItem = Array.isArray(body) ? body[0] : body;
9975
- if (bodyItem && "value" in bodyItem && bodyItem.value) {
9976
- const label = motivation === "commenting" ? "Comment" : "Assessment";
9977
- parts.push(`- ${label}: ${bodyItem.value}`);
9978
- }
9979
- }
9980
- annotationSection = `
9976
+ const { focus } = context;
9977
+ const mainResourceId = focus.kind === "annotation" ? focus.sourceResource["@id"] : focus.resource["@id"];
9978
+ const focalAnnotationId = focus.kind === "annotation" ? focus.annotation.id : void 0;
9979
+ if (focus.kind === "annotation") {
9980
+ const parts = [];
9981
+ parts.push(`- Annotation motivation: ${focus.annotation.motivation}`);
9982
+ parts.push(`- Source resource: ${focus.sourceResource.name} ${idLabel(mainResourceId)}`);
9983
+ const { motivation, body } = focus.annotation;
9984
+ if (motivation === "commenting" || motivation === "assessing") {
9985
+ const bodyItem = Array.isArray(body) ? body[0] : body;
9986
+ if (bodyItem && "value" in bodyItem && bodyItem.value) {
9987
+ const label = motivation === "commenting" ? "Comment" : "Assessment";
9988
+ parts.push(`- ${label}: ${bodyItem.value}`);
9989
+ }
9990
+ }
9991
+ annotationSection = `
9981
9992
 
9982
9993
  Annotation context:
9983
9994
  ${parts.join("\n")}`;
9984
- }
9985
- let contextSection = "";
9986
- if (context?.sourceContext) {
9987
- const { before, selected, after } = context.sourceContext;
9988
- contextSection = `
9995
+ if (focus.selected) {
9996
+ const { before, text, after } = focus.selected;
9997
+ contextSection = `
9989
9998
 
9990
9999
  Source document context:
9991
10000
  ---
9992
10001
  ${before ? `...${before}` : ""}
9993
- **[${selected}]**
10002
+ **[${text}]**
9994
10003
  ${after ? `${after}...` : ""}
9995
10004
  ---
9996
10005
  `;
9997
- }
9998
- let graphContextSection = "";
9999
- if (context?.graphContext) {
10000
- const gc = context.graphContext;
10001
- const connections = gc.connections ?? [];
10002
- const citedBy = gc.citedBy ?? [];
10003
- const parts = [];
10004
- if (connections.length > 0) {
10005
- const connList = connections.map((c) => `${c.resourceName}${c.entityTypes?.length ? ` (${c.entityTypes.join(", ")})` : ""}`).join(", ");
10006
- parts.push(`- Connected resources: ${connList}`);
10007
- }
10008
- if (gc.citedByCount && gc.citedByCount > 0) {
10009
- const citedNames = citedBy.map((c) => c.resourceName).join(", ");
10010
- parts.push(`- This resource is cited by ${gc.citedByCount} other resource${gc.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
10011
- }
10012
- if (gc.siblingEntityTypes && gc.siblingEntityTypes.length > 0) {
10013
- parts.push(`- Related entity types in this document: ${gc.siblingEntityTypes.join(", ")}`);
10014
- }
10015
- if (gc.inferredRelationshipSummary) {
10016
- parts.push(`- Relationship summary: ${gc.inferredRelationshipSummary}`);
10006
+ }
10007
+ } else {
10008
+ const parts = [`- Resource: ${focus.resource.name} ${idLabel(mainResourceId)}`];
10009
+ if (focus.summary) parts.push(`- Summary: ${focus.summary}`);
10010
+ if (focus.suggestedReferences && focus.suggestedReferences.length > 0) {
10011
+ parts.push(`- Suggested references: ${focus.suggestedReferences.join(", ")}`);
10012
+ }
10013
+ resourceSection = `
10014
+
10015
+ Resource context:
10016
+ ${parts.join("\n")}`;
10017
+ if (focus.content?.main) {
10018
+ resourceSection += `
10019
+
10020
+ Resource content:
10021
+ ---
10022
+ ${focus.content.main.slice(0, RESOURCE_CONTENT_CAP)}
10023
+ ---`;
10024
+ }
10025
+ const related = Object.entries(focus.content?.related ?? {});
10026
+ if (related.length > 0) {
10027
+ const blocks = related.map(([id, text]) => `[${id}]
10028
+ ${text.slice(0, RESOURCE_CONTENT_CAP)}`).join("\n\n");
10029
+ resourceSection += `
10030
+
10031
+ Related resource content:
10032
+ ---
10033
+ ${blocks}
10034
+ ---`;
10035
+ }
10017
10036
  }
10018
- if (parts.length > 0) {
10019
- graphContextSection = `
10037
+ if (mainResourceId) {
10038
+ const views = deriveViews(context.graph, mainResourceId, focalAnnotationId);
10039
+ const parts = [];
10040
+ if (views.connections.length > 0) {
10041
+ const connList = views.connections.map((c) => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(", ")})` : ""} ${idLabel(c.resourceId)}`).join(", ");
10042
+ parts.push(`- Connected resources: ${connList}`);
10043
+ }
10044
+ if (views.citedByCount > 0) {
10045
+ const citedNames = views.citedBy.map((c) => `${c.resourceName} ${idLabel(c.resourceId)}`).join(", ");
10046
+ parts.push(`- This resource is cited by ${views.citedByCount} other resource${views.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
10047
+ }
10048
+ if (views.siblingEntityTypes.length > 0) {
10049
+ parts.push(`- Related entity types in this document: ${views.siblingEntityTypes.join(", ")}`);
10050
+ }
10051
+ if (context.inferredRelationshipSummary) {
10052
+ parts.push(`- Relationship summary: ${context.inferredRelationshipSummary}`);
10053
+ }
10054
+ if (parts.length > 0) {
10055
+ graphSection = `
10020
10056
 
10021
10057
  Knowledge graph context:
10022
10058
  ${parts.join("\n")}`;
10059
+ }
10023
10060
  }
10024
10061
  }
10025
10062
  let semanticContextSection = "";
10026
10063
  const similar = context?.semanticContext?.similar ?? [];
10027
10064
  if (similar.length > 0) {
10028
- const lines = [...similar].sort((a, b) => b.score - a.score).slice(0, 3).map((m) => `- (${m.score.toFixed(2)}) ${m.text.slice(0, 240)}`);
10065
+ const lines = [...similar].sort((a, b) => b.score - a.score).slice(0, SEMANTIC_MATCH_LIMIT).map((m) => `- ${idLabel(m.resourceId, m.annotationId)} (${m.score.toFixed(2)}) ${m.text.slice(0, SEMANTIC_MATCH_CHARS)}`);
10029
10066
  semanticContextSection = `
10030
10067
 
10031
10068
  Related passages from the knowledge base:
10032
10069
  ${lines.join("\n")}`;
10033
10070
  }
10034
- const structureGuidance = finalMaxTokens >= 1e3 ? "organized into titled sections (## Section) with well-structured paragraphs" : "organized into well-structured paragraphs";
10035
- const prompt = `Generate a concise, informative resource about "${topic}".
10036
- ${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}
10037
- ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${graphContextSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
10071
+ let leadLine;
10072
+ if (task === "resource") {
10073
+ leadLine = `Generate a concise, informative resource about "${topic}".`;
10074
+ } else if (task === "answer") {
10075
+ leadLine = `Answer the following question directly and concisely, grounded in the provided context: "${topic}"`;
10076
+ } else if (task === "summary") {
10077
+ leadLine = `Write a concise summary of "${topic}".`;
10078
+ } else {
10079
+ logger2.warn("Unknown task \u2014 using it verbatim as the framing instruction", { task });
10080
+ leadLine = `${task}
10081
+ Topic: "${topic}"`;
10082
+ }
10083
+ const isPlainText = outputMediaType === "text/plain";
10084
+ let structureRequirement = "";
10085
+ let titleRequirement = "";
10086
+ if (structure === "sections") {
10087
+ structureRequirement = isPlainText ? "\n- Organize the content into titled sections with well-structured paragraphs" : "\n- Organize the content into titled sections (## Section) with well-structured paragraphs";
10088
+ if (!isPlainText) {
10089
+ titleRequirement = "\n- Start with a clear heading (# Title)";
10090
+ }
10091
+ } else if (structure === "prose") {
10092
+ structureRequirement = "\n- Write flowing, well-structured paragraphs with no section headings";
10093
+ } else if (structure === "chat") {
10094
+ structureRequirement = "\n- Structure the content as a conversational chat transcript \u2014 a sequence of alternating, speaker-labeled turns (no section headings)";
10095
+ } else if (structure) {
10096
+ logger2.warn("Unknown structure \u2014 passing it through as freeform organization guidance", { structure });
10097
+ structureRequirement = `
10098
+ - Organize the output as: ${structure}`;
10099
+ }
10100
+ const citeRequirement = cite ? "\n- Ground every claim in the provided context. Immediately after each claim, cite its source by emitting [[<id>]], where <id> is an id shown in square brackets in the context above (for a passage labeled [abc], emit [[abc]]). Cite only ids that appear in the context." : "";
10101
+ const formatRequirements = isPlainText ? `- Write the response as plain text \u2014 no formatting markup (no #, *, backticks, headings, or links)
10102
+ - Begin with the title on its own first line` : `- Use markdown formatting
10103
+ - Write the response as markdown`;
10104
+ const prompt = `${leadLine}
10105
+ ${userPrompt ? `Instruction: ${userPrompt}` : ""}
10106
+ ${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}${annotationSection}${contextSection}${resourceSection}${graphSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
10038
10107
 
10039
10108
  Requirements:
10040
- - Start with a clear heading (# Title)
10041
- - Aim for approximately ${finalMaxTokens} tokens of content, ${structureGuidance}
10042
- - Be factual and informative
10043
- - Use markdown formatting
10044
- - Write the response as markdown`;
10109
+ - Aim for approximately ${finalMaxTokens} tokens of content
10110
+ - Be factual and informative${structureRequirement}${titleRequirement}${citeRequirement}
10111
+ ${formatRequirements}`;
10045
10112
  const parseResponse = (response2) => {
10046
10113
  let content = response2.trim();
10047
10114
  if (content.startsWith("```markdown") || content.startsWith("```md")) {
@@ -10079,6 +10146,67 @@ Requirements:
10079
10146
  });
10080
10147
  return result;
10081
10148
  }
10149
+
10150
+ // src/workers/generation/citation-resolver.ts
10151
+ var CITATION_TOKEN = /\[\[([^\s[\]/]+)(?:\/([^\s[\]/]+))?\]\]/g;
10152
+ function collectContextResourceIds(context) {
10153
+ const ids = /* @__PURE__ */ new Set();
10154
+ if (!context) return ids;
10155
+ const { focus } = context;
10156
+ ids.add(focus.kind === "annotation" ? focus.sourceResource["@id"] : focus.resource["@id"]);
10157
+ for (const node of context.graph.nodes) {
10158
+ if (node.type === "resource") ids.add(node.id);
10159
+ }
10160
+ for (const m of context.semanticContext?.similar ?? []) ids.add(m.resourceId);
10161
+ if (focus.kind === "resource") {
10162
+ for (const id of Object.keys(focus.content?.related ?? {})) ids.add(id);
10163
+ }
10164
+ return ids;
10165
+ }
10166
+ function resolveCitationTokens(content, validResourceIds, logger2) {
10167
+ const citations = [];
10168
+ let clean = "";
10169
+ let last = 0;
10170
+ for (const match of content.matchAll(CITATION_TOKEN)) {
10171
+ const token = match[0];
10172
+ const citedResourceId = match[1];
10173
+ const annotationId = match[2];
10174
+ clean += content.slice(last, match.index).replace(/[ \t]+$/, "");
10175
+ last = match.index + token.length;
10176
+ if (!validResourceIds.has(citedResourceId)) {
10177
+ logger2.warn("Citation token references an id absent from the provided context \u2014 dropped", {
10178
+ resourceId: citedResourceId
10179
+ });
10180
+ continue;
10181
+ }
10182
+ const end = clean.length;
10183
+ let start = 0;
10184
+ for (let i = end - 2; i >= 0; i--) {
10185
+ const ch = clean[i];
10186
+ if (ch === "." || ch === "!" || ch === "?" || ch === "\n") {
10187
+ start = i + 1;
10188
+ break;
10189
+ }
10190
+ }
10191
+ while (start < end && /\s/.test(clean[start])) start++;
10192
+ const exact = clean.slice(start, end);
10193
+ if (exact.length === 0) {
10194
+ logger2.warn("Citation token has no preceding claim text \u2014 dropped", {
10195
+ resourceId: citedResourceId
10196
+ });
10197
+ continue;
10198
+ }
10199
+ citations.push({
10200
+ resourceId: citedResourceId,
10201
+ ...annotationId ? { annotationId } : {},
10202
+ start,
10203
+ end,
10204
+ exact
10205
+ });
10206
+ }
10207
+ clean += content.slice(last);
10208
+ return { content: clean, citations };
10209
+ }
10082
10210
  function toMatch(r) {
10083
10211
  return {
10084
10212
  exact: r.exact,
@@ -10360,6 +10488,13 @@ async function processTagJob(content, inferenceClient, params, userId, generator
10360
10488
  };
10361
10489
  }
10362
10490
  async function processGenerationJob(inferenceClient, params, onProgress, logger2) {
10491
+ const GENERATABLE_MEDIA_TYPES = ["text/markdown", "text/plain"];
10492
+ const outputMediaType = params.outputMediaType ?? "text/markdown";
10493
+ if (!GENERATABLE_MEDIA_TYPES.includes(outputMediaType)) {
10494
+ throw new Error(
10495
+ `Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(" or ")}.`
10496
+ );
10497
+ }
10363
10498
  onProgress(20, "Fetching context...", "fetching");
10364
10499
  const title = params.title ?? "Untitled";
10365
10500
  const entityTypes = (params.entityTypes ?? []).map(String);
@@ -10374,13 +10509,25 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger2
10374
10509
  params.context,
10375
10510
  params.temperature,
10376
10511
  params.maxTokens,
10377
- params.sourceLanguage
10512
+ params.sourceLanguage,
10513
+ outputMediaType,
10514
+ params.task,
10515
+ params.structure,
10516
+ params.cite
10378
10517
  );
10518
+ let content = generated.content;
10519
+ let citations = [];
10520
+ if (params.cite === true) {
10521
+ const resolved = resolveCitationTokens(content, collectContextResourceIds(params.context), logger2);
10522
+ content = resolved.content;
10523
+ citations = resolved.citations;
10524
+ }
10379
10525
  onProgress(85, "Creating resource...", "creating");
10380
10526
  return {
10381
- content: generated.content,
10527
+ content,
10382
10528
  title: generated.title ?? title,
10383
- format: "text/markdown",
10529
+ format: outputMediaType,
10530
+ citations,
10384
10531
  result: {
10385
10532
  resourceId: "",
10386
10533
  resourceName: generated.title ?? title
@@ -10600,6 +10747,34 @@ async function handleJobInner(adapter, config, job) {
10600
10747
  ...genParams.entityTypes && genParams.entityTypes.length > 0 ? { entityTypes: genParams.entityTypes } : {},
10601
10748
  generator
10602
10749
  });
10750
+ if (!genParams.referenceId) {
10751
+ const { annotation: provenanceRef } = assembleAnnotation(
10752
+ {
10753
+ motivation: "linking",
10754
+ target: { source: String(resourceId) },
10755
+ body: { type: "SpecificResource", source: String(newResourceId), purpose: "linking" }
10756
+ },
10757
+ generator
10758
+ );
10759
+ await emitEvent(session, "mark:create", { annotation: provenanceRef, userId, resourceId });
10760
+ }
10761
+ for (const citation of genResult.citations) {
10762
+ const { annotation: citationRef } = assembleAnnotation(
10763
+ {
10764
+ motivation: "linking",
10765
+ target: {
10766
+ source: String(newResourceId),
10767
+ selector: [
10768
+ { type: "TextPositionSelector", start: citation.start, end: citation.end },
10769
+ { type: "TextQuoteSelector", exact: citation.exact }
10770
+ ]
10771
+ },
10772
+ body: { type: "SpecificResource", source: citation.resourceId, purpose: "linking" }
10773
+ },
10774
+ generator
10775
+ );
10776
+ await emitEvent(session, "mark:create", { annotation: citationRef, userId, resourceId: newResourceId });
10777
+ }
10603
10778
  await emitEvent(session, "job:complete", {
10604
10779
  ...lifecycleBase,
10605
10780
  result: { resourceId: newResourceId, resourceName: genResult.title }
@@ -10610,75 +10785,8 @@ async function handleJobInner(adapter, config, job) {
10610
10785
  }
10611
10786
  }
10612
10787
 
10613
- // src/worker-main.ts
10614
- var import_rxjs3 = __toESM(require_cjs());
10615
- var ALL_JOB_TYPES = [
10616
- "reference-annotation",
10617
- "generation",
10618
- "highlight-annotation",
10619
- "assessment-annotation",
10620
- "comment-annotation",
10621
- "tag-annotation"
10622
- ];
10623
- var configPath = join(homedir(), ".semiontconfig");
10624
- var tomlReader = {
10625
- readIfExists: (p) => existsSync(p) ? readFileSync(p, "utf-8") : null
10626
- };
10627
- var envConfig = createTomlConfigLoader(
10628
- tomlReader,
10629
- configPath,
10630
- process.env
10631
- )(null, "local");
10632
- var workerInferenceMap = envConfig._metadata?.workers;
10633
- if (!workerInferenceMap || Object.keys(workerInferenceMap).length === 0) {
10634
- throw new Error(
10635
- 'No worker inference config found in ~/.semiontconfig. Add at least [environments.<env>.workers.default.inference] with type = "..." and model = "...".'
10636
- );
10637
- }
10638
- function resolveWorker(jobType) {
10639
- const specific = workerInferenceMap[jobType];
10640
- if (specific) return specific;
10641
- const def = workerInferenceMap["default"];
10642
- if (def) return def;
10643
- throw new Error(
10644
- `No inference config for worker '${jobType}' and no workers.default in ~/.semiontconfig.`
10645
- );
10646
- }
10647
- var backendPublicURL = envConfig.services?.backend?.publicURL;
10648
- if (!backendPublicURL) {
10649
- throw new Error("services.backend.publicURL is required in ~/.semiontconfig");
10650
- }
10651
- var backendBaseUrl = backendPublicURL;
10652
- var workerSecret = process.env.SEMIONT_WORKER_SECRET ?? "";
10653
- var healthPort = 9090;
10654
- var logger = createProcessLogger("worker");
10655
- function clientKey(w) {
10656
- return [w.type, w.model, w.apiKey ?? "", w.endpoint ?? "", w.baseURL ?? ""].join("|");
10657
- }
10658
- function toClientConfig(w) {
10659
- return {
10660
- type: w.type,
10661
- model: w.model,
10662
- ...w.endpoint && { endpoint: w.endpoint },
10663
- ...w.baseURL && { baseURL: w.baseURL },
10664
- ...w.apiKey && { apiKey: w.apiKey }
10665
- };
10666
- }
10667
- var groups = /* @__PURE__ */ new Map();
10668
- for (const jobType of ALL_JOB_TYPES) {
10669
- const inference = resolveWorker(jobType);
10670
- const key = clientKey(inference);
10671
- let group = groups.get(key);
10672
- if (!group) {
10673
- group = {
10674
- inference,
10675
- jobTypes: [],
10676
- client: createInferenceClient(toClientConfig(inference), logger)
10677
- };
10678
- groups.set(key, group);
10679
- }
10680
- group.jobTypes.push(jobType);
10681
- }
10788
+ // src/worker-runtime.ts
10789
+ var import_rxjs2 = __toESM(require_cjs());
10682
10790
  function parseBackendUrl(url) {
10683
10791
  const parsed = new URL(url);
10684
10792
  const protocol = parsed.protocol.replace(":", "") === "https" ? "https" : "http";
@@ -10686,29 +10794,32 @@ function parseBackendUrl(url) {
10686
10794
  const port = parsed.port ? Number(parsed.port) : protocol === "https" ? 443 : 80;
10687
10795
  return { protocol, host, port };
10688
10796
  }
10689
- async function authenticateAgent(provider, model) {
10690
- if (!workerSecret) {
10797
+ async function authenticateAgent(opts) {
10798
+ const { backendBaseUrl: backendBaseUrl2, workerSecret: workerSecret2, provider, model } = opts;
10799
+ if (!workerSecret2) {
10691
10800
  throw new Error("SEMIONT_WORKER_SECRET is required to authenticate worker agents");
10692
10801
  }
10693
- const response = await fetch(`${backendBaseUrl}/api/tokens/agent`, {
10802
+ const response = await fetch(`${backendBaseUrl2}/api/tokens/agent`, {
10694
10803
  method: "POST",
10695
10804
  headers: { "Content-Type": "application/json" },
10696
- body: JSON.stringify({ secret: workerSecret, provider, model })
10805
+ body: JSON.stringify({ secret: workerSecret2, provider, model })
10697
10806
  });
10698
10807
  if (!response.ok) {
10699
10808
  throw new Error(`Agent authentication failed for ${provider}:${model}: ${response.status} ${response.statusText}`);
10700
10809
  }
10701
10810
  return await response.json();
10702
10811
  }
10703
- async function startAgentWorker(group) {
10812
+ async function startAgentWorker(opts) {
10813
+ const { group, backendBaseUrl: backendBaseUrl2, workerSecret: workerSecret2, logger: logger2 } = opts;
10704
10814
  const { inference } = group;
10705
- const { protocol, host, port } = parseBackendUrl(backendBaseUrl);
10706
- const { token: initialToken, did } = await authenticateAgent(inference.type, inference.model);
10707
- const generator = softwareToAgent({
10708
- domain: host,
10815
+ const { protocol, host, port } = parseBackendUrl(backendBaseUrl2);
10816
+ const { token: initialToken, did } = await authenticateAgent({
10817
+ backendBaseUrl: backendBaseUrl2,
10818
+ workerSecret: workerSecret2,
10709
10819
  provider: inference.type,
10710
10820
  model: inference.model
10711
10821
  });
10822
+ const generator = didToAgent(did);
10712
10823
  const kbId = `agent-${inference.type}-${inference.model}-${hostname()}`;
10713
10824
  const endpoint = { kind: "http", host, port, protocol };
10714
10825
  const kb = {
@@ -10719,7 +10830,7 @@ async function startAgentWorker(group) {
10719
10830
  };
10720
10831
  const storage = new InMemorySessionStorage();
10721
10832
  setStoredSession(storage, kbId, { access: initialToken, refresh: "" });
10722
- const token$ = new import_rxjs3.BehaviorSubject(null);
10833
+ const token$ = new import_rxjs2.BehaviorSubject(null);
10723
10834
  let session;
10724
10835
  const transport = new HttpTransport({
10725
10836
  baseUrl: baseUrl(kbBackendUrl(endpoint)),
@@ -10735,10 +10846,15 @@ async function startAgentWorker(group) {
10735
10846
  token$,
10736
10847
  refresh: async () => {
10737
10848
  try {
10738
- const { token } = await authenticateAgent(inference.type, inference.model);
10849
+ const { token } = await authenticateAgent({
10850
+ backendBaseUrl: backendBaseUrl2,
10851
+ workerSecret: workerSecret2,
10852
+ provider: inference.type,
10853
+ model: inference.model
10854
+ });
10739
10855
  return token;
10740
10856
  } catch (err) {
10741
- logger.error("Agent token refresh failed", {
10857
+ logger2.error("Agent token refresh failed", {
10742
10858
  error: err instanceof Error ? err.message : String(err),
10743
10859
  agent: did
10744
10860
  });
@@ -10746,7 +10862,7 @@ async function startAgentWorker(group) {
10746
10862
  }
10747
10863
  },
10748
10864
  onError: (err) => {
10749
- logger.error("Session error", { code: err.code, message: err.message, agent: did });
10865
+ logger2.error("Session error", { code: err.code, message: err.message, agent: did });
10750
10866
  }
10751
10867
  });
10752
10868
  await session.ready;
@@ -10755,9 +10871,9 @@ async function startAgentWorker(group) {
10755
10871
  jobTypes: group.jobTypes,
10756
10872
  inferenceClient: group.client,
10757
10873
  generator,
10758
- logger
10874
+ logger: logger2
10759
10875
  });
10760
- logger.info("Agent ready", {
10876
+ logger2.info("Agent ready", {
10761
10877
  did,
10762
10878
  provider: inference.type,
10763
10879
  model: inference.model,
@@ -10771,6 +10887,73 @@ async function startAgentWorker(group) {
10771
10887
  }
10772
10888
  };
10773
10889
  }
10890
+ var ALL_JOB_TYPES = [
10891
+ "reference-annotation",
10892
+ "generation",
10893
+ "highlight-annotation",
10894
+ "assessment-annotation",
10895
+ "comment-annotation",
10896
+ "tag-annotation"
10897
+ ];
10898
+ var configPath = join(homedir(), ".semiontconfig");
10899
+ var tomlReader = {
10900
+ readIfExists: (p) => existsSync(p) ? readFileSync(p, "utf-8") : null
10901
+ };
10902
+ var envConfig = createTomlConfigLoader(
10903
+ tomlReader,
10904
+ configPath,
10905
+ process.env
10906
+ )(null, "local");
10907
+ var workerInferenceMap = envConfig._metadata?.workers;
10908
+ if (!workerInferenceMap || Object.keys(workerInferenceMap).length === 0) {
10909
+ throw new Error(
10910
+ 'No worker inference config found in ~/.semiontconfig. Add at least [environments.<env>.workers.default.inference] with type = "..." and model = "...".'
10911
+ );
10912
+ }
10913
+ function resolveWorker(jobType) {
10914
+ const specific = workerInferenceMap[jobType];
10915
+ if (specific) return specific;
10916
+ const def = workerInferenceMap["default"];
10917
+ if (def) return def;
10918
+ throw new Error(
10919
+ `No inference config for worker '${jobType}' and no workers.default in ~/.semiontconfig.`
10920
+ );
10921
+ }
10922
+ var backendPublicURL = envConfig.services?.backend?.publicURL;
10923
+ if (!backendPublicURL) {
10924
+ throw new Error("services.backend.publicURL is required in ~/.semiontconfig");
10925
+ }
10926
+ var backendBaseUrl = backendPublicURL;
10927
+ var workerSecret = process.env.SEMIONT_WORKER_SECRET ?? "";
10928
+ var healthPort = 9090;
10929
+ var logger = createProcessLogger("worker");
10930
+ function clientKey(w) {
10931
+ return [w.type, w.model, w.apiKey ?? "", w.endpoint ?? "", w.baseURL ?? ""].join("|");
10932
+ }
10933
+ function toClientConfig(w) {
10934
+ return {
10935
+ type: w.type,
10936
+ model: w.model,
10937
+ ...w.endpoint && { endpoint: w.endpoint },
10938
+ ...w.baseURL && { baseURL: w.baseURL },
10939
+ ...w.apiKey && { apiKey: w.apiKey }
10940
+ };
10941
+ }
10942
+ var groups = /* @__PURE__ */ new Map();
10943
+ for (const jobType of ALL_JOB_TYPES) {
10944
+ const inference = resolveWorker(jobType);
10945
+ const key = clientKey(inference);
10946
+ let group = groups.get(key);
10947
+ if (!group) {
10948
+ group = {
10949
+ inference,
10950
+ jobTypes: [],
10951
+ client: createInferenceClient(toClientConfig(inference), logger)
10952
+ };
10953
+ groups.set(key, group);
10954
+ }
10955
+ group.jobTypes.push(jobType);
10956
+ }
10774
10957
  async function main() {
10775
10958
  const { initObservabilityNode } = await import('@semiont/observability/node');
10776
10959
  initObservabilityNode({ serviceName: "semiont-worker" });
@@ -10782,7 +10965,11 @@ async function main() {
10782
10965
  jobTypes: g.jobTypes
10783
10966
  }))
10784
10967
  });
10785
- const workers = await Promise.all(Array.from(groups.values()).map(startAgentWorker));
10968
+ const workers = await Promise.all(
10969
+ Array.from(groups.values()).map(
10970
+ (group) => startAgentWorker({ group, backendBaseUrl, workerSecret, logger })
10971
+ )
10972
+ );
10786
10973
  const health = createServer((req, res) => {
10787
10974
  if (req.url === "/health") {
10788
10975
  res.writeHead(200, { "Content-Type": "application/json" });