@semiont/jobs 0.6.6 → 0.6.8

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,7 +1,7 @@
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';
1
+ import { JOB_TYPES, 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
- import { createInferenceClient, StructuredReadError } from '@semiont/inference';
4
+ import { createInferenceClient, answerLimitsRequests, StructuredReadError } from '@semiont/inference';
5
5
  import { execFileSync } from 'child_process';
6
6
  import { existsSync, readFileSync, mkdtempSync, writeFileSync, rmSync } from 'fs';
7
7
  import { homedir, hostname, tmpdir } from 'os';
@@ -9384,18 +9384,9 @@ function createJobClaimAdapter(options) {
9384
9384
  function willRetryAfter(metadata, failureClass) {
9385
9385
  return failureClass !== "deterministic" && metadata.retryCount < metadata.maxRetries;
9386
9386
  }
9387
-
9388
- // src/types.ts
9389
- var JOB_TYPES = /* @__PURE__ */ new Set([
9390
- "reference-annotation",
9391
- "generation",
9392
- "highlight-annotation",
9393
- "assessment-annotation",
9394
- "comment-annotation",
9395
- "tag-annotation"
9396
- ]);
9387
+ var KNOWN_JOB_TYPES = new Set(JOB_TYPES);
9397
9388
  function isJobType(value) {
9398
- return JOB_TYPES.has(value);
9389
+ return KNOWN_JOB_TYPES.has(value);
9399
9390
  }
9400
9391
  function asJobParams(params) {
9401
9392
  if (typeof params.resourceId !== "string") {
@@ -12016,6 +12007,9 @@ var WORKER_CONSUMED_BROADCASTS = [
12016
12007
  // Cooperative cancellation of the ACTIVE job (JOB-RESTART-SAFETY P4).
12017
12008
  "job:cancel-requested"
12018
12009
  ];
12010
+ var WORKER_ANSWERED_OPERATIONS = [
12011
+ "job:limits-requested"
12012
+ ];
12019
12013
  var WORKER_CHANNELS = [
12020
12014
  ...replyChannelsFor(WORKER_AWAITED_OPERATIONS),
12021
12015
  ...WORKER_CONSUMED_BROADCASTS
@@ -12059,7 +12053,7 @@ async function authenticateAgent(opts) {
12059
12053
  );
12060
12054
  }
12061
12055
  async function startAgentWorker(opts) {
12062
- const { group, gatewayBaseUrl: gatewayBaseUrl2, credential: credential2, contentReads: contentReads2, logger: logger2 } = opts;
12056
+ const { group, gatewayBaseUrl: gatewayBaseUrl2, credential: credential2, contentReads: contentReads2, reportsLimitsOf, logger: logger2 } = opts;
12063
12057
  const { inference } = group;
12064
12058
  const { protocol, host, port } = parseGatewayUrl(gatewayBaseUrl2);
12065
12059
  const { token: initialToken, did } = await authenticateAgent({
@@ -12091,8 +12085,9 @@ async function startAgentWorker(opts) {
12091
12085
  token$,
12092
12086
  tokenRefresher: () => session.refresh().then((t) => t ?? null),
12093
12087
  // Only the reply channels this process awaits — not the full bridged
12094
- // set. See WORKER_AWAITED_OPERATIONS.
12095
- channels: WORKER_CHANNELS
12088
+ // set. See WORKER_AWAITED_OPERATIONS. The agent that reports its pool's
12089
+ // limits also subscribes the requests it answers.
12090
+ channels: reportsLimitsOf.length > 0 ? [...WORKER_CHANNELS, ...WORKER_ANSWERED_OPERATIONS] : WORKER_CHANNELS
12096
12091
  });
12097
12092
  const content = new HttpContentTransport(transport);
12098
12093
  const client = new SemiontClient(transport, content, transport);
@@ -12136,6 +12131,7 @@ async function startAgentWorker(opts) {
12136
12131
  contentReads: contentReads2,
12137
12132
  logger: logger2
12138
12133
  });
12134
+ const limitsResponder = reportsLimitsOf.length > 0 ? answerLimitsRequests(transport, "job:limits-requested", reportsLimitsOf, logger2) : void 0;
12139
12135
  logger2.info("Agent ready", {
12140
12136
  did,
12141
12137
  provider: inference.type,
@@ -12152,19 +12148,12 @@ async function startAgentWorker(opts) {
12152
12148
  ...adapter.vitals()
12153
12149
  }),
12154
12150
  dispose: async () => {
12151
+ limitsResponder?.unsubscribe();
12155
12152
  adapter.dispose();
12156
12153
  await session.dispose();
12157
12154
  }
12158
12155
  };
12159
12156
  }
12160
- var ALL_JOB_TYPES = [
12161
- "reference-annotation",
12162
- "generation",
12163
- "highlight-annotation",
12164
- "assessment-annotation",
12165
- "comment-annotation",
12166
- "tag-annotation"
12167
- ];
12168
12157
  var configPath = join(homedir(), ".semiontconfig");
12169
12158
  var tomlReader = {
12170
12159
  readIfExists: (p) => existsSync(p) ? readFileSync(p, "utf-8") : null
@@ -12172,7 +12161,8 @@ var tomlReader = {
12172
12161
  var envConfig = createTomlConfigLoader(
12173
12162
  tomlReader,
12174
12163
  configPath,
12175
- process.env
12164
+ process.env,
12165
+ "worker"
12176
12166
  )(null);
12177
12167
  var workerInferenceMap = envConfig._metadata?.workers;
12178
12168
  if (!workerInferenceMap || Object.keys(workerInferenceMap).length === 0) {
@@ -12220,7 +12210,7 @@ function toClientConfig(w) {
12220
12210
  };
12221
12211
  }
12222
12212
  var groups = /* @__PURE__ */ new Map();
12223
- for (const jobType of ALL_JOB_TYPES) {
12213
+ for (const jobType of JOB_TYPES) {
12224
12214
  const inference = resolveWorker(jobType);
12225
12215
  const key = clientKey(inference);
12226
12216
  let group = groups.get(key);
@@ -12247,8 +12237,17 @@ async function main() {
12247
12237
  }))
12248
12238
  });
12249
12239
  const workers = await Promise.all(
12240
+ // The first agent reports every group's limits: the gateway delivers only
12241
+ // the first reply to a request, so one agent answers for the pool.
12250
12242
  Array.from(groups.values()).map(
12251
- (group) => startAgentWorker({ group, gatewayBaseUrl, credential, contentReads, logger })
12243
+ (group, i, all) => startAgentWorker({
12244
+ group,
12245
+ gatewayBaseUrl,
12246
+ credential,
12247
+ contentReads,
12248
+ logger,
12249
+ reportsLimitsOf: i === 0 ? all.map((g) => g.client) : []
12250
+ })
12252
12251
  )
12253
12252
  );
12254
12253
  const health = createServer((req, res) => {