@semiont/jobs 0.5.39 → 0.6.1

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 { replyChannelsFor, createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, isObject, isString, isNumber, RETRY_RULES, busRequest, isArray, resourceId, getPrimaryMediaType, isGenerationJobParams, capabilitiesOf, assembleAnnotation, findClaimSpan, textSourceOf, yieldsGeometryOf, decodeRepresentation, GENERATABLE_MEDIA_TYPES, BusRequestError, locate, createFragmentSelector, deriveViews, annotationId, estimateTokens, reconcileSelector, getLocaleEnglishName, cutChunk, chunkText } from '@semiont/core';
1
+ import { replyChannelsFor, createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, serviceAccountToken, isTransientFetchError, isObject, isString, isNumber, RETRY_RULES, resourceId, getPrimaryMediaType, isGenerationJobParams, capabilitiesOf, assembleAnnotation, findClaimSpan, busRequest, BusRequestError, isArray, textSourceOf, yieldsGeometryOf, decodeRepresentation, GENERATABLE_MEDIA_TYPES, locate, createFragmentSelector, deriveViews, annotationId, estimateTokens, reconcileSelector, getLocaleEnglishName, cutChunk, chunkText } from '@semiont/core';
2
2
  import { archivistContentReads, extractPdfTextLayer, withinByteBudget, MAX_PDF_BYTES } from '@semiont/content';
3
3
  import { withSpan, SpanKind, recordJobOutcome, recordAnchorOutcome, recordDetectionCall } from '@semiont/observability';
4
4
  import { createInferenceClient, StructuredReadError } from '@semiont/inference';
@@ -9223,13 +9223,15 @@ function readUnitCursors(raw, completedUnits) {
9223
9223
  }
