@dogpile/sdk 0.3.1 → 0.5.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/CHANGELOG.md +201 -0
- package/README.md +1 -0
- package/dist/browser/index.js +2328 -237
- package/dist/browser/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts +11 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +88 -2
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/runtime/audit.d.ts +42 -0
- package/dist/runtime/audit.d.ts.map +1 -0
- package/dist/runtime/audit.js +73 -0
- package/dist/runtime/audit.js.map +1 -0
- package/dist/runtime/broadcast.d.ts.map +1 -1
- package/dist/runtime/broadcast.js +39 -36
- package/dist/runtime/broadcast.js.map +1 -1
- package/dist/runtime/cancellation.d.ts +26 -0
- package/dist/runtime/cancellation.d.ts.map +1 -1
- package/dist/runtime/cancellation.js +38 -1
- package/dist/runtime/cancellation.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +79 -1
- package/dist/runtime/coordinator.d.ts.map +1 -1
- package/dist/runtime/coordinator.js +979 -61
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/decisions.d.ts +25 -3
- package/dist/runtime/decisions.d.ts.map +1 -1
- package/dist/runtime/decisions.js +241 -3
- package/dist/runtime/decisions.js.map +1 -1
- package/dist/runtime/defaults.d.ts +37 -1
- package/dist/runtime/defaults.d.ts.map +1 -1
- package/dist/runtime/defaults.js +359 -4
- package/dist/runtime/defaults.js.map +1 -1
- package/dist/runtime/engine.d.ts +17 -4
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +770 -35
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/health.d.ts +51 -0
- package/dist/runtime/health.d.ts.map +1 -0
- package/dist/runtime/health.js +85 -0
- package/dist/runtime/health.js.map +1 -0
- package/dist/runtime/introspection.d.ts +96 -0
- package/dist/runtime/introspection.d.ts.map +1 -0
- package/dist/runtime/introspection.js +31 -0
- package/dist/runtime/introspection.js.map +1 -0
- package/dist/runtime/metrics.d.ts +44 -0
- package/dist/runtime/metrics.d.ts.map +1 -0
- package/dist/runtime/metrics.js +12 -0
- package/dist/runtime/metrics.js.map +1 -0
- package/dist/runtime/model.d.ts.map +1 -1
- package/dist/runtime/model.js +34 -7
- package/dist/runtime/model.js.map +1 -1
- package/dist/runtime/provenance.d.ts +25 -0
- package/dist/runtime/provenance.d.ts.map +1 -0
- package/dist/runtime/provenance.js +13 -0
- package/dist/runtime/provenance.js.map +1 -0
- package/dist/runtime/sequential.d.ts.map +1 -1
- package/dist/runtime/sequential.js +47 -37
- package/dist/runtime/sequential.js.map +1 -1
- package/dist/runtime/shared.d.ts.map +1 -1
- package/dist/runtime/shared.js +39 -36
- package/dist/runtime/shared.js.map +1 -1
- package/dist/runtime/tracing.d.ts +31 -0
- package/dist/runtime/tracing.d.ts.map +1 -0
- package/dist/runtime/tracing.js +18 -0
- package/dist/runtime/tracing.js.map +1 -0
- package/dist/runtime/validation.d.ts +10 -0
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/runtime/validation.js +73 -0
- package/dist/runtime/validation.js.map +1 -1
- package/dist/types/events.d.ts +339 -12
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/replay.d.ts +7 -1
- package/dist/types/replay.d.ts.map +1 -1
- package/dist/types.d.ts +255 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +39 -1
- package/src/index.ts +15 -0
- package/src/providers/openai-compatible.ts +83 -3
- package/src/runtime/audit.ts +121 -0
- package/src/runtime/broadcast.ts +40 -37
- package/src/runtime/cancellation.ts +59 -1
- package/src/runtime/coordinator.ts +1221 -61
- package/src/runtime/decisions.ts +307 -4
- package/src/runtime/defaults.ts +389 -4
- package/src/runtime/engine.ts +1004 -35
- package/src/runtime/health.ts +136 -0
- package/src/runtime/introspection.ts +122 -0
- package/src/runtime/metrics.ts +45 -0
- package/src/runtime/model.ts +38 -6
- package/src/runtime/provenance.ts +43 -0
- package/src/runtime/sequential.ts +49 -38
- package/src/runtime/shared.ts +40 -37
- package/src/runtime/tracing.ts +35 -0
- package/src/runtime/validation.ts +81 -0
- package/src/types/events.ts +369 -12
- package/src/types/replay.ts +14 -1
- package/src/types.ts +279 -4
package/dist/browser/index.js
CHANGED
|
@@ -193,6 +193,27 @@ function addCost(left, right) {
|
|
|
193
193
|
totalTokens: left.totalTokens + right.totalTokens
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
|
+
function resolveOnChildFailure(runOption, engineOption) {
|
|
197
|
+
return runOption ?? engineOption ?? "continue";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Walk a parent's events and accumulate the cost contributed by every
|
|
201
|
+
* sub-run (BUDGET-03 / D-06). Internal helper — not part of the public surface.
|
|
202
|
+
*
|
|
203
|
+
* - `sub-run-completed` events contribute `event.subResult.cost`.
|
|
204
|
+
* - `sub-run-failed` events contribute `event.partialCost` (real provider
|
|
205
|
+
* spend captured before the throw).
|
|
206
|
+
*
|
|
207
|
+
* Used by the `parent-rollup-drift` parity check in
|
|
208
|
+
* {@link recomputeAccountingFromTrace} to verify the parent's recorded
|
|
209
|
+
* accounting equals `localOnly + Σ children` recursively.
|
|
210
|
+
*/
|
|
211
|
+
function accumulateSubRunCost(events) {
|
|
212
|
+
let total = emptyCost();
|
|
213
|
+
for (const event of events) if (event.type === "sub-run-completed") total = addCost(total, event.subResult.cost);
|
|
214
|
+
else if (event.type === "sub-run-failed") total = addCost(total, event.partialCost);
|
|
215
|
+
return total;
|
|
216
|
+
}
|
|
196
217
|
function createTranscriptLink(transcript) {
|
|
197
218
|
return {
|
|
198
219
|
kind: "trace-transcript",
|
|
@@ -241,8 +262,8 @@ function createRunMetadata(options) {
|
|
|
241
262
|
tier: options.tier,
|
|
242
263
|
modelProviderId: options.modelProviderId,
|
|
243
264
|
agentsUsed: options.agentsUsed,
|
|
244
|
-
startedAt: firstEvent
|
|
245
|
-
completedAt: lastEvent
|
|
265
|
+
startedAt: eventTimestamp$1(firstEvent) ?? "",
|
|
266
|
+
completedAt: eventTimestamp$1(lastEvent) ?? ""
|
|
246
267
|
};
|
|
247
268
|
}
|
|
248
269
|
function createReplayTraceRunInputs(options) {
|
|
@@ -291,7 +312,14 @@ function createReplayTraceBudgetStateChanges(events) {
|
|
|
291
312
|
case "model-response":
|
|
292
313
|
case "model-output-chunk":
|
|
293
314
|
case "tool-call":
|
|
294
|
-
case "tool-result":
|
|
315
|
+
case "tool-result":
|
|
316
|
+
case "sub-run-started":
|
|
317
|
+
case "sub-run-completed":
|
|
318
|
+
case "sub-run-failed":
|
|
319
|
+
case "sub-run-parent-aborted":
|
|
320
|
+
case "sub-run-budget-clamped":
|
|
321
|
+
case "sub-run-queued":
|
|
322
|
+
case "sub-run-concurrency-clamped": return [];
|
|
295
323
|
}
|
|
296
324
|
});
|
|
297
325
|
}
|
|
@@ -314,7 +342,7 @@ function createReplayTraceProtocolDecision(protocol, event, eventIndex, options
|
|
|
314
342
|
eventType: event.type,
|
|
315
343
|
protocol,
|
|
316
344
|
decision: options.decision ?? defaultProtocolDecision(event),
|
|
317
|
-
at: event
|
|
345
|
+
at: eventTimestamp$1(event),
|
|
318
346
|
...options.turn !== void 0 ? { turn: options.turn } : {},
|
|
319
347
|
...options.phase !== void 0 ? { phase: options.phase } : {},
|
|
320
348
|
...options.round !== void 0 ? { round: options.round } : {},
|
|
@@ -388,6 +416,24 @@ function createReplayTraceProtocolDecision(protocol, event, eventIndex, options
|
|
|
388
416
|
output: event.output,
|
|
389
417
|
cost: event.cost
|
|
390
418
|
};
|
|
419
|
+
case "sub-run-started": return {
|
|
420
|
+
...base,
|
|
421
|
+
input: event.intent
|
|
422
|
+
};
|
|
423
|
+
case "sub-run-completed": return {
|
|
424
|
+
...base,
|
|
425
|
+
output: event.subResult.output,
|
|
426
|
+
cost: event.subResult.cost
|
|
427
|
+
};
|
|
428
|
+
case "sub-run-failed": return { ...base };
|
|
429
|
+
case "sub-run-parent-aborted": return { ...base };
|
|
430
|
+
case "sub-run-budget-clamped": return { ...base };
|
|
431
|
+
case "sub-run-queued": return {
|
|
432
|
+
...base,
|
|
433
|
+
childRunId: event.childRunId,
|
|
434
|
+
queuePosition: event.queuePosition
|
|
435
|
+
};
|
|
436
|
+
case "sub-run-concurrency-clamped": return { ...base };
|
|
391
437
|
}
|
|
392
438
|
}
|
|
393
439
|
function defaultProtocolDecision(event) {
|
|
@@ -402,6 +448,13 @@ function defaultProtocolDecision(event) {
|
|
|
402
448
|
case "broadcast": return "collect-broadcast-round";
|
|
403
449
|
case "budget-stop": return "stop-for-budget";
|
|
404
450
|
case "final": return "finalize-output";
|
|
451
|
+
case "sub-run-started": return "start-sub-run";
|
|
452
|
+
case "sub-run-completed": return "complete-sub-run";
|
|
453
|
+
case "sub-run-failed": return "fail-sub-run";
|
|
454
|
+
case "sub-run-parent-aborted": return "mark-sub-run-parent-aborted";
|
|
455
|
+
case "sub-run-budget-clamped": return "mark-sub-run-budget-clamped";
|
|
456
|
+
case "sub-run-queued": return "queue-sub-run";
|
|
457
|
+
case "sub-run-concurrency-clamped": return "mark-sub-run-concurrency-clamped";
|
|
405
458
|
}
|
|
406
459
|
}
|
|
407
460
|
function eventAgentScope(event) {
|
|
@@ -422,7 +475,7 @@ function createReplayTraceFinalOutput(output, event) {
|
|
|
422
475
|
kind: "replay-trace-final-output",
|
|
423
476
|
output,
|
|
424
477
|
cost: emptyCost(),
|
|
425
|
-
completedAt: event
|
|
478
|
+
completedAt: eventTimestamp$1(event),
|
|
426
479
|
transcript: {
|
|
427
480
|
kind: "trace-transcript",
|
|
428
481
|
entryCount: 0,
|
|
@@ -430,6 +483,11 @@ function createReplayTraceFinalOutput(output, event) {
|
|
|
430
483
|
}
|
|
431
484
|
};
|
|
432
485
|
}
|
|
486
|
+
function eventTimestamp$1(event) {
|
|
487
|
+
if (event === void 0) return void 0;
|
|
488
|
+
if ("at" in event) return event.at;
|
|
489
|
+
return event.type === "model-response" ? event.completedAt : event.startedAt;
|
|
490
|
+
}
|
|
433
491
|
function nextProviderCallId(runId, providerCalls) {
|
|
434
492
|
return `${runId}:provider-call:${providerCalls.length + 1}`;
|
|
435
493
|
}
|
|
@@ -456,6 +514,7 @@ function canonicalizeRunResult(result) {
|
|
|
456
514
|
cost: canonicalizeSerializable(result.cost),
|
|
457
515
|
...result.evaluation !== void 0 ? { evaluation: canonicalizeSerializable(result.evaluation) } : {},
|
|
458
516
|
eventLog,
|
|
517
|
+
health: canonicalizeSerializable(result.health),
|
|
459
518
|
metadata: canonicalizeSerializable(result.metadata),
|
|
460
519
|
output: result.output,
|
|
461
520
|
...result.quality !== void 0 ? { quality: canonicalizeSerializable(result.quality) } : {},
|
|
@@ -467,6 +526,216 @@ function canonicalizeRunResult(result) {
|
|
|
467
526
|
function stableJsonStringify(value) {
|
|
468
527
|
return JSON.stringify(canonicalizeSerializable(value));
|
|
469
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* The eight numeric fields recursively verified by `recomputeAccountingFromTrace`.
|
|
531
|
+
*
|
|
532
|
+
* These are the only summable scalars on `RunAccounting`. Non-numeric fields
|
|
533
|
+
* (`kind`, `tier`, `budget`, `termination`, `budgetStateChanges`) and derived
|
|
534
|
+
* ratios (`usdCapUtilization`, `totalTokenCapUtilization`) are NOT in this set.
|
|
535
|
+
*/
|
|
536
|
+
var RECOMPUTE_FIELD_ORDER = [
|
|
537
|
+
"cost.usd",
|
|
538
|
+
"cost.inputTokens",
|
|
539
|
+
"cost.outputTokens",
|
|
540
|
+
"cost.totalTokens",
|
|
541
|
+
"usage.usd",
|
|
542
|
+
"usage.inputTokens",
|
|
543
|
+
"usage.outputTokens",
|
|
544
|
+
"usage.totalTokens"
|
|
545
|
+
];
|
|
546
|
+
var USD_FIELDS = new Set(["cost.usd", "usage.usd"]);
|
|
547
|
+
var FLOAT_EPSILON = 1e-9;
|
|
548
|
+
function readNumericField(accounting, field) {
|
|
549
|
+
switch (field) {
|
|
550
|
+
case "cost.usd": return accounting.cost.usd;
|
|
551
|
+
case "cost.inputTokens": return accounting.cost.inputTokens;
|
|
552
|
+
case "cost.outputTokens": return accounting.cost.outputTokens;
|
|
553
|
+
case "cost.totalTokens": return accounting.cost.totalTokens;
|
|
554
|
+
case "usage.usd": return accounting.usage.usd;
|
|
555
|
+
case "usage.inputTokens": return accounting.usage.inputTokens;
|
|
556
|
+
case "usage.outputTokens": return accounting.usage.outputTokens;
|
|
557
|
+
case "usage.totalTokens": return accounting.usage.totalTokens;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
function fieldsEqual(field, a, b) {
|
|
561
|
+
if (USD_FIELDS.has(field)) return Math.abs(a - b) < FLOAT_EPSILON;
|
|
562
|
+
return a === b;
|
|
563
|
+
}
|
|
564
|
+
function firstDifferingField(recorded, recomputed) {
|
|
565
|
+
for (const field of RECOMPUTE_FIELD_ORDER) {
|
|
566
|
+
const a = readNumericField(recorded, field);
|
|
567
|
+
const b = readNumericField(recomputed, field);
|
|
568
|
+
if (!fieldsEqual(field, a, b)) return {
|
|
569
|
+
field,
|
|
570
|
+
recorded: a,
|
|
571
|
+
recomputed: b
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
function buildLocalAccounting(trace) {
|
|
577
|
+
return createRunAccounting({
|
|
578
|
+
tier: trace.tier,
|
|
579
|
+
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
580
|
+
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
581
|
+
cost: trace.finalOutput.cost,
|
|
582
|
+
events: trace.events
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function lastCostBearingEventCost(events) {
|
|
586
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
587
|
+
const event = events[index];
|
|
588
|
+
if (event === void 0) continue;
|
|
589
|
+
if (event.type === "final" || event.type === "agent-turn" || event.type === "broadcast" || event.type === "budget-stop") return event.cost;
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Recompute a parent's `RunAccounting` from a saved `Trace` for replay-time
|
|
595
|
+
* tamper detection.
|
|
596
|
+
*
|
|
597
|
+
* @remarks
|
|
598
|
+
* Returns the parent's local `RunAccounting` (built the same way `replay()`
|
|
599
|
+
* builds it today, from `trace.finalOutput.cost` and `trace.events`). While
|
|
600
|
+
* walking events, every `sub-run-completed` is recursed into and the
|
|
601
|
+
* recomputed child accounting is compared field-by-field to the recorded
|
|
602
|
+
* `event.subResult.accounting`. A mismatch on any of the eight enumerated
|
|
603
|
+
* numeric fields throws `DogpileError({ code: "invalid-configuration" })`
|
|
604
|
+
* with `detail.reason: "trace-accounting-mismatch"` and a concrete
|
|
605
|
+
* `detail.field` identifying the first differing numeric.
|
|
606
|
+
*
|
|
607
|
+
* Pure: no provider calls, no I/O, no clock reads.
|
|
608
|
+
*
|
|
609
|
+
* Non-summed fields (`kind`, `tier`, `budget`, `termination`,
|
|
610
|
+
* `budgetStateChanges`) and derived ratios (`usdCapUtilization`,
|
|
611
|
+
* `totalTokenCapUtilization`) are not in the comparison set.
|
|
612
|
+
*/
|
|
613
|
+
function recomputeAccountingFromTrace(trace) {
|
|
614
|
+
const local = buildLocalAccounting(trace);
|
|
615
|
+
const lastEventCost = lastCostBearingEventCost(trace.events);
|
|
616
|
+
if (lastEventCost !== null) {
|
|
617
|
+
const drift = firstDifferingField(local, createRunAccounting({
|
|
618
|
+
tier: trace.tier,
|
|
619
|
+
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
620
|
+
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
621
|
+
cost: lastEventCost,
|
|
622
|
+
events: trace.events
|
|
623
|
+
}));
|
|
624
|
+
if (drift !== null) throw new DogpileError({
|
|
625
|
+
code: "invalid-configuration",
|
|
626
|
+
message: `Trace accounting mismatch at parent run ${trace.runId}: field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed}.`,
|
|
627
|
+
retryable: false,
|
|
628
|
+
detail: {
|
|
629
|
+
kind: "trace-validation",
|
|
630
|
+
reason: "trace-accounting-mismatch",
|
|
631
|
+
eventIndex: -1,
|
|
632
|
+
childRunId: trace.runId,
|
|
633
|
+
field: drift.field,
|
|
634
|
+
recorded: drift.recorded,
|
|
635
|
+
recomputed: drift.recomputed
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
for (let eventIndex = 0; eventIndex < trace.events.length; eventIndex += 1) {
|
|
640
|
+
const event = trace.events[eventIndex];
|
|
641
|
+
if (event === void 0) continue;
|
|
642
|
+
if (event.type === "sub-run-completed") {
|
|
643
|
+
const childRecordedRollup = createRunAccounting({
|
|
644
|
+
tier: trace.tier,
|
|
645
|
+
cost: event.subResult.cost,
|
|
646
|
+
events: []
|
|
647
|
+
});
|
|
648
|
+
const childRecordedAccounting = event.subResult.accounting;
|
|
649
|
+
const drift = firstDifferingField(childRecordedAccounting, childRecordedRollup);
|
|
650
|
+
if (drift !== null) throw new DogpileError({
|
|
651
|
+
code: "invalid-configuration",
|
|
652
|
+
message: `Trace parent-rollup mismatch at sub-run ${event.childRunId}: field "${drift.field}" recorded ${drift.recorded} on accounting, ${drift.recomputed} on subResult.cost.`,
|
|
653
|
+
retryable: false,
|
|
654
|
+
detail: {
|
|
655
|
+
kind: "trace-validation",
|
|
656
|
+
reason: "trace-accounting-mismatch",
|
|
657
|
+
subReason: "parent-rollup-drift",
|
|
658
|
+
eventIndex,
|
|
659
|
+
childRunId: event.childRunId,
|
|
660
|
+
field: drift.field,
|
|
661
|
+
recorded: drift.recorded,
|
|
662
|
+
recomputed: drift.recomputed
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
} else if (event.type === "sub-run-failed") {
|
|
666
|
+
const partialFromTrace = lastCostBearingEventCost(event.partialTrace.events) ?? emptyCost();
|
|
667
|
+
const drift = firstDifferingField(createRunAccounting({
|
|
668
|
+
tier: trace.tier,
|
|
669
|
+
cost: event.partialCost,
|
|
670
|
+
events: []
|
|
671
|
+
}), createRunAccounting({
|
|
672
|
+
tier: trace.tier,
|
|
673
|
+
cost: partialFromTrace,
|
|
674
|
+
events: []
|
|
675
|
+
}));
|
|
676
|
+
if (drift !== null) throw new DogpileError({
|
|
677
|
+
code: "invalid-configuration",
|
|
678
|
+
message: `Trace parent-rollup mismatch at sub-run ${event.childRunId}: partialCost field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed} from partialTrace events.`,
|
|
679
|
+
retryable: false,
|
|
680
|
+
detail: {
|
|
681
|
+
kind: "trace-validation",
|
|
682
|
+
reason: "trace-accounting-mismatch",
|
|
683
|
+
subReason: "parent-rollup-drift",
|
|
684
|
+
eventIndex,
|
|
685
|
+
childRunId: event.childRunId,
|
|
686
|
+
field: drift.field,
|
|
687
|
+
recorded: drift.recorded,
|
|
688
|
+
recomputed: drift.recomputed
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const subRunTotal = accumulateSubRunCost(trace.events);
|
|
694
|
+
const parentTotal = trace.finalOutput.cost;
|
|
695
|
+
for (const field of RECOMPUTE_FIELD_ORDER) {
|
|
696
|
+
if (field.startsWith("usage.")) continue;
|
|
697
|
+
const [, key] = field.split(".");
|
|
698
|
+
const parentValue = parentTotal[key];
|
|
699
|
+
const childValue = subRunTotal[key];
|
|
700
|
+
if (childValue - parentValue > FLOAT_EPSILON) throw new DogpileError({
|
|
701
|
+
code: "invalid-configuration",
|
|
702
|
+
message: `Trace parent-rollup mismatch at run ${trace.runId}: field "${field}" Σ children ${childValue} exceeds parent recorded ${parentValue}.`,
|
|
703
|
+
retryable: false,
|
|
704
|
+
detail: {
|
|
705
|
+
kind: "trace-validation",
|
|
706
|
+
reason: "trace-accounting-mismatch",
|
|
707
|
+
subReason: "parent-rollup-drift",
|
|
708
|
+
eventIndex: -1,
|
|
709
|
+
childRunId: trace.runId,
|
|
710
|
+
field,
|
|
711
|
+
recorded: parentValue,
|
|
712
|
+
recomputed: childValue
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
for (let eventIndex = 0; eventIndex < trace.events.length; eventIndex += 1) {
|
|
717
|
+
const event = trace.events[eventIndex];
|
|
718
|
+
if (event === void 0 || event.type !== "sub-run-completed") continue;
|
|
719
|
+
const childRecomputed = recomputeAccountingFromTrace(event.subResult.trace);
|
|
720
|
+
const childRecorded = event.subResult.accounting;
|
|
721
|
+
const drift = firstDifferingField(childRecorded, childRecomputed);
|
|
722
|
+
if (drift !== null) throw new DogpileError({
|
|
723
|
+
code: "invalid-configuration",
|
|
724
|
+
message: `Trace accounting mismatch at sub-run ${event.childRunId}: field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed}.`,
|
|
725
|
+
retryable: false,
|
|
726
|
+
detail: {
|
|
727
|
+
kind: "trace-validation",
|
|
728
|
+
reason: "trace-accounting-mismatch",
|
|
729
|
+
eventIndex,
|
|
730
|
+
childRunId: event.childRunId,
|
|
731
|
+
field: drift.field,
|
|
732
|
+
recorded: drift.recorded,
|
|
733
|
+
recomputed: drift.recomputed
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
return local;
|
|
738
|
+
}
|
|
470
739
|
function canonicalizeSerializable(value) {
|
|
471
740
|
if (Array.isArray(value)) return value.map((item) => canonicalizeSerializable(item));
|
|
472
741
|
if (typeof value === "number") {
|
|
@@ -484,7 +753,96 @@ function canonicalizeSerializable(value) {
|
|
|
484
753
|
return output;
|
|
485
754
|
}
|
|
486
755
|
//#endregion
|
|
756
|
+
//#region src/runtime/health.ts
|
|
757
|
+
/**
|
|
758
|
+
* Default health thresholds used for `result.health` auto-computation.
|
|
759
|
+
*
|
|
760
|
+
* Both threshold-gated anomalies (runaway-turns, budget-near-miss) are suppressed
|
|
761
|
+
* by default. Only threshold-free anomalies (empty-contribution) can fire on the
|
|
762
|
+
* auto-compute path.
|
|
763
|
+
*/
|
|
764
|
+
var DEFAULT_HEALTH_THRESHOLDS = Object.freeze({});
|
|
765
|
+
/**
|
|
766
|
+
* Compute a health summary from a completed run trace.
|
|
767
|
+
*
|
|
768
|
+
* Pure function - no side effects, no I/O, no storage access. Deterministic:
|
|
769
|
+
* given the same trace and thresholds, always produces the same result.
|
|
770
|
+
*
|
|
771
|
+
* @param trace - Completed run trace (from RunResult.trace or a stored trace).
|
|
772
|
+
* @param thresholds - Optional threshold overrides. Defaults to DEFAULT_HEALTH_THRESHOLDS.
|
|
773
|
+
*/
|
|
774
|
+
function computeHealth(trace, thresholds = DEFAULT_HEALTH_THRESHOLDS) {
|
|
775
|
+
assertFiniteNonNegativeThreshold(thresholds.runawayTurns, "runawayTurns");
|
|
776
|
+
assertBudgetNearMissThreshold(thresholds.budgetNearMissPct);
|
|
777
|
+
const turnEvents = trace.events.filter((event) => event.type === "agent-turn");
|
|
778
|
+
const agentIds = new Set(turnEvents.map((event) => event.agentId));
|
|
779
|
+
const totalTurns = turnEvents.length;
|
|
780
|
+
const agentCount = agentIds.size;
|
|
781
|
+
const maxUsd = trace.budget.caps?.maxUsd;
|
|
782
|
+
const finalCost = trace.finalOutput.cost.usd;
|
|
783
|
+
const budgetUtilizationPct = maxUsd !== void 0 ? maxUsd === 0 ? finalCost === 0 ? 0 : 100 : finalCost / maxUsd * 100 : null;
|
|
784
|
+
const anomalies = [];
|
|
785
|
+
if (thresholds.runawayTurns !== void 0) for (const agentId of agentIds) {
|
|
786
|
+
const count = turnEvents.filter((event) => event.agentId === agentId).length;
|
|
787
|
+
if (count > thresholds.runawayTurns) anomalies.push({
|
|
788
|
+
code: "runaway-turns",
|
|
789
|
+
severity: "error",
|
|
790
|
+
value: count,
|
|
791
|
+
threshold: thresholds.runawayTurns,
|
|
792
|
+
agentId
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
if (thresholds.budgetNearMissPct !== void 0 && budgetUtilizationPct !== null) {
|
|
796
|
+
if (budgetUtilizationPct >= thresholds.budgetNearMissPct) anomalies.push({
|
|
797
|
+
code: "budget-near-miss",
|
|
798
|
+
severity: "warning",
|
|
799
|
+
value: budgetUtilizationPct,
|
|
800
|
+
threshold: thresholds.budgetNearMissPct
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
for (const event of turnEvents) if (event.output.trim() === "") anomalies.push({
|
|
804
|
+
code: "empty-contribution",
|
|
805
|
+
severity: "error",
|
|
806
|
+
value: 0,
|
|
807
|
+
threshold: 0,
|
|
808
|
+
agentId: event.agentId
|
|
809
|
+
});
|
|
810
|
+
return {
|
|
811
|
+
anomalies,
|
|
812
|
+
stats: {
|
|
813
|
+
totalTurns,
|
|
814
|
+
agentCount,
|
|
815
|
+
budgetUtilizationPct
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function assertFiniteNonNegativeThreshold(value, name) {
|
|
820
|
+
if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw new RangeError(`${name} must be a finite non-negative number`);
|
|
821
|
+
}
|
|
822
|
+
function assertBudgetNearMissThreshold(value) {
|
|
823
|
+
assertFiniteNonNegativeThreshold(value, "budgetNearMissPct");
|
|
824
|
+
if (value !== void 0 && value > 100) throw new RangeError("budgetNearMissPct must be between 0 and 100");
|
|
825
|
+
}
|
|
826
|
+
//#endregion
|
|
487
827
|
//#region src/runtime/cancellation.ts
|
|
828
|
+
/**
|
|
829
|
+
* Classify an abort signal's reason into the BUDGET-01 / BUDGET-02
|
|
830
|
+
* `detail.reason` discriminator.
|
|
831
|
+
*
|
|
832
|
+
* - `"timeout"` when the reason is a {@link DogpileError} with `code === "timeout"`
|
|
833
|
+
* (matches the parent-deadline abort path in `engine.ts:createTimeoutAbortLifecycle`).
|
|
834
|
+
* - `"parent-aborted"` for every other reason — explicit caller abort, plain
|
|
835
|
+
* `Error`, `undefined`, or arbitrary primitive.
|
|
836
|
+
*/
|
|
837
|
+
function classifyAbortReason(signalReasonOrError) {
|
|
838
|
+
if (DogpileError.isInstance(signalReasonOrError) && signalReasonOrError.code === "timeout") return "timeout";
|
|
839
|
+
return "parent-aborted";
|
|
840
|
+
}
|
|
841
|
+
function classifyChildTimeoutSource(_error, context) {
|
|
842
|
+
if (context.isProviderError) return "provider";
|
|
843
|
+
if (context.decisionTimeoutMs !== void 0 || context.engineDefaultTimeoutMs !== void 0) return "engine";
|
|
844
|
+
return "provider";
|
|
845
|
+
}
|
|
488
846
|
function throwIfAborted(signal, providerId) {
|
|
489
847
|
if (!signal?.aborted) return;
|
|
490
848
|
throw createAbortErrorFromSignal(signal, providerId);
|
|
@@ -501,7 +859,7 @@ function createAbortError(providerId, detail, cause) {
|
|
|
501
859
|
}
|
|
502
860
|
function createAbortErrorFromSignal(signal, providerId) {
|
|
503
861
|
if (DogpileError.isInstance(signal.reason)) return signal.reason;
|
|
504
|
-
return createAbortError(providerId,
|
|
862
|
+
return createAbortError(providerId, { reason: classifyAbortReason(signal.reason) }, signal.reason);
|
|
505
863
|
}
|
|
506
864
|
function createTimeoutError(providerId, timeoutMs) {
|
|
507
865
|
return new DogpileError({
|
|
@@ -512,23 +870,223 @@ function createTimeoutError(providerId, timeoutMs) {
|
|
|
512
870
|
detail: { timeoutMs }
|
|
513
871
|
});
|
|
514
872
|
}
|
|
873
|
+
function createEngineDeadlineTimeoutError(providerId, timeoutMs) {
|
|
874
|
+
return new DogpileError({
|
|
875
|
+
code: "provider-timeout",
|
|
876
|
+
message: `The child engine deadline expired after ${timeoutMs}ms.`,
|
|
877
|
+
retryable: true,
|
|
878
|
+
providerId,
|
|
879
|
+
detail: {
|
|
880
|
+
timeoutMs,
|
|
881
|
+
source: "engine"
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
515
885
|
//#endregion
|
|
516
886
|
//#region src/runtime/decisions.ts
|
|
517
|
-
|
|
887
|
+
var PROTOCOL_NAMES = [
|
|
888
|
+
"coordinator",
|
|
889
|
+
"sequential",
|
|
890
|
+
"broadcast",
|
|
891
|
+
"shared"
|
|
892
|
+
];
|
|
893
|
+
function parseAgentDecision(output, context = {}) {
|
|
894
|
+
const delegateBlock = matchDelegateBlock(output);
|
|
895
|
+
if (delegateBlock !== void 0) return parseDelegateDecision(delegateBlock, context);
|
|
896
|
+
return parseParticipateDecision(output);
|
|
897
|
+
}
|
|
898
|
+
function isParticipatingDecision(decision) {
|
|
899
|
+
if (decision === void 0 || isDelegateDecisionArray(decision) || decision.type !== "participate") return false;
|
|
900
|
+
return decision.participation !== "abstain";
|
|
901
|
+
}
|
|
902
|
+
function isDelegateDecisionArray(decision) {
|
|
903
|
+
return Array.isArray(decision);
|
|
904
|
+
}
|
|
905
|
+
function parseParticipateDecision(output) {
|
|
518
906
|
const selectedRole = matchLine(output, /^role_selected:\s*(.+)$/imu);
|
|
519
907
|
const participation = matchLine(output, /^participation:\s*(contribute|abstain)$/imu);
|
|
520
908
|
const rationale = matchLine(output, /^rationale:\s*(.+)$/imu);
|
|
521
909
|
const contribution = matchContribution(output);
|
|
522
910
|
if (!selectedRole || !participation || !isAgentParticipation(participation) || !rationale || !contribution) return;
|
|
523
911
|
return {
|
|
912
|
+
type: "participate",
|
|
524
913
|
selectedRole,
|
|
525
914
|
participation,
|
|
526
915
|
rationale,
|
|
527
916
|
contribution
|
|
528
917
|
};
|
|
529
918
|
}
|
|
530
|
-
|
|
531
|
-
|
|
919
|
+
/**
|
|
920
|
+
* Locate a `delegate:` line followed by a fenced JSON block in the agent's
|
|
921
|
+
* output. Returns the raw JSON text inside the fence, or `undefined` when no
|
|
922
|
+
* delegate block is present. Tolerates ```` ```json ```` and bare ```` ``` ````.
|
|
923
|
+
*/
|
|
924
|
+
function matchDelegateBlock(output) {
|
|
925
|
+
return output.match(/^delegate:\s*\r?\n\s*```(?:json)?\s*\r?\n([\s\S]*?)\r?\n\s*```/imu)?.[1];
|
|
926
|
+
}
|
|
927
|
+
function parseDelegateDecision(jsonText, context) {
|
|
928
|
+
let parsed;
|
|
929
|
+
try {
|
|
930
|
+
parsed = JSON.parse(jsonText);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
throwInvalidDelegate({
|
|
933
|
+
path: "decision",
|
|
934
|
+
message: `delegate JSON did not parse: ${error instanceof Error ? error.message : String(error)}`,
|
|
935
|
+
expected: "valid JSON object",
|
|
936
|
+
received: truncate(jsonText)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
if (Array.isArray(parsed)) {
|
|
940
|
+
if (parsed.length === 0) throwInvalidDelegate({
|
|
941
|
+
path: "decision",
|
|
942
|
+
message: "delegate array must not be empty.",
|
|
943
|
+
expected: "array with 1..8 delegate objects",
|
|
944
|
+
received: "empty array"
|
|
945
|
+
});
|
|
946
|
+
return parsed.map((item) => parseSingleDelegateObject(item, context));
|
|
947
|
+
}
|
|
948
|
+
return parseSingleDelegateObject(parsed, context);
|
|
949
|
+
}
|
|
950
|
+
function parseSingleDelegateObject(parsed, context) {
|
|
951
|
+
if (parsed === null || typeof parsed !== "object") throwInvalidDelegate({
|
|
952
|
+
path: "decision",
|
|
953
|
+
message: "delegate decision must be a JSON object.",
|
|
954
|
+
expected: "object",
|
|
955
|
+
received: describe(parsed)
|
|
956
|
+
});
|
|
957
|
+
const record = parsed;
|
|
958
|
+
const protocol = record["protocol"];
|
|
959
|
+
if (typeof protocol !== "string" || !PROTOCOL_NAMES.includes(protocol)) throwInvalidDelegate({
|
|
960
|
+
path: "decision.protocol",
|
|
961
|
+
message: `protocol "${describe(protocol)}" is not a known coordination protocol.`,
|
|
962
|
+
expected: PROTOCOL_NAMES.join(" | "),
|
|
963
|
+
received: describe(protocol)
|
|
964
|
+
});
|
|
965
|
+
const intentRaw = record["intent"];
|
|
966
|
+
const intent = typeof intentRaw === "string" ? intentRaw.trim() : "";
|
|
967
|
+
if (intent.length === 0) throwInvalidDelegate({
|
|
968
|
+
path: "decision.intent",
|
|
969
|
+
message: "delegate decision must include a non-empty intent string.",
|
|
970
|
+
expected: "non-empty string",
|
|
971
|
+
received: describe(intentRaw)
|
|
972
|
+
});
|
|
973
|
+
const result = {
|
|
974
|
+
type: "delegate",
|
|
975
|
+
protocol,
|
|
976
|
+
intent
|
|
977
|
+
};
|
|
978
|
+
if (record["model"] !== void 0) {
|
|
979
|
+
const model = record["model"];
|
|
980
|
+
if (typeof model !== "string" || model.length === 0) throwInvalidDelegate({
|
|
981
|
+
path: "decision.model",
|
|
982
|
+
message: "delegate decision model must be a non-empty string when present.",
|
|
983
|
+
expected: "non-empty string",
|
|
984
|
+
received: describe(model)
|
|
985
|
+
});
|
|
986
|
+
if (context.parentProviderId !== void 0 && model !== context.parentProviderId) throwInvalidDelegate({
|
|
987
|
+
path: "decision.model",
|
|
988
|
+
message: `delegate decision model "${model}" does not match parent provider id "${context.parentProviderId}".`,
|
|
989
|
+
expected: context.parentProviderId,
|
|
990
|
+
received: model
|
|
991
|
+
});
|
|
992
|
+
result.model = model;
|
|
993
|
+
}
|
|
994
|
+
if (record["budget"] !== void 0) result.budget = parseDelegateBudget(record["budget"]);
|
|
995
|
+
if (record["maxConcurrentChildren"] !== void 0) {
|
|
996
|
+
const value = record["maxConcurrentChildren"];
|
|
997
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throwInvalidDelegate({
|
|
998
|
+
path: "decision.maxConcurrentChildren",
|
|
999
|
+
message: "delegate decision maxConcurrentChildren must be a positive integer when present.",
|
|
1000
|
+
expected: "integer >= 1",
|
|
1001
|
+
received: describe(value)
|
|
1002
|
+
});
|
|
1003
|
+
result.maxConcurrentChildren = value;
|
|
1004
|
+
}
|
|
1005
|
+
if (context.currentDepth !== void 0 && context.maxDepth !== void 0) {
|
|
1006
|
+
if (context.currentDepth + 1 > context.maxDepth) throw depthOverflowError(context.currentDepth, context.maxDepth);
|
|
1007
|
+
}
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Build the canonical depth-overflow `DogpileError`. Used by the parser (this
|
|
1012
|
+
* file) and the coordinator dispatcher; kept here so both call sites produce
|
|
1013
|
+
* the exact same error shape (D-14, D-15).
|
|
1014
|
+
*/
|
|
1015
|
+
function depthOverflowError(currentDepth, maxDepth) {
|
|
1016
|
+
return new DogpileError({
|
|
1017
|
+
code: "invalid-configuration",
|
|
1018
|
+
message: `Depth overflow: cannot dispatch sub-run at depth ${currentDepth + 1} (maxDepth = ${maxDepth}).`,
|
|
1019
|
+
retryable: false,
|
|
1020
|
+
detail: {
|
|
1021
|
+
kind: "delegate-validation",
|
|
1022
|
+
path: "decision.protocol",
|
|
1023
|
+
reason: "depth-overflow",
|
|
1024
|
+
currentDepth,
|
|
1025
|
+
maxDepth
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Dispatcher-time depth gate. Throws the same error shape the parser uses; the
|
|
1031
|
+
* dual gate (parser + dispatcher) defends against any TOCTOU window between
|
|
1032
|
+
* decision parsing and child-run spin-up (D-14).
|
|
1033
|
+
*/
|
|
1034
|
+
function assertDepthWithinLimit(currentDepth, maxDepth) {
|
|
1035
|
+
if (currentDepth + 1 > maxDepth) throw depthOverflowError(currentDepth, maxDepth);
|
|
1036
|
+
}
|
|
1037
|
+
function parseDelegateBudget(raw) {
|
|
1038
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throwInvalidDelegate({
|
|
1039
|
+
path: "decision.budget",
|
|
1040
|
+
message: "delegate decision budget must be an object.",
|
|
1041
|
+
expected: "object",
|
|
1042
|
+
received: describe(raw)
|
|
1043
|
+
});
|
|
1044
|
+
const record = raw;
|
|
1045
|
+
const budget = {};
|
|
1046
|
+
if (record["timeoutMs"] !== void 0) {
|
|
1047
|
+
const value = record["timeoutMs"];
|
|
1048
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
1049
|
+
path: "decision.budget.timeoutMs",
|
|
1050
|
+
message: "delegate decision budget.timeoutMs must be a non-negative integer.",
|
|
1051
|
+
expected: "integer >= 0",
|
|
1052
|
+
received: describe(value)
|
|
1053
|
+
});
|
|
1054
|
+
budget.timeoutMs = value;
|
|
1055
|
+
}
|
|
1056
|
+
if (record["maxTokens"] !== void 0) {
|
|
1057
|
+
const value = record["maxTokens"];
|
|
1058
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
1059
|
+
path: "decision.budget.maxTokens",
|
|
1060
|
+
message: "delegate decision budget.maxTokens must be a non-negative integer.",
|
|
1061
|
+
expected: "integer >= 0",
|
|
1062
|
+
received: describe(value)
|
|
1063
|
+
});
|
|
1064
|
+
budget.maxTokens = value;
|
|
1065
|
+
}
|
|
1066
|
+
if (record["maxIterations"] !== void 0) {
|
|
1067
|
+
const value = record["maxIterations"];
|
|
1068
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
1069
|
+
path: "decision.budget.maxIterations",
|
|
1070
|
+
message: "delegate decision budget.maxIterations must be a non-negative integer.",
|
|
1071
|
+
expected: "integer >= 0",
|
|
1072
|
+
received: describe(value)
|
|
1073
|
+
});
|
|
1074
|
+
budget.maxIterations = value;
|
|
1075
|
+
}
|
|
1076
|
+
return budget;
|
|
1077
|
+
}
|
|
1078
|
+
function throwInvalidDelegate(failure) {
|
|
1079
|
+
throw new DogpileError({
|
|
1080
|
+
code: "invalid-configuration",
|
|
1081
|
+
message: `Invalid Dogpile configuration at ${failure.path}: ${failure.message}`,
|
|
1082
|
+
retryable: false,
|
|
1083
|
+
detail: {
|
|
1084
|
+
kind: "delegate-validation",
|
|
1085
|
+
path: failure.path,
|
|
1086
|
+
expected: failure.expected,
|
|
1087
|
+
received: failure.received
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
532
1090
|
}
|
|
533
1091
|
function matchLine(output, pattern) {
|
|
534
1092
|
return output.match(pattern)?.[1]?.trim();
|
|
@@ -540,16 +1098,38 @@ function matchContribution(output) {
|
|
|
540
1098
|
function isAgentParticipation(value) {
|
|
541
1099
|
return value === "contribute" || value === "abstain";
|
|
542
1100
|
}
|
|
1101
|
+
function describe(value) {
|
|
1102
|
+
if (value === null) return "null";
|
|
1103
|
+
if (Array.isArray(value)) return "array";
|
|
1104
|
+
if (typeof value === "string") return JSON.stringify(value).slice(0, 200);
|
|
1105
|
+
return typeof value;
|
|
1106
|
+
}
|
|
1107
|
+
function truncate(value) {
|
|
1108
|
+
return value.length > 200 ? `${value.slice(0, 200)}…` : value;
|
|
1109
|
+
}
|
|
543
1110
|
//#endregion
|
|
544
1111
|
//#region src/runtime/model.ts
|
|
545
1112
|
async function generateModelTurn(options) {
|
|
546
1113
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1114
|
+
const modelId = options.model.modelId ?? options.model.id;
|
|
1115
|
+
const traceRequest = requestForTrace(options.request);
|
|
547
1116
|
let response;
|
|
548
1117
|
throwIfAborted(options.request.signal, options.model.id);
|
|
1118
|
+
options.emit({
|
|
1119
|
+
type: "model-request",
|
|
1120
|
+
runId: options.runId,
|
|
1121
|
+
callId: options.callId,
|
|
1122
|
+
providerId: options.model.id,
|
|
1123
|
+
modelId,
|
|
1124
|
+
startedAt,
|
|
1125
|
+
agentId: options.agent.id,
|
|
1126
|
+
role: options.agent.role,
|
|
1127
|
+
request: traceRequest
|
|
1128
|
+
});
|
|
549
1129
|
if (!options.model.stream) {
|
|
550
1130
|
response = await options.model.generate(options.request);
|
|
551
1131
|
throwIfAborted(options.request.signal, options.model.id);
|
|
552
|
-
recordProviderCall(response, startedAt, options);
|
|
1132
|
+
recordProviderCall(response, startedAt, modelId, traceRequest, options);
|
|
553
1133
|
return response;
|
|
554
1134
|
}
|
|
555
1135
|
let text = "";
|
|
@@ -589,27 +1169,41 @@ async function generateModelTurn(options) {
|
|
|
589
1169
|
...metadata !== void 0 ? { metadata } : {}
|
|
590
1170
|
};
|
|
591
1171
|
throwIfAborted(options.request.signal, options.model.id);
|
|
592
|
-
recordProviderCall(response, startedAt, options);
|
|
1172
|
+
recordProviderCall(response, startedAt, modelId, traceRequest, options);
|
|
593
1173
|
return response;
|
|
594
1174
|
}
|
|
595
|
-
function recordProviderCall(response, startedAt, options) {
|
|
1175
|
+
function recordProviderCall(response, startedAt, modelId, request, options) {
|
|
1176
|
+
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1177
|
+
options.emit({
|
|
1178
|
+
type: "model-response",
|
|
1179
|
+
runId: options.runId,
|
|
1180
|
+
callId: options.callId,
|
|
1181
|
+
providerId: options.model.id,
|
|
1182
|
+
modelId,
|
|
1183
|
+
startedAt,
|
|
1184
|
+
completedAt,
|
|
1185
|
+
agentId: options.agent.id,
|
|
1186
|
+
role: options.agent.role,
|
|
1187
|
+
response
|
|
1188
|
+
});
|
|
596
1189
|
options.onProviderCall?.({
|
|
597
1190
|
kind: "replay-trace-provider-call",
|
|
598
1191
|
callId: options.callId,
|
|
599
1192
|
providerId: options.model.id,
|
|
1193
|
+
modelId,
|
|
600
1194
|
startedAt,
|
|
601
|
-
completedAt
|
|
1195
|
+
completedAt,
|
|
602
1196
|
agentId: options.agent.id,
|
|
603
1197
|
role: options.agent.role,
|
|
604
|
-
request
|
|
1198
|
+
request,
|
|
605
1199
|
response
|
|
606
1200
|
});
|
|
607
1201
|
}
|
|
608
1202
|
function requestForTrace(request) {
|
|
609
1203
|
return {
|
|
610
|
-
messages: request.messages,
|
|
1204
|
+
messages: request.messages.map((message) => ({ ...message })),
|
|
611
1205
|
temperature: request.temperature,
|
|
612
|
-
metadata: request.metadata
|
|
1206
|
+
metadata: JSON.parse(JSON.stringify(request.metadata))
|
|
613
1207
|
};
|
|
614
1208
|
}
|
|
615
1209
|
//#endregion
|
|
@@ -997,6 +1591,7 @@ var budgetTiers = [
|
|
|
997
1591
|
"balanced",
|
|
998
1592
|
"quality"
|
|
999
1593
|
];
|
|
1594
|
+
var onChildFailureModes = ["continue", "abort"];
|
|
1000
1595
|
/**
|
|
1001
1596
|
* Validate high-level caller options before any protocol execution starts.
|
|
1002
1597
|
*/
|
|
@@ -1015,11 +1610,25 @@ function validateDogpileOptions(options) {
|
|
|
1015
1610
|
validateOptionalFunction(options.evaluate, "evaluate");
|
|
1016
1611
|
validateOptionalSeed(options.seed, "seed");
|
|
1017
1612
|
validateOptionalAbortSignal(options.signal, "signal");
|
|
1613
|
+
validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
|
|
1614
|
+
validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
|
|
1615
|
+
validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
|
|
1616
|
+
validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
|
|
1018
1617
|
}
|
|
1019
1618
|
function validateMissionIntent(intent, path = "intent") {
|
|
1020
1619
|
validateNonEmptyString(intent, path, "intent is required.");
|
|
1021
1620
|
}
|
|
1022
1621
|
/**
|
|
1622
|
+
* Validate per-call run/stream options (`Engine.run(intent, options)` / `Engine.stream(...)`).
|
|
1623
|
+
*/
|
|
1624
|
+
function validateRunCallOptions(options, path = "options") {
|
|
1625
|
+
if (options === void 0) return;
|
|
1626
|
+
const record = requireRecord(options, path);
|
|
1627
|
+
validateOptionalNonNegativeInteger(record.maxDepth, `${path}.maxDepth`);
|
|
1628
|
+
validateOptionalPositiveInteger(record.maxConcurrentChildren, `${path}.maxConcurrentChildren`);
|
|
1629
|
+
validateOptionalOnChildFailure(record.onChildFailure, `${path}.onChildFailure`);
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1023
1632
|
* Validate low-level engine configuration before normalizing reusable controls.
|
|
1024
1633
|
*/
|
|
1025
1634
|
function validateEngineOptions(options) {
|
|
@@ -1036,6 +1645,10 @@ function validateEngineOptions(options) {
|
|
|
1036
1645
|
validateOptionalFunction(options.evaluate, "evaluate");
|
|
1037
1646
|
validateOptionalSeed(options.seed, "seed");
|
|
1038
1647
|
validateOptionalAbortSignal(options.signal, "signal");
|
|
1648
|
+
validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
|
|
1649
|
+
validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
|
|
1650
|
+
validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
|
|
1651
|
+
validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
|
|
1039
1652
|
}
|
|
1040
1653
|
function validateProtocolSelection(value, path) {
|
|
1041
1654
|
if (typeof value === "string") {
|
|
@@ -1083,6 +1696,23 @@ function validateBudgetTier(value, path) {
|
|
|
1083
1696
|
actual: value
|
|
1084
1697
|
});
|
|
1085
1698
|
}
|
|
1699
|
+
function validateOptionalOnChildFailure(value, path) {
|
|
1700
|
+
if (value === void 0) return;
|
|
1701
|
+
if (value === "continue" || value === "abort") return;
|
|
1702
|
+
throw new DogpileError({
|
|
1703
|
+
code: "invalid-configuration",
|
|
1704
|
+
message: `Invalid onChildFailure: expected "continue" or "abort", got ${JSON.stringify(value)}`,
|
|
1705
|
+
retryable: false,
|
|
1706
|
+
detail: {
|
|
1707
|
+
kind: "configuration-validation",
|
|
1708
|
+
path,
|
|
1709
|
+
rule: "enum",
|
|
1710
|
+
expected: onChildFailureModes.join(" | "),
|
|
1711
|
+
received: describeValue(value),
|
|
1712
|
+
reason: "invalid-on-child-failure"
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1086
1716
|
/**
|
|
1087
1717
|
* Validate configured model provider definitions at registration boundaries.
|
|
1088
1718
|
*/
|
|
@@ -1092,6 +1722,21 @@ function validateModelProviderRegistration(value, path = "model") {
|
|
|
1092
1722
|
validateFunction(record.generate, `${path}.generate`);
|
|
1093
1723
|
validateOptionalFunction(record.stream, `${path}.stream`);
|
|
1094
1724
|
}
|
|
1725
|
+
/**
|
|
1726
|
+
* Engine-time defense-in-depth check that a provider's optional
|
|
1727
|
+
* `metadata.locality` is one of the two valid values when present (Phase 3 D-03).
|
|
1728
|
+
* Catches user-implemented providers that bypass TypeScript checks.
|
|
1729
|
+
*/
|
|
1730
|
+
function validateProviderLocality(provider, pathPrefix = "model") {
|
|
1731
|
+
const loc = provider.metadata?.locality;
|
|
1732
|
+
if (loc !== void 0 && loc !== "local" && loc !== "remote") invalidConfiguration({
|
|
1733
|
+
path: `${pathPrefix}.metadata.locality`,
|
|
1734
|
+
rule: "enum",
|
|
1735
|
+
message: `${pathPrefix}.metadata.locality must be "local" or "remote" when provided.`,
|
|
1736
|
+
expected: "\"local\" | \"remote\"",
|
|
1737
|
+
actual: loc
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1095
1740
|
function validateOptionalAgents(value, path) {
|
|
1096
1741
|
if (value === void 0) return;
|
|
1097
1742
|
if (!Array.isArray(value)) invalidConfiguration({
|
|
@@ -1371,6 +2016,16 @@ function validateOptionalNonNegativeInteger(value, path) {
|
|
|
1371
2016
|
actual: value
|
|
1372
2017
|
});
|
|
1373
2018
|
}
|
|
2019
|
+
function validateOptionalPositiveFiniteNumber(value, path) {
|
|
2020
|
+
if (value === void 0) return;
|
|
2021
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) invalidConfiguration({
|
|
2022
|
+
path,
|
|
2023
|
+
rule: "positive-finite-number",
|
|
2024
|
+
message: "value must be a positive finite number.",
|
|
2025
|
+
expected: "finite number > 0",
|
|
2026
|
+
actual: value
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
1374
2029
|
function validateOptionalNonNegativeNumber(value, path) {
|
|
1375
2030
|
if (value === void 0) return;
|
|
1376
2031
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) invalidConfiguration({
|
|
@@ -2491,44 +3146,45 @@ async function runBroadcast(options) {
|
|
|
2491
3146
|
emit(final);
|
|
2492
3147
|
recordProtocolDecision(final, { transcriptEntryCount: transcript.length });
|
|
2493
3148
|
const finalEvent = events.at(-1);
|
|
3149
|
+
const trace = {
|
|
3150
|
+
schemaVersion: "1.0",
|
|
3151
|
+
runId,
|
|
3152
|
+
protocol: "broadcast",
|
|
3153
|
+
tier: options.tier,
|
|
3154
|
+
modelProviderId: options.model.id,
|
|
3155
|
+
agentsUsed: options.agents,
|
|
3156
|
+
inputs: createReplayTraceRunInputs({
|
|
3157
|
+
intent: options.intent,
|
|
3158
|
+
protocol: options.protocol,
|
|
3159
|
+
tier: options.tier,
|
|
3160
|
+
modelProviderId: options.model.id,
|
|
3161
|
+
agents: options.agents,
|
|
3162
|
+
temperature: options.temperature
|
|
3163
|
+
}),
|
|
3164
|
+
budget: createReplayTraceBudget({
|
|
3165
|
+
tier: options.tier,
|
|
3166
|
+
...options.budget ? { caps: options.budget } : {},
|
|
3167
|
+
...options.terminate ? { termination: options.terminate } : {}
|
|
3168
|
+
}),
|
|
3169
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
3170
|
+
seed: createReplayTraceSeed(options.seed),
|
|
3171
|
+
protocolDecisions,
|
|
3172
|
+
providerCalls,
|
|
3173
|
+
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
|
|
3174
|
+
type: "final",
|
|
3175
|
+
runId,
|
|
3176
|
+
at: "",
|
|
3177
|
+
output,
|
|
3178
|
+
cost: totalCost,
|
|
3179
|
+
transcript: createTranscriptLink(transcript)
|
|
3180
|
+
}),
|
|
3181
|
+
events,
|
|
3182
|
+
transcript
|
|
3183
|
+
};
|
|
2494
3184
|
return {
|
|
2495
3185
|
output,
|
|
2496
3186
|
eventLog: createRunEventLog(runId, "broadcast", events),
|
|
2497
|
-
trace
|
|
2498
|
-
schemaVersion: "1.0",
|
|
2499
|
-
runId,
|
|
2500
|
-
protocol: "broadcast",
|
|
2501
|
-
tier: options.tier,
|
|
2502
|
-
modelProviderId: options.model.id,
|
|
2503
|
-
agentsUsed: options.agents,
|
|
2504
|
-
inputs: createReplayTraceRunInputs({
|
|
2505
|
-
intent: options.intent,
|
|
2506
|
-
protocol: options.protocol,
|
|
2507
|
-
tier: options.tier,
|
|
2508
|
-
modelProviderId: options.model.id,
|
|
2509
|
-
agents: options.agents,
|
|
2510
|
-
temperature: options.temperature
|
|
2511
|
-
}),
|
|
2512
|
-
budget: createReplayTraceBudget({
|
|
2513
|
-
tier: options.tier,
|
|
2514
|
-
...options.budget ? { caps: options.budget } : {},
|
|
2515
|
-
...options.terminate ? { termination: options.terminate } : {}
|
|
2516
|
-
}),
|
|
2517
|
-
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
2518
|
-
seed: createReplayTraceSeed(options.seed),
|
|
2519
|
-
protocolDecisions,
|
|
2520
|
-
providerCalls,
|
|
2521
|
-
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
|
|
2522
|
-
type: "final",
|
|
2523
|
-
runId,
|
|
2524
|
-
at: "",
|
|
2525
|
-
output,
|
|
2526
|
-
cost: totalCost,
|
|
2527
|
-
transcript: createTranscriptLink(transcript)
|
|
2528
|
-
}),
|
|
2529
|
-
events,
|
|
2530
|
-
transcript
|
|
2531
|
-
},
|
|
3187
|
+
trace,
|
|
2532
3188
|
transcript,
|
|
2533
3189
|
usage: createRunUsage(totalCost),
|
|
2534
3190
|
metadata: createRunMetadata({
|
|
@@ -2546,7 +3202,8 @@ async function runBroadcast(options) {
|
|
|
2546
3202
|
cost: totalCost,
|
|
2547
3203
|
events
|
|
2548
3204
|
}),
|
|
2549
|
-
cost: totalCost
|
|
3205
|
+
cost: totalCost,
|
|
3206
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
2550
3207
|
};
|
|
2551
3208
|
function stopIfNeeded() {
|
|
2552
3209
|
throwIfAborted(options.signal, options.model.id);
|
|
@@ -2608,22 +3265,77 @@ function responseCost$3(response) {
|
|
|
2608
3265
|
}
|
|
2609
3266
|
//#endregion
|
|
2610
3267
|
//#region src/runtime/coordinator.ts
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
const
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
3268
|
+
/**
|
|
3269
|
+
* Hard-coded loop guard for the delegate dispatch in the coordinator plan
|
|
3270
|
+
* turn. After this many consecutive delegate decisions the coordinator throws
|
|
3271
|
+
* `invalid-configuration` (T-03-01). Not a public option.
|
|
3272
|
+
*/
|
|
3273
|
+
var MAX_DISPATCH_PER_TURN = 8;
|
|
3274
|
+
var DEFAULT_MAX_CONCURRENT_CHILDREN$1 = 4;
|
|
3275
|
+
function createSemaphore(maxConcurrent) {
|
|
3276
|
+
let inFlight = 0;
|
|
3277
|
+
const waiters = [];
|
|
3278
|
+
return {
|
|
3279
|
+
acquire() {
|
|
3280
|
+
if (inFlight < maxConcurrent) {
|
|
3281
|
+
inFlight += 1;
|
|
3282
|
+
return Promise.resolve();
|
|
3283
|
+
}
|
|
3284
|
+
return new Promise((resolve) => {
|
|
3285
|
+
waiters.push(() => {
|
|
3286
|
+
inFlight += 1;
|
|
3287
|
+
resolve();
|
|
3288
|
+
});
|
|
3289
|
+
});
|
|
3290
|
+
},
|
|
3291
|
+
release() {
|
|
3292
|
+
inFlight -= 1;
|
|
3293
|
+
const next = waiters.shift();
|
|
3294
|
+
if (next !== void 0) next();
|
|
3295
|
+
},
|
|
3296
|
+
get inFlight() {
|
|
3297
|
+
return inFlight;
|
|
3298
|
+
},
|
|
3299
|
+
get queued() {
|
|
3300
|
+
return waiters.length;
|
|
3301
|
+
}
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Walk the coordinator's active provider set and return the FIRST provider
|
|
3306
|
+
* whose metadata.locality === "local", or undefined if none found.
|
|
3307
|
+
*
|
|
3308
|
+
* Walk order (forward-compat): options.model first, then options.agents in
|
|
3309
|
+
* declaration order. AgentSpec has no `model` field today (Phase 3 D-11
|
|
3310
|
+
* forward-compat scaffolding); the agent walk uses optional chaining and
|
|
3311
|
+
* effectively no-ops until a future phase adds AgentSpec.model.
|
|
3312
|
+
*/
|
|
3313
|
+
function findFirstLocalProvider(options) {
|
|
3314
|
+
if (options.model.metadata?.locality === "local") return options.model;
|
|
3315
|
+
for (const agent of options.agents) {
|
|
3316
|
+
const agentModel = agent.model;
|
|
3317
|
+
if (agentModel?.metadata?.locality === "local") return agentModel;
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
async function runCoordinator(options) {
|
|
3321
|
+
const runId = createRunId();
|
|
3322
|
+
const events = [];
|
|
3323
|
+
const transcript = [];
|
|
3324
|
+
const protocolDecisions = [];
|
|
3325
|
+
const providerCalls = [];
|
|
3326
|
+
const dispatchedChildren = /* @__PURE__ */ new Map();
|
|
3327
|
+
let totalCost = emptyCost();
|
|
3328
|
+
let concurrencyClampEmitted = false;
|
|
3329
|
+
const maxTurns = options.protocol.maxTurns ?? options.agents.length;
|
|
3330
|
+
const activeAgents = options.agents.slice(0, maxTurns);
|
|
3331
|
+
const coordinator = activeAgents[0];
|
|
3332
|
+
const startedAtMs = nowMs();
|
|
3333
|
+
let stopped = false;
|
|
3334
|
+
let termination;
|
|
3335
|
+
let triggeringFailureForAbortMode;
|
|
3336
|
+
const wrapUpHint = createWrapUpHintController({
|
|
3337
|
+
protocol: options.protocol,
|
|
3338
|
+
tier: options.tier,
|
|
2627
3339
|
...options.budget ? { budget: options.budget } : {},
|
|
2628
3340
|
...options.terminate ? { terminate: options.terminate } : {},
|
|
2629
3341
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}
|
|
@@ -2636,6 +3348,51 @@ async function runCoordinator(options) {
|
|
|
2636
3348
|
const recordProtocolDecision = (event, decisionOptions) => {
|
|
2637
3349
|
protocolDecisions.push(createReplayTraceProtocolDecision("coordinator", event, events.length - 1, decisionOptions));
|
|
2638
3350
|
};
|
|
3351
|
+
const drainOnParentAbort = (reasonSource) => {
|
|
3352
|
+
const reason = classifyAbortReason(reasonSource);
|
|
3353
|
+
for (const child of dispatchedChildren.values()) {
|
|
3354
|
+
if (child.closed) continue;
|
|
3355
|
+
const partialCost = child.started ? lastCostBearingEventCost(child.childEvents) ?? emptyCost() : emptyCost();
|
|
3356
|
+
const partialTrace = buildPartialTrace({
|
|
3357
|
+
childRunId: child.childRunId,
|
|
3358
|
+
events: [...child.childEvents],
|
|
3359
|
+
startedAtMs: child.startedAtMs,
|
|
3360
|
+
protocol: child.decision.protocol,
|
|
3361
|
+
tier: options.tier,
|
|
3362
|
+
modelProviderId: options.model.id,
|
|
3363
|
+
agents: options.agents,
|
|
3364
|
+
intent: child.decision.intent,
|
|
3365
|
+
temperature: options.temperature,
|
|
3366
|
+
...child.childTimeoutMs !== void 0 ? { childTimeoutMs: child.childTimeoutMs } : {},
|
|
3367
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
3368
|
+
});
|
|
3369
|
+
const failedEvent = {
|
|
3370
|
+
type: "sub-run-failed",
|
|
3371
|
+
runId,
|
|
3372
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3373
|
+
childRunId: child.childRunId,
|
|
3374
|
+
parentRunId: runId,
|
|
3375
|
+
parentDecisionId: child.parentDecisionId,
|
|
3376
|
+
parentDecisionArrayIndex: child.parentDecisionArrayIndex,
|
|
3377
|
+
error: child.started ? {
|
|
3378
|
+
code: "aborted",
|
|
3379
|
+
message: "Parent run aborted.",
|
|
3380
|
+
detail: { reason }
|
|
3381
|
+
} : {
|
|
3382
|
+
code: "aborted",
|
|
3383
|
+
message: "Sibling delegate failed; queued delegate never started.",
|
|
3384
|
+
detail: { reason: "sibling-failed" }
|
|
3385
|
+
},
|
|
3386
|
+
partialTrace,
|
|
3387
|
+
partialCost
|
|
3388
|
+
};
|
|
3389
|
+
child.closed = true;
|
|
3390
|
+
totalCost = addCost(totalCost, partialCost);
|
|
3391
|
+
emit(failedEvent);
|
|
3392
|
+
recordProtocolDecision(failedEvent);
|
|
3393
|
+
}
|
|
3394
|
+
};
|
|
3395
|
+
options.registerAbortDrain?.(drainOnParentAbort);
|
|
2639
3396
|
const toolExecutor = createRuntimeToolExecutor({
|
|
2640
3397
|
runId,
|
|
2641
3398
|
protocol: "coordinator",
|
|
@@ -2666,24 +3423,231 @@ async function runCoordinator(options) {
|
|
|
2666
3423
|
}
|
|
2667
3424
|
if (coordinator) {
|
|
2668
3425
|
if (!stopIfNeeded()) {
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
3426
|
+
let dispatchInput = buildCoordinatorPlanInput(options.intent, coordinator);
|
|
3427
|
+
let dispatchCount = 0;
|
|
3428
|
+
while (true) {
|
|
3429
|
+
const turnOutcome = await runCoordinatorTurn({
|
|
3430
|
+
agent: coordinator,
|
|
3431
|
+
coordinator,
|
|
3432
|
+
input: dispatchInput,
|
|
3433
|
+
phase: "plan",
|
|
3434
|
+
options,
|
|
3435
|
+
runId,
|
|
3436
|
+
transcript,
|
|
3437
|
+
totalCost,
|
|
3438
|
+
providerCalls,
|
|
3439
|
+
toolExecutor,
|
|
3440
|
+
toolAvailability,
|
|
3441
|
+
events,
|
|
3442
|
+
startedAtMs,
|
|
3443
|
+
wrapUpHint,
|
|
3444
|
+
emit,
|
|
3445
|
+
recordProtocolDecision
|
|
3446
|
+
});
|
|
3447
|
+
totalCost = turnOutcome.totalCost;
|
|
3448
|
+
if (turnOutcome.decision === void 0) break;
|
|
3449
|
+
const delegates = Array.isArray(turnOutcome.decision) ? turnOutcome.decision : turnOutcome.decision.type === "delegate" ? [turnOutcome.decision] : [];
|
|
3450
|
+
if (delegates.length === 0) break;
|
|
3451
|
+
if (dispatchCount + delegates.length > MAX_DISPATCH_PER_TURN) throw new DogpileError({
|
|
3452
|
+
code: "invalid-configuration",
|
|
3453
|
+
message: `Coordinator plan turn delegated ${delegates.length} more children after ${dispatchCount}; max is ${MAX_DISPATCH_PER_TURN}.`,
|
|
3454
|
+
retryable: false,
|
|
3455
|
+
detail: {
|
|
3456
|
+
kind: "delegate-validation",
|
|
3457
|
+
path: "decision",
|
|
3458
|
+
reason: "loop-guard-exceeded",
|
|
3459
|
+
maxDispatchPerTurn: MAX_DISPATCH_PER_TURN
|
|
3460
|
+
}
|
|
3461
|
+
});
|
|
3462
|
+
const parentDecisionId = String(events.length - 1);
|
|
3463
|
+
const parentDepth = options.currentDepth ?? 0;
|
|
3464
|
+
const decisionMax = delegates.reduce((max, delegate) => Math.min(max, delegate.maxConcurrentChildren ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
3465
|
+
let effectiveForTurn = Math.min(options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN$1, decisionMax);
|
|
3466
|
+
const requestedMax = effectiveForTurn;
|
|
3467
|
+
const localProvider = findFirstLocalProvider(options);
|
|
3468
|
+
if (localProvider !== void 0) {
|
|
3469
|
+
effectiveForTurn = 1;
|
|
3470
|
+
if (!concurrencyClampEmitted) {
|
|
3471
|
+
const clampEvent = {
|
|
3472
|
+
type: "sub-run-concurrency-clamped",
|
|
3473
|
+
runId,
|
|
3474
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3475
|
+
requestedMax,
|
|
3476
|
+
effectiveMax: 1,
|
|
3477
|
+
reason: "local-provider-detected",
|
|
3478
|
+
providerId: localProvider.id
|
|
3479
|
+
};
|
|
3480
|
+
emit(clampEvent);
|
|
3481
|
+
recordProtocolDecision(clampEvent);
|
|
3482
|
+
concurrencyClampEmitted = true;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
const semaphore = createSemaphore(effectiveForTurn);
|
|
3486
|
+
const childRunIds = delegates.map(() => createRunId());
|
|
3487
|
+
const dispatchedForTurn = delegates.map((delegate, index) => {
|
|
3488
|
+
const childRunId = childRunIds[index];
|
|
3489
|
+
if (childRunId === void 0) throw new Error("missing child run id");
|
|
3490
|
+
const dispatchedChild = {
|
|
3491
|
+
childRunId,
|
|
3492
|
+
decision: delegate,
|
|
3493
|
+
parentDecisionId,
|
|
3494
|
+
parentDecisionArrayIndex: index,
|
|
3495
|
+
parentDepth,
|
|
3496
|
+
controller: new AbortController(),
|
|
3497
|
+
removeParentListener: void 0,
|
|
3498
|
+
childEvents: [],
|
|
3499
|
+
started: false,
|
|
3500
|
+
closed: false,
|
|
3501
|
+
startedAtMs: Date.now(),
|
|
3502
|
+
childTimeoutMs: void 0,
|
|
3503
|
+
failure: void 0
|
|
3504
|
+
};
|
|
3505
|
+
dispatchedChildren.set(childRunId, dispatchedChild);
|
|
3506
|
+
return dispatchedChild;
|
|
3507
|
+
});
|
|
3508
|
+
const dispatchResults = [];
|
|
3509
|
+
let firstFailureIndex;
|
|
3510
|
+
const tasks = delegates.map(async (delegate, index) => {
|
|
3511
|
+
const childRunId = childRunIds[index];
|
|
3512
|
+
if (childRunId === void 0) throw new Error("missing child run id");
|
|
3513
|
+
if (semaphore.inFlight >= effectiveForTurn) {
|
|
3514
|
+
const queuedEvent = {
|
|
3515
|
+
type: "sub-run-queued",
|
|
3516
|
+
runId,
|
|
3517
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3518
|
+
childRunId,
|
|
3519
|
+
parentRunId: runId,
|
|
3520
|
+
parentDecisionId,
|
|
3521
|
+
parentDecisionArrayIndex: index,
|
|
3522
|
+
protocol: delegate.protocol,
|
|
3523
|
+
intent: delegate.intent,
|
|
3524
|
+
depth: parentDepth + 1,
|
|
3525
|
+
queuePosition: semaphore.queued
|
|
3526
|
+
};
|
|
3527
|
+
emit(queuedEvent);
|
|
3528
|
+
recordProtocolDecision(queuedEvent);
|
|
3529
|
+
}
|
|
3530
|
+
await semaphore.acquire();
|
|
3531
|
+
try {
|
|
3532
|
+
const dispatchedChild = dispatchedForTurn[index];
|
|
3533
|
+
if (!dispatchedChild) throw new Error("missing dispatched child");
|
|
3534
|
+
if (firstFailureIndex !== void 0) {
|
|
3535
|
+
if (dispatchedChild.closed) {
|
|
3536
|
+
dispatchResults.push({
|
|
3537
|
+
index,
|
|
3538
|
+
result: {
|
|
3539
|
+
nextInput: "",
|
|
3540
|
+
taggedText: `[sub-run ${childRunId}]: skipped because the parent run aborted`,
|
|
3541
|
+
completedAtMs: Date.now()
|
|
3542
|
+
}
|
|
3543
|
+
});
|
|
3544
|
+
return;
|
|
3545
|
+
}
|
|
3546
|
+
const partialCost = emptyCost();
|
|
3547
|
+
const partialTrace = buildPartialTrace({
|
|
3548
|
+
childRunId,
|
|
3549
|
+
events: [],
|
|
3550
|
+
startedAtMs: Date.now(),
|
|
3551
|
+
protocol: delegate.protocol,
|
|
3552
|
+
tier: options.tier,
|
|
3553
|
+
modelProviderId: options.model.id,
|
|
3554
|
+
agents: options.agents,
|
|
3555
|
+
intent: delegate.intent,
|
|
3556
|
+
temperature: options.temperature,
|
|
3557
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
3558
|
+
});
|
|
3559
|
+
const failedEvent = {
|
|
3560
|
+
type: "sub-run-failed",
|
|
3561
|
+
runId,
|
|
3562
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3563
|
+
childRunId,
|
|
3564
|
+
parentRunId: runId,
|
|
3565
|
+
parentDecisionId,
|
|
3566
|
+
parentDecisionArrayIndex: index,
|
|
3567
|
+
error: {
|
|
3568
|
+
code: "aborted",
|
|
3569
|
+
message: "Sibling delegate failed; queued delegate never started.",
|
|
3570
|
+
detail: { reason: "sibling-failed" }
|
|
3571
|
+
},
|
|
3572
|
+
partialTrace,
|
|
3573
|
+
partialCost
|
|
3574
|
+
};
|
|
3575
|
+
emit(failedEvent);
|
|
3576
|
+
recordProtocolDecision(failedEvent);
|
|
3577
|
+
dispatchedChild.closed = true;
|
|
3578
|
+
dispatchResults.push({
|
|
3579
|
+
index,
|
|
3580
|
+
result: {
|
|
3581
|
+
nextInput: "",
|
|
3582
|
+
taggedText: `[sub-run ${childRunId}]: skipped because a sibling delegate failed`,
|
|
3583
|
+
completedAtMs: Date.now()
|
|
3584
|
+
}
|
|
3585
|
+
});
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
const result = await dispatchDelegate({
|
|
3589
|
+
decision: delegate,
|
|
3590
|
+
childRunId,
|
|
3591
|
+
parentDecisionId,
|
|
3592
|
+
parentDecisionArrayIndex: index,
|
|
3593
|
+
parentDepth,
|
|
3594
|
+
parentRunId: runId,
|
|
3595
|
+
options,
|
|
3596
|
+
transcript,
|
|
3597
|
+
emit,
|
|
3598
|
+
recordProtocolDecision,
|
|
3599
|
+
recordSubRunCost: (cost) => {
|
|
3600
|
+
totalCost = addCost(totalCost, cost);
|
|
3601
|
+
},
|
|
3602
|
+
dispatchedChild
|
|
3603
|
+
});
|
|
3604
|
+
dispatchResults.push({
|
|
3605
|
+
index,
|
|
3606
|
+
result
|
|
3607
|
+
});
|
|
3608
|
+
} catch (error) {
|
|
3609
|
+
firstFailureIndex ??= index;
|
|
3610
|
+
const failure = dispatchedForTurn[index]?.failure;
|
|
3611
|
+
if (delegates.length === 1 && (options.onChildFailure === "abort" || failure === void 0 || isDelegateValidationError(error))) throw error;
|
|
3612
|
+
let taggedText = `[sub-run ${childRunId} failed]: ${error instanceof Error ? error.message : String(error)}`;
|
|
3613
|
+
if (failure) {
|
|
3614
|
+
const error = failure.error;
|
|
3615
|
+
taggedText = `[sub-run ${childRunId} failed | code=${error.code} | spent=$${failure.partialCost.usd.toFixed(3)}]: ${error.message}`;
|
|
3616
|
+
}
|
|
3617
|
+
dispatchResults.push({
|
|
3618
|
+
index,
|
|
3619
|
+
result: {
|
|
3620
|
+
nextInput: "",
|
|
3621
|
+
taggedText,
|
|
3622
|
+
completedAtMs: Date.now()
|
|
3623
|
+
}
|
|
3624
|
+
});
|
|
3625
|
+
} finally {
|
|
3626
|
+
semaphore.release();
|
|
3627
|
+
}
|
|
3628
|
+
});
|
|
3629
|
+
const firstRejected = (await Promise.allSettled(tasks)).find((result) => result.status === "rejected");
|
|
3630
|
+
if (firstRejected?.status === "rejected" && delegates.length === 1 && (options.onChildFailure === "abort" || dispatchResults.length === 0)) throw firstRejected.reason;
|
|
3631
|
+
dispatchResults.sort((a, b) => a.result.completedAtMs - b.result.completedAtMs);
|
|
3632
|
+
const taggedResults = dispatchResults.map((entry) => entry.result.taggedText).join("\n\n");
|
|
3633
|
+
const currentWaveFailures = dispatchedForTurn.map((child) => child.failure).filter((failure) => failure !== void 0);
|
|
3634
|
+
if (options.onChildFailure === "abort" && currentWaveFailures.length > 0) {
|
|
3635
|
+
triggeringFailureForAbortMode ??= currentWaveFailures[0];
|
|
3636
|
+
break;
|
|
3637
|
+
}
|
|
3638
|
+
const failuresSection = buildFailuresSection(currentWaveFailures);
|
|
3639
|
+
const coordinatorAgent = options.agents[0] ?? {
|
|
3640
|
+
id: "coordinator",
|
|
3641
|
+
role: "coordinator"
|
|
3642
|
+
};
|
|
3643
|
+
dispatchInput = [
|
|
3644
|
+
buildCoordinatorPlanInput(options.intent, coordinatorAgent),
|
|
3645
|
+
taggedResults,
|
|
3646
|
+
failuresSection,
|
|
3647
|
+
"Using the sub-run results above, decide the next step (participate or delegate)."
|
|
3648
|
+
].filter((section) => Boolean(section)).join("\n\n");
|
|
3649
|
+
dispatchCount += delegates.length;
|
|
3650
|
+
}
|
|
2687
3651
|
stopIfNeeded();
|
|
2688
3652
|
}
|
|
2689
3653
|
if (!stopIfNeeded()) {
|
|
@@ -2741,7 +3705,7 @@ async function runCoordinator(options) {
|
|
|
2741
3705
|
stopIfNeeded();
|
|
2742
3706
|
}
|
|
2743
3707
|
if (!stopIfNeeded()) {
|
|
2744
|
-
|
|
3708
|
+
const synthesisOutcome = await runCoordinatorTurn({
|
|
2745
3709
|
agent: coordinator,
|
|
2746
3710
|
coordinator,
|
|
2747
3711
|
input: buildFinalSynthesisInput(options.intent, transcript, coordinator),
|
|
@@ -2759,6 +3723,17 @@ async function runCoordinator(options) {
|
|
|
2759
3723
|
emit,
|
|
2760
3724
|
recordProtocolDecision
|
|
2761
3725
|
});
|
|
3726
|
+
totalCost = synthesisOutcome.totalCost;
|
|
3727
|
+
if (Array.isArray(synthesisOutcome.decision) || synthesisOutcome.decision?.type === "delegate") throw new DogpileError({
|
|
3728
|
+
code: "invalid-configuration",
|
|
3729
|
+
message: "Coordinator final-synthesis turn cannot emit a delegate decision in Phase 1",
|
|
3730
|
+
retryable: false,
|
|
3731
|
+
detail: {
|
|
3732
|
+
kind: "delegate-validation",
|
|
3733
|
+
path: "decision",
|
|
3734
|
+
phase: "final-synthesis"
|
|
3735
|
+
}
|
|
3736
|
+
});
|
|
2762
3737
|
stopIfNeeded();
|
|
2763
3738
|
}
|
|
2764
3739
|
}
|
|
@@ -2776,44 +3751,46 @@ async function runCoordinator(options) {
|
|
|
2776
3751
|
emit(final);
|
|
2777
3752
|
recordProtocolDecision(final, { transcriptEntryCount: transcript.length });
|
|
2778
3753
|
const finalEvent = events.at(-1);
|
|
3754
|
+
const trace = {
|
|
3755
|
+
schemaVersion: "1.0",
|
|
3756
|
+
runId,
|
|
3757
|
+
protocol: "coordinator",
|
|
3758
|
+
tier: options.tier,
|
|
3759
|
+
modelProviderId: options.model.id,
|
|
3760
|
+
agentsUsed: activeAgents,
|
|
3761
|
+
inputs: createReplayTraceRunInputs({
|
|
3762
|
+
intent: options.intent,
|
|
3763
|
+
protocol: options.protocol,
|
|
3764
|
+
tier: options.tier,
|
|
3765
|
+
modelProviderId: options.model.id,
|
|
3766
|
+
agents: activeAgents,
|
|
3767
|
+
temperature: options.temperature
|
|
3768
|
+
}),
|
|
3769
|
+
budget: createReplayTraceBudget({
|
|
3770
|
+
tier: options.tier,
|
|
3771
|
+
...options.budget ? { caps: options.budget } : {},
|
|
3772
|
+
...options.terminate ? { termination: options.terminate } : {}
|
|
3773
|
+
}),
|
|
3774
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
3775
|
+
seed: createReplayTraceSeed(options.seed),
|
|
3776
|
+
protocolDecisions,
|
|
3777
|
+
providerCalls,
|
|
3778
|
+
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
|
|
3779
|
+
type: "final",
|
|
3780
|
+
runId,
|
|
3781
|
+
at: "",
|
|
3782
|
+
output,
|
|
3783
|
+
cost: totalCost,
|
|
3784
|
+
transcript: createTranscriptLink(transcript)
|
|
3785
|
+
}),
|
|
3786
|
+
...triggeringFailureForAbortMode !== void 0 ? { triggeringFailureForAbortMode } : {},
|
|
3787
|
+
events,
|
|
3788
|
+
transcript
|
|
3789
|
+
};
|
|
2779
3790
|
return {
|
|
2780
3791
|
output,
|
|
2781
3792
|
eventLog: createRunEventLog(runId, "coordinator", events),
|
|
2782
|
-
trace
|
|
2783
|
-
schemaVersion: "1.0",
|
|
2784
|
-
runId,
|
|
2785
|
-
protocol: "coordinator",
|
|
2786
|
-
tier: options.tier,
|
|
2787
|
-
modelProviderId: options.model.id,
|
|
2788
|
-
agentsUsed: activeAgents,
|
|
2789
|
-
inputs: createReplayTraceRunInputs({
|
|
2790
|
-
intent: options.intent,
|
|
2791
|
-
protocol: options.protocol,
|
|
2792
|
-
tier: options.tier,
|
|
2793
|
-
modelProviderId: options.model.id,
|
|
2794
|
-
agents: activeAgents,
|
|
2795
|
-
temperature: options.temperature
|
|
2796
|
-
}),
|
|
2797
|
-
budget: createReplayTraceBudget({
|
|
2798
|
-
tier: options.tier,
|
|
2799
|
-
...options.budget ? { caps: options.budget } : {},
|
|
2800
|
-
...options.terminate ? { termination: options.terminate } : {}
|
|
2801
|
-
}),
|
|
2802
|
-
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
2803
|
-
seed: createReplayTraceSeed(options.seed),
|
|
2804
|
-
protocolDecisions,
|
|
2805
|
-
providerCalls,
|
|
2806
|
-
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
|
|
2807
|
-
type: "final",
|
|
2808
|
-
runId,
|
|
2809
|
-
at: "",
|
|
2810
|
-
output,
|
|
2811
|
-
cost: totalCost,
|
|
2812
|
-
transcript: createTranscriptLink(transcript)
|
|
2813
|
-
}),
|
|
2814
|
-
events,
|
|
2815
|
-
transcript
|
|
2816
|
-
},
|
|
3793
|
+
trace,
|
|
2817
3794
|
transcript,
|
|
2818
3795
|
usage: createRunUsage(totalCost),
|
|
2819
3796
|
metadata: createRunMetadata({
|
|
@@ -2831,7 +3808,8 @@ async function runCoordinator(options) {
|
|
|
2831
3808
|
cost: totalCost,
|
|
2832
3809
|
events
|
|
2833
3810
|
}),
|
|
2834
|
-
cost: totalCost
|
|
3811
|
+
cost: totalCost,
|
|
3812
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
2835
3813
|
};
|
|
2836
3814
|
function stopIfNeeded() {
|
|
2837
3815
|
throwIfAborted(options.signal, options.model.id);
|
|
@@ -2868,6 +3846,9 @@ async function runCoordinator(options) {
|
|
|
2868
3846
|
recordProtocolDecision(event, { transcriptEntryCount: transcript.length });
|
|
2869
3847
|
}
|
|
2870
3848
|
}
|
|
3849
|
+
function isDelegateValidationError(error) {
|
|
3850
|
+
return DogpileError.isInstance(error) && error.code === "invalid-configuration" && error.detail?.["kind"] === "delegate-validation";
|
|
3851
|
+
}
|
|
2871
3852
|
async function runCoordinatorTurn(turn) {
|
|
2872
3853
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
2873
3854
|
const request = {
|
|
@@ -2911,7 +3892,11 @@ async function runCoordinatorTurn(turn) {
|
|
|
2911
3892
|
turn.providerCalls.push(call);
|
|
2912
3893
|
}
|
|
2913
3894
|
});
|
|
2914
|
-
const decision = parseAgentDecision(response.text
|
|
3895
|
+
const decision = parseAgentDecision(response.text, {
|
|
3896
|
+
parentProviderId: turn.options.model.id,
|
|
3897
|
+
currentDepth: turn.options.currentDepth ?? 0,
|
|
3898
|
+
maxDepth: turn.options.effectiveMaxDepth ?? Number.POSITIVE_INFINITY
|
|
3899
|
+
});
|
|
2915
3900
|
const totalCost = addCost(turn.totalCost, responseCost$2(response));
|
|
2916
3901
|
const toolCalls = await executeModelResponseToolRequests({
|
|
2917
3902
|
response,
|
|
@@ -2947,7 +3932,10 @@ async function runCoordinatorTurn(turn) {
|
|
|
2947
3932
|
phase: turn.phase,
|
|
2948
3933
|
transcriptEntryCount: turn.transcript.length
|
|
2949
3934
|
});
|
|
2950
|
-
return
|
|
3935
|
+
return {
|
|
3936
|
+
totalCost,
|
|
3937
|
+
decision
|
|
3938
|
+
};
|
|
2951
3939
|
}
|
|
2952
3940
|
async function runCoordinatorWorkerTurn(turn) {
|
|
2953
3941
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
@@ -2992,7 +3980,21 @@ async function runCoordinatorWorkerTurn(turn) {
|
|
|
2992
3980
|
turn.providerCallSlots[turn.providerCallIndex] = call;
|
|
2993
3981
|
}
|
|
2994
3982
|
});
|
|
2995
|
-
const decision = parseAgentDecision(response.text
|
|
3983
|
+
const decision = parseAgentDecision(response.text, {
|
|
3984
|
+
parentProviderId: turn.options.model.id,
|
|
3985
|
+
currentDepth: turn.options.currentDepth ?? 0,
|
|
3986
|
+
maxDepth: turn.options.effectiveMaxDepth ?? Number.POSITIVE_INFINITY
|
|
3987
|
+
});
|
|
3988
|
+
if (Array.isArray(decision) || decision?.type === "delegate") throw new DogpileError({
|
|
3989
|
+
code: "invalid-configuration",
|
|
3990
|
+
message: "Workers cannot emit delegate decisions in Phase 1",
|
|
3991
|
+
retryable: false,
|
|
3992
|
+
detail: {
|
|
3993
|
+
kind: "delegate-validation",
|
|
3994
|
+
path: "decision",
|
|
3995
|
+
phase: "worker"
|
|
3996
|
+
}
|
|
3997
|
+
});
|
|
2996
3998
|
const toolCalls = await executeModelResponseToolRequests({
|
|
2997
3999
|
response,
|
|
2998
4000
|
executor: turn.toolExecutor,
|
|
@@ -3019,6 +4021,30 @@ function buildSystemPrompt$2(agent, coordinator) {
|
|
|
3019
4021
|
function buildCoordinatorPlanInput(intent, coordinator) {
|
|
3020
4022
|
return `Mission: ${intent}\nCoordinator ${coordinator.id}: assign the work, name the plan, and provide the first contribution.`;
|
|
3021
4023
|
}
|
|
4024
|
+
function buildFailuresSection(failures) {
|
|
4025
|
+
if (failures.length === 0) return null;
|
|
4026
|
+
return [
|
|
4027
|
+
"## Sub-run failures since last decision",
|
|
4028
|
+
"",
|
|
4029
|
+
"```json",
|
|
4030
|
+
JSON.stringify(failures, null, 2),
|
|
4031
|
+
"```"
|
|
4032
|
+
].join("\n");
|
|
4033
|
+
}
|
|
4034
|
+
function dispatchWaveFailureFromEvent(intent, event) {
|
|
4035
|
+
const reason = typeof event.error.detail?.["reason"] === "string" ? event.error.detail["reason"] : void 0;
|
|
4036
|
+
if (reason === "sibling-failed" || reason === "parent-aborted") return;
|
|
4037
|
+
return {
|
|
4038
|
+
childRunId: event.childRunId,
|
|
4039
|
+
intent,
|
|
4040
|
+
error: {
|
|
4041
|
+
code: event.error.code,
|
|
4042
|
+
message: event.error.message,
|
|
4043
|
+
...reason !== void 0 ? { detail: { reason } } : {}
|
|
4044
|
+
},
|
|
4045
|
+
partialCost: { usd: event.partialCost.usd }
|
|
4046
|
+
};
|
|
4047
|
+
}
|
|
3022
4048
|
function buildWorkerInput(intent, transcript, coordinator) {
|
|
3023
4049
|
const prior = transcript.map((entry) => `${entry.role} (${entry.agentId}): ${entry.output}`).join("\n\n");
|
|
3024
4050
|
return `Mission: ${intent}\n\nCoordinator: ${coordinator.id}\nPrior contributions:\n${prior}\n\nFollow the coordinator-managed plan and provide your assigned contribution.`;
|
|
@@ -3035,6 +4061,388 @@ function responseCost$2(response) {
|
|
|
3035
4061
|
totalTokens: response.usage?.totalTokens ?? 0
|
|
3036
4062
|
};
|
|
3037
4063
|
}
|
|
4064
|
+
/**
|
|
4065
|
+
* Dispatch a single delegate decision as a recursive sub-run.
|
|
4066
|
+
*
|
|
4067
|
+
* D-11: child reuses the parent provider object verbatim.
|
|
4068
|
+
* D-16: `recursive: true` flag set when both parent and child protocol are
|
|
4069
|
+
* `coordinator`.
|
|
4070
|
+
* D-17: tagged result text appended to the next coordinator prompt.
|
|
4071
|
+
* D-18: synthetic transcript entry pushed for replay/provenance.
|
|
4072
|
+
*
|
|
4073
|
+
* On thrown error from the child engine, builds `partialTrace` from a locally
|
|
4074
|
+
* tee'd `childEvents` buffer — `runProtocol`'s error contract is unchanged.
|
|
4075
|
+
*/
|
|
4076
|
+
async function dispatchDelegate(input) {
|
|
4077
|
+
const { decision, options } = input;
|
|
4078
|
+
if (options.effectiveMaxDepth !== void 0) assertDepthWithinLimit(input.parentDepth, options.effectiveMaxDepth);
|
|
4079
|
+
const childRunId = input.childRunId ?? createRunId();
|
|
4080
|
+
const recursive = decision.protocol === "coordinator";
|
|
4081
|
+
const decisionTimeoutMs = decision.budget?.timeoutMs;
|
|
4082
|
+
const parentDeadlineMs = options.parentDeadlineMs;
|
|
4083
|
+
const remainingMs = parentDeadlineMs !== void 0 ? Math.max(0, parentDeadlineMs - Date.now()) : void 0;
|
|
4084
|
+
if (parentDeadlineMs !== void 0 && remainingMs === 0) throw new DogpileError({
|
|
4085
|
+
code: "aborted",
|
|
4086
|
+
message: "Parent deadline elapsed before sub-run dispatch.",
|
|
4087
|
+
retryable: false,
|
|
4088
|
+
providerId: options.model.id,
|
|
4089
|
+
detail: { reason: "timeout" }
|
|
4090
|
+
});
|
|
4091
|
+
let childTimeoutMs;
|
|
4092
|
+
let clampedFrom;
|
|
4093
|
+
if (remainingMs !== void 0) if (decisionTimeoutMs !== void 0) if (decisionTimeoutMs > remainingMs) {
|
|
4094
|
+
clampedFrom = decisionTimeoutMs;
|
|
4095
|
+
childTimeoutMs = remainingMs;
|
|
4096
|
+
} else childTimeoutMs = decisionTimeoutMs;
|
|
4097
|
+
else childTimeoutMs = remainingMs;
|
|
4098
|
+
else if (decisionTimeoutMs !== void 0) childTimeoutMs = decisionTimeoutMs;
|
|
4099
|
+
else if (options.defaultSubRunTimeoutMs !== void 0) childTimeoutMs = options.defaultSubRunTimeoutMs;
|
|
4100
|
+
if (!options.runProtocol) throw new DogpileError({
|
|
4101
|
+
code: "invalid-configuration",
|
|
4102
|
+
message: "Coordinator delegate dispatch requires the engine `runProtocol` callback. Use `Dogpile.run` / `createEngine` rather than calling `runCoordinator` directly when delegate is in play.",
|
|
4103
|
+
retryable: false,
|
|
4104
|
+
detail: {
|
|
4105
|
+
kind: "delegate-validation",
|
|
4106
|
+
path: "runProtocol"
|
|
4107
|
+
}
|
|
4108
|
+
});
|
|
4109
|
+
const childEvents = input.dispatchedChild.childEvents;
|
|
4110
|
+
const parentEmit = input.emit;
|
|
4111
|
+
const teedEmit = (event) => {
|
|
4112
|
+
childEvents.push(event);
|
|
4113
|
+
if (input.dispatchedChild.closed) return;
|
|
4114
|
+
if (options.streamEvents && options.emit) {
|
|
4115
|
+
const inbound = event.parentRunIds;
|
|
4116
|
+
options.emit({
|
|
4117
|
+
...event,
|
|
4118
|
+
parentRunIds: [input.parentRunId, ...inbound ?? []]
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
4121
|
+
};
|
|
4122
|
+
const childStartedAt = Date.now();
|
|
4123
|
+
input.dispatchedChild.startedAtMs = childStartedAt;
|
|
4124
|
+
if (clampedFrom !== void 0 && childTimeoutMs !== void 0) {
|
|
4125
|
+
const clampEvent = {
|
|
4126
|
+
type: "sub-run-budget-clamped",
|
|
4127
|
+
runId: input.parentRunId,
|
|
4128
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4129
|
+
childRunId,
|
|
4130
|
+
parentRunId: input.parentRunId,
|
|
4131
|
+
parentDecisionId: input.parentDecisionId,
|
|
4132
|
+
requestedTimeoutMs: clampedFrom,
|
|
4133
|
+
clampedTimeoutMs: childTimeoutMs,
|
|
4134
|
+
reason: "exceeded-parent-remaining"
|
|
4135
|
+
};
|
|
4136
|
+
input.emit(clampEvent);
|
|
4137
|
+
input.recordProtocolDecision(clampEvent);
|
|
4138
|
+
}
|
|
4139
|
+
const startEvent = {
|
|
4140
|
+
type: "sub-run-started",
|
|
4141
|
+
runId: input.parentRunId,
|
|
4142
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4143
|
+
childRunId,
|
|
4144
|
+
parentRunId: input.parentRunId,
|
|
4145
|
+
parentDecisionId: input.parentDecisionId,
|
|
4146
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4147
|
+
protocol: decision.protocol,
|
|
4148
|
+
intent: decision.intent,
|
|
4149
|
+
depth: input.parentDepth + 1,
|
|
4150
|
+
...recursive ? { recursive: true } : {}
|
|
4151
|
+
};
|
|
4152
|
+
parentEmit(startEvent);
|
|
4153
|
+
input.recordProtocolDecision(startEvent);
|
|
4154
|
+
const parentSignal = options.signal;
|
|
4155
|
+
let removeParentAbortListener;
|
|
4156
|
+
if (parentSignal !== void 0) if (parentSignal.aborted) input.dispatchedChild.controller.abort(parentSignal.reason);
|
|
4157
|
+
else {
|
|
4158
|
+
const handler = () => {
|
|
4159
|
+
input.dispatchedChild.controller.abort(parentSignal.reason);
|
|
4160
|
+
};
|
|
4161
|
+
parentSignal.addEventListener("abort", handler, { once: true });
|
|
4162
|
+
removeParentAbortListener = () => {
|
|
4163
|
+
parentSignal.removeEventListener("abort", handler);
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
input.dispatchedChild.removeParentListener = removeParentAbortListener;
|
|
4167
|
+
input.dispatchedChild.started = true;
|
|
4168
|
+
input.dispatchedChild.childTimeoutMs = childTimeoutMs;
|
|
4169
|
+
const childDeadlineReason = childTimeoutMs !== void 0 && parentDeadlineMs === void 0 ? createEngineDeadlineTimeoutError(options.model.id, childTimeoutMs) : void 0;
|
|
4170
|
+
const childDeadlineTimer = childDeadlineReason !== void 0 ? setTimeout(() => {
|
|
4171
|
+
input.dispatchedChild.controller.abort(childDeadlineReason);
|
|
4172
|
+
}, childTimeoutMs) : void 0;
|
|
4173
|
+
const childOptions = {
|
|
4174
|
+
runId: childRunId,
|
|
4175
|
+
intent: decision.intent,
|
|
4176
|
+
protocol: decision.protocol,
|
|
4177
|
+
tier: options.tier,
|
|
4178
|
+
model: options.model,
|
|
4179
|
+
agents: options.agents,
|
|
4180
|
+
tools: options.tools,
|
|
4181
|
+
temperature: options.temperature,
|
|
4182
|
+
...childTimeoutMs !== void 0 ? { budget: { timeoutMs: childTimeoutMs } } : {},
|
|
4183
|
+
signal: input.dispatchedChild.controller.signal,
|
|
4184
|
+
emit: teedEmit,
|
|
4185
|
+
...options.streamEvents !== void 0 ? { streamEvents: options.streamEvents } : {},
|
|
4186
|
+
currentDepth: input.parentDepth + 1,
|
|
4187
|
+
...options.effectiveMaxDepth !== void 0 ? { effectiveMaxDepth: options.effectiveMaxDepth } : {},
|
|
4188
|
+
...options.effectiveMaxConcurrentChildren !== void 0 ? { effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren } : {},
|
|
4189
|
+
...options.onChildFailure !== void 0 ? { onChildFailure: options.onChildFailure } : {},
|
|
4190
|
+
...parentDeadlineMs !== void 0 ? { parentDeadlineMs } : {},
|
|
4191
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
4192
|
+
};
|
|
4193
|
+
let subResult;
|
|
4194
|
+
try {
|
|
4195
|
+
subResult = await options.runProtocol(childOptions);
|
|
4196
|
+
} catch (error) {
|
|
4197
|
+
if (childDeadlineTimer !== void 0) clearTimeout(childDeadlineTimer);
|
|
4198
|
+
removeParentAbortListener?.();
|
|
4199
|
+
if (input.dispatchedChild.closed) {
|
|
4200
|
+
const enrichedError = enrichAbortErrorWithParentReason(error, parentSignal);
|
|
4201
|
+
if (DogpileError.isInstance(enrichedError)) throw enrichedError;
|
|
4202
|
+
throw error;
|
|
4203
|
+
}
|
|
4204
|
+
const failedDecision = {
|
|
4205
|
+
type: "delegate",
|
|
4206
|
+
protocol: decision.protocol,
|
|
4207
|
+
intent: decision.intent,
|
|
4208
|
+
...decision.model !== void 0 ? { model: decision.model } : {},
|
|
4209
|
+
...decision.budget !== void 0 ? { budget: decision.budget } : {}
|
|
4210
|
+
};
|
|
4211
|
+
const partialTrace = buildPartialTrace({
|
|
4212
|
+
childRunId,
|
|
4213
|
+
events: childEvents,
|
|
4214
|
+
startedAtMs: childStartedAt,
|
|
4215
|
+
protocol: decision.protocol,
|
|
4216
|
+
tier: options.tier,
|
|
4217
|
+
modelProviderId: options.model.id,
|
|
4218
|
+
agents: options.agents,
|
|
4219
|
+
intent: decision.intent,
|
|
4220
|
+
temperature: options.temperature,
|
|
4221
|
+
...childTimeoutMs !== void 0 ? { childTimeoutMs } : {},
|
|
4222
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
4223
|
+
});
|
|
4224
|
+
const enrichedError = enrichProviderTimeoutSource(enrichAbortErrorWithParentReason(error, parentSignal), {
|
|
4225
|
+
...decisionTimeoutMs !== void 0 ? { decisionTimeoutMs } : {},
|
|
4226
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { engineDefaultTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
4227
|
+
});
|
|
4228
|
+
if (DogpileError.isInstance(enrichedError)) options.failureInstancesByChildRunId?.set(childRunId, enrichedError);
|
|
4229
|
+
const errorPayload = errorPayloadFromUnknown(enrichedError, failedDecision);
|
|
4230
|
+
const partialCost = lastCostBearingEventCost(childEvents) ?? emptyCost();
|
|
4231
|
+
input.recordSubRunCost(partialCost);
|
|
4232
|
+
const failEvent = {
|
|
4233
|
+
type: "sub-run-failed",
|
|
4234
|
+
runId: input.parentRunId,
|
|
4235
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4236
|
+
childRunId,
|
|
4237
|
+
parentRunId: input.parentRunId,
|
|
4238
|
+
parentDecisionId: input.parentDecisionId,
|
|
4239
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4240
|
+
error: errorPayload,
|
|
4241
|
+
partialTrace,
|
|
4242
|
+
partialCost
|
|
4243
|
+
};
|
|
4244
|
+
parentEmit(failEvent);
|
|
4245
|
+
input.recordProtocolDecision(failEvent);
|
|
4246
|
+
input.dispatchedChild.closed = true;
|
|
4247
|
+
input.dispatchedChild.failure = dispatchWaveFailureFromEvent(decision.intent, failEvent);
|
|
4248
|
+
if (DogpileError.isInstance(enrichedError)) throw enrichedError;
|
|
4249
|
+
throw new DogpileError({
|
|
4250
|
+
code: "invalid-configuration",
|
|
4251
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4252
|
+
retryable: false,
|
|
4253
|
+
detail: {
|
|
4254
|
+
kind: "delegate-validation",
|
|
4255
|
+
path: "decision",
|
|
4256
|
+
reason: "child-run-failed"
|
|
4257
|
+
}
|
|
4258
|
+
});
|
|
4259
|
+
}
|
|
4260
|
+
if (childDeadlineTimer !== void 0) clearTimeout(childDeadlineTimer);
|
|
4261
|
+
removeParentAbortListener?.();
|
|
4262
|
+
input.recordSubRunCost(subResult.cost);
|
|
4263
|
+
const completedEvent = {
|
|
4264
|
+
type: "sub-run-completed",
|
|
4265
|
+
runId: input.parentRunId,
|
|
4266
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4267
|
+
childRunId,
|
|
4268
|
+
parentRunId: input.parentRunId,
|
|
4269
|
+
parentDecisionId: input.parentDecisionId,
|
|
4270
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4271
|
+
subResult
|
|
4272
|
+
};
|
|
4273
|
+
parentEmit(completedEvent);
|
|
4274
|
+
input.recordProtocolDecision(completedEvent);
|
|
4275
|
+
input.dispatchedChild.closed = true;
|
|
4276
|
+
if (parentSignal?.aborted) {
|
|
4277
|
+
const abortMarker = {
|
|
4278
|
+
type: "sub-run-parent-aborted",
|
|
4279
|
+
runId: input.parentRunId,
|
|
4280
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4281
|
+
childRunId,
|
|
4282
|
+
parentRunId: input.parentRunId,
|
|
4283
|
+
reason: "parent-aborted"
|
|
4284
|
+
};
|
|
4285
|
+
parentEmit(abortMarker);
|
|
4286
|
+
input.recordProtocolDecision(abortMarker);
|
|
4287
|
+
throw enrichAbortErrorWithParentReason(createAbortErrorFromSignal(parentSignal, options.model.id), parentSignal);
|
|
4288
|
+
}
|
|
4289
|
+
const decisionAsJson = {
|
|
4290
|
+
type: "delegate",
|
|
4291
|
+
protocol: decision.protocol,
|
|
4292
|
+
intent: decision.intent,
|
|
4293
|
+
...decision.model !== void 0 ? { model: decision.model } : {},
|
|
4294
|
+
...decision.budget !== void 0 ? { budget: decision.budget } : {}
|
|
4295
|
+
};
|
|
4296
|
+
const taggedText = renderSubRunResult(childRunId, subResult);
|
|
4297
|
+
input.transcript.push({
|
|
4298
|
+
agentId: `sub-run:${childRunId}`,
|
|
4299
|
+
role: "delegate-result",
|
|
4300
|
+
input: JSON.stringify(decisionAsJson),
|
|
4301
|
+
output: taggedText
|
|
4302
|
+
});
|
|
4303
|
+
const coordinatorAgent = options.agents[0];
|
|
4304
|
+
return {
|
|
4305
|
+
nextInput: `${buildCoordinatorPlanInput(input.options.intent, coordinatorAgent ?? {
|
|
4306
|
+
id: "coordinator",
|
|
4307
|
+
role: "coordinator"
|
|
4308
|
+
})}\n\n${taggedText}\n\nUsing the sub-run result above, decide the next step (participate or delegate).`,
|
|
4309
|
+
taggedText,
|
|
4310
|
+
completedAtMs: Date.now()
|
|
4311
|
+
};
|
|
4312
|
+
}
|
|
4313
|
+
/**
|
|
4314
|
+
* D-17 prompt-injection helper. Renders a child `RunResult` as the canonical
|
|
4315
|
+
* tagged-result block injected into the parent coordinator's next prompt.
|
|
4316
|
+
*
|
|
4317
|
+
* Format:
|
|
4318
|
+
* `[sub-run <childRunId>]: <output>`
|
|
4319
|
+
* `[sub-run <childRunId> stats]: turns=<N> costUsd=<X> durationMs=<Y>`
|
|
4320
|
+
*
|
|
4321
|
+
* The stats line is a soft contract — field names stable, ordering stable.
|
|
4322
|
+
*/
|
|
4323
|
+
function renderSubRunResult(childRunId, subResult) {
|
|
4324
|
+
const turns = subResult.transcript.length;
|
|
4325
|
+
const costUsd = subResult.cost.usd ?? 0;
|
|
4326
|
+
const startedAt = eventTimestamp(subResult.trace.events[0]);
|
|
4327
|
+
const endedAt = eventTimestamp(subResult.trace.events.at(-1));
|
|
4328
|
+
const durationMs = startedAt && endedAt ? Math.max(0, Date.parse(endedAt) - Date.parse(startedAt)) : 0;
|
|
4329
|
+
return [`[sub-run ${childRunId}]: ${subResult.output}`, `[sub-run ${childRunId} stats]: turns=${turns} costUsd=${costUsd} durationMs=${durationMs}`].join("\n");
|
|
4330
|
+
}
|
|
4331
|
+
function eventTimestamp(event) {
|
|
4332
|
+
if (event === void 0) return void 0;
|
|
4333
|
+
if ("at" in event) return event.at;
|
|
4334
|
+
return event.type === "model-response" ? event.completedAt : event.startedAt;
|
|
4335
|
+
}
|
|
4336
|
+
/**
|
|
4337
|
+
* Build a JSON-serializable {@link Trace} for `sub-run-failed.partialTrace`
|
|
4338
|
+
* from a buffered tee of child emits. Keeps `runProtocol`'s error contract
|
|
4339
|
+
* unchanged — Plan 03 step 8.
|
|
4340
|
+
*/
|
|
4341
|
+
function buildPartialTrace(input) {
|
|
4342
|
+
const protocolName = typeof input.protocol === "string" ? input.protocol : input.protocol.kind;
|
|
4343
|
+
const protocolConfig = typeof input.protocol === "string" ? { kind: input.protocol } : input.protocol;
|
|
4344
|
+
return {
|
|
4345
|
+
schemaVersion: "1.0",
|
|
4346
|
+
runId: input.childRunId,
|
|
4347
|
+
protocol: protocolName,
|
|
4348
|
+
tier: input.tier,
|
|
4349
|
+
modelProviderId: input.modelProviderId,
|
|
4350
|
+
agentsUsed: input.agents,
|
|
4351
|
+
inputs: createReplayTraceRunInputs({
|
|
4352
|
+
intent: input.intent,
|
|
4353
|
+
protocol: protocolConfig,
|
|
4354
|
+
tier: input.tier,
|
|
4355
|
+
modelProviderId: input.modelProviderId,
|
|
4356
|
+
agents: input.agents,
|
|
4357
|
+
temperature: input.temperature
|
|
4358
|
+
}),
|
|
4359
|
+
budget: createReplayTraceBudget({
|
|
4360
|
+
tier: input.tier,
|
|
4361
|
+
...input.childTimeoutMs !== void 0 ? { caps: { timeoutMs: input.childTimeoutMs } } : {}
|
|
4362
|
+
}),
|
|
4363
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(input.events),
|
|
4364
|
+
seed: createReplayTraceSeed(input.seed),
|
|
4365
|
+
protocolDecisions: [],
|
|
4366
|
+
providerCalls: [],
|
|
4367
|
+
finalOutput: {
|
|
4368
|
+
kind: "replay-trace-final-output",
|
|
4369
|
+
output: "",
|
|
4370
|
+
cost: emptyCost(),
|
|
4371
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4372
|
+
transcript: createTranscriptLink([])
|
|
4373
|
+
},
|
|
4374
|
+
events: input.events,
|
|
4375
|
+
transcript: []
|
|
4376
|
+
};
|
|
4377
|
+
}
|
|
4378
|
+
/**
|
|
4379
|
+
* BUDGET-01 / D-08: when a child sub-run threw because the parent's signal
|
|
4380
|
+
* aborted, lock the `detail.reason` discriminator on the resulting
|
|
4381
|
+
* `code: "aborted"` error. Preserves any pre-existing detail keys (e.g.,
|
|
4382
|
+
* `detail.status: "cancelled"` attached by `createStreamCancellationError`).
|
|
4383
|
+
*
|
|
4384
|
+
* No-op when:
|
|
4385
|
+
* - parent.signal is undefined or not aborted (child failure was unrelated)
|
|
4386
|
+
* - error is not a DogpileError with `code: "aborted"`
|
|
4387
|
+
* - error already has a `detail.reason` set (preserve upstream classification)
|
|
4388
|
+
*/
|
|
4389
|
+
function enrichAbortErrorWithParentReason(error, parentSignal) {
|
|
4390
|
+
if (parentSignal === void 0 || !parentSignal.aborted) return error;
|
|
4391
|
+
if (!DogpileError.isInstance(error) || error.code !== "aborted") return error;
|
|
4392
|
+
const existingDetail = error.detail ?? {};
|
|
4393
|
+
if (existingDetail["reason"] !== void 0) return error;
|
|
4394
|
+
const reason = classifyAbortReason(parentSignal.reason);
|
|
4395
|
+
return new DogpileError({
|
|
4396
|
+
code: "aborted",
|
|
4397
|
+
message: error.message,
|
|
4398
|
+
retryable: error.retryable ?? false,
|
|
4399
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4400
|
+
detail: {
|
|
4401
|
+
...existingDetail,
|
|
4402
|
+
reason
|
|
4403
|
+
},
|
|
4404
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
function enrichProviderTimeoutSource(error, context) {
|
|
4408
|
+
if (!DogpileError.isInstance(error) || error.code !== "provider-timeout") return error;
|
|
4409
|
+
const existingDetail = error.detail ?? {};
|
|
4410
|
+
if (existingDetail["source"] !== void 0) return error;
|
|
4411
|
+
const source = classifyChildTimeoutSource(error, {
|
|
4412
|
+
...context,
|
|
4413
|
+
isProviderError: true
|
|
4414
|
+
});
|
|
4415
|
+
return new DogpileError({
|
|
4416
|
+
code: "provider-timeout",
|
|
4417
|
+
message: error.message,
|
|
4418
|
+
retryable: error.retryable ?? true,
|
|
4419
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4420
|
+
detail: {
|
|
4421
|
+
...existingDetail,
|
|
4422
|
+
source
|
|
4423
|
+
},
|
|
4424
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
function errorPayloadFromUnknown(error, failedDecision) {
|
|
4428
|
+
if (DogpileError.isInstance(error)) {
|
|
4429
|
+
const detail = {
|
|
4430
|
+
...error.detail ?? {},
|
|
4431
|
+
failedDecision
|
|
4432
|
+
};
|
|
4433
|
+
return {
|
|
4434
|
+
code: error.code,
|
|
4435
|
+
message: error.message,
|
|
4436
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4437
|
+
detail
|
|
4438
|
+
};
|
|
4439
|
+
}
|
|
4440
|
+
return {
|
|
4441
|
+
code: "invalid-configuration",
|
|
4442
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4443
|
+
detail: { failedDecision }
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
3038
4446
|
//#endregion
|
|
3039
4447
|
//#region src/runtime/sequential.ts
|
|
3040
4448
|
async function runSequential(options) {
|
|
@@ -3173,7 +4581,8 @@ async function runSequential(options) {
|
|
|
3173
4581
|
});
|
|
3174
4582
|
if (stopIfNeeded()) break;
|
|
3175
4583
|
}
|
|
3176
|
-
const
|
|
4584
|
+
const reversed = [...transcript].reverse();
|
|
4585
|
+
const output = reversed.find((entry) => isParticipatingDecision(entry.decision))?.output ?? reversed.find((entry) => entry.decision === void 0)?.output ?? "";
|
|
3177
4586
|
throwIfAborted(options.signal, options.model.id);
|
|
3178
4587
|
const final = {
|
|
3179
4588
|
type: "final",
|
|
@@ -3187,44 +4596,45 @@ async function runSequential(options) {
|
|
|
3187
4596
|
emit(final);
|
|
3188
4597
|
recordProtocolDecision(final, { transcriptEntryCount: transcript.length });
|
|
3189
4598
|
const finalEvent = events.at(-1);
|
|
4599
|
+
const trace = {
|
|
4600
|
+
schemaVersion: "1.0",
|
|
4601
|
+
runId,
|
|
4602
|
+
protocol: "sequential",
|
|
4603
|
+
tier: options.tier,
|
|
4604
|
+
modelProviderId: options.model.id,
|
|
4605
|
+
agentsUsed: activeAgents,
|
|
4606
|
+
inputs: createReplayTraceRunInputs({
|
|
4607
|
+
intent: options.intent,
|
|
4608
|
+
protocol: options.protocol,
|
|
4609
|
+
tier: options.tier,
|
|
4610
|
+
modelProviderId: options.model.id,
|
|
4611
|
+
agents: activeAgents,
|
|
4612
|
+
temperature: options.temperature
|
|
4613
|
+
}),
|
|
4614
|
+
budget: createReplayTraceBudget({
|
|
4615
|
+
tier: options.tier,
|
|
4616
|
+
...options.budget ? { caps: options.budget } : {},
|
|
4617
|
+
...options.terminate ? { termination: options.terminate } : {}
|
|
4618
|
+
}),
|
|
4619
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
4620
|
+
seed: createReplayTraceSeed(options.seed),
|
|
4621
|
+
protocolDecisions,
|
|
4622
|
+
providerCalls,
|
|
4623
|
+
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? events[0] ?? {
|
|
4624
|
+
type: "final",
|
|
4625
|
+
runId,
|
|
4626
|
+
at: "",
|
|
4627
|
+
output,
|
|
4628
|
+
cost: totalCost,
|
|
4629
|
+
transcript: createTranscriptLink(transcript)
|
|
4630
|
+
}),
|
|
4631
|
+
events,
|
|
4632
|
+
transcript
|
|
4633
|
+
};
|
|
3190
4634
|
return {
|
|
3191
4635
|
output,
|
|
3192
4636
|
eventLog: createRunEventLog(runId, "sequential", events),
|
|
3193
|
-
trace
|
|
3194
|
-
schemaVersion: "1.0",
|
|
3195
|
-
runId,
|
|
3196
|
-
protocol: "sequential",
|
|
3197
|
-
tier: options.tier,
|
|
3198
|
-
modelProviderId: options.model.id,
|
|
3199
|
-
agentsUsed: activeAgents,
|
|
3200
|
-
inputs: createReplayTraceRunInputs({
|
|
3201
|
-
intent: options.intent,
|
|
3202
|
-
protocol: options.protocol,
|
|
3203
|
-
tier: options.tier,
|
|
3204
|
-
modelProviderId: options.model.id,
|
|
3205
|
-
agents: activeAgents,
|
|
3206
|
-
temperature: options.temperature
|
|
3207
|
-
}),
|
|
3208
|
-
budget: createReplayTraceBudget({
|
|
3209
|
-
tier: options.tier,
|
|
3210
|
-
...options.budget ? { caps: options.budget } : {},
|
|
3211
|
-
...options.terminate ? { termination: options.terminate } : {}
|
|
3212
|
-
}),
|
|
3213
|
-
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
3214
|
-
seed: createReplayTraceSeed(options.seed),
|
|
3215
|
-
protocolDecisions,
|
|
3216
|
-
providerCalls,
|
|
3217
|
-
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? events[0] ?? {
|
|
3218
|
-
type: "final",
|
|
3219
|
-
runId,
|
|
3220
|
-
at: "",
|
|
3221
|
-
output,
|
|
3222
|
-
cost: totalCost,
|
|
3223
|
-
transcript: createTranscriptLink(transcript)
|
|
3224
|
-
}),
|
|
3225
|
-
events,
|
|
3226
|
-
transcript
|
|
3227
|
-
},
|
|
4637
|
+
trace,
|
|
3228
4638
|
transcript,
|
|
3229
4639
|
usage: createRunUsage(totalCost),
|
|
3230
4640
|
metadata: createRunMetadata({
|
|
@@ -3242,7 +4652,8 @@ async function runSequential(options) {
|
|
|
3242
4652
|
cost: totalCost,
|
|
3243
4653
|
events
|
|
3244
4654
|
}),
|
|
3245
|
-
cost: totalCost
|
|
4655
|
+
cost: totalCost,
|
|
4656
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
3246
4657
|
};
|
|
3247
4658
|
function stopIfNeeded() {
|
|
3248
4659
|
throwIfAborted(options.signal, options.model.id);
|
|
@@ -3461,44 +4872,45 @@ async function runShared(options) {
|
|
|
3461
4872
|
emit(final);
|
|
3462
4873
|
recordProtocolDecision(final, { transcriptEntryCount: transcript.length });
|
|
3463
4874
|
const finalEvent = events.at(-1);
|
|
4875
|
+
const trace = {
|
|
4876
|
+
schemaVersion: "1.0",
|
|
4877
|
+
runId,
|
|
4878
|
+
protocol: "shared",
|
|
4879
|
+
tier: options.tier,
|
|
4880
|
+
modelProviderId: options.model.id,
|
|
4881
|
+
agentsUsed: activeAgents,
|
|
4882
|
+
inputs: createReplayTraceRunInputs({
|
|
4883
|
+
intent: options.intent,
|
|
4884
|
+
protocol: options.protocol,
|
|
4885
|
+
tier: options.tier,
|
|
4886
|
+
modelProviderId: options.model.id,
|
|
4887
|
+
agents: activeAgents,
|
|
4888
|
+
temperature: options.temperature
|
|
4889
|
+
}),
|
|
4890
|
+
budget: createReplayTraceBudget({
|
|
4891
|
+
tier: options.tier,
|
|
4892
|
+
...options.budget ? { caps: options.budget } : {},
|
|
4893
|
+
...options.terminate ? { termination: options.terminate } : {}
|
|
4894
|
+
}),
|
|
4895
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
4896
|
+
seed: createReplayTraceSeed(options.seed),
|
|
4897
|
+
protocolDecisions,
|
|
4898
|
+
providerCalls,
|
|
4899
|
+
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
|
|
4900
|
+
type: "final",
|
|
4901
|
+
runId,
|
|
4902
|
+
at: "",
|
|
4903
|
+
output,
|
|
4904
|
+
cost: totalCost,
|
|
4905
|
+
transcript: createTranscriptLink(transcript)
|
|
4906
|
+
}),
|
|
4907
|
+
events,
|
|
4908
|
+
transcript
|
|
4909
|
+
};
|
|
3464
4910
|
return {
|
|
3465
4911
|
output,
|
|
3466
4912
|
eventLog: createRunEventLog(runId, "shared", events),
|
|
3467
|
-
trace
|
|
3468
|
-
schemaVersion: "1.0",
|
|
3469
|
-
runId,
|
|
3470
|
-
protocol: "shared",
|
|
3471
|
-
tier: options.tier,
|
|
3472
|
-
modelProviderId: options.model.id,
|
|
3473
|
-
agentsUsed: activeAgents,
|
|
3474
|
-
inputs: createReplayTraceRunInputs({
|
|
3475
|
-
intent: options.intent,
|
|
3476
|
-
protocol: options.protocol,
|
|
3477
|
-
tier: options.tier,
|
|
3478
|
-
modelProviderId: options.model.id,
|
|
3479
|
-
agents: activeAgents,
|
|
3480
|
-
temperature: options.temperature
|
|
3481
|
-
}),
|
|
3482
|
-
budget: createReplayTraceBudget({
|
|
3483
|
-
tier: options.tier,
|
|
3484
|
-
...options.budget ? { caps: options.budget } : {},
|
|
3485
|
-
...options.terminate ? { termination: options.terminate } : {}
|
|
3486
|
-
}),
|
|
3487
|
-
budgetStateChanges: createReplayTraceBudgetStateChanges(events),
|
|
3488
|
-
seed: createReplayTraceSeed(options.seed),
|
|
3489
|
-
protocolDecisions,
|
|
3490
|
-
providerCalls,
|
|
3491
|
-
finalOutput: createReplayTraceFinalOutput(output, finalEvent ?? {
|
|
3492
|
-
type: "final",
|
|
3493
|
-
runId,
|
|
3494
|
-
at: "",
|
|
3495
|
-
output,
|
|
3496
|
-
cost: totalCost,
|
|
3497
|
-
transcript: createTranscriptLink(transcript)
|
|
3498
|
-
}),
|
|
3499
|
-
events,
|
|
3500
|
-
transcript
|
|
3501
|
-
},
|
|
4913
|
+
trace,
|
|
3502
4914
|
transcript,
|
|
3503
4915
|
usage: createRunUsage(totalCost),
|
|
3504
4916
|
metadata: createRunMetadata({
|
|
@@ -3516,7 +4928,8 @@ async function runShared(options) {
|
|
|
3516
4928
|
cost: totalCost,
|
|
3517
4929
|
events
|
|
3518
4930
|
}),
|
|
3519
|
-
cost: totalCost
|
|
4931
|
+
cost: totalCost,
|
|
4932
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
3520
4933
|
};
|
|
3521
4934
|
function stopIfNeeded() {
|
|
3522
4935
|
throwIfAborted(options.signal, options.model.id);
|
|
@@ -3572,7 +4985,17 @@ function responseCost(response) {
|
|
|
3572
4985
|
};
|
|
3573
4986
|
}
|
|
3574
4987
|
//#endregion
|
|
4988
|
+
//#region src/runtime/tracing.ts
|
|
4989
|
+
var DOGPILE_SPAN_NAMES = {
|
|
4990
|
+
RUN: "dogpile.run",
|
|
4991
|
+
SUB_RUN: "dogpile.sub-run",
|
|
4992
|
+
AGENT_TURN: "dogpile.agent-turn",
|
|
4993
|
+
MODEL_CALL: "dogpile.model-call"
|
|
4994
|
+
};
|
|
4995
|
+
//#endregion
|
|
3575
4996
|
//#region src/runtime/engine.ts
|
|
4997
|
+
var DEFAULT_MAX_DEPTH = 4;
|
|
4998
|
+
var DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
|
|
3576
4999
|
var defaultHighLevelProtocol = "sequential";
|
|
3577
5000
|
var defaultHighLevelTier = "balanced";
|
|
3578
5001
|
/**
|
|
@@ -3593,9 +5016,20 @@ function createEngine(options) {
|
|
|
3593
5016
|
const temperature = options.temperature ?? tierTemperature(options.tier);
|
|
3594
5017
|
const agents = orderAgentsForTemperature(options.agents ?? defaultAgents(), temperature, options.seed);
|
|
3595
5018
|
const terminate = options.terminate ?? (options.budget ? conditionFromBudget(options.budget) : void 0);
|
|
5019
|
+
const engineMaxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
5020
|
+
const engineMaxConcurrentChildren = options.maxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN;
|
|
5021
|
+
const engineOnChildFailure = options.onChildFailure;
|
|
3596
5022
|
return {
|
|
3597
|
-
run(intent) {
|
|
5023
|
+
run(intent, runOptions) {
|
|
3598
5024
|
validateMissionIntent(intent);
|
|
5025
|
+
validateRunCallOptions(runOptions);
|
|
5026
|
+
validateProviderLocality(options.model, "model");
|
|
5027
|
+
const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
|
|
5028
|
+
assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
|
|
5029
|
+
const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
|
|
5030
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
5031
|
+
const startedAtMs = Date.now();
|
|
5032
|
+
const parentDeadlineMs = options.budget?.timeoutMs !== void 0 ? startedAtMs + options.budget.timeoutMs : void 0;
|
|
3599
5033
|
return runNonStreamingProtocol({
|
|
3600
5034
|
intent,
|
|
3601
5035
|
protocol,
|
|
@@ -3609,11 +5043,26 @@ function createEngine(options) {
|
|
|
3609
5043
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
3610
5044
|
...terminate ? { terminate } : {},
|
|
3611
5045
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
3612
|
-
...options.evaluate ? { evaluate: options.evaluate } : {}
|
|
5046
|
+
...options.evaluate ? { evaluate: options.evaluate } : {},
|
|
5047
|
+
...options.tracer ? { tracer: options.tracer } : {},
|
|
5048
|
+
...options.metricsHook ? { metricsHook: options.metricsHook } : {},
|
|
5049
|
+
...options.logger ? { logger: options.logger } : {},
|
|
5050
|
+
currentDepth: 0,
|
|
5051
|
+
effectiveMaxDepth,
|
|
5052
|
+
effectiveMaxConcurrentChildren,
|
|
5053
|
+
onChildFailure,
|
|
5054
|
+
...parentDeadlineMs !== void 0 ? { parentDeadlineMs } : {},
|
|
5055
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
3613
5056
|
});
|
|
3614
5057
|
},
|
|
3615
|
-
stream(intent) {
|
|
5058
|
+
stream(intent, runOptions) {
|
|
3616
5059
|
validateMissionIntent(intent);
|
|
5060
|
+
validateRunCallOptions(runOptions);
|
|
5061
|
+
validateProviderLocality(options.model, "model");
|
|
5062
|
+
const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
|
|
5063
|
+
assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
|
|
5064
|
+
const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
|
|
5065
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
3617
5066
|
const pendingEvents = [];
|
|
3618
5067
|
const pendingResolvers = [];
|
|
3619
5068
|
const emittedEvents = [];
|
|
@@ -3630,7 +5079,10 @@ function createEngine(options) {
|
|
|
3630
5079
|
const abortRace = createAbortRace(abortController.signal, options.model.id);
|
|
3631
5080
|
let complete = false;
|
|
3632
5081
|
let lastRunId = "";
|
|
5082
|
+
let rootRunId;
|
|
3633
5083
|
let pendingFinalEvent;
|
|
5084
|
+
let activeAbortDrain;
|
|
5085
|
+
const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
|
|
3634
5086
|
let status = "running";
|
|
3635
5087
|
let resolveResult;
|
|
3636
5088
|
let rejectResult;
|
|
@@ -3676,6 +5128,8 @@ function createEngine(options) {
|
|
|
3676
5128
|
async function execute() {
|
|
3677
5129
|
if (status !== "running") return;
|
|
3678
5130
|
try {
|
|
5131
|
+
const streamStartedAtMs = Date.now();
|
|
5132
|
+
const streamParentDeadlineMs = options.budget?.timeoutMs !== void 0 ? streamStartedAtMs + options.budget.timeoutMs : void 0;
|
|
3679
5133
|
const baseResult = await abortRace.run(runProtocol({
|
|
3680
5134
|
intent,
|
|
3681
5135
|
protocol,
|
|
@@ -3688,17 +5142,35 @@ function createEngine(options) {
|
|
|
3688
5142
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
3689
5143
|
signal: abortController.signal,
|
|
3690
5144
|
...terminate ? { terminate } : {},
|
|
5145
|
+
currentDepth: 0,
|
|
5146
|
+
effectiveMaxDepth,
|
|
5147
|
+
effectiveMaxConcurrentChildren,
|
|
5148
|
+
onChildFailure,
|
|
5149
|
+
...streamParentDeadlineMs !== void 0 ? { parentDeadlineMs: streamParentDeadlineMs } : {},
|
|
5150
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {},
|
|
5151
|
+
...options.tracer ? { tracer: options.tracer } : {},
|
|
5152
|
+
...options.metricsHook ? { metricsHook: options.metricsHook } : {},
|
|
5153
|
+
...options.logger ? { logger: options.logger } : {},
|
|
5154
|
+
streamEvents: true,
|
|
3691
5155
|
emit(event) {
|
|
3692
5156
|
if (status !== "running") return;
|
|
5157
|
+
const parentRunIds = event.parentRunIds;
|
|
5158
|
+
if (rootRunId === void 0 && parentRunIds === void 0) rootRunId = event.runId;
|
|
3693
5159
|
lastRunId = event.runId;
|
|
3694
|
-
if (event.type === "final") {
|
|
5160
|
+
if (event.type === "final" && event.runId === rootRunId) {
|
|
3695
5161
|
pendingFinalEvent = event;
|
|
3696
5162
|
return;
|
|
3697
5163
|
}
|
|
3698
5164
|
publish(event);
|
|
3699
|
-
}
|
|
5165
|
+
},
|
|
5166
|
+
registerAbortDrain(drain) {
|
|
5167
|
+
activeAbortDrain = drain;
|
|
5168
|
+
},
|
|
5169
|
+
failureInstancesByChildRunId
|
|
3700
5170
|
}));
|
|
3701
5171
|
if (status !== "running") return;
|
|
5172
|
+
const terminalThrow = resolveRuntimeTerminalThrow(baseResult.trace, failureInstancesByChildRunId);
|
|
5173
|
+
if (terminalThrow) throw terminalThrow;
|
|
3702
5174
|
const finalizedResult = await abortRace.run(applyRunEvaluation(baseResult, options.evaluate));
|
|
3703
5175
|
if (status !== "running") return;
|
|
3704
5176
|
const finalEvent = finalizedResult.trace.events.at(-1);
|
|
@@ -3711,6 +5183,10 @@ function createEngine(options) {
|
|
|
3711
5183
|
if (isStreamHandleStatus(status, "cancelled")) return;
|
|
3712
5184
|
const runtimeError = timeoutLifecycle.translateError(error);
|
|
3713
5185
|
status = isCancellationError(runtimeError) ? "cancelled" : "failed";
|
|
5186
|
+
if (shouldPublishAborted(runtimeError)) {
|
|
5187
|
+
activeAbortDrain?.(runtimeError);
|
|
5188
|
+
publish(createStreamAbortedEvent(runtimeError, lastRunId));
|
|
5189
|
+
}
|
|
3714
5190
|
publish(createStreamErrorEvent(runtimeError, lastRunId));
|
|
3715
5191
|
closeStream();
|
|
3716
5192
|
rejectResult(runtimeError);
|
|
@@ -3719,15 +5195,18 @@ function createEngine(options) {
|
|
|
3719
5195
|
function cancelRun(cause) {
|
|
3720
5196
|
if (status !== "running") return;
|
|
3721
5197
|
const error = createStreamCancellationError(options.model.id, cause);
|
|
3722
|
-
status = "cancelled";
|
|
3723
5198
|
abortController.abort(error);
|
|
5199
|
+
activeAbortDrain?.(error);
|
|
5200
|
+
publish(createStreamAbortedEvent(error, lastRunId));
|
|
3724
5201
|
publish(createStreamErrorEvent(error, lastRunId));
|
|
5202
|
+
status = "cancelled";
|
|
3725
5203
|
closeStream();
|
|
3726
5204
|
rejectResult(error);
|
|
3727
5205
|
}
|
|
3728
5206
|
function closeStream() {
|
|
3729
5207
|
if (complete) return;
|
|
3730
5208
|
complete = true;
|
|
5209
|
+
failureInstancesByChildRunId.clear();
|
|
3731
5210
|
removeCallerAbortListener();
|
|
3732
5211
|
timeoutLifecycle.cleanup();
|
|
3733
5212
|
abortRace.cleanup();
|
|
@@ -3783,7 +5262,8 @@ function createNonStreamingAbortLifecycle(options) {
|
|
|
3783
5262
|
const timeoutLifecycle = createTimeoutAbortLifecycle({
|
|
3784
5263
|
abortController,
|
|
3785
5264
|
timeoutMs: options.timeoutMs,
|
|
3786
|
-
providerId: options.providerId
|
|
5265
|
+
providerId: options.providerId,
|
|
5266
|
+
timeoutErrorSource: options.timeoutErrorSource ?? "runtime"
|
|
3787
5267
|
});
|
|
3788
5268
|
const abortRace = createAbortRace(abortController.signal, options.providerId);
|
|
3789
5269
|
const removeCallerAbortListener = wireCallerAbortSignal(options.callerSignal, abortController, () => {
|
|
@@ -3811,7 +5291,11 @@ function createTimeoutAbortLifecycle(options) {
|
|
|
3811
5291
|
},
|
|
3812
5292
|
cleanup() {}
|
|
3813
5293
|
};
|
|
3814
|
-
const
|
|
5294
|
+
const timeoutSource = classifyChildTimeoutSource(void 0, {
|
|
5295
|
+
...options.timeoutErrorSource === "engine" ? { engineDefaultTimeoutMs: options.timeoutMs } : {},
|
|
5296
|
+
isProviderError: false
|
|
5297
|
+
});
|
|
5298
|
+
const timeoutError = options.timeoutErrorSource === "engine" && timeoutSource === "engine" ? createEngineDeadlineTimeoutError(options.providerId, options.timeoutMs) : createTimeoutError(options.providerId, options.timeoutMs);
|
|
3815
5299
|
const timeoutId = setTimeout(() => {
|
|
3816
5300
|
options.abortController.abort(timeoutError);
|
|
3817
5301
|
}, options.timeoutMs);
|
|
@@ -3879,6 +5363,23 @@ function timeoutMsFromTermination(condition) {
|
|
|
3879
5363
|
function readAbortSignalReason(signal) {
|
|
3880
5364
|
return signal?.aborted ? signal.reason : void 0;
|
|
3881
5365
|
}
|
|
5366
|
+
function createStreamAbortedEvent(error, runId) {
|
|
5367
|
+
return {
|
|
5368
|
+
type: "aborted",
|
|
5369
|
+
runId,
|
|
5370
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5371
|
+
reason: streamAbortedReason(error)
|
|
5372
|
+
};
|
|
5373
|
+
}
|
|
5374
|
+
function shouldPublishAborted(error) {
|
|
5375
|
+
return DogpileError.isInstance(error) && (error.code === "aborted" || error.code === "timeout");
|
|
5376
|
+
}
|
|
5377
|
+
function streamAbortedReason(error) {
|
|
5378
|
+
if (DogpileError.isInstance(error)) {
|
|
5379
|
+
if (error.code === "timeout" || error.detail?.["reason"] === "timeout") return "timeout";
|
|
5380
|
+
}
|
|
5381
|
+
return "parent-aborted";
|
|
5382
|
+
}
|
|
3882
5383
|
function createStreamErrorEvent(error, runId) {
|
|
3883
5384
|
if (DogpileError.isInstance(error)) return {
|
|
3884
5385
|
type: "error",
|
|
@@ -3910,11 +5411,336 @@ function dogpileErrorStreamDetail(error) {
|
|
|
3910
5411
|
if (error.detail !== void 0) for (const [key, value] of Object.entries(error.detail)) detail[key] = value;
|
|
3911
5412
|
return detail;
|
|
3912
5413
|
}
|
|
5414
|
+
function openRunTracing(options) {
|
|
5415
|
+
if (!options.tracer) return;
|
|
5416
|
+
const runSpan = options.tracer.startSpan(DOGPILE_SPAN_NAMES.RUN, {
|
|
5417
|
+
...options.parentSpan ? { parent: options.parentSpan } : {},
|
|
5418
|
+
attributes: {
|
|
5419
|
+
"dogpile.run.protocol": options.protocolKind,
|
|
5420
|
+
"dogpile.run.tier": String(options.tier),
|
|
5421
|
+
"dogpile.run.intent": options.intent.slice(0, 200)
|
|
5422
|
+
}
|
|
5423
|
+
});
|
|
5424
|
+
return {
|
|
5425
|
+
tracer: options.tracer,
|
|
5426
|
+
runSpan,
|
|
5427
|
+
subRunSpans: /* @__PURE__ */ new Map(),
|
|
5428
|
+
agentTurnSpans: /* @__PURE__ */ new Map(),
|
|
5429
|
+
modelCallSpans: /* @__PURE__ */ new Map(),
|
|
5430
|
+
pendingModelRequests: /* @__PURE__ */ new Map(),
|
|
5431
|
+
agentTurnCounters: /* @__PURE__ */ new Map(),
|
|
5432
|
+
turnAccumByAgent: /* @__PURE__ */ new Map(),
|
|
5433
|
+
agentIds: /* @__PURE__ */ new Set(),
|
|
5434
|
+
turnCount: 0,
|
|
5435
|
+
lastCost: emptyCost()
|
|
5436
|
+
};
|
|
5437
|
+
}
|
|
5438
|
+
function openRunMetrics(options) {
|
|
5439
|
+
if (!options.metricsHook) return;
|
|
5440
|
+
return {
|
|
5441
|
+
metricsHook: options.metricsHook,
|
|
5442
|
+
logger: options.logger,
|
|
5443
|
+
startedAtMs: Date.now(),
|
|
5444
|
+
subRunStartTimes: /* @__PURE__ */ new Map(),
|
|
5445
|
+
totalCost: emptyCost(),
|
|
5446
|
+
nestedCost: emptyCost(),
|
|
5447
|
+
turns: 0
|
|
5448
|
+
};
|
|
5449
|
+
}
|
|
5450
|
+
function routeMetricsError(err, logger) {
|
|
5451
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5452
|
+
try {
|
|
5453
|
+
if (logger !== void 0) logger.error("dogpile:metricsHook threw", { error: msg });
|
|
5454
|
+
else console.error("dogpile:metricsHook threw", { error: msg });
|
|
5455
|
+
} catch {}
|
|
5456
|
+
}
|
|
5457
|
+
function fireHook(callback, snapshot, logger) {
|
|
5458
|
+
if (!callback) return;
|
|
5459
|
+
try {
|
|
5460
|
+
const result = callback(snapshot);
|
|
5461
|
+
if (result && typeof result.catch === "function") result.catch((err) => {
|
|
5462
|
+
routeMetricsError(err, logger);
|
|
5463
|
+
});
|
|
5464
|
+
} catch (err) {
|
|
5465
|
+
routeMetricsError(err, logger);
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
function buildRunSnapshot(result, startedAtMs) {
|
|
5469
|
+
const nestedCosts = nestedSubRunCosts(result);
|
|
5470
|
+
const outcome = result.trace.events.find((event) => event.type === "budget-stop") !== void 0 ? "budget-stopped" : "completed";
|
|
5471
|
+
const totalInputTokens = result.cost.inputTokens;
|
|
5472
|
+
const totalOutputTokens = result.cost.outputTokens;
|
|
5473
|
+
const totalCostUsd = result.cost.usd;
|
|
5474
|
+
return {
|
|
5475
|
+
outcome,
|
|
5476
|
+
inputTokens: totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0),
|
|
5477
|
+
outputTokens: totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0),
|
|
5478
|
+
costUsd: totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0),
|
|
5479
|
+
totalInputTokens,
|
|
5480
|
+
totalOutputTokens,
|
|
5481
|
+
totalCostUsd,
|
|
5482
|
+
turns: result.trace.events.filter((event) => event.type === "agent-turn").length,
|
|
5483
|
+
durationMs: Date.now() - startedAtMs
|
|
5484
|
+
};
|
|
5485
|
+
}
|
|
5486
|
+
function buildSubRunSnapshot(subResult, durationMs) {
|
|
5487
|
+
const nestedCosts = nestedSubRunCosts(subResult);
|
|
5488
|
+
const outcome = subResult.trace.events.find((event) => event.type === "budget-stop") !== void 0 ? "budget-stopped" : "completed";
|
|
5489
|
+
const totalInputTokens = subResult.cost.inputTokens;
|
|
5490
|
+
const totalOutputTokens = subResult.cost.outputTokens;
|
|
5491
|
+
const totalCostUsd = subResult.cost.usd;
|
|
5492
|
+
return {
|
|
5493
|
+
outcome,
|
|
5494
|
+
inputTokens: totalInputTokens - nestedCosts.reduce((sum, cost) => sum + cost.inputTokens, 0),
|
|
5495
|
+
outputTokens: totalOutputTokens - nestedCosts.reduce((sum, cost) => sum + cost.outputTokens, 0),
|
|
5496
|
+
costUsd: totalCostUsd - nestedCosts.reduce((sum, cost) => sum + cost.usd, 0),
|
|
5497
|
+
totalInputTokens,
|
|
5498
|
+
totalOutputTokens,
|
|
5499
|
+
totalCostUsd,
|
|
5500
|
+
turns: subResult.trace.events.filter((event) => event.type === "agent-turn").length,
|
|
5501
|
+
durationMs
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5504
|
+
function nestedSubRunCosts(result) {
|
|
5505
|
+
return result.trace.events.flatMap((event) => {
|
|
5506
|
+
if (event.type === "sub-run-completed") return [event.subResult.cost];
|
|
5507
|
+
if (event.type === "sub-run-failed") return [event.partialCost];
|
|
5508
|
+
return [];
|
|
5509
|
+
});
|
|
5510
|
+
}
|
|
5511
|
+
function subtractCost(total, nested) {
|
|
5512
|
+
return {
|
|
5513
|
+
usd: total.usd - nested.usd,
|
|
5514
|
+
inputTokens: total.inputTokens - nested.inputTokens,
|
|
5515
|
+
outputTokens: total.outputTokens - nested.outputTokens,
|
|
5516
|
+
totalTokens: total.totalTokens - nested.totalTokens
|
|
5517
|
+
};
|
|
5518
|
+
}
|
|
5519
|
+
function handleMetricsEvent(state, event) {
|
|
5520
|
+
if (event.parentRunIds !== void 0) return;
|
|
5521
|
+
switch (event.type) {
|
|
5522
|
+
case "agent-turn":
|
|
5523
|
+
state.totalCost = event.cost;
|
|
5524
|
+
state.turns += 1;
|
|
5525
|
+
break;
|
|
5526
|
+
case "broadcast":
|
|
5527
|
+
case "budget-stop":
|
|
5528
|
+
case "final":
|
|
5529
|
+
state.totalCost = event.cost;
|
|
5530
|
+
break;
|
|
5531
|
+
case "sub-run-started":
|
|
5532
|
+
state.subRunStartTimes.set(event.childRunId, Date.now());
|
|
5533
|
+
break;
|
|
5534
|
+
case "sub-run-completed": {
|
|
5535
|
+
state.totalCost = addCost(state.totalCost, event.subResult.cost);
|
|
5536
|
+
state.nestedCost = addCost(state.nestedCost, event.subResult.cost);
|
|
5537
|
+
const startMs = state.subRunStartTimes.get(event.childRunId);
|
|
5538
|
+
const durationMs = startMs !== void 0 ? Date.now() - startMs : 0;
|
|
5539
|
+
state.subRunStartTimes.delete(event.childRunId);
|
|
5540
|
+
const snapshot = buildSubRunSnapshot(event.subResult, durationMs);
|
|
5541
|
+
fireHook(state.metricsHook.onSubRunComplete, snapshot, state.logger);
|
|
5542
|
+
break;
|
|
5543
|
+
}
|
|
5544
|
+
case "sub-run-failed":
|
|
5545
|
+
state.totalCost = addCost(state.totalCost, event.partialCost);
|
|
5546
|
+
state.nestedCost = addCost(state.nestedCost, event.partialCost);
|
|
5547
|
+
state.subRunStartTimes.delete(event.childRunId);
|
|
5548
|
+
break;
|
|
5549
|
+
default: break;
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
function closeRunMetrics(state, result) {
|
|
5553
|
+
if (result !== void 0) {
|
|
5554
|
+
const snapshot = buildRunSnapshot(result, state.startedAtMs);
|
|
5555
|
+
fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
|
|
5556
|
+
return;
|
|
5557
|
+
}
|
|
5558
|
+
const ownCost = subtractCost(state.totalCost, state.nestedCost);
|
|
5559
|
+
const snapshot = {
|
|
5560
|
+
outcome: "aborted",
|
|
5561
|
+
inputTokens: ownCost.inputTokens,
|
|
5562
|
+
outputTokens: ownCost.outputTokens,
|
|
5563
|
+
costUsd: ownCost.usd,
|
|
5564
|
+
totalInputTokens: state.totalCost.inputTokens,
|
|
5565
|
+
totalOutputTokens: state.totalCost.outputTokens,
|
|
5566
|
+
totalCostUsd: state.totalCost.usd,
|
|
5567
|
+
turns: state.turns,
|
|
5568
|
+
durationMs: Date.now() - state.startedAtMs
|
|
5569
|
+
};
|
|
5570
|
+
fireHook(state.metricsHook.onRunComplete, snapshot, state.logger);
|
|
5571
|
+
}
|
|
5572
|
+
function handleTracingEvent(state, event) {
|
|
5573
|
+
if (event.parentRunIds !== void 0) return;
|
|
5574
|
+
if (state.runId === void 0) {
|
|
5575
|
+
state.runId = event.runId;
|
|
5576
|
+
state.runSpan.setAttribute("dogpile.run.id", event.runId);
|
|
5577
|
+
}
|
|
5578
|
+
switch (event.type) {
|
|
5579
|
+
case "model-request": {
|
|
5580
|
+
state.pendingModelRequests.set(event.callId, event);
|
|
5581
|
+
state.agentIds.add(event.agentId);
|
|
5582
|
+
if (!state.agentTurnSpans.has(event.agentId)) {
|
|
5583
|
+
const turnNumber = (state.agentTurnCounters.get(event.agentId) ?? 0) + 1;
|
|
5584
|
+
state.agentTurnCounters.set(event.agentId, turnNumber);
|
|
5585
|
+
const turnParent = state.subRunSpans.get(event.runId) ?? state.runSpan;
|
|
5586
|
+
const turnSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.AGENT_TURN, {
|
|
5587
|
+
parent: turnParent,
|
|
5588
|
+
attributes: {
|
|
5589
|
+
"dogpile.agent.id": event.agentId,
|
|
5590
|
+
"dogpile.agent.role": event.role,
|
|
5591
|
+
"dogpile.turn.number": turnNumber,
|
|
5592
|
+
"dogpile.model.id": event.modelId
|
|
5593
|
+
}
|
|
5594
|
+
});
|
|
5595
|
+
state.agentTurnSpans.set(event.agentId, turnSpan);
|
|
5596
|
+
}
|
|
5597
|
+
const callParent = state.agentTurnSpans.get(event.agentId) ?? state.subRunSpans.get(event.runId) ?? state.runSpan;
|
|
5598
|
+
const callSpan = state.tracer.startSpan(DOGPILE_SPAN_NAMES.MODEL_CALL, {
|
|
5599
|
+
parent: callParent,
|
|
5600
|
+
attributes: {
|
|
5601
|
+
"dogpile.model.id": event.modelId,
|
|
5602
|
+
"dogpile.call.id": event.callId,
|
|
5603
|
+
"dogpile.provider.id": event.providerId
|
|
5604
|
+
}
|
|
5605
|
+
});
|
|
5606
|
+
state.modelCallSpans.set(event.callId, callSpan);
|
|
5607
|
+
break;
|
|
5608
|
+
}
|
|
5609
|
+
case "model-response": {
|
|
5610
|
+
const span = state.modelCallSpans.get(event.callId);
|
|
5611
|
+
if (span) {
|
|
5612
|
+
const inputTokens = event.response.usage?.inputTokens ?? 0;
|
|
5613
|
+
const outputTokens = event.response.usage?.outputTokens ?? 0;
|
|
5614
|
+
const responseCost = {
|
|
5615
|
+
usd: event.response.costUsd ?? 0,
|
|
5616
|
+
inputTokens,
|
|
5617
|
+
outputTokens,
|
|
5618
|
+
totalTokens: event.response.usage?.totalTokens ?? inputTokens + outputTokens
|
|
5619
|
+
};
|
|
5620
|
+
span.setAttribute("dogpile.model.input_tokens", inputTokens);
|
|
5621
|
+
span.setAttribute("dogpile.model.output_tokens", outputTokens);
|
|
5622
|
+
if (event.response.costUsd !== void 0) span.setAttribute("dogpile.model.cost_usd", event.response.costUsd);
|
|
5623
|
+
span.setStatus("ok");
|
|
5624
|
+
span.end();
|
|
5625
|
+
state.modelCallSpans.delete(event.callId);
|
|
5626
|
+
const accum = state.turnAccumByAgent.get(event.agentId) ?? {
|
|
5627
|
+
inputTokens: 0,
|
|
5628
|
+
outputTokens: 0,
|
|
5629
|
+
costUsd: 0
|
|
5630
|
+
};
|
|
5631
|
+
accum.inputTokens += inputTokens;
|
|
5632
|
+
accum.outputTokens += outputTokens;
|
|
5633
|
+
accum.costUsd += responseCost.usd;
|
|
5634
|
+
state.turnAccumByAgent.set(event.agentId, accum);
|
|
5635
|
+
state.lastCost = addCost(state.lastCost, responseCost);
|
|
5636
|
+
}
|
|
5637
|
+
state.pendingModelRequests.delete(event.callId);
|
|
5638
|
+
break;
|
|
5639
|
+
}
|
|
5640
|
+
case "agent-turn": {
|
|
5641
|
+
state.agentIds.add(event.agentId);
|
|
5642
|
+
state.turnCount += 1;
|
|
5643
|
+
state.lastCost = event.cost;
|
|
5644
|
+
const turnSpan = state.agentTurnSpans.get(event.agentId);
|
|
5645
|
+
if (turnSpan) {
|
|
5646
|
+
turnSpan.setAttribute("dogpile.agent.role", event.role);
|
|
5647
|
+
const accum = state.turnAccumByAgent.get(event.agentId);
|
|
5648
|
+
turnSpan.setAttribute("dogpile.turn.cost_usd", accum?.costUsd ?? 0);
|
|
5649
|
+
turnSpan.setAttribute("dogpile.turn.input_tokens", accum?.inputTokens ?? 0);
|
|
5650
|
+
turnSpan.setAttribute("dogpile.turn.output_tokens", accum?.outputTokens ?? 0);
|
|
5651
|
+
turnSpan.setStatus("ok");
|
|
5652
|
+
turnSpan.end();
|
|
5653
|
+
state.agentTurnSpans.delete(event.agentId);
|
|
5654
|
+
}
|
|
5655
|
+
state.turnAccumByAgent.delete(event.agentId);
|
|
5656
|
+
break;
|
|
5657
|
+
}
|
|
5658
|
+
case "broadcast":
|
|
5659
|
+
case "budget-stop":
|
|
5660
|
+
case "final":
|
|
5661
|
+
state.lastCost = event.cost;
|
|
5662
|
+
break;
|
|
5663
|
+
case "sub-run-started": {
|
|
5664
|
+
const span = state.tracer.startSpan(DOGPILE_SPAN_NAMES.SUB_RUN, {
|
|
5665
|
+
parent: state.runSpan,
|
|
5666
|
+
attributes: {
|
|
5667
|
+
"dogpile.sub_run.child_run_id": event.childRunId,
|
|
5668
|
+
"dogpile.sub_run.parent_run_id": event.parentRunId,
|
|
5669
|
+
"dogpile.sub_run.depth": event.depth
|
|
5670
|
+
}
|
|
5671
|
+
});
|
|
5672
|
+
state.subRunSpans.set(event.childRunId, span);
|
|
5673
|
+
break;
|
|
5674
|
+
}
|
|
5675
|
+
case "sub-run-completed": {
|
|
5676
|
+
const span = state.subRunSpans.get(event.childRunId);
|
|
5677
|
+
if (span) {
|
|
5678
|
+
span.setStatus("ok");
|
|
5679
|
+
span.end();
|
|
5680
|
+
state.subRunSpans.delete(event.childRunId);
|
|
5681
|
+
}
|
|
5682
|
+
break;
|
|
5683
|
+
}
|
|
5684
|
+
case "sub-run-failed": {
|
|
5685
|
+
const span = state.subRunSpans.get(event.childRunId);
|
|
5686
|
+
if (span) {
|
|
5687
|
+
span.setStatus("error", event.error.message);
|
|
5688
|
+
span.end();
|
|
5689
|
+
state.subRunSpans.delete(event.childRunId);
|
|
5690
|
+
}
|
|
5691
|
+
break;
|
|
5692
|
+
}
|
|
5693
|
+
default: break;
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
function closeRunTracing(state, result, error) {
|
|
5697
|
+
if (error !== void 0) {
|
|
5698
|
+
if (state.runId !== void 0) state.runSpan.setAttribute("dogpile.run.id", state.runId);
|
|
5699
|
+
state.runSpan.setAttribute("dogpile.run.agent_count", state.agentIds.size);
|
|
5700
|
+
state.runSpan.setAttribute("dogpile.run.turn_count", state.turnCount);
|
|
5701
|
+
state.runSpan.setAttribute("dogpile.run.cost_usd", state.lastCost.usd);
|
|
5702
|
+
state.runSpan.setAttribute("dogpile.run.input_tokens", state.lastCost.inputTokens);
|
|
5703
|
+
state.runSpan.setAttribute("dogpile.run.output_tokens", state.lastCost.outputTokens);
|
|
5704
|
+
state.runSpan.setAttribute("dogpile.run.outcome", "aborted");
|
|
5705
|
+
state.runSpan.setStatus("error", error instanceof Error ? error.message : String(error));
|
|
5706
|
+
closeOpenTracingSpans(state);
|
|
5707
|
+
state.runSpan.end();
|
|
5708
|
+
return;
|
|
5709
|
+
}
|
|
5710
|
+
if (result === void 0) {
|
|
5711
|
+
closeOpenTracingSpans(state);
|
|
5712
|
+
state.runSpan.end();
|
|
5713
|
+
return;
|
|
5714
|
+
}
|
|
5715
|
+
const terminationReason = result.trace.events.find((event) => event.type === "budget-stop")?.reason;
|
|
5716
|
+
const outcome = terminationReason !== void 0 ? "budget-stopped" : "completed";
|
|
5717
|
+
state.runSpan.setAttribute("dogpile.run.id", result.trace.runId);
|
|
5718
|
+
state.runSpan.setAttribute("dogpile.run.agent_count", result.trace.agentsUsed.length);
|
|
5719
|
+
state.runSpan.setAttribute("dogpile.run.turn_count", result.trace.events.filter((event) => event.type === "agent-turn").length);
|
|
5720
|
+
state.runSpan.setAttribute("dogpile.run.cost_usd", result.cost.usd);
|
|
5721
|
+
state.runSpan.setAttribute("dogpile.run.input_tokens", result.cost.inputTokens);
|
|
5722
|
+
state.runSpan.setAttribute("dogpile.run.output_tokens", result.cost.outputTokens);
|
|
5723
|
+
state.runSpan.setAttribute("dogpile.run.outcome", outcome);
|
|
5724
|
+
if (terminationReason !== void 0) state.runSpan.setAttribute("dogpile.run.termination_reason", terminationReason);
|
|
5725
|
+
state.runSpan.setStatus("ok");
|
|
5726
|
+
closeOpenTracingSpans(state);
|
|
5727
|
+
state.runSpan.end();
|
|
5728
|
+
}
|
|
5729
|
+
function closeOpenTracingSpans(state) {
|
|
5730
|
+
for (const span of state.modelCallSpans.values()) span.end();
|
|
5731
|
+
state.modelCallSpans.clear();
|
|
5732
|
+
for (const span of state.agentTurnSpans.values()) span.end();
|
|
5733
|
+
state.agentTurnSpans.clear();
|
|
5734
|
+
for (const span of state.subRunSpans.values()) span.end();
|
|
5735
|
+
state.subRunSpans.clear();
|
|
5736
|
+
}
|
|
3913
5737
|
async function runNonStreamingProtocol(options) {
|
|
5738
|
+
const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
|
|
3914
5739
|
const abortLifecycle = createNonStreamingAbortLifecycle({
|
|
3915
5740
|
callerSignal: options.signal,
|
|
3916
5741
|
timeoutMs: runtimeTimeoutMs(options),
|
|
3917
|
-
providerId: options.model.id
|
|
5742
|
+
providerId: options.model.id,
|
|
5743
|
+
timeoutErrorSource: options.currentDepth !== void 0 && options.currentDepth > 0 && options.parentDeadlineMs === void 0 ? "engine" : "runtime"
|
|
3918
5744
|
});
|
|
3919
5745
|
try {
|
|
3920
5746
|
const emittedEvents = [];
|
|
@@ -3923,7 +5749,8 @@ async function runNonStreamingProtocol(options) {
|
|
|
3923
5749
|
...abortLifecycle.signal !== void 0 ? { signal: abortLifecycle.signal } : {},
|
|
3924
5750
|
emit(event) {
|
|
3925
5751
|
emittedEvents.push(event);
|
|
3926
|
-
}
|
|
5752
|
+
},
|
|
5753
|
+
failureInstancesByChildRunId
|
|
3927
5754
|
}));
|
|
3928
5755
|
const events = emittedEvents.length > 0 ? emittedEvents : result.trace.events;
|
|
3929
5756
|
const trace = {
|
|
@@ -3942,12 +5769,16 @@ async function runNonStreamingProtocol(options) {
|
|
|
3942
5769
|
events
|
|
3943
5770
|
}),
|
|
3944
5771
|
eventLog: createRunEventLog(trace.runId, trace.protocol, events),
|
|
3945
|
-
trace
|
|
5772
|
+
trace,
|
|
5773
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
3946
5774
|
};
|
|
5775
|
+
const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
|
|
5776
|
+
if (terminalThrow) throw terminalThrow;
|
|
3947
5777
|
return canonicalizeRunResult(await abortLifecycle.run(applyRunEvaluation(runResult, options.evaluate)));
|
|
3948
5778
|
} catch (error) {
|
|
3949
5779
|
throw abortLifecycle.translateError(error);
|
|
3950
5780
|
} finally {
|
|
5781
|
+
failureInstancesByChildRunId.clear();
|
|
3951
5782
|
abortLifecycle.cleanup();
|
|
3952
5783
|
}
|
|
3953
5784
|
}
|
|
@@ -3977,7 +5808,39 @@ function finalEventWithEvaluation(event, evaluation) {
|
|
|
3977
5808
|
evaluation
|
|
3978
5809
|
};
|
|
3979
5810
|
}
|
|
3980
|
-
function runProtocol(options) {
|
|
5811
|
+
async function runProtocol(options) {
|
|
5812
|
+
const tracing = openRunTracing({
|
|
5813
|
+
...options.tracer ? { tracer: options.tracer } : {},
|
|
5814
|
+
...options.parentSpan ? { parentSpan: options.parentSpan } : {},
|
|
5815
|
+
intent: options.intent,
|
|
5816
|
+
protocolKind: options.protocol.kind,
|
|
5817
|
+
tier: options.tier
|
|
5818
|
+
});
|
|
5819
|
+
const metrics = openRunMetrics({
|
|
5820
|
+
...options.metricsHook ? { metricsHook: options.metricsHook } : {},
|
|
5821
|
+
...options.logger ? { logger: options.logger } : {}
|
|
5822
|
+
});
|
|
5823
|
+
const emitForProtocol = tracing || metrics || options.emit ? (event) => {
|
|
5824
|
+
if (tracing) handleTracingEvent(tracing, event);
|
|
5825
|
+
if (metrics) handleMetricsEvent(metrics, event);
|
|
5826
|
+
options.emit?.(event);
|
|
5827
|
+
} : void 0;
|
|
5828
|
+
const protocolOptions = tracing ? {
|
|
5829
|
+
...options,
|
|
5830
|
+
subRunSpansByChildId: tracing.subRunSpans
|
|
5831
|
+
} : options;
|
|
5832
|
+
try {
|
|
5833
|
+
const result = await runProtocolInner(protocolOptions, emitForProtocol);
|
|
5834
|
+
if (tracing) closeRunTracing(tracing, result);
|
|
5835
|
+
if (metrics && (options.currentDepth === 0 || options.currentDepth === void 0)) closeRunMetrics(metrics, result);
|
|
5836
|
+
return result;
|
|
5837
|
+
} catch (error) {
|
|
5838
|
+
if (tracing) closeRunTracing(tracing, void 0, error);
|
|
5839
|
+
if (metrics && (options.currentDepth === 0 || options.currentDepth === void 0)) closeRunMetrics(metrics, void 0);
|
|
5840
|
+
throw error;
|
|
5841
|
+
}
|
|
5842
|
+
}
|
|
5843
|
+
function runProtocolInner(options, emitForProtocol) {
|
|
3981
5844
|
switch (options.protocol.kind) {
|
|
3982
5845
|
case "sequential": return runSequential({
|
|
3983
5846
|
intent: options.intent,
|
|
@@ -3992,7 +5855,7 @@ function runProtocol(options) {
|
|
|
3992
5855
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
3993
5856
|
...options.terminate ? { terminate: options.terminate } : {},
|
|
3994
5857
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
3995
|
-
...
|
|
5858
|
+
...emitForProtocol ? { emit: emitForProtocol } : {}
|
|
3996
5859
|
});
|
|
3997
5860
|
case "broadcast": return runBroadcast({
|
|
3998
5861
|
intent: options.intent,
|
|
@@ -4007,7 +5870,7 @@ function runProtocol(options) {
|
|
|
4007
5870
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
4008
5871
|
...options.terminate ? { terminate: options.terminate } : {},
|
|
4009
5872
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
4010
|
-
...
|
|
5873
|
+
...emitForProtocol ? { emit: emitForProtocol } : {}
|
|
4011
5874
|
});
|
|
4012
5875
|
case "coordinator": return runCoordinator({
|
|
4013
5876
|
intent: options.intent,
|
|
@@ -4022,7 +5885,27 @@ function runProtocol(options) {
|
|
|
4022
5885
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
4023
5886
|
...options.terminate ? { terminate: options.terminate } : {},
|
|
4024
5887
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
4025
|
-
...
|
|
5888
|
+
...emitForProtocol ? { emit: emitForProtocol } : {},
|
|
5889
|
+
...options.streamEvents !== void 0 ? { streamEvents: options.streamEvents } : {},
|
|
5890
|
+
currentDepth: options.currentDepth ?? 0,
|
|
5891
|
+
effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
|
|
5892
|
+
effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN,
|
|
5893
|
+
onChildFailure: options.onChildFailure ?? "continue",
|
|
5894
|
+
...options.parentDeadlineMs !== void 0 ? { parentDeadlineMs: options.parentDeadlineMs } : {},
|
|
5895
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {},
|
|
5896
|
+
...options.registerAbortDrain !== void 0 ? { registerAbortDrain: options.registerAbortDrain } : {},
|
|
5897
|
+
...options.failureInstancesByChildRunId !== void 0 ? { failureInstancesByChildRunId: options.failureInstancesByChildRunId } : {},
|
|
5898
|
+
runProtocol: (childInput) => {
|
|
5899
|
+
const { runId: childRunId, ...childProtocolInput } = childInput;
|
|
5900
|
+
const childParent = options.subRunSpansByChildId?.get(childRunId) ?? options.parentSpan;
|
|
5901
|
+
return runProtocol({
|
|
5902
|
+
...childProtocolInput,
|
|
5903
|
+
protocol: normalizeProtocol(childProtocolInput.protocol),
|
|
5904
|
+
...options.tracer ? { tracer: options.tracer } : {},
|
|
5905
|
+
...childParent ? { parentSpan: childParent } : {},
|
|
5906
|
+
...options.logger ? { logger: options.logger } : {}
|
|
5907
|
+
});
|
|
5908
|
+
}
|
|
4026
5909
|
});
|
|
4027
5910
|
case "shared": return runShared({
|
|
4028
5911
|
intent: options.intent,
|
|
@@ -4037,7 +5920,7 @@ function runProtocol(options) {
|
|
|
4037
5920
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
4038
5921
|
...options.terminate ? { terminate: options.terminate } : {},
|
|
4039
5922
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
4040
|
-
...
|
|
5923
|
+
...emitForProtocol ? { emit: emitForProtocol } : {}
|
|
4041
5924
|
});
|
|
4042
5925
|
}
|
|
4043
5926
|
}
|
|
@@ -4085,13 +5968,22 @@ function stream(options) {
|
|
|
4085
5968
|
* the ergonomic {@link RunResult} wrapper from the JSON-serializable
|
|
4086
5969
|
* {@link Trace} returned by a previous `run()`, `stream()`, or
|
|
4087
5970
|
* `Dogpile.pile()` call.
|
|
5971
|
+
*
|
|
5972
|
+
* Tracing and metrics: replay is intentionally tracing-free and metrics-free.
|
|
5973
|
+
* Even when an engine instance has been configured with a `tracer` or
|
|
5974
|
+
* `metricsHook` on its `EngineOptions`, calling this function emits no spans
|
|
5975
|
+
* or callbacks — replaying historical events with current timestamps would
|
|
5976
|
+
* confuse observability backends. See `docs/developer-usage.md`.
|
|
4088
5977
|
*/
|
|
4089
5978
|
function replay(trace) {
|
|
4090
5979
|
const cost = trace.finalOutput.cost;
|
|
4091
5980
|
const lastEvent = trace.events.at(-1);
|
|
5981
|
+
const accounting = recomputeAccountingFromTrace(trace);
|
|
5982
|
+
const replayThrow = resolveReplayTerminalThrow(trace);
|
|
5983
|
+
if (replayThrow) throw replayThrow;
|
|
4092
5984
|
const baseResult = {
|
|
4093
5985
|
output: trace.finalOutput.output,
|
|
4094
|
-
eventLog: createRunEventLog(trace.runId, trace.protocol, trace.
|
|
5986
|
+
eventLog: createRunEventLog(trace.runId, trace.protocol, synthesizeProviderEvents(trace, trace.providerCalls)),
|
|
4095
5987
|
trace,
|
|
4096
5988
|
transcript: trace.transcript,
|
|
4097
5989
|
usage: createRunUsage(cost),
|
|
@@ -4103,14 +5995,9 @@ function replay(trace) {
|
|
|
4103
5995
|
agentsUsed: trace.agentsUsed,
|
|
4104
5996
|
events: trace.events
|
|
4105
5997
|
}),
|
|
4106
|
-
accounting
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
4110
|
-
cost,
|
|
4111
|
-
events: trace.events
|
|
4112
|
-
}),
|
|
4113
|
-
cost
|
|
5998
|
+
accounting,
|
|
5999
|
+
cost,
|
|
6000
|
+
health: computeHealth(trace, DEFAULT_HEALTH_THRESHOLDS)
|
|
4114
6001
|
};
|
|
4115
6002
|
if (lastEvent?.type !== "final") return baseResult;
|
|
4116
6003
|
return {
|
|
@@ -4119,30 +6006,137 @@ function replay(trace) {
|
|
|
4119
6006
|
...lastEvent.evaluation !== void 0 ? { evaluation: lastEvent.evaluation } : {}
|
|
4120
6007
|
};
|
|
4121
6008
|
}
|
|
6009
|
+
function synthesizeProviderEvents(trace, providerCalls) {
|
|
6010
|
+
if (trace.events.some((event) => event.type === "model-request" || event.type === "model-response")) return trace.events;
|
|
6011
|
+
const baseEvents = trace.events.filter((event) => event.type !== "model-request" && event.type !== "model-response");
|
|
6012
|
+
const result = [];
|
|
6013
|
+
let turnCount = 0;
|
|
6014
|
+
for (const event of baseEvents) {
|
|
6015
|
+
if (event.type === "agent-turn") {
|
|
6016
|
+
const call = providerCalls[turnCount];
|
|
6017
|
+
if (call !== void 0) {
|
|
6018
|
+
const modelId = typeof call.modelId === "string" && call.modelId.length > 0 ? call.modelId : call.providerId;
|
|
6019
|
+
result.push({
|
|
6020
|
+
type: "model-request",
|
|
6021
|
+
runId: trace.runId,
|
|
6022
|
+
callId: call.callId,
|
|
6023
|
+
providerId: call.providerId,
|
|
6024
|
+
modelId,
|
|
6025
|
+
startedAt: call.startedAt,
|
|
6026
|
+
agentId: call.agentId,
|
|
6027
|
+
role: call.role,
|
|
6028
|
+
request: call.request
|
|
6029
|
+
});
|
|
6030
|
+
result.push({
|
|
6031
|
+
type: "model-response",
|
|
6032
|
+
runId: trace.runId,
|
|
6033
|
+
callId: call.callId,
|
|
6034
|
+
providerId: call.providerId,
|
|
6035
|
+
modelId,
|
|
6036
|
+
startedAt: call.startedAt,
|
|
6037
|
+
completedAt: call.completedAt,
|
|
6038
|
+
agentId: call.agentId,
|
|
6039
|
+
role: call.role,
|
|
6040
|
+
response: call.response
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
6043
|
+
turnCount += 1;
|
|
6044
|
+
}
|
|
6045
|
+
result.push(event);
|
|
6046
|
+
}
|
|
6047
|
+
return result;
|
|
6048
|
+
}
|
|
6049
|
+
function resolveRuntimeTerminalThrow(trace, failureInstancesByChildRunId) {
|
|
6050
|
+
if (trace.triggeringFailureForAbortMode !== void 0) return failureInstancesByChildRunId.get(trace.triggeringFailureForAbortMode.childRunId) ?? null;
|
|
6051
|
+
const finalEvent = trace.events.at(-1);
|
|
6052
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === void 0) return null;
|
|
6053
|
+
const lastFailure = findLastRealFailure(trace.events, failureInstancesByChildRunId);
|
|
6054
|
+
if (lastFailure === null) return null;
|
|
6055
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) return null;
|
|
6056
|
+
return lastFailure.error;
|
|
6057
|
+
}
|
|
6058
|
+
function findLastRealFailure(events, failureInstancesByChildRunId) {
|
|
6059
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
6060
|
+
const event = events[index];
|
|
6061
|
+
if (event?.type !== "sub-run-failed") continue;
|
|
6062
|
+
const instance = failureInstancesByChildRunId.get(event.childRunId);
|
|
6063
|
+
if (instance) return {
|
|
6064
|
+
error: instance,
|
|
6065
|
+
eventIndex: index
|
|
6066
|
+
};
|
|
6067
|
+
}
|
|
6068
|
+
return null;
|
|
6069
|
+
}
|
|
6070
|
+
function resolveReplayTerminalThrow(trace) {
|
|
6071
|
+
if (trace.triggeringFailureForAbortMode !== void 0) return dogpileErrorFromSerializedPayload(trace.triggeringFailureForAbortMode.error);
|
|
6072
|
+
const finalEvent = trace.events.at(-1);
|
|
6073
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === void 0) return null;
|
|
6074
|
+
const lastFailure = reconstructLastRealFailure(trace.events);
|
|
6075
|
+
if (lastFailure === null) return null;
|
|
6076
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) return null;
|
|
6077
|
+
return lastFailure.error;
|
|
6078
|
+
}
|
|
6079
|
+
function reconstructLastRealFailure(events) {
|
|
6080
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
6081
|
+
const event = events[index];
|
|
6082
|
+
if (event?.type !== "sub-run-failed" || isSyntheticSubRunFailure(event)) continue;
|
|
6083
|
+
return {
|
|
6084
|
+
error: dogpileErrorFromSerializedPayload(event.error),
|
|
6085
|
+
eventIndex: index
|
|
6086
|
+
};
|
|
6087
|
+
}
|
|
6088
|
+
return null;
|
|
6089
|
+
}
|
|
6090
|
+
function hasFinalSynthesisAfterEvent(trace, eventIndex) {
|
|
6091
|
+
return trace.protocolDecisions.some((decision) => {
|
|
6092
|
+
return decision.phase === "final-synthesis" && decision.eventIndex > eventIndex;
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
function isSyntheticSubRunFailure(event) {
|
|
6096
|
+
const reason = event.error.detail?.["reason"];
|
|
6097
|
+
return reason === "sibling-failed" || reason === "parent-aborted";
|
|
6098
|
+
}
|
|
6099
|
+
function dogpileErrorFromSerializedPayload(input) {
|
|
6100
|
+
return new DogpileError({
|
|
6101
|
+
code: input.code,
|
|
6102
|
+
message: input.message,
|
|
6103
|
+
...input.providerId !== void 0 ? { providerId: input.providerId } : {},
|
|
6104
|
+
...input.detail !== void 0 ? { detail: input.detail } : {}
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
4122
6107
|
/**
|
|
4123
6108
|
* Replay a saved completed trace as a stream without invoking a model provider.
|
|
4124
6109
|
*
|
|
4125
6110
|
* @remarks
|
|
4126
|
-
* This is the streaming counterpart to {@link replay}. It yields the
|
|
4127
|
-
*
|
|
4128
|
-
*
|
|
4129
|
-
* replay remains storage-free and
|
|
6111
|
+
* This is the streaming counterpart to {@link replay}. It yields the same
|
|
6112
|
+
* event sequence exposed by the replayed result event log, including legacy
|
|
6113
|
+
* provenance synthesis when a saved trace predates model request/response
|
|
6114
|
+
* events. Since all data comes from the trace, replay remains storage-free and
|
|
6115
|
+
* provider-free.
|
|
6116
|
+
*
|
|
6117
|
+
* Tracing and metrics: replayStream is intentionally tracing-free and
|
|
6118
|
+
* metrics-free. Even when an engine instance has been configured with a
|
|
6119
|
+
* `tracer` or `metricsHook` on its `EngineOptions`, calling this function
|
|
6120
|
+
* emits no spans or callbacks — replaying historical events with current
|
|
6121
|
+
* timestamps would confuse observability backends. See `docs/developer-usage.md`.
|
|
4130
6122
|
*/
|
|
4131
6123
|
function replayStream(trace) {
|
|
6124
|
+
const result = Promise.resolve(replay(trace));
|
|
6125
|
+
const replayEvents = replayStreamEvents(trace);
|
|
4132
6126
|
return {
|
|
4133
6127
|
get status() {
|
|
4134
6128
|
return "completed";
|
|
4135
6129
|
},
|
|
4136
|
-
result
|
|
6130
|
+
result,
|
|
4137
6131
|
cancel() {},
|
|
4138
6132
|
subscribe(subscriber) {
|
|
4139
|
-
for (const event of
|
|
6133
|
+
for (const event of replayEvents) subscriber(event);
|
|
4140
6134
|
return { unsubscribe() {} };
|
|
4141
6135
|
},
|
|
4142
6136
|
[Symbol.asyncIterator]() {
|
|
4143
6137
|
let index = 0;
|
|
4144
6138
|
return { next() {
|
|
4145
|
-
const event =
|
|
6139
|
+
const event = replayEvents[index];
|
|
4146
6140
|
if (event) {
|
|
4147
6141
|
index += 1;
|
|
4148
6142
|
return Promise.resolve({
|
|
@@ -4158,6 +6152,22 @@ function replayStream(trace) {
|
|
|
4158
6152
|
}
|
|
4159
6153
|
};
|
|
4160
6154
|
}
|
|
6155
|
+
function replayStreamEvents(trace, parentRunIds = []) {
|
|
6156
|
+
const events = [];
|
|
6157
|
+
for (const event of synthesizeProviderEvents(trace, trace.providerCalls)) {
|
|
6158
|
+
if (event.type === "sub-run-completed") events.push(...replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]));
|
|
6159
|
+
events.push(wrapReplayStreamEvent(event, parentRunIds));
|
|
6160
|
+
}
|
|
6161
|
+
return events;
|
|
6162
|
+
}
|
|
6163
|
+
function wrapReplayStreamEvent(event, parentRunIds) {
|
|
6164
|
+
if (parentRunIds.length === 0) return event;
|
|
6165
|
+
const inbound = event.parentRunIds;
|
|
6166
|
+
return {
|
|
6167
|
+
...event,
|
|
6168
|
+
parentRunIds: [...parentRunIds, ...inbound ?? []]
|
|
6169
|
+
};
|
|
6170
|
+
}
|
|
4161
6171
|
function wireCallerAbortSignal(callerSignal, abortController, cancelRun) {
|
|
4162
6172
|
if (!callerSignal) return () => {};
|
|
4163
6173
|
const cancelFromCaller = () => {
|
|
@@ -4181,7 +6191,10 @@ function createStreamCancellationError(providerId, cause) {
|
|
|
4181
6191
|
retryable: false,
|
|
4182
6192
|
providerId,
|
|
4183
6193
|
...cause !== void 0 ? { cause } : {},
|
|
4184
|
-
detail: {
|
|
6194
|
+
detail: {
|
|
6195
|
+
status: "cancelled",
|
|
6196
|
+
reason: "parent-aborted"
|
|
6197
|
+
}
|
|
4185
6198
|
});
|
|
4186
6199
|
}
|
|
4187
6200
|
function isCancellationError(error) {
|
|
@@ -4195,6 +6208,20 @@ function withHighLevelDefaults(options) {
|
|
|
4195
6208
|
tier: options.tier ?? defaultHighLevelTier
|
|
4196
6209
|
};
|
|
4197
6210
|
}
|
|
6211
|
+
function assertRunDoesNotRaiseEngineMax(path, runValue, engineValue) {
|
|
6212
|
+
if (runValue === void 0 || runValue <= engineValue) return;
|
|
6213
|
+
throw new DogpileError({
|
|
6214
|
+
code: "invalid-configuration",
|
|
6215
|
+
message: `${path} cannot raise the engine ceiling (${engineValue}).`,
|
|
6216
|
+
retryable: false,
|
|
6217
|
+
detail: {
|
|
6218
|
+
kind: "configuration-validation",
|
|
6219
|
+
path,
|
|
6220
|
+
expected: `integer <= ${engineValue}`,
|
|
6221
|
+
actual: runValue
|
|
6222
|
+
}
|
|
6223
|
+
});
|
|
6224
|
+
}
|
|
4198
6225
|
/**
|
|
4199
6226
|
* Branded high-level SDK namespace.
|
|
4200
6227
|
*
|
|
@@ -4221,6 +6248,8 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4221
6248
|
validateOptions(options);
|
|
4222
6249
|
const providerId = options.id ?? `openai-compatible:${options.model}`;
|
|
4223
6250
|
const fetchImplementation = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
6251
|
+
const detectedLocality = classifyHostLocality(new URL(String(options.baseURL ?? defaultBaseURL)).hostname);
|
|
6252
|
+
const resolvedLocality = options.locality === "local" ? "local" : options.locality === "remote" ? "remote" : detectedLocality;
|
|
4224
6253
|
if (!fetchImplementation) throw new DogpileError({
|
|
4225
6254
|
code: "invalid-configuration",
|
|
4226
6255
|
message: "createOpenAICompatibleProvider() requires a fetch implementation in this runtime.",
|
|
@@ -4234,6 +6263,8 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4234
6263
|
});
|
|
4235
6264
|
return {
|
|
4236
6265
|
id: providerId,
|
|
6266
|
+
modelId: options.model,
|
|
6267
|
+
metadata: { locality: resolvedLocality },
|
|
4237
6268
|
async generate(request) {
|
|
4238
6269
|
let response;
|
|
4239
6270
|
try {
|
|
@@ -4246,9 +6277,11 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4246
6277
|
} catch (error) {
|
|
4247
6278
|
throw normalizeFetchError(error, providerId);
|
|
4248
6279
|
}
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
6280
|
+
if (!response.ok) {
|
|
6281
|
+
const payload = await readJsonLenient(response);
|
|
6282
|
+
throw createProviderError(response, payload, providerId);
|
|
6283
|
+
}
|
|
6284
|
+
const completion = asChatCompletionResponse(await readJson(response, providerId), providerId);
|
|
4252
6285
|
const text = readAssistantText(completion, providerId);
|
|
4253
6286
|
const usage = normalizeUsage(completion.usage);
|
|
4254
6287
|
const finishReason = normalizeFinishReason(completion.choices?.[0]?.finish_reason);
|
|
@@ -4277,6 +6310,22 @@ function validateOptions(options) {
|
|
|
4277
6310
|
if (options.fetch !== void 0 && typeof options.fetch !== "function") throwInvalid("fetch", "a fetch-compatible function when provided");
|
|
4278
6311
|
if (options.maxOutputTokens !== void 0 && (!Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens <= 0)) throwInvalid("maxOutputTokens", "a positive integer when provided");
|
|
4279
6312
|
if (options.costEstimator !== void 0 && typeof options.costEstimator !== "function") throwInvalid("costEstimator", "a function when provided");
|
|
6313
|
+
if (options.locality !== void 0 && options.locality !== "local" && options.locality !== "remote") throwInvalid("locality", "\"local\" | \"remote\" when provided");
|
|
6314
|
+
if (options.locality === "remote") {
|
|
6315
|
+
const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
|
|
6316
|
+
if (classifyHostLocality(baseURL.hostname) === "local") throw new DogpileError({
|
|
6317
|
+
code: "invalid-configuration",
|
|
6318
|
+
message: `locality "remote" cannot be set when baseURL resolves to a local host (${baseURL.hostname}).`,
|
|
6319
|
+
retryable: false,
|
|
6320
|
+
detail: {
|
|
6321
|
+
kind: "configuration-validation",
|
|
6322
|
+
path: "locality",
|
|
6323
|
+
expected: "\"local\" (or omit to auto-detect)",
|
|
6324
|
+
reason: "remote-override-on-local-host",
|
|
6325
|
+
host: baseURL.hostname
|
|
6326
|
+
}
|
|
6327
|
+
});
|
|
6328
|
+
}
|
|
4280
6329
|
}
|
|
4281
6330
|
function throwInvalid(path, expected) {
|
|
4282
6331
|
throw new DogpileError({
|
|
@@ -4290,6 +6339,38 @@ function throwInvalid(path, expected) {
|
|
|
4290
6339
|
}
|
|
4291
6340
|
});
|
|
4292
6341
|
}
|
|
6342
|
+
/**
|
|
6343
|
+
* Classify a URL hostname as "local" or "remote" per Phase 3 D-02.
|
|
6344
|
+
* Local: localhost, *.local mDNS, IPv4 loopback (127.0.0.0/8), RFC1918
|
|
6345
|
+
* (10/8, 172.16/12, 192.168/16), link-local (169.254/16), IPv6 loopback (::1),
|
|
6346
|
+
* IPv6 ULA (fc00::/7), IPv6 link-local (fe80::/10).
|
|
6347
|
+
*
|
|
6348
|
+
* Pure function: no I/O, no side effects. Exported for tests and future reuse.
|
|
6349
|
+
*/
|
|
6350
|
+
function classifyHostLocality(host) {
|
|
6351
|
+
const lower = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
6352
|
+
const mappedIpv4 = ipv4MappedToDottedQuad(lower);
|
|
6353
|
+
if (mappedIpv4 !== void 0) return classifyHostLocality(mappedIpv4);
|
|
6354
|
+
if (lower === "localhost") return "local";
|
|
6355
|
+
if (lower.endsWith(".local")) return "local";
|
|
6356
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(lower)) return "local";
|
|
6357
|
+
if (/^10(?:\.\d{1,3}){3}$/.test(lower)) return "local";
|
|
6358
|
+
if (/^172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
6359
|
+
if (/^192\.168(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
6360
|
+
if (/^169\.254(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
6361
|
+
if (lower === "::1") return "local";
|
|
6362
|
+
if (/^f[cd][0-9a-f]{2}:/.test(lower)) return "local";
|
|
6363
|
+
if (/^fe[89ab][0-9a-f]?:/.test(lower)) return "local";
|
|
6364
|
+
return "remote";
|
|
6365
|
+
}
|
|
6366
|
+
function ipv4MappedToDottedQuad(host) {
|
|
6367
|
+
const match = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
|
|
6368
|
+
if (match === null) return;
|
|
6369
|
+
const high = Number.parseInt(match[1] ?? "", 16);
|
|
6370
|
+
const low = Number.parseInt(match[2] ?? "", 16);
|
|
6371
|
+
if (!Number.isFinite(high) || !Number.isFinite(low)) return;
|
|
6372
|
+
return `${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`;
|
|
6373
|
+
}
|
|
4293
6374
|
function createURL(options) {
|
|
4294
6375
|
const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
|
|
4295
6376
|
const path = options.path ?? defaultPath;
|
|
@@ -4339,6 +6420,13 @@ async function readJson(response, providerId) {
|
|
|
4339
6420
|
});
|
|
4340
6421
|
}
|
|
4341
6422
|
}
|
|
6423
|
+
async function readJsonLenient(response) {
|
|
6424
|
+
try {
|
|
6425
|
+
return await response.json();
|
|
6426
|
+
} catch {
|
|
6427
|
+
return;
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
4342
6430
|
function asChatCompletionResponse(payload, providerId) {
|
|
4343
6431
|
if (!isRecord(payload)) throw new DogpileError({
|
|
4344
6432
|
code: "provider-invalid-response",
|
|
@@ -4409,14 +6497,17 @@ function responseMetadata(response) {
|
|
|
4409
6497
|
});
|
|
4410
6498
|
}
|
|
4411
6499
|
function createProviderError(response, payload, providerId) {
|
|
6500
|
+
const code = codeForStatus(response.status);
|
|
6501
|
+
const timeoutSource = code === "provider-timeout" ? { source: "provider" } : {};
|
|
4412
6502
|
return new DogpileError({
|
|
4413
|
-
code
|
|
6503
|
+
code,
|
|
4414
6504
|
message: providerResponseErrorMessage(response, payload),
|
|
4415
6505
|
retryable: response.status === 408 || response.status === 429 || response.status >= 500,
|
|
4416
6506
|
providerId,
|
|
4417
6507
|
detail: removeUndefined({
|
|
4418
6508
|
statusCode: response.status,
|
|
4419
6509
|
statusText: response.statusText,
|
|
6510
|
+
...timeoutSource,
|
|
4420
6511
|
response: isJsonValue(payload) ? payload : void 0
|
|
4421
6512
|
})
|
|
4422
6513
|
});
|
|
@@ -4737,6 +6828,6 @@ function defaultSleep(ms, signal) {
|
|
|
4737
6828
|
});
|
|
4738
6829
|
}
|
|
4739
6830
|
//#endregion
|
|
4740
|
-
export { DEFAULT_RETRYABLE_DOGPILE_CODES, Dogpile, DogpileError, budget, builtInDogpileToolIdentity, builtInDogpileToolInputSchema, builtInDogpileToolPermissions, combineTerminationDecisions, consoleLogger, convergence, createCodeExecToolAdapter, createEngine, createOpenAICompatibleProvider, createRuntimeToolExecutor, createWebSearchToolAdapter, evaluateBudget, evaluateConvergence, evaluateFirstOf, evaluateJudge, evaluateTermination, evaluateTerminationStop, firstOf, judge, loggerFromEvents, noopLogger, normalizeBuiltInDogpileTool, normalizeBuiltInDogpileTools, normalizeRuntimeToolAdapterError, replay, replayStream, run, runtimeToolAvailability, runtimeToolManifest, stream, validateBuiltInDogpileToolInput, withRetry };
|
|
6831
|
+
export { DEFAULT_RETRYABLE_DOGPILE_CODES, DOGPILE_SPAN_NAMES, Dogpile, DogpileError, budget, builtInDogpileToolIdentity, builtInDogpileToolInputSchema, builtInDogpileToolPermissions, combineTerminationDecisions, consoleLogger, convergence, createCodeExecToolAdapter, createEngine, createOpenAICompatibleProvider, createRuntimeToolExecutor, createWebSearchToolAdapter, evaluateBudget, evaluateConvergence, evaluateFirstOf, evaluateJudge, evaluateTermination, evaluateTerminationStop, firstOf, judge, loggerFromEvents, noopLogger, normalizeBuiltInDogpileTool, normalizeBuiltInDogpileTools, normalizeRuntimeToolAdapterError, replay, replayStream, run, runtimeToolAvailability, runtimeToolManifest, stream, validateBuiltInDogpileToolInput, withRetry };
|
|
4741
6832
|
|
|
4742
6833
|
//# sourceMappingURL=index.js.map
|