@semiont/jobs 0.5.10 → 0.5.12

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, busRequest, getPrimaryMediaType, textExtractionOf, assembleAnnotation, reconcileSelector, didToAgent, getLocaleEnglishName, isArray, isObject, isString, deriveViews } 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;
@@ -9940,7 +9940,13 @@ Example output:
9940
9940
  function getLanguageName(locale) {
9941
9941
  return getLocaleEnglishName(locale) || locale;
9942
9942
  }
9943
- async function generateResourceFromTopic(topic, entityTypes, client, logger2, userPrompt, locale, context, temperature, maxTokens, sourceLanguage, outputMediaType = "text/markdown") {
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) {
9944
9950
  logger2.debug("Generating resource from topic", {
9945
9951
  topicPreview: topic.substring(0, 100),
9946
9952
  entityTypes,
@@ -9949,7 +9955,10 @@ async function generateResourceFromTopic(topic, entityTypes, client, logger2, us
9949
9955
  sourceLanguage,
9950
9956
  hasContext: !!context,
9951
9957
  temperature,
9952
- maxTokens
9958
+ maxTokens,
9959
+ outputMediaType,
9960
+ task,
9961
+ structure
9953
9962
  });
9954
9963
  const finalTemperature = temperature ?? 0.7;
9955
9964
  const finalMaxTokens = maxTokens ?? 500;
@@ -9970,7 +9979,7 @@ The source resource and embedded context are in ${getLanguageName(sourceLanguage
9970
9979
  if (focus.kind === "annotation") {
9971
9980
  const parts = [];
9972
9981
  parts.push(`- Annotation motivation: ${focus.annotation.motivation}`);
9973
- parts.push(`- Source resource: ${focus.sourceResource.name}`);
9982
+ parts.push(`- Source resource: ${focus.sourceResource.name} ${idLabel(mainResourceId)}`);
9974
9983
  const { motivation, body } = focus.annotation;
9975
9984
  if (motivation === "commenting" || motivation === "assessing") {
9976
9985
  const bodyItem = Array.isArray(body) ? body[0] : body;
@@ -9996,8 +10005,7 @@ ${after ? `${after}...` : ""}
9996
10005
  `;
9997
10006
  }
9998
10007
  } else {
9999
- const RESOURCE_CONTENT_CAP = 4e3;
10000
- const parts = [`- Resource: ${focus.resource.name}`];
10008
+ const parts = [`- Resource: ${focus.resource.name} ${idLabel(mainResourceId)}`];
10001
10009
  if (focus.summary) parts.push(`- Summary: ${focus.summary}`);
10002
10010
  if (focus.suggestedReferences && focus.suggestedReferences.length > 0) {
10003
10011
  parts.push(`- Suggested references: ${focus.suggestedReferences.join(", ")}`);
@@ -10030,11 +10038,11 @@ ${blocks}
10030
10038
  const views = deriveViews(context.graph, mainResourceId, focalAnnotationId);
10031
10039
  const parts = [];
10032
10040
  if (views.connections.length > 0) {
10033
- const connList = views.connections.map((c) => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(", ")})` : ""}`).join(", ");
10041
+ const connList = views.connections.map((c) => `${c.resourceName}${c.entityTypes.length ? ` (${c.entityTypes.join(", ")})` : ""} ${idLabel(c.resourceId)}`).join(", ");
10034
10042
  parts.push(`- Connected resources: ${connList}`);
10035
10043
  }
10036
10044
  if (views.citedByCount > 0) {
10037
- const citedNames = views.citedBy.map((c) => c.resourceName).join(", ");
10045
+ const citedNames = views.citedBy.map((c) => `${c.resourceName} ${idLabel(c.resourceId)}`).join(", ");
10038
10046
  parts.push(`- This resource is cited by ${views.citedByCount} other resource${views.citedByCount > 1 ? "s" : ""}${citedNames ? `: ${citedNames}` : ""}`);
10039
10047
  }
10040
10048
  if (views.siblingEntityTypes.length > 0) {
@@ -10054,25 +10062,52 @@ ${parts.join("\n")}`;
10054
10062
  let semanticContextSection = "";
10055
10063
  const similar = context?.semanticContext?.similar ?? [];
10056
10064
  if (similar.length > 0) {
10057
- 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)}`);
10058
10066
  semanticContextSection = `
10059
10067
 
10060
10068
  Related passages from the knowledge base:
10061
10069
  ${lines.join("\n")}`;
10070
+ }
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}"`;
10062
10082
  }
10063
10083
  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";
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." : "";
10065
10101
  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
10102
+ - Begin with the title on its own first line` : `- Use markdown formatting
10068
10103
  - Write the response as markdown`;
10069
- const prompt = `Generate a concise, informative resource about "${topic}".
10070
- ${entityTypes.length > 0 ? `Focus on these entity types: ${entityTypes.join(", ")}.` : ""}
10071
- ${userPrompt ? `Additional context: ${userPrompt}` : ""}${annotationSection}${contextSection}${resourceSection}${graphSection}${semanticContextSection}${sourceLanguageInstruction}${languageInstruction}
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}
10072
10107
 
10073
10108
  Requirements:
10074
- - Aim for approximately ${finalMaxTokens} tokens of content, ${structureGuidance}
10075
- - Be factual and informative
10109
+ - Aim for approximately ${finalMaxTokens} tokens of content
10110
+ - Be factual and informative${structureRequirement}${titleRequirement}${citeRequirement}
10076
10111
  ${formatRequirements}`;
10077
10112
  const parseResponse = (response2) => {
10078
10113
  let content = response2.trim();
@@ -10111,6 +10146,67 @@ ${formatRequirements}`;
10111
10146
  });
