@reaatech/media-pipeline-mcp-core 0.3.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/dist/index.cjs ADDED
@@ -0,0 +1,2252 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ A2AError: () => A2AError,
24
+ ArtifactAccessDeniedError: () => ArtifactAccessDeniedError,
25
+ ArtifactMetaSchema: () => ArtifactMetaSchema,
26
+ ArtifactNotFoundError: () => ArtifactNotFoundError,
27
+ ArtifactRegistry: () => ArtifactRegistry,
28
+ ArtifactSchema: () => ArtifactSchema,
29
+ ArtifactTypeSchema: () => ArtifactTypeSchema,
30
+ BrandKitSchema: () => BrandKitSchema,
31
+ BudgetConfigSchema: () => BudgetConfigSchema,
32
+ BudgetExceededError: () => BudgetExceededError,
33
+ CacheConfigSchema: () => CacheConfigSchema,
34
+ ContextRefSchema: () => ContextRefSchema,
35
+ ContextRefTypeError: () => ContextRefTypeError,
36
+ ContextRefUnknownError: () => ContextRefUnknownError,
37
+ CostRecordSchema: () => CostRecordSchema,
38
+ CostSummarySchema: () => CostSummarySchema,
39
+ CustomEvaluator: () => CustomEvaluator,
40
+ DimensionCheckEvaluator: () => DimensionCheckEvaluator,
41
+ EstimateUnsupportedError: () => EstimateUnsupportedError,
42
+ FfmpegUnavailableError: () => FfmpegUnavailableError,
43
+ FormatUnsupportedError: () => FormatUnsupportedError,
44
+ IdempotencyConflictError: () => IdempotencyConflictError,
45
+ InvalidInputError: () => InvalidInputError,
46
+ InvalidResourceUriError: () => InvalidResourceUriError,
47
+ JudgeConfigSchema: () => JudgeConfigSchema,
48
+ JudgeRubricSchema: () => JudgeRubricSchema,
49
+ JudgeUnavailableError: () => JudgeUnavailableError,
50
+ KeyVaultUnavailableError: () => KeyVaultUnavailableError,
51
+ LLMJudgeEvaluator: () => LLMJudgeEvaluator,
52
+ LoudnessGateFailedError: () => LoudnessGateFailedError,
53
+ MockProvider: () => MockProvider,
54
+ PipelineDefinitionSchema: () => PipelineDefinitionSchema,
55
+ PipelineEstimator: () => PipelineEstimator,
56
+ PipelineEventSchema: () => PipelineEventSchema,
57
+ PipelineEventTypeSchema: () => PipelineEventTypeSchema,
58
+ PipelineExecutor: () => PipelineExecutor,
59
+ PipelineSchema: () => PipelineSchema,
60
+ PipelineStatusSchema: () => PipelineStatusSchema,
61
+ PipelineStepSchema: () => PipelineStepSchema,
62
+ PipelineValidator: () => PipelineValidator,
63
+ ProvenanceSigningFailedError: () => ProvenanceSigningFailedError,
64
+ ProviderInputSchema: () => ProviderInputSchema,
65
+ ProviderOutputSchema: () => ProviderOutputSchema,
66
+ QualityGateActionSchema: () => QualityGateActionSchema,
67
+ QualityGateResultSchema: () => QualityGateResultSchema,
68
+ QualityGateSchema: () => QualityGateSchema,
69
+ RatioUnsupportedError: () => RatioUnsupportedError,
70
+ RouteCandidateSchema: () => RouteCandidateSchema,
71
+ RouteConfigSchema: () => RouteConfigSchema,
72
+ RouterAllCandidatesFailedError: () => RouterAllCandidatesFailedError,
73
+ RouterFastestIneligibleError: () => RouterFastestIneligibleError,
74
+ RouterNoCandidatesError: () => RouterNoCandidatesError,
75
+ RunContextSchema: () => RunContextSchema,
76
+ RunInProgressError: () => RunInProgressError,
77
+ RunNotFoundError: () => RunNotFoundError,
78
+ RunNotResumableError: () => RunNotResumableError,
79
+ SafetyGateRejectedError: () => SafetyGateRejectedError,
80
+ SafetyProviderUnavailableError: () => SafetyProviderUnavailableError,
81
+ StateStoreUnavailableError: () => StateStoreUnavailableError,
82
+ StorageResultSchema: () => StorageResultSchema,
83
+ StyleRefSchema: () => StyleRefSchema,
84
+ TenantNotFoundError: () => TenantNotFoundError,
85
+ TenantPolicyViolationError: () => TenantPolicyViolationError,
86
+ ThresholdEvaluator: () => ThresholdEvaluator,
87
+ ValidationResultSchema: () => ValidationResultSchema,
88
+ VariantsAllRejectedError: () => VariantsAllRejectedError,
89
+ VariantsConfigSchema: () => VariantsConfigSchema,
90
+ VoiceRefSchema: () => VoiceRefSchema,
91
+ WebhookProviderUnknownError: () => WebhookProviderUnknownError,
92
+ WebhookSignatureInvalidError: () => WebhookSignatureInvalidError,
93
+ WorkflowExpiredError: () => WorkflowExpiredError,
94
+ WorkflowNotFoundError: () => WorkflowNotFoundError,
95
+ createEventBus: () => createEventBus,
96
+ createQualityGateEvaluator: () => createQualityGateEvaluator,
97
+ createStepStateRecord: () => createStepStateRecord,
98
+ mockOperations: () => mockOperations
99
+ });
100
+ module.exports = __toCommonJS(index_exports);
101
+
102
+ // src/types/index.ts
103
+ var import_zod = require("zod");
104
+ var ArtifactTypeSchema = import_zod.z.enum(["image", "video", "audio", "text", "document"]);
105
+ var ArtifactSchema = import_zod.z.object({
106
+ id: import_zod.z.string(),
107
+ type: ArtifactTypeSchema,
108
+ uri: import_zod.z.string(),
109
+ mimeType: import_zod.z.string(),
110
+ metadata: import_zod.z.record(import_zod.z.unknown()).default({}),
111
+ sourceStep: import_zod.z.string().optional(),
112
+ createdAt: import_zod.z.string().datetime().optional()
113
+ });
114
+ var QualityGateActionSchema = import_zod.z.enum(["fail", "retry", "warn"]);
115
+ var QualityGateSchema = import_zod.z.object({
116
+ type: import_zod.z.enum(["llm-judge", "threshold", "dimension-check", "custom"]),
117
+ config: import_zod.z.record(import_zod.z.unknown()),
118
+ action: QualityGateActionSchema,
119
+ maxRetries: import_zod.z.number().int().min(0).optional()
120
+ });
121
+ var BudgetConfigSchema = import_zod.z.object({
122
+ maxUsd: import_zod.z.number().nonnegative(),
123
+ onExceed: import_zod.z.enum(["abort", "suspend"]),
124
+ warnAtPct: import_zod.z.number().min(0).max(1).optional()
125
+ });
126
+ var CacheConfigSchema = import_zod.z.object({
127
+ mode: import_zod.z.enum(["use", "refresh", "skip"]).optional(),
128
+ ttlSeconds: import_zod.z.number().int().positive().optional(),
129
+ scope: import_zod.z.enum(["global", "tenant"]).optional()
130
+ });
131
+ var RouteCandidateSchema = import_zod.z.object({
132
+ provider: import_zod.z.string(),
133
+ model: import_zod.z.string(),
134
+ maxQueueMs: import_zod.z.number().int().positive().optional(),
135
+ maxUsd: import_zod.z.number().nonnegative().optional(),
136
+ inputOverrides: import_zod.z.record(import_zod.z.unknown()).optional(),
137
+ weight: import_zod.z.number().nonnegative().optional()
138
+ });
139
+ var RouteConfigSchema = import_zod.z.object({
140
+ strategy: import_zod.z.enum(["first-success", "cheapest-acceptable", "fastest"]),
141
+ candidates: import_zod.z.array(RouteCandidateSchema).min(1),
142
+ timeoutMs: import_zod.z.number().int().positive().optional(),
143
+ healthTtlMs: import_zod.z.number().int().positive().optional()
144
+ });
145
+ var JudgeRubricSchema = import_zod.z.object({
146
+ dimensions: import_zod.z.array(
147
+ import_zod.z.object({
148
+ name: import_zod.z.string(),
149
+ weight: import_zod.z.number().min(0).max(1),
150
+ description: import_zod.z.string()
151
+ })
152
+ )
153
+ });
154
+ var JudgeConfigSchema = import_zod.z.discriminatedUnion("type", [
155
+ import_zod.z.object({
156
+ type: import_zod.z.literal("llm-judge"),
157
+ criteria: import_zod.z.string(),
158
+ model: import_zod.z.string().optional(),
159
+ provider: import_zod.z.string().optional(),
160
+ rubric: JudgeRubricSchema.optional()
161
+ }),
162
+ import_zod.z.object({
163
+ type: import_zod.z.literal("image-judge"),
164
+ criteria: import_zod.z.enum(["clip-score", "aesthetic"]),
165
+ reference: import_zod.z.string().optional()
166
+ }),
167
+ import_zod.z.object({ type: import_zod.z.literal("rule"), expression: import_zod.z.string() }),
168
+ import_zod.z.object({ type: import_zod.z.literal("custom"), toolName: import_zod.z.string() })
169
+ ]);
170
+ var VariantsConfigSchema = import_zod.z.object({
171
+ n: import_zod.z.number().int().min(2).max(16),
172
+ seedStrategy: import_zod.z.enum(["random", "sequential", "fixed-list"]).optional(),
173
+ seeds: import_zod.z.array(import_zod.z.number().int()).optional(),
174
+ judge: JudgeConfigSchema,
175
+ loserAction: import_zod.z.enum(["archive", "discard"]).optional(),
176
+ perVariantCandidate: import_zod.z.boolean().optional(),
177
+ minScore: import_zod.z.number().min(0).max(1).optional()
178
+ });
179
+ var VoiceRefSchema = import_zod.z.object({
180
+ provider: import_zod.z.enum(["elevenlabs", "openai", "google", "deepgram-tts"]),
181
+ voiceId: import_zod.z.string(),
182
+ settings: import_zod.z.record(import_zod.z.unknown()).optional()
183
+ });
184
+ var StyleRefSchema = import_zod.z.object({
185
+ description: import_zod.z.string(),
186
+ negative: import_zod.z.string().optional(),
187
+ perProvider: import_zod.z.record(import_zod.z.object({ description: import_zod.z.string().optional(), negative: import_zod.z.string().optional() })).optional()
188
+ });
189
+ var BrandKitSchema = import_zod.z.object({
190
+ primaryColor: import_zod.z.string().optional(),
191
+ secondaryColor: import_zod.z.string().optional(),
192
+ fontFamily: import_zod.z.string().optional(),
193
+ logoArtifactId: import_zod.z.string().optional(),
194
+ extras: import_zod.z.record(import_zod.z.unknown()).optional()
195
+ });
196
+ var ContextRefSchema = import_zod.z.discriminatedUnion("kind", [
197
+ import_zod.z.object({ kind: import_zod.z.literal("voice"), name: import_zod.z.string() }),
198
+ import_zod.z.object({ kind: import_zod.z.literal("style"), name: import_zod.z.string() }),
199
+ import_zod.z.object({ kind: import_zod.z.literal("brand"), key: import_zod.z.string() })
200
+ ]);
201
+ var RunContextSchema = import_zod.z.object({
202
+ voices: import_zod.z.record(VoiceRefSchema).optional(),
203
+ styles: import_zod.z.record(StyleRefSchema).optional(),
204
+ brandKit: BrandKitSchema.optional(),
205
+ vars: import_zod.z.record(import_zod.z.unknown()).optional()
206
+ });
207
+ var PipelineStepSchema = import_zod.z.object({
208
+ id: import_zod.z.string(),
209
+ operation: import_zod.z.string(),
210
+ inputs: import_zod.z.record(import_zod.z.string()),
211
+ config: import_zod.z.record(import_zod.z.unknown()).default({}),
212
+ qualityGate: QualityGateSchema.optional(),
213
+ variants: VariantsConfigSchema.optional(),
214
+ gates: import_zod.z.array(QualityGateSchema).optional(),
215
+ cache: CacheConfigSchema.optional(),
216
+ route: RouteConfigSchema.optional()
217
+ });
218
+ var PipelineStatusSchema = import_zod.z.enum([
219
+ "pending",
220
+ "running",
221
+ "completed",
222
+ "failed",
223
+ "gated",
224
+ "cancelled"
225
+ ]);
226
+ var PipelineSchema = import_zod.z.object({
227
+ id: import_zod.z.string(),
228
+ steps: import_zod.z.array(PipelineStepSchema),
229
+ status: PipelineStatusSchema.default("pending"),
230
+ artifacts: import_zod.z.map(import_zod.z.string(), ArtifactSchema).default(/* @__PURE__ */ new Map()),
231
+ failedStep: import_zod.z.string().optional(),
232
+ gatedStep: import_zod.z.string().optional(),
233
+ currentStep: import_zod.z.string().optional(),
234
+ completedSteps: import_zod.z.array(import_zod.z.string()).default([]),
235
+ startedAt: import_zod.z.string().datetime().optional(),
236
+ completedAt: import_zod.z.string().datetime().optional()
237
+ });
238
+ var PipelineDefinitionSchema = import_zod.z.object({
239
+ id: import_zod.z.string(),
240
+ steps: import_zod.z.array(
241
+ import_zod.z.object({
242
+ id: import_zod.z.string(),
243
+ operation: import_zod.z.string(),
244
+ inputs: import_zod.z.record(import_zod.z.string()),
245
+ config: import_zod.z.record(import_zod.z.unknown()).default({}),
246
+ qualityGate: QualityGateSchema.optional(),
247
+ variants: VariantsConfigSchema.optional(),
248
+ gates: import_zod.z.array(QualityGateSchema).optional(),
249
+ cache: CacheConfigSchema.optional(),
250
+ route: RouteConfigSchema.optional()
251
+ })
252
+ ),
253
+ resumable: import_zod.z.boolean().default(true).optional(),
254
+ budget: BudgetConfigSchema.optional(),
255
+ context: RunContextSchema.optional()
256
+ });
257
+ var ProviderInputSchema = import_zod.z.object({
258
+ operation: import_zod.z.string(),
259
+ inputs: import_zod.z.record(import_zod.z.unknown()),
260
+ config: import_zod.z.record(import_zod.z.unknown()).optional()
261
+ });
262
+ var ProviderOutputSchema = import_zod.z.object({
263
+ artifact: ArtifactSchema,
264
+ cost_usd: import_zod.z.number().optional(),
265
+ duration_ms: import_zod.z.number().optional(),
266
+ metadata: import_zod.z.record(import_zod.z.unknown()).optional()
267
+ });
268
+ var QualityGateResultSchema = import_zod.z.object({
269
+ passed: import_zod.z.boolean(),
270
+ reasoning: import_zod.z.string().optional(),
271
+ score: import_zod.z.number().optional(),
272
+ action: QualityGateActionSchema
273
+ });
274
+ var PipelineEventTypeSchema = import_zod.z.enum([
275
+ // Legacy
276
+ "pipeline:start",
277
+ "pipeline:complete",
278
+ "pipeline:failed",
279
+ "pipeline:gated",
280
+ "step:start",
281
+ "step:complete",
282
+ "step:failed",
283
+ "step:gated",
284
+ "step:retry",
285
+ // Spec-canonical (§0.1)
286
+ "run-created",
287
+ "run-started",
288
+ "run-completed",
289
+ "run-failed",
290
+ "run-suspended",
291
+ "run-resumed",
292
+ "step-started",
293
+ "step-progress",
294
+ "step-completed",
295
+ "step-failed",
296
+ "step-cached",
297
+ "step-gated"
298
+ ]);
299
+ var PipelineEventSchema = import_zod.z.object({
300
+ type: PipelineEventTypeSchema,
301
+ pipelineId: import_zod.z.string(),
302
+ stepId: import_zod.z.string().optional(),
303
+ artifactId: import_zod.z.string().optional(),
304
+ timestamp: import_zod.z.string().datetime(),
305
+ data: import_zod.z.record(import_zod.z.unknown()).optional()
306
+ });
307
+ var CostRecordSchema = import_zod.z.object({
308
+ operation: import_zod.z.string(),
309
+ provider: import_zod.z.string(),
310
+ model: import_zod.z.string().optional(),
311
+ cost_usd: import_zod.z.number(),
312
+ artifactId: import_zod.z.string().optional(),
313
+ pipelineId: import_zod.z.string().optional(),
314
+ timestamp: import_zod.z.string().datetime()
315
+ });
316
+ var CostSummarySchema = import_zod.z.object({
317
+ total_usd: import_zod.z.number(),
318
+ by_operation: import_zod.z.map(import_zod.z.string(), import_zod.z.number()).default(/* @__PURE__ */ new Map()),
319
+ by_provider: import_zod.z.map(import_zod.z.string(), import_zod.z.number()).default(/* @__PURE__ */ new Map()),
320
+ by_pipeline: import_zod.z.map(import_zod.z.string(), import_zod.z.number()).default(/* @__PURE__ */ new Map())
321
+ });
322
+ var ArtifactMetaSchema = import_zod.z.object({
323
+ id: import_zod.z.string(),
324
+ type: ArtifactTypeSchema,
325
+ mimeType: import_zod.z.string(),
326
+ size: import_zod.z.number().optional(),
327
+ metadata: import_zod.z.record(import_zod.z.unknown()).optional(),
328
+ createdAt: import_zod.z.string().datetime().optional(),
329
+ sourceStep: import_zod.z.string().optional()
330
+ });
331
+ var StorageResultSchema = import_zod.z.object({
332
+ data: import_zod.z.instanceof(ReadableStream).or(import_zod.z.instanceof(Buffer)),
333
+ meta: ArtifactMetaSchema
334
+ });
335
+ var ValidationResultSchema = import_zod.z.object({
336
+ valid: import_zod.z.boolean(),
337
+ errors: import_zod.z.array(import_zod.z.string()).default([]),
338
+ warnings: import_zod.z.array(import_zod.z.string()).default([]),
339
+ estimated_cost_usd: import_zod.z.number().optional(),
340
+ estimated_duration_ms: import_zod.z.number().optional()
341
+ });
342
+
343
+ // src/artifact-registry.ts
344
+ var import_uuid = require("uuid");
345
+ var ArtifactRegistry = class {
346
+ artifacts = /* @__PURE__ */ new Map();
347
+ register(artifact) {
348
+ return this.registerWithId((0, import_uuid.v4)(), artifact);
349
+ }
350
+ registerWithId(id, artifact) {
351
+ const fullArtifact = {
352
+ ...artifact,
353
+ id,
354
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
355
+ };
356
+ this.artifacts.set(id, fullArtifact);
357
+ return fullArtifact;
358
+ }
359
+ get(id) {
360
+ return this.artifacts.get(id);
361
+ }
362
+ delete(id) {
363
+ return this.artifacts.delete(id);
364
+ }
365
+ list() {
366
+ return Array.from(this.artifacts.values());
367
+ }
368
+ findBySourceStep(stepId) {
369
+ let latest;
370
+ for (const artifact of this.artifacts.values()) {
371
+ if (artifact.sourceStep === stepId) {
372
+ if (!latest || artifact.createdAt && latest.createdAt && artifact.createdAt > latest.createdAt) {
373
+ latest = artifact;
374
+ }
375
+ }
376
+ }
377
+ return latest;
378
+ }
379
+ deleteBySourceStep(stepId) {
380
+ let deleted = 0;
381
+ for (const [id, artifact] of this.artifacts.entries()) {
382
+ if (artifact.sourceStep === stepId) {
383
+ this.artifacts.delete(id);
384
+ deleted++;
385
+ }
386
+ }
387
+ return deleted;
388
+ }
389
+ clear() {
390
+ this.artifacts.clear();
391
+ }
392
+ size() {
393
+ return this.artifacts.size;
394
+ }
395
+ };
396
+
397
+ // src/quality-gates/evaluator.ts
398
+ var ThresholdEvaluator = class {
399
+ async evaluate(gate, artifact) {
400
+ const checks = gate.config.checks;
401
+ if (!Array.isArray(checks)) {
402
+ return {
403
+ passed: false,
404
+ reasoning: "Invalid threshold configuration: checks must be an array",
405
+ action: gate.action
406
+ };
407
+ }
408
+ const failures = [];
409
+ for (const check of checks) {
410
+ const { field, operator, value } = check;
411
+ const actual = field.startsWith("metadata.") ? this.getNestedValue({ metadata: artifact.metadata }, field) : this.getNestedValue(artifact.metadata, field);
412
+ if (actual === void 0) {
413
+ failures.push(`Field '${field}' not found in artifact metadata`);
414
+ continue;
415
+ }
416
+ const numActual = Number(actual);
417
+ if (Number.isNaN(numActual)) {
418
+ failures.push(`Field '${field}' is not numeric: ${actual}`);
419
+ continue;
420
+ }
421
+ const passed = this.compare(numActual, operator, value);
422
+ if (!passed) {
423
+ failures.push(`${field} (${numActual}) ${operator} ${value} failed`);
424
+ }
425
+ }
426
+ return {
427
+ passed: failures.length === 0,
428
+ reasoning: failures.length > 0 ? failures.join("; ") : "All checks passed",
429
+ action: gate.action
430
+ };
431
+ }
432
+ getNestedValue(obj, path) {
433
+ return path.split(".").reduce((current, key) => {
434
+ if (current === null || current === void 0) return void 0;
435
+ if (key === "__proto__" || key === "constructor" || key === "prototype") return void 0;
436
+ if (typeof current === "object") {
437
+ return current[key];
438
+ }
439
+ return void 0;
440
+ }, obj);
441
+ }
442
+ compare(actual, operator, expected) {
443
+ switch (operator) {
444
+ case ">=":
445
+ return actual >= expected;
446
+ case "<=":
447
+ return actual <= expected;
448
+ case ">":
449
+ return actual > expected;
450
+ case "<":
451
+ return actual < expected;
452
+ case "==":
453
+ case "===":
454
+ return actual === expected;
455
+ case "!=":
456
+ return actual !== expected;
457
+ default:
458
+ throw new Error(`Unknown operator: ${operator}`);
459
+ }
460
+ }
461
+ };
462
+ var DimensionCheckEvaluator = class {
463
+ async evaluate(gate, artifact) {
464
+ const { expectedWidth, expectedHeight, tolerance = 0 } = gate.config;
465
+ const width = expectedWidth;
466
+ const height = expectedHeight;
467
+ const tol = tolerance;
468
+ if (typeof width !== "number" || typeof height !== "number") {
469
+ return {
470
+ passed: false,
471
+ reasoning: "Invalid dimension-check configuration: expectedWidth and expectedHeight must be numbers",
472
+ action: gate.action
473
+ };
474
+ }
475
+ const actualWidth = artifact.metadata.width;
476
+ const actualHeight = artifact.metadata.height;
477
+ if (actualWidth === void 0 || actualHeight === void 0) {
478
+ return {
479
+ passed: false,
480
+ reasoning: "Artifact missing width or height metadata",
481
+ action: gate.action
482
+ };
483
+ }
484
+ const widthDiff = Math.abs(actualWidth - width);
485
+ const heightDiff = Math.abs(actualHeight - height);
486
+ const widthTolerance = width * tol;
487
+ const heightTolerance = height * tol;
488
+ const widthOk = widthDiff <= widthTolerance;
489
+ const heightOk = heightDiff <= heightTolerance;
490
+ if (widthOk && heightOk) {
491
+ return {
492
+ passed: true,
493
+ reasoning: `Dimensions ${actualWidth}x${actualHeight} match expected ${width}x${height} within ${tol * 100}% tolerance`,
494
+ action: gate.action
495
+ };
496
+ }
497
+ return {
498
+ passed: false,
499
+ reasoning: `Dimensions ${actualWidth}x${actualHeight} do not match expected ${width}x${height} within ${tol * 100}% tolerance`,
500
+ action: gate.action
501
+ };
502
+ }
503
+ };
504
+ var LLMJudgeEvaluator = class {
505
+ evaluateFn;
506
+ constructor(evaluateFn) {
507
+ this.evaluateFn = evaluateFn;
508
+ }
509
+ async evaluate(gate, artifact) {
510
+ const { prompt, timeout } = gate.config;
511
+ if (typeof prompt !== "string") {
512
+ return {
513
+ passed: false,
514
+ reasoning: "Invalid LLM-judge configuration: prompt must be a string",
515
+ action: gate.action
516
+ };
517
+ }
518
+ const timeoutMs = typeof timeout === "number" ? timeout : 3e4;
519
+ try {
520
+ let timeoutId;
521
+ const result = await Promise.race([
522
+ this.evaluateFn(prompt, artifact),
523
+ new Promise((_, reject) => {
524
+ timeoutId = setTimeout(
525
+ () => reject(new Error(`LLM-judge timeout after ${timeoutMs}ms`)),
526
+ timeoutMs
527
+ );
528
+ })
529
+ ]);
530
+ if (timeoutId) clearTimeout(timeoutId);
531
+ return {
532
+ passed: result.pass,
533
+ reasoning: result.reasoning,
534
+ score: result.score,
535
+ action: gate.action
536
+ };
537
+ } catch (error) {
538
+ return {
539
+ passed: false,
540
+ reasoning: `LLM-judge evaluation failed: ${error instanceof Error ? error.message : "Unknown error"}`,
541
+ action: gate.action
542
+ };
543
+ }
544
+ }
545
+ };
546
+ var CustomEvaluator = class {
547
+ checkFn;
548
+ constructor(checkFn) {
549
+ this.checkFn = checkFn;
550
+ }
551
+ async evaluate(gate, artifact) {
552
+ try {
553
+ const passed = await this.checkFn(artifact, gate.config);
554
+ return {
555
+ passed,
556
+ reasoning: passed ? "Custom check passed" : "Custom check failed",
557
+ action: gate.action
558
+ };
559
+ } catch (error) {
560
+ return {
561
+ passed: false,
562
+ reasoning: `Custom check error: ${error instanceof Error ? error.message : "Unknown error"}`,
563
+ action: gate.action
564
+ };
565
+ }
566
+ }
567
+ };
568
+ function createQualityGateEvaluator(gate, llmJudgeFn, customCheckFn) {
569
+ switch (gate.type) {
570
+ case "threshold":
571
+ return new ThresholdEvaluator();
572
+ case "dimension-check":
573
+ return new DimensionCheckEvaluator();
574
+ case "llm-judge":
575
+ if (!llmJudgeFn) {
576
+ throw new Error("LLM-judge evaluator requires an evaluate function");
577
+ }
578
+ return new LLMJudgeEvaluator(llmJudgeFn);
579
+ case "custom":
580
+ if (!customCheckFn) {
581
+ throw new Error("Custom evaluator requires a check function to be provided");
582
+ }
583
+ return new CustomEvaluator(customCheckFn);
584
+ default:
585
+ throw new Error(`Unknown quality gate type: ${gate.type}`);
586
+ }
587
+ }
588
+
589
+ // src/pipeline-validator.ts
590
+ var PipelineValidator = class {
591
+ providerAvailability;
592
+ constructor(providerAvailability) {
593
+ this.providerAvailability = providerAvailability;
594
+ }
595
+ validate(definition) {
596
+ const errors = [];
597
+ const warnings = [];
598
+ const schemaResult = PipelineDefinitionSchema.safeParse(definition);
599
+ if (!schemaResult.success) {
600
+ errors.push("Pipeline definition schema validation failed");
601
+ for (const issue of schemaResult.error.issues) {
602
+ errors.push(` - ${issue.path.join(".")}: ${issue.message}`);
603
+ }
604
+ return { valid: false, errors, warnings };
605
+ }
606
+ const stepIds = /* @__PURE__ */ new Set();
607
+ for (const step of definition.steps) {
608
+ if (stepIds.has(step.id)) {
609
+ errors.push(`Duplicate step ID: ${step.id}`);
610
+ }
611
+ if (step.id.includes("..") || step.id.includes("/") || step.id.includes("\\")) {
612
+ errors.push(`Invalid step ID '${step.id}': contains path traversal characters`);
613
+ }
614
+ stepIds.add(step.id);
615
+ }
616
+ const referenceErrors = this.validateReferences(definition);
617
+ errors.push(...referenceErrors);
618
+ const providerErrors = this.validateProviders(definition);
619
+ errors.push(...providerErrors);
620
+ const gateWarnings = this.validateQualityGates(definition);
621
+ warnings.push(...gateWarnings);
622
+ let estimatedCost = 0;
623
+ let estimatedDuration = 0;
624
+ for (const step of definition.steps) {
625
+ if (this.providerAvailability.isAvailable(step.operation)) {
626
+ estimatedCost += this.providerAvailability.getEstimatedCost(step.operation, step.config);
627
+ estimatedDuration += this.providerAvailability.getEstimatedDuration(
628
+ step.operation,
629
+ step.config
630
+ );
631
+ } else {
632
+ warnings.push(`Provider not available for operation: ${step.operation}`);
633
+ }
634
+ }
635
+ return {
636
+ valid: errors.length === 0,
637
+ errors,
638
+ warnings,
639
+ estimated_cost_usd: estimatedCost,
640
+ estimated_duration_ms: estimatedDuration
641
+ };
642
+ }
643
+ validateReferences(definition) {
644
+ const errors = [];
645
+ const definedStepIds = new Set(definition.steps.map((s) => s.id));
646
+ const outputReferences = /* @__PURE__ */ new Map();
647
+ for (const step of definition.steps) {
648
+ for (const [paramName, inputValue] of Object.entries(step.inputs)) {
649
+ const match = inputValue.match(/^\{\{(\w+)\.output\}\}$/);
650
+ if (match) {
651
+ const referencedStepId = match[1];
652
+ if (!definedStepIds.has(referencedStepId)) {
653
+ errors.push(
654
+ `Step '${step.id}' references non-existent step '${referencedStepId}' in input '${paramName}'`
655
+ );
656
+ continue;
657
+ }
658
+ const referencedIndex = definition.steps.findIndex((s) => s.id === referencedStepId);
659
+ const currentIndex = definition.steps.findIndex((s) => s.id === step.id);
660
+ if (referencedIndex >= currentIndex) {
661
+ errors.push(
662
+ `Step '${step.id}' references future step '${referencedStepId}' (circular/forward reference not allowed)`
663
+ );
664
+ }
665
+ if (!outputReferences.has(referencedStepId)) {
666
+ outputReferences.set(referencedStepId, step.id);
667
+ }
668
+ }
669
+ }
670
+ }
671
+ return errors;
672
+ }
673
+ validateProviders(definition) {
674
+ const errors = [];
675
+ for (const step of definition.steps) {
676
+ if (!this.providerAvailability.isAvailable(step.operation)) {
677
+ errors.push(`No provider available for operation '${step.operation}' in step '${step.id}'`);
678
+ }
679
+ }
680
+ return errors;
681
+ }
682
+ validateQualityGates(definition) {
683
+ const warnings = [];
684
+ for (const step of definition.steps) {
685
+ if (step.qualityGate) {
686
+ const gate = step.qualityGate;
687
+ if (gate.action === "retry" && gate.maxRetries === void 0) {
688
+ warnings.push(
689
+ `Step '${step.id}' has retry action but no maxRetries specified (defaulting to 1)`
690
+ );
691
+ }
692
+ if (gate.type === "llm-judge" && !gate.config.prompt) {
693
+ warnings.push(`Step '${step.id}' has llm-judge gate without a prompt`);
694
+ }
695
+ if (gate.type === "threshold" && !Array.isArray(gate.config.checks)) {
696
+ warnings.push(`Step '${step.id}' has threshold gate without checks configured`);
697
+ }
698
+ if (gate.type === "dimension-check" && (gate.config.expectedWidth === void 0 || gate.config.expectedHeight === void 0)) {
699
+ warnings.push(`Step '${step.id}' has dimension-check gate without expected dimensions`);
700
+ }
701
+ }
702
+ const stepExt = step;
703
+ if (stepExt.gates && Array.isArray(stepExt.gates)) {
704
+ for (let gi = 0; gi < stepExt.gates.length; gi++) {
705
+ const gate = stepExt.gates[gi];
706
+ if (gate.action === "retry" && gate.maxRetries === void 0) {
707
+ warnings.push(
708
+ `Step '${step.id}' gate[${gi}] has retry action but no maxRetries specified`
709
+ );
710
+ }
711
+ }
712
+ }
713
+ }
714
+ this.validateBudgetConfig(definition, warnings);
715
+ this.validateVariantsConfig(definition, warnings);
716
+ this.validateRunContext(definition, warnings);
717
+ return warnings;
718
+ }
719
+ validateBudgetConfig(definition, warnings) {
720
+ const budget = definition.budget;
721
+ if (budget) {
722
+ if (budget.maxUsd !== void 0 && budget.maxUsd <= 0) {
723
+ warnings.push("Budget maxUsd must be greater than 0");
724
+ }
725
+ if (budget.onExceed && !["abort", "suspend"].includes(budget.onExceed)) {
726
+ warnings.push(`Budget onExceed must be 'abort' or 'suspend', got '${budget.onExceed}'`);
727
+ }
728
+ }
729
+ }
730
+ validateVariantsConfig(definition, warnings) {
731
+ for (const step of definition.steps) {
732
+ const variants = step.variants;
733
+ if (variants) {
734
+ if (variants.n !== void 0 && (variants.n < 2 || variants.n > 16)) {
735
+ warnings.push(`Step '${step.id}' variants.n must be between 2 and 16, got ${variants.n}`);
736
+ }
737
+ if (variants.seedStrategy !== void 0 && !["random", "sequential", "fixed-list"].includes(variants.seedStrategy)) {
738
+ warnings.push(
739
+ `Step '${step.id}' variants.seedStrategy must be one of random, sequential, fixed-list`
740
+ );
741
+ }
742
+ if (!variants.judge) {
743
+ warnings.push(`Step '${step.id}' has variants without a judge configuration`);
744
+ }
745
+ }
746
+ }
747
+ }
748
+ validateRunContext(definition, warnings) {
749
+ const context = definition.context;
750
+ if (context) {
751
+ if (context.voices) {
752
+ for (const [name, ref] of Object.entries(context.voices)) {
753
+ const voiceRef = ref;
754
+ if (voiceRef.provider && !["elevenlabs", "openai", "google", "deepgram-tts"].includes(voiceRef.provider)) {
755
+ warnings.push(`Context voice '${name}' has unknown provider '${voiceRef.provider}'`);
756
+ }
757
+ }
758
+ }
759
+ if (context.styles) {
760
+ for (const [name] of Object.entries(context.styles)) {
761
+ if (name.includes("..") || name.includes("/")) {
762
+ warnings.push(`Context style name '${name}' contains invalid characters`);
763
+ }
764
+ }
765
+ }
766
+ }
767
+ }
768
+ };
769
+
770
+ // src/pipeline-executor.ts
771
+ var import_node_crypto = require("crypto");
772
+
773
+ // src/errors.ts
774
+ var A2AError = class extends Error {
775
+ code;
776
+ retryable;
777
+ constructor(message) {
778
+ super(message);
779
+ this.name = "A2AError";
780
+ this.code = "A2A_ERROR";
781
+ this.retryable = false;
782
+ }
783
+ };
784
+ var IdempotencyConflictError = class extends A2AError {
785
+ constructor(reason, existingRunId) {
786
+ super(
787
+ `Idempotency conflict: ${reason}${existingRunId ? ` (existing run: ${existingRunId})` : ""}`
788
+ );
789
+ this.reason = reason;
790
+ this.existingRunId = existingRunId;
791
+ }
792
+ reason;
793
+ existingRunId;
794
+ code = "IDEMPOTENCY_CONFLICT";
795
+ retryable = false;
796
+ };
797
+ var BudgetExceededError = class extends A2AError {
798
+ constructor(spentUsd, capUsd, scope) {
799
+ super(`Budget exceeded: $${spentUsd} spent, $${capUsd} cap (${scope})`);
800
+ this.spentUsd = spentUsd;
801
+ this.capUsd = capUsd;
802
+ this.scope = scope;
803
+ }
804
+ spentUsd;
805
+ capUsd;
806
+ scope;
807
+ code = "BUDGET_EXCEEDED";
808
+ retryable = false;
809
+ };
810
+ var RunNotFoundError = class extends A2AError {
811
+ code = "RUN_NOT_FOUND";
812
+ retryable = false;
813
+ constructor() {
814
+ super("Run not found");
815
+ }
816
+ };
817
+ var RunInProgressError = class extends A2AError {
818
+ code = "RUN_IN_PROGRESS";
819
+ retryable = true;
820
+ constructor() {
821
+ super("Run is already in progress");
822
+ }
823
+ };
824
+ var RunNotResumableError = class extends A2AError {
825
+ code = "RUN_NOT_RESUMABLE";
826
+ retryable = false;
827
+ constructor() {
828
+ super("Run is not resumable");
829
+ }
830
+ };
831
+ var WebhookSignatureInvalidError = class extends A2AError {
832
+ code = "WEBHOOK_SIGNATURE_INVALID";
833
+ retryable = false;
834
+ constructor() {
835
+ super("Webhook signature is invalid");
836
+ }
837
+ };
838
+ var WebhookProviderUnknownError = class extends A2AError {
839
+ code = "WEBHOOK_PROVIDER_UNKNOWN";
840
+ retryable = false;
841
+ constructor() {
842
+ super("Webhook provider is unknown");
843
+ }
844
+ };
845
+ var StateStoreUnavailableError = class extends A2AError {
846
+ code = "STATE_STORE_UNAVAILABLE";
847
+ retryable = true;
848
+ constructor() {
849
+ super("State store is unavailable");
850
+ }
851
+ };
852
+ var EstimateUnsupportedError = class extends A2AError {
853
+ code = "ESTIMATE_UNSUPPORTED";
854
+ retryable = false;
855
+ constructor() {
856
+ super("Cost estimation is not supported for this operation");
857
+ }
858
+ };
859
+ var ArtifactNotFoundError = class extends A2AError {
860
+ code = "ARTIFACT_NOT_FOUND";
861
+ retryable = false;
862
+ constructor() {
863
+ super("Artifact not found");
864
+ }
865
+ };
866
+ var RouterAllCandidatesFailedError = class extends A2AError {
867
+ constructor(attemptedCandidates, lastError) {
868
+ super(`All router candidates failed: ${attemptedCandidates.join(", ")}`);
869
+ this.attemptedCandidates = attemptedCandidates;
870
+ this.lastError = lastError;
871
+ }
872
+ attemptedCandidates;
873
+ lastError;
874
+ code = "ROUTER_ALL_CANDIDATES_FAILED";
875
+ retryable = false;
876
+ };
877
+ var RouterNoCandidatesError = class extends A2AError {
878
+ code = "ROUTER_NO_CANDIDATES";
879
+ retryable = false;
880
+ constructor() {
881
+ super("No eligible router candidates found");
882
+ }
883
+ };
884
+ var RouterFastestIneligibleError = class extends A2AError {
885
+ code = "ROUTER_FASTEST_INELIGIBLE";
886
+ retryable = false;
887
+ constructor() {
888
+ super("Fastest router candidate is ineligible");
889
+ }
890
+ };
891
+ var SafetyGateRejectedError = class extends A2AError {
892
+ constructor(category, score) {
893
+ super(`Safety gate rejected: ${category} (score: ${score})`);
894
+ this.category = category;
895
+ this.score = score;
896
+ }
897
+ category;
898
+ score;
899
+ code = "SAFETY_GATE_REJECTED";
900
+ retryable = false;
901
+ };
902
+ var TenantNotFoundError = class extends A2AError {
903
+ code = "TENANT_NOT_FOUND";
904
+ retryable = false;
905
+ constructor() {
906
+ super("Tenant not found");
907
+ }
908
+ };
909
+ var KeyVaultUnavailableError = class extends A2AError {
910
+ code = "KEY_VAULT_UNAVAILABLE";
911
+ retryable = true;
912
+ constructor() {
913
+ super("Key vault is unavailable");
914
+ }
915
+ };
916
+ var FfmpegUnavailableError = class extends A2AError {
917
+ code = "FFMPEG_UNAVAILABLE";
918
+ retryable = false;
919
+ };
920
+ var VariantsAllRejectedError = class extends A2AError {
921
+ constructor(reason) {
922
+ super();
923
+ this.reason = reason;
924
+ }
925
+ reason;
926
+ code = "VARIANTS_ALL_REJECTED";
927
+ retryable = false;
928
+ };
929
+ var JudgeUnavailableError = class extends A2AError {
930
+ code = "JUDGE_UNAVAILABLE";
931
+ retryable = true;
932
+ };
933
+ var WorkflowNotFoundError = class extends A2AError {
934
+ constructor(workflowName) {
935
+ super(workflowName ? `Workflow not found: ${workflowName}` : "Workflow not found");
936
+ this.workflowName = workflowName;
937
+ }
938
+ workflowName;
939
+ code = "WORKFLOW_NOT_FOUND";
940
+ retryable = false;
941
+ };
942
+ var WorkflowExpiredError = class extends A2AError {
943
+ constructor(detail) {
944
+ super(
945
+ `Workflow expired${detail ? `: ${detail}` : " \u2014 did not complete within retention period"}`
946
+ );
947
+ this.detail = detail;
948
+ }
949
+ detail;
950
+ code = "WORKFLOW_EXPIRED";
951
+ retryable = false;
952
+ };
953
+ var ContextRefUnknownError = class extends A2AError {
954
+ constructor(kind, name) {
955
+ super();
956
+ this.kind = kind;
957
+ this.name = name;
958
+ }
959
+ kind;
960
+ name;
961
+ code = "CONTEXT_REF_UNKNOWN";
962
+ retryable = false;
963
+ };
964
+ var ContextRefTypeError = class extends A2AError {
965
+ constructor(stepOp, refKind) {
966
+ super();
967
+ this.stepOp = stepOp;
968
+ this.refKind = refKind;
969
+ }
970
+ stepOp;
971
+ refKind;
972
+ code = "CONTEXT_REF_TYPE_MISMATCH";
973
+ retryable = false;
974
+ };
975
+ var LoudnessGateFailedError = class extends A2AError {
976
+ code = "LOUDNESS_GATE_FAILED";
977
+ retryable = false;
978
+ };
979
+ var TenantPolicyViolationError = class extends A2AError {
980
+ code = "TENANT_POLICY_VIOLATION";
981
+ retryable = false;
982
+ };
983
+ var ProvenanceSigningFailedError = class extends A2AError {
984
+ code = "PROVENANCE_SIGNING_FAILED";
985
+ retryable = false;
986
+ };
987
+ var SafetyProviderUnavailableError = class extends A2AError {
988
+ code = "SAFETY_PROVIDER_UNAVAILABLE";
989
+ retryable = true;
990
+ };
991
+ var RatioUnsupportedError = class extends A2AError {
992
+ constructor(ratio, provider) {
993
+ super(`Ratio ${ratio} not natively supported by provider ${provider}`);
994
+ this.ratio = ratio;
995
+ this.provider = provider;
996
+ }
997
+ ratio;
998
+ provider;
999
+ code = "RATIO_UNSUPPORTED";
1000
+ retryable = false;
1001
+ };
1002
+ var InvalidInputError = class extends A2AError {
1003
+ constructor(reason) {
1004
+ super(`Invalid input: ${reason}`);
1005
+ this.reason = reason;
1006
+ }
1007
+ reason;
1008
+ code = "INVALID_INPUT";
1009
+ retryable = false;
1010
+ };
1011
+ var FormatUnsupportedError = class extends A2AError {
1012
+ constructor(format, operation) {
1013
+ super(`Format unsupported: ${format} for ${operation}`);
1014
+ this.format = format;
1015
+ this.operation = operation;
1016
+ }
1017
+ format;
1018
+ operation;
1019
+ code = "FORMAT_UNSUPPORTED";
1020
+ retryable = false;
1021
+ };
1022
+ var ArtifactAccessDeniedError = class extends A2AError {
1023
+ code = "ARTIFACT_ACCESS_DENIED";
1024
+ retryable = false;
1025
+ };
1026
+ var InvalidResourceUriError = class extends A2AError {
1027
+ constructor(uri) {
1028
+ super(uri ? `Invalid resource URI: ${uri}` : "Invalid resource URI");
1029
+ this.uri = uri;
1030
+ }
1031
+ uri;
1032
+ code = "INVALID_RESOURCE_URI";
1033
+ retryable = false;
1034
+ };
1035
+
1036
+ // src/pipeline-executor.ts
1037
+ var LEGACY_TO_SPEC_EVENT = {
1038
+ "pipeline:start": "run-started",
1039
+ "pipeline:complete": "run-completed",
1040
+ "pipeline:failed": "run-failed",
1041
+ "pipeline:gated": "run-suspended",
1042
+ "step:start": "step-started",
1043
+ "step:complete": "step-completed",
1044
+ "step:failed": "step-failed",
1045
+ "step:gated": "step-gated"
1046
+ };
1047
+ function createStepStateRecord(stepId, status) {
1048
+ return { stepId, status: status ?? "pending", attempts: 0 };
1049
+ }
1050
+ var PipelineExecutor = class {
1051
+ registry;
1052
+ providers = /* @__PURE__ */ new Map();
1053
+ defaultPipelineTimeoutMs;
1054
+ llmJudgeFn;
1055
+ customCheckFn;
1056
+ prepareInputs;
1057
+ persistArtifact;
1058
+ onEvent;
1059
+ onCost;
1060
+ persistence;
1061
+ ledger;
1062
+ routeStepFn;
1063
+ variantsStepFn;
1064
+ ratiosStepFn;
1065
+ context;
1066
+ contextResolveFn;
1067
+ gateEvalFn;
1068
+ tenantPolicyEnforceFn;
1069
+ signProvenance;
1070
+ constructor(options) {
1071
+ this.registry = new ArtifactRegistry();
1072
+ this.defaultPipelineTimeoutMs = options.defaultPipelineTimeoutMs ?? 3e5;
1073
+ this.llmJudgeFn = options.llmJudgeFn;
1074
+ this.customCheckFn = options.customCheckFn;
1075
+ this.prepareInputs = options.prepareInputs;
1076
+ this.persistArtifact = options.persistArtifact;
1077
+ for (const provider of options.providers) {
1078
+ for (const op of provider.supportedOperations) {
1079
+ this.providers.set(op, provider);
1080
+ }
1081
+ }
1082
+ this.onEvent = options.onEvent;
1083
+ this.onCost = options.onCost;
1084
+ this.persistence = options.persistence;
1085
+ this.ledger = options.ledger;
1086
+ this.routeStepFn = options.routeStepFn;
1087
+ this.variantsStepFn = options.variantsStepFn;
1088
+ this.ratiosStepFn = options.ratiosStepFn;
1089
+ this.context = options.context;
1090
+ this.contextResolveFn = options.contextResolveFn;
1091
+ this.gateEvalFn = options.gateEvalFn;
1092
+ this.tenantPolicyEnforceFn = options.tenantPolicyEnforceFn;
1093
+ this.signProvenance = options.signProvenance;
1094
+ }
1095
+ async execute(definition, options) {
1096
+ const pipeline = {
1097
+ id: definition.id,
1098
+ steps: definition.steps,
1099
+ status: "running",
1100
+ artifacts: /* @__PURE__ */ new Map(),
1101
+ completedSteps: [],
1102
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1103
+ };
1104
+ const stepStates = definition.steps.map(
1105
+ (s) => createStepStateRecord(s.id, "pending")
1106
+ );
1107
+ const runId = options?.runId ?? (0, import_node_crypto.randomUUID)();
1108
+ this.currentPipelineDefHash = this.hashPipelineDefinition(definition);
1109
+ const budget = definition.budget;
1110
+ let runCost = 0;
1111
+ if (this.persistence) {
1112
+ const runRecord = {
1113
+ runId,
1114
+ pipelineId: definition.id,
1115
+ status: "running",
1116
+ definition,
1117
+ stepStates,
1118
+ artifacts: {},
1119
+ totalCostUsd: 0,
1120
+ startedAt: pipeline.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1121
+ resumable: definition.resumable ?? true
1122
+ };
1123
+ await this.persistence.createRun(runRecord);
1124
+ }
1125
+ this.emitEvent({
1126
+ type: "pipeline:start",
1127
+ pipelineId: pipeline.id,
1128
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1129
+ data: { runId }
1130
+ });
1131
+ const pipelineTimeout = setTimeout(() => {
1132
+ if (pipeline.status === "running") {
1133
+ pipeline.status = "failed";
1134
+ pipeline.failedStep = pipeline.currentStep;
1135
+ this.emitEvent({
1136
+ type: "pipeline:failed",
1137
+ pipelineId: pipeline.id,
1138
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1139
+ data: { reason: "Pipeline timeout exceeded" }
1140
+ });
1141
+ }
1142
+ }, this.defaultPipelineTimeoutMs);
1143
+ try {
1144
+ for (let stepIndex = 0; stepIndex < pipeline.steps.length; stepIndex++) {
1145
+ const step = pipeline.steps[stepIndex];
1146
+ pipeline.currentStep = step.id;
1147
+ if (budget) {
1148
+ stepStates[stepIndex].status = "running";
1149
+ await this.updateRunState(runId, stepStates);
1150
+ const estimate = await this.quickEstimateStep(step, budget);
1151
+ if (runCost + estimate.usdHigh > budget.maxUsd) {
1152
+ if (budget.onExceed === "abort") {
1153
+ throw new BudgetExceededError(runCost + estimate.usdHigh, budget.maxUsd, "run");
1154
+ }
1155
+ pipeline.status = "gated";
1156
+ pipeline.gatedStep = step.id;
1157
+ stepStates[stepIndex].status = "gated";
1158
+ await this.updateRunState(runId, stepStates, runCost);
1159
+ this.emitEvent({
1160
+ type: "pipeline:gated",
1161
+ pipelineId: pipeline.id,
1162
+ stepId: step.id,
1163
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1164
+ data: {
1165
+ reason: `Budget preflight: $${estimate.usdHigh} would exceed $${budget.maxUsd}`
1166
+ }
1167
+ });
1168
+ break;
1169
+ }
1170
+ }
1171
+ const result = await this.executeStep(step, pipeline);
1172
+ if (pipeline.status === "failed") {
1173
+ stepStates[stepIndex].status = "failed";
1174
+ await this.updateRunState(runId, stepStates, runCost);
1175
+ break;
1176
+ }
1177
+ if (result.status === "failed") {
1178
+ pipeline.status = "failed";
1179
+ pipeline.failedStep = step.id;
1180
+ stepStates[stepIndex].status = "failed";
1181
+ await this.updateRunState(runId, stepStates, runCost);
1182
+ this.emitEvent({
1183
+ type: "pipeline:failed",
1184
+ pipelineId: pipeline.id,
1185
+ stepId: step.id,
1186
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1187
+ });
1188
+ break;
1189
+ }
1190
+ if (result.status === "gated") {
1191
+ pipeline.status = "gated";
1192
+ pipeline.gatedStep = step.id;
1193
+ stepStates[stepIndex].status = "gated";
1194
+ await this.updateRunState(runId, stepStates, runCost);
1195
+ this.emitEvent({
1196
+ type: "pipeline:gated",
1197
+ pipelineId: pipeline.id,
1198
+ stepId: step.id,
1199
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1200
+ });
1201
+ break;
1202
+ }
1203
+ if (result.artifact) {
1204
+ pipeline.artifacts.set(result.artifact.id, result.artifact);
1205
+ }
1206
+ stepStates[stepIndex].status = "completed";
1207
+ stepStates[stepIndex].artifactId = result.artifact?.id;
1208
+ pipeline.completedSteps.push(step.id);
1209
+ const stepCost = result.costUsd ?? 0;
1210
+ if (stepCost > 0) {
1211
+ const prior = runCost;
1212
+ runCost += stepCost;
1213
+ if (budget) {
1214
+ const warnAtPct = budget.warnAtPct ?? 0.8;
1215
+ const threshold = warnAtPct * budget.maxUsd;
1216
+ if (prior < threshold && runCost >= threshold) {
1217
+ this.emitEvent({
1218
+ type: "step-progress",
1219
+ pipelineId: pipeline.id,
1220
+ stepId: step.id,
1221
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1222
+ data: {
1223
+ budgetWarning: true,
1224
+ costUsdAccrued: runCost,
1225
+ warnAtPct,
1226
+ maxUsd: budget.maxUsd
1227
+ }
1228
+ });
1229
+ }
1230
+ }
1231
+ }
1232
+ }
1233
+ if (pipeline.status === "running") {
1234
+ pipeline.status = "completed";
1235
+ await this.updateRunState(runId, stepStates, runCost, "completed");
1236
+ this.emitEvent({
1237
+ type: "pipeline:complete",
1238
+ pipelineId: pipeline.id,
1239
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1240
+ });
1241
+ }
1242
+ } catch (error) {
1243
+ pipeline.status = "failed";
1244
+ pipeline.failedStep = pipeline.currentStep;
1245
+ await this.updateRunState(runId, stepStates, runCost, "failed", error);
1246
+ this.emitEvent({
1247
+ type: "pipeline:failed",
1248
+ pipelineId: pipeline.id,
1249
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1250
+ data: { error: error instanceof Error ? error.message : "Unknown error" }
1251
+ });
1252
+ } finally {
1253
+ clearTimeout(pipelineTimeout);
1254
+ pipeline.completedAt = (/* @__PURE__ */ new Date()).toISOString();
1255
+ pipeline.currentStep = void 0;
1256
+ }
1257
+ return pipeline;
1258
+ }
1259
+ async resume(first, second) {
1260
+ if (typeof first === "string") {
1261
+ return this.resumeByRunId(first, second);
1262
+ }
1263
+ return this.resumeLegacy(first, second);
1264
+ }
1265
+ async resumeByRunId(runId, fromStepId) {
1266
+ if (!this.persistence) {
1267
+ throw new Error("Persistence store required for resume");
1268
+ }
1269
+ const acquired = await this.persistence.acquireLock(runId);
1270
+ if (!acquired) {
1271
+ throw new RunInProgressError();
1272
+ }
1273
+ try {
1274
+ const run = await this.persistence.getRun(runId);
1275
+ if (!run) {
1276
+ throw new Error(`Run not found: ${runId}`);
1277
+ }
1278
+ if (run.status === "completed" || run.status === "cancelled") {
1279
+ throw new RunNotResumableError();
1280
+ }
1281
+ if (run.resumable === false) {
1282
+ throw new RunNotResumableError();
1283
+ }
1284
+ const pipeline = {
1285
+ id: run.pipelineId,
1286
+ steps: run.definition.steps,
1287
+ status: "running",
1288
+ artifacts: /* @__PURE__ */ new Map(),
1289
+ completedSteps: [],
1290
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1291
+ };
1292
+ const stepStates = [...run.stepStates];
1293
+ const runCost = run.totalCostUsd;
1294
+ const budget = run.definition.budget;
1295
+ this.currentPipelineDefHash = this.hashPipelineDefinition(run.definition);
1296
+ for (const [stepId, artifactId] of Object.entries(run.artifacts)) {
1297
+ const step = run.definition.steps.find((s) => s.id === stepId);
1298
+ if (step) {
1299
+ const art = {
1300
+ id: artifactId,
1301
+ type: "image",
1302
+ uri: `run://${runId}/${artifactId}`,
1303
+ mimeType: "application/octet-stream",
1304
+ metadata: {},
1305
+ sourceStep: stepId,
1306
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1307
+ };
1308
+ this.registry.registerWithId(artifactId, art);
1309
+ pipeline.artifacts.set(artifactId, art);
1310
+ }
1311
+ }
1312
+ const startStepIndex = fromStepId ? run.definition.steps.findIndex((s) => s.id === fromStepId) : run.stepStates.findIndex(
1313
+ (s) => s.status !== "completed" && s.status !== "skipped" && s.status !== "cached"
1314
+ );
1315
+ if (startStepIndex === -1) {
1316
+ pipeline.status = "completed";
1317
+ return pipeline;
1318
+ }
1319
+ for (let i = 0; i < startStepIndex; i++) {
1320
+ if (stepStates[i].status !== "completed") {
1321
+ stepStates[i].status = "skipped";
1322
+ }
1323
+ if (stepStates[i].artifactId) {
1324
+ pipeline.completedSteps.push(run.definition.steps[i].id);
1325
+ }
1326
+ }
1327
+ for (let i = startStepIndex; i < stepStates.length; i++) {
1328
+ stepStates[i].attempts = 0;
1329
+ }
1330
+ await this.persistence.updateRun(runId, {
1331
+ status: "running",
1332
+ stepStates
1333
+ });
1334
+ for (let i = startStepIndex; i < run.definition.steps.length; i++) {
1335
+ const step = run.definition.steps[i];
1336
+ pipeline.currentStep = step.id;
1337
+ if (stepStates[i].status === "completed" || stepStates[i].status === "cached") {
1338
+ pipeline.completedSteps.push(step.id);
1339
+ continue;
1340
+ }
1341
+ if (budget) {
1342
+ stepStates[i].status = "running";
1343
+ await this.persistence.updateRun(runId, { stepStates });
1344
+ const estimate = await this.quickEstimateStep(step, budget);
1345
+ if (runCost + estimate.usdHigh > budget.maxUsd) {
1346
+ if (budget.onExceed === "abort") {
1347
+ throw new BudgetExceededError(runCost + estimate.usdHigh, budget.maxUsd, "run");
1348
+ }
1349
+ pipeline.status = "gated";
1350
+ pipeline.gatedStep = step.id;
1351
+ stepStates[i].status = "gated";
1352
+ await this.persistence.updateRun(runId, {
1353
+ status: "gated",
1354
+ stepStates,
1355
+ totalCostUsd: runCost
1356
+ });
1357
+ break;
1358
+ }
1359
+ }
1360
+ const result = await this.executeStep(step, pipeline);
1361
+ if (result.status === "failed") {
1362
+ pipeline.status = "failed";
1363
+ pipeline.failedStep = step.id;
1364
+ stepStates[i].status = "failed";
1365
+ await this.persistence.updateRun(runId, {
1366
+ status: "failed",
1367
+ stepStates,
1368
+ totalCostUsd: runCost
1369
+ });
1370
+ break;
1371
+ }
1372
+ if (result.status === "gated") {
1373
+ pipeline.status = "gated";
1374
+ pipeline.gatedStep = step.id;
1375
+ stepStates[i].status = "gated";
1376
+ await this.persistence.updateRun(runId, {
1377
+ status: "gated",
1378
+ stepStates,
1379
+ totalCostUsd: runCost
1380
+ });
1381
+ break;
1382
+ }
1383
+ if (result.artifact) {
1384
+ pipeline.artifacts.set(result.artifact.id, result.artifact);
1385
+ }
1386
+ stepStates[i].status = "completed";
1387
+ stepStates[i].artifactId = result.artifact?.id;
1388
+ pipeline.completedSteps.push(step.id);
1389
+ }
1390
+ if (pipeline.status === "running") {
1391
+ pipeline.status = "completed";
1392
+ await this.persistence.updateRun(runId, {
1393
+ status: "completed",
1394
+ stepStates,
1395
+ totalCostUsd: runCost,
1396
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1397
+ error: void 0
1398
+ });
1399
+ }
1400
+ pipeline.completedAt = (/* @__PURE__ */ new Date()).toISOString();
1401
+ pipeline.currentStep = void 0;
1402
+ return pipeline;
1403
+ } finally {
1404
+ await this.persistence.releaseLock(runId);
1405
+ }
1406
+ }
1407
+ async resumeLegacy(pipeline, action) {
1408
+ if (pipeline.status !== "gated" && pipeline.status !== "failed") {
1409
+ throw new Error(`Cannot resume pipeline with status: ${pipeline.status}`);
1410
+ }
1411
+ if (action === "abort") {
1412
+ pipeline.status = "failed";
1413
+ pipeline.completedAt = (/* @__PURE__ */ new Date()).toISOString();
1414
+ return pipeline;
1415
+ }
1416
+ const resumeStepId = pipeline.status === "gated" ? pipeline.gatedStep : pipeline.failedStep;
1417
+ const resumeStepLabel = pipeline.status === "gated" ? "gated" : "failed";
1418
+ const resumeStepIndex = pipeline.steps.findIndex((s) => s.id === resumeStepId);
1419
+ if (!resumeStepId || resumeStepIndex === -1) {
1420
+ throw new Error(
1421
+ `${resumeStepLabel[0].toUpperCase() + resumeStepLabel.slice(1)} step not found`
1422
+ );
1423
+ }
1424
+ if (action === "skip") {
1425
+ const resumeStep = pipeline.steps[resumeStepIndex];
1426
+ if (!pipeline.completedSteps.includes(resumeStep.id)) {
1427
+ pipeline.completedSteps.push(resumeStep.id);
1428
+ }
1429
+ pipeline.status = "running";
1430
+ pipeline.gatedStep = void 0;
1431
+ pipeline.failedStep = void 0;
1432
+ for (let i = resumeStepIndex + 1; i < pipeline.steps.length; i++) {
1433
+ const step = pipeline.steps[i];
1434
+ pipeline.currentStep = step.id;
1435
+ const result = await this.executeStep(step, pipeline);
1436
+ if (result.status === "failed") {
1437
+ pipeline.status = "failed";
1438
+ pipeline.failedStep = step.id;
1439
+ break;
1440
+ }
1441
+ if (result.status === "gated") {
1442
+ pipeline.status = "gated";
1443
+ pipeline.gatedStep = step.id;
1444
+ break;
1445
+ }
1446
+ if (result.artifact) {
1447
+ pipeline.artifacts.set(result.artifact.id, result.artifact);
1448
+ }
1449
+ pipeline.completedSteps.push(step.id);
1450
+ }
1451
+ if (pipeline.status === "running") {
1452
+ pipeline.status = "completed";
1453
+ }
1454
+ } else if (action === "retry") {
1455
+ const step = pipeline.steps[resumeStepIndex];
1456
+ const existingArtifact = this.registry.findBySourceStep(step.id);
1457
+ if (existingArtifact) {
1458
+ this.registry.delete(existingArtifact.id);
1459
+ pipeline.artifacts.delete(existingArtifact.id);
1460
+ }
1461
+ const stepsAfterGate = pipeline.completedSteps.filter((s) => {
1462
+ const index = pipeline.steps.findIndex((ps) => ps.id === s);
1463
+ return index >= resumeStepIndex;
1464
+ });
1465
+ pipeline.completedSteps = pipeline.completedSteps.filter((s) => !stepsAfterGate.includes(s));
1466
+ pipeline.status = "running";
1467
+ pipeline.gatedStep = void 0;
1468
+ pipeline.failedStep = void 0;
1469
+ for (let i = resumeStepIndex; i < pipeline.steps.length; i++) {
1470
+ const currentStep = pipeline.steps[i];
1471
+ pipeline.currentStep = currentStep.id;
1472
+ const result = await this.executeStep(currentStep, pipeline);
1473
+ if (result.status === "failed") {
1474
+ pipeline.status = "failed";
1475
+ pipeline.failedStep = currentStep.id;
1476
+ break;
1477
+ }
1478
+ if (result.status === "gated") {
1479
+ pipeline.status = "gated";
1480
+ pipeline.gatedStep = currentStep.id;
1481
+ break;
1482
+ }
1483
+ if (result.artifact) {
1484
+ pipeline.artifacts.set(result.artifact.id, result.artifact);
1485
+ }
1486
+ if (!pipeline.completedSteps.includes(currentStep.id)) {
1487
+ pipeline.completedSteps.push(currentStep.id);
1488
+ }
1489
+ }
1490
+ if (pipeline.status === "running") {
1491
+ pipeline.status = "completed";
1492
+ }
1493
+ }
1494
+ pipeline.completedAt = (/* @__PURE__ */ new Date()).toISOString();
1495
+ pipeline.currentStep = void 0;
1496
+ return pipeline;
1497
+ }
1498
+ async estimate(definition) {
1499
+ const perStep = [];
1500
+ const warnings = [];
1501
+ for (const step of definition.steps) {
1502
+ const provider = this.providers.get(step.operation);
1503
+ if (!provider) {
1504
+ warnings.push({
1505
+ stepId: step.id,
1506
+ code: "no-estimator",
1507
+ message: `No provider available for operation '${step.operation}'`
1508
+ });
1509
+ perStep.push({
1510
+ stepId: step.id,
1511
+ operation: step.operation,
1512
+ provider: "unknown",
1513
+ modelId: step.config?.model ?? "default",
1514
+ usdLow: 0,
1515
+ usdHigh: 0.01,
1516
+ estimable: false,
1517
+ fallbackUsed: "default-bound"
1518
+ });
1519
+ continue;
1520
+ }
1521
+ const estimate = await this.quickEstimateStep(step);
1522
+ const stepEntry = {
1523
+ stepId: step.id,
1524
+ operation: step.operation,
1525
+ provider: provider.name,
1526
+ modelId: step.config?.model ?? "default",
1527
+ usdLow: estimate.usdLow,
1528
+ usdHigh: estimate.usdHigh,
1529
+ estimable: estimate.estimable
1530
+ };
1531
+ if (!estimate.estimable) {
1532
+ stepEntry.fallbackUsed = "default-bound";
1533
+ warnings.push({
1534
+ stepId: step.id,
1535
+ code: "no-estimator",
1536
+ message: `Provider '${provider.name}' did not return an estimate for '${step.operation}'`
1537
+ });
1538
+ }
1539
+ const maxTokens = step.inputs?.max_tokens;
1540
+ if (typeof maxTokens === "number" && maxTokens > 0) {
1541
+ warnings.push({
1542
+ stepId: step.id,
1543
+ code: "variable-output",
1544
+ message: `Step '${step.id}' has variable output (max_tokens=${maxTokens}); usdLow uses ~30% of cap, usdHigh uses 100%`
1545
+ });
1546
+ }
1547
+ perStep.push(stepEntry);
1548
+ const stepRoute = step.route;
1549
+ if (stepRoute?.candidates && stepRoute.candidates.length > 1) {
1550
+ warnings.push({
1551
+ stepId: step.id,
1552
+ code: "router-spread",
1553
+ message: `Step '${step.id}' routes across ${stepRoute.candidates.length} candidates, estimates may vary`
1554
+ });
1555
+ }
1556
+ for (const inputVal of Object.values(step.inputs)) {
1557
+ if (inputVal.includes("{{") && inputVal.includes(".output}}")) {
1558
+ warnings.push({
1559
+ stepId: step.id,
1560
+ code: "depends-on-prior-step",
1561
+ message: `Step '${step.id}' cost depends on variable output from prior step`
1562
+ });
1563
+ break;
1564
+ }
1565
+ }
1566
+ }
1567
+ const totalUsdLow = perStep.reduce((s, e) => s + e.usdLow, 0);
1568
+ const totalUsdHigh = perStep.reduce((s, e) => s + e.usdHigh, 0);
1569
+ return { totalUsdLow, totalUsdHigh, perStep, warnings };
1570
+ }
1571
+ // ─── Private Methods ──────────────────────────────────────────────────────
1572
+ async updateRunState(runId, stepStates, totalCostUsd, status, error) {
1573
+ if (!this.persistence) return;
1574
+ const patch = { stepStates };
1575
+ if (totalCostUsd !== void 0) patch.totalCostUsd = totalCostUsd;
1576
+ if (status) patch.status = status;
1577
+ if (error) patch.error = error instanceof Error ? error.message : "Unknown error";
1578
+ await this.persistence.updateRun(
1579
+ runId,
1580
+ patch
1581
+ );
1582
+ }
1583
+ /**
1584
+ * Estimate the per-step cost band for budget preflight (F4) and dry-run (F5).
1585
+ *
1586
+ * Calls `provider.estimateCost` when available — that's what the spec wants, and
1587
+ * what makes preflight actually enforce caps against real pricing.
1588
+ *
1589
+ * When the provider doesn't implement estimateCost (legacy mocks, certain test fakes),
1590
+ * falls back to a tiny default band so preflight doesn't catastrophically block.
1591
+ * Variable-output ops widen the high band: when max_tokens is present, treat low as
1592
+ * ~30% of max and high as 100% per plan §F5.
1593
+ */
1594
+ async quickEstimateStep(step, _budget) {
1595
+ const provider = this.providers.get(step.operation);
1596
+ if (!provider) {
1597
+ return { usdLow: 0, usdHigh: 0.01, estimable: false };
1598
+ }
1599
+ if (typeof provider.estimateCost !== "function") {
1600
+ return { usdLow: 1e-3, usdHigh: 0.01, estimable: false };
1601
+ }
1602
+ try {
1603
+ const est = await provider.estimateCost({
1604
+ operation: step.operation,
1605
+ params: step.inputs,
1606
+ config: step.config ?? {}
1607
+ });
1608
+ const baseHigh = est.costUsd;
1609
+ const maxTokens = step.inputs?.max_tokens;
1610
+ const isVariableOutput = typeof maxTokens === "number" && maxTokens > 0;
1611
+ return {
1612
+ usdLow: isVariableOutput ? baseHigh * 0.3 : baseHigh,
1613
+ usdHigh: baseHigh,
1614
+ estimable: true,
1615
+ estimatedDurationMs: est.estimatedDurationMs
1616
+ };
1617
+ } catch {
1618
+ return { usdLow: 1e-3, usdHigh: 0.01, estimable: false };
1619
+ }
1620
+ }
1621
+ async executeStep(step, pipeline) {
1622
+ const maxRetries = step.qualityGate?.action === "retry" ? step.qualityGate.maxRetries ?? 1 : 0;
1623
+ let lastResult = null;
1624
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1625
+ if (attempt > 0) {
1626
+ this.registry.deleteBySourceStep(step.id);
1627
+ this.emitEvent({
1628
+ type: "step:retry",
1629
+ pipelineId: pipeline.id,
1630
+ stepId: step.id,
1631
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1632
+ data: { attempt: attempt + 1, maxRetries }
1633
+ });
1634
+ }
1635
+ this.emitEvent({
1636
+ type: "step:start",
1637
+ pipelineId: pipeline.id,
1638
+ stepId: step.id,
1639
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1640
+ data: { attempt: attempt + 1 }
1641
+ });
1642
+ try {
1643
+ const result = await this.executeStepOnce(step, pipeline);
1644
+ if (!result) {
1645
+ return { status: "failed" };
1646
+ }
1647
+ if (step.qualityGate) {
1648
+ const gateResult = await this.evaluateQualityGate(step.qualityGate, result.artifact);
1649
+ if (!gateResult.passed) {
1650
+ if (gateResult.action === "fail") {
1651
+ this.emitEvent({
1652
+ type: "step:failed",
1653
+ pipelineId: pipeline.id,
1654
+ stepId: step.id,
1655
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1656
+ data: { reason: gateResult.reasoning }
1657
+ });
1658
+ return { status: "failed", artifact: result.artifact };
1659
+ }
1660
+ if (gateResult.action === "retry" && attempt < maxRetries) {
1661
+ this.emitEvent({
1662
+ type: "step:gated",
1663
+ pipelineId: pipeline.id,
1664
+ stepId: step.id,
1665
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1666
+ data: { reason: gateResult.reasoning, willRetry: true }
1667
+ });
1668
+ lastResult = { status: "completed", artifact: result.artifact };
1669
+ continue;
1670
+ }
1671
+ if (gateResult.action === "retry" && attempt >= maxRetries) {
1672
+ this.emitEvent({
1673
+ type: "step:gated",
1674
+ pipelineId: pipeline.id,
1675
+ stepId: step.id,
1676
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1677
+ data: { reason: gateResult.reasoning, maxRetriesExceeded: true }
1678
+ });
1679
+ return { status: "gated", artifact: result.artifact };
1680
+ }
1681
+ if (gateResult.action === "warn") {
1682
+ console.warn(`Quality gate warning for step ${step.id}: ${gateResult.reasoning}`);
1683
+ }
1684
+ }
1685
+ }
1686
+ const gates = step.gates;
1687
+ if (gates && gates.length > 0) {
1688
+ for (const gate of gates) {
1689
+ if (gate.type === "safety" && this.gateEvalFn) {
1690
+ const gateResult2 = await this.gateEvalFn({
1691
+ gate,
1692
+ artifact: result.artifact,
1693
+ artifactUri: result.artifact.uri,
1694
+ stepId: step.id
1695
+ });
1696
+ if (gateResult2) {
1697
+ if (!gateResult2.passed) {
1698
+ const safetyResult = gateResult2;
1699
+ if (gateResult2.action === "fail") {
1700
+ throw new SafetyGateRejectedError(
1701
+ safetyResult.reasoning ?? "csam",
1702
+ safetyResult.score ?? 0
1703
+ );
1704
+ }
1705
+ this.emitEvent({
1706
+ type: "step:gated",
1707
+ pipelineId: pipeline.id,
1708
+ stepId: step.id,
1709
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1710
+ data: { reason: `Safety gate rejected: ${safetyResult.reasoning}` }
1711
+ });
1712
+ return { status: "gated", artifact: result.artifact };
1713
+ }
1714
+ continue;
1715
+ }
1716
+ }
1717
+ if (gate.type === "loudness" && this.gateEvalFn) {
1718
+ const gateResult2 = await this.gateEvalFn({
1719
+ gate,
1720
+ artifact: result.artifact,
1721
+ artifactUri: result.artifact.uri,
1722
+ stepId: step.id
1723
+ });
1724
+ if (gateResult2) {
1725
+ if (!gateResult2.passed) {
1726
+ if (gateResult2.action === "fail") {
1727
+ throw new LoudnessGateFailedError();
1728
+ }
1729
+ this.emitEvent({
1730
+ type: "step:gated",
1731
+ pipelineId: pipeline.id,
1732
+ stepId: step.id,
1733
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1734
+ data: { reason: "Loudness gate failed" }
1735
+ });
1736
+ return { status: "gated", artifact: result.artifact };
1737
+ }
1738
+ if (gateResult2.resultArtifact) {
1739
+ return { status: "completed", artifact: gateResult2.resultArtifact };
1740
+ }
1741
+ continue;
1742
+ }
1743
+ }
1744
+ const gateResult = await this.evaluateQualityGate(
1745
+ gate,
1746
+ result.artifact
1747
+ );
1748
+ if (!gateResult.passed) {
1749
+ this.emitEvent({
1750
+ type: "step:gated",
1751
+ pipelineId: pipeline.id,
1752
+ stepId: step.id,
1753
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1754
+ data: { reason: `Additional gate failed: ${gateResult.reasoning}` }
1755
+ });
1756
+ return { status: "gated", artifact: result.artifact };
1757
+ }
1758
+ }
1759
+ }
1760
+ this.emitEvent({
1761
+ type: "step:complete",
1762
+ pipelineId: pipeline.id,
1763
+ stepId: step.id,
1764
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1765
+ data: { artifactId: result.artifact.id }
1766
+ });
1767
+ return { status: "completed", artifact: result.artifact, costUsd: result.costUsd };
1768
+ } catch (error) {
1769
+ this.emitEvent({
1770
+ type: "step:failed",
1771
+ pipelineId: pipeline.id,
1772
+ stepId: step.id,
1773
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1774
+ data: { error: error instanceof Error ? error.message : "Unknown error" }
1775
+ });
1776
+ if (attempt < maxRetries) {
1777
+ continue;
1778
+ }
1779
+ return { status: "failed" };
1780
+ }
1781
+ }
1782
+ return lastResult ?? { status: "failed" };
1783
+ }
1784
+ async executeStepOnce(step, pipeline) {
1785
+ let resolvedInputs = await this.resolveInputs(step.inputs);
1786
+ const stepExt = step;
1787
+ if (this.contextResolveFn && this.context && stepExt.contextRefs) {
1788
+ const providerName = this.providers.get(step.operation)?.name ?? "unknown";
1789
+ resolvedInputs = this.contextResolveFn({
1790
+ inputs: resolvedInputs,
1791
+ context: this.context,
1792
+ providerName
1793
+ });
1794
+ }
1795
+ const route = stepExt.route;
1796
+ if (route && this.routeStepFn) {
1797
+ const routeResult = await this.routeStepFn({
1798
+ route,
1799
+ operation: step.operation,
1800
+ resolvedInputs,
1801
+ stepConfig: step.config,
1802
+ pipelineId: pipeline.id,
1803
+ stepId: step.id,
1804
+ getProviderByName: (name) => this.providers.get(name) ?? this.findProviderByName(name)
1805
+ });
1806
+ if (routeResult) {
1807
+ return { artifact: routeResult.artifact };
1808
+ }
1809
+ }
1810
+ const variants = stepExt.variants;
1811
+ if (variants && this.variantsStepFn) {
1812
+ const variantsResult = await this.variantsStepFn({
1813
+ variants,
1814
+ step,
1815
+ resolvedInputs,
1816
+ pipelineId: pipeline.id,
1817
+ stepId: step.id
1818
+ });
1819
+ if (variantsResult) {
1820
+ return { artifact: variantsResult.artifact };
1821
+ }
1822
+ }
1823
+ const ratios = stepExt.ratios;
1824
+ if (ratios && this.ratiosStepFn) {
1825
+ const ratiosResult = await this.ratiosStepFn({
1826
+ ratios,
1827
+ operation: step.operation,
1828
+ resolvedInputs,
1829
+ stepConfig: step.config,
1830
+ stepId: step.id
1831
+ });
1832
+ if (ratiosResult) {
1833
+ return { artifact: ratiosResult.artifact };
1834
+ }
1835
+ }
1836
+ const provider = this.providers.get(step.operation);
1837
+ if (!provider) {
1838
+ throw new Error(`No provider available for operation: ${step.operation}`);
1839
+ }
1840
+ if (this.tenantPolicyEnforceFn) {
1841
+ this.tenantPolicyEnforceFn(provider.name, step.config.model);
1842
+ }
1843
+ const providerInputs = this.prepareInputs ? await this.prepareInputs(step.operation, resolvedInputs) : resolvedInputs;
1844
+ const cacheConfig = stepExt.cache;
1845
+ const providerWithCache = provider;
1846
+ const result = cacheConfig && typeof providerWithCache.executeWithCache === "function" ? await providerWithCache.executeWithCache(
1847
+ { operation: step.operation, params: providerInputs, config: step.config },
1848
+ cacheConfig.mode ? { mode: cacheConfig.mode, ttlSeconds: cacheConfig.ttlSeconds } : void 0
1849
+ ) : await provider.execute(step.operation, providerInputs, step.config);
1850
+ const artifactId = (0, import_node_crypto.randomUUID)();
1851
+ const persisted = this.persistArtifact ? await this.persistArtifact({
1852
+ artifactId,
1853
+ operation: step.operation,
1854
+ data: result.data,
1855
+ artifact: {
1856
+ ...result.artifact,
1857
+ sourceStep: step.id
1858
+ },
1859
+ pipelineId: pipeline.id,
1860
+ stepId: step.id
1861
+ }) : void 0;
1862
+ const artifact = this.registry.registerWithId(artifactId, {
1863
+ ...result.artifact,
1864
+ uri: persisted?.uri ?? result.artifact.uri,
1865
+ sourceStep: step.id
1866
+ });
1867
+ if (result.cost_usd !== void 0) {
1868
+ this.onCost?.({
1869
+ operation: step.operation,
1870
+ provider: provider.name,
1871
+ model: step.config.model,
1872
+ cost_usd: result.cost_usd,
1873
+ artifactId: artifact.id,
1874
+ pipelineId: pipeline.id,
1875
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1876
+ });
1877
+ if (this.ledger) {
1878
+ await this.ledger.charge({
1879
+ runId: pipeline.id,
1880
+ stepId: step.id,
1881
+ operation: step.operation,
1882
+ provider: provider.name,
1883
+ costUsd: result.cost_usd
1884
+ });
1885
+ }
1886
+ }
1887
+ if (this.signProvenance) {
1888
+ const ingredientArtifactIds = [];
1889
+ for (const value of Object.values(step.inputs ?? {})) {
1890
+ const m = value.match?.(/^\{\{(\w+)\.output\}\}$/);
1891
+ if (m) {
1892
+ const upstream = this.registry.findBySourceStep(m[1]);
1893
+ if (upstream) ingredientArtifactIds.push(upstream.id);
1894
+ }
1895
+ }
1896
+ await this.signProvenance({
1897
+ artifactId: artifact.id,
1898
+ runId: pipeline.id,
1899
+ // executor's Pipeline.id is the runId for in-tree runs
1900
+ pipelineDefHash: this.currentPipelineDefHash ?? "",
1901
+ stepId: step.id,
1902
+ operation: step.operation,
1903
+ providerId: provider.name,
1904
+ modelId: step.config?.model,
1905
+ ingredientArtifactIds
1906
+ });
1907
+ }
1908
+ return { artifact, costUsd: result.cost_usd };
1909
+ }
1910
+ /** Hash the normalized pipeline definition for provenance manifests (F17) and for
1911
+ * PipelineRunRecord auditing. Same canonical-json rule as F2 cache keys. */
1912
+ hashPipelineDefinition(def) {
1913
+ const normalized = { id: def.id, steps: def.steps };
1914
+ const canonical = JSON.stringify(normalized, Object.keys(normalized).sort());
1915
+ const { createHash } = require("crypto");
1916
+ return createHash("sha256").update(canonical).digest("hex");
1917
+ }
1918
+ /** Set inside execute()/resume() before stepping through steps; read by signProvenance. */
1919
+ currentPipelineDefHash;
1920
+ findProviderByName(name) {
1921
+ for (const provider of this.providers.values()) {
1922
+ if (provider.name === name) return provider;
1923
+ }
1924
+ return void 0;
1925
+ }
1926
+ async resolveInputs(inputSpec) {
1927
+ const resolved = {};
1928
+ for (const [key, value] of Object.entries(inputSpec)) {
1929
+ const match = value.match(/^\{\{(\w+)\.output\}\}$/);
1930
+ if (match) {
1931
+ const stepId = match[1];
1932
+ const artifact = this.registry.findBySourceStep(stepId);
1933
+ if (!artifact) {
1934
+ throw new ArtifactNotFoundError();
1935
+ }
1936
+ resolved[key] = artifact.id;
1937
+ } else {
1938
+ resolved[key] = value;
1939
+ }
1940
+ }
1941
+ return resolved;
1942
+ }
1943
+ async evaluateQualityGate(_gate, artifact) {
1944
+ const evaluator = createQualityGateEvaluator(_gate, this.llmJudgeFn, this.customCheckFn);
1945
+ return await evaluator.evaluate(_gate, artifact);
1946
+ }
1947
+ emitEvent(event) {
1948
+ this.onEvent?.(event);
1949
+ const specType = LEGACY_TO_SPEC_EVENT[event.type];
1950
+ if (specType && specType !== event.type) {
1951
+ this.onEvent?.({ ...event, type: specType });
1952
+ }
1953
+ }
1954
+ getRegistry() {
1955
+ return this.registry;
1956
+ }
1957
+ };
1958
+
1959
+ // src/pipeline-estimator.ts
1960
+ var PipelineEstimator = class {
1961
+ ledger;
1962
+ estimateOperation;
1963
+ constructor(options = {}) {
1964
+ this.ledger = options.ledger;
1965
+ this.estimateOperation = options.estimateOperation;
1966
+ }
1967
+ async estimate(pipeline) {
1968
+ const perStep = [];
1969
+ const warnings = [];
1970
+ for (const step of pipeline.steps) {
1971
+ let usdLow = 1e-3;
1972
+ let usdHigh = 0.01;
1973
+ let estimable = true;
1974
+ let fallbackUsed;
1975
+ const provider = "unknown";
1976
+ const modelId = step.config.model ?? "default";
1977
+ if (this.estimateOperation) {
1978
+ const opEst = await this.estimateOperation(step.operation, step.config);
1979
+ if (opEst !== null) {
1980
+ usdLow = opEst.usdLow;
1981
+ usdHigh = opEst.usdHigh;
1982
+ } else {
1983
+ estimable = false;
1984
+ fallbackUsed = "default-bound";
1985
+ warnings.push({
1986
+ stepId: step.id,
1987
+ code: "no-estimator",
1988
+ message: `No estimator available for operation '${step.operation}'`
1989
+ });
1990
+ }
1991
+ } else {
1992
+ estimable = false;
1993
+ fallbackUsed = "default-bound";
1994
+ }
1995
+ if (!estimable && this.ledger) {
1996
+ try {
1997
+ const historical = await this.ledger.getTotalCost();
1998
+ if (historical > 0) {
1999
+ fallbackUsed = "cached-stats";
2000
+ }
2001
+ } catch {
2002
+ }
2003
+ }
2004
+ const route = step.route;
2005
+ if (route?.candidates && route.candidates.length > 1) {
2006
+ warnings.push({
2007
+ stepId: step.id,
2008
+ code: "router-spread",
2009
+ message: `Step '${step.id}' routes across ${route.candidates.length} candidates, estimates may vary`
2010
+ });
2011
+ }
2012
+ for (const inputVal of Object.values(step.inputs)) {
2013
+ if (typeof inputVal === "string" && inputVal.includes("{{") && inputVal.includes(".output}}")) {
2014
+ warnings.push({
2015
+ stepId: step.id,
2016
+ code: "depends-on-prior-step",
2017
+ message: `Step '${step.id}' cost depends on variable output from prior step`
2018
+ });
2019
+ break;
2020
+ }
2021
+ }
2022
+ perStep.push({
2023
+ stepId: step.id,
2024
+ operation: step.operation,
2025
+ provider,
2026
+ modelId,
2027
+ usdLow,
2028
+ usdHigh,
2029
+ estimable,
2030
+ ...fallbackUsed ? { fallbackUsed } : {}
2031
+ });
2032
+ }
2033
+ const totalUsdLow = perStep.reduce((s, e) => s + e.usdLow, 0);
2034
+ const totalUsdHigh = perStep.reduce((s, e) => s + e.usdHigh, 0);
2035
+ return { totalUsdLow, totalUsdHigh, perStep, warnings };
2036
+ }
2037
+ };
2038
+
2039
+ // src/mock-provider.ts
2040
+ var MockProvider = class {
2041
+ name;
2042
+ supportedOperations;
2043
+ delay;
2044
+ failureRate;
2045
+ baseCost;
2046
+ alwaysPass;
2047
+ constructor(config = {}) {
2048
+ this.name = config.name ?? "mock";
2049
+ this.supportedOperations = config.operations ?? [
2050
+ "mock.generate",
2051
+ "mock.transform",
2052
+ "mock.extract"
2053
+ ];
2054
+ this.delay = config.delay ?? 100;
2055
+ this.failureRate = config.failureRate ?? 0;
2056
+ this.baseCost = config.baseCost ?? 1e-3;
2057
+ this.alwaysPass = config.alwaysPass ?? false;
2058
+ }
2059
+ async execute(operation, _inputs, config) {
2060
+ await new Promise((resolve) => setTimeout(resolve, this.delay));
2061
+ if (Math.random() < this.failureRate) {
2062
+ throw new Error("Mock provider simulated failure");
2063
+ }
2064
+ let type = "image";
2065
+ let mimeType = "image/png";
2066
+ if (operation.includes("audio")) {
2067
+ type = "audio";
2068
+ mimeType = "audio/mpeg";
2069
+ } else if (operation.includes("video")) {
2070
+ type = "video";
2071
+ mimeType = "video/mp4";
2072
+ } else if (operation.includes("text") || operation.includes("extract")) {
2073
+ type = "text";
2074
+ mimeType = "text/plain";
2075
+ } else if (operation.includes("document")) {
2076
+ type = "document";
2077
+ mimeType = "application/pdf";
2078
+ }
2079
+ const metadata = {
2080
+ width: 1024,
2081
+ height: 1024,
2082
+ quality: this.alwaysPass ? 0.99 : 0.9,
2083
+ ...config
2084
+ };
2085
+ if (type === "audio") {
2086
+ metadata.duration = 30;
2087
+ metadata.sampleRate = 44100;
2088
+ } else if (type === "video") {
2089
+ metadata.duration = 10;
2090
+ metadata.fps = 30;
2091
+ }
2092
+ const uri = `mock://${operation}/${Date.now()}.${mimeType.split("/")[1]}`;
2093
+ return {
2094
+ data: Buffer.from(JSON.stringify({ operation, metadata })),
2095
+ artifact: {
2096
+ type,
2097
+ uri,
2098
+ mimeType,
2099
+ metadata,
2100
+ sourceStep: void 0
2101
+ },
2102
+ cost_usd: this.baseCost,
2103
+ duration_ms: this.delay
2104
+ };
2105
+ }
2106
+ async healthCheck() {
2107
+ return Math.random() > this.failureRate;
2108
+ }
2109
+ };
2110
+ var mockOperations = {
2111
+ generate: "mock.generate",
2112
+ transform: "mock.transform",
2113
+ extract: "mock.extract",
2114
+ imageGenerate: "image.generate",
2115
+ imageUpscale: "image.upscale",
2116
+ imageRemoveBackground: "image.remove_background",
2117
+ audioTts: "audio.tts",
2118
+ audioStt: "audio.stt"
2119
+ };
2120
+
2121
+ // src/event-bus.ts
2122
+ function createEventBus() {
2123
+ const handlers = /* @__PURE__ */ new Map();
2124
+ function on(kind, handler) {
2125
+ if (!handlers.has(kind)) {
2126
+ handlers.set(kind, /* @__PURE__ */ new Set());
2127
+ }
2128
+ handlers.get(kind)?.add(handler);
2129
+ return () => {
2130
+ handlers.get(kind)?.delete(handler);
2131
+ };
2132
+ }
2133
+ function emit(event) {
2134
+ const kindHandlers = handlers.get(event.kind);
2135
+ if (kindHandlers) {
2136
+ for (const handler of kindHandlers) {
2137
+ const result = handler(event);
2138
+ if (result instanceof Promise) {
2139
+ result.catch((err) => {
2140
+ console.error(`EventBus handler error for ${event.kind}:`, err);
2141
+ });
2142
+ }
2143
+ }
2144
+ }
2145
+ }
2146
+ function awaitEvent(kind, predicate, timeoutMs) {
2147
+ return new Promise((resolve, reject) => {
2148
+ let disposer = () => {
2149
+ };
2150
+ const timer = timeoutMs !== void 0 && timeoutMs > 0 ? setTimeout(() => {
2151
+ disposer();
2152
+ reject(new Error(`await(${kind}) timed out after ${timeoutMs}ms`));
2153
+ }, timeoutMs) : void 0;
2154
+ const removeHandler = on(kind, (event) => {
2155
+ const match = predicate ? predicate(event) : true;
2156
+ if (match) {
2157
+ if (timer) clearTimeout(timer);
2158
+ removeHandler();
2159
+ resolve(event);
2160
+ }
2161
+ });
2162
+ disposer = () => {
2163
+ if (timer) clearTimeout(timer);
2164
+ removeHandler();
2165
+ };
2166
+ });
2167
+ }
2168
+ return {
2169
+ on,
2170
+ emit,
2171
+ await: awaitEvent
2172
+ };
2173
+ }
2174
+ // Annotate the CommonJS export names for ESM import in node:
2175
+ 0 && (module.exports = {
2176
+ A2AError,
2177
+ ArtifactAccessDeniedError,
2178
+ ArtifactMetaSchema,
2179
+ ArtifactNotFoundError,
2180
+ ArtifactRegistry,
2181
+ ArtifactSchema,
2182
+ ArtifactTypeSchema,
2183
+ BrandKitSchema,
2184
+ BudgetConfigSchema,
2185
+ BudgetExceededError,
2186
+ CacheConfigSchema,
2187
+ ContextRefSchema,
2188
+ ContextRefTypeError,
2189
+ ContextRefUnknownError,
2190
+ CostRecordSchema,
2191
+ CostSummarySchema,
2192
+ CustomEvaluator,
2193
+ DimensionCheckEvaluator,
2194
+ EstimateUnsupportedError,
2195
+ FfmpegUnavailableError,
2196
+ FormatUnsupportedError,
2197
+ IdempotencyConflictError,
2198
+ InvalidInputError,
2199
+ InvalidResourceUriError,
2200
+ JudgeConfigSchema,
2201
+ JudgeRubricSchema,
2202
+ JudgeUnavailableError,
2203
+ KeyVaultUnavailableError,
2204
+ LLMJudgeEvaluator,
2205
+ LoudnessGateFailedError,
2206
+ MockProvider,
2207
+ PipelineDefinitionSchema,
2208
+ PipelineEstimator,
2209
+ PipelineEventSchema,
2210
+ PipelineEventTypeSchema,
2211
+ PipelineExecutor,
2212
+ PipelineSchema,
2213
+ PipelineStatusSchema,
2214
+ PipelineStepSchema,
2215
+ PipelineValidator,
2216
+ ProvenanceSigningFailedError,
2217
+ ProviderInputSchema,
2218
+ ProviderOutputSchema,
2219
+ QualityGateActionSchema,
2220
+ QualityGateResultSchema,
2221
+ QualityGateSchema,
2222
+ RatioUnsupportedError,
2223
+ RouteCandidateSchema,
2224
+ RouteConfigSchema,
2225
+ RouterAllCandidatesFailedError,
2226
+ RouterFastestIneligibleError,
2227
+ RouterNoCandidatesError,
2228
+ RunContextSchema,
2229
+ RunInProgressError,
2230
+ RunNotFoundError,
2231
+ RunNotResumableError,
2232
+ SafetyGateRejectedError,
2233
+ SafetyProviderUnavailableError,
2234
+ StateStoreUnavailableError,
2235
+ StorageResultSchema,
2236
+ StyleRefSchema,
2237
+ TenantNotFoundError,
2238
+ TenantPolicyViolationError,
2239
+ ThresholdEvaluator,
2240
+ ValidationResultSchema,
2241
+ VariantsAllRejectedError,
2242
+ VariantsConfigSchema,
2243
+ VoiceRefSchema,
2244
+ WebhookProviderUnknownError,
2245
+ WebhookSignatureInvalidError,
2246
+ WorkflowExpiredError,
2247
+ WorkflowNotFoundError,
2248
+ createEventBus,
2249
+ createQualityGateEvaluator,
2250
+ createStepStateRecord,
2251
+ mockOperations
2252
+ });