@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
package/dist/index.js
CHANGED
|
@@ -20,21 +20,31 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
QUEUE_ORCHESTRATION_KEYS: () => QUEUE_ORCHESTRATION_KEYS,
|
|
23
24
|
SQS_MAX_DELAY_SECONDS: () => SQS_MAX_DELAY_SECONDS,
|
|
25
|
+
TokenBudgetExceededError: () => TokenBudgetExceededError,
|
|
24
26
|
clearWorkersConfigCache: () => clearWorkersConfigCache,
|
|
25
27
|
createLambdaEntrypoint: () => createLambdaEntrypoint,
|
|
26
28
|
createLambdaHandler: () => createLambdaHandler,
|
|
27
29
|
createWorker: () => createWorker,
|
|
28
30
|
createWorkerLogger: () => createWorkerLogger,
|
|
31
|
+
defaultMapChainContinueFromPrevious: () => defaultMapChainContinueFromPrevious,
|
|
32
|
+
defaultMapChainPassthrough: () => defaultMapChainPassthrough,
|
|
33
|
+
defineHitlConfig: () => defineHitlConfig,
|
|
29
34
|
defineWorkerQueue: () => defineWorkerQueue,
|
|
30
35
|
dispatch: () => dispatch,
|
|
31
36
|
dispatchLocal: () => dispatchLocal,
|
|
32
37
|
dispatchQueue: () => dispatchQueue,
|
|
33
38
|
dispatchWorker: () => dispatchWorker,
|
|
39
|
+
executeWithRetry: () => executeWithRetry,
|
|
34
40
|
getQueueStartUrl: () => getQueueStartUrl,
|
|
35
41
|
getWorkersConfig: () => getWorkersConfig,
|
|
36
42
|
getWorkersTriggerUrl: () => getWorkersTriggerUrl,
|
|
43
|
+
matchesRetryPattern: () => matchesRetryPattern,
|
|
44
|
+
queueOrchestrationFieldsSchema: () => queueOrchestrationFieldsSchema,
|
|
45
|
+
repeatStep: () => repeatStep,
|
|
37
46
|
resolveQueueUrl: () => resolveQueueUrl,
|
|
47
|
+
withQueueOrchestrationEnvelope: () => withQueueOrchestrationEnvelope,
|
|
38
48
|
wrapHandlerForQueue: () => wrapHandlerForQueue
|
|
39
49
|
});
|
|
40
50
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -78,6 +88,9 @@ function serializeContext(ctx) {
|
|
|
78
88
|
if (ctx.requestId) {
|
|
79
89
|
serialized.requestId = ctx.requestId;
|
|
80
90
|
}
|
|
91
|
+
if (ctx.userId) {
|
|
92
|
+
serialized.userId = ctx.userId;
|
|
93
|
+
}
|
|
81
94
|
if (ctx.metadata && typeof ctx.metadata === "object") {
|
|
82
95
|
Object.assign(serialized, ctx.metadata);
|
|
83
96
|
}
|
|
@@ -92,6 +105,8 @@ async function dispatch(workerId, input, inputSchema, options, ctx) {
|
|
|
92
105
|
const jobId = options.jobId || `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
93
106
|
const triggerUrl = getWorkersTriggerUrl();
|
|
94
107
|
const serializedContext = ctx ? serializeContext(ctx) : {};
|
|
108
|
+
const userId = options.userId ?? ctx?.userId;
|
|
109
|
+
if (userId) serializedContext.userId = userId;
|
|
95
110
|
const messageBody = {
|
|
96
111
|
workerId,
|
|
97
112
|
jobId,
|
|
@@ -99,7 +114,8 @@ async function dispatch(workerId, input, inputSchema, options, ctx) {
|
|
|
99
114
|
context: serializedContext,
|
|
100
115
|
webhookUrl: options.webhookUrl,
|
|
101
116
|
metadata: options.metadata || {},
|
|
102
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
117
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
118
|
+
...options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {}
|
|
103
119
|
};
|
|
104
120
|
const headers = {
|
|
105
121
|
"Content-Type": "application/json"
|
|
@@ -134,6 +150,8 @@ async function dispatchWorker(workerId, input, options = {}, ctx) {
|
|
|
134
150
|
const jobId = options.jobId || `job-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
135
151
|
const triggerUrl = getWorkersTriggerUrl();
|
|
136
152
|
const serializedContext = ctx ? serializeContext(ctx) : {};
|
|
153
|
+
const userId = options.userId ?? ctx?.userId;
|
|
154
|
+
if (userId) serializedContext.userId = userId;
|
|
137
155
|
const messageBody = {
|
|
138
156
|
workerId,
|
|
139
157
|
jobId,
|
|
@@ -141,7 +159,8 @@ async function dispatchWorker(workerId, input, options = {}, ctx) {
|
|
|
141
159
|
context: serializedContext,
|
|
142
160
|
webhookUrl: options.webhookUrl,
|
|
143
161
|
metadata: options.metadata || {},
|
|
144
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
162
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
163
|
+
...options.maxTokens !== void 0 ? { maxTokens: options.maxTokens } : {}
|
|
145
164
|
};
|
|
146
165
|
const headers = { "Content-Type": "application/json" };
|
|
147
166
|
const triggerKey = process.env.WORKERS_TRIGGER_API_KEY;
|
|
@@ -171,6 +190,7 @@ async function dispatchQueue(queueId, initialInput, options = {}, _ctx) {
|
|
|
171
190
|
const headers = { "Content-Type": "application/json" };
|
|
172
191
|
const triggerKey = process.env.WORKERS_TRIGGER_API_KEY;
|
|
173
192
|
if (triggerKey) headers["x-workers-trigger-key"] = triggerKey;
|
|
193
|
+
const userId = options.userId ?? _ctx?.userId;
|
|
174
194
|
const response = await fetch(queueStartUrl, {
|
|
175
195
|
method: "POST",
|
|
176
196
|
headers,
|
|
@@ -179,6 +199,7 @@ async function dispatchQueue(queueId, initialInput, options = {}, _ctx) {
|
|
|
179
199
|
initialInput: normalizedInput,
|
|
180
200
|
metadata: options.metadata ?? {},
|
|
181
201
|
jobId,
|
|
202
|
+
...userId ? { userId } : {},
|
|
182
203
|
...options.webhookUrl ? { webhookUrl: options.webhookUrl } : {}
|
|
183
204
|
})
|
|
184
205
|
});
|
|
@@ -236,7 +257,7 @@ async function getJobById(jobId) {
|
|
|
236
257
|
return null;
|
|
237
258
|
}
|
|
238
259
|
}
|
|
239
|
-
function createMongoJobStore(workerId, jobId, input, metadata) {
|
|
260
|
+
function createMongoJobStore(workerId, jobId, input, metadata, userId) {
|
|
240
261
|
return {
|
|
241
262
|
update: async (update) => {
|
|
242
263
|
try {
|
|
@@ -275,6 +296,7 @@ function createMongoJobStore(workerId, jobId, input, metadata) {
|
|
|
275
296
|
output: update.output,
|
|
276
297
|
error: update.error,
|
|
277
298
|
metadata: metadataUpdate,
|
|
299
|
+
...userId ? { userId } : {},
|
|
278
300
|
createdAt: now,
|
|
279
301
|
updatedAt: now,
|
|
280
302
|
completedAt: set.completedAt
|
|
@@ -340,7 +362,7 @@ function createMongoJobStore(workerId, jobId, input, metadata) {
|
|
|
340
362
|
}
|
|
341
363
|
};
|
|
342
364
|
}
|
|
343
|
-
async function upsertJob(jobId, workerId, input, metadata) {
|
|
365
|
+
async function upsertJob(jobId, workerId, input, metadata, userId) {
|
|
344
366
|
const coll = await getCollection();
|
|
345
367
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
346
368
|
await coll.updateOne(
|
|
@@ -353,6 +375,7 @@ async function upsertJob(jobId, workerId, input, metadata) {
|
|
|
353
375
|
status: "queued",
|
|
354
376
|
input: input ?? {},
|
|
355
377
|
metadata: metadata ?? {},
|
|
378
|
+
...userId ? { userId } : {},
|
|
356
379
|
createdAt: now,
|
|
357
380
|
updatedAt: now
|
|
358
381
|
}
|
|
@@ -431,13 +454,14 @@ async function loadJob(jobId) {
|
|
|
431
454
|
error: parseJson(data.error),
|
|
432
455
|
metadata: parseJson(data.metadata) ?? {},
|
|
433
456
|
internalJobs,
|
|
457
|
+
...data.userId ? { userId: data.userId } : {},
|
|
434
458
|
createdAt: data.createdAt,
|
|
435
459
|
updatedAt: data.updatedAt,
|
|
436
460
|
completedAt: data.completedAt
|
|
437
461
|
};
|
|
438
462
|
return record;
|
|
439
463
|
}
|
|
440
|
-
function createRedisJobStore(workerId, jobId, input, metadata) {
|
|
464
|
+
function createRedisJobStore(workerId, jobId, input, metadata, userId) {
|
|
441
465
|
return {
|
|
442
466
|
update: async (update) => {
|
|
443
467
|
const redis = getRedis();
|
|
@@ -495,28 +519,20 @@ function createRedisJobStore(workerId, jobId, input, metadata) {
|
|
|
495
519
|
}
|
|
496
520
|
};
|
|
497
521
|
}
|
|
498
|
-
async function upsertRedisJob(jobId, workerId, input, metadata) {
|
|
522
|
+
async function upsertRedisJob(jobId, workerId, input, metadata, userId) {
|
|
499
523
|
const redis = getRedis();
|
|
500
524
|
const key = jobKey(jobId);
|
|
501
525
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
502
|
-
const doc = {
|
|
503
|
-
jobId,
|
|
504
|
-
workerId,
|
|
505
|
-
status: "queued",
|
|
506
|
-
input,
|
|
507
|
-
metadata,
|
|
508
|
-
createdAt: now,
|
|
509
|
-
updatedAt: now
|
|
510
|
-
};
|
|
511
526
|
const toSet = {
|
|
512
527
|
jobId,
|
|
513
528
|
workerId,
|
|
514
|
-
status:
|
|
515
|
-
input: JSON.stringify(
|
|
516
|
-
metadata: JSON.stringify(
|
|
529
|
+
status: "queued",
|
|
530
|
+
input: JSON.stringify(input ?? {}),
|
|
531
|
+
metadata: JSON.stringify(metadata ?? {}),
|
|
517
532
|
createdAt: now,
|
|
518
533
|
updatedAt: now
|
|
519
534
|
};
|
|
535
|
+
if (userId) toSet.userId = userId;
|
|
520
536
|
await redis.hset(key, toSet);
|
|
521
537
|
if (jobTtlSeconds > 0) {
|
|
522
538
|
await redis.expire(key, jobTtlSeconds);
|
|
@@ -608,6 +624,7 @@ async function loadQueueJobRedis(queueJobId) {
|
|
|
608
624
|
status: String(d.status ?? "running"),
|
|
609
625
|
steps: stepsFromHash(d.steps),
|
|
610
626
|
metadata: metadataFromHash(d.metadata),
|
|
627
|
+
...d.userId ? { userId: String(d.userId) } : {},
|
|
611
628
|
createdAt: String(d.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
612
629
|
updatedAt: String(d.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
613
630
|
completedAt: d.completedAt != null ? String(d.completedAt) : void 0
|
|
@@ -627,9 +644,8 @@ async function saveQueueJobRedis(record) {
|
|
|
627
644
|
createdAt: record.createdAt || now,
|
|
628
645
|
updatedAt: record.updatedAt || now
|
|
629
646
|
};
|
|
630
|
-
if (record.completedAt)
|
|
631
|
-
|
|
632
|
-
}
|
|
647
|
+
if (record.completedAt) toSet.completedAt = record.completedAt;
|
|
648
|
+
if (record.userId) toSet.userId = record.userId;
|
|
633
649
|
await redis.hset(key, toSet);
|
|
634
650
|
if (queueJobTtlSeconds > 0) {
|
|
635
651
|
await redis.expire(key, queueJobTtlSeconds);
|
|
@@ -646,7 +662,7 @@ function preferRedis() {
|
|
|
646
662
|
return getStoreType() !== "mongodb" && Boolean((redisUrl2 || "").trim() && (redisToken2 || "").trim());
|
|
647
663
|
}
|
|
648
664
|
async function upsertInitialQueueJob(options) {
|
|
649
|
-
const { queueJobId, queueId, firstWorkerId, firstWorkerJobId, metadata } = options;
|
|
665
|
+
const { queueJobId, queueId, firstWorkerId, firstWorkerJobId, metadata, userId } = options;
|
|
650
666
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
651
667
|
if (preferMongo()) {
|
|
652
668
|
const coll = await getMongoQueueCollection();
|
|
@@ -683,6 +699,7 @@ async function upsertInitialQueueJob(options) {
|
|
|
683
699
|
}
|
|
684
700
|
],
|
|
685
701
|
metadata: metadata ?? {},
|
|
702
|
+
...userId ? { userId } : {},
|
|
686
703
|
createdAt: now,
|
|
687
704
|
updatedAt: now
|
|
688
705
|
};
|
|
@@ -721,6 +738,7 @@ async function upsertInitialQueueJob(options) {
|
|
|
721
738
|
}
|
|
722
739
|
],
|
|
723
740
|
metadata: metadata ?? {},
|
|
741
|
+
...userId ? { userId } : {},
|
|
724
742
|
createdAt: now,
|
|
725
743
|
updatedAt: now
|
|
726
744
|
};
|
|
@@ -833,6 +851,156 @@ async function appendQueueJobStepInStore(options) {
|
|
|
833
851
|
}
|
|
834
852
|
}
|
|
835
853
|
|
|
854
|
+
// src/queue.ts
|
|
855
|
+
var QUEUE_ORCHESTRATION_KEYS = [
|
|
856
|
+
"__workerQueue",
|
|
857
|
+
"__hitlInput",
|
|
858
|
+
"__hitlDecision",
|
|
859
|
+
"__hitlPending",
|
|
860
|
+
"hitl"
|
|
861
|
+
];
|
|
862
|
+
function repeatStep(count, factory) {
|
|
863
|
+
return Array.from({ length: count }, (_, i) => factory(i));
|
|
864
|
+
}
|
|
865
|
+
function defineWorkerQueue(config) {
|
|
866
|
+
return config;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/retryConfig.ts
|
|
870
|
+
var BUILT_IN_PATTERNS = {
|
|
871
|
+
"rate-limit": {
|
|
872
|
+
match: (err) => /rate.?limit|too.?many.?requests/i.test(err.message) || err.status === 429 || err.code === 429 || err.name === "RateLimitError",
|
|
873
|
+
delayMs: (attempt) => attempt * 1e4,
|
|
874
|
+
// 10s, 20s, 30s…
|
|
875
|
+
injectContext: false
|
|
876
|
+
},
|
|
877
|
+
"json-parse": {
|
|
878
|
+
match: (err) => err.name === "SyntaxError" || err.name === "ZodError" || /json|parse|unexpected.?token|invalid.?format/i.test(err.message),
|
|
879
|
+
delayMs: (_attempt) => 0,
|
|
880
|
+
// Immediate — model self-corrects from ctx
|
|
881
|
+
injectContext: true
|
|
882
|
+
},
|
|
883
|
+
overloaded: {
|
|
884
|
+
match: (err) => /overloaded|model.?is.?busy/i.test(err.message) || err.status === 529 || err.code === 529,
|
|
885
|
+
delayMs: (attempt) => attempt * 15e3,
|
|
886
|
+
// 15s, 30s…
|
|
887
|
+
injectContext: false
|
|
888
|
+
},
|
|
889
|
+
"server-error": {
|
|
890
|
+
match: (err) => /internal.?server.?error|service.?unavailable|bad.?gateway/i.test(err.message) || typeof err.status === "number" && err.status >= 500 && err.status < 600,
|
|
891
|
+
delayMs: (attempt) => attempt * 5e3,
|
|
892
|
+
// 5s, 10s…
|
|
893
|
+
injectContext: false
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
function matchesRetryPattern(err, patterns, attempt) {
|
|
897
|
+
for (const pattern of patterns) {
|
|
898
|
+
if (typeof pattern === "string") {
|
|
899
|
+
const impl = BUILT_IN_PATTERNS[pattern];
|
|
900
|
+
if (impl.match(err)) {
|
|
901
|
+
return { matched: true, delayMs: impl.delayMs(attempt), injectContext: impl.injectContext };
|
|
902
|
+
}
|
|
903
|
+
} else {
|
|
904
|
+
let matched = false;
|
|
905
|
+
if (pattern.match instanceof RegExp) {
|
|
906
|
+
matched = pattern.match.test(err.message);
|
|
907
|
+
} else {
|
|
908
|
+
try {
|
|
909
|
+
matched = pattern.match(err);
|
|
910
|
+
} catch {
|
|
911
|
+
matched = false;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (matched) {
|
|
915
|
+
const delayMs = typeof pattern.delayMs === "function" ? pattern.delayMs(attempt) : pattern.delayMs ?? 0;
|
|
916
|
+
return { matched: true, delayMs, injectContext: pattern.injectContext ?? false };
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return { matched: false, delayMs: 0, injectContext: false };
|
|
921
|
+
}
|
|
922
|
+
async function executeWithRetry(fn, config, onRetry) {
|
|
923
|
+
const maxAttempts = config.maxAttempts ?? 3;
|
|
924
|
+
let lastError;
|
|
925
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
926
|
+
const retryCtx = attempt > 1 && lastError ? {
|
|
927
|
+
attempt,
|
|
928
|
+
maxAttempts,
|
|
929
|
+
lastError: {
|
|
930
|
+
message: lastError.message,
|
|
931
|
+
name: lastError.name,
|
|
932
|
+
stack: lastError.stack,
|
|
933
|
+
code: lastError.code ?? lastError.status
|
|
934
|
+
}
|
|
935
|
+
} : void 0;
|
|
936
|
+
try {
|
|
937
|
+
return await fn(retryCtx);
|
|
938
|
+
} catch (err) {
|
|
939
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
940
|
+
if (err?.name === "TokenBudgetExceededError") throw err;
|
|
941
|
+
if (attempt >= maxAttempts) throw err;
|
|
942
|
+
const retryAttemptNumber = attempt;
|
|
943
|
+
const { matched, delayMs } = matchesRetryPattern(lastError, config.on, retryAttemptNumber);
|
|
944
|
+
if (!matched) throw err;
|
|
945
|
+
const nextCtx = {
|
|
946
|
+
attempt: attempt + 1,
|
|
947
|
+
maxAttempts,
|
|
948
|
+
lastError: {
|
|
949
|
+
message: lastError.message,
|
|
950
|
+
name: lastError.name,
|
|
951
|
+
stack: lastError.stack,
|
|
952
|
+
code: lastError.code ?? lastError.status
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
onRetry?.(nextCtx, delayMs);
|
|
956
|
+
if (delayMs > 0) {
|
|
957
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
throw lastError ?? new Error("executeWithRetry: unknown error");
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/tokenBudget.ts
|
|
965
|
+
var TokenBudgetExceededError = class extends Error {
|
|
966
|
+
constructor(used, budget) {
|
|
967
|
+
super(`Token budget exceeded: used ${used} tokens (budget: ${budget})`);
|
|
968
|
+
this.name = "TokenBudgetExceededError";
|
|
969
|
+
this.used = used;
|
|
970
|
+
this.budget = budget;
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
function createTokenTracker(budget) {
|
|
974
|
+
let inputTokens = 0;
|
|
975
|
+
let outputTokens = 0;
|
|
976
|
+
function checkBudget() {
|
|
977
|
+
if (budget !== null) {
|
|
978
|
+
const total = inputTokens + outputTokens;
|
|
979
|
+
if (total > budget) {
|
|
980
|
+
throw new TokenBudgetExceededError(total, budget);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
report(usage) {
|
|
986
|
+
inputTokens += usage.inputTokens;
|
|
987
|
+
outputTokens += usage.outputTokens;
|
|
988
|
+
checkBudget();
|
|
989
|
+
},
|
|
990
|
+
getState() {
|
|
991
|
+
return { inputTokens, outputTokens, budget };
|
|
992
|
+
},
|
|
993
|
+
getBudgetInfo() {
|
|
994
|
+
const used = inputTokens + outputTokens;
|
|
995
|
+
return {
|
|
996
|
+
used,
|
|
997
|
+
budget,
|
|
998
|
+
remaining: budget !== null ? Math.max(0, budget - used) : null
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
|
|
836
1004
|
// src/handler.ts
|
|
837
1005
|
var SQS_MAX_DELAY_SECONDS = 900;
|
|
838
1006
|
function createWorkerLogger(jobId, workerId) {
|
|
@@ -855,6 +1023,67 @@ function createWorkerLogger(jobId, workerId) {
|
|
|
855
1023
|
};
|
|
856
1024
|
}
|
|
857
1025
|
var WORKER_QUEUE_KEY = "__workerQueue";
|
|
1026
|
+
async function loadPreviousOutputsBeforeStep(queueRuntime, queueJobId, beforeStepIndex) {
|
|
1027
|
+
if (!queueJobId || typeof queueRuntime.getQueueJob !== "function") {
|
|
1028
|
+
return [];
|
|
1029
|
+
}
|
|
1030
|
+
try {
|
|
1031
|
+
const job = await queueRuntime.getQueueJob(queueJobId);
|
|
1032
|
+
if (!job?.steps) return [];
|
|
1033
|
+
return job.steps.slice(0, beforeStepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
if (process.env.AI_WORKER_QUEUES_DEBUG === "1") {
|
|
1036
|
+
console.warn("[Worker] getQueueJob failed (resume mapping):", e?.message ?? e);
|
|
1037
|
+
}
|
|
1038
|
+
return [];
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
async function maybeApplyHitlResumeMapper(params, queueRuntime) {
|
|
1042
|
+
const inputObj = params.input;
|
|
1043
|
+
if (!inputObj || typeof inputObj !== "object") return;
|
|
1044
|
+
if (!("__hitlInput" in inputObj)) return;
|
|
1045
|
+
const wq = inputObj[WORKER_QUEUE_KEY];
|
|
1046
|
+
if (!wq?.id || typeof wq.stepIndex !== "number") return;
|
|
1047
|
+
const queueId = wq.id;
|
|
1048
|
+
const stepIndex = wq.stepIndex;
|
|
1049
|
+
const initialInput = wq.initialInput;
|
|
1050
|
+
const queueJobId = wq.queueJobId;
|
|
1051
|
+
const previousOutputs = await loadPreviousOutputsBeforeStep(queueRuntime, queueJobId, stepIndex);
|
|
1052
|
+
const pendingInput = { ...inputObj };
|
|
1053
|
+
for (const key of QUEUE_ORCHESTRATION_KEYS) {
|
|
1054
|
+
delete pendingInput[key];
|
|
1055
|
+
}
|
|
1056
|
+
delete pendingInput[WORKER_QUEUE_KEY];
|
|
1057
|
+
const reviewerInput = inputObj.__hitlInput;
|
|
1058
|
+
const decision = inputObj.__hitlDecision;
|
|
1059
|
+
let merged;
|
|
1060
|
+
if (typeof queueRuntime.invokeResume === "function") {
|
|
1061
|
+
merged = await queueRuntime.invokeResume(queueId, stepIndex, {
|
|
1062
|
+
initialInput,
|
|
1063
|
+
previousOutputs,
|
|
1064
|
+
reviewerInput,
|
|
1065
|
+
pendingInput
|
|
1066
|
+
});
|
|
1067
|
+
} else {
|
|
1068
|
+
merged = {
|
|
1069
|
+
...pendingInput,
|
|
1070
|
+
...reviewerInput !== null && typeof reviewerInput === "object" ? reviewerInput : {}
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
const mergedObj = merged !== null && typeof merged === "object" ? merged : { value: merged };
|
|
1074
|
+
params.input = {
|
|
1075
|
+
...mergedObj,
|
|
1076
|
+
[WORKER_QUEUE_KEY]: wq,
|
|
1077
|
+
...decision !== void 0 ? { __hitlDecision: decision } : {}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
function getWorkerQueueContext(input, metadata) {
|
|
1081
|
+
const fromInput = input !== null && typeof input === "object" && WORKER_QUEUE_KEY in input ? input[WORKER_QUEUE_KEY] : void 0;
|
|
1082
|
+
const fromMeta = metadata !== void 0 && typeof metadata === "object" && WORKER_QUEUE_KEY in metadata ? metadata[WORKER_QUEUE_KEY] : void 0;
|
|
1083
|
+
const q = fromInput ?? fromMeta;
|
|
1084
|
+
if (q === null || typeof q !== "object") return void 0;
|
|
1085
|
+
return q;
|
|
1086
|
+
}
|
|
858
1087
|
async function notifyQueueJobStep(queueJobId, action, params) {
|
|
859
1088
|
try {
|
|
860
1089
|
if (action === "append") {
|
|
@@ -874,7 +1103,7 @@ async function notifyQueueJobStep(queueJobId, action, params) {
|
|
|
874
1103
|
return;
|
|
875
1104
|
}
|
|
876
1105
|
if (params.stepIndex === void 0) return;
|
|
877
|
-
const status = action === "start" ? "running" : action === "complete" ? "completed" : action === "fail" ? "failed" : void 0;
|
|
1106
|
+
const status = action === "start" ? "running" : action === "awaiting_approval" ? "awaiting_approval" : action === "complete" ? "completed" : action === "fail" ? "failed" : void 0;
|
|
878
1107
|
if (!status) return;
|
|
879
1108
|
await updateQueueJobStepInStore({
|
|
880
1109
|
queueJobId,
|
|
@@ -894,6 +1123,13 @@ async function notifyQueueJobStep(queueJobId, action, params) {
|
|
|
894
1123
|
status
|
|
895
1124
|
});
|
|
896
1125
|
} catch (err) {
|
|
1126
|
+
if (action === "append") {
|
|
1127
|
+
console.error("[Worker] Queue append failed (rethrowing):", {
|
|
1128
|
+
queueJobId,
|
|
1129
|
+
error: err?.message ?? String(err)
|
|
1130
|
+
});
|
|
1131
|
+
throw err;
|
|
1132
|
+
}
|
|
897
1133
|
console.warn("[Worker] Queue job update error:", {
|
|
898
1134
|
queueJobId,
|
|
899
1135
|
action,
|
|
@@ -903,26 +1139,152 @@ async function notifyQueueJobStep(queueJobId, action, params) {
|
|
|
903
1139
|
}
|
|
904
1140
|
function wrapHandlerForQueue(handler, queueRuntime) {
|
|
905
1141
|
return async (params) => {
|
|
906
|
-
|
|
907
|
-
const
|
|
908
|
-
|
|
1142
|
+
await maybeApplyHitlResumeMapper(params, queueRuntime);
|
|
1143
|
+
const inputObj = params.input !== null && typeof params.input === "object" ? params.input : {};
|
|
1144
|
+
const queueContextRaw = inputObj[WORKER_QUEUE_KEY];
|
|
1145
|
+
const queueCtxForRetry = queueContextRaw && typeof queueContextRaw === "object" ? queueContextRaw : void 0;
|
|
1146
|
+
const stepRetryConfig = queueCtxForRetry?.id && typeof queueCtxForRetry.stepIndex === "number" && typeof queueRuntime.getStepAt === "function" ? queueRuntime.getStepAt(queueCtxForRetry.id, queueCtxForRetry.stepIndex)?.retry : void 0;
|
|
1147
|
+
const domainInput = { ...inputObj };
|
|
1148
|
+
for (const key of QUEUE_ORCHESTRATION_KEYS) {
|
|
1149
|
+
delete domainInput[key];
|
|
1150
|
+
}
|
|
1151
|
+
delete domainInput[WORKER_QUEUE_KEY];
|
|
1152
|
+
params.input = domainInput;
|
|
1153
|
+
let output;
|
|
1154
|
+
if (stepRetryConfig && stepRetryConfig.on.length > 0) {
|
|
1155
|
+
output = await executeWithRetry(
|
|
1156
|
+
async (retryCtx) => {
|
|
1157
|
+
params.ctx.retryContext = retryCtx;
|
|
1158
|
+
return handler(params);
|
|
1159
|
+
},
|
|
1160
|
+
stepRetryConfig,
|
|
1161
|
+
(retryCtx, delayMs) => {
|
|
1162
|
+
const logger = params.ctx.logger;
|
|
1163
|
+
if (logger?.warn) {
|
|
1164
|
+
logger.warn(
|
|
1165
|
+
`[queue-retry] Retrying step (attempt ${retryCtx.attempt}/${retryCtx.maxAttempts}): ${retryCtx.lastError.message}`,
|
|
1166
|
+
{ delayMs }
|
|
1167
|
+
);
|
|
1168
|
+
} else {
|
|
1169
|
+
console.warn("[queue-retry] Step retry", { attempt: retryCtx.attempt, error: retryCtx.lastError.message, delayMs });
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
);
|
|
1173
|
+
} else {
|
|
1174
|
+
output = await handler(params);
|
|
1175
|
+
}
|
|
1176
|
+
if (!queueContextRaw || typeof queueContextRaw !== "object") {
|
|
1177
|
+
return output;
|
|
1178
|
+
}
|
|
1179
|
+
const queueContext = queueContextRaw;
|
|
1180
|
+
if (!queueContext.id) {
|
|
909
1181
|
return output;
|
|
910
1182
|
}
|
|
911
1183
|
const { id: queueId, stepIndex, initialInput, queueJobId } = queueContext;
|
|
912
|
-
const
|
|
913
|
-
const
|
|
1184
|
+
const arrayStepIndex = queueContext.arrayStepIndex ?? stepIndex;
|
|
1185
|
+
const jobId = params.ctx.jobId;
|
|
1186
|
+
const workerId = params.ctx.workerId ?? "";
|
|
914
1187
|
const next = queueRuntime.getNextStep(queueId, stepIndex);
|
|
915
1188
|
const childJobId = next ? `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}` : void 0;
|
|
1189
|
+
const iterationCount = queueContext.iterationCount ?? 0;
|
|
1190
|
+
if (typeof queueRuntime.invokeLoop === "function") {
|
|
1191
|
+
const currentStep = typeof queueRuntime.getStepAt === "function" ? queueRuntime.getStepAt(queueId, stepIndex) : void 0;
|
|
1192
|
+
const maxIterations = currentStep?.loop?.maxIterations ?? 50;
|
|
1193
|
+
if (iterationCount < maxIterations - 1) {
|
|
1194
|
+
let previousOutputsForLoop = [];
|
|
1195
|
+
let stepsLengthBeforeAppend = arrayStepIndex + 1;
|
|
1196
|
+
if (queueJobId && typeof queueRuntime.getQueueJob === "function") {
|
|
1197
|
+
try {
|
|
1198
|
+
const job = await queueRuntime.getQueueJob(queueJobId);
|
|
1199
|
+
if (job?.steps) {
|
|
1200
|
+
previousOutputsForLoop = job.steps.slice(0, stepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
|
|
1201
|
+
stepsLengthBeforeAppend = job.steps.length;
|
|
1202
|
+
}
|
|
1203
|
+
} catch {
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
previousOutputsForLoop = previousOutputsForLoop.concat([{ stepIndex, workerId, output }]);
|
|
1207
|
+
const shouldLoop = await queueRuntime.invokeLoop(queueId, stepIndex, {
|
|
1208
|
+
output,
|
|
1209
|
+
stepIndex,
|
|
1210
|
+
iterationCount,
|
|
1211
|
+
initialInput,
|
|
1212
|
+
previousOutputs: previousOutputsForLoop
|
|
1213
|
+
});
|
|
1214
|
+
if (shouldLoop) {
|
|
1215
|
+
const loopJobId = `job-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1216
|
+
let loopInput = output;
|
|
1217
|
+
if (typeof queueRuntime.invokeChain === "function") {
|
|
1218
|
+
loopInput = await queueRuntime.invokeChain(queueId, stepIndex, {
|
|
1219
|
+
initialInput,
|
|
1220
|
+
previousOutputs: previousOutputsForLoop
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
const loopInputWithQueue = {
|
|
1224
|
+
...loopInput !== null && typeof loopInput === "object" ? loopInput : { value: loopInput },
|
|
1225
|
+
[WORKER_QUEUE_KEY]: {
|
|
1226
|
+
id: queueId,
|
|
1227
|
+
stepIndex,
|
|
1228
|
+
// definition index stays fixed
|
|
1229
|
+
arrayStepIndex: stepsLengthBeforeAppend,
|
|
1230
|
+
// actual index for next iteration
|
|
1231
|
+
initialInput,
|
|
1232
|
+
queueJobId,
|
|
1233
|
+
iterationCount: iterationCount + 1
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
if (queueJobId) {
|
|
1237
|
+
await notifyQueueJobStep(queueJobId, "append", { workerJobId: loopJobId, workerId });
|
|
1238
|
+
}
|
|
1239
|
+
if (queueJobId && typeof arrayStepIndex === "number") {
|
|
1240
|
+
await notifyQueueJobStep(queueJobId, "complete", {
|
|
1241
|
+
queueId,
|
|
1242
|
+
stepIndex: arrayStepIndex,
|
|
1243
|
+
workerJobId: jobId,
|
|
1244
|
+
workerId,
|
|
1245
|
+
output
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
if (currentStep?.requiresApproval && queueJobId) {
|
|
1249
|
+
const hitlUiSpec = currentStep.hitl && typeof currentStep.hitl === "object" && "ui" in currentStep.hitl ? currentStep.hitl.ui : void 0;
|
|
1250
|
+
const pendingInput = {
|
|
1251
|
+
...loopInputWithQueue,
|
|
1252
|
+
...hitlUiSpec !== void 0 ? { hitl: { uiSpec: hitlUiSpec } } : {},
|
|
1253
|
+
__hitlPending: {
|
|
1254
|
+
queueId,
|
|
1255
|
+
queueJobId,
|
|
1256
|
+
stepIndex,
|
|
1257
|
+
workerId,
|
|
1258
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
await notifyQueueJobStep(queueJobId, "awaiting_approval", {
|
|
1262
|
+
queueId,
|
|
1263
|
+
stepIndex: stepsLengthBeforeAppend,
|
|
1264
|
+
workerJobId: loopJobId,
|
|
1265
|
+
workerId,
|
|
1266
|
+
input: pendingInput
|
|
1267
|
+
});
|
|
1268
|
+
return output;
|
|
1269
|
+
}
|
|
1270
|
+
await params.ctx.dispatchWorker(workerId, loopInputWithQueue, {
|
|
1271
|
+
await: false,
|
|
1272
|
+
jobId: loopJobId
|
|
1273
|
+
});
|
|
1274
|
+
return output;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
916
1278
|
if (next && queueJobId) {
|
|
917
1279
|
await notifyQueueJobStep(queueJobId, "append", {
|
|
918
1280
|
workerJobId: childJobId,
|
|
919
1281
|
workerId: next.workerId
|
|
920
1282
|
});
|
|
921
1283
|
}
|
|
922
|
-
if (queueJobId && typeof
|
|
1284
|
+
if (queueJobId && typeof arrayStepIndex === "number") {
|
|
923
1285
|
await notifyQueueJobStep(queueJobId, "complete", {
|
|
924
1286
|
queueId,
|
|
925
|
-
stepIndex,
|
|
1287
|
+
stepIndex: arrayStepIndex,
|
|
926
1288
|
workerJobId: jobId,
|
|
927
1289
|
workerId,
|
|
928
1290
|
output
|
|
@@ -932,7 +1294,7 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
932
1294
|
return output;
|
|
933
1295
|
}
|
|
934
1296
|
let nextInput = output;
|
|
935
|
-
if (
|
|
1297
|
+
if (typeof queueRuntime.invokeChain === "function") {
|
|
936
1298
|
let previousOutputs = [];
|
|
937
1299
|
if (queueJobId && typeof queueRuntime.getQueueJob === "function") {
|
|
938
1300
|
try {
|
|
@@ -940,7 +1302,7 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
940
1302
|
if (job?.steps) {
|
|
941
1303
|
const fromStore = job.steps.slice(0, stepIndex).map((s, i) => ({ stepIndex: i, workerId: s.workerId, output: s.output }));
|
|
942
1304
|
previousOutputs = fromStore.concat([
|
|
943
|
-
{ stepIndex, workerId: params.ctx
|
|
1305
|
+
{ stepIndex, workerId: params.ctx.workerId ?? "", output }
|
|
944
1306
|
]);
|
|
945
1307
|
}
|
|
946
1308
|
} catch (e) {
|
|
@@ -949,12 +1311,10 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
949
1311
|
}
|
|
950
1312
|
}
|
|
951
1313
|
}
|
|
952
|
-
nextInput = await queueRuntime.
|
|
953
|
-
queueId,
|
|
954
|
-
stepIndex + 1,
|
|
1314
|
+
nextInput = await queueRuntime.invokeChain(queueId, stepIndex + 1, {
|
|
955
1315
|
initialInput,
|
|
956
1316
|
previousOutputs
|
|
957
|
-
);
|
|
1317
|
+
});
|
|
958
1318
|
}
|
|
959
1319
|
const nextInputWithQueue = {
|
|
960
1320
|
...nextInput !== null && typeof nextInput === "object" ? nextInput : { value: nextInput },
|
|
@@ -974,6 +1334,37 @@ function wrapHandlerForQueue(handler, queueRuntime) {
|
|
|
974
1334
|
delaySeconds: next.delaySeconds
|
|
975
1335
|
});
|
|
976
1336
|
}
|
|
1337
|
+
if (next.requiresApproval && queueJobId && typeof stepIndex === "number") {
|
|
1338
|
+
const hitlUiSpec = next.hitl && typeof next.hitl === "object" && "ui" in next.hitl ? next.hitl.ui : void 0;
|
|
1339
|
+
const pendingInput = {
|
|
1340
|
+
...nextInputWithQueue,
|
|
1341
|
+
...hitlUiSpec !== void 0 ? { hitl: { uiSpec: hitlUiSpec } } : {},
|
|
1342
|
+
__hitlPending: {
|
|
1343
|
+
queueId,
|
|
1344
|
+
queueJobId,
|
|
1345
|
+
stepIndex: stepIndex + 1,
|
|
1346
|
+
workerId: next.workerId,
|
|
1347
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
await notifyQueueJobStep(queueJobId, "awaiting_approval", {
|
|
1351
|
+
queueId,
|
|
1352
|
+
stepIndex: stepIndex + 1,
|
|
1353
|
+
workerJobId: childJobId,
|
|
1354
|
+
workerId: next.workerId,
|
|
1355
|
+
input: pendingInput
|
|
1356
|
+
});
|
|
1357
|
+
if (debug) {
|
|
1358
|
+
console.log("[Worker] Queue chain paused for HITL approval:", {
|
|
1359
|
+
queueId,
|
|
1360
|
+
queueJobId,
|
|
1361
|
+
nextStep: stepIndex + 1,
|
|
1362
|
+
nextWorkerId: next.workerId,
|
|
1363
|
+
pendingWorkerJobId: childJobId
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
return output;
|
|
1367
|
+
}
|
|
977
1368
|
await params.ctx.dispatchWorker(next.workerId, nextInputWithQueue, {
|
|
978
1369
|
await: false,
|
|
979
1370
|
delaySeconds: next.delaySeconds,
|
|
@@ -997,6 +1388,7 @@ function createDispatchWorker(parentJobId, parentWorkerId, parentContext, jobSto
|
|
|
997
1388
|
const metadata = options?.metadata ?? {};
|
|
998
1389
|
const serializedContext = {};
|
|
999
1390
|
if (parentContext.requestId) serializedContext.requestId = parentContext.requestId;
|
|
1391
|
+
if (parentContext.userId) serializedContext.userId = parentContext.userId;
|
|
1000
1392
|
const messageBody = {
|
|
1001
1393
|
workerId: calleeWorkerId,
|
|
1002
1394
|
jobId: childJobId,
|
|
@@ -1086,13 +1478,14 @@ async function sendWebhook(webhookUrl, payload) {
|
|
|
1086
1478
|
});
|
|
1087
1479
|
}
|
|
1088
1480
|
}
|
|
1089
|
-
function createLambdaHandler(handler, outputSchema) {
|
|
1481
|
+
function createLambdaHandler(handler, outputSchema, options) {
|
|
1090
1482
|
return async (event, lambdaContext) => {
|
|
1091
1483
|
const promises = event.Records.map(async (record) => {
|
|
1092
1484
|
let messageBody = null;
|
|
1093
1485
|
try {
|
|
1094
1486
|
messageBody = JSON.parse(record.body);
|
|
1095
|
-
const { workerId, jobId, input, context, webhookUrl, metadata = {} } = messageBody;
|
|
1487
|
+
const { workerId, jobId, input, context, webhookUrl, metadata = {}, userId: messageUserId, maxTokens } = messageBody;
|
|
1488
|
+
const userId = context.userId ?? messageUserId;
|
|
1096
1489
|
const raw = (process.env.WORKER_DATABASE_TYPE || "upstash-redis").toLowerCase();
|
|
1097
1490
|
const jobStoreType = raw === "mongodb" ? "mongodb" : "upstash-redis";
|
|
1098
1491
|
if (jobStoreType === "upstash-redis" && isRedisJobStoreConfigured()) {
|
|
@@ -1118,33 +1511,45 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
1118
1511
|
}
|
|
1119
1512
|
let jobStore;
|
|
1120
1513
|
if (jobStoreType === "upstash-redis" && isRedisJobStoreConfigured()) {
|
|
1121
|
-
await upsertRedisJob(jobId, workerId, input, metadata);
|
|
1122
|
-
jobStore = createRedisJobStore(workerId, jobId, input, metadata);
|
|
1514
|
+
await upsertRedisJob(jobId, workerId, input, metadata, userId);
|
|
1515
|
+
jobStore = createRedisJobStore(workerId, jobId, input, metadata, userId);
|
|
1123
1516
|
} else if (jobStoreType === "mongodb" || isMongoJobStoreConfigured()) {
|
|
1124
|
-
await upsertJob(jobId, workerId, input, metadata);
|
|
1125
|
-
jobStore = createMongoJobStore(workerId, jobId, input, metadata);
|
|
1517
|
+
await upsertJob(jobId, workerId, input, metadata, userId);
|
|
1518
|
+
jobStore = createMongoJobStore(workerId, jobId, input, metadata, userId);
|
|
1519
|
+
}
|
|
1520
|
+
if (userId) {
|
|
1521
|
+
console.log(`[WORKER_USER:${userId}]`, { jobId, workerId, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1126
1522
|
}
|
|
1127
1523
|
const baseContext = {
|
|
1128
1524
|
jobId,
|
|
1129
1525
|
workerId,
|
|
1130
1526
|
requestId: context.requestId || lambdaContext.awsRequestId,
|
|
1527
|
+
...userId ? { userId } : {},
|
|
1131
1528
|
...context
|
|
1132
1529
|
};
|
|
1530
|
+
const tokenTracker = createTokenTracker(maxTokens ?? null);
|
|
1531
|
+
const logger = createWorkerLogger(jobId, workerId);
|
|
1133
1532
|
const handlerContext = {
|
|
1134
1533
|
...baseContext,
|
|
1135
1534
|
...jobStore ? { jobStore } : {},
|
|
1136
|
-
logger
|
|
1137
|
-
dispatchWorker: createDispatchWorker(
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
jobStore
|
|
1142
|
-
|
|
1535
|
+
logger,
|
|
1536
|
+
dispatchWorker: createDispatchWorker(jobId, workerId, baseContext, jobStore),
|
|
1537
|
+
reportTokenUsage: async (usage) => {
|
|
1538
|
+
tokenTracker.report(usage);
|
|
1539
|
+
const state = tokenTracker.getState();
|
|
1540
|
+
if (jobStore) {
|
|
1541
|
+
await jobStore.update({ metadata: { tokenUsage: state } }).catch((e) => {
|
|
1542
|
+
logger.warn("Failed to persist tokenUsage to job store", { error: e?.message });
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
},
|
|
1546
|
+
getTokenBudget: () => tokenTracker.getBudgetInfo(),
|
|
1547
|
+
retryContext: void 0
|
|
1143
1548
|
};
|
|
1144
1549
|
if (jobStore) {
|
|
1145
1550
|
try {
|
|
1146
1551
|
await jobStore.update({ status: "running" });
|
|
1147
|
-
const queueCtxForLog = input
|
|
1552
|
+
const queueCtxForLog = getWorkerQueueContext(input, metadata);
|
|
1148
1553
|
console.log("[Worker] Job status updated to running:", {
|
|
1149
1554
|
jobId,
|
|
1150
1555
|
workerId,
|
|
@@ -1159,7 +1564,7 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
1159
1564
|
});
|
|
1160
1565
|
}
|
|
1161
1566
|
}
|
|
1162
|
-
const queueCtx = input
|
|
1567
|
+
const queueCtx = getWorkerQueueContext(input, metadata);
|
|
1163
1568
|
if (queueCtx?.queueJobId && typeof queueCtx.stepIndex === "number") {
|
|
1164
1569
|
if (queueCtx.stepIndex === 0) {
|
|
1165
1570
|
try {
|
|
@@ -1168,7 +1573,8 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
1168
1573
|
queueId: queueCtx.id,
|
|
1169
1574
|
firstWorkerId: workerId,
|
|
1170
1575
|
firstWorkerJobId: jobId,
|
|
1171
|
-
metadata
|
|
1576
|
+
metadata,
|
|
1577
|
+
userId
|
|
1172
1578
|
});
|
|
1173
1579
|
} catch (e) {
|
|
1174
1580
|
console.warn("[Worker] Failed to upsert initial queue job:", {
|
|
@@ -1180,7 +1586,9 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
1180
1586
|
}
|
|
1181
1587
|
await notifyQueueJobStep(queueCtx.queueJobId, "start", {
|
|
1182
1588
|
queueId: queueCtx.id,
|
|
1183
|
-
|
|
1589
|
+
// Use arrayStepIndex when set — it tracks the actual steps[] position for
|
|
1590
|
+
// looping steps where the definition index stays fixed across iterations.
|
|
1591
|
+
stepIndex: queueCtx.arrayStepIndex ?? queueCtx.stepIndex,
|
|
1184
1592
|
workerJobId: jobId,
|
|
1185
1593
|
workerId,
|
|
1186
1594
|
input
|
|
@@ -1188,12 +1596,21 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
1188
1596
|
}
|
|
1189
1597
|
let output;
|
|
1190
1598
|
try {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1599
|
+
const workerRetryConfig = options?.retry;
|
|
1600
|
+
const executeHandler = async (retryCtx) => {
|
|
1601
|
+
handlerContext.retryContext = retryCtx;
|
|
1602
|
+
const result = await handler({ input, ctx: handlerContext });
|
|
1603
|
+
return outputSchema ? outputSchema.parse(result) : result;
|
|
1604
|
+
};
|
|
1605
|
+
if (workerRetryConfig && workerRetryConfig.on.length > 0) {
|
|
1606
|
+
output = await executeWithRetry(executeHandler, workerRetryConfig, (retryCtx, delayMs) => {
|
|
1607
|
+
logger.warn(
|
|
1608
|
+
`[worker-retry] Retrying handler (attempt ${retryCtx.attempt}/${retryCtx.maxAttempts}): ${retryCtx.lastError.message}`,
|
|
1609
|
+
{ delayMs }
|
|
1610
|
+
);
|
|
1611
|
+
});
|
|
1612
|
+
} else {
|
|
1613
|
+
output = await executeHandler(void 0);
|
|
1197
1614
|
}
|
|
1198
1615
|
} catch (error) {
|
|
1199
1616
|
const errorPayload = {
|
|
@@ -1225,7 +1642,7 @@ function createLambdaHandler(handler, outputSchema) {
|
|
|
1225
1642
|
});
|
|
1226
1643
|
}
|
|
1227
1644
|
}
|
|
1228
|
-
const queueCtxFail = input
|
|
1645
|
+
const queueCtxFail = getWorkerQueueContext(input, metadata);
|
|
1229
1646
|
if (queueCtxFail?.queueJobId && typeof queueCtxFail.stepIndex === "number") {
|
|
1230
1647
|
await notifyQueueJobStep(queueCtxFail.queueJobId, "fail", {
|
|
1231
1648
|
queueId: queueCtxFail.id,
|
|
@@ -1331,25 +1748,73 @@ function clearWorkersConfigCache() {
|
|
|
1331
1748
|
cacheExpiry = 0;
|
|
1332
1749
|
}
|
|
1333
1750
|
|
|
1334
|
-
// src/
|
|
1335
|
-
|
|
1751
|
+
// src/queueInputEnvelope.ts
|
|
1752
|
+
var import_zod = require("zod");
|
|
1753
|
+
var queueOrchestrationFieldsSchema = import_zod.z.object({
|
|
1754
|
+
__workerQueue: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
|
|
1755
|
+
__hitlPending: import_zod.z.unknown().optional(),
|
|
1756
|
+
__hitlInput: import_zod.z.unknown().optional(),
|
|
1757
|
+
__hitlDecision: import_zod.z.unknown().optional()
|
|
1758
|
+
});
|
|
1759
|
+
function withQueueOrchestrationEnvelope(domain) {
|
|
1760
|
+
return domain.merge(queueOrchestrationFieldsSchema);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/chainMapDefaults.ts
|
|
1764
|
+
function defaultMapChainPassthrough(ctx) {
|
|
1765
|
+
const { initialInput, previousOutputs } = ctx;
|
|
1766
|
+
if (previousOutputs.length > 0) {
|
|
1767
|
+
return previousOutputs[previousOutputs.length - 1]?.output;
|
|
1768
|
+
}
|
|
1769
|
+
return initialInput;
|
|
1770
|
+
}
|
|
1771
|
+
function defaultMapChainContinueFromPrevious(ctx) {
|
|
1772
|
+
const { previousOutputs } = ctx;
|
|
1773
|
+
const prev = previousOutputs[previousOutputs.length - 1]?.output;
|
|
1774
|
+
if (!prev || typeof prev.current !== "number") {
|
|
1775
|
+
return {
|
|
1776
|
+
mode: "continue",
|
|
1777
|
+
current: 0,
|
|
1778
|
+
history: [],
|
|
1779
|
+
nextNumber: 0,
|
|
1780
|
+
operator: "add"
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
return {
|
|
1784
|
+
mode: "continue",
|
|
1785
|
+
current: prev.current,
|
|
1786
|
+
history: prev.history ?? [],
|
|
1787
|
+
nextNumber: 0,
|
|
1788
|
+
operator: "add"
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// src/hitlConfig.ts
|
|
1793
|
+
function defineHitlConfig(config) {
|
|
1336
1794
|
return config;
|
|
1337
1795
|
}
|
|
1338
1796
|
|
|
1339
1797
|
// src/index.ts
|
|
1340
1798
|
function createWorker(config) {
|
|
1341
|
-
const { id, inputSchema, outputSchema, handler } = config;
|
|
1799
|
+
const { id, inputSchema, outputSchema, handler, retry: retryConfig } = config;
|
|
1342
1800
|
const agent = {
|
|
1343
1801
|
id,
|
|
1344
1802
|
handler,
|
|
1345
1803
|
inputSchema,
|
|
1346
1804
|
outputSchema,
|
|
1805
|
+
retry: retryConfig,
|
|
1347
1806
|
async dispatch(input, options) {
|
|
1348
1807
|
const mode = options.mode ?? "auto";
|
|
1349
1808
|
const envWantsLocal = process.env.NODE_ENV === "development" && process.env.WORKERS_LOCAL_MODE !== "false";
|
|
1350
1809
|
const isLocal = mode === "local" || mode === "auto" && envWantsLocal;
|
|
1351
1810
|
if (isLocal) {
|
|
1352
|
-
const
|
|
1811
|
+
const rawInputObj = input !== null && typeof input === "object" ? input : null;
|
|
1812
|
+
const domainInput = rawInputObj ? Object.fromEntries(
|
|
1813
|
+
Object.entries(rawInputObj).filter(
|
|
1814
|
+
([k]) => k !== "__workerQueue" && !QUEUE_ORCHESTRATION_KEYS.includes(k)
|
|
1815
|
+
)
|
|
1816
|
+
) : input;
|
|
1817
|
+
const parsedInput = inputSchema.parse(domainInput);
|
|
1353
1818
|
const localJobId = options.jobId || `local-${Date.now()}`;
|
|
1354
1819
|
let directJobStore = null;
|
|
1355
1820
|
const nextJsPathAlias = "@/app/api/workflows/stores/jobStore";
|
|
@@ -1571,6 +2036,7 @@ function createWorker(config) {
|
|
|
1571
2036
|
const metadata = options2?.metadata ?? {};
|
|
1572
2037
|
const serializedContext = {};
|
|
1573
2038
|
if (parentContext.requestId) serializedContext.requestId = parentContext.requestId;
|
|
2039
|
+
if (parentContext.userId) serializedContext.userId = parentContext.userId;
|
|
1574
2040
|
const messageBody = {
|
|
1575
2041
|
workerId: calleeWorkerId,
|
|
1576
2042
|
jobId: childJobId,
|
|
@@ -1675,23 +2141,48 @@ function createWorker(config) {
|
|
|
1675
2141
|
});
|
|
1676
2142
|
}
|
|
1677
2143
|
}
|
|
1678
|
-
const
|
|
2144
|
+
const localUserId = options.userId;
|
|
2145
|
+
const baseContext = {
|
|
2146
|
+
jobId: localJobId,
|
|
2147
|
+
workerId: id,
|
|
2148
|
+
...localUserId ? { userId: localUserId } : {}
|
|
2149
|
+
};
|
|
2150
|
+
if (localUserId) {
|
|
2151
|
+
console.log(`[WORKER_USER:${localUserId}]`, { jobId: localJobId, workerId: id, mode: "local", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2152
|
+
}
|
|
2153
|
+
const localLogger = createWorkerLogger(localJobId, id);
|
|
2154
|
+
const tokenTracker = createTokenTracker(options.maxTokens ?? null);
|
|
1679
2155
|
const handlerContext = {
|
|
1680
2156
|
...baseContext,
|
|
1681
2157
|
...jobStore ? { jobStore } : {},
|
|
1682
|
-
logger:
|
|
1683
|
-
dispatchWorker: createLocalDispatchWorker(
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
jobStore
|
|
1688
|
-
|
|
2158
|
+
logger: localLogger,
|
|
2159
|
+
dispatchWorker: createLocalDispatchWorker(localJobId, id, baseContext, jobStore),
|
|
2160
|
+
reportTokenUsage: async (usage) => {
|
|
2161
|
+
tokenTracker.report(usage);
|
|
2162
|
+
const state = tokenTracker.getState();
|
|
2163
|
+
if (jobStore) {
|
|
2164
|
+
await jobStore.update({ metadata: { tokenUsage: state } }).catch((e) => {
|
|
2165
|
+
localLogger.warn("Failed to persist tokenUsage", { error: e?.message });
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2168
|
+
},
|
|
2169
|
+
getTokenBudget: () => tokenTracker.getBudgetInfo(),
|
|
2170
|
+
retryContext: void 0
|
|
1689
2171
|
};
|
|
1690
2172
|
try {
|
|
1691
2173
|
if (jobStore) {
|
|
1692
2174
|
await jobStore.update({ status: "running" });
|
|
1693
2175
|
}
|
|
1694
|
-
const
|
|
2176
|
+
const executeLocal = async (retryCtx) => {
|
|
2177
|
+
handlerContext.retryContext = retryCtx;
|
|
2178
|
+
return dispatchLocal(handler, parsedInput, handlerContext);
|
|
2179
|
+
};
|
|
2180
|
+
const output = retryConfig && retryConfig.on.length > 0 ? await executeWithRetry(executeLocal, retryConfig, (retryCtx, delayMs) => {
|
|
2181
|
+
localLogger.warn(
|
|
2182
|
+
`[worker-retry] Local retry (attempt ${retryCtx.attempt}/${retryCtx.maxAttempts}): ${retryCtx.lastError.message}`,
|
|
2183
|
+
{ delayMs }
|
|
2184
|
+
);
|
|
2185
|
+
}) : await executeLocal(void 0);
|
|
1695
2186
|
if (jobStore) {
|
|
1696
2187
|
await jobStore.update({ status: "completed", output });
|
|
1697
2188
|
}
|
|
@@ -1758,25 +2249,35 @@ function createWorker(config) {
|
|
|
1758
2249
|
return agent;
|
|
1759
2250
|
}
|
|
1760
2251
|
function createLambdaEntrypoint(agent) {
|
|
1761
|
-
return createLambdaHandler(agent.handler, agent.outputSchema);
|
|
2252
|
+
return createLambdaHandler(agent.handler, agent.outputSchema, { retry: agent.retry });
|
|
1762
2253
|
}
|
|
1763
2254
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1764
2255
|
0 && (module.exports = {
|
|
2256
|
+
QUEUE_ORCHESTRATION_KEYS,
|
|
1765
2257
|
SQS_MAX_DELAY_SECONDS,
|
|
2258
|
+
TokenBudgetExceededError,
|
|
1766
2259
|
clearWorkersConfigCache,
|
|
1767
2260
|
createLambdaEntrypoint,
|
|
1768
2261
|
createLambdaHandler,
|
|
1769
2262
|
createWorker,
|
|
1770
2263
|
createWorkerLogger,
|
|
2264
|
+
defaultMapChainContinueFromPrevious,
|
|
2265
|
+
defaultMapChainPassthrough,
|
|
2266
|
+
defineHitlConfig,
|
|
1771
2267
|
defineWorkerQueue,
|
|
1772
2268
|
dispatch,
|
|
1773
2269
|
dispatchLocal,
|
|
1774
2270
|
dispatchQueue,
|
|
1775
2271
|
dispatchWorker,
|
|
2272
|
+
executeWithRetry,
|
|
1776
2273
|
getQueueStartUrl,
|
|
1777
2274
|
getWorkersConfig,
|
|
1778
2275
|
getWorkersTriggerUrl,
|
|
2276
|
+
matchesRetryPattern,
|
|
2277
|
+
queueOrchestrationFieldsSchema,
|
|
2278
|
+
repeatStep,
|
|
1779
2279
|
resolveQueueUrl,
|
|
2280
|
+
withQueueOrchestrationEnvelope,
|
|
1780
2281
|
wrapHandlerForQueue
|
|
1781
2282
|
});
|
|
1782
2283
|
//# sourceMappingURL=index.js.map
|