10112
10147
  return result;
10113
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
+ }
10114
10210
  function toMatch(r) {
10115
10211
  return {
10116
10212
  exact: r.exact,
@@ -10399,10 +10495,9 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger2
10399
10495
  `Unsupported outputMediaType for generation: ${outputMediaType}. Generation produces ${GENERATABLE_MEDIA_TYPES.join(" or ")}.`
10400
10496
  );
10401
10497
  }
10402
- onProgress(20, "Fetching context...", "fetching");
10403
10498
  const title = params.title ?? "Untitled";
10404
10499
  const entityTypes = (params.entityTypes ?? []).map(String);
10405
- onProgress(40, "Generating resource...", "generating");
10500
+ onProgress(5, "Generating resource...", "generating");
10406
10501
  const generated = await generateResourceFromTopic(
10407
10502
  title,
10408
10503
  entityTypes,
@@ -10414,13 +10509,24 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger2
10414
10509
  params.temperature,
10415
10510
  params.maxTokens,
10416
10511
  params.sourceLanguage,
10417
- outputMediaType
10512
+ outputMediaType,
10513
+ params.task,
10514
+ params.structure,
10515
+ params.cite
10418
10516
  );
10419
- onProgress(85, "Creating resource...", "creating");
10517
+ let content = generated.content;
10518
+ let citations = [];
10519
+ if (params.cite === true) {
10520
+ const resolved = resolveCitationTokens(content, collectContextResourceIds(params.context), logger2);
10521
+ content = resolved.content;
10522
+ citations = resolved.citations;
10523
+ }
10524
+ onProgress(95, "Creating resource...", "creating");
10420
10525
  return {
10421
- content: generated.content,
10526
+ content,
10422
10527
  title: generated.title ?? title,
10423
10528
  format: outputMediaType,
10529
+ citations,
10424
10530
  result: {
10425
10531
  resourceId: "",
10426
10532
  resourceName: generated.title ?? title
@@ -10651,6 +10757,23 @@ async function handleJobInner(adapter, config, job) {
10651
10757
  );
10652
10758
  await emitEvent(session, "mark:create", { annotation: provenanceRef, userId, resourceId });
10653
10759
  }
10760
+ for (const citation of genResult.citations) {
10761
+ const { annotation: citationRef } = assembleAnnotation(
10762
+ {
10763
+ motivation: "linking",
10764
+ target: {
10765
+ source: String(newResourceId),
10766
+ selector: [
10767
+ { type: "TextPositionSelector", start: citation.start, end: citation.end },
10768
+ { type: "TextQuoteSelector", exact: citation.exact }
10769
+ ]
10770
+ },
10771
+ body: { type: "SpecificResource", source: citation.resourceId, purpose: "linking" }
10772
+ },
10773
+ generator
10774
+ );
10775
+ await emitEvent(session, "mark:create", { annotation: citationRef, userId, resourceId: newResourceId });
10776
+ }
10654
10777
  await emitEvent(session, "job:complete", {
10655
10778
  ...lifecycleBase,
10656
10779
  result: { resourceId: newResourceId, resourceName: genResult.title }
@@ -10661,75 +10784,8 @@ async function handleJobInner(adapter, config, job) {
10661
10784
  }
10662
10785
  }
10663
10786
 
10664
- // src/worker-main.ts
10787
+ // src/worker-runtime.ts
10665
10788
  var import_rxjs2 = __toESM(require_cjs());
10666
- var ALL_JOB_TYPES = [
10667
- "reference-annotation",
10668
- "generation",
10669
- "highlight-annotation",
10670
- "assessment-annotation",
10671
- "comment-annotation",
10672
- "tag-annotation"
10673
- ];
10674
- var configPath = join(homedir(), ".semiontconfig");
10675
- var tomlReader = {
10676
- readIfExists: (p) => existsSync(p) ? readFileSync(p, "utf-8") : null
10677
- };
10678
- var envConfig = createTomlConfigLoader(
10679
- tomlReader,
10680
- configPath,
10681
- process.env
10682
- )(null, "local");
10683
- var workerInferenceMap = envConfig._metadata?.workers;
10684
- if (!workerInferenceMap || Object.keys(workerInferenceMap).length === 0) {
10685
- throw new Error(
10686
- 'No worker inference config found in ~/.semiontconfig. Add at least [environments.<env>.workers.default.inference] with type = "..." and model = "...".'
10687
- );
10688
- }
10689
- function resolveWorker(jobType) {
10690
- const specific = workerInferenceMap[jobType];
10691
- if (specific) return specific;
10692
- const def = workerInferenceMap["default"];
10693
- if (def) return def;
10694
- throw new Error(
10695
- `No inference config for worker '${jobType}' and no workers.default in ~/.semiontconfig.`
10696
- );
10697
- }
10698
- var backendPublicURL = envConfig.services?.backend?.publicURL;
10699
- if (!backendPublicURL) {
10700
- throw new Error("services.backend.publicURL is required in ~/.semiontconfig");
10701
- }
10702
- var backendBaseUrl = backendPublicURL;
10703
- var workerSecret = process.env.SEMIONT_WORKER_SECRET ?? "";
10704
- var healthPort = 9090;
10705
- var logger = createProcessLogger("worker");
10706
- function clientKey(w) {
10707
- return [w.type, w.model, w.apiKey ?? "", w.endpoint ?? "", w.baseURL ?? ""].join("|");
10708
- }
10709
- function toClientConfig(w) {
10710
- return {
10711
- type: w.type,
10712
- model: w.model,
10713
- ...w.endpoint && { endpoint: w.endpoint },
10714
- ...w.baseURL && { baseURL: w.baseURL },
10715
- ...w.apiKey && { apiKey: w.apiKey }
10716
- };
10717
- }
10718
- var groups = /* @__PURE__ */ new Map();
10719
- for (const jobType of ALL_JOB_TYPES) {
10720
- const inference = resolveWorker(jobType);
10721
- const key = clientKey(inference);
10722
- let group = groups.get(key);
10723
- if (!group) {
10724
- group = {
10725
- inference,
10726
- jobTypes: [],
10727
- client: createInferenceClient(toClientConfig(inference), logger)
10728
- };
10729
- groups.set(key, group);
10730
- }
10731
- group.jobTypes.push(jobType);
10732
- }
10733
10789
  function parseBackendUrl(url) {
10734
10790
  const parsed = new URL(url);
10735
10791
  const protocol = parsed.protocol.replace(":", "") === "https" ? "https" : "http";
@@ -10737,29 +10793,32 @@ function parseBackendUrl(url) {
10737
10793
  const port = parsed.port ? Number(parsed.port) : protocol === "https" ? 443 : 80;
10738
10794
  return { protocol, host, port };
10739
10795
  }
10740
- async function authenticateAgent(provider, model) {
10741
- if (!workerSecret) {
10796
+ async function authenticateAgent(opts) {
10797
+ const { backendBaseUrl: backendBaseUrl2, workerSecret: workerSecret2, provider, model } = opts;
10798
+ if (!workerSecret2) {
10742
10799
  throw new Error("SEMIONT_WORKER_SECRET is required to authenticate worker agents");
10743
10800
  }
10744
- const response = await fetch(`${backendBaseUrl}/api/tokens/agent`, {
10801
+ const response = await fetch(`${backendBaseUrl2}/api/tokens/agent`, {
10745
10802
  method: "POST",
10746
10803
  headers: { "Content-Type": "application/json" },
10747
- body: JSON.stringify({ secret: workerSecret, provider, model })
10804
+ body: JSON.stringify({ secret: workerSecret2, provider, model })
10748
10805
  });
10749
10806
  if (!response.ok) {
10750
10807
  throw new Error(`Agent authentication failed for ${provider}:${model}: ${response.status} ${response.statusText}`);
10751
10808
  }
10752
10809
  return await response.json();
10753
10810
  }
10754
- async function startAgentWorker(group) {
10811
+ async function startAgentWorker(opts) {
10812
+ const { group, backendBaseUrl: backendBaseUrl2, workerSecret: workerSecret2, logger: logger2 } = opts;
10755
10813
  const { inference } = group;
10756
- const { protocol, host, port } = parseBackendUrl(backendBaseUrl);
10757
- const { token: initialToken, did } = await authenticateAgent(inference.type, inference.model);
10758
- const generator = softwareToAgent({
10759
- domain: host,
10814
+ const { protocol, host, port } = parseBackendUrl(backendBaseUrl2);
10815
+ const { token: initialToken, did } = await authenticateAgent({
10816
+ backendBaseUrl: backendBaseUrl2,
10817
+ workerSecret: workerSecret2,
10760
10818
  provider: inference.type,
10761
10819
  model: inference.model
10762
10820
  });
10821
+ const generator = didToAgent(did);
10763
10822
  const kbId = `agent-${inference.type}-${inference.model}-${hostname()}`;
10764
10823
  const endpoint = { kind: "http", host, port, protocol };
10765
10824
  const kb = {
@@ -10786,10 +10845,15 @@ async function startAgentWorker(group) {
10786
10845
  token$,
10787
10846
  refresh: async () => {
10788
10847
  try {
10789
- const { token } = await authenticateAgent(inference.type, inference.model);
10848
+ const { token } = await authenticateAgent({
10849
+ backendBaseUrl: backendBaseUrl2,
10850
+ workerSecret: workerSecret2,
10851
+ provider: inference.type,
10852
+ model: inference.model
10853
+ });
10790
10854
  return token;
10791
10855
  } catch (err) {
10792
- logger.error("Agent token refresh failed", {
10856
+ logger2.error("Agent token refresh failed", {
10793
10857
  error: err instanceof Error ? err.message : String(err),
10794
10858
  agent: did
10795
10859
  });
@@ -10797,7 +10861,7 @@ async function startAgentWorker(group) {
10797
10861
  }
10798
10862
  },
10799
10863
  onError: (err) => {
10800
- logger.error("Session error", { code: err.code, message: err.message, agent: did });
10864
+ logger2.error("Session error", { code: err.code, message: err.message, agent: did });
10801
10865
  }
10802
10866
  });
10803
10867
  await session.ready;
@@ -10806,9 +10870,9 @@ async function startAgentWorker(group) {
10806
10870
  jobTypes: group.jobTypes,
10807
10871
  inferenceClient: group.client,
10808
10872
  generator,
10809
- logger
10873
+ logger: logger2
10810
10874
  });
10811
- logger.info("Agent ready", {
10875
+ logger2.info("Agent ready", {
10812
10876
  did,
10813
10877
  provider: inference.type,
10814
10878
  model: inference.model,
@@ -10822,6 +10886,73 @@ async function startAgentWorker(group) {
10822
10886
  }
10823
10887
  };
10824
10888
  }
10889
+ var ALL_JOB_TYPES = [
10890
+ "reference-annotation",
10891
+ "generation",
10892
+ "highlight-annotation",
10893
+ "assessment-annotation",
10894
+ "comment-annotation",
10895
+ "tag-annotation"
10896
+ ];
10897
+ var configPath = join(homedir(), ".semiontconfig");
10898
+ var tomlReader = {
10899
+ readIfExists: (p) => existsSync(p) ? readFileSync(p, "utf-8") : null
10900
+ };
10901
+ var envConfig = createTomlConfigLoader(
10902
+ tomlReader,
10903
+ configPath,
10904
+ process.env
10905
+ )(null, "local");
10906
+ var workerInferenceMap = envConfig._metadata?.workers;
10907
+ if (!workerInferenceMap || Object.keys(workerInferenceMap).length === 0) {
10908
+ throw new Error(
10909
+ 'No worker inference config found in ~/.semiontconfig. Add at least [environments.<env>.workers.default.inference] with type = "..." and model = "...".'
10910
+ );
10911
+ }
10912
+ function resolveWorker(jobType) {
10913
+ const specific = workerInferenceMap[jobType];
10914
+ if (specific) return specific;
10915
+ const def = workerInferenceMap["default"];
10916
+ if (def) return def;
10917
+ throw new Error(
10918
+ `No inference config for worker '${jobType}' and no workers.default in ~/.semiontconfig.`
10919
+ );
10920
+ }
10921
+ var backendPublicURL = envConfig.services?.backend?.publicURL;
10922
+ if (!backendPublicURL) {
10923
+ throw new Error("services.backend.publicURL is required in ~/.semiontconfig");
10924
+ }
10925
+ var backendBaseUrl = backendPublicURL;
10926
+ var workerSecret = process.env.SEMIONT_WORKER_SECRET ?? "";
10927
+ var healthPort = 9090;
10928
+ var logger = createProcessLogger("worker");
10929
+ function clientKey(w) {
10930
+ return [w.type, w.model, w.apiKey ?? "", w.endpoint ?? "", w.baseURL ?? ""].join("|");
10931
+ }
10932
+ function toClientConfig(w) {
10933
+ return {
10934
+ type: w.type,
10935
+ model: w.model,
10936
+ ...w.endpoint && { endpoint: w.endpoint },
10937
+ ...w.baseURL && { baseURL: w.baseURL },
10938
+ ...w.apiKey && { apiKey: w.apiKey }
10939
+ };
10940
+ }
10941
+ var groups = /* @__PURE__ */ new Map();
10942
+ for (const jobType of ALL_JOB_TYPES) {
10943
+ const inference = resolveWorker(jobType);
10944
+ const key = clientKey(inference);
10945
+ let group = groups.get(key);
10946
+ if (!group) {
10947
+ group = {
10948
+ inference,
10949
+ jobTypes: [],
10950
+ client: createInferenceClient(toClientConfig(inference), logger)
10951
+ };
10952
+ groups.set(key, group);
10953
+ }
10954
+ group.jobTypes.push(jobType);
10955
+ }
10825
10956
  async function main() {
10826
10957
  const { initObservabilityNode } = await import('@semiont/observability/node');
10827
10958
  initObservabilityNode({ serviceName: "semiont-worker" });
@@ -10833,7 +10964,11 @@ async function main() {
10833
10964
  jobTypes: g.jobTypes
10834
10965
  }))
10835
10966
  });
10836
- const workers = await Promise.all(Array.from(groups.values()).map(startAgentWorker));
10967
+ const workers = await Promise.all(
10968
+ Array.from(groups.values()).map(
10969
+ (group) => startAgentWorker({ group, backendBaseUrl, workerSecret, logger })
10970
+ )
10971
+ );
10837
10972
  const health = createServer((req, res) => {
10838
10973
  if (req.url === "/health") {
10839
10974
  res.writeHead(200, { "Content-Type": "application/json" });