@tangle-network/agent-runtime 0.43.0 → 0.44.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 +3 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +1 -1
- package/dist/{chunk-C5HMTTNY.js → chunk-GFKVVRQ7.js} +8 -8
- package/dist/{chunk-MNCB4SJ5.js → chunk-KDMRUD2P.js} +2 -2
- package/dist/{chunk-EKBSQYZE.js → chunk-S7JXV32P.js} +159 -25
- package/dist/chunk-S7JXV32P.js.map +1 -0
- package/dist/{chunk-MJDGCRAT.js → chunk-SKUZZCHE.js} +2 -2
- package/dist/{dynamic-B_7GgCwu.d.ts → dynamic-wUgp6UKs.d.ts} +1 -1
- package/dist/improvement.js +3 -3
- package/dist/index.d.ts +6 -6
- package/dist/index.js +15 -15
- package/dist/{kb-gate-DTBum3vH.d.ts → kb-gate-D0ZIhFOU.d.ts} +1 -1
- package/dist/{loop-runner-bin-CVoCBmYk.d.ts → loop-runner-bin-BLMa8He3.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +4 -4
- package/dist/loop-runner-bin.js +4 -4
- package/dist/loops.d.ts +101 -79
- package/dist/loops.js +9 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +5 -5
- package/dist/mcp/index.js +3 -3
- package/dist/{otel-export-BzvF1Ela.d.ts → otel-export-wFDmmurL.d.ts} +1 -1
- package/dist/profiles.d.ts +1 -1
- package/dist/run-loop-C4L1Sted.d.ts +89 -0
- package/dist/{types-Bcp071Jg.d.ts → types-DbJzz2uf.d.ts} +1 -1
- package/dist/workflow.d.ts +550 -0
- package/dist/workflow.js +1779 -0
- package/dist/workflow.js.map +1 -0
- package/package.json +29 -12
- package/dist/chunk-EKBSQYZE.js.map +0 -1
- /package/dist/{chunk-C5HMTTNY.js.map → chunk-GFKVVRQ7.js.map} +0 -0
- /package/dist/{chunk-MNCB4SJ5.js.map → chunk-KDMRUD2P.js.map} +0 -0
- /package/dist/{chunk-MJDGCRAT.js.map → chunk-SKUZZCHE.js.map} +0 -0
package/dist/workflow.js
ADDED
|
@@ -0,0 +1,1779 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSandboxForSpec,
|
|
3
|
+
describeSandboxPlacement,
|
|
4
|
+
extractLlmCallEvent,
|
|
5
|
+
runLoop
|
|
6
|
+
} from "./chunk-S7JXV32P.js";
|
|
7
|
+
import "./chunk-PY6NMZYX.js";
|
|
8
|
+
import {
|
|
9
|
+
ValidationError
|
|
10
|
+
} from "./chunk-SQSCRJ7U.js";
|
|
11
|
+
import "./chunk-DGUM43GV.js";
|
|
12
|
+
|
|
13
|
+
// src/workflow/agent-delegate.ts
|
|
14
|
+
function createSandboxWorkflowAgentDelegate(options) {
|
|
15
|
+
return async (prompt, agentOptions, ctx) => {
|
|
16
|
+
if (!options.client || typeof options.client.create !== "function") {
|
|
17
|
+
throw new ValidationError("workflow sandbox agent: client.create is required");
|
|
18
|
+
}
|
|
19
|
+
const profile = resolveProfile(options.profile, prompt, agentOptions, ctx);
|
|
20
|
+
const agentRunName = agentOptions.label ?? profile.name ?? "workflow-agent";
|
|
21
|
+
const box = await createSandboxForSpec(
|
|
22
|
+
options.client,
|
|
23
|
+
{
|
|
24
|
+
profile,
|
|
25
|
+
name: agentRunName,
|
|
26
|
+
taskToPrompt: (value) => value,
|
|
27
|
+
sandboxOverrides: options.sandboxOverrides
|
|
28
|
+
},
|
|
29
|
+
ctx.signal
|
|
30
|
+
);
|
|
31
|
+
const placement = describeSandboxPlacement(options.client, box);
|
|
32
|
+
const stream = options.stream ?? "prompt";
|
|
33
|
+
const events = [];
|
|
34
|
+
const tokenUsage = { input: 0, output: 0 };
|
|
35
|
+
let costUsd = 0;
|
|
36
|
+
try {
|
|
37
|
+
const source = stream === "task" ? box.streamTask(prompt, {
|
|
38
|
+
...resolveStreamOptions(options.taskOptions, prompt, agentOptions, ctx),
|
|
39
|
+
signal: ctx.signal
|
|
40
|
+
}) : box.streamPrompt(prompt, {
|
|
41
|
+
...resolveStreamOptions(options.promptOptions, prompt, agentOptions, ctx),
|
|
42
|
+
signal: ctx.signal
|
|
43
|
+
});
|
|
44
|
+
for await (const event of source) {
|
|
45
|
+
events.push(event);
|
|
46
|
+
const llmCall = extractLlmCallEvent(event, agentRunName);
|
|
47
|
+
if (!llmCall) continue;
|
|
48
|
+
costUsd += llmCall.costUsd ?? 0;
|
|
49
|
+
tokenUsage.input += llmCall.tokensIn ?? 0;
|
|
50
|
+
tokenUsage.output += llmCall.tokensOut ?? 0;
|
|
51
|
+
}
|
|
52
|
+
const output = options.output ? options.output.parse(events) : parseSandboxAgentDefaultOutput(events);
|
|
53
|
+
const trace = {
|
|
54
|
+
prompt,
|
|
55
|
+
options: agentOptions,
|
|
56
|
+
ctx,
|
|
57
|
+
profile,
|
|
58
|
+
output,
|
|
59
|
+
events,
|
|
60
|
+
stream,
|
|
61
|
+
placement,
|
|
62
|
+
costUsd,
|
|
63
|
+
tokenUsage
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
output,
|
|
67
|
+
costUsd,
|
|
68
|
+
tokenUsage,
|
|
69
|
+
trace: options.toTrace ? options.toTrace(trace) : defaultTrace(trace, options)
|
|
70
|
+
};
|
|
71
|
+
} finally {
|
|
72
|
+
if (options.deleteAfter) await box.delete();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function parseSandboxAgentDefaultOutput(events) {
|
|
77
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
78
|
+
const event = events[i];
|
|
79
|
+
const data = asRecord(event?.data);
|
|
80
|
+
const text = firstString(data, ["finalText", "response", "text", "output", "content"]);
|
|
81
|
+
if (text !== void 0) return text;
|
|
82
|
+
if ("result" in data) return data.result;
|
|
83
|
+
}
|
|
84
|
+
throw new ValidationError(
|
|
85
|
+
"workflow sandbox agent emitted no parseable terminal output; pass an output adapter"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function resolveProfile(resolver, prompt, options, ctx) {
|
|
89
|
+
return typeof resolver === "function" ? resolver(prompt, options, ctx) : resolver;
|
|
90
|
+
}
|
|
91
|
+
function resolveStreamOptions(resolver, prompt, options, ctx) {
|
|
92
|
+
return typeof resolver === "function" ? resolver(prompt, options, ctx) : resolver;
|
|
93
|
+
}
|
|
94
|
+
function defaultTrace(trace, options) {
|
|
95
|
+
return {
|
|
96
|
+
stream: trace.stream,
|
|
97
|
+
placement: trace.placement.kind,
|
|
98
|
+
sandboxId: trace.placement.sandboxId,
|
|
99
|
+
fleetId: trace.placement.fleetId,
|
|
100
|
+
machineId: trace.placement.machineId,
|
|
101
|
+
profileName: trace.profile.name,
|
|
102
|
+
eventCount: trace.events.length,
|
|
103
|
+
eventTypes: trace.events.map((event) => String(event.type ?? "")),
|
|
104
|
+
...options.includeEventsInTrace ? { events: trace.events } : {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function asRecord(value) {
|
|
108
|
+
return value && typeof value === "object" ? value : {};
|
|
109
|
+
}
|
|
110
|
+
function firstString(data, keys) {
|
|
111
|
+
for (const key of keys) {
|
|
112
|
+
const value = data[key];
|
|
113
|
+
if (typeof value === "string") return value;
|
|
114
|
+
}
|
|
115
|
+
return void 0;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/workflow/budget.ts
|
|
119
|
+
var WorkflowBudget = class {
|
|
120
|
+
total;
|
|
121
|
+
startedAt;
|
|
122
|
+
now;
|
|
123
|
+
costUsd = 0;
|
|
124
|
+
tokens = { input: 0, output: 0 };
|
|
125
|
+
agentCallCount = 0;
|
|
126
|
+
loopCallCount = 0;
|
|
127
|
+
constructor(total, now) {
|
|
128
|
+
this.total = total;
|
|
129
|
+
this.now = now;
|
|
130
|
+
this.startedAt = now();
|
|
131
|
+
}
|
|
132
|
+
spent() {
|
|
133
|
+
return {
|
|
134
|
+
costUsd: this.costUsd,
|
|
135
|
+
tokens: { ...this.tokens },
|
|
136
|
+
agentCalls: this.agentCallCount,
|
|
137
|
+
loopCalls: this.loopCallCount,
|
|
138
|
+
elapsedMs: this.elapsedMs()
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
remaining() {
|
|
142
|
+
const out = {};
|
|
143
|
+
if (this.total.maxCostUsd !== void 0) out.costUsd = this.total.maxCostUsd - this.costUsd;
|
|
144
|
+
if (this.total.maxTokens !== void 0) {
|
|
145
|
+
out.tokens = this.total.maxTokens - this.tokens.input - this.tokens.output;
|
|
146
|
+
}
|
|
147
|
+
if (this.total.maxAgentCalls !== void 0) {
|
|
148
|
+
out.agentCalls = this.total.maxAgentCalls - this.agentCallCount;
|
|
149
|
+
}
|
|
150
|
+
if (this.total.maxLoopCalls !== void 0) {
|
|
151
|
+
out.loopCalls = this.total.maxLoopCalls - this.loopCallCount;
|
|
152
|
+
}
|
|
153
|
+
if (this.total.maxWallMs !== void 0) out.wallMs = this.total.maxWallMs - this.elapsedMs();
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
nextAgentIndex() {
|
|
157
|
+
this.assertWall();
|
|
158
|
+
if (this.total.maxAgentCalls !== void 0 && this.agentCallCount >= this.total.maxAgentCalls) {
|
|
159
|
+
throw new ValidationError(
|
|
160
|
+
`workflow budget exhausted: maxAgentCalls=${this.total.maxAgentCalls}`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const index = this.agentCallCount;
|
|
164
|
+
this.agentCallCount += 1;
|
|
165
|
+
return index;
|
|
166
|
+
}
|
|
167
|
+
nextLoopIndex() {
|
|
168
|
+
this.assertWall();
|
|
169
|
+
if (this.total.maxLoopCalls !== void 0 && this.loopCallCount >= this.total.maxLoopCalls) {
|
|
170
|
+
throw new ValidationError(
|
|
171
|
+
`workflow budget exhausted: maxLoopCalls=${this.total.maxLoopCalls}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const index = this.loopCallCount;
|
|
175
|
+
this.loopCallCount += 1;
|
|
176
|
+
return index;
|
|
177
|
+
}
|
|
178
|
+
assertFanout(count) {
|
|
179
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
180
|
+
throw new ValidationError(
|
|
181
|
+
`workflow fanout count must be a non-negative integer, got ${count}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
if (this.total.maxFanout !== void 0 && count > this.total.maxFanout) {
|
|
185
|
+
throw new ValidationError(
|
|
186
|
+
`workflow fanout ${count} exceeds maxFanout=${this.total.maxFanout}`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
observe(result) {
|
|
191
|
+
const costUsd = finiteNonNegative(result.costUsd ?? 0, "costUsd");
|
|
192
|
+
const tokenUsage = {
|
|
193
|
+
input: finiteNonNegative(result.tokenUsage?.input ?? 0, "tokenUsage.input"),
|
|
194
|
+
output: finiteNonNegative(result.tokenUsage?.output ?? 0, "tokenUsage.output")
|
|
195
|
+
};
|
|
196
|
+
const agentCalls = finiteNonNegativeInteger(result.agentCalls ?? 0, "agentCalls");
|
|
197
|
+
const loopCalls = finiteNonNegativeInteger(result.loopCalls ?? 0, "loopCalls");
|
|
198
|
+
this.costUsd += costUsd;
|
|
199
|
+
this.tokens.input += tokenUsage.input;
|
|
200
|
+
this.tokens.output += tokenUsage.output;
|
|
201
|
+
this.agentCallCount += agentCalls;
|
|
202
|
+
this.loopCallCount += loopCalls;
|
|
203
|
+
this.assertSpend();
|
|
204
|
+
return { costUsd, tokenUsage };
|
|
205
|
+
}
|
|
206
|
+
assertWall() {
|
|
207
|
+
if (this.total.maxWallMs !== void 0 && this.elapsedMs() > this.total.maxWallMs) {
|
|
208
|
+
throw new ValidationError(`workflow budget exhausted: maxWallMs=${this.total.maxWallMs}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
remainingWallMs() {
|
|
212
|
+
if (this.total.maxWallMs === void 0) return void 0;
|
|
213
|
+
return Math.max(0, this.total.maxWallMs - this.elapsedMs());
|
|
214
|
+
}
|
|
215
|
+
elapsedMs() {
|
|
216
|
+
return Math.max(0, this.now() - this.startedAt);
|
|
217
|
+
}
|
|
218
|
+
assertSpend() {
|
|
219
|
+
if (this.total.maxCostUsd !== void 0 && this.costUsd > this.total.maxCostUsd) {
|
|
220
|
+
throw new ValidationError(`workflow budget exhausted: maxCostUsd=${this.total.maxCostUsd}`);
|
|
221
|
+
}
|
|
222
|
+
if (this.total.maxTokens !== void 0 && this.tokens.input + this.tokens.output > this.total.maxTokens) {
|
|
223
|
+
throw new ValidationError(`workflow budget exhausted: maxTokens=${this.total.maxTokens}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
function finiteNonNegative(value, field) {
|
|
228
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
229
|
+
throw new ValidationError(`workflow delegate returned invalid ${field}: ${value}`);
|
|
230
|
+
}
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
function finiteNonNegativeInteger(value, field) {
|
|
234
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
235
|
+
throw new ValidationError(`workflow delegate returned invalid ${field}: ${value}`);
|
|
236
|
+
}
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/workflow/loop-delegate.ts
|
|
241
|
+
function createRunLoopWorkflowDelegate(options) {
|
|
242
|
+
return async (input, loopOptions, ctx) => {
|
|
243
|
+
const result = await runLoop(options.toRunLoopOptions(input, loopOptions, ctx));
|
|
244
|
+
return {
|
|
245
|
+
output: options.toOutput ? options.toOutput(result) : defaultOutput(result),
|
|
246
|
+
costUsd: result.costUsd,
|
|
247
|
+
tokenUsage: result.tokenUsage,
|
|
248
|
+
trace: options.toTrace ? options.toTrace(result) : defaultTrace2(result)
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function defaultOutput(result) {
|
|
253
|
+
return result.winner?.output ?? {
|
|
254
|
+
decision: result.decision,
|
|
255
|
+
iterations: result.iterations.length
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function defaultTrace2(result) {
|
|
259
|
+
return {
|
|
260
|
+
decision: result.decision,
|
|
261
|
+
iterations: result.iterations.length,
|
|
262
|
+
winnerIterationIndex: result.winner?.iterationIndex,
|
|
263
|
+
costUsd: result.costUsd,
|
|
264
|
+
tokenUsage: result.tokenUsage
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/workflow/runtime.ts
|
|
269
|
+
import { randomUUID } from "crypto";
|
|
270
|
+
|
|
271
|
+
// src/workflow/schema.ts
|
|
272
|
+
function validateJsonSchemaDefinition(schema, path = "$") {
|
|
273
|
+
const record = assertSchemaRecord(schema, path);
|
|
274
|
+
const type = record.type;
|
|
275
|
+
switch (type) {
|
|
276
|
+
case "string":
|
|
277
|
+
requireAllowedKeys(record, path, ["type", "minLength", "maxLength", "enum"]);
|
|
278
|
+
optionalNonNegativeNumber(record.minLength, `${path}.minLength`, { integer: true });
|
|
279
|
+
optionalNonNegativeNumber(record.maxLength, `${path}.maxLength`, { integer: true });
|
|
280
|
+
optionalEnum(record.enum, path, "string");
|
|
281
|
+
return;
|
|
282
|
+
case "number":
|
|
283
|
+
case "integer":
|
|
284
|
+
requireAllowedKeys(record, path, ["type", "minimum", "maximum", "enum"]);
|
|
285
|
+
optionalFiniteNumber(record.minimum, `${path}.minimum`);
|
|
286
|
+
optionalFiniteNumber(record.maximum, `${path}.maximum`);
|
|
287
|
+
optionalEnum(record.enum, path, type);
|
|
288
|
+
return;
|
|
289
|
+
case "boolean":
|
|
290
|
+
requireAllowedKeys(record, path, ["type", "enum"]);
|
|
291
|
+
optionalEnum(record.enum, path, "boolean");
|
|
292
|
+
return;
|
|
293
|
+
case "null":
|
|
294
|
+
requireAllowedKeys(record, path, ["type"]);
|
|
295
|
+
return;
|
|
296
|
+
case "array":
|
|
297
|
+
requireAllowedKeys(record, path, ["type", "items", "minItems", "maxItems"]);
|
|
298
|
+
optionalNonNegativeNumber(record.minItems, `${path}.minItems`, { integer: true });
|
|
299
|
+
optionalNonNegativeNumber(record.maxItems, `${path}.maxItems`, { integer: true });
|
|
300
|
+
if (record.items !== void 0) validateJsonSchemaDefinition(record.items, `${path}.items`);
|
|
301
|
+
return;
|
|
302
|
+
case "object": {
|
|
303
|
+
requireAllowedKeys(record, path, ["type", "properties", "required", "additionalProperties"]);
|
|
304
|
+
if (record.properties !== void 0) {
|
|
305
|
+
const properties = assertSchemaPlainRecord(record.properties, `${path}.properties`);
|
|
306
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
307
|
+
validateJsonSchemaDefinition(child, `${path}.properties.${key}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (record.required !== void 0) {
|
|
311
|
+
if (!Array.isArray(record.required)) {
|
|
312
|
+
throw new ValidationError(`${path}.required: expected string array`);
|
|
313
|
+
}
|
|
314
|
+
for (const [index, value] of record.required.entries()) {
|
|
315
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
316
|
+
throw new ValidationError(`${path}.required[${index}]: expected non-empty string`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (record.additionalProperties !== void 0 && typeof record.additionalProperties !== "boolean") {
|
|
321
|
+
throw new ValidationError(`${path}.additionalProperties: expected boolean`);
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
default:
|
|
326
|
+
throw new ValidationError(`${path}.type: expected supported JSON schema type`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function validateJsonSchema(value, schema, path = "$") {
|
|
330
|
+
validateJsonSchemaDefinition(schema);
|
|
331
|
+
switch (schema.type) {
|
|
332
|
+
case "string":
|
|
333
|
+
assertType(typeof value === "string", path, "string");
|
|
334
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
335
|
+
throw new ValidationError(`${path}: expected string length >= ${schema.minLength}`);
|
|
336
|
+
}
|
|
337
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
338
|
+
throw new ValidationError(`${path}: expected string length <= ${schema.maxLength}`);
|
|
339
|
+
}
|
|
340
|
+
assertEnum(value, schema.enum, path);
|
|
341
|
+
return;
|
|
342
|
+
case "number":
|
|
343
|
+
assertType(typeof value === "number" && Number.isFinite(value), path, "number");
|
|
344
|
+
assertNumberBounds(value, schema.minimum, schema.maximum, path);
|
|
345
|
+
assertEnum(value, schema.enum, path);
|
|
346
|
+
return;
|
|
347
|
+
case "integer":
|
|
348
|
+
assertType(Number.isInteger(value), path, "integer");
|
|
349
|
+
assertNumberBounds(value, schema.minimum, schema.maximum, path);
|
|
350
|
+
assertEnum(value, schema.enum, path);
|
|
351
|
+
return;
|
|
352
|
+
case "boolean":
|
|
353
|
+
assertType(typeof value === "boolean", path, "boolean");
|
|
354
|
+
assertEnum(value, schema.enum, path);
|
|
355
|
+
return;
|
|
356
|
+
case "null":
|
|
357
|
+
assertType(value === null, path, "null");
|
|
358
|
+
return;
|
|
359
|
+
case "array":
|
|
360
|
+
assertType(Array.isArray(value), path, "array");
|
|
361
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
362
|
+
throw new ValidationError(`${path}: expected array length >= ${schema.minItems}`);
|
|
363
|
+
}
|
|
364
|
+
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
365
|
+
throw new ValidationError(`${path}: expected array length <= ${schema.maxItems}`);
|
|
366
|
+
}
|
|
367
|
+
if (schema.items) {
|
|
368
|
+
value.forEach((item, index) => {
|
|
369
|
+
validateJsonSchema(item, schema.items, `${path}[${index}]`);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
return;
|
|
373
|
+
case "object":
|
|
374
|
+
assertPlainObject(value, path);
|
|
375
|
+
validateObject(value, schema, path);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function validateObject(value, schema, path) {
|
|
380
|
+
for (const key of schema.required ?? []) {
|
|
381
|
+
if (!(key in value)) throw new ValidationError(`${path}: missing required property ${key}`);
|
|
382
|
+
}
|
|
383
|
+
const properties = schema.properties ?? {};
|
|
384
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
385
|
+
if (key in value) validateJsonSchema(value[key], propSchema, `${path}.${key}`);
|
|
386
|
+
}
|
|
387
|
+
if (schema.additionalProperties === false) {
|
|
388
|
+
for (const key of Object.keys(value)) {
|
|
389
|
+
if (!(key in properties)) throw new ValidationError(`${path}: unexpected property ${key}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function assertType(ok, path, expected) {
|
|
394
|
+
if (!ok) throw new ValidationError(`${path}: expected ${expected}`);
|
|
395
|
+
}
|
|
396
|
+
function assertPlainObject(value, path) {
|
|
397
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
398
|
+
throw new ValidationError(`${path}: expected object`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
function assertNumberBounds(value, minimum, maximum, path) {
|
|
402
|
+
if (minimum !== void 0 && value < minimum) {
|
|
403
|
+
throw new ValidationError(`${path}: expected number >= ${minimum}`);
|
|
404
|
+
}
|
|
405
|
+
if (maximum !== void 0 && value > maximum) {
|
|
406
|
+
throw new ValidationError(`${path}: expected number <= ${maximum}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function assertEnum(value, allowed, path) {
|
|
410
|
+
if (allowed && !allowed.includes(value)) {
|
|
411
|
+
throw new ValidationError(`${path}: expected one of ${allowed.map(String).join(", ")}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
function assertSchemaRecord(value, path) {
|
|
415
|
+
const record = assertSchemaPlainRecord(value, path);
|
|
416
|
+
if (typeof record.type !== "string" || record.type.length === 0) {
|
|
417
|
+
throw new ValidationError(`${path}.type: expected non-empty string`);
|
|
418
|
+
}
|
|
419
|
+
return record;
|
|
420
|
+
}
|
|
421
|
+
function assertSchemaPlainRecord(value, path) {
|
|
422
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
423
|
+
throw new ValidationError(`${path}: expected object`);
|
|
424
|
+
}
|
|
425
|
+
return value;
|
|
426
|
+
}
|
|
427
|
+
function requireAllowedKeys(record, path, allowedKeys) {
|
|
428
|
+
const allowed = new Set(allowedKeys);
|
|
429
|
+
for (const key of Object.keys(record)) {
|
|
430
|
+
if (!allowed.has(key)) throw new ValidationError(`${path}.${key}: unsupported schema keyword`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function optionalFiniteNumber(value, path) {
|
|
434
|
+
if (value === void 0) return;
|
|
435
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
436
|
+
throw new ValidationError(`${path}: expected finite number`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function optionalNonNegativeNumber(value, path, options = {}) {
|
|
440
|
+
optionalFiniteNumber(value, path);
|
|
441
|
+
if (value === void 0) return;
|
|
442
|
+
if (value < 0) throw new ValidationError(`${path}: expected non-negative number`);
|
|
443
|
+
if (options.integer && !Number.isInteger(value)) {
|
|
444
|
+
throw new ValidationError(`${path}: expected integer`);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function optionalEnum(value, path, expected) {
|
|
448
|
+
if (value === void 0) return;
|
|
449
|
+
if (!Array.isArray(value)) throw new ValidationError(`${path}.enum: expected array`);
|
|
450
|
+
for (const [index, item] of value.entries()) {
|
|
451
|
+
const itemPath = `${path}.enum[${index}]`;
|
|
452
|
+
if (expected === "integer") {
|
|
453
|
+
if (!Number.isInteger(item)) throw new ValidationError(`${itemPath}: expected integer`);
|
|
454
|
+
} else if (expected === "number") {
|
|
455
|
+
if (typeof item !== "number" || !Number.isFinite(item)) {
|
|
456
|
+
throw new ValidationError(`${itemPath}: expected finite number`);
|
|
457
|
+
}
|
|
458
|
+
} else if (typeof item !== expected) {
|
|
459
|
+
throw new ValidationError(`${itemPath}: expected ${expected}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/workflow/options.ts
|
|
465
|
+
function normalizeWorkflowAgentOptions(input) {
|
|
466
|
+
const record = normalizeOptionsRecord(input, "agent");
|
|
467
|
+
rejectUnknownOptionKeys(record, "agent", ["label", "metadata", "schema", "allowWorkflow"]);
|
|
468
|
+
const out = {};
|
|
469
|
+
const label = optionalStringOption(record.label, "agent.label");
|
|
470
|
+
if (label !== void 0) out.label = label;
|
|
471
|
+
const metadata = optionalJsonRecordOption(record.metadata, "agent.metadata");
|
|
472
|
+
if (metadata !== void 0) out.metadata = metadata;
|
|
473
|
+
const schema = optionalJsonSchemaOption(record.schema, "agent.schema");
|
|
474
|
+
if (schema !== void 0) out.schema = schema;
|
|
475
|
+
if (record.allowWorkflow !== void 0) {
|
|
476
|
+
if (typeof record.allowWorkflow !== "boolean") {
|
|
477
|
+
throw new ValidationError("agent.allowWorkflow: expected boolean");
|
|
478
|
+
}
|
|
479
|
+
out.allowWorkflow = record.allowWorkflow;
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
function normalizeWorkflowLoopOptions(input) {
|
|
484
|
+
const record = normalizeOptionsRecord(input, "loop");
|
|
485
|
+
rejectUnknownOptionKeys(record, "loop", ["label", "metadata", "schema"]);
|
|
486
|
+
return normalizeCheckpointLikeOptions(record, "loop");
|
|
487
|
+
}
|
|
488
|
+
function normalizeWorkflowCheckpointOptions(input, kind) {
|
|
489
|
+
const record = normalizeOptionsRecord(input, kind);
|
|
490
|
+
rejectUnknownOptionKeys(record, kind, ["label", "metadata", "schema"]);
|
|
491
|
+
return normalizeCheckpointLikeOptions(record, kind);
|
|
492
|
+
}
|
|
493
|
+
function normalizeCheckpointLikeOptions(record, kind) {
|
|
494
|
+
const out = {};
|
|
495
|
+
const label = optionalStringOption(record.label, `${kind}.label`);
|
|
496
|
+
if (label !== void 0) out.label = label;
|
|
497
|
+
const metadata = optionalJsonRecordOption(record.metadata, `${kind}.metadata`);
|
|
498
|
+
if (metadata !== void 0) out.metadata = metadata;
|
|
499
|
+
const schema = optionalJsonSchemaOption(record.schema, `${kind}.schema`);
|
|
500
|
+
if (schema !== void 0) out.schema = schema;
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
function cloneJsonRecord(value, path) {
|
|
504
|
+
const out = cloneJsonValue(value, path);
|
|
505
|
+
if (!out || typeof out !== "object" || Array.isArray(out)) {
|
|
506
|
+
throw new ValidationError(`${path}: expected object`);
|
|
507
|
+
}
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
function cloneJsonValue(value, path, seen = /* @__PURE__ */ new Set()) {
|
|
511
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
512
|
+
if (typeof value === "number") {
|
|
513
|
+
if (!Number.isFinite(value)) throw new ValidationError(`${path}: expected finite JSON number`);
|
|
514
|
+
return value;
|
|
515
|
+
}
|
|
516
|
+
if (value === void 0) {
|
|
517
|
+
throw new ValidationError(`${path}: expected JSON value, got undefined`);
|
|
518
|
+
}
|
|
519
|
+
if (typeof value !== "object") {
|
|
520
|
+
throw new ValidationError(`${path}: expected JSON value, got ${typeof value}`);
|
|
521
|
+
}
|
|
522
|
+
if (seen.has(value)) throw new ValidationError(`${path}: JSON value cannot contain cycles`);
|
|
523
|
+
seen.add(value);
|
|
524
|
+
try {
|
|
525
|
+
if (Array.isArray(value)) return cloneJsonArray(value, path, seen);
|
|
526
|
+
return clonePlainJsonObject(value, path, seen);
|
|
527
|
+
} finally {
|
|
528
|
+
seen.delete(value);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function cloneJsonArray(value, path, seen) {
|
|
532
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
533
|
+
throw new ValidationError(`${path}: symbol properties are not JSON`);
|
|
534
|
+
}
|
|
535
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
536
|
+
const out = [];
|
|
537
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
538
|
+
const descriptor = descriptors[index];
|
|
539
|
+
if (!descriptor) throw new ValidationError(`${path}[${index}]: sparse arrays are not JSON`);
|
|
540
|
+
if (!("value" in descriptor)) {
|
|
541
|
+
throw new ValidationError(`${path}[${index}]: accessor properties are not allowed`);
|
|
542
|
+
}
|
|
543
|
+
out.push(cloneJsonValue(descriptor.value, `${path}[${index}]`, seen));
|
|
544
|
+
}
|
|
545
|
+
for (const key of Object.keys(descriptors)) {
|
|
546
|
+
if (!/^\d+$/.test(key) && key !== "length") {
|
|
547
|
+
throw new ValidationError(`${path}.${key}: array properties are not JSON`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
function clonePlainJsonObject(value, path, seen) {
|
|
553
|
+
if (Object.prototype.toString.call(value) !== "[object Object]") {
|
|
554
|
+
throw new ValidationError(`${path}: expected plain JSON object`);
|
|
555
|
+
}
|
|
556
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
557
|
+
throw new ValidationError(`${path}: symbol properties are not JSON`);
|
|
558
|
+
}
|
|
559
|
+
const out = {};
|
|
560
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
561
|
+
for (const [key, descriptor] of Object.entries(descriptors)) {
|
|
562
|
+
if (key === "__proto__" || key === "prototype" || key === "constructor") {
|
|
563
|
+
throw new ValidationError(`${path}.${key}: unsafe object key`);
|
|
564
|
+
}
|
|
565
|
+
if (!("value" in descriptor)) {
|
|
566
|
+
throw new ValidationError(`${path}.${key}: accessor properties are not allowed`);
|
|
567
|
+
}
|
|
568
|
+
out[key] = cloneJsonValue(descriptor.value, `${path}.${key}`, seen);
|
|
569
|
+
}
|
|
570
|
+
return out;
|
|
571
|
+
}
|
|
572
|
+
function normalizeOptionsRecord(input, kind) {
|
|
573
|
+
if (input === void 0) return {};
|
|
574
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
575
|
+
throw new ValidationError(`${kind} options must be an object`);
|
|
576
|
+
}
|
|
577
|
+
return cloneJsonRecord(input, `${kind} options`);
|
|
578
|
+
}
|
|
579
|
+
function rejectUnknownOptionKeys(record, kind, allowedKeys) {
|
|
580
|
+
const allowed = new Set(allowedKeys);
|
|
581
|
+
for (const key of Object.keys(record)) {
|
|
582
|
+
if (!allowed.has(key)) throw new ValidationError(`${kind}.${key}: unsupported workflow option`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function optionalStringOption(value, path) {
|
|
586
|
+
if (value === void 0) return void 0;
|
|
587
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
588
|
+
throw new ValidationError(`${path}: expected non-empty string`);
|
|
589
|
+
}
|
|
590
|
+
return value;
|
|
591
|
+
}
|
|
592
|
+
function optionalJsonRecordOption(value, path) {
|
|
593
|
+
if (value === void 0) return void 0;
|
|
594
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
595
|
+
throw new ValidationError(`${path}: expected object`);
|
|
596
|
+
}
|
|
597
|
+
return cloneJsonRecord(value, path);
|
|
598
|
+
}
|
|
599
|
+
function optionalJsonSchemaOption(value, path) {
|
|
600
|
+
if (value === void 0) return void 0;
|
|
601
|
+
const schema = cloneJsonValue(value, path);
|
|
602
|
+
validateJsonSchemaDefinition(schema, path);
|
|
603
|
+
return schema;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/workflow/realm.ts
|
|
607
|
+
import vm from "vm";
|
|
608
|
+
function installWorkflowGlobals(context, globals) {
|
|
609
|
+
const wrapAsync = async (run) => {
|
|
610
|
+
try {
|
|
611
|
+
return encodeBridgeSuccess(await run());
|
|
612
|
+
} catch (err) {
|
|
613
|
+
return encodeBridgeError(err);
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
const wrapSync = (run) => {
|
|
617
|
+
try {
|
|
618
|
+
return encodeBridgeSuccess(run());
|
|
619
|
+
} catch (err) {
|
|
620
|
+
return encodeBridgeError(err);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
const install = vm.compileFunction(
|
|
624
|
+
`
|
|
625
|
+
const __unwrap = (payload) => {
|
|
626
|
+
const envelope = JSON.parse(payload)
|
|
627
|
+
if (envelope.ok) return envelope.value
|
|
628
|
+
const error = new Error(envelope.error.message)
|
|
629
|
+
error.name = envelope.error.name || 'Error'
|
|
630
|
+
throw error
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const __safeMath = Object.freeze({
|
|
634
|
+
abs: Math.abs,
|
|
635
|
+
ceil: Math.ceil,
|
|
636
|
+
floor: Math.floor,
|
|
637
|
+
max: Math.max,
|
|
638
|
+
min: Math.min,
|
|
639
|
+
pow: Math.pow,
|
|
640
|
+
round: Math.round,
|
|
641
|
+
sign: Math.sign,
|
|
642
|
+
sqrt: Math.sqrt,
|
|
643
|
+
trunc: Math.trunc,
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
Object.defineProperties(globalThis, {
|
|
647
|
+
agent: { value: async (...args) => __unwrap(await __agent(...args)), enumerable: true },
|
|
648
|
+
loop: { value: async (...args) => __unwrap(await __loop(...args)), enumerable: true },
|
|
649
|
+
verify: { value: async (...args) => __unwrap(await __verify(...args)), enumerable: true },
|
|
650
|
+
analyzeTrace: { value: async (...args) => __unwrap(await __analyzeTrace(...args)), enumerable: true },
|
|
651
|
+
review: { value: async (...args) => __unwrap(await __review(...args)), enumerable: true },
|
|
652
|
+
parallel: { value: async (...args) => __unwrap(await __parallel(...args)), enumerable: true },
|
|
653
|
+
pipeline: { value: async (...args) => __unwrap(await __pipeline(...args)), enumerable: true },
|
|
654
|
+
phase: { value: (...args) => __unwrap(__phase(...args)), enumerable: true },
|
|
655
|
+
log: { value: (...args) => __unwrap(__log(...args)), enumerable: true },
|
|
656
|
+
budget: {
|
|
657
|
+
value: Object.freeze({
|
|
658
|
+
get total() {
|
|
659
|
+
return __unwrap(__budgetTotal())
|
|
660
|
+
},
|
|
661
|
+
spent() {
|
|
662
|
+
return __unwrap(__budgetSpent())
|
|
663
|
+
},
|
|
664
|
+
remaining() {
|
|
665
|
+
return __unwrap(__budgetRemaining())
|
|
666
|
+
},
|
|
667
|
+
}),
|
|
668
|
+
enumerable: true,
|
|
669
|
+
},
|
|
670
|
+
Math: { value: __safeMath },
|
|
671
|
+
Date: { value: undefined },
|
|
672
|
+
Function: { value: undefined },
|
|
673
|
+
eval: { value: undefined },
|
|
674
|
+
fetch: { value: undefined },
|
|
675
|
+
XMLHttpRequest: { value: undefined },
|
|
676
|
+
WebSocket: { value: undefined },
|
|
677
|
+
WebAssembly: { value: undefined },
|
|
678
|
+
setTimeout: { value: undefined },
|
|
679
|
+
setInterval: { value: undefined },
|
|
680
|
+
setImmediate: { value: undefined },
|
|
681
|
+
queueMicrotask: { value: undefined },
|
|
682
|
+
process: { value: undefined },
|
|
683
|
+
require: { value: undefined },
|
|
684
|
+
module: { value: undefined },
|
|
685
|
+
exports: { value: undefined },
|
|
686
|
+
globalThis: { value: undefined },
|
|
687
|
+
window: { value: undefined },
|
|
688
|
+
document: { value: undefined },
|
|
689
|
+
self: { value: undefined },
|
|
690
|
+
})
|
|
691
|
+
`,
|
|
692
|
+
[
|
|
693
|
+
"__agent",
|
|
694
|
+
"__loop",
|
|
695
|
+
"__verify",
|
|
696
|
+
"__analyzeTrace",
|
|
697
|
+
"__review",
|
|
698
|
+
"__parallel",
|
|
699
|
+
"__pipeline",
|
|
700
|
+
"__phase",
|
|
701
|
+
"__log",
|
|
702
|
+
"__budgetTotal",
|
|
703
|
+
"__budgetSpent",
|
|
704
|
+
"__budgetRemaining"
|
|
705
|
+
],
|
|
706
|
+
{ parsingContext: context }
|
|
707
|
+
);
|
|
708
|
+
install(
|
|
709
|
+
(prompt, options) => wrapAsync(() => globals.agent(prompt, options)),
|
|
710
|
+
(input, options) => wrapAsync(() => globals.loop(input, options)),
|
|
711
|
+
(input, options) => wrapAsync(() => globals.verify(input, options)),
|
|
712
|
+
(input, options) => wrapAsync(() => globals.analyzeTrace(input, options)),
|
|
713
|
+
(input, options) => wrapAsync(() => globals.review(input, options)),
|
|
714
|
+
(thunks) => wrapAsync(() => globals.parallel(thunks)),
|
|
715
|
+
(items, ...stages) => wrapAsync(() => globals.pipeline(items, ...stages)),
|
|
716
|
+
(title) => wrapSync(() => globals.phase(title)),
|
|
717
|
+
(message) => wrapSync(() => globals.log(message)),
|
|
718
|
+
() => wrapSync(() => finiteCaps(globals.budget.total)),
|
|
719
|
+
() => wrapSync(() => globals.budget.spent()),
|
|
720
|
+
() => wrapSync(() => finiteRemaining(globals.budget.remaining()))
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
function normalizeRuntimeError(err) {
|
|
724
|
+
if (err instanceof Error) return err;
|
|
725
|
+
const message = errorMessage(err);
|
|
726
|
+
const name = errorName(err);
|
|
727
|
+
if (name === "ValidationError") return new ValidationError(message, { cause: err });
|
|
728
|
+
const out = new Error(message, { cause: err });
|
|
729
|
+
if (name) out.name = name;
|
|
730
|
+
return out;
|
|
731
|
+
}
|
|
732
|
+
function encodeBridgeSuccess(value) {
|
|
733
|
+
return JSON.stringify({ ok: true, value: toBridgeValue(value) });
|
|
734
|
+
}
|
|
735
|
+
function encodeBridgeError(err) {
|
|
736
|
+
return JSON.stringify({
|
|
737
|
+
ok: false,
|
|
738
|
+
error: {
|
|
739
|
+
name: errorName(err),
|
|
740
|
+
message: errorMessage(err)
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
function toBridgeValue(value, path = "$", seen = /* @__PURE__ */ new Set()) {
|
|
745
|
+
if (value === void 0) return void 0;
|
|
746
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
747
|
+
if (typeof value === "number") {
|
|
748
|
+
if (!Number.isFinite(value)) {
|
|
749
|
+
throw new ValidationError(`workflow bridge cannot return non-finite number at ${path}`);
|
|
750
|
+
}
|
|
751
|
+
return value;
|
|
752
|
+
}
|
|
753
|
+
if (Array.isArray(value)) {
|
|
754
|
+
if (seen.has(value)) throw new ValidationError(`workflow bridge cannot return cycle at ${path}`);
|
|
755
|
+
seen.add(value);
|
|
756
|
+
const out = value.map((item, index) => toBridgeValue(item, `${path}[${index}]`, seen));
|
|
757
|
+
seen.delete(value);
|
|
758
|
+
return out;
|
|
759
|
+
}
|
|
760
|
+
if (typeof value === "object") {
|
|
761
|
+
if (Object.prototype.toString.call(value) !== "[object Object]") {
|
|
762
|
+
throw new ValidationError(`workflow bridge cannot return non-plain object at ${path}`);
|
|
763
|
+
}
|
|
764
|
+
if (seen.has(value)) throw new ValidationError(`workflow bridge cannot return cycle at ${path}`);
|
|
765
|
+
seen.add(value);
|
|
766
|
+
const out = {};
|
|
767
|
+
for (const [key, child] of Object.entries(value)) {
|
|
768
|
+
if (child === void 0) continue;
|
|
769
|
+
out[key] = toBridgeValue(child, `${path}.${key}`, seen);
|
|
770
|
+
}
|
|
771
|
+
seen.delete(value);
|
|
772
|
+
return out;
|
|
773
|
+
}
|
|
774
|
+
throw new ValidationError(`workflow bridge cannot return ${typeof value} at ${path}`);
|
|
775
|
+
}
|
|
776
|
+
function finiteCaps(caps) {
|
|
777
|
+
return finiteNumberRecord(caps);
|
|
778
|
+
}
|
|
779
|
+
function finiteRemaining(remaining) {
|
|
780
|
+
return finiteNumberRecord(remaining);
|
|
781
|
+
}
|
|
782
|
+
function finiteNumberRecord(input) {
|
|
783
|
+
const out = {};
|
|
784
|
+
for (const [key, value] of Object.entries(input)) {
|
|
785
|
+
if (typeof value === "number" && Number.isFinite(value)) out[key] = value;
|
|
786
|
+
}
|
|
787
|
+
return out;
|
|
788
|
+
}
|
|
789
|
+
function errorMessage(err) {
|
|
790
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
791
|
+
const message = err.message;
|
|
792
|
+
if (typeof message === "string") return message;
|
|
793
|
+
}
|
|
794
|
+
return String(err);
|
|
795
|
+
}
|
|
796
|
+
function errorName(err) {
|
|
797
|
+
if (err && typeof err === "object" && "name" in err) {
|
|
798
|
+
const name = err.name;
|
|
799
|
+
if (typeof name === "string") return name;
|
|
800
|
+
}
|
|
801
|
+
return void 0;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/workflow/runtime-support.ts
|
|
805
|
+
import vm2 from "vm";
|
|
806
|
+
var DEFAULT_CAPS = {
|
|
807
|
+
maxCostUsd: Number.POSITIVE_INFINITY,
|
|
808
|
+
maxTokens: Number.POSITIVE_INFINITY,
|
|
809
|
+
maxWallMs: 10 * 6e4,
|
|
810
|
+
maxAgentCalls: 32,
|
|
811
|
+
maxLoopCalls: 16,
|
|
812
|
+
maxFanout: 8,
|
|
813
|
+
maxDepth: 1
|
|
814
|
+
};
|
|
815
|
+
async function runWorkflowBody(body, workflowName, globals, syncTimeoutMs = 1e3) {
|
|
816
|
+
const context = vm2.createContext(/* @__PURE__ */ Object.create(null), {
|
|
817
|
+
name: `workflow:${workflowName}`,
|
|
818
|
+
codeGeneration: { strings: false, wasm: false }
|
|
819
|
+
});
|
|
820
|
+
installWorkflowGlobals(context, globals);
|
|
821
|
+
const script = new vm2.Script(
|
|
822
|
+
`
|
|
823
|
+
'use strict'
|
|
824
|
+
const __workflowMain = async () => {
|
|
825
|
+
${body}
|
|
826
|
+
}
|
|
827
|
+
__workflowMain()
|
|
828
|
+
`,
|
|
829
|
+
{ filename: `${workflowName}.workflow.js` }
|
|
830
|
+
);
|
|
831
|
+
return script.runInContext(context, { timeout: syncTimeoutMs });
|
|
832
|
+
}
|
|
833
|
+
function decodeWorkflowDelegateResult(result, options) {
|
|
834
|
+
if (options.schema) validateJsonSchema(result.output, options.schema);
|
|
835
|
+
return options.decode ? options.decode(result.output) : result.output;
|
|
836
|
+
}
|
|
837
|
+
function normalizeWorkflowCaps(caps = {}) {
|
|
838
|
+
return {
|
|
839
|
+
maxCostUsd: normalizeCap(caps.maxCostUsd, DEFAULT_CAPS.maxCostUsd, "maxCostUsd"),
|
|
840
|
+
maxTokens: normalizeCap(caps.maxTokens, DEFAULT_CAPS.maxTokens, "maxTokens"),
|
|
841
|
+
maxWallMs: normalizeCap(caps.maxWallMs, DEFAULT_CAPS.maxWallMs, "maxWallMs"),
|
|
842
|
+
maxAgentCalls: normalizeCap(caps.maxAgentCalls, DEFAULT_CAPS.maxAgentCalls, "maxAgentCalls", {
|
|
843
|
+
integer: true
|
|
844
|
+
}),
|
|
845
|
+
maxLoopCalls: normalizeCap(caps.maxLoopCalls, DEFAULT_CAPS.maxLoopCalls, "maxLoopCalls", {
|
|
846
|
+
integer: true
|
|
847
|
+
}),
|
|
848
|
+
maxFanout: normalizeCap(caps.maxFanout, DEFAULT_CAPS.maxFanout, "maxFanout", {
|
|
849
|
+
integer: true
|
|
850
|
+
}),
|
|
851
|
+
maxDepth: normalizeCap(caps.maxDepth, DEFAULT_CAPS.maxDepth, "maxDepth", { integer: true })
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
async function waitForWorkflowBudget(run, budget, signal, abortWorkflow, scope = "delegate") {
|
|
855
|
+
budget.assertWall();
|
|
856
|
+
const wallMs = budget.remainingWallMs();
|
|
857
|
+
if (signal?.aborted) throw new ValidationError(`workflow aborted before ${scope} completed`);
|
|
858
|
+
if (wallMs !== void 0 && wallMs <= 0) {
|
|
859
|
+
throw new ValidationError("workflow budget exhausted: maxWallMs reached");
|
|
860
|
+
}
|
|
861
|
+
const promise = run();
|
|
862
|
+
if (wallMs === void 0 || !Number.isFinite(wallMs)) return promise;
|
|
863
|
+
let timeout;
|
|
864
|
+
let abort;
|
|
865
|
+
try {
|
|
866
|
+
return await Promise.race([
|
|
867
|
+
promise,
|
|
868
|
+
new Promise((_, reject) => {
|
|
869
|
+
timeout = setTimeout(() => {
|
|
870
|
+
reject(new ValidationError(`workflow ${scope} timed out`));
|
|
871
|
+
abortWorkflow?.();
|
|
872
|
+
}, wallMs);
|
|
873
|
+
abort = () => reject(new ValidationError(`workflow aborted before ${scope} completed`));
|
|
874
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
875
|
+
})
|
|
876
|
+
]);
|
|
877
|
+
} finally {
|
|
878
|
+
if (timeout) clearTimeout(timeout);
|
|
879
|
+
if (abort) signal?.removeEventListener("abort", abort);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function createWorkflowSignal(external) {
|
|
883
|
+
const controller = new AbortController();
|
|
884
|
+
const abort = () => {
|
|
885
|
+
if (!controller.signal.aborted) controller.abort();
|
|
886
|
+
};
|
|
887
|
+
if (external) {
|
|
888
|
+
if (external.aborted) {
|
|
889
|
+
abort();
|
|
890
|
+
} else {
|
|
891
|
+
external.addEventListener("abort", abort, { once: true });
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
signal: controller.signal,
|
|
896
|
+
abort,
|
|
897
|
+
cleanup: () => external?.removeEventListener("abort", abort)
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function checkpointStartedKind(kind) {
|
|
901
|
+
switch (kind) {
|
|
902
|
+
case "verifier":
|
|
903
|
+
return "workflow.verifier.started";
|
|
904
|
+
case "analyst":
|
|
905
|
+
return "workflow.analyst.started";
|
|
906
|
+
case "reviewer":
|
|
907
|
+
return "workflow.reviewer.started";
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function checkpointEndedKind(kind) {
|
|
911
|
+
switch (kind) {
|
|
912
|
+
case "verifier":
|
|
913
|
+
return "workflow.verifier.ended";
|
|
914
|
+
case "analyst":
|
|
915
|
+
return "workflow.analyst.ended";
|
|
916
|
+
case "reviewer":
|
|
917
|
+
return "workflow.reviewer.ended";
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
function checkpointFailedKind(kind) {
|
|
921
|
+
switch (kind) {
|
|
922
|
+
case "verifier":
|
|
923
|
+
return "workflow.verifier.failed";
|
|
924
|
+
case "analyst":
|
|
925
|
+
return "workflow.analyst.failed";
|
|
926
|
+
case "reviewer":
|
|
927
|
+
return "workflow.reviewer.failed";
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
function assertWorkflowString(value, name) {
|
|
931
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
932
|
+
throw new ValidationError(`workflow ${name} must be a non-empty string`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function isRejected(settled) {
|
|
936
|
+
return settled.status === "rejected";
|
|
937
|
+
}
|
|
938
|
+
function fulfilledValues(settled) {
|
|
939
|
+
return settled.map((item) => item.value);
|
|
940
|
+
}
|
|
941
|
+
function normalizeCap(value, fallback, field, options = {}) {
|
|
942
|
+
const cap = value ?? fallback;
|
|
943
|
+
if (typeof cap !== "number" || Number.isNaN(cap) || cap < 0) {
|
|
944
|
+
throw new ValidationError(`workflow caps.${field} must be a non-negative number`);
|
|
945
|
+
}
|
|
946
|
+
if (options.integer && Number.isFinite(cap) && !Number.isInteger(cap)) {
|
|
947
|
+
throw new ValidationError(`workflow caps.${field} must be an integer`);
|
|
948
|
+
}
|
|
949
|
+
return cap;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/workflow/validate.ts
|
|
953
|
+
import vm3 from "vm";
|
|
954
|
+
var FORBIDDEN_BODY_PATTERNS = [
|
|
955
|
+
[/\bimport\b/, "import"],
|
|
956
|
+
[/\bexport\b/, "export"],
|
|
957
|
+
[/\brequire\b/, "require"],
|
|
958
|
+
[/\bprocess\b/, "process"],
|
|
959
|
+
[/\bDate\b/, "Date"],
|
|
960
|
+
[/\bFunction\b/, "Function"],
|
|
961
|
+
[/\beval\b/, "eval"],
|
|
962
|
+
[/\bfetch\b/, "fetch"],
|
|
963
|
+
[/\bXMLHttpRequest\b/, "XMLHttpRequest"],
|
|
964
|
+
[/\bWebSocket\b/, "WebSocket"],
|
|
965
|
+
[/\bsetTimeout\b/, "setTimeout"],
|
|
966
|
+
[/\bsetInterval\b/, "setInterval"],
|
|
967
|
+
[/\bsetImmediate\b/, "setImmediate"],
|
|
968
|
+
[/\bqueueMicrotask\b/, "queueMicrotask"],
|
|
969
|
+
[/\bglobalThis\b/, "globalThis"],
|
|
970
|
+
[/\bwindow\b/, "window"],
|
|
971
|
+
[/\bdocument\b/, "document"],
|
|
972
|
+
[/\bself\b/, "self"],
|
|
973
|
+
[/\bconstructor\b/, "constructor"],
|
|
974
|
+
[/\bprototype\b/, "prototype"],
|
|
975
|
+
[/\b__proto__\b/, "__proto__"],
|
|
976
|
+
[/\bmodule\b/, "module"],
|
|
977
|
+
[/\bexports\b/, "exports"],
|
|
978
|
+
[/\bwhile\b/, "while"],
|
|
979
|
+
[/\bdo\b/, "do"],
|
|
980
|
+
[/\bfor\s*\(/, "for-loop"],
|
|
981
|
+
[/\bMath\s*\.\s*random\b/, "Math.random"]
|
|
982
|
+
];
|
|
983
|
+
var FORBIDDEN_META_PATTERNS = [
|
|
984
|
+
[/\(/, "call expression"],
|
|
985
|
+
[/\bfunction\b/, "function"],
|
|
986
|
+
[/=>/, "arrow function"],
|
|
987
|
+
[/\bnew\b/, "new"],
|
|
988
|
+
[/\bclass\b/, "class"],
|
|
989
|
+
[/\.\.\./, "spread"],
|
|
990
|
+
[/`/, "template literal"],
|
|
991
|
+
[/\bconstructor\b/, "constructor"],
|
|
992
|
+
[/\bprototype\b/, "prototype"],
|
|
993
|
+
[/\b__proto__\b/, "__proto__"]
|
|
994
|
+
];
|
|
995
|
+
function parseWorkflowScript(source) {
|
|
996
|
+
if (typeof source !== "string" || source.trim().length === 0) {
|
|
997
|
+
throw new ValidationError("workflow source must be a non-empty string");
|
|
998
|
+
}
|
|
999
|
+
const marker = /^\s*export\s+const\s+meta\s*=\s*/.exec(source);
|
|
1000
|
+
if (!marker) {
|
|
1001
|
+
throw new ValidationError("workflow source must start with `export const meta = { ... }`");
|
|
1002
|
+
}
|
|
1003
|
+
const literalStart = marker[0].length;
|
|
1004
|
+
const literalEnd = findObjectLiteralEnd(source, literalStart);
|
|
1005
|
+
const literal = source.slice(literalStart, literalEnd);
|
|
1006
|
+
const restStart = consumeOptionalSemicolon(source, literalEnd);
|
|
1007
|
+
const body = source.slice(restStart).trim();
|
|
1008
|
+
const meta = evaluateMetaLiteral(literal);
|
|
1009
|
+
validateWorkflowBody(body);
|
|
1010
|
+
return { meta, body };
|
|
1011
|
+
}
|
|
1012
|
+
function validateWorkflowBody(body) {
|
|
1013
|
+
if (body.length === 0) {
|
|
1014
|
+
throw new ValidationError("workflow body must not be empty");
|
|
1015
|
+
}
|
|
1016
|
+
if (body.includes("`")) {
|
|
1017
|
+
throw new ValidationError("workflow body cannot use template literals");
|
|
1018
|
+
}
|
|
1019
|
+
const stripped = stripStringsAndComments(body);
|
|
1020
|
+
for (const [pattern, label] of FORBIDDEN_BODY_PATTERNS) {
|
|
1021
|
+
if (pattern.test(stripped)) {
|
|
1022
|
+
throw new ValidationError(`workflow body uses forbidden capability: ${label}`);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function findObjectLiteralEnd(source, start) {
|
|
1027
|
+
let i = start;
|
|
1028
|
+
while (/\s/.test(source[i] ?? "")) i += 1;
|
|
1029
|
+
if (source[i] !== "{") {
|
|
1030
|
+
throw new ValidationError("workflow meta must be an object literal");
|
|
1031
|
+
}
|
|
1032
|
+
let depth = 0;
|
|
1033
|
+
let quote;
|
|
1034
|
+
let escaped = false;
|
|
1035
|
+
for (; i < source.length; i += 1) {
|
|
1036
|
+
const ch = source[i];
|
|
1037
|
+
if (quote) {
|
|
1038
|
+
if (escaped) {
|
|
1039
|
+
escaped = false;
|
|
1040
|
+
} else if (ch === "\\") {
|
|
1041
|
+
escaped = true;
|
|
1042
|
+
} else if (ch === quote) {
|
|
1043
|
+
quote = void 0;
|
|
1044
|
+
}
|
|
1045
|
+
continue;
|
|
1046
|
+
}
|
|
1047
|
+
if (ch === '"' || ch === "'") {
|
|
1048
|
+
quote = ch;
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
if (ch === "`") throw new ValidationError("workflow meta cannot use template literals");
|
|
1052
|
+
if (ch === "{" || ch === "[") depth += 1;
|
|
1053
|
+
if (ch === "}" || ch === "]") {
|
|
1054
|
+
depth -= 1;
|
|
1055
|
+
if (depth === 0) return i + 1;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
throw new ValidationError("workflow meta object literal is not closed");
|
|
1059
|
+
}
|
|
1060
|
+
function consumeOptionalSemicolon(source, start) {
|
|
1061
|
+
let i = start;
|
|
1062
|
+
while (/\s/.test(source[i] ?? "")) i += 1;
|
|
1063
|
+
if (source[i] === ";") return i + 1;
|
|
1064
|
+
return i;
|
|
1065
|
+
}
|
|
1066
|
+
function evaluateMetaLiteral(literal) {
|
|
1067
|
+
for (const [pattern, label] of FORBIDDEN_META_PATTERNS) {
|
|
1068
|
+
if (pattern.test(literal)) {
|
|
1069
|
+
throw new ValidationError(`workflow meta uses forbidden syntax: ${label}`);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
let value;
|
|
1073
|
+
try {
|
|
1074
|
+
value = new vm3.Script(`(${literal})`).runInNewContext(/* @__PURE__ */ Object.create(null), {
|
|
1075
|
+
timeout: 50,
|
|
1076
|
+
contextCodeGeneration: { strings: false, wasm: false }
|
|
1077
|
+
});
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
throw new ValidationError(
|
|
1080
|
+
`workflow meta is not a parseable object literal: ${err instanceof Error ? err.message : String(err)}`,
|
|
1081
|
+
{ cause: err }
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1085
|
+
throw new ValidationError("workflow meta must evaluate to an object");
|
|
1086
|
+
}
|
|
1087
|
+
const raw = value;
|
|
1088
|
+
if (typeof raw.name !== "string" || raw.name.trim().length === 0) {
|
|
1089
|
+
throw new ValidationError("workflow meta.name must be a non-empty string");
|
|
1090
|
+
}
|
|
1091
|
+
if (typeof raw.description !== "string" || raw.description.trim().length === 0) {
|
|
1092
|
+
throw new ValidationError("workflow meta.description must be a non-empty string");
|
|
1093
|
+
}
|
|
1094
|
+
const out = {
|
|
1095
|
+
name: raw.name,
|
|
1096
|
+
description: raw.description
|
|
1097
|
+
};
|
|
1098
|
+
if (raw.phases !== void 0) {
|
|
1099
|
+
if (!Array.isArray(raw.phases))
|
|
1100
|
+
throw new ValidationError("workflow meta.phases must be an array");
|
|
1101
|
+
out.phases = raw.phases.map((phase, index) => {
|
|
1102
|
+
if (!phase || typeof phase !== "object" || Array.isArray(phase)) {
|
|
1103
|
+
throw new ValidationError(`workflow meta.phases[${index}] must be an object`);
|
|
1104
|
+
}
|
|
1105
|
+
const title = phase.title;
|
|
1106
|
+
if (typeof title !== "string" || title.trim().length === 0) {
|
|
1107
|
+
throw new ValidationError(`workflow meta.phases[${index}].title must be a non-empty string`);
|
|
1108
|
+
}
|
|
1109
|
+
return { title };
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
return out;
|
|
1113
|
+
}
|
|
1114
|
+
function stripStringsAndComments(source) {
|
|
1115
|
+
let out = "";
|
|
1116
|
+
let quote;
|
|
1117
|
+
let escaped = false;
|
|
1118
|
+
let lineComment = false;
|
|
1119
|
+
let blockComment = false;
|
|
1120
|
+
for (let i = 0; i < source.length; i += 1) {
|
|
1121
|
+
const ch = source[i];
|
|
1122
|
+
const next = source[i + 1];
|
|
1123
|
+
if (lineComment) {
|
|
1124
|
+
if (ch === "\n") {
|
|
1125
|
+
lineComment = false;
|
|
1126
|
+
out += "\n";
|
|
1127
|
+
} else {
|
|
1128
|
+
out += " ";
|
|
1129
|
+
}
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
if (blockComment) {
|
|
1133
|
+
if (ch === "*" && next === "/") {
|
|
1134
|
+
blockComment = false;
|
|
1135
|
+
out += " ";
|
|
1136
|
+
i += 1;
|
|
1137
|
+
} else {
|
|
1138
|
+
out += ch === "\n" ? "\n" : " ";
|
|
1139
|
+
}
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (quote) {
|
|
1143
|
+
if (escaped) {
|
|
1144
|
+
escaped = false;
|
|
1145
|
+
} else if (ch === "\\") {
|
|
1146
|
+
escaped = true;
|
|
1147
|
+
} else if (ch === quote) {
|
|
1148
|
+
quote = void 0;
|
|
1149
|
+
}
|
|
1150
|
+
out += ch === "\n" ? "\n" : " ";
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
if (ch === "/" && next === "/") {
|
|
1154
|
+
lineComment = true;
|
|
1155
|
+
out += " ";
|
|
1156
|
+
i += 1;
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
if (ch === "/" && next === "*") {
|
|
1160
|
+
blockComment = true;
|
|
1161
|
+
out += " ";
|
|
1162
|
+
i += 1;
|
|
1163
|
+
continue;
|
|
1164
|
+
}
|
|
1165
|
+
if (ch === '"' || ch === "'") {
|
|
1166
|
+
quote = ch;
|
|
1167
|
+
out += " ";
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
out += ch;
|
|
1171
|
+
}
|
|
1172
|
+
return out;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/workflow/runtime.ts
|
|
1176
|
+
async function runWorkflow(options) {
|
|
1177
|
+
const parsed = parseWorkflowScript(options.source);
|
|
1178
|
+
const runId = options.runId ?? `workflow-${randomUUID()}`;
|
|
1179
|
+
const depth = options.depth ?? 0;
|
|
1180
|
+
const caps = normalizeWorkflowCaps(options.caps);
|
|
1181
|
+
if (depth > caps.maxDepth) {
|
|
1182
|
+
throw new ValidationError(`workflow depth ${depth} exceeds maxDepth=${caps.maxDepth}`);
|
|
1183
|
+
}
|
|
1184
|
+
const now = options.now ?? Date.now;
|
|
1185
|
+
const budget = new WorkflowBudget(caps, now);
|
|
1186
|
+
const workflowSignal = createWorkflowSignal(options.signal);
|
|
1187
|
+
const events = [];
|
|
1188
|
+
const startedAt = now();
|
|
1189
|
+
let currentPhase;
|
|
1190
|
+
const checkpointCounts = {
|
|
1191
|
+
verifier: 0,
|
|
1192
|
+
analyst: 0,
|
|
1193
|
+
reviewer: 0
|
|
1194
|
+
};
|
|
1195
|
+
const emit = async (event) => {
|
|
1196
|
+
events.push(event);
|
|
1197
|
+
await options.traceEmitter?.emit(event);
|
|
1198
|
+
};
|
|
1199
|
+
const delegateCtx = () => ({
|
|
1200
|
+
workflowRunId: runId,
|
|
1201
|
+
depth,
|
|
1202
|
+
phase: currentPhase,
|
|
1203
|
+
signal: workflowSignal.signal,
|
|
1204
|
+
caps,
|
|
1205
|
+
budget,
|
|
1206
|
+
metadata: options.metadata
|
|
1207
|
+
});
|
|
1208
|
+
const emitNow = (event) => ({
|
|
1209
|
+
...event,
|
|
1210
|
+
runId,
|
|
1211
|
+
timestamp: now()
|
|
1212
|
+
});
|
|
1213
|
+
const emitDelegateFailure = async (kind, args) => {
|
|
1214
|
+
const normalized = normalizeRuntimeError(args.err);
|
|
1215
|
+
await emit(
|
|
1216
|
+
emitNow({
|
|
1217
|
+
kind,
|
|
1218
|
+
payload: {
|
|
1219
|
+
index: args.index,
|
|
1220
|
+
label: args.label,
|
|
1221
|
+
durationMs: now() - args.startedAt,
|
|
1222
|
+
message: normalized.message,
|
|
1223
|
+
code: normalized.name,
|
|
1224
|
+
phase: currentPhase
|
|
1225
|
+
}
|
|
1226
|
+
})
|
|
1227
|
+
);
|
|
1228
|
+
throw normalized;
|
|
1229
|
+
};
|
|
1230
|
+
const globals = {
|
|
1231
|
+
budget,
|
|
1232
|
+
phase(title) {
|
|
1233
|
+
assertWorkflowString(title, "phase title");
|
|
1234
|
+
currentPhase = title;
|
|
1235
|
+
void emit(emitNow({ kind: "workflow.phase", payload: { title } }));
|
|
1236
|
+
},
|
|
1237
|
+
log(message) {
|
|
1238
|
+
assertWorkflowString(message, "log message");
|
|
1239
|
+
void emit(emitNow({ kind: "workflow.log", payload: { message, phase: currentPhase } }));
|
|
1240
|
+
},
|
|
1241
|
+
async parallel(thunks) {
|
|
1242
|
+
if (!Array.isArray(thunks)) throw new ValidationError("parallel() expects an array");
|
|
1243
|
+
budget.assertFanout(thunks.length);
|
|
1244
|
+
thunks.forEach((thunk, index) => {
|
|
1245
|
+
if (typeof thunk !== "function") {
|
|
1246
|
+
throw new ValidationError(`parallel() branch ${index} is not a function`);
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
const opStarted = now();
|
|
1250
|
+
const operationPhase = currentPhase;
|
|
1251
|
+
await emit(
|
|
1252
|
+
emitNow({
|
|
1253
|
+
kind: "workflow.parallel.started",
|
|
1254
|
+
payload: { branchCount: thunks.length, phase: operationPhase }
|
|
1255
|
+
})
|
|
1256
|
+
);
|
|
1257
|
+
let firstFailure;
|
|
1258
|
+
const settled = await Promise.allSettled(
|
|
1259
|
+
thunks.map(async (thunk, index) => {
|
|
1260
|
+
const branchStarted = now();
|
|
1261
|
+
await emit(
|
|
1262
|
+
emitNow({
|
|
1263
|
+
kind: "workflow.branch.started",
|
|
1264
|
+
payload: { operation: "parallel", branchIndex: index, phase: operationPhase }
|
|
1265
|
+
})
|
|
1266
|
+
);
|
|
1267
|
+
try {
|
|
1268
|
+
const value = await thunk();
|
|
1269
|
+
await emit(
|
|
1270
|
+
emitNow({
|
|
1271
|
+
kind: "workflow.branch.ended",
|
|
1272
|
+
payload: {
|
|
1273
|
+
operation: "parallel",
|
|
1274
|
+
branchIndex: index,
|
|
1275
|
+
durationMs: now() - branchStarted,
|
|
1276
|
+
phase: operationPhase
|
|
1277
|
+
}
|
|
1278
|
+
})
|
|
1279
|
+
);
|
|
1280
|
+
return value;
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
const normalized = normalizeRuntimeError(err);
|
|
1283
|
+
firstFailure ??= normalized;
|
|
1284
|
+
workflowSignal.abort();
|
|
1285
|
+
await emit(
|
|
1286
|
+
emitNow({
|
|
1287
|
+
kind: "workflow.branch.failed",
|
|
1288
|
+
payload: {
|
|
1289
|
+
operation: "parallel",
|
|
1290
|
+
branchIndex: index,
|
|
1291
|
+
durationMs: now() - branchStarted,
|
|
1292
|
+
message: normalized.message,
|
|
1293
|
+
code: normalized.name,
|
|
1294
|
+
phase: operationPhase
|
|
1295
|
+
}
|
|
1296
|
+
})
|
|
1297
|
+
);
|
|
1298
|
+
throw normalized;
|
|
1299
|
+
}
|
|
1300
|
+
})
|
|
1301
|
+
);
|
|
1302
|
+
const rejected = settled.find(isRejected);
|
|
1303
|
+
if (rejected) throw firstFailure ?? normalizeRuntimeError(rejected.reason);
|
|
1304
|
+
await emit(
|
|
1305
|
+
emitNow({
|
|
1306
|
+
kind: "workflow.parallel.ended",
|
|
1307
|
+
payload: {
|
|
1308
|
+
branchCount: thunks.length,
|
|
1309
|
+
durationMs: now() - opStarted,
|
|
1310
|
+
phase: currentPhase
|
|
1311
|
+
}
|
|
1312
|
+
})
|
|
1313
|
+
);
|
|
1314
|
+
return fulfilledValues(settled);
|
|
1315
|
+
},
|
|
1316
|
+
async pipeline(items, ...stages) {
|
|
1317
|
+
if (!Array.isArray(items)) throw new ValidationError("pipeline() expects an item array");
|
|
1318
|
+
budget.assertFanout(items.length);
|
|
1319
|
+
if (stages.length === 0) throw new ValidationError("pipeline() expects at least one stage");
|
|
1320
|
+
stages.forEach((stage, index) => {
|
|
1321
|
+
if (typeof stage !== "function") {
|
|
1322
|
+
throw new ValidationError(`pipeline() stage ${index} is not a function`);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
const opStarted = now();
|
|
1326
|
+
const operationPhase = currentPhase;
|
|
1327
|
+
await emit(
|
|
1328
|
+
emitNow({
|
|
1329
|
+
kind: "workflow.pipeline.started",
|
|
1330
|
+
payload: { itemCount: items.length, stageCount: stages.length, phase: operationPhase }
|
|
1331
|
+
})
|
|
1332
|
+
);
|
|
1333
|
+
let firstFailure;
|
|
1334
|
+
const settled = await Promise.allSettled(
|
|
1335
|
+
items.map(async (item, index) => {
|
|
1336
|
+
const branchStarted = now();
|
|
1337
|
+
await emit(
|
|
1338
|
+
emitNow({
|
|
1339
|
+
kind: "workflow.branch.started",
|
|
1340
|
+
payload: {
|
|
1341
|
+
operation: "pipeline",
|
|
1342
|
+
branchIndex: index,
|
|
1343
|
+
stageCount: stages.length,
|
|
1344
|
+
phase: operationPhase
|
|
1345
|
+
}
|
|
1346
|
+
})
|
|
1347
|
+
);
|
|
1348
|
+
let stageIndex = -1;
|
|
1349
|
+
try {
|
|
1350
|
+
let value = item;
|
|
1351
|
+
for (const stage of stages) {
|
|
1352
|
+
stageIndex += 1;
|
|
1353
|
+
value = await stage(value, item, index);
|
|
1354
|
+
}
|
|
1355
|
+
await emit(
|
|
1356
|
+
emitNow({
|
|
1357
|
+
kind: "workflow.branch.ended",
|
|
1358
|
+
payload: {
|
|
1359
|
+
operation: "pipeline",
|
|
1360
|
+
branchIndex: index,
|
|
1361
|
+
durationMs: now() - branchStarted,
|
|
1362
|
+
stageCount: stages.length,
|
|
1363
|
+
phase: operationPhase
|
|
1364
|
+
}
|
|
1365
|
+
})
|
|
1366
|
+
);
|
|
1367
|
+
return value;
|
|
1368
|
+
} catch (err) {
|
|
1369
|
+
const normalized = normalizeRuntimeError(err);
|
|
1370
|
+
firstFailure ??= normalized;
|
|
1371
|
+
workflowSignal.abort();
|
|
1372
|
+
await emit(
|
|
1373
|
+
emitNow({
|
|
1374
|
+
kind: "workflow.branch.failed",
|
|
1375
|
+
payload: {
|
|
1376
|
+
operation: "pipeline",
|
|
1377
|
+
branchIndex: index,
|
|
1378
|
+
durationMs: now() - branchStarted,
|
|
1379
|
+
message: normalized.message,
|
|
1380
|
+
code: normalized.name,
|
|
1381
|
+
phase: operationPhase,
|
|
1382
|
+
...stageIndex >= 0 ? { stageIndex } : {}
|
|
1383
|
+
}
|
|
1384
|
+
})
|
|
1385
|
+
);
|
|
1386
|
+
throw normalized;
|
|
1387
|
+
}
|
|
1388
|
+
})
|
|
1389
|
+
);
|
|
1390
|
+
const rejected = settled.find(isRejected);
|
|
1391
|
+
if (rejected) throw firstFailure ?? normalizeRuntimeError(rejected.reason);
|
|
1392
|
+
await emit(
|
|
1393
|
+
emitNow({
|
|
1394
|
+
kind: "workflow.pipeline.ended",
|
|
1395
|
+
payload: {
|
|
1396
|
+
itemCount: items.length,
|
|
1397
|
+
stageCount: stages.length,
|
|
1398
|
+
durationMs: now() - opStarted,
|
|
1399
|
+
phase: currentPhase
|
|
1400
|
+
}
|
|
1401
|
+
})
|
|
1402
|
+
);
|
|
1403
|
+
return fulfilledValues(settled);
|
|
1404
|
+
},
|
|
1405
|
+
async agent(prompt, rawAgentOptions) {
|
|
1406
|
+
assertWorkflowString(prompt, "agent prompt");
|
|
1407
|
+
const agentOptions = normalizeWorkflowAgentOptions(rawAgentOptions);
|
|
1408
|
+
const index = budget.nextAgentIndex();
|
|
1409
|
+
const label = agentOptions.label;
|
|
1410
|
+
const opStarted = now();
|
|
1411
|
+
await emit(
|
|
1412
|
+
emitNow({
|
|
1413
|
+
kind: "workflow.agent.started",
|
|
1414
|
+
payload: {
|
|
1415
|
+
index,
|
|
1416
|
+
label,
|
|
1417
|
+
promptChars: prompt.length,
|
|
1418
|
+
phase: currentPhase,
|
|
1419
|
+
metadata: agentOptions.metadata
|
|
1420
|
+
}
|
|
1421
|
+
})
|
|
1422
|
+
);
|
|
1423
|
+
try {
|
|
1424
|
+
const result = await waitForWorkflowBudget(
|
|
1425
|
+
() => options.agent(prompt, agentOptions, delegateCtx()),
|
|
1426
|
+
budget,
|
|
1427
|
+
workflowSignal.signal,
|
|
1428
|
+
workflowSignal.abort
|
|
1429
|
+
);
|
|
1430
|
+
const output = decodeWorkflowDelegateResult(result, agentOptions);
|
|
1431
|
+
const usage = budget.observe(result);
|
|
1432
|
+
await emit(
|
|
1433
|
+
emitNow({
|
|
1434
|
+
kind: "workflow.agent.ended",
|
|
1435
|
+
payload: {
|
|
1436
|
+
index,
|
|
1437
|
+
label,
|
|
1438
|
+
durationMs: now() - opStarted,
|
|
1439
|
+
costUsd: usage.costUsd,
|
|
1440
|
+
tokenUsage: usage.tokenUsage,
|
|
1441
|
+
phase: currentPhase,
|
|
1442
|
+
trace: result.trace
|
|
1443
|
+
}
|
|
1444
|
+
})
|
|
1445
|
+
);
|
|
1446
|
+
return output;
|
|
1447
|
+
} catch (err) {
|
|
1448
|
+
return emitDelegateFailure("workflow.agent.failed", {
|
|
1449
|
+
err,
|
|
1450
|
+
index,
|
|
1451
|
+
label,
|
|
1452
|
+
startedAt: opStarted
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
},
|
|
1456
|
+
async loop(input, rawLoopOptions) {
|
|
1457
|
+
if (!options.loop) throw new ValidationError("workflow loop() delegate is not configured");
|
|
1458
|
+
const loopOptions = normalizeWorkflowLoopOptions(rawLoopOptions);
|
|
1459
|
+
const index = budget.nextLoopIndex();
|
|
1460
|
+
const label = loopOptions.label;
|
|
1461
|
+
const opStarted = now();
|
|
1462
|
+
await emit(
|
|
1463
|
+
emitNow({
|
|
1464
|
+
kind: "workflow.loop.started",
|
|
1465
|
+
payload: {
|
|
1466
|
+
index,
|
|
1467
|
+
label,
|
|
1468
|
+
phase: currentPhase,
|
|
1469
|
+
metadata: loopOptions.metadata
|
|
1470
|
+
}
|
|
1471
|
+
})
|
|
1472
|
+
);
|
|
1473
|
+
try {
|
|
1474
|
+
const result = await waitForWorkflowBudget(
|
|
1475
|
+
() => options.loop(input, loopOptions, delegateCtx()),
|
|
1476
|
+
budget,
|
|
1477
|
+
workflowSignal.signal,
|
|
1478
|
+
workflowSignal.abort
|
|
1479
|
+
);
|
|
1480
|
+
const output = decodeWorkflowDelegateResult(result, loopOptions);
|
|
1481
|
+
const usage = budget.observe(result);
|
|
1482
|
+
await emit(
|
|
1483
|
+
emitNow({
|
|
1484
|
+
kind: "workflow.loop.ended",
|
|
1485
|
+
payload: {
|
|
1486
|
+
index,
|
|
1487
|
+
label,
|
|
1488
|
+
durationMs: now() - opStarted,
|
|
1489
|
+
costUsd: usage.costUsd,
|
|
1490
|
+
tokenUsage: usage.tokenUsage,
|
|
1491
|
+
phase: currentPhase,
|
|
1492
|
+
trace: result.trace
|
|
1493
|
+
}
|
|
1494
|
+
})
|
|
1495
|
+
);
|
|
1496
|
+
return output;
|
|
1497
|
+
} catch (err) {
|
|
1498
|
+
return emitDelegateFailure("workflow.loop.failed", {
|
|
1499
|
+
err,
|
|
1500
|
+
index,
|
|
1501
|
+
label,
|
|
1502
|
+
startedAt: opStarted
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
},
|
|
1506
|
+
verify(input, rawVerifierOptions) {
|
|
1507
|
+
if (!options.verifier) {
|
|
1508
|
+
throw new ValidationError("workflow verify() delegate is not configured");
|
|
1509
|
+
}
|
|
1510
|
+
const verifierOptions = normalizeWorkflowCheckpointOptions(rawVerifierOptions, "verify");
|
|
1511
|
+
return runCheckpoint({
|
|
1512
|
+
kind: "verifier",
|
|
1513
|
+
input,
|
|
1514
|
+
checkpointOptions: verifierOptions,
|
|
1515
|
+
delegate: options.verifier
|
|
1516
|
+
});
|
|
1517
|
+
},
|
|
1518
|
+
analyzeTrace(input, rawAnalystOptions) {
|
|
1519
|
+
if (!options.analyst) {
|
|
1520
|
+
throw new ValidationError("workflow analyzeTrace() delegate is not configured");
|
|
1521
|
+
}
|
|
1522
|
+
const analystOptions = normalizeWorkflowCheckpointOptions(rawAnalystOptions, "analyzeTrace");
|
|
1523
|
+
return runCheckpoint({
|
|
1524
|
+
kind: "analyst",
|
|
1525
|
+
input,
|
|
1526
|
+
checkpointOptions: analystOptions,
|
|
1527
|
+
delegate: options.analyst
|
|
1528
|
+
});
|
|
1529
|
+
},
|
|
1530
|
+
review(input, rawReviewerOptions) {
|
|
1531
|
+
if (!options.reviewer) {
|
|
1532
|
+
throw new ValidationError("workflow review() delegate is not configured");
|
|
1533
|
+
}
|
|
1534
|
+
const reviewerOptions = normalizeWorkflowCheckpointOptions(rawReviewerOptions, "review");
|
|
1535
|
+
return runCheckpoint({
|
|
1536
|
+
kind: "reviewer",
|
|
1537
|
+
input,
|
|
1538
|
+
checkpointOptions: reviewerOptions,
|
|
1539
|
+
delegate: options.reviewer
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
async function runCheckpoint(args) {
|
|
1544
|
+
const index = checkpointCounts[args.kind];
|
|
1545
|
+
checkpointCounts[args.kind] += 1;
|
|
1546
|
+
const label = args.checkpointOptions.label;
|
|
1547
|
+
const opStarted = now();
|
|
1548
|
+
await emit(
|
|
1549
|
+
emitNow({
|
|
1550
|
+
kind: checkpointStartedKind(args.kind),
|
|
1551
|
+
payload: {
|
|
1552
|
+
index,
|
|
1553
|
+
label,
|
|
1554
|
+
phase: currentPhase,
|
|
1555
|
+
metadata: args.checkpointOptions.metadata
|
|
1556
|
+
}
|
|
1557
|
+
})
|
|
1558
|
+
);
|
|
1559
|
+
try {
|
|
1560
|
+
const result = await waitForWorkflowBudget(
|
|
1561
|
+
() => args.delegate(args.input, args.checkpointOptions, delegateCtx()),
|
|
1562
|
+
budget,
|
|
1563
|
+
workflowSignal.signal,
|
|
1564
|
+
workflowSignal.abort
|
|
1565
|
+
);
|
|
1566
|
+
const output = decodeWorkflowDelegateResult(result, args.checkpointOptions);
|
|
1567
|
+
const usage = budget.observe(result);
|
|
1568
|
+
await emit(
|
|
1569
|
+
emitNow({
|
|
1570
|
+
kind: checkpointEndedKind(args.kind),
|
|
1571
|
+
payload: {
|
|
1572
|
+
index,
|
|
1573
|
+
label,
|
|
1574
|
+
durationMs: now() - opStarted,
|
|
1575
|
+
costUsd: usage.costUsd,
|
|
1576
|
+
tokenUsage: usage.tokenUsage,
|
|
1577
|
+
phase: currentPhase,
|
|
1578
|
+
trace: result.trace
|
|
1579
|
+
}
|
|
1580
|
+
})
|
|
1581
|
+
);
|
|
1582
|
+
return output;
|
|
1583
|
+
} catch (err) {
|
|
1584
|
+
return emitDelegateFailure(checkpointFailedKind(args.kind), {
|
|
1585
|
+
err,
|
|
1586
|
+
index,
|
|
1587
|
+
label,
|
|
1588
|
+
startedAt: opStarted
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
await emit(emitNow({ kind: "workflow.started", payload: { meta: parsed.meta, depth, caps } }));
|
|
1593
|
+
try {
|
|
1594
|
+
const output = await waitForWorkflowBudget(
|
|
1595
|
+
() => runWorkflowBody(parsed.body, parsed.meta.name, globals, options.syncTimeoutMs),
|
|
1596
|
+
budget,
|
|
1597
|
+
options.signal,
|
|
1598
|
+
workflowSignal.abort,
|
|
1599
|
+
"body"
|
|
1600
|
+
);
|
|
1601
|
+
const spent = budget.spent();
|
|
1602
|
+
const result = {
|
|
1603
|
+
runId,
|
|
1604
|
+
meta: parsed.meta,
|
|
1605
|
+
output,
|
|
1606
|
+
events,
|
|
1607
|
+
durationMs: now() - startedAt,
|
|
1608
|
+
costUsd: spent.costUsd,
|
|
1609
|
+
tokenUsage: spent.tokens,
|
|
1610
|
+
agentCalls: spent.agentCalls,
|
|
1611
|
+
loopCalls: spent.loopCalls
|
|
1612
|
+
};
|
|
1613
|
+
await emit(
|
|
1614
|
+
emitNow({
|
|
1615
|
+
kind: "workflow.ended",
|
|
1616
|
+
payload: {
|
|
1617
|
+
durationMs: result.durationMs,
|
|
1618
|
+
costUsd: result.costUsd,
|
|
1619
|
+
tokenUsage: result.tokenUsage,
|
|
1620
|
+
agentCalls: result.agentCalls,
|
|
1621
|
+
loopCalls: result.loopCalls
|
|
1622
|
+
}
|
|
1623
|
+
})
|
|
1624
|
+
);
|
|
1625
|
+
return { ...result, events };
|
|
1626
|
+
} catch (err) {
|
|
1627
|
+
const normalized = normalizeRuntimeError(err);
|
|
1628
|
+
workflowSignal.abort();
|
|
1629
|
+
await emit(
|
|
1630
|
+
emitNow({
|
|
1631
|
+
kind: "workflow.failed",
|
|
1632
|
+
payload: {
|
|
1633
|
+
message: normalized.message,
|
|
1634
|
+
code: normalized.name,
|
|
1635
|
+
phase: currentPhase
|
|
1636
|
+
}
|
|
1637
|
+
})
|
|
1638
|
+
);
|
|
1639
|
+
throw normalized;
|
|
1640
|
+
} finally {
|
|
1641
|
+
workflowSignal.cleanup();
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// src/workflow/nested-workflow-delegate.ts
|
|
1646
|
+
function createNestedWorkflowAgentDelegate(options) {
|
|
1647
|
+
if (!options.agent) {
|
|
1648
|
+
throw new ValidationError("nested workflow delegate: base agent delegate is required");
|
|
1649
|
+
}
|
|
1650
|
+
const delegate = async (prompt, agentOptions, ctx) => {
|
|
1651
|
+
if (!agentOptions.allowWorkflow) return options.agent(prompt, agentOptions, ctx);
|
|
1652
|
+
assertNestedDepth(ctx);
|
|
1653
|
+
const input = {
|
|
1654
|
+
source: prompt,
|
|
1655
|
+
options: agentOptions,
|
|
1656
|
+
parent: ctx
|
|
1657
|
+
};
|
|
1658
|
+
const caps = boundNestedCaps(resolveNestedCaps(options.caps, input), ctx);
|
|
1659
|
+
const metadata = resolveMetadata(options.metadata, input);
|
|
1660
|
+
const result = await runWorkflow({
|
|
1661
|
+
source: prompt,
|
|
1662
|
+
runId: options.runId?.(input),
|
|
1663
|
+
depth: ctx.depth + 1,
|
|
1664
|
+
caps,
|
|
1665
|
+
metadata: {
|
|
1666
|
+
parentWorkflowRunId: ctx.workflowRunId,
|
|
1667
|
+
parentPhase: ctx.phase,
|
|
1668
|
+
parentAgentLabel: agentOptions.label,
|
|
1669
|
+
...ctx.metadata ?? {},
|
|
1670
|
+
...metadata ?? {}
|
|
1671
|
+
},
|
|
1672
|
+
signal: ctx.signal,
|
|
1673
|
+
agent: delegate,
|
|
1674
|
+
loop: options.loop,
|
|
1675
|
+
verifier: options.verifier,
|
|
1676
|
+
analyst: options.analyst,
|
|
1677
|
+
reviewer: options.reviewer,
|
|
1678
|
+
traceEmitter: options.traceEmitter
|
|
1679
|
+
});
|
|
1680
|
+
return {
|
|
1681
|
+
output: options.toOutput ? options.toOutput(result, input) : result.output,
|
|
1682
|
+
costUsd: result.costUsd,
|
|
1683
|
+
tokenUsage: result.tokenUsage,
|
|
1684
|
+
agentCalls: result.agentCalls,
|
|
1685
|
+
loopCalls: result.loopCalls,
|
|
1686
|
+
trace: options.toTrace ? options.toTrace(result, input) : defaultTrace3(result, input, options)
|
|
1687
|
+
};
|
|
1688
|
+
};
|
|
1689
|
+
return delegate;
|
|
1690
|
+
}
|
|
1691
|
+
function resolveNestedCaps(resolver, input) {
|
|
1692
|
+
const caps = typeof resolver === "function" ? resolver(input) : resolver;
|
|
1693
|
+
if (!caps || typeof caps !== "object") {
|
|
1694
|
+
throw new ValidationError("nested workflow caps must be an object");
|
|
1695
|
+
}
|
|
1696
|
+
assertFiniteCap(caps.maxWallMs, "maxWallMs");
|
|
1697
|
+
assertFiniteCap(caps.maxAgentCalls, "maxAgentCalls");
|
|
1698
|
+
assertFiniteCap(caps.maxLoopCalls, "maxLoopCalls");
|
|
1699
|
+
assertFiniteCap(caps.maxFanout, "maxFanout");
|
|
1700
|
+
assertFiniteCap(caps.maxDepth, "maxDepth");
|
|
1701
|
+
if (caps.maxCostUsd !== void 0) assertFiniteCap(caps.maxCostUsd, "maxCostUsd");
|
|
1702
|
+
if (caps.maxTokens !== void 0) assertFiniteCap(caps.maxTokens, "maxTokens");
|
|
1703
|
+
return caps;
|
|
1704
|
+
}
|
|
1705
|
+
function boundNestedCaps(caps, ctx) {
|
|
1706
|
+
const remaining = ctx.budget.remaining();
|
|
1707
|
+
return {
|
|
1708
|
+
...caps,
|
|
1709
|
+
maxCostUsd: minOptionalCap(caps.maxCostUsd, remaining.costUsd),
|
|
1710
|
+
maxTokens: minOptionalCap(caps.maxTokens, remaining.tokens),
|
|
1711
|
+
maxWallMs: minRequiredCap(caps.maxWallMs, remaining.wallMs, "maxWallMs"),
|
|
1712
|
+
maxAgentCalls: minRequiredCap(caps.maxAgentCalls, remaining.agentCalls, "maxAgentCalls"),
|
|
1713
|
+
maxLoopCalls: minRequiredCap(caps.maxLoopCalls, remaining.loopCalls, "maxLoopCalls"),
|
|
1714
|
+
maxFanout: minRequiredCap(caps.maxFanout, ctx.caps.maxFanout, "maxFanout"),
|
|
1715
|
+
maxDepth: minRequiredCap(caps.maxDepth, ctx.caps.maxDepth, "maxDepth")
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
function assertNestedDepth(ctx) {
|
|
1719
|
+
const maxDepth = ctx.caps.maxDepth;
|
|
1720
|
+
if (typeof maxDepth !== "number" || !Number.isFinite(maxDepth)) {
|
|
1721
|
+
throw new ValidationError("nested workflow parent caps.maxDepth must be finite");
|
|
1722
|
+
}
|
|
1723
|
+
const childDepth = ctx.depth + 1;
|
|
1724
|
+
if (childDepth > maxDepth) {
|
|
1725
|
+
throw new ValidationError(`nested workflow depth ${childDepth} exceeds maxDepth=${maxDepth}`);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
function minRequiredCap(child, parent, field) {
|
|
1729
|
+
assertFiniteCap(child, field);
|
|
1730
|
+
if (parent === void 0 || !Number.isFinite(parent)) return child;
|
|
1731
|
+
return Math.min(child, parent);
|
|
1732
|
+
}
|
|
1733
|
+
function minOptionalCap(child, parent) {
|
|
1734
|
+
if (child !== void 0 && parent !== void 0 && Number.isFinite(parent)) {
|
|
1735
|
+
return Math.min(child, parent);
|
|
1736
|
+
}
|
|
1737
|
+
if (child !== void 0) return child;
|
|
1738
|
+
return parent !== void 0 && Number.isFinite(parent) ? parent : void 0;
|
|
1739
|
+
}
|
|
1740
|
+
function assertFiniteCap(value, field) {
|
|
1741
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
1742
|
+
throw new ValidationError(`nested workflow caps.${field} must be a finite non-negative number`);
|
|
1743
|
+
}
|
|
1744
|
+
if ((field === "maxAgentCalls" || field === "maxLoopCalls" || field === "maxFanout" || field === "maxDepth") && !Number.isInteger(value)) {
|
|
1745
|
+
throw new ValidationError(`nested workflow caps.${field} must be an integer`);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
function resolveMetadata(resolver, input) {
|
|
1749
|
+
return typeof resolver === "function" ? resolver(input) : resolver;
|
|
1750
|
+
}
|
|
1751
|
+
function defaultTrace3(result, input, options) {
|
|
1752
|
+
return {
|
|
1753
|
+
nested: true,
|
|
1754
|
+
runId: result.runId,
|
|
1755
|
+
parentRunId: input.parent.workflowRunId,
|
|
1756
|
+
depth: input.parent.depth + 1,
|
|
1757
|
+
metaName: result.meta.name,
|
|
1758
|
+
eventCount: result.events.length,
|
|
1759
|
+
eventKinds: result.events.map((event) => event.kind),
|
|
1760
|
+
durationMs: result.durationMs,
|
|
1761
|
+
costUsd: result.costUsd,
|
|
1762
|
+
tokenUsage: result.tokenUsage,
|
|
1763
|
+
agentCalls: result.agentCalls,
|
|
1764
|
+
loopCalls: result.loopCalls,
|
|
1765
|
+
...options.includeEventsInTrace ? { events: result.events } : {}
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
export {
|
|
1769
|
+
WorkflowBudget,
|
|
1770
|
+
createNestedWorkflowAgentDelegate,
|
|
1771
|
+
createRunLoopWorkflowDelegate,
|
|
1772
|
+
createSandboxWorkflowAgentDelegate,
|
|
1773
|
+
parseSandboxAgentDefaultOutput,
|
|
1774
|
+
parseWorkflowScript,
|
|
1775
|
+
runWorkflow,
|
|
1776
|
+
validateJsonSchema,
|
|
1777
|
+
validateWorkflowBody
|
|
1778
|
+
};
|
|
1779
|
+
//# sourceMappingURL=workflow.js.map
|