@semiont/jobs 0.5.38 → 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 +53 -23
- package/dist/index.js +45 -7
- package/dist/index.js.map +1 -1
- package/dist/worker-main.js +152 -80
- 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,62 +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
|
-
isProcessing$.next(false);
|
|
9290
|
-
});
|
|
9291
|
-
});
|
|
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();
|
|
9292
9330
|
},
|
|
9293
9331
|
stop: () => {
|
|
9294
|
-
|
|
9295
|
-
|
|
9332
|
+
for (const s of subscriptions) s.unsubscribe();
|
|
9333
|
+
subscriptions = [];
|
|
9296
9334
|
started = false;
|
|
9297
9335
|
},
|
|
9298
9336
|
completeJob: () => {
|
|
@@ -9303,6 +9341,7 @@ function createJobClaimAdapter(options) {
|
|
|
9303
9341
|
activeJob$.next(null);
|
|
9304
9342
|
isProcessing$.next(false);
|
|
9305
9343
|
jobsCompleted$.next(jobsCompleted$.getValue() + 1);
|
|
9344
|
+
pull();
|
|
9306
9345
|
},
|
|
9307
9346
|
failJob: (jid, error) => {
|
|
9308
9347
|
const now = Date.now();
|
|
@@ -9312,6 +9351,7 @@ function createJobClaimAdapter(options) {
|
|
|
9312
9351
|
activeJob$.next(null);
|
|
9313
9352
|
isProcessing$.next(false);
|
|
9314
9353
|
errors$.next({ jobId: jid, error });
|
|
9354
|
+
pull();
|
|
9315
9355
|
},
|
|
9316
9356
|
vitals: () => {
|
|
9317
9357
|
const active = activeJob$.getValue();
|
|
@@ -9328,13 +9368,14 @@ function createJobClaimAdapter(options) {
|
|
|
9328
9368
|
lastActivityAt = Date.now();
|
|
9329
9369
|
},
|
|
9330
9370
|
dispose: () => {
|
|
9331
|
-
|
|
9332
|
-
|
|
9371
|
+
for (const s of subscriptions) s.unsubscribe();
|
|
9372
|
+
subscriptions = [];
|
|
9333
9373
|
started = false;
|
|
9334
9374
|
activeJob$.complete();
|
|
9335
9375
|
isProcessing$.complete();
|
|
9336
9376
|
jobsCompleted$.complete();
|
|
9337
9377
|
errors$.complete();
|
|
9378
|
+
refused$.complete();
|
|
9338
9379
|
}
|
|
9339
9380
|
};
|
|
9340
9381
|
}
|
|
@@ -10838,7 +10879,7 @@ function makeSpanDeduper() {
|
|
|
10838
10879
|
return out;
|
|
10839
10880
|
};
|
|
10840
10881
|
}
|
|
10841
|
-
function buildTextAnnotation(content, resourceId,
|
|
10882
|
+
function buildTextAnnotation(content, resourceId, generator, motivation, match, body) {
|
|
10842
10883
|
if (content.substring(match.start, match.end) !== match.exact) {
|
|
10843
10884
|
throw new Error(
|
|
10844
10885
|
`buildTextAnnotation invariant: content.substring(${match.start}, ${match.end}) !== exact for resource ${resourceId}, motivation ${motivation}`
|
|
@@ -10860,16 +10901,12 @@ function buildTextAnnotation(content, resourceId, userId, generator, motivation,
|
|
|
10860
10901
|
);
|
|
10861
10902
|
}
|
|
10862
10903
|
}
|
|
10863
|
-
const creator = didToAgent(userId);
|
|
10864
|
-
const wasAttributedTo = creator["@id"] === generator["@id"] ? [generator] : [creator, generator];
|
|
10865
10904
|
return {
|
|
10866
10905
|
"@context": "http://www.w3.org/ns/anno.jsonld",
|
|
10867
10906
|
"type": "Annotation",
|
|
10868
10907
|
"id": annotationIdFor({ resourceId, motivation, anchor: spanAnchor(match), body }),
|
|
10869
10908
|
motivation,
|
|
10870
|
-
creator,
|
|
10871
10909
|
generator,
|
|
10872
|
-
wasAttributedTo,
|
|
10873
10910
|
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10874
10911
|
target: {
|
|
10875
10912
|
type: "SpecificResource",
|
|
@@ -10887,7 +10924,7 @@ function buildTextAnnotation(content, resourceId, userId, generator, motivation,
|
|
|
10887
10924
|
...body !== void 0 ? { body } : {}
|
|
10888
10925
|
};
|
|
10889
10926
|
}
|
|
10890
|
-
function buildPdfAnnotation(anchored, resourceId,
|
|
10927
|
+
function buildPdfAnnotation(anchored, resourceId, generator, motivation, match, body) {
|
|
10891
10928
|
const { rects, overlap } = locate(anchored, match.start, match.end);
|
|
10892
10929
|
const coveredText = overlap.length ? anchored.text.substring(
|
|
10893
10930
|
Math.min(...overlap.map((i) => i.start)),
|
|
@@ -10904,16 +10941,12 @@ function buildPdfAnnotation(anchored, resourceId, userId, generator, motivation,
|
|
|
10904
10941
|
`buildPdfAnnotation invariant: covered text does not contain exact for resource ${resourceId}, motivation ${motivation}`
|
|
10905
10942
|
);
|
|
10906
10943
|
}
|
|
10907
|
-
const creator = didToAgent(userId);
|
|
10908
|
-
const wasAttributedTo = creator["@id"] === generator["@id"] ? [generator] : [creator, generator];
|
|
10909
10944
|
return {
|
|
10910
10945
|
"@context": "http://www.w3.org/ns/anno.jsonld",
|
|
10911
10946
|
"type": "Annotation",
|
|
10912
10947
|
"id": annotationIdFor({ resourceId, motivation, anchor: spanAnchor(match), body }),
|
|
10913
10948
|
motivation,
|
|
10914
|
-
creator,
|
|
10915
10949
|
generator,
|
|
10916
|
-
wasAttributedTo,
|
|
10917
10950
|
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10918
10951
|
target: {
|
|
10919
10952
|
type: "SpecificResource",
|
|
@@ -11400,7 +11433,7 @@ async function processGenerationJob(inferenceClient, params, onProgress, logger2
|
|
|
11400
11433
|
}
|
|
11401
11434
|
|
|
11402
11435
|
// src/workers/detection/prepare-detection.ts
|
|
11403
|
-
async function prepareDetection(mediaType, content, resourceId,
|
|
11436
|
+
async function prepareDetection(mediaType, content, resourceId, generator, consult) {
|
|
11404
11437
|
if (textSourceOf(mediaType) === "none") return { declined: "no-extractor" };
|
|
11405
11438
|
if (yieldsGeometryOf(mediaType)) {
|
|
11406
11439
|
const answer = await consult(resourceId);
|
|
@@ -11410,7 +11443,7 @@ async function prepareDetection(mediaType, content, resourceId, userId, generato
|
|
|
11410
11443
|
const anchored = { text: answer.text, items: answer.items ?? [] };
|
|
11411
11444
|
return {
|
|
11412
11445
|
text: answer.text,
|
|
11413
|
-
buildAnnotation: (motivation, match, body) => buildPdfAnnotation(anchored, resourceId,
|
|
11446
|
+
buildAnnotation: (motivation, match, body) => buildPdfAnnotation(anchored, resourceId, generator, motivation, match, body)
|
|
11414
11447
|
};
|
|
11415
11448
|
}
|
|
11416
11449
|
// The Smelter's own decline (encrypted, corrupt) — passed through by name.
|
|
@@ -11436,7 +11469,7 @@ async function prepareDetection(mediaType, content, resourceId, userId, generato
|
|
|
11436
11469
|
if (!text.trim()) return { declined: "empty" };
|
|
11437
11470
|
return {
|
|
11438
11471
|
text,
|
|
11439
|
-
buildAnnotation: (motivation, match, body) => buildTextAnnotation(text, resourceId,
|
|
11472
|
+
buildAnnotation: (motivation, match, body) => buildTextAnnotation(text, resourceId, generator, motivation, match, body)
|
|
11440
11473
|
};
|
|
11441
11474
|
}
|
|
11442
11475
|
function referenceIdOf(job) {
|
|
@@ -11452,13 +11485,15 @@ function referenceIdOf(job) {
|
|
|
11452
11485
|
return typeof ref === "string" ? ref : void 0;
|
|
11453
11486
|
}
|
|
11454
11487
|
var MARK_COMMIT_TIMEOUT_MS = 6e4;
|
|
11455
|
-
async function commitAnnotations(session, resourceId, annotations) {
|
|
11488
|
+
async function commitAnnotations(session, resourceId, annotations, jobId) {
|
|
11456
11489
|
if (annotations.length === 0) return void 0;
|
|
11457
11490
|
try {
|
|
11458
11491
|
await busRequest(
|
|
11459
11492
|
session.client.transport.actor,
|
|
11460
11493
|
"mark:commit",
|
|
11461
|
-
|
|
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 },
|
|
11462
11497
|
MARK_COMMIT_TIMEOUT_MS
|
|
11463
11498
|
);
|
|
11464
11499
|
return "acknowledged";
|
|
@@ -11498,10 +11533,22 @@ function startWorkerProcess(config) {
|
|
|
11498
11533
|
bus: httpTransport.actor,
|
|
11499
11534
|
jobTypes: config.jobTypes
|
|
11500
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
|
+
});
|
|
11501
11549
|
const completedUnitsByJob = /* @__PURE__ */ new Map();
|
|
11502
11550
|
const unitCursorsByJob = /* @__PURE__ */ new Map();
|
|
11503
11551
|
let activeCancel = null;
|
|
11504
|
-
httpTransport.actor.addChannels?.(["job:cancel-requested"]);
|
|
11505
11552
|
httpTransport.on("job:cancel-requested", (event) => {
|
|
11506
11553
|
const targetId = event.jobId;
|
|
11507
11554
|
if (targetId && activeCancel?.jobId === targetId) {
|
|
@@ -11585,7 +11632,7 @@ async function handleJob(adapter, config, job, completedUnitsByJob = /* @__PURE_
|
|
|
11585
11632
|
}
|
|
11586
11633
|
async function handleJobInner(adapter, config, job, completedUnitsByJob, signal, unitCursorsByJob = /* @__PURE__ */ new Map()) {
|
|
11587
11634
|
const { session, inferenceClient, generator } = config;
|
|
11588
|
-
const {
|
|
11635
|
+
const { jobId } = job;
|
|
11589
11636
|
if (!isJobType(job.type)) {
|
|
11590
11637
|
adapter.failJob(jobId, `Unrecognized job type: ${job.type}`);
|
|
11591
11638
|
return;
|
|
@@ -11618,7 +11665,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
|
|
|
11618
11665
|
const mediaType = getPrimaryMediaType(descriptor);
|
|
11619
11666
|
const source = await withSpan(
|
|
11620
11667
|
"detection:prepare",
|
|
11621
|
-
() => prepareDetection(mediaType ?? "", config.contentReads, resourceId$1,
|
|
11668
|
+
() => prepareDetection(mediaType ?? "", config.contentReads, resourceId$1, generator, (rid) => session.client.browse.resourceAnchoredText(rid)),
|
|
11622
11669
|
{ attrs: { "resource.id": resourceId$1, "media.type": mediaType ?? "unknown" } }
|
|
11623
11670
|
);
|
|
11624
11671
|
if ("declined" in source) {
|
|
@@ -11660,7 +11707,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
|
|
|
11660
11707
|
};
|
|
11661
11708
|
const unitCursors = /* @__PURE__ */ new Map();
|
|
11662
11709
|
const commitChunk = async (annotations, checkpoint) => {
|
|
11663
|
-
record(await commitAnnotations(session, String(resourceId$1), annotations));
|
|
11710
|
+
record(await commitAnnotations(session, String(resourceId$1), annotations, jobId));
|
|
11664
11711
|
unitCursors.set(checkpoint.unit, checkpoint.cursor);
|
|
11665
11712
|
unitCursorsByJob.set(job.jobId, Object.fromEntries(unitCursors));
|
|
11666
11713
|
await emitEvent(session, "job:checkpoint", {
|
|
@@ -11826,7 +11873,10 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
|
|
|
11826
11873
|
...genParams.prompt ? { generationPrompt: genParams.prompt } : {},
|
|
11827
11874
|
...genParams.language ? { language: genParams.language } : {},
|
|
11828
11875
|
...genParams.entityTypes && genParams.entityTypes.length > 0 ? { entityTypes: genParams.entityTypes } : {},
|
|
11829
|
-
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
|
|
11830
11880
|
});
|
|
11831
11881
|
if (!genReferenceId) {
|
|
11832
11882
|
const { annotation: provenanceRef } = assembleAnnotation(
|
|
@@ -11837,7 +11887,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
|
|
|
11837
11887
|
},
|
|
11838
11888
|
generator
|
|
11839
11889
|
);
|
|
11840
|
-
record(await commitAnnotations(session, String(resourceId$1), [provenanceRef]));
|
|
11890
|
+
record(await commitAnnotations(session, String(resourceId$1), [provenanceRef], jobId));
|
|
11841
11891
|
}
|
|
11842
11892
|
const citationRefs = [];
|
|
11843
11893
|
if (genResult.format === "application/pdf" && genResult.citations.length > 0) {
|
|
@@ -11863,7 +11913,6 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
|
|
|
11863
11913
|
const citationRef = buildPdfAnnotation(
|
|
11864
11914
|
layer,
|
|
11865
11915
|
resourceId(String(newResourceId)),
|
|
11866
|
-
userId,
|
|
11867
11916
|
generator,
|
|
11868
11917
|
"linking",
|
|
11869
11918
|
{ exact: layer.text.slice(span.start, span.end), start: span.start, end: span.end },
|
|
@@ -11891,7 +11940,7 @@ async function handleJobInner(adapter, config, job, completedUnitsByJob, signal,
|
|
|
11891
11940
|
citationRefs.push(citationRef);
|
|
11892
11941
|
}
|
|
11893
11942
|
}
|
|
11894
|
-
record(await commitAnnotations(session, String(newResourceId), citationRefs));
|
|
11943
|
+
record(await commitAnnotations(session, String(newResourceId), citationRefs, jobId));
|
|
11895
11944
|
await emitEvent(session, "job:complete", {
|
|
11896
11945
|
...terminalBase(),
|
|
11897
11946
|
result: { kind: "generation", resourceId: newResourceId, resourceName: genResult.title, truncated: genResult.result.truncated }
|
|
@@ -11961,7 +12010,16 @@ var WORKER_AWAITED_OPERATIONS = [
|
|
|
11961
12010
|
// rare error path is no reason to let it back in.
|
|
11962
12011
|
"browse:annotation-requested"
|
|
11963
12012
|
];
|
|
11964
|
-
var
|
|
12013
|
+
var WORKER_CONSUMED_BROADCASTS = [
|
|
12014
|
+
// The queue announcement the claim adapter races for.
|
|
12015
|
+
"job:queued",
|
|
12016
|
+
// Cooperative cancellation of the ACTIVE job (JOB-RESTART-SAFETY P4).
|
|
12017
|
+
"job:cancel-requested"
|
|
12018
|
+
];
|
|
12019
|
+
var WORKER_CHANNELS = [
|
|
12020
|
+
...replyChannelsFor(WORKER_AWAITED_OPERATIONS),
|
|
12021
|
+
...WORKER_CONSUMED_BROADCASTS
|
|
12022
|
+
];
|
|
11965
12023
|
function parseGatewayUrl(url) {
|
|
11966
12024
|
const parsed = new URL(url);
|
|
11967
12025
|
const protocol = parsed.protocol.replace(":", "") === "https" ? "https" : "http";
|
|
@@ -11970,16 +12028,17 @@ function parseGatewayUrl(url) {
|
|
|
11970
12028
|
return { protocol, host, port };
|
|
11971
12029
|
}
|
|
11972
12030
|
async function authenticateAgent(opts) {
|
|
11973
|
-
const { gatewayBaseUrl: gatewayBaseUrl2,
|
|
11974
|
-
if (!workerSecret2) {
|
|
11975
|
-
throw new Error("SEMIONT_WORKER_SECRET is required to authenticate worker agents");
|
|
11976
|
-
}
|
|
12031
|
+
const { gatewayBaseUrl: gatewayBaseUrl2, credential: credential2, provider, model, logger: logger2, retry = STARTUP_FETCH_RETRY } = opts;
|
|
11977
12032
|
return retryWithBackoff(
|
|
11978
12033
|
async () => {
|
|
12034
|
+
const caller = await serviceAccountToken(credential2);
|
|
11979
12035
|
const response = await fetch(`${gatewayBaseUrl2}/api/tokens/agent`, {
|
|
11980
12036
|
method: "POST",
|
|
11981
|
-
headers: {
|
|
11982
|
-
|
|
12037
|
+
headers: {
|
|
12038
|
+
"Content-Type": "application/json",
|
|
12039
|
+
Authorization: `Bearer ${caller}`
|
|
12040
|
+
},
|
|
12041
|
+
body: JSON.stringify({ provider, model })
|
|
11983
12042
|
});
|
|
11984
12043
|
if (!response.ok) {
|
|
11985
12044
|
throw new Error(`Agent authentication failed for ${provider}:${model}: ${response.status} ${response.statusText}`);
|
|
@@ -12000,12 +12059,12 @@ async function authenticateAgent(opts) {
|
|
|
12000
12059
|
);
|
|
12001
12060
|
}
|
|
12002
12061
|
async function startAgentWorker(opts) {
|
|
12003
|
-
const { group, gatewayBaseUrl: gatewayBaseUrl2,
|
|
12062
|
+
const { group, gatewayBaseUrl: gatewayBaseUrl2, credential: credential2, contentReads: contentReads2, logger: logger2 } = opts;
|
|
12004
12063
|
const { inference } = group;
|
|
12005
12064
|
const { protocol, host, port } = parseGatewayUrl(gatewayBaseUrl2);
|
|
12006
12065
|
const { token: initialToken, did } = await authenticateAgent({
|
|
12007
12066
|
gatewayBaseUrl: gatewayBaseUrl2,
|
|
12008
|
-
|
|
12067
|
+
credential: credential2,
|
|
12009
12068
|
provider: inference.type,
|
|
12010
12069
|
model: inference.model,
|
|
12011
12070
|
logger: logger2
|
|
@@ -12016,11 +12075,15 @@ async function startAgentWorker(opts) {
|
|
|
12016
12075
|
const kb = {
|
|
12017
12076
|
id: kbId,
|
|
12018
12077
|
label: `${inference.type} / ${inference.model} @ ${host}`,
|
|
12019
|
-
email: `agent@${host}`,
|
|
12020
12078
|
endpoint
|
|
12021
12079
|
};
|
|
12022
12080
|
const storage = new InMemorySessionStorage();
|
|
12023
|
-
setStoredSession(storage, kbId, {
|
|
12081
|
+
setStoredSession(storage, kbId, {
|
|
12082
|
+
access: initialToken,
|
|
12083
|
+
refresh: "",
|
|
12084
|
+
clientId: credential2.clientId,
|
|
12085
|
+
tokenEndpoint: credential2.issuer
|
|
12086
|
+
});
|
|
12024
12087
|
const token$ = new import_rxjs2.BehaviorSubject(null);
|
|
12025
12088
|
let session;
|
|
12026
12089
|
const transport = new HttpTransport({
|
|
@@ -12042,7 +12105,7 @@ async function startAgentWorker(opts) {
|
|
|
12042
12105
|
try {
|
|
12043
12106
|
const { token } = await authenticateAgent({
|
|
12044
12107
|
gatewayBaseUrl: gatewayBaseUrl2,
|
|
12045
|
-
|
|
12108
|
+
credential: credential2,
|
|
12046
12109
|
provider: inference.type,
|
|
12047
12110
|
model: inference.model,
|
|
12048
12111
|
logger: logger2
|
|
@@ -12131,8 +12194,17 @@ if (!gatewayPublicURL) {
|
|
|
12131
12194
|
throw new Error("services.gateway.publicURL is required in ~/.semiontconfig");
|
|
12132
12195
|
}
|
|
12133
12196
|
var gatewayBaseUrl = gatewayPublicURL;
|
|
12134
|
-
var
|
|
12135
|
-
|
|
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);
|
|
12136
12208
|
var healthPort = 24100;
|
|
12137
12209
|
var logger = createProcessLogger("worker");
|
|
12138
12210
|
function clientKey(w) {
|
|
@@ -12176,7 +12248,7 @@ async function main() {
|
|
|
12176
12248
|
});
|
|
12177
12249
|
const workers = await Promise.all(
|
|
12178
12250
|
Array.from(groups.values()).map(
|
|
12179
|
-
(group) => startAgentWorker({ group, gatewayBaseUrl,
|
|
12251
|
+
(group) => startAgentWorker({ group, gatewayBaseUrl, credential, contentReads, logger })
|
|
12180
12252
|
)
|
|
12181
12253
|
);
|
|
12182
12254
|
const health = createServer((req, res) => {
|