@microfox/ai-worker 1.0.4 → 1.0.5
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/CHANGELOG.md +8 -0
- package/README.md +22 -0
- package/dist/chainMapDefaults.d.mts +21 -0
- package/dist/chainMapDefaults.d.ts +21 -0
- package/dist/chainMapDefaults.js +59 -0
- package/dist/chainMapDefaults.js.map +1 -0
- package/dist/chainMapDefaults.mjs +10 -0
- package/dist/chainMapDefaults.mjs.map +1 -0
- package/dist/chunk-BCRJIFKB.mjs +9 -0
- package/dist/chunk-BCRJIFKB.mjs.map +1 -0
- package/dist/{chunk-72XGFZCE.mjs → chunk-CILTGUUQ.mjs} +14 -3
- package/dist/chunk-CILTGUUQ.mjs.map +1 -0
- package/dist/{chunk-7LQNS2SG.mjs → chunk-QHX55IML.mjs} +442 -56
- package/dist/chunk-QHX55IML.mjs.map +1 -0
- package/dist/chunk-SQB5FQCZ.mjs +21 -0
- package/dist/chunk-SQB5FQCZ.mjs.map +1 -0
- package/dist/{chunk-AOXGONGI.mjs → chunk-T7DRPKR6.mjs} +7 -5
- package/dist/chunk-T7DRPKR6.mjs.map +1 -0
- package/dist/chunk-XCKWV2WZ.mjs +34 -0
- package/dist/chunk-XCKWV2WZ.mjs.map +1 -0
- package/dist/chunk-ZW4PNCDH.mjs +17 -0
- package/dist/chunk-ZW4PNCDH.mjs.map +1 -0
- package/dist/client.d.mts +148 -2
- package/dist/client.d.ts +148 -2
- package/dist/client.js +13 -2
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +1 -1
- package/dist/handler.d.mts +121 -23
- package/dist/handler.d.ts +121 -23
- package/dist/handler.js +450 -58
- package/dist/handler.js.map +1 -1
- package/dist/handler.mjs +5 -2
- package/dist/hitlConfig.d.mts +46 -0
- package/dist/hitlConfig.d.ts +46 -0
- package/dist/hitlConfig.js +33 -0
- package/dist/hitlConfig.js.map +1 -0
- package/dist/hitlConfig.mjs +8 -0
- package/dist/hitlConfig.mjs.map +1 -0
- package/dist/index.d.mts +23 -4
- package/dist/index.d.ts +23 -4
- package/dist/index.js +575 -74
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +78 -20
- package/dist/index.mjs.map +1 -1
- package/dist/queue-B5n6YVQV.d.ts +306 -0
- package/dist/queue-DaR2UuZi.d.mts +306 -0
- package/dist/queue.d.mts +3 -0
- package/dist/queue.d.ts +3 -0
- package/dist/queue.js +47 -0
- package/dist/queue.js.map +1 -0
- package/dist/queue.mjs +12 -0
- package/dist/queue.mjs.map +1 -0
- package/dist/queueInputEnvelope.d.mts +31 -0
- package/dist/queueInputEnvelope.d.ts +31 -0
- package/dist/queueInputEnvelope.js +42 -0
- package/dist/queueInputEnvelope.js.map +1 -0
- package/dist/queueInputEnvelope.mjs +10 -0
- package/dist/queueInputEnvelope.mjs.map +1 -0
- package/dist/queueJobStore.d.mts +3 -2
- package/dist/queueJobStore.d.ts +3 -2
- package/dist/queueJobStore.js +6 -4
- package/dist/queueJobStore.js.map +1 -1
- package/dist/queueJobStore.mjs +1 -1
- package/package.json +7 -2
- package/dist/chunk-72XGFZCE.mjs.map +0 -1
- package/dist/chunk-7LQNS2SG.mjs.map +0 -1
- package/dist/chunk-AOXGONGI.mjs.map +0 -1
- package/dist/client-BqSJQ9mZ.d.mts +0 -183
- package/dist/client-BqSJQ9mZ.d.ts +0 -183
|
@@ -2,7 +2,10 @@ import {
|
|
|
2
2
|
appendQueueJobStepInStore,
|
|
3
3
|
updateQueueJobStepInStore,
|
|
4
4
|
upsertInitialQueueJob
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-T7DRPKR6.mjs";
|
|
6
|
+
import {
|
|
7
|
+
QUEUE_ORCHESTRATION_KEYS
|
|
8
|
+
} from "./chunk-SQB5FQCZ.mjs";
|
|
6
9
|
|
|
7
10
|
// src/handler.ts
|
|
8
11
|
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
|
|
@@ -47,7 +50,7 @@ async function getJobById(jobId) {
|
|
|
47
50
|
return null;
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
|
-
function createMongoJobStore(workerId, jobId, input, metadata) {
|
|
53
|
+
function createMongoJobStore(workerId, jobId, input, metadata, userId) {
|
|
51
54
|
return {
|
|
52
55
|
update: async (update) => {
|
|
53
56
|
try {
|
|
@@ -86,6 +89,7 @@ function createMongoJobStore(workerId, jobId, input, metadata) {
|
|
|
86
89
|
output: update.output,
|
|
87
90
|
error: update.error,
|
|
88
91
|
metadata: metadataUpdate,
|
|
92
|
+
...userId ? { userId } : {},
|
|
89
93
|
createdAt: now,
|
|
90
94
|
updatedAt: now,
|
|
91
95
|
completedAt: set.completedAt
|
|
@@ -151,7 +155,7 @@ function createMongoJobStore(workerId, jobId, input, metadata) {
|
|
|
151
155
|
}
|
|
152
156
|
};
|
|
153
157
|
}
|
|
154
|
-
async function upsertJob(jobId, workerId, input, metadata) {
|
|
158
|
+
async function upsertJob(jobId, workerId, input, metadata, userId) {
|
|
155
159
|
const coll = await getCollection();
|
|
156
160
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
157
161
|
await coll.updateOne(
|
|
@@ -164,6 +168,7 @@ async function upsertJob(jobId, workerId, input, metadata) {
|
|
|
164
168
|
status: "queued",
|
|
165
169
|
input: input ?? {},
|
|
166
170
|
metadata: metadata ?? {},
|
|
171
|
+
...userId ? { userId } : {},
|
|
167
172
|
createdAt: now,
|
|
168
173
|
updatedAt: now
|
|
169
174
|
}
|
|
@@ -242,13 +247,14 @@ async function loadJob(jobId) {
|
|
|
242
247
|
error: parseJson(data.error),
|
|
243
248
|
metadata: parseJson(data.metadata) ?? {},
|
|
244
249
|
internalJobs,
|
|
250
|
+
...data.userId ? { userId: data.userId } : {},
|
|
245
251
|
createdAt: data.createdAt,
|
|
246
252
|
updatedAt: data.updatedAt,
|
|
247
253
|
completedAt: data.completedAt
|
|
248
254
|
};
|
|
249
255
|
return record;
|
|
250
256
|
}
|
|
251
|
-
function createRedisJobStore(workerId, jobId, input, metadata) {
|
|
257
|
+
function createRedisJobStore(workerId, jobId, input, metadata, userId) {
|
|
252
258
|
return {
|
|
253
259
|
update: async (update) => {
|
|
254
260
|
const redis = getRedis();
|
|
@@ -306,34 +312,161 @@ function createRedisJobStore(workerId, jobId, input, metadata) {
|
|
|
306
312
|
}
|
|
307
313
|
};
|
|
308
314
|
}
|
|
309
|
-
async function upsertRedisJob(jobId, workerId, input, metadata) {
|
|
315
|
+
async function upsertRedisJob(jobId, workerId, input, metadata, userId) {
|
|
310
316
|
const redis = getRedis();
|
|
311
317
|
const key = jobKey(jobId);
|
|
312
318
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
313
|
-
const doc = {
|
|
314
|
-
jobId,
|
|
315
|
-
workerId,
|
|
316
|
-
status: "queued",
|
|
317
|
-
input,
|
|
318
|
-
metadata,
|
|
319
|
-
createdAt: now,
|
|
320
|
-
updatedAt: now
|
|
321
|
-
};
|
|
322
319
|
const toSet = {
|
|
323
320
|
jobId,
|
|
324
321
|
workerId,
|
|
325
|
-
status:
|
|
326
|
-
input: JSON.stringify(
|
|
327
|
-
metadata: JSON.stringify(
|
|
322
|
+
status: "queued",
|
|
323
|
+
input: JSON.stringify(input ?? {}),
|
|
324
|
+
metadata: JSON.stringify(metadata ?? {}),
|
|
328
325
|
createdAt: now,
|
|
329
326
|
updatedAt: now
|
|
330
327
|
};
|
|
328
|
+
if (userId) toSet.userId = userId;
|
|
331
329
|
await redis.hset(key, toSet);
|
|
332
330
|
if (jobTtlSeconds > 0) {
|
|
333
331
|
await redis.expire(key, jobTtlSeconds);
|
|
334
332
|
}
|
|
335
333
|
}
|
|
336
334
|
|
|
335
|
+
// src/retryConfig.ts
|
|
336
|
+
var BUILT_IN_PATTERNS = {
|
|
337
|
+
"rate-limit": {
|
|
338
|
+
match: (err) => /rate.?limit|too.?many.?requests/i.test(err.message) || err.status === 429 || err.code === 429 || err.name === "RateLimitError",
|
|
339
|
+
delayMs: (attempt) => attempt * 1e4,
|
|
340
|
+
// 10s, 20s, 30s…
|
|
341
|
+
injectContext: false
|
|
342
|
+
},
|
|
343
|
+
"json-parse": {
|
|
344
|
+
match: (err) => err.name === "SyntaxError" || err.name === "ZodError" || /json|parse|unexpected.?token|invalid.?format/i.test(err.message),
|
|
345
|
+
delayMs: (_attempt) => 0,
|
|
346
|
+
// Immediate — model self-corrects from ctx
|
|
347
|
+
injectContext: true
|
|
348
|
+
},
|
|
349
|
+
overloaded: {
|
|
350
|
+
match: (err) => /overloaded|model.?is.?busy/i.test(err.message) || err.status === 529 || err.code === 529,
|
|
351
|
+
delayMs: (attempt) => attempt * 15e3,
|
|
352
|
+
// 15s, 30s…
|
|
353
|
+
injectContext: false
|
|
354
|
+
},
|
|
355
|
+
"server-error": {
|
|
356
|
+
match: (err) => /internal.?server.?error|service.?unavailable|bad.?gateway/i.test(err.message) || typeof err.status === "number" && err.status >= 500 && err.status < 600,
|
|
357
|
+
delayMs: (attempt) => attempt * 5e3,
|
|
358
|
+
// 5s, 10s…
|
|
359
|
+
injectContext: false
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
function matchesRetryPattern(err, patterns, attempt) {
|
|
363
|
+
for (const pattern of patterns) {
|
|
364
|
+
if (typeof pattern === "string") {
|
|
365
|
+
const impl = BUILT_IN_PATTERNS[pattern];
|
|
366
|
+
if (impl.match(err)) {
|
|
367
|
+
return { matched: true, delayMs: impl.delayMs(attempt), injectContext: impl.injectContext };
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
let matched = false;
|
|
371
|
+
if (pattern.match instanceof RegExp) {
|
|
372
|
+
matched = pattern.match.test(err.message);
|
|
373
|
+
} else {
|
|
374
|
+
try {
|
|
375
|
+
matched = pattern.match(err);
|
|
376
|
+
} catch {
|
|
377
|
+
matched = false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (matched) {
|
|
381
|
+
const delayMs = typeof pattern.delayMs === "function" ? pattern.delayMs(attempt) : pattern.delayMs ?? 0;
|
|
382
|
+
return { matched: true, delayMs, injectContext: pattern.injectContext ?? false };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return { matched: false, delayMs: 0, injectContext: false };
|
|
387
|
+
}
|
|
388
|
+
async function executeWithRetry(fn, config, onRetry) {
|
|
389
|
+
const maxAttempts = config.maxAttempts ?? 3;
|
|
390
|
+
let lastError;
|
|
391
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
392
|
+
const retryCtx = attempt > 1 && lastError ? {
|
|
393
|
+
attempt,
|
|
394
|
+
maxAttempts,
|
|
395
|
+
lastError: {
|
|
396
|
+
message: lastError.message,
|
|
397
|
+
name: lastError.name,
|
|
398
|
+
stack: lastError.stack,
|
|
399
|
+
code: lastError.code ?? lastError.status
|
|
400
|
+
}
|
|
401
|
+
} : void 0;
|
|
402
|
+
try {
|
|
403
|
+
return await fn(retryCtx);
|
|
404
|
+
} catch (err) {
|
|
405
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
406
|
+
if (err?.name === "TokenBudgetExceededError") throw err;
|
|
407
|
+
if (attempt >= maxAttempts) throw err;
|
|
408
|
+
const retryAttemptNumber = attempt;
|
|
409
|
+
const { matched, delayMs } = matchesRetryPattern(lastError, config.on, retryAttemptNumber);
|
|
410
|
+
if (!matched) throw err;
|
|
411
|
+
const nextCtx = {
|
|
412
|
+
attempt: attempt + 1,
|
|
413
|
+
maxAttempts,
|
|
414
|
+
lastError: {
|
|
415
|
+
message: lastError.message,
|
|
416
|
+
name: lastError.name,
|
|
417
|
+
stack: lastError.stack,
|
|
418
|
+
code: lastError.code ?? lastError.status
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
onRetry?.(nextCtx, delayMs);
|
|
422
|
+
if (delayMs > 0) {
|
|
423
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
throw lastError ?? new Error("executeWithRetry: unknown error");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/tokenBudget.ts
|
|
431
|
+
var TokenBudgetExceededError = class extends Error {
|
|
432
|
+
constructor(used, budget) {
|
|
433
|
+
super(`Token budget exceeded: used ${used} tokens (budget: ${budget})`);
|
|
434
|
+
this.name = "TokenBudgetExceededError";
|
|
435
|
+
this.used = used;
|
|
436
|
+
this.budget = budget;
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
function createTokenTracker(budget) {
|
|
440
|
+
let inputTokens = 0;
|
|
441
|
+
let outputTokens = 0;
|
|
442
|
+
function checkBudget() {
|
|
443
|
+
if (budget !== null) {
|
|
444
|
+
const total = inputTokens + outputTokens;
|
|
445
|
+
if (total > budget) {
|
|
446
|
+
throw new TokenBudgetExceededError(total, budget);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
report(usage) {
|
|
452
|
+
inputTokens += usage.inputTokens;
|
|
453
|
+
outputTokens += usage.outputTokens;
|
|
454
|
+
checkBudget();
|
|
455
|
+
},
|
|
456
|
+
getState() {
|
|
457
|
+
return { inputTokens, outputTokens, budget };
|
|
458
|
+
},
|
|
459
|
+
getBudgetInfo() {
|
|
460
|
+
const used = inputTokens + outputTokens;
|
|
461
|
+
return {
|
|
462
|
+
used,
|
|
463
|
+
budget,
|
|
464
|
+
remaining: budget !== null ? Math.max(0, budget - used) : null
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
337
470
|
// src/handler.ts
|
|
338
471
|
var SQS_MAX_DELAY_SECONDS = 900;
|
|
339
472
|
function createWorkerLogger(jobId, workerId) {
|
|
@@ -356,6 +489,67 @@ function createWorkerLogger(jobId, workerId) {
|
|
|
356
489
|
};
|
|
357
490
|
}
|
|
358
491
|
var WORKER_QUEUE_KEY = "__workerQueue";
|
|
492
|
+
async function loadPreviousOutputsBeforeStep(queueRuntime, queueJobId, beforeStepIndex) {
|
|
493
|
+
if (!queueJobId || typeof queueRuntime.getQueueJob !== "function") {
|
|
494
|
+
return [];
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
const job = await queueRuntime.getQueueJob(queueJobId);
|
|
498
|
+
if (!job?.steps) return [];
|
|
499
|
+
return job.steps.slice(0, beforeStepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
|
|
500
|
+
} catch (e) {
|
|
501
|
+
if (process.env.AI_WORKER_QUEUES_DEBUG === "1") {
|
|
502
|
+
console.warn("[Worker] getQueueJob failed (resume mapping):", e?.message ?? e);
|
|
503
|
+
}
|
|
504
|
+
return [];
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async function maybeApplyHitlResumeMapper(params, queueRuntime) {
|
|
508
|
+
const inputObj = params.input;
|
|
509
|
+
if (!inputObj || typeof inputObj !== "object") return;
|
|
510
|
+
if (!("__hitlInput" in inputObj)) return;
|
|
511
|
+
const wq = inputObj[WORKER_QUEUE_KEY];
|
|
512
|
+
if (!wq?.id || typeof wq.stepIndex !== "number") return;
|
|
513
|
+
const queueId = wq.id;
|
|
514
|
+
const stepIndex = wq.stepIndex;
|
|
515
|
+
const initialInput = wq.initialInput;
|
|
516
|
+
const queueJobId = wq.queueJobId;
|
|
517
|
+
const previousOutputs = await loadPreviousOutputsBeforeStep(queueRuntime, queueJobId, stepIndex);
|
|
518
|
+
const pendingInput = { ...inputObj };
|
|
519
|
+
for (const key of QUEUE_ORCHESTRATION_KEYS) {
|
|
520
|
+
delete pendingInput[key];
|
|
521
|
+
}
|
|
522
|
+
delete pendingInput[WORKER_QUEUE_KEY];
|
|
523
|
+
const reviewerInput = inputObj.__hitlInput;
|
|
524
|
+
const decision = inputObj.__hitlDecision;
|
|
525
|
+
let merged;
|
|
526
|
+
if (typeof queueRuntime.invokeResume === "function") {
|
|
527
|
+
merged = await queueRuntime.invokeResume(queueId, stepIndex, {
|
|
528
|
+
initialInput,
|
|
529
|
+
previousOutputs,
|
|
530
|
+
reviewerInput,
|
|
531
|
+
pendingInput
|
|
532
|
+
});
|
|
533
|
+
} else {
|
|
534
|
+
merged = {
|
|
535
|
+
...pendingInput,
|
|
536
|
+
...reviewerInput !== null && typeof reviewerInput === "object" ? reviewerInput : {}
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
const mergedObj = merged !== null && typeof merged === "object" ? merged : { value: merged };
|
|
540
|
+
params.input = {
|
|
541
|
+
...mergedObj,
|
|
542
|
+
[WORKER_QUEUE_KEY]: wq,
|
|
543
|
+
...decision !== void 0 ? { __hitlDecision: decision } : {}
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function getWorkerQueueContext(input, metadata) {
|
|
547
|
+
const fromInput = input !== null && typeof input === "object" && WORKER_QUEUE_KEY in input ? input[WORKER_QUEUE_KEY] : void 0;
|
|
548
|
+
const fromMeta = metadata !== void 0 && typeof metadata === "object" && WORKER_QUEUE_KEY in metadata ? metadata[WORKER_QUEUE_KEY] : void 0;
|
|
549
|
+
const q = fromInput ?? fromMeta;
|
|
550
|
+
if (q === null || typeof q !== "object") return void 0;
|
|
551
|
+
return q;
|
|
552
|
+
}
|
|
359
553
|
async function notifyQueueJobStep(queueJobId, action, params) {
|
|
360
554
|
try {
|
|
361
555
|
if (action === "append") {
|
|
@@ -375,7 +569,7 @@ async function notifyQueueJobStep(queueJobId, action, params) {
|
|
|
375
569
|
return;
|
|
376
570
|
}
|
|
377
571
|
if (params.stepIndex === void 0) return;
|
|
378
|
-
const status = action === "start" ? "running" : action === "complete" ? "completed" : action === "fail" ? "failed" : void 0;
|
|
572
|
+
const status = action === "start" ? "running" : action === "awaiting_approval" ? "awaiting_approval" : action === "complete" ? "completed" : action === "fail" ? "failed" : void 0;
|
|
379
573
|
if (!status) return;
|
|
380
574
|
await updateQueueJobStepInStore({
|
|
381
575
|
queueJobId,
|
|
@@ -395,6 +589,13 @@ async function notifyQueueJobStep(queueJobId, action, params) {
|
|
|
395
589
|
status
|
|
396
590
|
});
|
|
397
591
|
} catch (err) {
|
|
592
|
+
if (action === "append") {
|
|
593
|
+
console.error("[Worker] Queue append failed (rethrowing):", {
|
|
594
|
+
queueJobId,
|
|
595
|
+
error: err?.message ?? String(err)
|
|
596
|
+
});
|
|
597
|
+
throw err;
|
|
598
|
+
}
|
|
398
599
|
console.warn("[Worker] Queue job update error:", {
|
|
399
600
|
queueJobId,
|
|
400
601
|
action,
|
|
@@ -404,26 +605,152 @@ async function notifyQueueJobStep(queueJobId, action, params) {
|
|
|
404
605
|
}
|
|
405
606
|
function wrapHandlerForQueue(handler, queueRuntime) {
|
|
406
607
|
return async (params) => {
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
608
|
+
await maybeApplyHitlResumeMapper(params, queueRuntime);
|
|
609
|
+
const inputObj = params.input !== null && typeof params.input === "object" ? params.input : {};
|
|
610
|
+
const queueContextRaw = inputObj[WORKER_QUEUE_KEY];
|
|
611
|
+
const queueCtxForRetry = queueContextRaw && typeof queueContextRaw === "object" ? queueContextRaw : void 0;
|
|
612
|
+
const stepRetryConfig = queueCtxForRetry?.id && typeof queueCtxForRetry.stepIndex === "number" && typeof queueRuntime.getStepAt === "function" ? queueRuntime.getStepAt(queueCtxForRetry.id, queueCtxForRetry.stepIndex)?.retry : void 0;
|
|
613
|
+
const domainInput = { ...inputObj };
|
|
614
|
+
for (const key of QUEUE_ORCHESTRATION_KEYS) {
|
|
615
|
+
delete domainInput[key];
|
|
616
|
+
}
|
|
617
|
+
delete domainInput[WORKER_QUEUE_KEY];
|
|
618
|
+
params.input = domainInput;
|
|
619
|
+
let output;
|
|
620
|
+
if (stepRetryConfig && stepRetryConfig.on.length > 0) {
|
|
621
|
+
output = await executeWithRetry(
|
|
622
|
+
async (retryCtx) => {
|
|
623
|
+
params.ctx.retryContext = retryCtx;
|
|
624
|
+
return handler(params);
|
|
625
|
+
},
|
|
626
|
+
stepRetryConfig,
|
|
627
|
+
(retryCtx, delayMs) => {
|
|
628
|
+
const logger = params.ctx.logger;
|
|
629
|
+
if (logger?.warn) {
|
|
630
|
+
logger.warn(
|
|
631
|
+
`[queue-retry] Retrying step (attempt ${retryCtx.attempt}/${retryCtx.maxAttempts}): ${retryCtx.lastError.message}`,
|
|
632
|
+
{ delayMs }
|
|
633
|
+
);
|
|
634
|
+
} else {
|
|
635
|
+
console.warn("[queue-retry] Step retry", { attempt: retryCtx.attempt, error: retryCtx.lastError.message, delayMs });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
);
|
|
639
|
+
} else {
|
|
640
|
+
output = await handler(params);
|
|
641
|
+
}
|
|
642
|
+
if (!queueContextRaw || typeof queueContextRaw !== "object") {
|
|
643
|
+
return output;
|
|
644
|
+
}
|
|
645
|
+
const queueContext = queueContextRaw;
|
|
646
|
+
if (!queueContext.id) {
|
|
410
647
|
return output;
|
|
411
648
|
}
|
|
412
649
|
const { id: queueId, stepIndex, initialInput, queueJobId } = queueContext;
|
|
413
|
-
const
|
|
414
|
-
const
|
|
650
|
+
const arrayStepIndex = queueContext.arrayStepIndex ?? stepIndex;
|
|
651
|
+
const jobId = params.ctx.jobId;
|
|
652
|
+
const workerId = params.ctx.workerId ?? "";
|
|
415
653
|
const next = queueRuntime.getNextStep(queueId, stepIndex);
|
|
416
654
|
const childJobId = next ? `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}` : void 0;
|
|
655
|
+
const iterationCount = queueContext.iterationCount ?? 0;
|
|
656
|
+
if (typeof queueRuntime.invokeLoop === "function") {
|
|
657
|
+
const currentStep = typeof queueRuntime.getStepAt === "function" ? queueRuntime.getStepAt(queueId, stepIndex) : void 0;
|
|
658
|
+
const maxIterations = currentStep?.loop?.maxIterations ?? 50;
|
|
659
|
+
if (iterationCount < maxIterations - 1) {
|
|
660
|
+
let previousOutputsForLoop = [];
|
|
661
|
+
let stepsLengthBeforeAppend = arrayStepIndex + 1;
|
|
662
|
+
if (queueJobId && typeof queueRuntime.getQueueJob === "function") {
|
|
663
|
+
try {
|
|
664
|
+
const job = await queueRuntime.getQueueJob(queueJobId);
|
|
665
|
+
if (job?.steps) {
|
|
666
|
+
previousOutputsForLoop = job.steps.slice(0, stepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
|
|
667
|
+
stepsLengthBeforeAppend = job.steps.length;
|
|
668
|
+
}
|
|
669
|
+
} catch {
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
previousOutputsForLoop = previousOutputsForLoop.concat([{ stepIndex, workerId, output }]);
|
|
673
|
+
const shouldLoop = await queueRuntime.invokeLoop(queueId, stepIndex, {
|
|
674
|
+
output,
|
|
675
|
+
stepIndex,
|
|
676
|
+
iterationCount,
|
|
677
|
+
initialInput,
|
|
678
|
+
previousOutputs: previousOutputsForLoop
|
|
679
|
+
});
|
|
680
|
+
if (shouldLoop) {
|
|
681
|
+
const loopJobId = `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
682
|
+
let loopInput = output;
|
|
683
|
+
if (typeof queueRuntime.invokeChain === "function") {
|
|
684
|
+
loopInput = await queueRuntime.invokeChain(queueId, stepIndex, {
|
|
685
|
+
initialInput,
|
|
686
|
+
previousOutputs: previousOutputsForLoop
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
const loopInputWithQueue = {
|
|
690
|
+
...loopInput !== null && typeof loopInput === "object" ? loopInput : { value: loopInput },
|
|
691
|
+
[WORKER_QUEUE_KEY]: {
|
|
692
|
+
id: queueId,
|
|
693
|
+
stepIndex,
|
|
694
|
+
// definition index stays fixed
|
|
695
|
+
arrayStepIndex: stepsLengthBeforeAppend,
|
|
696
|
+
// actual index for next iteration
|
|
697
|
+
initialInput,
|
|
698
|
+
queueJobId,
|
|
699
|
+
iterationCount: iterationCount + 1
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
if (queueJobId) {
|
|
703
|
+
await notifyQueueJobStep(queueJobId, "append", { workerJobId: loopJobId, workerId });
|
|
704
|
+
}
|
|
705
|
+
if (queueJobId && typeof arrayStepIndex === "number") {
|
|
706
|
+
await notifyQueueJobStep(queueJobId, "complete", {
|
|
707
|
+
queueId,
|
|
708
|
+
stepIndex: arrayStepIndex,
|
|
709
|
+
workerJobId: jobId,
|
|
710
|
+
workerId,
|
|
711
|
+
output
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
if (currentStep?.requiresApproval && queueJobId) {
|
|
715
|
+
const hitlUiSpec = currentStep.hitl && typeof currentStep.hitl === "object" && "ui" in currentStep.hitl ? currentStep.hitl.ui : void 0;
|
|
716
|
+
const pendingInput = {
|
|
717
|
+
...loopInputWithQueue,
|
|
718
|
+
...hitlUiSpec !== void 0 ? { hitl: { uiSpec: hitlUiSpec } } : {},
|
|
719
|
+
__hitlPending: {
|
|
720
|
+
queueId,
|
|
721
|
+
queueJobId,
|
|
722
|
+
stepIndex,
|
|
723
|
+
workerId,
|
|
724
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
await notifyQueueJobStep(queueJobId, "awaiting_approval", {
|
|
728
|
+
queueId,
|
|
729
|
+
stepIndex: stepsLengthBeforeAppend,
|
|
730
|
+
workerJobId: loopJobId,
|
|
731
|
+
workerId,
|
|
732
|
+
input: pendingInput
|
|
733
|
+
});
|
|
734
|
+
return output;
|
|
735
|
+
}
|
|
736
|
+
await params.ctx.dispatchWorker(workerId, loopInputWithQueue, {
|
|
737
|
+
await: false,
|
|
738
|
+
jobId: loopJobId
|
|
739
|
+
});
|
|
740
|
+
return output;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
417
744
|
if (next && queueJobId) {
|
|
418
745
|
await notifyQueueJobStep(queueJobId, "append", {
|
|
419
746
|
workerJobId: childJobId,
|
|
420
747
|
workerId: next.workerId
|
|
421
748
|
});
|
|
422
749
|
}
|
|
423
|
-
if (queueJobId && typeof
|
|
750
|
+
if (queueJobId && typeof arrayStepIndex === "number") {
|
|
424
751
|
await notifyQueueJobStep(queueJobId, "complete", {
|
|
425
752
|
queueId,
|
|
426
|
-
stepIndex,
|
|
753
|
+
stepIndex: arrayStepIndex,
|
|
427
754
|
workerJobId: jobId,
|
|
428
755
|
workerId,
|
|
429
756
|
output
|
|
@@ -433,7 +760,7 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
433
760
|
return output;
|
|
434
761
|
}
|
|
435
762
|
let nextInput = output;
|
|
436
|
-
if (
|
|
763
|
+
if (typeof queueRuntime.invokeChain === "function") {
|
|
437
764
|
let previousOutputs = [];
|
|
438
765
|
if (queueJobId && typeof queueRuntime.getQueueJob === "function") {
|
|
439
766
|
try {
|
|
@@ -441,7 +768,7 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
441
768
|
if (job?.steps) {
|
|
442
769
|
const fromStore = job.steps.slice(0, stepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
|
|
443
770
|
previousOutputs = fromStore.concat([
|
|
444
|
-
{ stepIndex, workerId: params.ctx
|
|
771
|
+
{ stepIndex, workerId: params.ctx.workerId ?? "", output }
|
|
445
772
|
]);
|
|
446
773
|
}
|
|
447
774
|
} catch (e) {
|
|
@@ -450,12 +777,10 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
450
777
|
}
|
|
451
778
|
}
|
|
452
779
|
}
|
|
453
|
-
nextInput = await queueRuntime.
|
|
454
|
-
queueId,
|
|
455
|
-
stepIndex + 1,
|
|
780
|
+
nextInput = await queueRuntime.invokeChain(queueId, stepIndex + 1, {
|
|
456
781
|
initialInput,
|
|
457
782
|
previousOutputs
|
|
458
|
-
);
|
|
783
|
+
});
|
|
459
784
|
}
|
|
460
785
|
const nextInputWithQueue = {
|
|
461
786
|
...nextInput !== null && typeof nextInput === "object" ? nextInput : { value: nextInput },
|
|
@@ -475,6 +800,37 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
475
800
|
delaySeconds: next.delaySeconds
|
|
476
801
|
});
|
|
477
802
|
}
|
|
803
|
+
if (next.requiresApproval && queueJobId && typeof stepIndex === "number") {
|
|
804
|
+
const hitlUiSpec = next.hitl && typeof next.hitl === "object" && "ui" in next.hitl ? next.hitl.ui : void 0;
|
|
805
|
+
const pendingInput = {
|
|
806
|
+
...nextInputWithQueue,
|
|
807
|
+
...hitlUiSpec !== void 0 ? { hitl: { uiSpec: hitlUiSpec } } : {},
|
|
808
|
+
__hitlPending: {
|
|
809
|
+
queueId,
|
|
810
|
+
queueJobId,
|
|
811
|
+
stepIndex: stepIndex + 1,
|
|
812
|
+
workerId: next.workerId,
|
|
813
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
await notifyQueueJobStep(queueJobId, "awaiting_approval", {
|
|
817
|
+
queueId,
|
|
818
|
+
stepIndex: stepIndex + 1,
|
|
819
|
+
workerJobId: childJobId,
|
|
820
|
+
workerId: next.workerId,
|
|
821
|
+
input: pendingInput
|
|
822
|
+
});
|
|
823
|
+
if (debug) {
|
|
824
|
+
console.log("[Worker] Queue chain paused for HITL approval:", {
|
|
825
|
+
queueId,
|
|
826
|
+
queueJobId,
|
|
827
|
+
nextStep: stepIndex + 1,
|
|
828
|
+
nextWorkerId: next.workerId,
|
|
829
|
+
pendingWorkerJobId: childJobId
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
return output;
|
|
833
|
+
}
|
|
478
834
|
await params.ctx.dispatchWorker(next.workerId, nextInputWithQueue, {
|
|
479
835
|
await: false,
|
|
480
836
|
delaySeconds: next.delaySeconds,
|
|
@@ -498,6 +854,7 @@ function createDispatchWorker(parentJobId, parentWorkerId, parentContext, jobSto
|
|
|
498
854
|
const metadata = options?.metadata ?? {};
|
|
499
855
|
const serializedContext = {};
|
|
500
856
|
if (parentContext.requestId) serializedContext.requestId = parentContext.requestId;
|
|
857
|
+
if (parentContext.userId) serializedContext.userId = parentContext.userId;
|
|
501
858
|
const messageBody = {
|
|
502
859
|
workerId: calleeWorkerId,
|
|
503
860
|
jobId: childJobId,
|
|
@@ -587,13 +944,14 @@ async function sendWebhook(webhookUrl, payload) {
|
|
|
587
944
|
});
|
|
588
945
|
}
|
|
589
946
|
}
|
|
590
|
-
function createLambdaHandler(handler, outputSchema) {
|
|
947
|
+
function createLambdaHandler(handler, outputSchema, options) {
|
|
591
948
|
return async (event, lambdaContext) => {
|
|
592
949
|
const promises = event.Records.map(async (record) => {
|
|
593
950
|
let messageBody = null;
|
|
594
951
|
try {
|
|
595
952
|
messageBody = JSON.parse(record.body);
|
|
596
|
-
const { workerId, jobId, input, context, webhookUrl, metadata = {} } = messageBody;
|
|
953
|
+
const { workerId, jobId, input, context, webhookUrl, metadata = {}, userId: messageUserId, maxTokens } = messageBody;
|
|
954
|
+
const userId = context.userId ?? messageUserId;
|
|
597
955
|
const raw = (process.env.WORKER_DATABASE_TYPE || "upstash-redis").toLowerCase();
|
|
598
956
|
const jobStoreType = raw === "mongodb" ? "mongodb" : "upstash-redis";
|
|
599
957
|
if (jobStoreType === "upstash-redis" && isRedisJobStoreConfigured()) {
|
|
@@ -619,33 +977,45 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
619
977
|
}
|
|
620
978
|
let jobStore;
|
|
621
979
|
if (jobStoreType === "upstash-redis" && isRedisJobStoreConfigured()) {
|
|
622
|
-
await upsertRedisJob(jobId, workerId, input, metadata);
|
|
623
|
-
jobStore = createRedisJobStore(workerId, jobId, input, metadata);
|
|
980
|
+
await upsertRedisJob(jobId, workerId, input, metadata, userId);
|
|
981
|
+
jobStore = createRedisJobStore(workerId, jobId, input, metadata, userId);
|
|
624
982
|
} else if (jobStoreType === "mongodb" || isMongoJobStoreConfigured()) {
|
|
625
|
-
await upsertJob(jobId, workerId, input, metadata);
|
|
626
|
-
jobStore = createMongoJobStore(workerId, jobId, input, metadata);
|
|
983
|
+
await upsertJob(jobId, workerId, input, metadata, userId);
|
|
984
|
+
jobStore = createMongoJobStore(workerId, jobId, input, metadata, userId);
|
|
985
|
+
}
|
|
986
|
+
if (userId) {
|
|
987
|
+
console.log(`[WORKER_USER:${userId}]`, { jobId, workerId, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
627
988
|
}
|
|
628
989
|
const baseContext = {
|
|
629
990
|
jobId,
|
|
630
991
|
workerId,
|
|
631
992
|
requestId: context.requestId || lambdaContext.awsRequestId,
|
|
993
|
+
...userId ? { userId } : {},
|
|
632
994
|
...context
|
|
633
995
|
};
|
|
996
|
+
const tokenTracker = createTokenTracker(maxTokens ?? null);
|
|
997
|
+
const logger = createWorkerLogger(jobId, workerId);
|
|
634
998
|
const handlerContext = {
|
|
635
999
|
...baseContext,
|
|
636
1000
|
...jobStore ? { jobStore } : {},
|
|
637
|
-
logger
|
|
638
|
-
dispatchWorker: createDispatchWorker(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
jobStore
|
|
643
|
-
|
|
1001
|
+
logger,
|
|
1002
|
+
dispatchWorker: createDispatchWorker(jobId, workerId, baseContext, jobStore),
|
|
1003
|
+
reportTokenUsage: async (usage) => {
|
|
1004
|
+
tokenTracker.report(usage);
|
|
1005
|
+
const state = tokenTracker.getState();
|
|
1006
|
+
if (jobStore) {
|
|
1007
|
+
await jobStore.update({ metadata: { tokenUsage: state } }).catch((e) => {
|
|
1008
|
+
logger.warn("Failed to persist tokenUsage to job store", { error: e?.message });
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
getTokenBudget: () => tokenTracker.getBudgetInfo(),
|
|
1013
|
+
retryContext: void 0
|
|
644
1014
|
};
|
|
645
1015
|
if (jobStore) {
|
|
646
1016
|
try {
|
|
647
1017
|
await jobStore.update({ status: "running" });
|
|
648
|
-
const queueCtxForLog = input
|
|
1018
|
+
const queueCtxForLog = getWorkerQueueContext(input, metadata);
|
|
649
1019
|
console.log("[Worker] Job status updated to running:", {
|
|
650
1020
|
jobId,
|
|
651
1021
|
workerId,
|
|
@@ -660,7 +1030,7 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
660
1030
|
});
|
|
661
1031
|
}
|
|
662
1032
|
}
|
|
663
|
-
const queueCtx = input
|
|
1033
|
+
const queueCtx = getWorkerQueueContext(input, metadata);
|
|
664
1034
|
if (queueCtx?.queueJobId && typeof queueCtx.stepIndex === "number") {
|
|
665
1035
|
if (queueCtx.stepIndex === 0) {
|
|
666
1036
|
try {
|
|
@@ -669,7 +1039,8 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
669
1039
|
queueId: queueCtx.id,
|
|
670
1040
|
firstWorkerId: workerId,
|
|
671
1041
|
firstWorkerJobId: jobId,
|
|
672
|
-
metadata
|
|
1042
|
+
metadata,
|
|
1043
|
+
userId
|
|
673
1044
|
});
|
|
674
1045
|
} catch (e) {
|
|
675
1046
|
console.warn("[Worker] Failed to upsert initial queue job:", {
|
|
@@ -681,7 +1052,9 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
681
1052
|
}
|
|
682
1053
|
await notifyQueueJobStep(queueCtx.queueJobId, "start", {
|
|
683
1054
|
queueId: queueCtx.id,
|
|
684
|
-
|
|
1055
|
+
// Use arrayStepIndex when set — it tracks the actual steps[] position for
|
|
1056
|
+
// looping steps where the definition index stays fixed across iterations.
|
|
1057
|
+
stepIndex: queueCtx.arrayStepIndex ?? queueCtx.stepIndex,
|
|
685
1058
|
workerJobId: jobId,
|
|
686
1059
|
workerId,
|
|
687
1060
|
input
|
|
@@ -689,12 +1062,21 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
689
1062
|
}
|
|
690
1063
|
let output;
|
|
691
1064
|
try {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1065
|
+
const workerRetryConfig = options?.retry;
|
|
1066
|
+
const executeHandler = async (retryCtx) => {
|
|
1067
|
+
handlerContext.retryContext = retryCtx;
|
|
1068
|
+
const result = await handler({ input, ctx: handlerContext });
|
|
1069
|
+
return outputSchema ? outputSchema.parse(result) : result;
|
|
1070
|
+
};
|
|
1071
|
+
if (workerRetryConfig && workerRetryConfig.on.length > 0) {
|
|
1072
|
+
output = await executeWithRetry(executeHandler, workerRetryConfig, (retryCtx, delayMs) => {
|
|
1073
|
+
logger.warn(
|
|
1074
|
+
`[worker-retry] Retrying handler (attempt ${retryCtx.attempt}/${retryCtx.maxAttempts}): ${retryCtx.lastError.message}`,
|
|
1075
|
+
{ delayMs }
|
|
1076
|
+
);
|
|
1077
|
+
});
|
|
1078
|
+
} else {
|
|
1079
|
+
output = await executeHandler(void 0);
|
|
698
1080
|
}
|
|
699
1081
|
} catch (error) {
|
|
700
1082
|
const errorPayload = {
|
|
@@ -726,7 +1108,7 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
726
1108
|
});
|
|
727
1109
|
}
|
|
728
1110
|
}
|
|
729
|
-
const queueCtxFail = input
|
|
1111
|
+
const queueCtxFail = getWorkerQueueContext(input, metadata);
|
|
730
1112
|
if (queueCtxFail?.queueJobId && typeof queueCtxFail.stepIndex === "number") {
|
|
731
1113
|
await notifyQueueJobStep(queueCtxFail.queueJobId, "fail", {
|
|
732
1114
|
queueId: queueCtxFail.id,
|
|
@@ -789,9 +1171,13 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
789
1171
|
}
|
|
790
1172
|
|
|
791
1173
|
export {
|
|
1174
|
+
matchesRetryPattern,
|
|
1175
|
+
executeWithRetry,
|
|
1176
|
+
TokenBudgetExceededError,
|
|
1177
|
+
createTokenTracker,
|
|
792
1178
|
SQS_MAX_DELAY_SECONDS,
|
|
793
1179
|
createWorkerLogger,
|
|
794
1180
|
wrapHandlerForQueue,
|
|
795
1181
|
createLambdaHandler
|
|
796
1182
|
};
|
|
797
|
-
//# sourceMappingURL=chunk-
|
|
1183
|
+
//# sourceMappingURL=chunk-QHX55IML.mjs.map
|