@semiont/jobs 0.5.39 → 0.6.0
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/README.md +4 -10
- package/dist/index.d.ts +31 -21
- package/dist/index.js +32 -3
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +142 -77
- package/dist/worker-main.js.map +1 -1
- package/package.json +9 -9
package/dist/worker-main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { replyChannelsFor, createTomlConfigLoader, didToAgent, baseUrl, STARTUP_FETCH_RETRY, retryWithBackoff, isTransientFetchError, isObject, isString, isNumber, RETRY_RULES,
|
|
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
|
-
|
|
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
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
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:
|
|
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(
|
|
9257
|
-
maxRetries: isNumber(
|
|
9258
|
-
}
|
|
9259
|
-
}
|
|
9260
|
-
|
|
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
|
-
|
|
9272
|
-
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
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
|
-
|
|
9294
|
-
|
|
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
|
-
|
|
9331
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
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,
|
|
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: {
|
|
11989
|
-
|
|
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,
|
|
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
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
|
12142
|
-
|
|
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,
|
|
12251
|
+
(group) => startAgentWorker({ group, gatewayBaseUrl, credential, contentReads, logger })
|
|
12187
12252
|
)
|
|
12188
12253
|
);
|
|
12189
12254
|
const health = createServer((req, res) => {
|