9224
9224
  function createJobClaimAdapter(options) {
9225
9225
  const { bus, jobTypes } = options;
9226
- const requestBus = bus;
9227
9226
  const activeJob$ = new import_rxjs.BehaviorSubject(null);
9228
9227
  const isProcessing$ = new import_rxjs.BehaviorSubject(false);
9229
9228
  const jobsCompleted$ = new import_rxjs.BehaviorSubject(0);
9230
9229
  const errors$ = new import_rxjs.Subject();
9231
- let jobSubscription = null;
9230
+ const refused$ = new import_rxjs.Subject();
9231
+ let subscriptions = [];
9232
9232
  let started = false;
9233
+ let claimInFlight = false;
9234
+ let wakePending = false;
9233
9235
  let lastQueuedEventAt = null;
9234
9236
  let lastClaimAt = null;
9235
9237
  let lastFinishedAt = null;
@@ -9237,61 +9239,98 @@ function createJobClaimAdapter(options) {
9237
9239
  let activeSince = null;
9238
9240
  const iso = (t) => t === null ? null : new Date(t).toISOString();
9239
9241
  const claimNext = async () => {
9242
+ let record;
9240
9243
  try {
9241
- const job = await busRequest(requestBus, "job:claim", { types: jobTypes }, 1e4);
9242
- if (!isString(job.metadata?.id) || !isString(job.metadata?.type)) return null;
9243
- const completedUnits = isArray(job.metadata?.completedUnits) ? job.metadata.completedUnits.filter(isString) : [];
9244
- const unitCursors = readUnitCursors(job.metadata?.unitCursors, completedUnits);
9245
- const params = job.params ?? {};
9246
- return {
9247
- jobId: job.metadata.id,
9248
- type: job.metadata.type,
9244
+ record = await busRequest(bus, "job:claim", { types: jobTypes }, 1e4);
9245
+ } catch (error) {
9246
+ if (error instanceof BusRequestError) {
9247
+ if (error.code === "bus.none-pending") return { declined: true };
9248
+ return { refused: { code: error.code, message: error.message } };
9249
+ }
9250
+ return { refused: { code: null, message: error instanceof Error ? error.message : String(error) } };
9251
+ }
9252
+ if (!isString(record.metadata?.id) || !isString(record.metadata?.type)) {
9253
+ return { refused: { code: null, message: "claimed record carries no job id or type" } };
9254
+ }
9255
+ const completedUnits = isArray(record.metadata?.completedUnits) ? record.metadata.completedUnits.filter(isString) : [];
9256
+ const unitCursors = readUnitCursors(record.metadata?.unitCursors, completedUnits);
9257
+ const params = record.params ?? {};
9258
+ return {
9259
+ job: {
9260
+ jobId: record.metadata.id,
9261
+ type: record.metadata.type,
9249
9262
  resourceId: isString(params.resourceId) ? params.resourceId : "",
9250
- userId: job.metadata?.userId ?? "",
9263
+ userId: record.metadata?.userId ?? "",
9251
9264
  params,
9252
9265
  completedUnits,
9253
9266
  unitCursors,
9254
9267
  // Absent or malformed metadata reads as "no budget left" — a worker
9255
9268
  // that cannot see the budget must not claim a retry is coming.
9256
- retryCount: isNumber(job.metadata?.retryCount) ? job.metadata.retryCount : 0,
9257
- maxRetries: isNumber(job.metadata?.maxRetries) ? job.metadata.maxRetries : 0
9258
- };
9259
- } catch {
9260
- return null;
9269
+ retryCount: isNumber(record.metadata?.retryCount) ? record.metadata.retryCount : 0,
9270
+ maxRetries: isNumber(record.metadata?.maxRetries) ? record.metadata.maxRetries : 0
9271
+ }
9272
+ };
9273
+ };
9274
+ const pull = () => {
9275
+ if (!started) return;
9276
+ if (activeJob$.getValue() !== null) return;
9277
+ if (claimInFlight) {
9278
+ wakePending = true;
9279
+ return;
9261
9280
  }
9281
+ claimInFlight = true;
9282
+ wakePending = false;
9283
+ isProcessing$.next(true);
9284
+ void claimNext().then((outcome) => {
9285
+ claimInFlight = false;
9286
+ if ("job" in outcome) {
9287
+ const now = Date.now();
9288
+ lastClaimAt = now;
9289
+ lastActivityAt = now;
9290
+ activeSince = now;
9291
+ wakePending = false;
9292
+ activeJob$.next(outcome.job);
9293
+ return;
9294
+ }
9295
+ if ("refused" in outcome) refused$.next(outcome.refused);
9296
+ isProcessing$.next(false);
9297
+ if (wakePending) {
9298
+ wakePending = false;
9299
+ pull();
9300
+ }
9301
+ });
9262
9302
  };
9263
9303
  return {
9264
9304
  activeJob$: activeJob$.asObservable(),
9265
9305
  isProcessing$: isProcessing$.asObservable(),
9266
9306
  jobsCompleted$: jobsCompleted$.asObservable(),
9267
9307
  errors$: errors$.asObservable(),
9308
+ refused$: refused$.asObservable(),
9268
9309
  start: () => {
9269
9310
  if (started) return;
9270
9311
  started = true;
9271
- jobSubscription = bus.stream("job:queued").subscribe((event) => {
9272
- lastQueuedEventAt = Date.now();
9273
- const jobType = event.jobType;
9274
- if (jobTypes.length > 0 && !jobTypes.includes(jobType)) return;
9275
- if (isProcessing$.getValue()) return;
9276
- isProcessing$.next(true);
9277
- claimNext().then((job) => {
9278
- if (job) {
9279
- const now = Date.now();
9280
- lastClaimAt = now;
9281
- lastActivityAt = now;
9282
- activeSince = now;
9283
- activeJob$.next(job);
9284
- } else {
9285
- isProcessing$.next(false);
9286
- }
9287
- }).catch(() => {
9288
- isProcessing$.next(false);
9289
- });
9290
- });
9312
+ subscriptions.push(
9313
+ bus.stream("job:queued").subscribe((event) => {
9314
+ lastQueuedEventAt = Date.now();
9315
+ if (jobTypes.length > 0 && !jobTypes.includes(event.jobType)) return;
9316
+ pull();
9317
+ })
9318
+ );
9319
+ let observed = false;
9320
+ let wasOpen = false;
9321
+ subscriptions.push(
9322
+ bus.state$.subscribe((state) => {
9323
+ const open = state === "open";
9324
+ if (observed && open && !wasOpen) pull();
9325
+ observed = true;
9326
+ wasOpen = open;
9327
+ })
9328
+ );
9329
+ if (wasOpen) pull();
9291
9330
  },
9292
9331
  stop: () => {
9293
- jobSubscription?.unsubscribe();
9294
- jobSubscription = null;
9332
+ for (const s of subscriptions) s.unsubscribe();
9333
+ subscriptions = [];
9295
9334
  started = false;
9296
9335
  },
9297
9336
  completeJob: () => {
@@ -9302,6 +9341,7 @@ function createJobClaimAdapter(options) {
9302
9341
  activeJob$.next(null);
9303
9342
  isProcessing$.next(false);
9304
9343
  jobsCompleted$.next(jobsCompleted$.getValue() + 1);
9344
+ pull();
9305
9345
  },
9306
9346
  failJob: (jid, error) => {
9307
9347
  const now = Date.now();
@@ -9311,6 +9351,7 @@ function createJobClaimAdapter(options) {
9311
9351
  activeJob$.next(null);
9312
9352
  isProcessing$.next(false);
9313
9353
  errors$.next({ jobId: jid, error });
9354
+ pull();
9314
9355
  },
9315
9356
  vitals: () => {
9316
9357
  const active = activeJob$.getValue();
@@ -9327,13 +9368,14 @@ function createJobClaimAdapter(options) {
9327
9368
  lastActivityAt = Date.now();
9328
9369
  },
9329
9370
  dispose: () => {
9330
- jobSubscription?.unsubscribe();
9331
- jobSubscription = null;
9371
+ for (const s of subscriptions) s.unsubscribe();
9372
+ subscriptions = [];
9332
9373
  started = false;
9333
9374
  activeJob$.complete();
9334
9375
  isProcessing$.complete();
9335
9376
  jobsCompleted$.complete();
9336
9377
  errors$.complete();
9378
+ refused$.complete();
9337
9379
  }
9338
9380
  };
9339
9381
  }
@@ -10837,7 +10879,7 @@ function makeSpanDeduper() {
10837
10879
  return out;
10838
10880
  };
10839
10881
  }
10840
- function buildTextAnnotation(content, resourceId, userId, generator, motivation, match, body) {
10882
+ function buildTextAnnotation(content, resourceId, generator, motivation, match, body) {
10841
10883
  if (content.substring(match.start, match.end) !== match.exact) {
10842
10884
  throw new Error(
10843
10885
  `buildTextAnnotation invariant: content.substring(${match.start}, ${match.end}) !== exact for resource ${resourceId}, motivation ${motivation}`
@@ -10859,16 +10901,12 @@ function buildTextAnnotation(content, resourceId, userId, generator, motivation,
10859
10901
  );
10860
10902
  }
10861
10903
  }
10862
- const creator = didToAgent(userId);
10863
- const wasAttributedTo = creator["@id"] === generator["@id"] ? [generator] : [creator, generator];
10864
10904
  return {
10865
10905
  "@context": "http://www.w3.org/ns/anno.jsonld",
10866
10906
  "type": "Annotation",
10867
10907
  "id": annotationIdFor({ resourceId, motivation, anchor: spanAnchor(match), body }),
10868
10908
  motivation,
10869
- creator,
10870
10909
  generator,
10871
- wasAttributedTo,
10872
10910
  created: (/* @__PURE__ */ new Date()).toISOString(),
10873
10911
  target: {
10874
10912
  type: "SpecificResource",
@@ -10886,7 +10924,7 @@ function buildTextAnnotation(content, resourceId, userId, generator, motivation,
10886
10924
  ...body !== void 0 ? { body } : {}
10887
10925
  };
10888
10926
  }
10889
- function buildPdfAnnotation(anchored, resourceId, userId, generator, motivation, match, body) {
10927
+ function buildPdfAnnotation(anchored, resourceId, generator, motivation, match, body) {
10890
10928
  const { rects, overlap } = locate(anchored, match.start, match.end);
10891
10929
  const coveredText = overlap.length ? anchored.text.substring(
10892
10930
  Math.min(...overlap.map((i) => i.start)),
@@ -10903,16 +10941,12 @@ function buildPdfAnnotation(anchored, resourceId, userId, generator, motivation,
10903
10941
  `buildPdfAnnotation invariant: covered text does not contain exact for resource ${resourceId}, motivation ${motivation}`
10904
10942
  );
10905
10943
  }
10906
- const creator = didToAgent(userId);
10907
- const wasAttributedTo = creator["@id"] === generator["@id"] ? [generator] : [creator, generator];
10908
10944
  return {
10909
10945
  "@context": "http://www.w3.org/ns/anno.jsonld",
10910
10946
  "type": "Annotation",
10911
10947
  "id": annotationIdFor({ resourceId, motivation, anchor: spanAnchor(match), body }),
10912
10948
  motivation,
10913
- creator,
10914
10949
  generator,
10915
- wasAttributedTo,
10916
10950
  created: (/* @__PURE__ */ new Date()).toISOString(),
10917
10951
  target: {
10918
10952
  type: "SpecificResource",
@@ -11399,7 +11433,7 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger2
11399
11433
  }
11400
11434
 
11401
11435
  // src/workers/detection/prepare-detection.ts
11402
- async function prepareDetection(mediaType, content, resourceId, userId, generator, consult) {
11436
+ async function prepareDetection(mediaType, content, resourceId, generator, consult) {
11403
11437
  if (textSourceOf(mediaType) === "none") return { declined: "no-extractor" };
11404
11438
  if (yieldsGeometryOf(mediaType)) {
11405
11439
  const answer = await consult(resourceId);
@@ -11409,7 +11443,7 @@ async function prepareDetection(mediaType, content, resourceId, userId, generato
11409
11443
  const anchored = { text: answer.text, items: answer.items ?? [] };
11410
11444
  return {
11411
11445
  text: answer.text,
11412
- buildAnnotation: (motivation, match, body) => buildPdfAnnotation(anchored, resourceId, userId, generator, motivation, match, body)
11446
+ buildAnnotation: (motivation, match, body) => buildPdfAnnotation(anchored, resourceId, generator, motivation, match, body)
11413
11447
  };
11414
11448
  }
11415
11449
  // The Smelter's own decline (encrypted, corrupt) — passed through by name.
@@ -11435,7 +11469,7 @@ async function prepareDetection(mediaType, content, resourceId, userId, generato
11435
11469
  if (!text.trim()) return { declined: "empty" };
11436
11470
  return {
11437
11471
  text,
11438
- buildAnnotation: (motivation, match, body) => buildTextAnnotation(text, resourceId, userId, generator, motivation, match, body)
11472
+ buildAnnotation: (motivation, match, body) => buildTextAnnotation(text, resourceId, generator, motivation, match, body)
11439
11473
  };
11440
11474
  }
11441
11475
  function referenceIdOf(job) {
@@ -11451,13 +11485,15 @@ function referenceIdOf(job) {
11451
11485
  return typeof ref === "string" ? ref : void 0;
11452
11486
  }
11453
11487
  var MARK_COMMIT_TIMEOUT_MS = 6e4;
11454
- async function commitAnnotations(session, resourceId, annotations) {
11488
+ async function commitAnnotations(session, resourceId, annotations, jobId) {
11455
11489
  if (annotations.length === 0) return void 0;
11456
11490
  try {
11457
11491
  await busRequest(
11458
11492
  session.client.transport.actor,
11459
11493
  "mark:commit",
11460
- { resourceId, annotations },
11494
+ // The batch cites the job it fulfils. Who asked for these annotations is
11495
+ // derived downstream from that job's own events; the worker never says.
11496
+ { resourceId, annotations, jobId },
11461
11497
  MARK_COMMIT_TIMEOUT_MS
11462
11498
  );
11463
11499
  return "acknowledged";
@@ -11497,6 +11533,19 @@ function startWorkerProcess(config) {
11497
11533
  bus: httpTransport.actor,
11498
11534
  jobTypes: config.jobTypes
11499
11535
  });
11536
+ const exit = config.exit ?? ((code) => process.exit(code));
11537
+ adapter.refused$.subscribe(({ code, message }) => {
11538
+ if (code === "bus.unauthorized") {
11539
+ logger2.error("Claim refused: this worker is not authorized to claim jobs \u2014 exiting for restart", {
11540
+ code,
11541
+ message,
11542
+ jobTypes: config.jobTypes
11543
+ });
11544
+ exit(1);
11545
+ return;
11546
+ }
11547
+ logger2.warn("Claim declined; parked until the next wake-up", { code, message });
11548
+ });
11500
11549
  const completedUnitsByJob = /* @__PURE__ */ new Map();
11501
11550
  const unitCursorsByJob = /* @__PURE__ */ new Map();
11502
11551
  let activeCancel = null;
@@ -11583,7 +11632,7 @@ async function handleJob(adapter, config, job, completedUnitsByJob = /* @__PURE_
11583
11632
  }
11584
11633
  async function handleJobInner(adapter, config, job, completedUnitsByJob, signal, unitCursorsByJob = /* @__PURE__ */ new Map()) {
11585
11634
  const { session, inferenceClient, generator } = config;
11586
- const { userId, jobId } = job;
11635
+ const { jobId } = job;
11587
11636
  if (!isJobType(job.type)) {
11588
11637
  adapter.failJob(jobId, `Unrecognized job type: ${job.type}`);
11589
11638
  return;
@@ -11616,7 +11665,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
11616
11665
  const mediaType = getPrimaryMediaType(descriptor);
11617
11666
  const source = await withSpan(
11618
11667
  "detection:prepare",
11619
- () => prepareDetection(mediaType ?? "", config.contentReads, resourceId$1, userId, generator, (rid) => session.client.browse.resourceAnchoredText(rid)),
11668
+ () => prepareDetection(mediaType ?? "", config.contentReads, resourceId$1, generator, (rid) => session.client.browse.resourceAnchoredText(rid)),
11620
11669
  { attrs: { "resource.id": resourceId$1, "media.type": mediaType ?? "unknown" } }
11621
11670
  );
11622
11671
  if ("declined" in source) {
@@ -11658,7 +11707,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
11658
11707
  };
11659
11708
  const unitCursors = /* @__PURE__ */ new Map();
11660
11709
  const commitChunk = async (annotations, checkpoint) => {
11661
- record(await commitAnnotations(session, String(resourceId$1), annotations));
11710
+ record(await commitAnnotations(session, String(resourceId$1), annotations, jobId));
11662
11711
  unitCursors.set(checkpoint.unit, checkpoint.cursor);
11663
11712
  unitCursorsByJob.set(job.jobId, Object.fromEntries(unitCursors));
11664
11713
  await emitEvent(session, "job:checkpoint", {
@@ -11824,7 +11873,10 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
11824
11873
  ...genParams.prompt ? { generationPrompt: genParams.prompt } : {},
11825
11874
  ...genParams.language ? { language: genParams.language } : {},
11826
11875
  ...genParams.entityTypes && genParams.entityTypes.length > 0 ? { entityTypes: genParams.entityTypes } : {},
11827
- generator
11876
+ generator,
11877
+ // The resource cites the job it fulfils; who asked for it is derived
11878
+ // downstream from that job's events, never stated here.
11879
+ jobId
11828
11880
  });
11829
11881
  if (!genReferenceId) {
11830
11882
  const { annotation: provenanceRef } = assembleAnnotation(
@@ -11835,7 +11887,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
11835
11887
  },
11836
11888
  generator
11837
11889
  );
11838
- record(await commitAnnotations(session, String(resourceId$1), [provenanceRef]));
11890
+ record(await commitAnnotations(session, String(resourceId$1), [provenanceRef], jobId));
11839
11891
  }
11840
11892
  const citationRefs = [];
11841
11893
  if (genResult.format === "application/pdf" && genResult.citations.length > 0) {
@@ -11861,7 +11913,6 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
11861
11913
  const citationRef = buildPdfAnnotation(
11862
11914
  layer,
11863
11915
  resourceId(String(newResourceId)),
11864
- userId,
11865
11916
  generator,
11866
11917
  "linking",
11867
11918
  { exact: layer.text.slice(span.start, span.end), start: span.start, end: span.end },
@@ -11889,7 +11940,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
11889
11940
  citationRefs.push(citationRef);
11890
11941
  }
11891
11942
  }
11892
- record(await commitAnnotations(session, String(newResourceId), citationRefs));
11943
+ record(await commitAnnotations(session, String(newResourceId), citationRefs, jobId));
11893
11944
  await emitEvent(session, "job:complete", {
11894
11945
  ...terminalBase(),
11895
11946
  result: { kind: "generation", resourceId: newResourceId, resourceName: genResult.title, truncated: genResult.result.truncated }
@@ -11977,16 +12028,17 @@ function parseGatewayUrl(url) {
11977
12028
  return { protocol, host, port };
11978
12029
  }
11979
12030
  async function authenticateAgent(opts) {
11980
- const { gatewayBaseUrl: gatewayBaseUrl2, workerSecret: workerSecret2, provider, model, logger: logger2, retry = STARTUP_FETCH_RETRY } = opts;
11981
- if (!workerSecret2) {
11982
- throw new Error("SEMIONT_WORKER_SECRET is required to authenticate worker agents");
11983
- }
12031
+ const { gatewayBaseUrl: gatewayBaseUrl2, credential: credential2, provider, model, logger: logger2, retry = STARTUP_FETCH_RETRY } = opts;
11984
12032
  return retryWithBackoff(
11985
12033
  async () => {
12034
+ const caller = await serviceAccountToken(credential2);
11986
12035
  const response = await fetch(`${gatewayBaseUrl2}/api/tokens/agent`, {
11987
12036
  method: "POST",
11988
- headers: { "Content-Type": "application/json" },
11989
- body: JSON.stringify({ secret: workerSecret2, provider, model })
12037
+ headers: {
12038
+ "Content-Type": "application/json",
12039
+ Authorization: `Bearer ${caller}`
12040
+ },
12041
+ body: JSON.stringify({ provider, model })
11990
12042
  });
11991
12043
  if (!response.ok) {
11992
12044
  throw new Error(`Agent authentication failed for ${provider}:${model}: ${response.status} ${response.statusText}`);
@@ -12007,12 +12059,12 @@ async function authenticateAgent(opts) {
12007
12059
  );
12008
12060
  }
12009
12061
  async function startAgentWorker(opts) {
12010
- const { group, gatewayBaseUrl: gatewayBaseUrl2, workerSecret: workerSecret2, contentReads: contentReads2, logger: logger2 } = opts;
12062
+ const { group, gatewayBaseUrl: gatewayBaseUrl2, credential: credential2, contentReads: contentReads2, logger: logger2 } = opts;
12011
12063
  const { inference } = group;
12012
12064
  const { protocol, host, port } = parseGatewayUrl(gatewayBaseUrl2);
12013
12065
  const { token: initialToken, did } = await authenticateAgent({
12014
12066
  gatewayBaseUrl: gatewayBaseUrl2,
12015
- workerSecret: workerSecret2,
12067
+ credential: credential2,
12016
12068
  provider: inference.type,
12017
12069
  model: inference.model,
12018
12070
  logger: logger2
@@ -12023,11 +12075,15 @@ async function startAgentWorker(opts) {
12023
12075
  const kb = {
12024
12076
  id: kbId,
12025
12077
  label: `${inference.type} / ${inference.model} @ ${host}`,
12026
- email: `agent@${host}`,
12027
12078
  endpoint
12028
12079
  };
12029
12080
  const storage = new InMemorySessionStorage();
12030
- setStoredSession(storage, kbId, { access: initialToken, refresh: "" });
12081
+ setStoredSession(storage, kbId, {
12082
+ access: initialToken,
12083
+ refresh: "",
12084
+ clientId: credential2.clientId,
12085
+ tokenEndpoint: credential2.issuer
12086
+ });
12031
12087
  const token$ = new import_rxjs2.BehaviorSubject(null);
12032
12088
  let session;
12033
12089
  const transport = new HttpTransport({
@@ -12049,7 +12105,7 @@ async function startAgentWorker(opts) {
12049
12105
  try {
12050
12106
  const { token } = await authenticateAgent({
12051
12107
  gatewayBaseUrl: gatewayBaseUrl2,
12052
- workerSecret: workerSecret2,
12108
+ credential: credential2,
12053
12109
  provider: inference.type,
12054
12110
  model: inference.model,
12055
12111
  logger: logger2
@@ -12138,8 +12194,17 @@ if (!gatewayPublicURL) {
12138
12194
  throw new Error("services.gateway.publicURL is required in ~/.semiontconfig");
12139
12195
  }
12140
12196
  var gatewayBaseUrl = gatewayPublicURL;
12141
- var workerSecret = process.env.SEMIONT_WORKER_SECRET ?? "";
12142
- var contentReads = archivistContentReads(envConfig);
12197
+ var issuerUrl = envConfig.services?.identity?.issuer;
12198
+ if (!issuerUrl) {
12199
+ throw new Error("services.identity.issuer is required: a worker authenticates at the knowledge base's issuer");
12200
+ }
12201
+ var clientId = process.env.SEMIONT_OIDC_CLIENT_ID;
12202
+ var clientSecret = process.env.SEMIONT_OIDC_CLIENT_SECRET;
12203
+ if (!clientId || !clientSecret) {
12204
+ throw new Error("SEMIONT_OIDC_CLIENT_ID and SEMIONT_OIDC_CLIENT_SECRET are required to authenticate as a service account");
12205
+ }
12206
+ var credential = { issuer: issuerUrl, clientId, clientSecret };
12207
+ var contentReads = archivistContentReads(envConfig, credential);
12143
12208
  var healthPort = 24100;
12144
12209
  var logger = createProcessLogger("worker");
12145
12210
  function clientKey(w) {
@@ -12183,7 +12248,7 @@ async function main() {
12183
12248
  });
12184
12249
  const workers = await Promise.all(
12185
12250
  Array.from(groups.values()).map(
12186
- (group) => startAgentWorker({ group, gatewayBaseUrl, workerSecret, contentReads, logger })
12251
+ (group) => startAgentWorker({ group, gatewayBaseUrl, credential, contentReads, logger })
12187
12252
  )
12188
12253
  );
12189
12254
  const health = createServer((req, res) => {