@dogpile/sdk 0.3.0 → 0.4.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 +145 -0
- package/README.md +1 -0
- package/dist/browser/index.js +2270 -507
- package/dist/browser/index.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +87 -2
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/runtime/broadcast.d.ts.map +1 -1
- package/dist/runtime/broadcast.js +1 -13
- 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 +74 -1
- package/dist/runtime/coordinator.d.ts.map +1 -1
- package/dist/runtime/coordinator.js +929 -34
- 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 +347 -0
- package/dist/runtime/defaults.js.map +1 -1
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +254 -24
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/ids.d.ts +19 -0
- package/dist/runtime/ids.d.ts.map +1 -0
- package/dist/runtime/ids.js +36 -0
- package/dist/runtime/ids.js.map +1 -0
- package/dist/runtime/logger.d.ts +61 -0
- package/dist/runtime/logger.d.ts.map +1 -0
- package/dist/runtime/logger.js +114 -0
- package/dist/runtime/logger.js.map +1 -0
- package/dist/runtime/retry.d.ts +99 -0
- package/dist/runtime/retry.d.ts.map +1 -0
- package/dist/runtime/retry.js +181 -0
- package/dist/runtime/retry.js.map +1 -0
- package/dist/runtime/sequential.d.ts.map +1 -1
- package/dist/runtime/sequential.js +9 -11
- package/dist/runtime/sequential.js.map +1 -1
- package/dist/runtime/shared.d.ts.map +1 -1
- package/dist/runtime/shared.js +1 -13
- package/dist/runtime/shared.js.map +1 -1
- package/dist/runtime/tools/built-in.d.ts +99 -0
- package/dist/runtime/tools/built-in.d.ts.map +1 -0
- package/dist/runtime/tools/built-in.js +577 -0
- package/dist/runtime/tools/built-in.js.map +1 -0
- package/dist/runtime/tools/vercel-ai.d.ts +67 -0
- package/dist/runtime/tools/vercel-ai.d.ts.map +1 -0
- package/dist/runtime/tools/vercel-ai.js +148 -0
- package/dist/runtime/tools/vercel-ai.js.map +1 -0
- package/dist/runtime/tools.d.ts +5 -268
- package/dist/runtime/tools.d.ts.map +1 -1
- package/dist/runtime/tools.js +7 -770
- package/dist/runtime/tools.js.map +1 -1
- 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/benchmark.d.ts +276 -0
- package/dist/types/benchmark.d.ts.map +1 -0
- package/dist/types/benchmark.js +2 -0
- package/dist/types/benchmark.js.map +1 -0
- package/dist/types/events.d.ts +816 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +2 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/replay.d.ts +173 -0
- package/dist/types/replay.d.ts.map +1 -0
- package/dist/types/replay.js +2 -0
- package/dist/types/replay.js.map +1 -0
- package/dist/types.d.ts +135 -938
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +27 -1
- package/src/index.ts +14 -0
- package/src/providers/openai-compatible.ts +82 -3
- package/src/runtime/broadcast.ts +1 -16
- package/src/runtime/cancellation.ts +59 -1
- package/src/runtime/coordinator.ts +1164 -34
- package/src/runtime/decisions.ts +307 -4
- package/src/runtime/defaults.ts +376 -0
- package/src/runtime/engine.ts +363 -24
- package/src/runtime/ids.ts +41 -0
- package/src/runtime/logger.ts +152 -0
- package/src/runtime/retry.ts +270 -0
- package/src/runtime/sequential.ts +10 -13
- package/src/runtime/shared.ts +1 -16
- package/src/runtime/tools/built-in.ts +875 -0
- package/src/runtime/tools/vercel-ai.ts +269 -0
- package/src/runtime/tools.ts +60 -1255
- package/src/runtime/validation.ts +81 -0
- package/src/types/benchmark.ts +300 -0
- package/src/types/events.ts +895 -0
- package/src/types/replay.ts +212 -0
- package/src/types.ts +251 -997
package/dist/browser/index.js
CHANGED
|
@@ -67,6 +67,39 @@ function isRecord$2(value) {
|
|
|
67
67
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
68
68
|
}
|
|
69
69
|
//#endregion
|
|
70
|
+
//#region src/runtime/ids.ts
|
|
71
|
+
/**
|
|
72
|
+
* Repo-internal id and timing helpers used across all four protocols.
|
|
73
|
+
*
|
|
74
|
+
* Centralized here so a change to id format or fallback semantics happens in
|
|
75
|
+
* exactly one place — switching `protocol` must not change the run-id contract.
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* Generates a fresh run id using `globalThis.crypto.randomUUID`.
|
|
79
|
+
*
|
|
80
|
+
* Throws a `DogpileError` when no UUID source is available rather than falling
|
|
81
|
+
* back to a millisecond-based id (which collides under back-to-back runs in
|
|
82
|
+
* the same tick). Node 22+, Bun latest, and modern browsers all expose
|
|
83
|
+
* `crypto.randomUUID`; environments without it are unsupported by Dogpile.
|
|
84
|
+
*/
|
|
85
|
+
function createRunId() {
|
|
86
|
+
const random = globalThis.crypto?.randomUUID?.();
|
|
87
|
+
if (typeof random === "string" && random.length > 0) return random;
|
|
88
|
+
throw new DogpileError({
|
|
89
|
+
code: "invalid-configuration",
|
|
90
|
+
message: "Dogpile requires globalThis.crypto.randomUUID to mint a run id. Run on Node 22+, Bun latest, or a modern browser ESM environment."
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function nowMs() {
|
|
94
|
+
return globalThis.performance?.now() ?? Date.now();
|
|
95
|
+
}
|
|
96
|
+
function elapsedMs(startedAtMs) {
|
|
97
|
+
return Math.max(0, nowMs() - startedAtMs);
|
|
98
|
+
}
|
|
99
|
+
function providerCallIdFor(runId, oneBasedIndex) {
|
|
100
|
+
return `${runId}:provider-call:${oneBasedIndex}`;
|
|
101
|
+
}
|
|
102
|
+
//#endregion
|
|
70
103
|
//#region src/runtime/defaults.ts
|
|
71
104
|
function normalizeProtocol(protocol) {
|
|
72
105
|
if (typeof protocol !== "string") return protocol;
|
|
@@ -160,6 +193,27 @@ function addCost(left, right) {
|
|
|
160
193
|
totalTokens: left.totalTokens + right.totalTokens
|
|
161
194
|
};
|
|
162
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
|
+
}
|
|
163
217
|
function createTranscriptLink(transcript) {
|
|
164
218
|
return {
|
|
165
219
|
kind: "trace-transcript",
|
|
@@ -258,7 +312,14 @@ function createReplayTraceBudgetStateChanges(events) {
|
|
|
258
312
|
case "model-response":
|
|
259
313
|
case "model-output-chunk":
|
|
260
314
|
case "tool-call":
|
|
261
|
-
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 [];
|
|
262
323
|
}
|
|
263
324
|
});
|
|
264
325
|
}
|
|
@@ -355,6 +416,24 @@ function createReplayTraceProtocolDecision(protocol, event, eventIndex, options
|
|
|
355
416
|
output: event.output,
|
|
356
417
|
cost: event.cost
|
|
357
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 };
|
|
358
437
|
}
|
|
359
438
|
}
|
|
360
439
|
function defaultProtocolDecision(event) {
|
|
@@ -369,6 +448,13 @@ function defaultProtocolDecision(event) {
|
|
|
369
448
|
case "broadcast": return "collect-broadcast-round";
|
|
370
449
|
case "budget-stop": return "stop-for-budget";
|
|
371
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";
|
|
372
458
|
}
|
|
373
459
|
}
|
|
374
460
|
function eventAgentScope(event) {
|
|
@@ -434,6 +520,216 @@ function canonicalizeRunResult(result) {
|
|
|
434
520
|
function stableJsonStringify(value) {
|
|
435
521
|
return JSON.stringify(canonicalizeSerializable(value));
|
|
436
522
|
}
|
|
523
|
+
/**
|
|
524
|
+
* The eight numeric fields recursively verified by `recomputeAccountingFromTrace`.
|
|
525
|
+
*
|
|
526
|
+
* These are the only summable scalars on `RunAccounting`. Non-numeric fields
|
|
527
|
+
* (`kind`, `tier`, `budget`, `termination`, `budgetStateChanges`) and derived
|
|
528
|
+
* ratios (`usdCapUtilization`, `totalTokenCapUtilization`) are NOT in this set.
|
|
529
|
+
*/
|
|
530
|
+
var RECOMPUTE_FIELD_ORDER = [
|
|
531
|
+
"cost.usd",
|
|
532
|
+
"cost.inputTokens",
|
|
533
|
+
"cost.outputTokens",
|
|
534
|
+
"cost.totalTokens",
|
|
535
|
+
"usage.usd",
|
|
536
|
+
"usage.inputTokens",
|
|
537
|
+
"usage.outputTokens",
|
|
538
|
+
"usage.totalTokens"
|
|
539
|
+
];
|
|
540
|
+
var USD_FIELDS = new Set(["cost.usd", "usage.usd"]);
|
|
541
|
+
var FLOAT_EPSILON = 1e-9;
|
|
542
|
+
function readNumericField(accounting, field) {
|
|
543
|
+
switch (field) {
|
|
544
|
+
case "cost.usd": return accounting.cost.usd;
|
|
545
|
+
case "cost.inputTokens": return accounting.cost.inputTokens;
|
|
546
|
+
case "cost.outputTokens": return accounting.cost.outputTokens;
|
|
547
|
+
case "cost.totalTokens": return accounting.cost.totalTokens;
|
|
548
|
+
case "usage.usd": return accounting.usage.usd;
|
|
549
|
+
case "usage.inputTokens": return accounting.usage.inputTokens;
|
|
550
|
+
case "usage.outputTokens": return accounting.usage.outputTokens;
|
|
551
|
+
case "usage.totalTokens": return accounting.usage.totalTokens;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function fieldsEqual(field, a, b) {
|
|
555
|
+
if (USD_FIELDS.has(field)) return Math.abs(a - b) < FLOAT_EPSILON;
|
|
556
|
+
return a === b;
|
|
557
|
+
}
|
|
558
|
+
function firstDifferingField(recorded, recomputed) {
|
|
559
|
+
for (const field of RECOMPUTE_FIELD_ORDER) {
|
|
560
|
+
const a = readNumericField(recorded, field);
|
|
561
|
+
const b = readNumericField(recomputed, field);
|
|
562
|
+
if (!fieldsEqual(field, a, b)) return {
|
|
563
|
+
field,
|
|
564
|
+
recorded: a,
|
|
565
|
+
recomputed: b
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
function buildLocalAccounting(trace) {
|
|
571
|
+
return createRunAccounting({
|
|
572
|
+
tier: trace.tier,
|
|
573
|
+
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
574
|
+
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
575
|
+
cost: trace.finalOutput.cost,
|
|
576
|
+
events: trace.events
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
function lastCostBearingEventCost(events) {
|
|
580
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
581
|
+
const event = events[index];
|
|
582
|
+
if (event === void 0) continue;
|
|
583
|
+
if (event.type === "final" || event.type === "agent-turn" || event.type === "broadcast" || event.type === "budget-stop") return event.cost;
|
|
584
|
+
}
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Recompute a parent's `RunAccounting` from a saved `Trace` for replay-time
|
|
589
|
+
* tamper detection.
|
|
590
|
+
*
|
|
591
|
+
* @remarks
|
|
592
|
+
* Returns the parent's local `RunAccounting` (built the same way `replay()`
|
|
593
|
+
* builds it today, from `trace.finalOutput.cost` and `trace.events`). While
|
|
594
|
+
* walking events, every `sub-run-completed` is recursed into and the
|
|
595
|
+
* recomputed child accounting is compared field-by-field to the recorded
|
|
596
|
+
* `event.subResult.accounting`. A mismatch on any of the eight enumerated
|
|
597
|
+
* numeric fields throws `DogpileError({ code: "invalid-configuration" })`
|
|
598
|
+
* with `detail.reason: "trace-accounting-mismatch"` and a concrete
|
|
599
|
+
* `detail.field` identifying the first differing numeric.
|
|
600
|
+
*
|
|
601
|
+
* Pure: no provider calls, no I/O, no clock reads.
|
|
602
|
+
*
|
|
603
|
+
* Non-summed fields (`kind`, `tier`, `budget`, `termination`,
|
|
604
|
+
* `budgetStateChanges`) and derived ratios (`usdCapUtilization`,
|
|
605
|
+
* `totalTokenCapUtilization`) are not in the comparison set.
|
|
606
|
+
*/
|
|
607
|
+
function recomputeAccountingFromTrace(trace) {
|
|
608
|
+
const local = buildLocalAccounting(trace);
|
|
609
|
+
const lastEventCost = lastCostBearingEventCost(trace.events);
|
|
610
|
+
if (lastEventCost !== null) {
|
|
611
|
+
const drift = firstDifferingField(local, createRunAccounting({
|
|
612
|
+
tier: trace.tier,
|
|
613
|
+
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
614
|
+
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
615
|
+
cost: lastEventCost,
|
|
616
|
+
events: trace.events
|
|
617
|
+
}));
|
|
618
|
+
if (drift !== null) throw new DogpileError({
|
|
619
|
+
code: "invalid-configuration",
|
|
620
|
+
message: `Trace accounting mismatch at parent run ${trace.runId}: field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed}.`,
|
|
621
|
+
retryable: false,
|
|
622
|
+
detail: {
|
|
623
|
+
kind: "trace-validation",
|
|
624
|
+
reason: "trace-accounting-mismatch",
|
|
625
|
+
eventIndex: -1,
|
|
626
|
+
childRunId: trace.runId,
|
|
627
|
+
field: drift.field,
|
|
628
|
+
recorded: drift.recorded,
|
|
629
|
+
recomputed: drift.recomputed
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
for (let eventIndex = 0; eventIndex < trace.events.length; eventIndex += 1) {
|
|
634
|
+
const event = trace.events[eventIndex];
|
|
635
|
+
if (event === void 0) continue;
|
|
636
|
+
if (event.type === "sub-run-completed") {
|
|
637
|
+
const childRecordedRollup = createRunAccounting({
|
|
638
|
+
tier: trace.tier,
|
|
639
|
+
cost: event.subResult.cost,
|
|
640
|
+
events: []
|
|
641
|
+
});
|
|
642
|
+
const childRecordedAccounting = event.subResult.accounting;
|
|
643
|
+
const drift = firstDifferingField(childRecordedAccounting, childRecordedRollup);
|
|
644
|
+
if (drift !== null) throw new DogpileError({
|
|
645
|
+
code: "invalid-configuration",
|
|
646
|
+
message: `Trace parent-rollup mismatch at sub-run ${event.childRunId}: field "${drift.field}" recorded ${drift.recorded} on accounting, ${drift.recomputed} on subResult.cost.`,
|
|
647
|
+
retryable: false,
|
|
648
|
+
detail: {
|
|
649
|
+
kind: "trace-validation",
|
|
650
|
+
reason: "trace-accounting-mismatch",
|
|
651
|
+
subReason: "parent-rollup-drift",
|
|
652
|
+
eventIndex,
|
|
653
|
+
childRunId: event.childRunId,
|
|
654
|
+
field: drift.field,
|
|
655
|
+
recorded: drift.recorded,
|
|
656
|
+
recomputed: drift.recomputed
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
} else if (event.type === "sub-run-failed") {
|
|
660
|
+
const partialFromTrace = lastCostBearingEventCost(event.partialTrace.events) ?? emptyCost();
|
|
661
|
+
const drift = firstDifferingField(createRunAccounting({
|
|
662
|
+
tier: trace.tier,
|
|
663
|
+
cost: event.partialCost,
|
|
664
|
+
events: []
|
|
665
|
+
}), createRunAccounting({
|
|
666
|
+
tier: trace.tier,
|
|
667
|
+
cost: partialFromTrace,
|
|
668
|
+
events: []
|
|
669
|
+
}));
|
|
670
|
+
if (drift !== null) throw new DogpileError({
|
|
671
|
+
code: "invalid-configuration",
|
|
672
|
+
message: `Trace parent-rollup mismatch at sub-run ${event.childRunId}: partialCost field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed} from partialTrace events.`,
|
|
673
|
+
retryable: false,
|
|
674
|
+
detail: {
|
|
675
|
+
kind: "trace-validation",
|
|
676
|
+
reason: "trace-accounting-mismatch",
|
|
677
|
+
subReason: "parent-rollup-drift",
|
|
678
|
+
eventIndex,
|
|
679
|
+
childRunId: event.childRunId,
|
|
680
|
+
field: drift.field,
|
|
681
|
+
recorded: drift.recorded,
|
|
682
|
+
recomputed: drift.recomputed
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
const subRunTotal = accumulateSubRunCost(trace.events);
|
|
688
|
+
const parentTotal = trace.finalOutput.cost;
|
|
689
|
+
for (const field of RECOMPUTE_FIELD_ORDER) {
|
|
690
|
+
if (field.startsWith("usage.")) continue;
|
|
691
|
+
const [, key] = field.split(".");
|
|
692
|
+
const parentValue = parentTotal[key];
|
|
693
|
+
const childValue = subRunTotal[key];
|
|
694
|
+
if (childValue - parentValue > FLOAT_EPSILON) throw new DogpileError({
|
|
695
|
+
code: "invalid-configuration",
|
|
696
|
+
message: `Trace parent-rollup mismatch at run ${trace.runId}: field "${field}" Σ children ${childValue} exceeds parent recorded ${parentValue}.`,
|
|
697
|
+
retryable: false,
|
|
698
|
+
detail: {
|
|
699
|
+
kind: "trace-validation",
|
|
700
|
+
reason: "trace-accounting-mismatch",
|
|
701
|
+
subReason: "parent-rollup-drift",
|
|
702
|
+
eventIndex: -1,
|
|
703
|
+
childRunId: trace.runId,
|
|
704
|
+
field,
|
|
705
|
+
recorded: parentValue,
|
|
706
|
+
recomputed: childValue
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
for (let eventIndex = 0; eventIndex < trace.events.length; eventIndex += 1) {
|
|
711
|
+
const event = trace.events[eventIndex];
|
|
712
|
+
if (event === void 0 || event.type !== "sub-run-completed") continue;
|
|
713
|
+
const childRecomputed = recomputeAccountingFromTrace(event.subResult.trace);
|
|
714
|
+
const childRecorded = event.subResult.accounting;
|
|
715
|
+
const drift = firstDifferingField(childRecorded, childRecomputed);
|
|
716
|
+
if (drift !== null) throw new DogpileError({
|
|
717
|
+
code: "invalid-configuration",
|
|
718
|
+
message: `Trace accounting mismatch at sub-run ${event.childRunId}: field "${drift.field}" recorded ${drift.recorded}, recomputed ${drift.recomputed}.`,
|
|
719
|
+
retryable: false,
|
|
720
|
+
detail: {
|
|
721
|
+
kind: "trace-validation",
|
|
722
|
+
reason: "trace-accounting-mismatch",
|
|
723
|
+
eventIndex,
|
|
724
|
+
childRunId: event.childRunId,
|
|
725
|
+
field: drift.field,
|
|
726
|
+
recorded: drift.recorded,
|
|
727
|
+
recomputed: drift.recomputed
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
return local;
|
|
732
|
+
}
|
|
437
733
|
function canonicalizeSerializable(value) {
|
|
438
734
|
if (Array.isArray(value)) return value.map((item) => canonicalizeSerializable(item));
|
|
439
735
|
if (typeof value === "number") {
|
|
@@ -452,6 +748,24 @@ function canonicalizeSerializable(value) {
|
|
|
452
748
|
}
|
|
453
749
|
//#endregion
|
|
454
750
|
//#region src/runtime/cancellation.ts
|
|
751
|
+
/**
|
|
752
|
+
* Classify an abort signal's reason into the BUDGET-01 / BUDGET-02
|
|
753
|
+
* `detail.reason` discriminator.
|
|
754
|
+
*
|
|
755
|
+
* - `"timeout"` when the reason is a {@link DogpileError} with `code === "timeout"`
|
|
756
|
+
* (matches the parent-deadline abort path in `engine.ts:createTimeoutAbortLifecycle`).
|
|
757
|
+
* - `"parent-aborted"` for every other reason — explicit caller abort, plain
|
|
758
|
+
* `Error`, `undefined`, or arbitrary primitive.
|
|
759
|
+
*/
|
|
760
|
+
function classifyAbortReason(signalReasonOrError) {
|
|
761
|
+
if (DogpileError.isInstance(signalReasonOrError) && signalReasonOrError.code === "timeout") return "timeout";
|
|
762
|
+
return "parent-aborted";
|
|
763
|
+
}
|
|
764
|
+
function classifyChildTimeoutSource(_error, context) {
|
|
765
|
+
if (context.isProviderError) return "provider";
|
|
766
|
+
if (context.decisionTimeoutMs !== void 0 || context.engineDefaultTimeoutMs !== void 0) return "engine";
|
|
767
|
+
return "provider";
|
|
768
|
+
}
|
|
455
769
|
function throwIfAborted(signal, providerId) {
|
|
456
770
|
if (!signal?.aborted) return;
|
|
457
771
|
throw createAbortErrorFromSignal(signal, providerId);
|
|
@@ -468,7 +782,7 @@ function createAbortError(providerId, detail, cause) {
|
|
|
468
782
|
}
|
|
469
783
|
function createAbortErrorFromSignal(signal, providerId) {
|
|
470
784
|
if (DogpileError.isInstance(signal.reason)) return signal.reason;
|
|
471
|
-
return createAbortError(providerId,
|
|
785
|
+
return createAbortError(providerId, { reason: classifyAbortReason(signal.reason) }, signal.reason);
|
|
472
786
|
}
|
|
473
787
|
function createTimeoutError(providerId, timeoutMs) {
|
|
474
788
|
return new DogpileError({
|
|
@@ -479,23 +793,223 @@ function createTimeoutError(providerId, timeoutMs) {
|
|
|
479
793
|
detail: { timeoutMs }
|
|
480
794
|
});
|
|
481
795
|
}
|
|
796
|
+
function createEngineDeadlineTimeoutError(providerId, timeoutMs) {
|
|
797
|
+
return new DogpileError({
|
|
798
|
+
code: "provider-timeout",
|
|
799
|
+
message: `The child engine deadline expired after ${timeoutMs}ms.`,
|
|
800
|
+
retryable: true,
|
|
801
|
+
providerId,
|
|
802
|
+
detail: {
|
|
803
|
+
timeoutMs,
|
|
804
|
+
source: "engine"
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
482
808
|
//#endregion
|
|
483
809
|
//#region src/runtime/decisions.ts
|
|
484
|
-
|
|
810
|
+
var PROTOCOL_NAMES = [
|
|
811
|
+
"coordinator",
|
|
812
|
+
"sequential",
|
|
813
|
+
"broadcast",
|
|
814
|
+
"shared"
|
|
815
|
+
];
|
|
816
|
+
function parseAgentDecision(output, context = {}) {
|
|
817
|
+
const delegateBlock = matchDelegateBlock(output);
|
|
818
|
+
if (delegateBlock !== void 0) return parseDelegateDecision(delegateBlock, context);
|
|
819
|
+
return parseParticipateDecision(output);
|
|
820
|
+
}
|
|
821
|
+
function isParticipatingDecision(decision) {
|
|
822
|
+
if (decision === void 0 || isDelegateDecisionArray(decision) || decision.type !== "participate") return false;
|
|
823
|
+
return decision.participation !== "abstain";
|
|
824
|
+
}
|
|
825
|
+
function isDelegateDecisionArray(decision) {
|
|
826
|
+
return Array.isArray(decision);
|
|
827
|
+
}
|
|
828
|
+
function parseParticipateDecision(output) {
|
|
485
829
|
const selectedRole = matchLine(output, /^role_selected:\s*(.+)$/imu);
|
|
486
830
|
const participation = matchLine(output, /^participation:\s*(contribute|abstain)$/imu);
|
|
487
831
|
const rationale = matchLine(output, /^rationale:\s*(.+)$/imu);
|
|
488
832
|
const contribution = matchContribution(output);
|
|
489
833
|
if (!selectedRole || !participation || !isAgentParticipation(participation) || !rationale || !contribution) return;
|
|
490
834
|
return {
|
|
835
|
+
type: "participate",
|
|
491
836
|
selectedRole,
|
|
492
837
|
participation,
|
|
493
838
|
rationale,
|
|
494
839
|
contribution
|
|
495
840
|
};
|
|
496
841
|
}
|
|
497
|
-
|
|
498
|
-
|
|
842
|
+
/**
|
|
843
|
+
* Locate a `delegate:` line followed by a fenced JSON block in the agent's
|
|
844
|
+
* output. Returns the raw JSON text inside the fence, or `undefined` when no
|
|
845
|
+
* delegate block is present. Tolerates ```` ```json ```` and bare ```` ``` ````.
|
|
846
|
+
*/
|
|
847
|
+
function matchDelegateBlock(output) {
|
|
848
|
+
return output.match(/^delegate:\s*\r?\n\s*```(?:json)?\s*\r?\n([\s\S]*?)\r?\n\s*```/imu)?.[1];
|
|
849
|
+
}
|
|
850
|
+
function parseDelegateDecision(jsonText, context) {
|
|
851
|
+
let parsed;
|
|
852
|
+
try {
|
|
853
|
+
parsed = JSON.parse(jsonText);
|
|
854
|
+
} catch (error) {
|
|
855
|
+
throwInvalidDelegate({
|
|
856
|
+
path: "decision",
|
|
857
|
+
message: `delegate JSON did not parse: ${error instanceof Error ? error.message : String(error)}`,
|
|
858
|
+
expected: "valid JSON object",
|
|
859
|
+
received: truncate(jsonText)
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
if (Array.isArray(parsed)) {
|
|
863
|
+
if (parsed.length === 0) throwInvalidDelegate({
|
|
864
|
+
path: "decision",
|
|
865
|
+
message: "delegate array must not be empty.",
|
|
866
|
+
expected: "array with 1..8 delegate objects",
|
|
867
|
+
received: "empty array"
|
|
868
|
+
});
|
|
869
|
+
return parsed.map((item) => parseSingleDelegateObject(item, context));
|
|
870
|
+
}
|
|
871
|
+
return parseSingleDelegateObject(parsed, context);
|
|
872
|
+
}
|
|
873
|
+
function parseSingleDelegateObject(parsed, context) {
|
|
874
|
+
if (parsed === null || typeof parsed !== "object") throwInvalidDelegate({
|
|
875
|
+
path: "decision",
|
|
876
|
+
message: "delegate decision must be a JSON object.",
|
|
877
|
+
expected: "object",
|
|
878
|
+
received: describe(parsed)
|
|
879
|
+
});
|
|
880
|
+
const record = parsed;
|
|
881
|
+
const protocol = record["protocol"];
|
|
882
|
+
if (typeof protocol !== "string" || !PROTOCOL_NAMES.includes(protocol)) throwInvalidDelegate({
|
|
883
|
+
path: "decision.protocol",
|
|
884
|
+
message: `protocol "${describe(protocol)}" is not a known coordination protocol.`,
|
|
885
|
+
expected: PROTOCOL_NAMES.join(" | "),
|
|
886
|
+
received: describe(protocol)
|
|
887
|
+
});
|
|
888
|
+
const intentRaw = record["intent"];
|
|
889
|
+
const intent = typeof intentRaw === "string" ? intentRaw.trim() : "";
|
|
890
|
+
if (intent.length === 0) throwInvalidDelegate({
|
|
891
|
+
path: "decision.intent",
|
|
892
|
+
message: "delegate decision must include a non-empty intent string.",
|
|
893
|
+
expected: "non-empty string",
|
|
894
|
+
received: describe(intentRaw)
|
|
895
|
+
});
|
|
896
|
+
const result = {
|
|
897
|
+
type: "delegate",
|
|
898
|
+
protocol,
|
|
899
|
+
intent
|
|
900
|
+
};
|
|
901
|
+
if (record["model"] !== void 0) {
|
|
902
|
+
const model = record["model"];
|
|
903
|
+
if (typeof model !== "string" || model.length === 0) throwInvalidDelegate({
|
|
904
|
+
path: "decision.model",
|
|
905
|
+
message: "delegate decision model must be a non-empty string when present.",
|
|
906
|
+
expected: "non-empty string",
|
|
907
|
+
received: describe(model)
|
|
908
|
+
});
|
|
909
|
+
if (context.parentProviderId !== void 0 && model !== context.parentProviderId) throwInvalidDelegate({
|
|
910
|
+
path: "decision.model",
|
|
911
|
+
message: `delegate decision model "${model}" does not match parent provider id "${context.parentProviderId}".`,
|
|
912
|
+
expected: context.parentProviderId,
|
|
913
|
+
received: model
|
|
914
|
+
});
|
|
915
|
+
result.model = model;
|
|
916
|
+
}
|
|
917
|
+
if (record["budget"] !== void 0) result.budget = parseDelegateBudget(record["budget"]);
|
|
918
|
+
if (record["maxConcurrentChildren"] !== void 0) {
|
|
919
|
+
const value = record["maxConcurrentChildren"];
|
|
920
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) throwInvalidDelegate({
|
|
921
|
+
path: "decision.maxConcurrentChildren",
|
|
922
|
+
message: "delegate decision maxConcurrentChildren must be a positive integer when present.",
|
|
923
|
+
expected: "integer >= 1",
|
|
924
|
+
received: describe(value)
|
|
925
|
+
});
|
|
926
|
+
result.maxConcurrentChildren = value;
|
|
927
|
+
}
|
|
928
|
+
if (context.currentDepth !== void 0 && context.maxDepth !== void 0) {
|
|
929
|
+
if (context.currentDepth + 1 > context.maxDepth) throw depthOverflowError(context.currentDepth, context.maxDepth);
|
|
930
|
+
}
|
|
931
|
+
return result;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Build the canonical depth-overflow `DogpileError`. Used by the parser (this
|
|
935
|
+
* file) and the coordinator dispatcher; kept here so both call sites produce
|
|
936
|
+
* the exact same error shape (D-14, D-15).
|
|
937
|
+
*/
|
|
938
|
+
function depthOverflowError(currentDepth, maxDepth) {
|
|
939
|
+
return new DogpileError({
|
|
940
|
+
code: "invalid-configuration",
|
|
941
|
+
message: `Depth overflow: cannot dispatch sub-run at depth ${currentDepth + 1} (maxDepth = ${maxDepth}).`,
|
|
942
|
+
retryable: false,
|
|
943
|
+
detail: {
|
|
944
|
+
kind: "delegate-validation",
|
|
945
|
+
path: "decision.protocol",
|
|
946
|
+
reason: "depth-overflow",
|
|
947
|
+
currentDepth,
|
|
948
|
+
maxDepth
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Dispatcher-time depth gate. Throws the same error shape the parser uses; the
|
|
954
|
+
* dual gate (parser + dispatcher) defends against any TOCTOU window between
|
|
955
|
+
* decision parsing and child-run spin-up (D-14).
|
|
956
|
+
*/
|
|
957
|
+
function assertDepthWithinLimit(currentDepth, maxDepth) {
|
|
958
|
+
if (currentDepth + 1 > maxDepth) throw depthOverflowError(currentDepth, maxDepth);
|
|
959
|
+
}
|
|
960
|
+
function parseDelegateBudget(raw) {
|
|
961
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throwInvalidDelegate({
|
|
962
|
+
path: "decision.budget",
|
|
963
|
+
message: "delegate decision budget must be an object.",
|
|
964
|
+
expected: "object",
|
|
965
|
+
received: describe(raw)
|
|
966
|
+
});
|
|
967
|
+
const record = raw;
|
|
968
|
+
const budget = {};
|
|
969
|
+
if (record["timeoutMs"] !== void 0) {
|
|
970
|
+
const value = record["timeoutMs"];
|
|
971
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
972
|
+
path: "decision.budget.timeoutMs",
|
|
973
|
+
message: "delegate decision budget.timeoutMs must be a non-negative integer.",
|
|
974
|
+
expected: "integer >= 0",
|
|
975
|
+
received: describe(value)
|
|
976
|
+
});
|
|
977
|
+
budget.timeoutMs = value;
|
|
978
|
+
}
|
|
979
|
+
if (record["maxTokens"] !== void 0) {
|
|
980
|
+
const value = record["maxTokens"];
|
|
981
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
982
|
+
path: "decision.budget.maxTokens",
|
|
983
|
+
message: "delegate decision budget.maxTokens must be a non-negative integer.",
|
|
984
|
+
expected: "integer >= 0",
|
|
985
|
+
received: describe(value)
|
|
986
|
+
});
|
|
987
|
+
budget.maxTokens = value;
|
|
988
|
+
}
|
|
989
|
+
if (record["maxIterations"] !== void 0) {
|
|
990
|
+
const value = record["maxIterations"];
|
|
991
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) throwInvalidDelegate({
|
|
992
|
+
path: "decision.budget.maxIterations",
|
|
993
|
+
message: "delegate decision budget.maxIterations must be a non-negative integer.",
|
|
994
|
+
expected: "integer >= 0",
|
|
995
|
+
received: describe(value)
|
|
996
|
+
});
|
|
997
|
+
budget.maxIterations = value;
|
|
998
|
+
}
|
|
999
|
+
return budget;
|
|
1000
|
+
}
|
|
1001
|
+
function throwInvalidDelegate(failure) {
|
|
1002
|
+
throw new DogpileError({
|
|
1003
|
+
code: "invalid-configuration",
|
|
1004
|
+
message: `Invalid Dogpile configuration at ${failure.path}: ${failure.message}`,
|
|
1005
|
+
retryable: false,
|
|
1006
|
+
detail: {
|
|
1007
|
+
kind: "delegate-validation",
|
|
1008
|
+
path: failure.path,
|
|
1009
|
+
expected: failure.expected,
|
|
1010
|
+
received: failure.received
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
499
1013
|
}
|
|
500
1014
|
function matchLine(output, pattern) {
|
|
501
1015
|
return output.match(pattern)?.[1]?.trim();
|
|
@@ -507,6 +1021,15 @@ function matchContribution(output) {
|
|
|
507
1021
|
function isAgentParticipation(value) {
|
|
508
1022
|
return value === "contribute" || value === "abstain";
|
|
509
1023
|
}
|
|
1024
|
+
function describe(value) {
|
|
1025
|
+
if (value === null) return "null";
|
|
1026
|
+
if (Array.isArray(value)) return "array";
|
|
1027
|
+
if (typeof value === "string") return JSON.stringify(value).slice(0, 200);
|
|
1028
|
+
return typeof value;
|
|
1029
|
+
}
|
|
1030
|
+
function truncate(value) {
|
|
1031
|
+
return value.length > 200 ? `${value.slice(0, 200)}…` : value;
|
|
1032
|
+
}
|
|
510
1033
|
//#endregion
|
|
511
1034
|
//#region src/runtime/model.ts
|
|
512
1035
|
async function generateModelTurn(options) {
|
|
@@ -964,6 +1487,7 @@ var budgetTiers = [
|
|
|
964
1487
|
"balanced",
|
|
965
1488
|
"quality"
|
|
966
1489
|
];
|
|
1490
|
+
var onChildFailureModes = ["continue", "abort"];
|
|
967
1491
|
/**
|
|
968
1492
|
* Validate high-level caller options before any protocol execution starts.
|
|
969
1493
|
*/
|
|
@@ -982,11 +1506,25 @@ function validateDogpileOptions(options) {
|
|
|
982
1506
|
validateOptionalFunction(options.evaluate, "evaluate");
|
|
983
1507
|
validateOptionalSeed(options.seed, "seed");
|
|
984
1508
|
validateOptionalAbortSignal(options.signal, "signal");
|
|
1509
|
+
validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
|
|
1510
|
+
validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
|
|
1511
|
+
validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
|
|
1512
|
+
validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
|
|
985
1513
|
}
|
|
986
1514
|
function validateMissionIntent(intent, path = "intent") {
|
|
987
1515
|
validateNonEmptyString(intent, path, "intent is required.");
|
|
988
1516
|
}
|
|
989
1517
|
/**
|
|
1518
|
+
* Validate per-call run/stream options (`Engine.run(intent, options)` / `Engine.stream(...)`).
|
|
1519
|
+
*/
|
|
1520
|
+
function validateRunCallOptions(options, path = "options") {
|
|
1521
|
+
if (options === void 0) return;
|
|
1522
|
+
const record = requireRecord(options, path);
|
|
1523
|
+
validateOptionalNonNegativeInteger(record.maxDepth, `${path}.maxDepth`);
|
|
1524
|
+
validateOptionalPositiveInteger(record.maxConcurrentChildren, `${path}.maxConcurrentChildren`);
|
|
1525
|
+
validateOptionalOnChildFailure(record.onChildFailure, `${path}.onChildFailure`);
|
|
1526
|
+
}
|
|
1527
|
+
/**
|
|
990
1528
|
* Validate low-level engine configuration before normalizing reusable controls.
|
|
991
1529
|
*/
|
|
992
1530
|
function validateEngineOptions(options) {
|
|
@@ -1003,6 +1541,10 @@ function validateEngineOptions(options) {
|
|
|
1003
1541
|
validateOptionalFunction(options.evaluate, "evaluate");
|
|
1004
1542
|
validateOptionalSeed(options.seed, "seed");
|
|
1005
1543
|
validateOptionalAbortSignal(options.signal, "signal");
|
|
1544
|
+
validateOptionalNonNegativeInteger(options.maxDepth, "maxDepth");
|
|
1545
|
+
validateOptionalPositiveInteger(options.maxConcurrentChildren, "maxConcurrentChildren");
|
|
1546
|
+
validateOptionalPositiveFiniteNumber(options.defaultSubRunTimeoutMs, "defaultSubRunTimeoutMs");
|
|
1547
|
+
validateOptionalOnChildFailure(options.onChildFailure, "onChildFailure");
|
|
1006
1548
|
}
|
|
1007
1549
|
function validateProtocolSelection(value, path) {
|
|
1008
1550
|
if (typeof value === "string") {
|
|
@@ -1050,6 +1592,23 @@ function validateBudgetTier(value, path) {
|
|
|
1050
1592
|
actual: value
|
|
1051
1593
|
});
|
|
1052
1594
|
}
|
|
1595
|
+
function validateOptionalOnChildFailure(value, path) {
|
|
1596
|
+
if (value === void 0) return;
|
|
1597
|
+
if (value === "continue" || value === "abort") return;
|
|
1598
|
+
throw new DogpileError({
|
|
1599
|
+
code: "invalid-configuration",
|
|
1600
|
+
message: `Invalid onChildFailure: expected "continue" or "abort", got ${JSON.stringify(value)}`,
|
|
1601
|
+
retryable: false,
|
|
1602
|
+
detail: {
|
|
1603
|
+
kind: "configuration-validation",
|
|
1604
|
+
path,
|
|
1605
|
+
rule: "enum",
|
|
1606
|
+
expected: onChildFailureModes.join(" | "),
|
|
1607
|
+
received: describeValue(value),
|
|
1608
|
+
reason: "invalid-on-child-failure"
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1053
1612
|
/**
|
|
1054
1613
|
* Validate configured model provider definitions at registration boundaries.
|
|
1055
1614
|
*/
|
|
@@ -1059,6 +1618,21 @@ function validateModelProviderRegistration(value, path = "model") {
|
|
|
1059
1618
|
validateFunction(record.generate, `${path}.generate`);
|
|
1060
1619
|
validateOptionalFunction(record.stream, `${path}.stream`);
|
|
1061
1620
|
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Engine-time defense-in-depth check that a provider's optional
|
|
1623
|
+
* `metadata.locality` is one of the two valid values when present (Phase 3 D-03).
|
|
1624
|
+
* Catches user-implemented providers that bypass TypeScript checks.
|
|
1625
|
+
*/
|
|
1626
|
+
function validateProviderLocality(provider, pathPrefix = "model") {
|
|
1627
|
+
const loc = provider.metadata?.locality;
|
|
1628
|
+
if (loc !== void 0 && loc !== "local" && loc !== "remote") invalidConfiguration({
|
|
1629
|
+
path: `${pathPrefix}.metadata.locality`,
|
|
1630
|
+
rule: "enum",
|
|
1631
|
+
message: `${pathPrefix}.metadata.locality must be "local" or "remote" when provided.`,
|
|
1632
|
+
expected: "\"local\" | \"remote\"",
|
|
1633
|
+
actual: loc
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1062
1636
|
function validateOptionalAgents(value, path) {
|
|
1063
1637
|
if (value === void 0) return;
|
|
1064
1638
|
if (!Array.isArray(value)) invalidConfiguration({
|
|
@@ -1338,6 +1912,16 @@ function validateOptionalNonNegativeInteger(value, path) {
|
|
|
1338
1912
|
actual: value
|
|
1339
1913
|
});
|
|
1340
1914
|
}
|
|
1915
|
+
function validateOptionalPositiveFiniteNumber(value, path) {
|
|
1916
|
+
if (value === void 0) return;
|
|
1917
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) invalidConfiguration({
|
|
1918
|
+
path,
|
|
1919
|
+
rule: "positive-finite-number",
|
|
1920
|
+
message: "value must be a positive finite number.",
|
|
1921
|
+
expected: "finite number > 0",
|
|
1922
|
+
actual: value
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1341
1925
|
function validateOptionalNonNegativeNumber(value, path) {
|
|
1342
1926
|
if (value === void 0) return;
|
|
1343
1927
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) invalidConfiguration({
|
|
@@ -1402,7 +1986,7 @@ function describeValue(value) {
|
|
|
1402
1986
|
return typeof value;
|
|
1403
1987
|
}
|
|
1404
1988
|
//#endregion
|
|
1405
|
-
//#region src/runtime/tools.ts
|
|
1989
|
+
//#region src/runtime/tools/built-in.ts
|
|
1406
1990
|
var webSearchIdentity = {
|
|
1407
1991
|
id: "dogpile.tools.webSearch",
|
|
1408
1992
|
namespace: "dogpile",
|
|
@@ -1488,9 +2072,6 @@ function builtInDogpileToolIdentity(name) {
|
|
|
1488
2072
|
function builtInDogpileToolInputSchema(name) {
|
|
1489
2073
|
return name === "webSearch" ? webSearchInputSchema : codeExecInputSchema;
|
|
1490
2074
|
}
|
|
1491
|
-
/**
|
|
1492
|
-
* Return the default permission declarations for one built-in tool name.
|
|
1493
|
-
*/
|
|
1494
2075
|
function builtInDogpileToolPermissions(name) {
|
|
1495
2076
|
return name === "webSearch" ? webSearchPermissions : codeExecPermissions;
|
|
1496
2077
|
}
|
|
@@ -1501,276 +2082,28 @@ function validateBuiltInDogpileToolInput(name, input) {
|
|
|
1501
2082
|
issues
|
|
1502
2083
|
};
|
|
1503
2084
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
validateRuntimeToolRegistrations(options.tools);
|
|
1516
|
-
const tools = Array.from(options.tools);
|
|
1517
|
-
let callCount = 0;
|
|
1518
|
-
return {
|
|
1519
|
-
tools,
|
|
1520
|
-
async execute(request) {
|
|
1521
|
-
const tool = tools.find((candidate) => candidate.identity.id === request.toolId);
|
|
1522
|
-
const identity = tool?.identity ?? {
|
|
1523
|
-
id: request.toolId,
|
|
1524
|
-
name: request.toolId
|
|
1525
|
-
};
|
|
1526
|
-
const callIndex = callCount;
|
|
1527
|
-
callCount += 1;
|
|
1528
|
-
const toolCallId = request.toolCallId ?? options.makeToolCallId?.(identity, callIndex) ?? defaultToolCallId(options.runId, callIndex);
|
|
1529
|
-
const context = createExecutionContext(options, request, toolCallId);
|
|
1530
|
-
options.emit?.({
|
|
1531
|
-
type: "tool-call",
|
|
1532
|
-
runId: options.runId,
|
|
1533
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1534
|
-
toolCallId,
|
|
1535
|
-
tool: identity,
|
|
1536
|
-
input: request.input,
|
|
1537
|
-
...request.agentId ? { agentId: request.agentId } : {},
|
|
1538
|
-
...request.role ? { role: request.role } : {}
|
|
1539
|
-
});
|
|
1540
|
-
const result = await executeRuntimeTool(tool, identity, request.input, context);
|
|
1541
|
-
options.emit?.({
|
|
1542
|
-
type: "tool-result",
|
|
1543
|
-
runId: options.runId,
|
|
1544
|
-
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1545
|
-
toolCallId,
|
|
2085
|
+
function createWebSearchToolAdapter(options) {
|
|
2086
|
+
const identity = mergeIdentity(webSearchIdentity, options.identity);
|
|
2087
|
+
return normalizeBuiltInDogpileTool({
|
|
2088
|
+
name: "webSearch",
|
|
2089
|
+
...options.identity ? { identity: options.identity } : {},
|
|
2090
|
+
...options.permissions ? { permissions: options.permissions } : {},
|
|
2091
|
+
async execute(input, context) {
|
|
2092
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
2093
|
+
if (!fetchImplementation) return {
|
|
2094
|
+
type: "error",
|
|
2095
|
+
toolCallId: context.toolCallId,
|
|
1546
2096
|
tool: identity,
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
*/
|
|
1558
|
-
function runtimeToolManifest(tools) {
|
|
1559
|
-
return tools.map((tool) => {
|
|
1560
|
-
const inputSchema = {
|
|
1561
|
-
kind: tool.inputSchema.kind,
|
|
1562
|
-
schema: tool.inputSchema.schema,
|
|
1563
|
-
...tool.inputSchema.description ? { description: tool.inputSchema.description } : {}
|
|
1564
|
-
};
|
|
1565
|
-
return {
|
|
1566
|
-
identity: runtimeToolIdentityManifest(tool.identity),
|
|
1567
|
-
inputSchema,
|
|
1568
|
-
permissions: Array.from(tool.permissions ?? []).map(runtimeToolPermissionManifest)
|
|
1569
|
-
};
|
|
1570
|
-
});
|
|
1571
|
-
}
|
|
1572
|
-
/**
|
|
1573
|
-
* Return request metadata that makes runtime tools visible to provider
|
|
1574
|
-
* adapters, or an empty object when no tools are available.
|
|
1575
|
-
*/
|
|
1576
|
-
function runtimeToolAvailability(tools) {
|
|
1577
|
-
const manifest = runtimeToolManifest(tools);
|
|
1578
|
-
return manifest.length > 0 ? { tools: manifest } : {};
|
|
1579
|
-
}
|
|
1580
|
-
/**
|
|
1581
|
-
* Execute normalized tool requests returned by a provider response.
|
|
1582
|
-
*/
|
|
1583
|
-
async function executeModelResponseToolRequests(options) {
|
|
1584
|
-
const toolCalls = [];
|
|
1585
|
-
for (const request of options.response.toolRequests ?? []) {
|
|
1586
|
-
const result = await options.executor.execute({
|
|
1587
|
-
...request,
|
|
1588
|
-
agentId: request.agentId ?? options.agentId,
|
|
1589
|
-
role: request.role ?? options.role,
|
|
1590
|
-
turn: request.turn ?? options.turn,
|
|
1591
|
-
metadata: mergeToolMetadata(options.metadata, request.metadata)
|
|
1592
|
-
});
|
|
1593
|
-
toolCalls.push({
|
|
1594
|
-
toolCallId: result.toolCallId,
|
|
1595
|
-
tool: result.tool,
|
|
1596
|
-
input: request.input,
|
|
1597
|
-
result
|
|
1598
|
-
});
|
|
1599
|
-
}
|
|
1600
|
-
return toolCalls;
|
|
1601
|
-
}
|
|
1602
|
-
function runtimeToolIdentityManifest(identity) {
|
|
1603
|
-
return {
|
|
1604
|
-
id: identity.id,
|
|
1605
|
-
name: identity.name,
|
|
1606
|
-
...identity.namespace ? { namespace: identity.namespace } : {},
|
|
1607
|
-
...identity.version ? { version: identity.version } : {},
|
|
1608
|
-
...identity.description ? { description: identity.description } : {}
|
|
1609
|
-
};
|
|
1610
|
-
}
|
|
1611
|
-
function runtimeToolPermissionManifest(permission) {
|
|
1612
|
-
if (permission.kind === "network") return {
|
|
1613
|
-
kind: permission.kind,
|
|
1614
|
-
...permission.allowHosts ? { allowHosts: Array.from(permission.allowHosts) } : {},
|
|
1615
|
-
...permission.allowPrivateNetwork === void 0 ? {} : { allowPrivateNetwork: permission.allowPrivateNetwork }
|
|
1616
|
-
};
|
|
1617
|
-
if (permission.kind === "code-execution") return {
|
|
1618
|
-
kind: permission.kind,
|
|
1619
|
-
sandbox: permission.sandbox,
|
|
1620
|
-
...permission.languages ? { languages: Array.from(permission.languages) } : {},
|
|
1621
|
-
...permission.allowNetwork === void 0 ? {} : { allowNetwork: permission.allowNetwork }
|
|
1622
|
-
};
|
|
1623
|
-
return {
|
|
1624
|
-
kind: permission.kind,
|
|
1625
|
-
name: permission.name,
|
|
1626
|
-
...permission.description ? { description: permission.description } : {},
|
|
1627
|
-
...permission.metadata ? { metadata: permission.metadata } : {}
|
|
1628
|
-
};
|
|
1629
|
-
}
|
|
1630
|
-
function validateCodeExecAdapterInput(input, options) {
|
|
1631
|
-
const issues = [...validateCodeExecInput(input)];
|
|
1632
|
-
const languages = options.languages ?? codeExecLanguages;
|
|
1633
|
-
if (typeof input.language === "string" && isCodeExecLanguage(input.language) && !languages.includes(input.language)) issues.push({
|
|
1634
|
-
code: "invalid-value",
|
|
1635
|
-
path: "language",
|
|
1636
|
-
message: "codeExec.language is not enabled for this adapter.",
|
|
1637
|
-
detail: { allowed: Array.from(languages) }
|
|
1638
|
-
});
|
|
1639
|
-
const effectiveTimeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
|
|
1640
|
-
if (effectiveTimeoutMs !== void 0 && options.maxTimeoutMs !== void 0 && Number.isFinite(effectiveTimeoutMs) && Number.isFinite(options.maxTimeoutMs) && effectiveTimeoutMs > options.maxTimeoutMs) issues.push({
|
|
1641
|
-
code: "out-of-range",
|
|
1642
|
-
path: input.timeoutMs === void 0 ? "defaultTimeoutMs" : "timeoutMs",
|
|
1643
|
-
message: `codeExec.timeoutMs must be less than or equal to ${options.maxTimeoutMs}.`,
|
|
1644
|
-
detail: { maximum: options.maxTimeoutMs }
|
|
1645
|
-
});
|
|
1646
|
-
return issues.length === 0 ? { type: "valid" } : {
|
|
1647
|
-
type: "invalid",
|
|
1648
|
-
issues
|
|
1649
|
-
};
|
|
1650
|
-
}
|
|
1651
|
-
function createExecutionContext(options, request, toolCallId) {
|
|
1652
|
-
return {
|
|
1653
|
-
runId: options.runId,
|
|
1654
|
-
toolCallId,
|
|
1655
|
-
protocol: options.protocol,
|
|
1656
|
-
tier: options.tier,
|
|
1657
|
-
...request.agentId ? { agentId: request.agentId } : {},
|
|
1658
|
-
...request.role ? { role: request.role } : {},
|
|
1659
|
-
...request.turn !== void 0 ? { turn: request.turn } : {},
|
|
1660
|
-
...options.getTrace ? { trace: options.getTrace() } : {},
|
|
1661
|
-
...request.abortSignal ?? options.abortSignal ? { abortSignal: request.abortSignal ?? options.abortSignal } : {},
|
|
1662
|
-
...options.metadata || request.metadata ? { metadata: mergeToolMetadata(options.metadata, request.metadata) } : {}
|
|
1663
|
-
};
|
|
1664
|
-
}
|
|
1665
|
-
async function executeRuntimeTool(tool, identity, input, context) {
|
|
1666
|
-
if (!tool) return {
|
|
1667
|
-
type: "error",
|
|
1668
|
-
toolCallId: context.toolCallId,
|
|
1669
|
-
tool: identity,
|
|
1670
|
-
error: {
|
|
1671
|
-
code: "unavailable",
|
|
1672
|
-
message: `Runtime tool "${identity.id}" is not registered.`,
|
|
1673
|
-
retryable: false
|
|
1674
|
-
}
|
|
1675
|
-
};
|
|
1676
|
-
const validation = validateRuntimeToolInput(tool, input);
|
|
1677
|
-
if (validation.type === "invalid") return {
|
|
1678
|
-
type: "error",
|
|
1679
|
-
toolCallId: context.toolCallId,
|
|
1680
|
-
tool: identity,
|
|
1681
|
-
error: {
|
|
1682
|
-
code: "invalid-input",
|
|
1683
|
-
message: "Runtime tool input failed validation.",
|
|
1684
|
-
retryable: false,
|
|
1685
|
-
detail: { issues: validation.issues.map((issue) => ({
|
|
1686
|
-
code: issue.code,
|
|
1687
|
-
path: issue.path,
|
|
1688
|
-
message: issue.message,
|
|
1689
|
-
...issue.detail ? { detail: issue.detail } : {}
|
|
1690
|
-
})) }
|
|
1691
|
-
}
|
|
1692
|
-
};
|
|
1693
|
-
try {
|
|
1694
|
-
return await tool.execute(input, context);
|
|
1695
|
-
} catch (error) {
|
|
1696
|
-
return {
|
|
1697
|
-
type: "error",
|
|
1698
|
-
toolCallId: context.toolCallId,
|
|
1699
|
-
tool: identity,
|
|
1700
|
-
error: normalizeRuntimeToolAdapterError(error)
|
|
1701
|
-
};
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
function validateRuntimeToolInput(tool, input) {
|
|
1705
|
-
if (typeof tool.validateInput !== "function") return { type: "valid" };
|
|
1706
|
-
return tool.validateInput(input);
|
|
1707
|
-
}
|
|
1708
|
-
function mergeToolMetadata(base, request) {
|
|
1709
|
-
return {
|
|
1710
|
-
...base ?? {},
|
|
1711
|
-
...request ?? {}
|
|
1712
|
-
};
|
|
1713
|
-
}
|
|
1714
|
-
function defaultToolCallId(runId, callIndex) {
|
|
1715
|
-
return `${runId}:tool-${callIndex + 1}`;
|
|
1716
|
-
}
|
|
1717
|
-
/**
|
|
1718
|
-
* Convert an unknown adapter failure into Dogpile's serializable error data.
|
|
1719
|
-
*/
|
|
1720
|
-
function normalizeRuntimeToolAdapterError(error) {
|
|
1721
|
-
if (isRuntimeToolAdapterError(error)) return error;
|
|
1722
|
-
if (error instanceof DOMException && error.name === "AbortError") return {
|
|
1723
|
-
code: "aborted",
|
|
1724
|
-
message: error.message || "Tool execution was aborted.",
|
|
1725
|
-
retryable: true,
|
|
1726
|
-
detail: { name: error.name }
|
|
1727
|
-
};
|
|
1728
|
-
if (error instanceof Error) return {
|
|
1729
|
-
code: "backend-error",
|
|
1730
|
-
message: error.message,
|
|
1731
|
-
retryable: false,
|
|
1732
|
-
detail: { name: error.name }
|
|
1733
|
-
};
|
|
1734
|
-
return {
|
|
1735
|
-
code: "unknown",
|
|
1736
|
-
message: "Tool execution failed with a non-Error value.",
|
|
1737
|
-
retryable: false,
|
|
1738
|
-
detail: { valueType: typeof error }
|
|
1739
|
-
};
|
|
1740
|
-
}
|
|
1741
|
-
/**
|
|
1742
|
-
* Create Dogpile's built-in fetch-based web search adapter.
|
|
1743
|
-
*
|
|
1744
|
-
* @remarks
|
|
1745
|
-
* The adapter is backend-neutral: by default it sends a GET request with
|
|
1746
|
-
* `q` and `limit` query parameters, then accepts either `{ results: [...] }`
|
|
1747
|
-
* or a bare array of result objects from the response JSON. Callers can replace
|
|
1748
|
-
* request construction or response parsing for a specific search API while
|
|
1749
|
-
* keeping Dogpile's shared runtime tool contract, identity, permissions, input
|
|
1750
|
-
* validation, and serializable errors.
|
|
1751
|
-
*/
|
|
1752
|
-
function createWebSearchToolAdapter(options) {
|
|
1753
|
-
const identity = mergeIdentity(webSearchIdentity, options.identity);
|
|
1754
|
-
return normalizeBuiltInDogpileTool({
|
|
1755
|
-
name: "webSearch",
|
|
1756
|
-
...options.identity ? { identity: options.identity } : {},
|
|
1757
|
-
...options.permissions ? { permissions: options.permissions } : {},
|
|
1758
|
-
async execute(input, context) {
|
|
1759
|
-
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
1760
|
-
if (!fetchImplementation) return {
|
|
1761
|
-
type: "error",
|
|
1762
|
-
toolCallId: context.toolCallId,
|
|
1763
|
-
tool: identity,
|
|
1764
|
-
error: {
|
|
1765
|
-
code: "unavailable",
|
|
1766
|
-
message: "No fetch implementation is available for webSearch.",
|
|
1767
|
-
retryable: false
|
|
1768
|
-
}
|
|
1769
|
-
};
|
|
1770
|
-
const request = options.buildRequest ? options.buildRequest(input, context) : defaultWebSearchRequest(options, input, context);
|
|
1771
|
-
const response = await fetchImplementation(request.url, {
|
|
1772
|
-
...request.init,
|
|
1773
|
-
...context.abortSignal ? { signal: context.abortSignal } : {}
|
|
2097
|
+
error: {
|
|
2098
|
+
code: "unavailable",
|
|
2099
|
+
message: "No fetch implementation is available for webSearch.",
|
|
2100
|
+
retryable: false
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
const request = options.buildRequest ? options.buildRequest(input, context) : defaultWebSearchRequest(options, input, context);
|
|
2104
|
+
const response = await fetchImplementation(request.url, {
|
|
2105
|
+
...request.init,
|
|
2106
|
+
...context.abortSignal ? { signal: context.abortSignal } : {}
|
|
1774
2107
|
});
|
|
1775
2108
|
if (!response.ok) throw {
|
|
1776
2109
|
code: response.status >= 500 ? "unavailable" : "backend-error",
|
|
@@ -1791,15 +2124,6 @@ function createWebSearchToolAdapter(options) {
|
|
|
1791
2124
|
}
|
|
1792
2125
|
});
|
|
1793
2126
|
}
|
|
1794
|
-
/**
|
|
1795
|
-
* Create Dogpile's built-in code execution adapter around a caller-owned sandbox.
|
|
1796
|
-
*
|
|
1797
|
-
* @remarks
|
|
1798
|
-
* Dogpile core stays runtime-portable and never evaluates code itself. This
|
|
1799
|
-
* adapter supplies the stable `codeExec` identity, schema, permissions,
|
|
1800
|
-
* validation, timeout defaults, abort handling, and serializable errors while
|
|
1801
|
-
* the host application owns the sandbox boundary.
|
|
1802
|
-
*/
|
|
1803
2127
|
function createCodeExecToolAdapter(options) {
|
|
1804
2128
|
const identity = mergeIdentity(codeExecIdentity, options.identity);
|
|
1805
2129
|
const permissions = options.permissions ?? codeExecPermissionsFor(options.languages ?? codeExecLanguages, options.allowNetwork ?? false);
|
|
@@ -1818,7 +2142,7 @@ function createCodeExecToolAdapter(options) {
|
|
|
1818
2142
|
code: "invalid-input",
|
|
1819
2143
|
message: "Invalid codeExec tool input.",
|
|
1820
2144
|
retryable: false,
|
|
1821
|
-
detail: { issues: validation.issues }
|
|
2145
|
+
detail: { issues: serializeValidationIssues(validation.issues) }
|
|
1822
2146
|
}
|
|
1823
2147
|
};
|
|
1824
2148
|
const timeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
|
|
@@ -1871,9 +2195,6 @@ function normalizeBuiltInDogpileTool(definition) {
|
|
|
1871
2195
|
}
|
|
1872
2196
|
}
|
|
1873
2197
|
}
|
|
1874
|
-
/**
|
|
1875
|
-
* Normalize configured built-in Dogpile tool executors into runtime tools.
|
|
1876
|
-
*/
|
|
1877
2198
|
function normalizeBuiltInDogpileTools(tools) {
|
|
1878
2199
|
const normalized = [];
|
|
1879
2200
|
if (tools.webSearch) normalized.push(normalizeBuiltInDogpileTool(asWebSearchDefinition(tools.webSearch)));
|
|
@@ -1890,7 +2211,7 @@ async function executeBuiltInTool(identity, execute, input, context, name) {
|
|
|
1890
2211
|
code: "invalid-input",
|
|
1891
2212
|
message: `Invalid ${name} tool input.`,
|
|
1892
2213
|
retryable: false,
|
|
1893
|
-
detail: { issues: validation.issues }
|
|
2214
|
+
detail: { issues: serializeValidationIssues(validation.issues) }
|
|
1894
2215
|
}
|
|
1895
2216
|
};
|
|
1896
2217
|
try {
|
|
@@ -1925,6 +2246,27 @@ function mergeIdentity(defaultIdentity, options) {
|
|
|
1925
2246
|
...options.description !== void 0 ? { description: options.description } : {}
|
|
1926
2247
|
};
|
|
1927
2248
|
}
|
|
2249
|
+
function validateCodeExecAdapterInput(input, options) {
|
|
2250
|
+
const issues = [...validateCodeExecInput(input)];
|
|
2251
|
+
const languages = options.languages ?? codeExecLanguages;
|
|
2252
|
+
if (typeof input.language === "string" && isCodeExecLanguage(input.language) && !languages.includes(input.language)) issues.push({
|
|
2253
|
+
code: "invalid-value",
|
|
2254
|
+
path: "language",
|
|
2255
|
+
message: "codeExec.language is not enabled for this adapter.",
|
|
2256
|
+
detail: { allowed: Array.from(languages) }
|
|
2257
|
+
});
|
|
2258
|
+
const effectiveTimeoutMs = input.timeoutMs ?? options.defaultTimeoutMs;
|
|
2259
|
+
if (effectiveTimeoutMs !== void 0 && options.maxTimeoutMs !== void 0 && Number.isFinite(effectiveTimeoutMs) && Number.isFinite(options.maxTimeoutMs) && effectiveTimeoutMs > options.maxTimeoutMs) issues.push({
|
|
2260
|
+
code: "out-of-range",
|
|
2261
|
+
path: input.timeoutMs === void 0 ? "defaultTimeoutMs" : "timeoutMs",
|
|
2262
|
+
message: `codeExec.timeoutMs must be less than or equal to ${options.maxTimeoutMs}.`,
|
|
2263
|
+
detail: { maximum: options.maxTimeoutMs }
|
|
2264
|
+
});
|
|
2265
|
+
return issues.length === 0 ? { type: "valid" } : {
|
|
2266
|
+
type: "invalid",
|
|
2267
|
+
issues
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
1928
2270
|
function defaultWebSearchRequest(options, input, _context) {
|
|
1929
2271
|
const url = new URL(String(options.endpoint));
|
|
1930
2272
|
url.searchParams.set("q", input.query);
|
|
@@ -2024,122 +2366,341 @@ function normalizeWebSearchResult(value) {
|
|
|
2024
2366
|
message: "Web search result must be a JSON object.",
|
|
2025
2367
|
retryable: false
|
|
2026
2368
|
};
|
|
2027
|
-
const title = jsonString(value.title, "title");
|
|
2028
|
-
const url = jsonString(value.url, "url");
|
|
2029
|
-
const snippet = optionalJsonString(value.snippet, "snippet");
|
|
2030
|
-
const metadata = optionalJsonObject(value.metadata, "metadata");
|
|
2369
|
+
const title = jsonString(value.title, "title");
|
|
2370
|
+
const url = jsonString(value.url, "url");
|
|
2371
|
+
const snippet = optionalJsonString(value.snippet, "snippet");
|
|
2372
|
+
const metadata = optionalJsonObject(value.metadata, "metadata");
|
|
2373
|
+
return {
|
|
2374
|
+
title,
|
|
2375
|
+
url,
|
|
2376
|
+
...snippet !== void 0 ? { snippet } : {},
|
|
2377
|
+
...metadata !== void 0 ? { metadata } : {}
|
|
2378
|
+
};
|
|
2379
|
+
}
|
|
2380
|
+
function jsonString(value, fieldName) {
|
|
2381
|
+
if (typeof value !== "string" || value.trim().length === 0) throw {
|
|
2382
|
+
code: "backend-error",
|
|
2383
|
+
message: `Web search result ${fieldName} must be a non-empty string.`,
|
|
2384
|
+
retryable: false
|
|
2385
|
+
};
|
|
2386
|
+
return value;
|
|
2387
|
+
}
|
|
2388
|
+
function optionalJsonString(value, fieldName) {
|
|
2389
|
+
if (value === void 0) return void 0;
|
|
2390
|
+
if (typeof value !== "string") throw {
|
|
2391
|
+
code: "backend-error",
|
|
2392
|
+
message: `Web search result ${fieldName} must be a string when present.`,
|
|
2393
|
+
retryable: false
|
|
2394
|
+
};
|
|
2395
|
+
return value;
|
|
2396
|
+
}
|
|
2397
|
+
function optionalJsonObject(value, fieldName) {
|
|
2398
|
+
if (value === void 0) return void 0;
|
|
2399
|
+
if (!isJsonObject(value)) throw {
|
|
2400
|
+
code: "backend-error",
|
|
2401
|
+
message: `Web search result ${fieldName} must be a JSON object when present.`,
|
|
2402
|
+
retryable: false
|
|
2403
|
+
};
|
|
2404
|
+
return value;
|
|
2405
|
+
}
|
|
2406
|
+
function isJsonObject(value) {
|
|
2407
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2408
|
+
}
|
|
2409
|
+
function validateWebSearchInput(input) {
|
|
2410
|
+
const issues = [];
|
|
2411
|
+
if (typeof input.query !== "string") issues.push({
|
|
2412
|
+
code: input.query === void 0 ? "missing-field" : "invalid-type",
|
|
2413
|
+
path: "query",
|
|
2414
|
+
message: "webSearch.query must be a string."
|
|
2415
|
+
});
|
|
2416
|
+
else if (input.query.trim().length === 0) issues.push({
|
|
2417
|
+
code: "invalid-value",
|
|
2418
|
+
path: "query",
|
|
2419
|
+
message: "webSearch.query must not be empty."
|
|
2420
|
+
});
|
|
2421
|
+
if (input.maxResults !== void 0) {
|
|
2422
|
+
if (typeof input.maxResults !== "number" || !Number.isFinite(input.maxResults)) issues.push({
|
|
2423
|
+
code: "invalid-type",
|
|
2424
|
+
path: "maxResults",
|
|
2425
|
+
message: "webSearch.maxResults must be a finite number."
|
|
2426
|
+
});
|
|
2427
|
+
else if (input.maxResults < 1) issues.push({
|
|
2428
|
+
code: "out-of-range",
|
|
2429
|
+
path: "maxResults",
|
|
2430
|
+
message: "webSearch.maxResults must be greater than or equal to 1.",
|
|
2431
|
+
detail: { minimum: 1 }
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
return issues;
|
|
2435
|
+
}
|
|
2436
|
+
function validateCodeExecInput(input) {
|
|
2437
|
+
const issues = [];
|
|
2438
|
+
if (typeof input.language !== "string") issues.push({
|
|
2439
|
+
code: input.language === void 0 ? "missing-field" : "invalid-type",
|
|
2440
|
+
path: "language",
|
|
2441
|
+
message: "codeExec.language must be a string."
|
|
2442
|
+
});
|
|
2443
|
+
else if (!isCodeExecLanguage(input.language)) issues.push({
|
|
2444
|
+
code: "invalid-value",
|
|
2445
|
+
path: "language",
|
|
2446
|
+
message: "codeExec.language must be one of javascript, typescript, python, bash, or shell.",
|
|
2447
|
+
detail: { allowed: [
|
|
2448
|
+
"javascript",
|
|
2449
|
+
"typescript",
|
|
2450
|
+
"python",
|
|
2451
|
+
"bash",
|
|
2452
|
+
"shell"
|
|
2453
|
+
] }
|
|
2454
|
+
});
|
|
2455
|
+
if (typeof input.code !== "string") issues.push({
|
|
2456
|
+
code: input.code === void 0 ? "missing-field" : "invalid-type",
|
|
2457
|
+
path: "code",
|
|
2458
|
+
message: "codeExec.code must be a string."
|
|
2459
|
+
});
|
|
2460
|
+
if (input.timeoutMs !== void 0) {
|
|
2461
|
+
if (typeof input.timeoutMs !== "number" || !Number.isFinite(input.timeoutMs)) issues.push({
|
|
2462
|
+
code: "invalid-type",
|
|
2463
|
+
path: "timeoutMs",
|
|
2464
|
+
message: "codeExec.timeoutMs must be a finite number."
|
|
2465
|
+
});
|
|
2466
|
+
else if (input.timeoutMs < 1) issues.push({
|
|
2467
|
+
code: "out-of-range",
|
|
2468
|
+
path: "timeoutMs",
|
|
2469
|
+
message: "codeExec.timeoutMs must be greater than or equal to 1.",
|
|
2470
|
+
detail: { minimum: 1 }
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
return issues;
|
|
2474
|
+
}
|
|
2475
|
+
function isCodeExecLanguage(value) {
|
|
2476
|
+
return value === "javascript" || value === "typescript" || value === "python" || value === "bash" || value === "shell";
|
|
2477
|
+
}
|
|
2478
|
+
function normalizeRuntimeToolAdapterError(error) {
|
|
2479
|
+
if (isRuntimeToolAdapterError(error)) return error;
|
|
2480
|
+
if (error instanceof DOMException && error.name === "AbortError") return {
|
|
2481
|
+
code: "aborted",
|
|
2482
|
+
message: error.message || "Tool execution was aborted.",
|
|
2483
|
+
retryable: true,
|
|
2484
|
+
detail: { name: error.name }
|
|
2485
|
+
};
|
|
2486
|
+
if (error instanceof Error) return {
|
|
2487
|
+
code: "backend-error",
|
|
2488
|
+
message: error.message,
|
|
2489
|
+
retryable: false,
|
|
2490
|
+
detail: { name: error.name }
|
|
2491
|
+
};
|
|
2492
|
+
return {
|
|
2493
|
+
code: "unknown",
|
|
2494
|
+
message: "Tool execution failed with a non-Error value.",
|
|
2495
|
+
retryable: false,
|
|
2496
|
+
detail: { valueType: typeof error }
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
function isRuntimeToolAdapterError(error) {
|
|
2500
|
+
if (typeof error !== "object" || error === null || !("code" in error) || !("message" in error)) return false;
|
|
2501
|
+
const candidate = error;
|
|
2502
|
+
return isRuntimeToolAdapterErrorCode(candidate.code) && typeof candidate.message === "string";
|
|
2503
|
+
}
|
|
2504
|
+
function isRuntimeToolAdapterErrorCode(value) {
|
|
2505
|
+
return value === "invalid-input" || value === "permission-denied" || value === "timeout" || value === "aborted" || value === "unavailable" || value === "backend-error" || value === "unknown";
|
|
2506
|
+
}
|
|
2507
|
+
function serializeValidationIssues(issues) {
|
|
2508
|
+
return issues.map((issue) => ({
|
|
2509
|
+
code: issue.code,
|
|
2510
|
+
path: issue.path,
|
|
2511
|
+
message: issue.message,
|
|
2512
|
+
...issue.detail !== void 0 ? { detail: issue.detail } : {}
|
|
2513
|
+
}));
|
|
2514
|
+
}
|
|
2515
|
+
//#endregion
|
|
2516
|
+
//#region src/runtime/tools.ts
|
|
2517
|
+
/**
|
|
2518
|
+
* Create the shared runtime tool executor used by every first-party protocol.
|
|
2519
|
+
*
|
|
2520
|
+
* @remarks
|
|
2521
|
+
* The executor owns call id generation, read-only trace context construction,
|
|
2522
|
+
* adapter validation, error normalization, and matched `tool-call` /
|
|
2523
|
+
* `tool-result` events. Protocols only supply a normalized
|
|
2524
|
+
* {@link RuntimeToolExecutionRequest}, which keeps tool execution independent
|
|
2525
|
+
* of Coordinator, Sequential, Broadcast, or Shared control flow.
|
|
2526
|
+
*/
|
|
2527
|
+
function createRuntimeToolExecutor(options) {
|
|
2528
|
+
validateRuntimeToolRegistrations(options.tools);
|
|
2529
|
+
const tools = Array.from(options.tools);
|
|
2530
|
+
let callCount = 0;
|
|
2531
|
+
return {
|
|
2532
|
+
tools,
|
|
2533
|
+
async execute(request) {
|
|
2534
|
+
const tool = tools.find((candidate) => candidate.identity.id === request.toolId);
|
|
2535
|
+
const identity = tool?.identity ?? {
|
|
2536
|
+
id: request.toolId,
|
|
2537
|
+
name: request.toolId
|
|
2538
|
+
};
|
|
2539
|
+
const callIndex = callCount;
|
|
2540
|
+
callCount += 1;
|
|
2541
|
+
const toolCallId = request.toolCallId ?? options.makeToolCallId?.(identity, callIndex) ?? defaultToolCallId(options.runId, callIndex);
|
|
2542
|
+
const context = createExecutionContext(options, request, toolCallId);
|
|
2543
|
+
options.emit?.({
|
|
2544
|
+
type: "tool-call",
|
|
2545
|
+
runId: options.runId,
|
|
2546
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2547
|
+
toolCallId,
|
|
2548
|
+
tool: identity,
|
|
2549
|
+
input: request.input,
|
|
2550
|
+
...request.agentId ? { agentId: request.agentId } : {},
|
|
2551
|
+
...request.role ? { role: request.role } : {}
|
|
2552
|
+
});
|
|
2553
|
+
const result = await executeRuntimeTool(tool, identity, request.input, context);
|
|
2554
|
+
options.emit?.({
|
|
2555
|
+
type: "tool-result",
|
|
2556
|
+
runId: options.runId,
|
|
2557
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2558
|
+
toolCallId,
|
|
2559
|
+
tool: identity,
|
|
2560
|
+
result,
|
|
2561
|
+
...request.agentId ? { agentId: request.agentId } : {},
|
|
2562
|
+
...request.role ? { role: request.role } : {}
|
|
2563
|
+
});
|
|
2564
|
+
return result;
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Return a JSON-serializable manifest for tools visible to a protocol run.
|
|
2570
|
+
*/
|
|
2571
|
+
function runtimeToolManifest(tools) {
|
|
2572
|
+
return tools.map((tool) => {
|
|
2573
|
+
const inputSchema = {
|
|
2574
|
+
kind: tool.inputSchema.kind,
|
|
2575
|
+
schema: tool.inputSchema.schema,
|
|
2576
|
+
...tool.inputSchema.description ? { description: tool.inputSchema.description } : {}
|
|
2577
|
+
};
|
|
2578
|
+
return {
|
|
2579
|
+
identity: runtimeToolIdentityManifest(tool.identity),
|
|
2580
|
+
inputSchema,
|
|
2581
|
+
permissions: Array.from(tool.permissions ?? []).map(runtimeToolPermissionManifest)
|
|
2582
|
+
};
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* Return request metadata that makes runtime tools visible to provider
|
|
2587
|
+
* adapters, or an empty object when no tools are available.
|
|
2588
|
+
*/
|
|
2589
|
+
function runtimeToolAvailability(tools) {
|
|
2590
|
+
const manifest = runtimeToolManifest(tools);
|
|
2591
|
+
return manifest.length > 0 ? { tools: manifest } : {};
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Execute normalized tool requests returned by a provider response.
|
|
2595
|
+
*/
|
|
2596
|
+
async function executeModelResponseToolRequests(options) {
|
|
2597
|
+
const toolCalls = [];
|
|
2598
|
+
for (const request of options.response.toolRequests ?? []) {
|
|
2599
|
+
const result = await options.executor.execute({
|
|
2600
|
+
...request,
|
|
2601
|
+
agentId: request.agentId ?? options.agentId,
|
|
2602
|
+
role: request.role ?? options.role,
|
|
2603
|
+
turn: request.turn ?? options.turn,
|
|
2604
|
+
metadata: mergeToolMetadata(options.metadata, request.metadata)
|
|
2605
|
+
});
|
|
2606
|
+
toolCalls.push({
|
|
2607
|
+
toolCallId: result.toolCallId,
|
|
2608
|
+
tool: result.tool,
|
|
2609
|
+
input: request.input,
|
|
2610
|
+
result
|
|
2611
|
+
});
|
|
2612
|
+
}
|
|
2613
|
+
return toolCalls;
|
|
2614
|
+
}
|
|
2615
|
+
function runtimeToolIdentityManifest(identity) {
|
|
2616
|
+
return {
|
|
2617
|
+
id: identity.id,
|
|
2618
|
+
name: identity.name,
|
|
2619
|
+
...identity.namespace ? { namespace: identity.namespace } : {},
|
|
2620
|
+
...identity.version ? { version: identity.version } : {},
|
|
2621
|
+
...identity.description ? { description: identity.description } : {}
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
function runtimeToolPermissionManifest(permission) {
|
|
2625
|
+
if (permission.kind === "network") return {
|
|
2626
|
+
kind: permission.kind,
|
|
2627
|
+
...permission.allowHosts ? { allowHosts: Array.from(permission.allowHosts) } : {},
|
|
2628
|
+
...permission.allowPrivateNetwork === void 0 ? {} : { allowPrivateNetwork: permission.allowPrivateNetwork }
|
|
2629
|
+
};
|
|
2630
|
+
if (permission.kind === "code-execution") return {
|
|
2631
|
+
kind: permission.kind,
|
|
2632
|
+
sandbox: permission.sandbox,
|
|
2633
|
+
...permission.languages ? { languages: Array.from(permission.languages) } : {},
|
|
2634
|
+
...permission.allowNetwork === void 0 ? {} : { allowNetwork: permission.allowNetwork }
|
|
2635
|
+
};
|
|
2031
2636
|
return {
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
...
|
|
2035
|
-
...metadata
|
|
2637
|
+
kind: permission.kind,
|
|
2638
|
+
name: permission.name,
|
|
2639
|
+
...permission.description ? { description: permission.description } : {},
|
|
2640
|
+
...permission.metadata ? { metadata: permission.metadata } : {}
|
|
2036
2641
|
};
|
|
2037
2642
|
}
|
|
2038
|
-
function
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2643
|
+
function createExecutionContext(options, request, toolCallId) {
|
|
2644
|
+
return {
|
|
2645
|
+
runId: options.runId,
|
|
2646
|
+
toolCallId,
|
|
2647
|
+
protocol: options.protocol,
|
|
2648
|
+
tier: options.tier,
|
|
2649
|
+
...request.agentId ? { agentId: request.agentId } : {},
|
|
2650
|
+
...request.role ? { role: request.role } : {},
|
|
2651
|
+
...request.turn !== void 0 ? { turn: request.turn } : {},
|
|
2652
|
+
...options.getTrace ? { trace: options.getTrace() } : {},
|
|
2653
|
+
...request.abortSignal ?? options.abortSignal ? { abortSignal: request.abortSignal ?? options.abortSignal } : {},
|
|
2654
|
+
...options.metadata || request.metadata ? { metadata: mergeToolMetadata(options.metadata, request.metadata) } : {}
|
|
2043
2655
|
};
|
|
2044
|
-
return value;
|
|
2045
2656
|
}
|
|
2046
|
-
function
|
|
2047
|
-
if (
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2657
|
+
async function executeRuntimeTool(tool, identity, input, context) {
|
|
2658
|
+
if (!tool) return {
|
|
2659
|
+
type: "error",
|
|
2660
|
+
toolCallId: context.toolCallId,
|
|
2661
|
+
tool: identity,
|
|
2662
|
+
error: {
|
|
2663
|
+
code: "unavailable",
|
|
2664
|
+
message: `Runtime tool "${identity.id}" is not registered.`,
|
|
2665
|
+
retryable: false
|
|
2666
|
+
}
|
|
2052
2667
|
};
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2668
|
+
const validation = tool.validateInput?.(input);
|
|
2669
|
+
if (validation?.type === "invalid") return {
|
|
2670
|
+
type: "error",
|
|
2671
|
+
toolCallId: context.toolCallId,
|
|
2672
|
+
tool: identity,
|
|
2673
|
+
error: {
|
|
2674
|
+
code: "invalid-input",
|
|
2675
|
+
message: "Runtime tool input failed validation.",
|
|
2676
|
+
retryable: false,
|
|
2677
|
+
detail: { issues: validation.issues.map((issue) => ({
|
|
2678
|
+
code: issue.code,
|
|
2679
|
+
path: issue.path,
|
|
2680
|
+
message: issue.message,
|
|
2681
|
+
...issue.detail ? { detail: issue.detail } : {}
|
|
2682
|
+
})) }
|
|
2683
|
+
}
|
|
2061
2684
|
};
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
path: "query",
|
|
2072
|
-
message: "webSearch.query must be a string."
|
|
2073
|
-
});
|
|
2074
|
-
else if (input.query.trim().length === 0) issues.push({
|
|
2075
|
-
code: "invalid-value",
|
|
2076
|
-
path: "query",
|
|
2077
|
-
message: "webSearch.query must not be empty."
|
|
2078
|
-
});
|
|
2079
|
-
if (input.maxResults !== void 0) {
|
|
2080
|
-
if (typeof input.maxResults !== "number" || !Number.isFinite(input.maxResults)) issues.push({
|
|
2081
|
-
code: "invalid-type",
|
|
2082
|
-
path: "maxResults",
|
|
2083
|
-
message: "webSearch.maxResults must be a finite number."
|
|
2084
|
-
});
|
|
2085
|
-
else if (input.maxResults < 1) issues.push({
|
|
2086
|
-
code: "out-of-range",
|
|
2087
|
-
path: "maxResults",
|
|
2088
|
-
message: "webSearch.maxResults must be greater than or equal to 1.",
|
|
2089
|
-
detail: { minimum: 1 }
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
|
-
return issues;
|
|
2093
|
-
}
|
|
2094
|
-
function validateCodeExecInput(input) {
|
|
2095
|
-
const issues = [];
|
|
2096
|
-
if (typeof input.language !== "string") issues.push({
|
|
2097
|
-
code: input.language === void 0 ? "missing-field" : "invalid-type",
|
|
2098
|
-
path: "language",
|
|
2099
|
-
message: "codeExec.language must be a string."
|
|
2100
|
-
});
|
|
2101
|
-
else if (!isCodeExecLanguage(input.language)) issues.push({
|
|
2102
|
-
code: "invalid-value",
|
|
2103
|
-
path: "language",
|
|
2104
|
-
message: "codeExec.language must be one of javascript, typescript, python, bash, or shell.",
|
|
2105
|
-
detail: { allowed: [
|
|
2106
|
-
"javascript",
|
|
2107
|
-
"typescript",
|
|
2108
|
-
"python",
|
|
2109
|
-
"bash",
|
|
2110
|
-
"shell"
|
|
2111
|
-
] }
|
|
2112
|
-
});
|
|
2113
|
-
if (typeof input.code !== "string") issues.push({
|
|
2114
|
-
code: input.code === void 0 ? "missing-field" : "invalid-type",
|
|
2115
|
-
path: "code",
|
|
2116
|
-
message: "codeExec.code must be a string."
|
|
2117
|
-
});
|
|
2118
|
-
if (input.timeoutMs !== void 0) {
|
|
2119
|
-
if (typeof input.timeoutMs !== "number" || !Number.isFinite(input.timeoutMs)) issues.push({
|
|
2120
|
-
code: "invalid-type",
|
|
2121
|
-
path: "timeoutMs",
|
|
2122
|
-
message: "codeExec.timeoutMs must be a finite number."
|
|
2123
|
-
});
|
|
2124
|
-
else if (input.timeoutMs < 1) issues.push({
|
|
2125
|
-
code: "out-of-range",
|
|
2126
|
-
path: "timeoutMs",
|
|
2127
|
-
message: "codeExec.timeoutMs must be greater than or equal to 1.",
|
|
2128
|
-
detail: { minimum: 1 }
|
|
2129
|
-
});
|
|
2685
|
+
try {
|
|
2686
|
+
return await tool.execute(input, context);
|
|
2687
|
+
} catch (error) {
|
|
2688
|
+
return {
|
|
2689
|
+
type: "error",
|
|
2690
|
+
toolCallId: context.toolCallId,
|
|
2691
|
+
tool: identity,
|
|
2692
|
+
error: normalizeRuntimeToolAdapterError(error)
|
|
2693
|
+
};
|
|
2130
2694
|
}
|
|
2131
|
-
return issues;
|
|
2132
|
-
}
|
|
2133
|
-
function isCodeExecLanguage(value) {
|
|
2134
|
-
return value === "javascript" || value === "typescript" || value === "python" || value === "bash" || value === "shell";
|
|
2135
2695
|
}
|
|
2136
|
-
function
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2696
|
+
function mergeToolMetadata(base, request) {
|
|
2697
|
+
return {
|
|
2698
|
+
...base ?? {},
|
|
2699
|
+
...request ?? {}
|
|
2700
|
+
};
|
|
2140
2701
|
}
|
|
2141
|
-
function
|
|
2142
|
-
return
|
|
2702
|
+
function defaultToolCallId(runId, callIndex) {
|
|
2703
|
+
return `${runId}:tool-${callIndex + 1}`;
|
|
2143
2704
|
}
|
|
2144
2705
|
//#endregion
|
|
2145
2706
|
//#region src/runtime/wrap-up.ts
|
|
@@ -2289,7 +2850,7 @@ function minCap(left, right) {
|
|
|
2289
2850
|
//#endregion
|
|
2290
2851
|
//#region src/runtime/broadcast.ts
|
|
2291
2852
|
async function runBroadcast(options) {
|
|
2292
|
-
const runId = createRunId
|
|
2853
|
+
const runId = createRunId();
|
|
2293
2854
|
const events = [];
|
|
2294
2855
|
const transcript = [];
|
|
2295
2856
|
const protocolDecisions = [];
|
|
@@ -2298,7 +2859,7 @@ async function runBroadcast(options) {
|
|
|
2298
2859
|
const maxRounds = options.protocol.maxRounds ?? 2;
|
|
2299
2860
|
let firstRoundContributions = [];
|
|
2300
2861
|
let lastContributions = [];
|
|
2301
|
-
const startedAtMs = nowMs
|
|
2862
|
+
const startedAtMs = nowMs();
|
|
2302
2863
|
let stopped = false;
|
|
2303
2864
|
let termination;
|
|
2304
2865
|
const wrapUpHint = createWrapUpHintController({
|
|
@@ -2375,7 +2936,7 @@ async function runBroadcast(options) {
|
|
|
2375
2936
|
events,
|
|
2376
2937
|
transcript,
|
|
2377
2938
|
iteration: transcript.length,
|
|
2378
|
-
elapsedMs: elapsedMs
|
|
2939
|
+
elapsedMs: elapsedMs(startedAtMs)
|
|
2379
2940
|
})
|
|
2380
2941
|
};
|
|
2381
2942
|
const response = await generateModelTurn({
|
|
@@ -2385,7 +2946,7 @@ async function runBroadcast(options) {
|
|
|
2385
2946
|
agent,
|
|
2386
2947
|
input,
|
|
2387
2948
|
emit,
|
|
2388
|
-
callId: providerCallIdFor
|
|
2949
|
+
callId: providerCallIdFor(runId, providerCalls.length + agentIndex + 1),
|
|
2389
2950
|
onProviderCall(call) {
|
|
2390
2951
|
providerCallSlots[agentIndex] = call;
|
|
2391
2952
|
}
|
|
@@ -2550,7 +3111,7 @@ async function runBroadcast(options) {
|
|
|
2550
3111
|
events,
|
|
2551
3112
|
transcript,
|
|
2552
3113
|
iteration: transcript.length,
|
|
2553
|
-
elapsedMs: elapsedMs
|
|
3114
|
+
elapsedMs: elapsedMs(startedAtMs)
|
|
2554
3115
|
}));
|
|
2555
3116
|
if (!stopRecord) return false;
|
|
2556
3117
|
stopped = true;
|
|
@@ -2566,7 +3127,7 @@ async function runBroadcast(options) {
|
|
|
2566
3127
|
reason: record.budgetReason ?? "cost",
|
|
2567
3128
|
cost: totalCost,
|
|
2568
3129
|
iteration: transcript.length,
|
|
2569
|
-
elapsedMs: elapsedMs
|
|
3130
|
+
elapsedMs: elapsedMs(startedAtMs),
|
|
2570
3131
|
detail: record.detail ?? {}
|
|
2571
3132
|
};
|
|
2572
3133
|
emit(event);
|
|
@@ -2596,33 +3157,76 @@ function responseCost$3(response) {
|
|
|
2596
3157
|
totalTokens: response.usage?.totalTokens ?? 0
|
|
2597
3158
|
};
|
|
2598
3159
|
}
|
|
2599
|
-
function createRunId$3() {
|
|
2600
|
-
return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
|
|
2601
|
-
}
|
|
2602
|
-
function nowMs$3() {
|
|
2603
|
-
return globalThis.performance?.now() ?? Date.now();
|
|
2604
|
-
}
|
|
2605
|
-
function elapsedMs$3(startedAtMs) {
|
|
2606
|
-
return Math.max(0, nowMs$3() - startedAtMs);
|
|
2607
|
-
}
|
|
2608
|
-
function providerCallIdFor$2(runId, oneBasedIndex) {
|
|
2609
|
-
return `${runId}:provider-call:${oneBasedIndex}`;
|
|
2610
|
-
}
|
|
2611
3160
|
//#endregion
|
|
2612
3161
|
//#region src/runtime/coordinator.ts
|
|
3162
|
+
/**
|
|
3163
|
+
* Hard-coded loop guard for the delegate dispatch in the coordinator plan
|
|
3164
|
+
* turn. After this many consecutive delegate decisions the coordinator throws
|
|
3165
|
+
* `invalid-configuration` (T-03-01). Not a public option.
|
|
3166
|
+
*/
|
|
3167
|
+
var MAX_DISPATCH_PER_TURN = 8;
|
|
3168
|
+
var DEFAULT_MAX_CONCURRENT_CHILDREN$1 = 4;
|
|
3169
|
+
function createSemaphore(maxConcurrent) {
|
|
3170
|
+
let inFlight = 0;
|
|
3171
|
+
const waiters = [];
|
|
3172
|
+
return {
|
|
3173
|
+
acquire() {
|
|
3174
|
+
if (inFlight < maxConcurrent) {
|
|
3175
|
+
inFlight += 1;
|
|
3176
|
+
return Promise.resolve();
|
|
3177
|
+
}
|
|
3178
|
+
return new Promise((resolve) => {
|
|
3179
|
+
waiters.push(() => {
|
|
3180
|
+
inFlight += 1;
|
|
3181
|
+
resolve();
|
|
3182
|
+
});
|
|
3183
|
+
});
|
|
3184
|
+
},
|
|
3185
|
+
release() {
|
|
3186
|
+
inFlight -= 1;
|
|
3187
|
+
const next = waiters.shift();
|
|
3188
|
+
if (next !== void 0) next();
|
|
3189
|
+
},
|
|
3190
|
+
get inFlight() {
|
|
3191
|
+
return inFlight;
|
|
3192
|
+
},
|
|
3193
|
+
get queued() {
|
|
3194
|
+
return waiters.length;
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
}
|
|
3198
|
+
/**
|
|
3199
|
+
* Walk the coordinator's active provider set and return the FIRST provider
|
|
3200
|
+
* whose metadata.locality === "local", or undefined if none found.
|
|
3201
|
+
*
|
|
3202
|
+
* Walk order (forward-compat): options.model first, then options.agents in
|
|
3203
|
+
* declaration order. AgentSpec has no `model` field today (Phase 3 D-11
|
|
3204
|
+
* forward-compat scaffolding); the agent walk uses optional chaining and
|
|
3205
|
+
* effectively no-ops until a future phase adds AgentSpec.model.
|
|
3206
|
+
*/
|
|
3207
|
+
function findFirstLocalProvider(options) {
|
|
3208
|
+
if (options.model.metadata?.locality === "local") return options.model;
|
|
3209
|
+
for (const agent of options.agents) {
|
|
3210
|
+
const agentModel = agent.model;
|
|
3211
|
+
if (agentModel?.metadata?.locality === "local") return agentModel;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
2613
3214
|
async function runCoordinator(options) {
|
|
2614
|
-
const runId = createRunId
|
|
3215
|
+
const runId = createRunId();
|
|
2615
3216
|
const events = [];
|
|
2616
3217
|
const transcript = [];
|
|
2617
3218
|
const protocolDecisions = [];
|
|
2618
3219
|
const providerCalls = [];
|
|
3220
|
+
const dispatchedChildren = /* @__PURE__ */ new Map();
|
|
2619
3221
|
let totalCost = emptyCost();
|
|
3222
|
+
let concurrencyClampEmitted = false;
|
|
2620
3223
|
const maxTurns = options.protocol.maxTurns ?? options.agents.length;
|
|
2621
3224
|
const activeAgents = options.agents.slice(0, maxTurns);
|
|
2622
3225
|
const coordinator = activeAgents[0];
|
|
2623
|
-
const startedAtMs = nowMs
|
|
3226
|
+
const startedAtMs = nowMs();
|
|
2624
3227
|
let stopped = false;
|
|
2625
3228
|
let termination;
|
|
3229
|
+
let triggeringFailureForAbortMode;
|
|
2626
3230
|
const wrapUpHint = createWrapUpHintController({
|
|
2627
3231
|
protocol: options.protocol,
|
|
2628
3232
|
tier: options.tier,
|
|
@@ -2638,6 +3242,51 @@ async function runCoordinator(options) {
|
|
|
2638
3242
|
const recordProtocolDecision = (event, decisionOptions) => {
|
|
2639
3243
|
protocolDecisions.push(createReplayTraceProtocolDecision("coordinator", event, events.length - 1, decisionOptions));
|
|
2640
3244
|
};
|
|
3245
|
+
const drainOnParentAbort = (reasonSource) => {
|
|
3246
|
+
const reason = classifyAbortReason(reasonSource);
|
|
3247
|
+
for (const child of dispatchedChildren.values()) {
|
|
3248
|
+
if (child.closed) continue;
|
|
3249
|
+
const partialCost = child.started ? lastCostBearingEventCost(child.childEvents) ?? emptyCost() : emptyCost();
|
|
3250
|
+
const partialTrace = buildPartialTrace({
|
|
3251
|
+
childRunId: child.childRunId,
|
|
3252
|
+
events: [...child.childEvents],
|
|
3253
|
+
startedAtMs: child.startedAtMs,
|
|
3254
|
+
protocol: child.decision.protocol,
|
|
3255
|
+
tier: options.tier,
|
|
3256
|
+
modelProviderId: options.model.id,
|
|
3257
|
+
agents: options.agents,
|
|
3258
|
+
intent: child.decision.intent,
|
|
3259
|
+
temperature: options.temperature,
|
|
3260
|
+
...child.childTimeoutMs !== void 0 ? { childTimeoutMs: child.childTimeoutMs } : {},
|
|
3261
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
3262
|
+
});
|
|
3263
|
+
const failedEvent = {
|
|
3264
|
+
type: "sub-run-failed",
|
|
3265
|
+
runId,
|
|
3266
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3267
|
+
childRunId: child.childRunId,
|
|
3268
|
+
parentRunId: runId,
|
|
3269
|
+
parentDecisionId: child.parentDecisionId,
|
|
3270
|
+
parentDecisionArrayIndex: child.parentDecisionArrayIndex,
|
|
3271
|
+
error: child.started ? {
|
|
3272
|
+
code: "aborted",
|
|
3273
|
+
message: "Parent run aborted.",
|
|
3274
|
+
detail: { reason }
|
|
3275
|
+
} : {
|
|
3276
|
+
code: "aborted",
|
|
3277
|
+
message: "Sibling delegate failed; queued delegate never started.",
|
|
3278
|
+
detail: { reason: "sibling-failed" }
|
|
3279
|
+
},
|
|
3280
|
+
partialTrace,
|
|
3281
|
+
partialCost
|
|
3282
|
+
};
|
|
3283
|
+
child.closed = true;
|
|
3284
|
+
totalCost = addCost(totalCost, partialCost);
|
|
3285
|
+
emit(failedEvent);
|
|
3286
|
+
recordProtocolDecision(failedEvent);
|
|
3287
|
+
}
|
|
3288
|
+
};
|
|
3289
|
+
options.registerAbortDrain?.(drainOnParentAbort);
|
|
2641
3290
|
const toolExecutor = createRuntimeToolExecutor({
|
|
2642
3291
|
runId,
|
|
2643
3292
|
protocol: "coordinator",
|
|
@@ -2668,24 +3317,231 @@ async function runCoordinator(options) {
|
|
|
2668
3317
|
}
|
|
2669
3318
|
if (coordinator) {
|
|
2670
3319
|
if (!stopIfNeeded()) {
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
3320
|
+
let dispatchInput = buildCoordinatorPlanInput(options.intent, coordinator);
|
|
3321
|
+
let dispatchCount = 0;
|
|
3322
|
+
while (true) {
|
|
3323
|
+
const turnOutcome = await runCoordinatorTurn({
|
|
3324
|
+
agent: coordinator,
|
|
3325
|
+
coordinator,
|
|
3326
|
+
input: dispatchInput,
|
|
3327
|
+
phase: "plan",
|
|
3328
|
+
options,
|
|
3329
|
+
runId,
|
|
3330
|
+
transcript,
|
|
3331
|
+
totalCost,
|
|
3332
|
+
providerCalls,
|
|
3333
|
+
toolExecutor,
|
|
3334
|
+
toolAvailability,
|
|
3335
|
+
events,
|
|
3336
|
+
startedAtMs,
|
|
3337
|
+
wrapUpHint,
|
|
3338
|
+
emit,
|
|
3339
|
+
recordProtocolDecision
|
|
3340
|
+
});
|
|
3341
|
+
totalCost = turnOutcome.totalCost;
|
|
3342
|
+
if (turnOutcome.decision === void 0) break;
|
|
3343
|
+
const delegates = Array.isArray(turnOutcome.decision) ? turnOutcome.decision : turnOutcome.decision.type === "delegate" ? [turnOutcome.decision] : [];
|
|
3344
|
+
if (delegates.length === 0) break;
|
|
3345
|
+
if (dispatchCount + delegates.length > MAX_DISPATCH_PER_TURN) throw new DogpileError({
|
|
3346
|
+
code: "invalid-configuration",
|
|
3347
|
+
message: `Coordinator plan turn delegated ${delegates.length} more children after ${dispatchCount}; max is ${MAX_DISPATCH_PER_TURN}.`,
|
|
3348
|
+
retryable: false,
|
|
3349
|
+
detail: {
|
|
3350
|
+
kind: "delegate-validation",
|
|
3351
|
+
path: "decision",
|
|
3352
|
+
reason: "loop-guard-exceeded",
|
|
3353
|
+
maxDispatchPerTurn: MAX_DISPATCH_PER_TURN
|
|
3354
|
+
}
|
|
3355
|
+
});
|
|
3356
|
+
const parentDecisionId = String(events.length - 1);
|
|
3357
|
+
const parentDepth = options.currentDepth ?? 0;
|
|
3358
|
+
const decisionMax = delegates.reduce((max, delegate) => Math.min(max, delegate.maxConcurrentChildren ?? Number.POSITIVE_INFINITY), Number.POSITIVE_INFINITY);
|
|
3359
|
+
let effectiveForTurn = Math.min(options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN$1, decisionMax);
|
|
3360
|
+
const requestedMax = effectiveForTurn;
|
|
3361
|
+
const localProvider = findFirstLocalProvider(options);
|
|
3362
|
+
if (localProvider !== void 0) {
|
|
3363
|
+
effectiveForTurn = 1;
|
|
3364
|
+
if (!concurrencyClampEmitted) {
|
|
3365
|
+
const clampEvent = {
|
|
3366
|
+
type: "sub-run-concurrency-clamped",
|
|
3367
|
+
runId,
|
|
3368
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3369
|
+
requestedMax,
|
|
3370
|
+
effectiveMax: 1,
|
|
3371
|
+
reason: "local-provider-detected",
|
|
3372
|
+
providerId: localProvider.id
|
|
3373
|
+
};
|
|
3374
|
+
emit(clampEvent);
|
|
3375
|
+
recordProtocolDecision(clampEvent);
|
|
3376
|
+
concurrencyClampEmitted = true;
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
const semaphore = createSemaphore(effectiveForTurn);
|
|
3380
|
+
const childRunIds = delegates.map(() => createRunId());
|
|
3381
|
+
const dispatchedForTurn = delegates.map((delegate, index) => {
|
|
3382
|
+
const childRunId = childRunIds[index];
|
|
3383
|
+
if (childRunId === void 0) throw new Error("missing child run id");
|
|
3384
|
+
const dispatchedChild = {
|
|
3385
|
+
childRunId,
|
|
3386
|
+
decision: delegate,
|
|
3387
|
+
parentDecisionId,
|
|
3388
|
+
parentDecisionArrayIndex: index,
|
|
3389
|
+
parentDepth,
|
|
3390
|
+
controller: new AbortController(),
|
|
3391
|
+
removeParentListener: void 0,
|
|
3392
|
+
childEvents: [],
|
|
3393
|
+
started: false,
|
|
3394
|
+
closed: false,
|
|
3395
|
+
startedAtMs: Date.now(),
|
|
3396
|
+
childTimeoutMs: void 0,
|
|
3397
|
+
failure: void 0
|
|
3398
|
+
};
|
|
3399
|
+
dispatchedChildren.set(childRunId, dispatchedChild);
|
|
3400
|
+
return dispatchedChild;
|
|
3401
|
+
});
|
|
3402
|
+
const dispatchResults = [];
|
|
3403
|
+
let firstFailureIndex;
|
|
3404
|
+
const tasks = delegates.map(async (delegate, index) => {
|
|
3405
|
+
const childRunId = childRunIds[index];
|
|
3406
|
+
if (childRunId === void 0) throw new Error("missing child run id");
|
|
3407
|
+
if (semaphore.inFlight >= effectiveForTurn) {
|
|
3408
|
+
const queuedEvent = {
|
|
3409
|
+
type: "sub-run-queued",
|
|
3410
|
+
runId,
|
|
3411
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3412
|
+
childRunId,
|
|
3413
|
+
parentRunId: runId,
|
|
3414
|
+
parentDecisionId,
|
|
3415
|
+
parentDecisionArrayIndex: index,
|
|
3416
|
+
protocol: delegate.protocol,
|
|
3417
|
+
intent: delegate.intent,
|
|
3418
|
+
depth: parentDepth + 1,
|
|
3419
|
+
queuePosition: semaphore.queued
|
|
3420
|
+
};
|
|
3421
|
+
emit(queuedEvent);
|
|
3422
|
+
recordProtocolDecision(queuedEvent);
|
|
3423
|
+
}
|
|
3424
|
+
await semaphore.acquire();
|
|
3425
|
+
try {
|
|
3426
|
+
const dispatchedChild = dispatchedForTurn[index];
|
|
3427
|
+
if (!dispatchedChild) throw new Error("missing dispatched child");
|
|
3428
|
+
if (firstFailureIndex !== void 0) {
|
|
3429
|
+
if (dispatchedChild.closed) {
|
|
3430
|
+
dispatchResults.push({
|
|
3431
|
+
index,
|
|
3432
|
+
result: {
|
|
3433
|
+
nextInput: "",
|
|
3434
|
+
taggedText: `[sub-run ${childRunId}]: skipped because the parent run aborted`,
|
|
3435
|
+
completedAtMs: Date.now()
|
|
3436
|
+
}
|
|
3437
|
+
});
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
const partialCost = emptyCost();
|
|
3441
|
+
const partialTrace = buildPartialTrace({
|
|
3442
|
+
childRunId,
|
|
3443
|
+
events: [],
|
|
3444
|
+
startedAtMs: Date.now(),
|
|
3445
|
+
protocol: delegate.protocol,
|
|
3446
|
+
tier: options.tier,
|
|
3447
|
+
modelProviderId: options.model.id,
|
|
3448
|
+
agents: options.agents,
|
|
3449
|
+
intent: delegate.intent,
|
|
3450
|
+
temperature: options.temperature,
|
|
3451
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
3452
|
+
});
|
|
3453
|
+
const failedEvent = {
|
|
3454
|
+
type: "sub-run-failed",
|
|
3455
|
+
runId,
|
|
3456
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3457
|
+
childRunId,
|
|
3458
|
+
parentRunId: runId,
|
|
3459
|
+
parentDecisionId,
|
|
3460
|
+
parentDecisionArrayIndex: index,
|
|
3461
|
+
error: {
|
|
3462
|
+
code: "aborted",
|
|
3463
|
+
message: "Sibling delegate failed; queued delegate never started.",
|
|
3464
|
+
detail: { reason: "sibling-failed" }
|
|
3465
|
+
},
|
|
3466
|
+
partialTrace,
|
|
3467
|
+
partialCost
|
|
3468
|
+
};
|
|
3469
|
+
emit(failedEvent);
|
|
3470
|
+
recordProtocolDecision(failedEvent);
|
|
3471
|
+
dispatchedChild.closed = true;
|
|
3472
|
+
dispatchResults.push({
|
|
3473
|
+
index,
|
|
3474
|
+
result: {
|
|
3475
|
+
nextInput: "",
|
|
3476
|
+
taggedText: `[sub-run ${childRunId}]: skipped because a sibling delegate failed`,
|
|
3477
|
+
completedAtMs: Date.now()
|
|
3478
|
+
}
|
|
3479
|
+
});
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
const result = await dispatchDelegate({
|
|
3483
|
+
decision: delegate,
|
|
3484
|
+
childRunId,
|
|
3485
|
+
parentDecisionId,
|
|
3486
|
+
parentDecisionArrayIndex: index,
|
|
3487
|
+
parentDepth,
|
|
3488
|
+
parentRunId: runId,
|
|
3489
|
+
options,
|
|
3490
|
+
transcript,
|
|
3491
|
+
emit,
|
|
3492
|
+
recordProtocolDecision,
|
|
3493
|
+
recordSubRunCost: (cost) => {
|
|
3494
|
+
totalCost = addCost(totalCost, cost);
|
|
3495
|
+
},
|
|
3496
|
+
dispatchedChild
|
|
3497
|
+
});
|
|
3498
|
+
dispatchResults.push({
|
|
3499
|
+
index,
|
|
3500
|
+
result
|
|
3501
|
+
});
|
|
3502
|
+
} catch (error) {
|
|
3503
|
+
firstFailureIndex ??= index;
|
|
3504
|
+
const failure = dispatchedForTurn[index]?.failure;
|
|
3505
|
+
if (delegates.length === 1 && (options.onChildFailure === "abort" || failure === void 0 || isDelegateValidationError(error))) throw error;
|
|
3506
|
+
let taggedText = `[sub-run ${childRunId} failed]: ${error instanceof Error ? error.message : String(error)}`;
|
|
3507
|
+
if (failure) {
|
|
3508
|
+
const error = failure.error;
|
|
3509
|
+
taggedText = `[sub-run ${childRunId} failed | code=${error.code} | spent=$${failure.partialCost.usd.toFixed(3)}]: ${error.message}`;
|
|
3510
|
+
}
|
|
3511
|
+
dispatchResults.push({
|
|
3512
|
+
index,
|
|
3513
|
+
result: {
|
|
3514
|
+
nextInput: "",
|
|
3515
|
+
taggedText,
|
|
3516
|
+
completedAtMs: Date.now()
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
} finally {
|
|
3520
|
+
semaphore.release();
|
|
3521
|
+
}
|
|
3522
|
+
});
|
|
3523
|
+
const firstRejected = (await Promise.allSettled(tasks)).find((result) => result.status === "rejected");
|
|
3524
|
+
if (firstRejected?.status === "rejected" && delegates.length === 1 && (options.onChildFailure === "abort" || dispatchResults.length === 0)) throw firstRejected.reason;
|
|
3525
|
+
dispatchResults.sort((a, b) => a.result.completedAtMs - b.result.completedAtMs);
|
|
3526
|
+
const taggedResults = dispatchResults.map((entry) => entry.result.taggedText).join("\n\n");
|
|
3527
|
+
const currentWaveFailures = dispatchedForTurn.map((child) => child.failure).filter((failure) => failure !== void 0);
|
|
3528
|
+
if (options.onChildFailure === "abort" && currentWaveFailures.length > 0) {
|
|
3529
|
+
triggeringFailureForAbortMode ??= currentWaveFailures[0];
|
|
3530
|
+
break;
|
|
3531
|
+
}
|
|
3532
|
+
const failuresSection = buildFailuresSection(currentWaveFailures);
|
|
3533
|
+
const coordinatorAgent = options.agents[0] ?? {
|
|
3534
|
+
id: "coordinator",
|
|
3535
|
+
role: "coordinator"
|
|
3536
|
+
};
|
|
3537
|
+
dispatchInput = [
|
|
3538
|
+
buildCoordinatorPlanInput(options.intent, coordinatorAgent),
|
|
3539
|
+
taggedResults,
|
|
3540
|
+
failuresSection,
|
|
3541
|
+
"Using the sub-run results above, decide the next step (participate or delegate)."
|
|
3542
|
+
].filter((section) => Boolean(section)).join("\n\n");
|
|
3543
|
+
dispatchCount += delegates.length;
|
|
3544
|
+
}
|
|
2689
3545
|
stopIfNeeded();
|
|
2690
3546
|
}
|
|
2691
3547
|
if (!stopIfNeeded()) {
|
|
@@ -2699,7 +3555,7 @@ async function runCoordinator(options) {
|
|
|
2699
3555
|
options,
|
|
2700
3556
|
runId,
|
|
2701
3557
|
turn: transcript.length + index + 1,
|
|
2702
|
-
providerCallId: providerCallIdFor
|
|
3558
|
+
providerCallId: providerCallIdFor(runId, providerCalls.length + index + 1),
|
|
2703
3559
|
providerCallIndex: index,
|
|
2704
3560
|
providerCallSlots,
|
|
2705
3561
|
toolExecutor,
|
|
@@ -2743,7 +3599,7 @@ async function runCoordinator(options) {
|
|
|
2743
3599
|
stopIfNeeded();
|
|
2744
3600
|
}
|
|
2745
3601
|
if (!stopIfNeeded()) {
|
|
2746
|
-
|
|
3602
|
+
const synthesisOutcome = await runCoordinatorTurn({
|
|
2747
3603
|
agent: coordinator,
|
|
2748
3604
|
coordinator,
|
|
2749
3605
|
input: buildFinalSynthesisInput(options.intent, transcript, coordinator),
|
|
@@ -2761,6 +3617,17 @@ async function runCoordinator(options) {
|
|
|
2761
3617
|
emit,
|
|
2762
3618
|
recordProtocolDecision
|
|
2763
3619
|
});
|
|
3620
|
+
totalCost = synthesisOutcome.totalCost;
|
|
3621
|
+
if (Array.isArray(synthesisOutcome.decision) || synthesisOutcome.decision?.type === "delegate") throw new DogpileError({
|
|
3622
|
+
code: "invalid-configuration",
|
|
3623
|
+
message: "Coordinator final-synthesis turn cannot emit a delegate decision in Phase 1",
|
|
3624
|
+
retryable: false,
|
|
3625
|
+
detail: {
|
|
3626
|
+
kind: "delegate-validation",
|
|
3627
|
+
path: "decision",
|
|
3628
|
+
phase: "final-synthesis"
|
|
3629
|
+
}
|
|
3630
|
+
});
|
|
2764
3631
|
stopIfNeeded();
|
|
2765
3632
|
}
|
|
2766
3633
|
}
|
|
@@ -2813,6 +3680,7 @@ async function runCoordinator(options) {
|
|
|
2813
3680
|
cost: totalCost,
|
|
2814
3681
|
transcript: createTranscriptLink(transcript)
|
|
2815
3682
|
}),
|
|
3683
|
+
...triggeringFailureForAbortMode !== void 0 ? { triggeringFailureForAbortMode } : {},
|
|
2816
3684
|
events,
|
|
2817
3685
|
transcript
|
|
2818
3686
|
},
|
|
@@ -2847,7 +3715,7 @@ async function runCoordinator(options) {
|
|
|
2847
3715
|
events,
|
|
2848
3716
|
transcript,
|
|
2849
3717
|
iteration: transcript.length,
|
|
2850
|
-
elapsedMs: elapsedMs
|
|
3718
|
+
elapsedMs: elapsedMs(startedAtMs)
|
|
2851
3719
|
}));
|
|
2852
3720
|
if (!stopRecord) return false;
|
|
2853
3721
|
stopped = true;
|
|
@@ -2863,13 +3731,16 @@ async function runCoordinator(options) {
|
|
|
2863
3731
|
reason: record.budgetReason ?? "cost",
|
|
2864
3732
|
cost: totalCost,
|
|
2865
3733
|
iteration: transcript.length,
|
|
2866
|
-
elapsedMs: elapsedMs
|
|
3734
|
+
elapsedMs: elapsedMs(startedAtMs),
|
|
2867
3735
|
detail: record.detail ?? {}
|
|
2868
3736
|
};
|
|
2869
3737
|
emit(event);
|
|
2870
3738
|
recordProtocolDecision(event, { transcriptEntryCount: transcript.length });
|
|
2871
3739
|
}
|
|
2872
3740
|
}
|
|
3741
|
+
function isDelegateValidationError(error) {
|
|
3742
|
+
return DogpileError.isInstance(error) && error.code === "invalid-configuration" && error.detail?.["kind"] === "delegate-validation";
|
|
3743
|
+
}
|
|
2873
3744
|
async function runCoordinatorTurn(turn) {
|
|
2874
3745
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
2875
3746
|
const request = {
|
|
@@ -2898,7 +3769,7 @@ async function runCoordinatorTurn(turn) {
|
|
|
2898
3769
|
events: turn.events,
|
|
2899
3770
|
transcript: turn.transcript,
|
|
2900
3771
|
iteration: turn.transcript.length,
|
|
2901
|
-
elapsedMs: elapsedMs
|
|
3772
|
+
elapsedMs: elapsedMs(turn.startedAtMs)
|
|
2902
3773
|
})
|
|
2903
3774
|
};
|
|
2904
3775
|
const response = await generateModelTurn({
|
|
@@ -2913,7 +3784,11 @@ async function runCoordinatorTurn(turn) {
|
|
|
2913
3784
|
turn.providerCalls.push(call);
|
|
2914
3785
|
}
|
|
2915
3786
|
});
|
|
2916
|
-
const decision = parseAgentDecision(response.text
|
|
3787
|
+
const decision = parseAgentDecision(response.text, {
|
|
3788
|
+
parentProviderId: turn.options.model.id,
|
|
3789
|
+
currentDepth: turn.options.currentDepth ?? 0,
|
|
3790
|
+
maxDepth: turn.options.effectiveMaxDepth ?? Number.POSITIVE_INFINITY
|
|
3791
|
+
});
|
|
2917
3792
|
const totalCost = addCost(turn.totalCost, responseCost$2(response));
|
|
2918
3793
|
const toolCalls = await executeModelResponseToolRequests({
|
|
2919
3794
|
response,
|
|
@@ -2949,7 +3824,10 @@ async function runCoordinatorTurn(turn) {
|
|
|
2949
3824
|
phase: turn.phase,
|
|
2950
3825
|
transcriptEntryCount: turn.transcript.length
|
|
2951
3826
|
});
|
|
2952
|
-
return
|
|
3827
|
+
return {
|
|
3828
|
+
totalCost,
|
|
3829
|
+
decision
|
|
3830
|
+
};
|
|
2953
3831
|
}
|
|
2954
3832
|
async function runCoordinatorWorkerTurn(turn) {
|
|
2955
3833
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
@@ -2979,7 +3857,7 @@ async function runCoordinatorWorkerTurn(turn) {
|
|
|
2979
3857
|
events: turn.events,
|
|
2980
3858
|
transcript: turn.transcript,
|
|
2981
3859
|
iteration: turn.turn - 1,
|
|
2982
|
-
elapsedMs: elapsedMs
|
|
3860
|
+
elapsedMs: elapsedMs(turn.startedAtMs)
|
|
2983
3861
|
})
|
|
2984
3862
|
};
|
|
2985
3863
|
const response = await generateModelTurn({
|
|
@@ -2994,7 +3872,21 @@ async function runCoordinatorWorkerTurn(turn) {
|
|
|
2994
3872
|
turn.providerCallSlots[turn.providerCallIndex] = call;
|
|
2995
3873
|
}
|
|
2996
3874
|
});
|
|
2997
|
-
const decision = parseAgentDecision(response.text
|
|
3875
|
+
const decision = parseAgentDecision(response.text, {
|
|
3876
|
+
parentProviderId: turn.options.model.id,
|
|
3877
|
+
currentDepth: turn.options.currentDepth ?? 0,
|
|
3878
|
+
maxDepth: turn.options.effectiveMaxDepth ?? Number.POSITIVE_INFINITY
|
|
3879
|
+
});
|
|
3880
|
+
if (Array.isArray(decision) || decision?.type === "delegate") throw new DogpileError({
|
|
3881
|
+
code: "invalid-configuration",
|
|
3882
|
+
message: "Workers cannot emit delegate decisions in Phase 1",
|
|
3883
|
+
retryable: false,
|
|
3884
|
+
detail: {
|
|
3885
|
+
kind: "delegate-validation",
|
|
3886
|
+
path: "decision",
|
|
3887
|
+
phase: "worker"
|
|
3888
|
+
}
|
|
3889
|
+
});
|
|
2998
3890
|
const toolCalls = await executeModelResponseToolRequests({
|
|
2999
3891
|
response,
|
|
3000
3892
|
executor: turn.toolExecutor,
|
|
@@ -3021,6 +3913,30 @@ function buildSystemPrompt$2(agent, coordinator) {
|
|
|
3021
3913
|
function buildCoordinatorPlanInput(intent, coordinator) {
|
|
3022
3914
|
return `Mission: ${intent}\nCoordinator ${coordinator.id}: assign the work, name the plan, and provide the first contribution.`;
|
|
3023
3915
|
}
|
|
3916
|
+
function buildFailuresSection(failures) {
|
|
3917
|
+
if (failures.length === 0) return null;
|
|
3918
|
+
return [
|
|
3919
|
+
"## Sub-run failures since last decision",
|
|
3920
|
+
"",
|
|
3921
|
+
"```json",
|
|
3922
|
+
JSON.stringify(failures, null, 2),
|
|
3923
|
+
"```"
|
|
3924
|
+
].join("\n");
|
|
3925
|
+
}
|
|
3926
|
+
function dispatchWaveFailureFromEvent(intent, event) {
|
|
3927
|
+
const reason = typeof event.error.detail?.["reason"] === "string" ? event.error.detail["reason"] : void 0;
|
|
3928
|
+
if (reason === "sibling-failed" || reason === "parent-aborted") return;
|
|
3929
|
+
return {
|
|
3930
|
+
childRunId: event.childRunId,
|
|
3931
|
+
intent,
|
|
3932
|
+
error: {
|
|
3933
|
+
code: event.error.code,
|
|
3934
|
+
message: event.error.message,
|
|
3935
|
+
...reason !== void 0 ? { detail: { reason } } : {}
|
|
3936
|
+
},
|
|
3937
|
+
partialCost: { usd: event.partialCost.usd }
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3024
3940
|
function buildWorkerInput(intent, transcript, coordinator) {
|
|
3025
3941
|
const prior = transcript.map((entry) => `${entry.role} (${entry.agentId}): ${entry.output}`).join("\n\n");
|
|
3026
3942
|
return `Mission: ${intent}\n\nCoordinator: ${coordinator.id}\nPrior contributions:\n${prior}\n\nFollow the coordinator-managed plan and provide your assigned contribution.`;
|
|
@@ -3037,22 +3953,386 @@ function responseCost$2(response) {
|
|
|
3037
3953
|
totalTokens: response.usage?.totalTokens ?? 0
|
|
3038
3954
|
};
|
|
3039
3955
|
}
|
|
3040
|
-
|
|
3041
|
-
|
|
3956
|
+
/**
|
|
3957
|
+
* Dispatch a single delegate decision as a recursive sub-run.
|
|
3958
|
+
*
|
|
3959
|
+
* D-11: child reuses the parent provider object verbatim.
|
|
3960
|
+
* D-16: `recursive: true` flag set when both parent and child protocol are
|
|
3961
|
+
* `coordinator`.
|
|
3962
|
+
* D-17: tagged result text appended to the next coordinator prompt.
|
|
3963
|
+
* D-18: synthetic transcript entry pushed for replay/provenance.
|
|
3964
|
+
*
|
|
3965
|
+
* On thrown error from the child engine, builds `partialTrace` from a locally
|
|
3966
|
+
* tee'd `childEvents` buffer — `runProtocol`'s error contract is unchanged.
|
|
3967
|
+
*/
|
|
3968
|
+
async function dispatchDelegate(input) {
|
|
3969
|
+
const { decision, options } = input;
|
|
3970
|
+
if (options.effectiveMaxDepth !== void 0) assertDepthWithinLimit(input.parentDepth, options.effectiveMaxDepth);
|
|
3971
|
+
const childRunId = input.childRunId ?? createRunId();
|
|
3972
|
+
const recursive = decision.protocol === "coordinator";
|
|
3973
|
+
const decisionTimeoutMs = decision.budget?.timeoutMs;
|
|
3974
|
+
const parentDeadlineMs = options.parentDeadlineMs;
|
|
3975
|
+
const remainingMs = parentDeadlineMs !== void 0 ? Math.max(0, parentDeadlineMs - Date.now()) : void 0;
|
|
3976
|
+
if (parentDeadlineMs !== void 0 && remainingMs === 0) throw new DogpileError({
|
|
3977
|
+
code: "aborted",
|
|
3978
|
+
message: "Parent deadline elapsed before sub-run dispatch.",
|
|
3979
|
+
retryable: false,
|
|
3980
|
+
providerId: options.model.id,
|
|
3981
|
+
detail: { reason: "timeout" }
|
|
3982
|
+
});
|
|
3983
|
+
let childTimeoutMs;
|
|
3984
|
+
let clampedFrom;
|
|
3985
|
+
if (remainingMs !== void 0) if (decisionTimeoutMs !== void 0) if (decisionTimeoutMs > remainingMs) {
|
|
3986
|
+
clampedFrom = decisionTimeoutMs;
|
|
3987
|
+
childTimeoutMs = remainingMs;
|
|
3988
|
+
} else childTimeoutMs = decisionTimeoutMs;
|
|
3989
|
+
else childTimeoutMs = remainingMs;
|
|
3990
|
+
else if (decisionTimeoutMs !== void 0) childTimeoutMs = decisionTimeoutMs;
|
|
3991
|
+
else if (options.defaultSubRunTimeoutMs !== void 0) childTimeoutMs = options.defaultSubRunTimeoutMs;
|
|
3992
|
+
if (!options.runProtocol) throw new DogpileError({
|
|
3993
|
+
code: "invalid-configuration",
|
|
3994
|
+
message: "Coordinator delegate dispatch requires the engine `runProtocol` callback. Use `Dogpile.run` / `createEngine` rather than calling `runCoordinator` directly when delegate is in play.",
|
|
3995
|
+
retryable: false,
|
|
3996
|
+
detail: {
|
|
3997
|
+
kind: "delegate-validation",
|
|
3998
|
+
path: "runProtocol"
|
|
3999
|
+
}
|
|
4000
|
+
});
|
|
4001
|
+
const childEvents = input.dispatchedChild.childEvents;
|
|
4002
|
+
const parentEmit = input.emit;
|
|
4003
|
+
const teedEmit = (event) => {
|
|
4004
|
+
childEvents.push(event);
|
|
4005
|
+
if (input.dispatchedChild.closed) return;
|
|
4006
|
+
if (options.streamEvents && options.emit) {
|
|
4007
|
+
const inbound = event.parentRunIds;
|
|
4008
|
+
options.emit({
|
|
4009
|
+
...event,
|
|
4010
|
+
parentRunIds: [input.parentRunId, ...inbound ?? []]
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
const childStartedAt = Date.now();
|
|
4015
|
+
input.dispatchedChild.startedAtMs = childStartedAt;
|
|
4016
|
+
if (clampedFrom !== void 0 && childTimeoutMs !== void 0) {
|
|
4017
|
+
const clampEvent = {
|
|
4018
|
+
type: "sub-run-budget-clamped",
|
|
4019
|
+
runId: input.parentRunId,
|
|
4020
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4021
|
+
childRunId,
|
|
4022
|
+
parentRunId: input.parentRunId,
|
|
4023
|
+
parentDecisionId: input.parentDecisionId,
|
|
4024
|
+
requestedTimeoutMs: clampedFrom,
|
|
4025
|
+
clampedTimeoutMs: childTimeoutMs,
|
|
4026
|
+
reason: "exceeded-parent-remaining"
|
|
4027
|
+
};
|
|
4028
|
+
input.emit(clampEvent);
|
|
4029
|
+
input.recordProtocolDecision(clampEvent);
|
|
4030
|
+
}
|
|
4031
|
+
const startEvent = {
|
|
4032
|
+
type: "sub-run-started",
|
|
4033
|
+
runId: input.parentRunId,
|
|
4034
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4035
|
+
childRunId,
|
|
4036
|
+
parentRunId: input.parentRunId,
|
|
4037
|
+
parentDecisionId: input.parentDecisionId,
|
|
4038
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4039
|
+
protocol: decision.protocol,
|
|
4040
|
+
intent: decision.intent,
|
|
4041
|
+
depth: input.parentDepth + 1,
|
|
4042
|
+
...recursive ? { recursive: true } : {}
|
|
4043
|
+
};
|
|
4044
|
+
parentEmit(startEvent);
|
|
4045
|
+
input.recordProtocolDecision(startEvent);
|
|
4046
|
+
const parentSignal = options.signal;
|
|
4047
|
+
let removeParentAbortListener;
|
|
4048
|
+
if (parentSignal !== void 0) if (parentSignal.aborted) input.dispatchedChild.controller.abort(parentSignal.reason);
|
|
4049
|
+
else {
|
|
4050
|
+
const handler = () => {
|
|
4051
|
+
input.dispatchedChild.controller.abort(parentSignal.reason);
|
|
4052
|
+
};
|
|
4053
|
+
parentSignal.addEventListener("abort", handler, { once: true });
|
|
4054
|
+
removeParentAbortListener = () => {
|
|
4055
|
+
parentSignal.removeEventListener("abort", handler);
|
|
4056
|
+
};
|
|
4057
|
+
}
|
|
4058
|
+
input.dispatchedChild.removeParentListener = removeParentAbortListener;
|
|
4059
|
+
input.dispatchedChild.started = true;
|
|
4060
|
+
input.dispatchedChild.childTimeoutMs = childTimeoutMs;
|
|
4061
|
+
const childDeadlineReason = childTimeoutMs !== void 0 && parentDeadlineMs === void 0 ? createEngineDeadlineTimeoutError(options.model.id, childTimeoutMs) : void 0;
|
|
4062
|
+
const childDeadlineTimer = childDeadlineReason !== void 0 ? setTimeout(() => {
|
|
4063
|
+
input.dispatchedChild.controller.abort(childDeadlineReason);
|
|
4064
|
+
}, childTimeoutMs) : void 0;
|
|
4065
|
+
const childOptions = {
|
|
4066
|
+
intent: decision.intent,
|
|
4067
|
+
protocol: decision.protocol,
|
|
4068
|
+
tier: options.tier,
|
|
4069
|
+
model: options.model,
|
|
4070
|
+
agents: options.agents,
|
|
4071
|
+
tools: options.tools,
|
|
4072
|
+
temperature: options.temperature,
|
|
4073
|
+
...childTimeoutMs !== void 0 ? { budget: { timeoutMs: childTimeoutMs } } : {},
|
|
4074
|
+
signal: input.dispatchedChild.controller.signal,
|
|
4075
|
+
emit: teedEmit,
|
|
4076
|
+
...options.streamEvents !== void 0 ? { streamEvents: options.streamEvents } : {},
|
|
4077
|
+
currentDepth: input.parentDepth + 1,
|
|
4078
|
+
...options.effectiveMaxDepth !== void 0 ? { effectiveMaxDepth: options.effectiveMaxDepth } : {},
|
|
4079
|
+
...options.effectiveMaxConcurrentChildren !== void 0 ? { effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren } : {},
|
|
4080
|
+
...options.onChildFailure !== void 0 ? { onChildFailure: options.onChildFailure } : {},
|
|
4081
|
+
...parentDeadlineMs !== void 0 ? { parentDeadlineMs } : {},
|
|
4082
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
4083
|
+
};
|
|
4084
|
+
let subResult;
|
|
4085
|
+
try {
|
|
4086
|
+
subResult = await options.runProtocol(childOptions);
|
|
4087
|
+
} catch (error) {
|
|
4088
|
+
if (childDeadlineTimer !== void 0) clearTimeout(childDeadlineTimer);
|
|
4089
|
+
removeParentAbortListener?.();
|
|
4090
|
+
if (input.dispatchedChild.closed) {
|
|
4091
|
+
const enrichedError = enrichAbortErrorWithParentReason(error, parentSignal);
|
|
4092
|
+
if (DogpileError.isInstance(enrichedError)) throw enrichedError;
|
|
4093
|
+
throw error;
|
|
4094
|
+
}
|
|
4095
|
+
const failedDecision = {
|
|
4096
|
+
type: "delegate",
|
|
4097
|
+
protocol: decision.protocol,
|
|
4098
|
+
intent: decision.intent,
|
|
4099
|
+
...decision.model !== void 0 ? { model: decision.model } : {},
|
|
4100
|
+
...decision.budget !== void 0 ? { budget: decision.budget } : {}
|
|
4101
|
+
};
|
|
4102
|
+
const partialTrace = buildPartialTrace({
|
|
4103
|
+
childRunId,
|
|
4104
|
+
events: childEvents,
|
|
4105
|
+
startedAtMs: childStartedAt,
|
|
4106
|
+
protocol: decision.protocol,
|
|
4107
|
+
tier: options.tier,
|
|
4108
|
+
modelProviderId: options.model.id,
|
|
4109
|
+
agents: options.agents,
|
|
4110
|
+
intent: decision.intent,
|
|
4111
|
+
temperature: options.temperature,
|
|
4112
|
+
...childTimeoutMs !== void 0 ? { childTimeoutMs } : {},
|
|
4113
|
+
...options.seed !== void 0 ? { seed: options.seed } : {}
|
|
4114
|
+
});
|
|
4115
|
+
const enrichedError = enrichProviderTimeoutSource(enrichAbortErrorWithParentReason(error, parentSignal), {
|
|
4116
|
+
...decisionTimeoutMs !== void 0 ? { decisionTimeoutMs } : {},
|
|
4117
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { engineDefaultTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
4118
|
+
});
|
|
4119
|
+
if (DogpileError.isInstance(enrichedError)) options.failureInstancesByChildRunId?.set(childRunId, enrichedError);
|
|
4120
|
+
const errorPayload = errorPayloadFromUnknown(enrichedError, failedDecision);
|
|
4121
|
+
const partialCost = lastCostBearingEventCost(childEvents) ?? emptyCost();
|
|
4122
|
+
input.recordSubRunCost(partialCost);
|
|
4123
|
+
const failEvent = {
|
|
4124
|
+
type: "sub-run-failed",
|
|
4125
|
+
runId: input.parentRunId,
|
|
4126
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4127
|
+
childRunId,
|
|
4128
|
+
parentRunId: input.parentRunId,
|
|
4129
|
+
parentDecisionId: input.parentDecisionId,
|
|
4130
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4131
|
+
error: errorPayload,
|
|
4132
|
+
partialTrace,
|
|
4133
|
+
partialCost
|
|
4134
|
+
};
|
|
4135
|
+
parentEmit(failEvent);
|
|
4136
|
+
input.recordProtocolDecision(failEvent);
|
|
4137
|
+
input.dispatchedChild.closed = true;
|
|
4138
|
+
input.dispatchedChild.failure = dispatchWaveFailureFromEvent(decision.intent, failEvent);
|
|
4139
|
+
if (DogpileError.isInstance(enrichedError)) throw enrichedError;
|
|
4140
|
+
throw new DogpileError({
|
|
4141
|
+
code: "invalid-configuration",
|
|
4142
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4143
|
+
retryable: false,
|
|
4144
|
+
detail: {
|
|
4145
|
+
kind: "delegate-validation",
|
|
4146
|
+
path: "decision",
|
|
4147
|
+
reason: "child-run-failed"
|
|
4148
|
+
}
|
|
4149
|
+
});
|
|
4150
|
+
}
|
|
4151
|
+
if (childDeadlineTimer !== void 0) clearTimeout(childDeadlineTimer);
|
|
4152
|
+
removeParentAbortListener?.();
|
|
4153
|
+
input.recordSubRunCost(subResult.cost);
|
|
4154
|
+
const completedEvent = {
|
|
4155
|
+
type: "sub-run-completed",
|
|
4156
|
+
runId: input.parentRunId,
|
|
4157
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4158
|
+
childRunId,
|
|
4159
|
+
parentRunId: input.parentRunId,
|
|
4160
|
+
parentDecisionId: input.parentDecisionId,
|
|
4161
|
+
parentDecisionArrayIndex: input.parentDecisionArrayIndex,
|
|
4162
|
+
subResult
|
|
4163
|
+
};
|
|
4164
|
+
parentEmit(completedEvent);
|
|
4165
|
+
input.recordProtocolDecision(completedEvent);
|
|
4166
|
+
input.dispatchedChild.closed = true;
|
|
4167
|
+
if (parentSignal?.aborted) {
|
|
4168
|
+
const abortMarker = {
|
|
4169
|
+
type: "sub-run-parent-aborted",
|
|
4170
|
+
runId: input.parentRunId,
|
|
4171
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4172
|
+
childRunId,
|
|
4173
|
+
parentRunId: input.parentRunId,
|
|
4174
|
+
reason: "parent-aborted"
|
|
4175
|
+
};
|
|
4176
|
+
parentEmit(abortMarker);
|
|
4177
|
+
input.recordProtocolDecision(abortMarker);
|
|
4178
|
+
throw enrichAbortErrorWithParentReason(createAbortErrorFromSignal(parentSignal, options.model.id), parentSignal);
|
|
4179
|
+
}
|
|
4180
|
+
const decisionAsJson = {
|
|
4181
|
+
type: "delegate",
|
|
4182
|
+
protocol: decision.protocol,
|
|
4183
|
+
intent: decision.intent,
|
|
4184
|
+
...decision.model !== void 0 ? { model: decision.model } : {},
|
|
4185
|
+
...decision.budget !== void 0 ? { budget: decision.budget } : {}
|
|
4186
|
+
};
|
|
4187
|
+
const taggedText = renderSubRunResult(childRunId, subResult);
|
|
4188
|
+
input.transcript.push({
|
|
4189
|
+
agentId: `sub-run:${childRunId}`,
|
|
4190
|
+
role: "delegate-result",
|
|
4191
|
+
input: JSON.stringify(decisionAsJson),
|
|
4192
|
+
output: taggedText
|
|
4193
|
+
});
|
|
4194
|
+
const coordinatorAgent = options.agents[0];
|
|
4195
|
+
return {
|
|
4196
|
+
nextInput: `${buildCoordinatorPlanInput(input.options.intent, coordinatorAgent ?? {
|
|
4197
|
+
id: "coordinator",
|
|
4198
|
+
role: "coordinator"
|
|
4199
|
+
})}\n\n${taggedText}\n\nUsing the sub-run result above, decide the next step (participate or delegate).`,
|
|
4200
|
+
taggedText,
|
|
4201
|
+
completedAtMs: Date.now()
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
/**
|
|
4205
|
+
* D-17 prompt-injection helper. Renders a child `RunResult` as the canonical
|
|
4206
|
+
* tagged-result block injected into the parent coordinator's next prompt.
|
|
4207
|
+
*
|
|
4208
|
+
* Format:
|
|
4209
|
+
* `[sub-run <childRunId>]: <output>`
|
|
4210
|
+
* `[sub-run <childRunId> stats]: turns=<N> costUsd=<X> durationMs=<Y>`
|
|
4211
|
+
*
|
|
4212
|
+
* The stats line is a soft contract — field names stable, ordering stable.
|
|
4213
|
+
*/
|
|
4214
|
+
function renderSubRunResult(childRunId, subResult) {
|
|
4215
|
+
const turns = subResult.transcript.length;
|
|
4216
|
+
const costUsd = subResult.cost.usd ?? 0;
|
|
4217
|
+
const startedAt = subResult.trace.events[0]?.at;
|
|
4218
|
+
const endedAt = subResult.trace.events.at(-1)?.at;
|
|
4219
|
+
const durationMs = startedAt && endedAt ? Math.max(0, Date.parse(endedAt) - Date.parse(startedAt)) : 0;
|
|
4220
|
+
return [`[sub-run ${childRunId}]: ${subResult.output}`, `[sub-run ${childRunId} stats]: turns=${turns} costUsd=${costUsd} durationMs=${durationMs}`].join("\n");
|
|
3042
4221
|
}
|
|
3043
|
-
|
|
3044
|
-
|
|
4222
|
+
/**
|
|
4223
|
+
* Build a JSON-serializable {@link Trace} for `sub-run-failed.partialTrace`
|
|
4224
|
+
* from a buffered tee of child emits. Keeps `runProtocol`'s error contract
|
|
4225
|
+
* unchanged — Plan 03 step 8.
|
|
4226
|
+
*/
|
|
4227
|
+
function buildPartialTrace(input) {
|
|
4228
|
+
const protocolName = typeof input.protocol === "string" ? input.protocol : input.protocol.kind;
|
|
4229
|
+
const protocolConfig = typeof input.protocol === "string" ? { kind: input.protocol } : input.protocol;
|
|
4230
|
+
return {
|
|
4231
|
+
schemaVersion: "1.0",
|
|
4232
|
+
runId: input.childRunId,
|
|
4233
|
+
protocol: protocolName,
|
|
4234
|
+
tier: input.tier,
|
|
4235
|
+
modelProviderId: input.modelProviderId,
|
|
4236
|
+
agentsUsed: input.agents,
|
|
4237
|
+
inputs: createReplayTraceRunInputs({
|
|
4238
|
+
intent: input.intent,
|
|
4239
|
+
protocol: protocolConfig,
|
|
4240
|
+
tier: input.tier,
|
|
4241
|
+
modelProviderId: input.modelProviderId,
|
|
4242
|
+
agents: input.agents,
|
|
4243
|
+
temperature: input.temperature
|
|
4244
|
+
}),
|
|
4245
|
+
budget: createReplayTraceBudget({
|
|
4246
|
+
tier: input.tier,
|
|
4247
|
+
...input.childTimeoutMs !== void 0 ? { caps: { timeoutMs: input.childTimeoutMs } } : {}
|
|
4248
|
+
}),
|
|
4249
|
+
budgetStateChanges: createReplayTraceBudgetStateChanges(input.events),
|
|
4250
|
+
seed: createReplayTraceSeed(input.seed),
|
|
4251
|
+
protocolDecisions: [],
|
|
4252
|
+
providerCalls: [],
|
|
4253
|
+
finalOutput: {
|
|
4254
|
+
kind: "replay-trace-final-output",
|
|
4255
|
+
output: "",
|
|
4256
|
+
cost: emptyCost(),
|
|
4257
|
+
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4258
|
+
transcript: createTranscriptLink([])
|
|
4259
|
+
},
|
|
4260
|
+
events: input.events,
|
|
4261
|
+
transcript: []
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
/**
|
|
4265
|
+
* BUDGET-01 / D-08: when a child sub-run threw because the parent's signal
|
|
4266
|
+
* aborted, lock the `detail.reason` discriminator on the resulting
|
|
4267
|
+
* `code: "aborted"` error. Preserves any pre-existing detail keys (e.g.,
|
|
4268
|
+
* `detail.status: "cancelled"` attached by `createStreamCancellationError`).
|
|
4269
|
+
*
|
|
4270
|
+
* No-op when:
|
|
4271
|
+
* - parent.signal is undefined or not aborted (child failure was unrelated)
|
|
4272
|
+
* - error is not a DogpileError with `code: "aborted"`
|
|
4273
|
+
* - error already has a `detail.reason` set (preserve upstream classification)
|
|
4274
|
+
*/
|
|
4275
|
+
function enrichAbortErrorWithParentReason(error, parentSignal) {
|
|
4276
|
+
if (parentSignal === void 0 || !parentSignal.aborted) return error;
|
|
4277
|
+
if (!DogpileError.isInstance(error) || error.code !== "aborted") return error;
|
|
4278
|
+
const existingDetail = error.detail ?? {};
|
|
4279
|
+
if (existingDetail["reason"] !== void 0) return error;
|
|
4280
|
+
const reason = classifyAbortReason(parentSignal.reason);
|
|
4281
|
+
return new DogpileError({
|
|
4282
|
+
code: "aborted",
|
|
4283
|
+
message: error.message,
|
|
4284
|
+
retryable: error.retryable ?? false,
|
|
4285
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4286
|
+
detail: {
|
|
4287
|
+
...existingDetail,
|
|
4288
|
+
reason
|
|
4289
|
+
},
|
|
4290
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
4291
|
+
});
|
|
3045
4292
|
}
|
|
3046
|
-
function
|
|
3047
|
-
|
|
4293
|
+
function enrichProviderTimeoutSource(error, context) {
|
|
4294
|
+
if (!DogpileError.isInstance(error) || error.code !== "provider-timeout") return error;
|
|
4295
|
+
const existingDetail = error.detail ?? {};
|
|
4296
|
+
if (existingDetail["source"] !== void 0) return error;
|
|
4297
|
+
const source = classifyChildTimeoutSource(error, {
|
|
4298
|
+
...context,
|
|
4299
|
+
isProviderError: true
|
|
4300
|
+
});
|
|
4301
|
+
return new DogpileError({
|
|
4302
|
+
code: "provider-timeout",
|
|
4303
|
+
message: error.message,
|
|
4304
|
+
retryable: error.retryable ?? true,
|
|
4305
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4306
|
+
detail: {
|
|
4307
|
+
...existingDetail,
|
|
4308
|
+
source
|
|
4309
|
+
},
|
|
4310
|
+
...error.cause !== void 0 ? { cause: error.cause } : {}
|
|
4311
|
+
});
|
|
3048
4312
|
}
|
|
3049
|
-
function
|
|
3050
|
-
|
|
4313
|
+
function errorPayloadFromUnknown(error, failedDecision) {
|
|
4314
|
+
if (DogpileError.isInstance(error)) {
|
|
4315
|
+
const detail = {
|
|
4316
|
+
...error.detail ?? {},
|
|
4317
|
+
failedDecision
|
|
4318
|
+
};
|
|
4319
|
+
return {
|
|
4320
|
+
code: error.code,
|
|
4321
|
+
message: error.message,
|
|
4322
|
+
...error.providerId !== void 0 ? { providerId: error.providerId } : {},
|
|
4323
|
+
detail
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
4326
|
+
return {
|
|
4327
|
+
code: "invalid-configuration",
|
|
4328
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4329
|
+
detail: { failedDecision }
|
|
4330
|
+
};
|
|
3051
4331
|
}
|
|
3052
4332
|
//#endregion
|
|
3053
4333
|
//#region src/runtime/sequential.ts
|
|
3054
4334
|
async function runSequential(options) {
|
|
3055
|
-
const runId = createRunId
|
|
4335
|
+
const runId = createRunId();
|
|
3056
4336
|
const events = [];
|
|
3057
4337
|
const transcript = [];
|
|
3058
4338
|
const protocolDecisions = [];
|
|
@@ -3060,7 +4340,7 @@ async function runSequential(options) {
|
|
|
3060
4340
|
let totalCost = emptyCost();
|
|
3061
4341
|
const maxTurns = options.protocol.maxTurns ?? options.agents.length;
|
|
3062
4342
|
const activeAgents = options.agents.slice(0, maxTurns);
|
|
3063
|
-
const startedAtMs = nowMs
|
|
4343
|
+
const startedAtMs = nowMs();
|
|
3064
4344
|
let stopped = false;
|
|
3065
4345
|
let termination;
|
|
3066
4346
|
const wrapUpHint = createWrapUpHintController({
|
|
@@ -3135,7 +4415,7 @@ async function runSequential(options) {
|
|
|
3135
4415
|
events,
|
|
3136
4416
|
transcript,
|
|
3137
4417
|
iteration: transcript.length,
|
|
3138
|
-
elapsedMs: elapsedMs
|
|
4418
|
+
elapsedMs: elapsedMs(startedAtMs)
|
|
3139
4419
|
})
|
|
3140
4420
|
};
|
|
3141
4421
|
const response = await generateModelTurn({
|
|
@@ -3187,7 +4467,8 @@ async function runSequential(options) {
|
|
|
3187
4467
|
});
|
|
3188
4468
|
if (stopIfNeeded()) break;
|
|
3189
4469
|
}
|
|
3190
|
-
const
|
|
4470
|
+
const reversed = [...transcript].reverse();
|
|
4471
|
+
const output = reversed.find((entry) => isParticipatingDecision(entry.decision))?.output ?? reversed.find((entry) => entry.decision === void 0)?.output ?? "";
|
|
3191
4472
|
throwIfAborted(options.signal, options.model.id);
|
|
3192
4473
|
const final = {
|
|
3193
4474
|
type: "final",
|
|
@@ -3270,7 +4551,7 @@ async function runSequential(options) {
|
|
|
3270
4551
|
events,
|
|
3271
4552
|
transcript,
|
|
3272
4553
|
iteration: transcript.length,
|
|
3273
|
-
elapsedMs: elapsedMs
|
|
4554
|
+
elapsedMs: elapsedMs(startedAtMs)
|
|
3274
4555
|
}));
|
|
3275
4556
|
if (!stopRecord) return false;
|
|
3276
4557
|
stopped = true;
|
|
@@ -3286,7 +4567,7 @@ async function runSequential(options) {
|
|
|
3286
4567
|
reason: record.budgetReason ?? "cost",
|
|
3287
4568
|
cost: totalCost,
|
|
3288
4569
|
iteration: transcript.length,
|
|
3289
|
-
elapsedMs: elapsedMs
|
|
4570
|
+
elapsedMs: elapsedMs(startedAtMs),
|
|
3290
4571
|
detail: record.detail ?? {}
|
|
3291
4572
|
};
|
|
3292
4573
|
emit(event);
|
|
@@ -3309,15 +4590,6 @@ function responseCost$1(response) {
|
|
|
3309
4590
|
totalTokens: response.usage?.totalTokens ?? 0
|
|
3310
4591
|
};
|
|
3311
4592
|
}
|
|
3312
|
-
function createRunId$1() {
|
|
3313
|
-
return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
|
|
3314
|
-
}
|
|
3315
|
-
function nowMs$1() {
|
|
3316
|
-
return globalThis.performance?.now() ?? Date.now();
|
|
3317
|
-
}
|
|
3318
|
-
function elapsedMs$1(startedAtMs) {
|
|
3319
|
-
return Math.max(0, nowMs$1() - startedAtMs);
|
|
3320
|
-
}
|
|
3321
4593
|
//#endregion
|
|
3322
4594
|
//#region src/runtime/shared.ts
|
|
3323
4595
|
async function runShared(options) {
|
|
@@ -3594,20 +4866,10 @@ function responseCost(response) {
|
|
|
3594
4866
|
totalTokens: response.usage?.totalTokens ?? 0
|
|
3595
4867
|
};
|
|
3596
4868
|
}
|
|
3597
|
-
function createRunId() {
|
|
3598
|
-
return globalThis.crypto?.randomUUID?.() ?? `run-${Date.now().toString(36)}`;
|
|
3599
|
-
}
|
|
3600
|
-
function nowMs() {
|
|
3601
|
-
return globalThis.performance?.now() ?? Date.now();
|
|
3602
|
-
}
|
|
3603
|
-
function elapsedMs(startedAtMs) {
|
|
3604
|
-
return Math.max(0, nowMs() - startedAtMs);
|
|
3605
|
-
}
|
|
3606
|
-
function providerCallIdFor(runId, oneBasedIndex) {
|
|
3607
|
-
return `${runId}:provider-call:${oneBasedIndex}`;
|
|
3608
|
-
}
|
|
3609
4869
|
//#endregion
|
|
3610
4870
|
//#region src/runtime/engine.ts
|
|
4871
|
+
var DEFAULT_MAX_DEPTH = 4;
|
|
4872
|
+
var DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
|
|
3611
4873
|
var defaultHighLevelProtocol = "sequential";
|
|
3612
4874
|
var defaultHighLevelTier = "balanced";
|
|
3613
4875
|
/**
|
|
@@ -3628,9 +4890,20 @@ function createEngine(options) {
|
|
|
3628
4890
|
const temperature = options.temperature ?? tierTemperature(options.tier);
|
|
3629
4891
|
const agents = orderAgentsForTemperature(options.agents ?? defaultAgents(), temperature, options.seed);
|
|
3630
4892
|
const terminate = options.terminate ?? (options.budget ? conditionFromBudget(options.budget) : void 0);
|
|
4893
|
+
const engineMaxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
4894
|
+
const engineMaxConcurrentChildren = options.maxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN;
|
|
4895
|
+
const engineOnChildFailure = options.onChildFailure;
|
|
3631
4896
|
return {
|
|
3632
|
-
run(intent) {
|
|
4897
|
+
run(intent, runOptions) {
|
|
3633
4898
|
validateMissionIntent(intent);
|
|
4899
|
+
validateRunCallOptions(runOptions);
|
|
4900
|
+
validateProviderLocality(options.model, "model");
|
|
4901
|
+
const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
|
|
4902
|
+
assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
|
|
4903
|
+
const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
|
|
4904
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
4905
|
+
const startedAtMs = Date.now();
|
|
4906
|
+
const parentDeadlineMs = options.budget?.timeoutMs !== void 0 ? startedAtMs + options.budget.timeoutMs : void 0;
|
|
3634
4907
|
return runNonStreamingProtocol({
|
|
3635
4908
|
intent,
|
|
3636
4909
|
protocol,
|
|
@@ -3644,11 +4917,23 @@ function createEngine(options) {
|
|
|
3644
4917
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
3645
4918
|
...terminate ? { terminate } : {},
|
|
3646
4919
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
3647
|
-
...options.evaluate ? { evaluate: options.evaluate } : {}
|
|
4920
|
+
...options.evaluate ? { evaluate: options.evaluate } : {},
|
|
4921
|
+
currentDepth: 0,
|
|
4922
|
+
effectiveMaxDepth,
|
|
4923
|
+
effectiveMaxConcurrentChildren,
|
|
4924
|
+
onChildFailure,
|
|
4925
|
+
...parentDeadlineMs !== void 0 ? { parentDeadlineMs } : {},
|
|
4926
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {}
|
|
3648
4927
|
});
|
|
3649
4928
|
},
|
|
3650
|
-
stream(intent) {
|
|
4929
|
+
stream(intent, runOptions) {
|
|
3651
4930
|
validateMissionIntent(intent);
|
|
4931
|
+
validateRunCallOptions(runOptions);
|
|
4932
|
+
validateProviderLocality(options.model, "model");
|
|
4933
|
+
const effectiveMaxDepth = Math.min(engineMaxDepth, runOptions?.maxDepth ?? Number.POSITIVE_INFINITY);
|
|
4934
|
+
assertRunDoesNotRaiseEngineMax("maxConcurrentChildren", runOptions?.maxConcurrentChildren, engineMaxConcurrentChildren);
|
|
4935
|
+
const effectiveMaxConcurrentChildren = Math.min(engineMaxConcurrentChildren, runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY);
|
|
4936
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
3652
4937
|
const pendingEvents = [];
|
|
3653
4938
|
const pendingResolvers = [];
|
|
3654
4939
|
const emittedEvents = [];
|
|
@@ -3665,7 +4950,10 @@ function createEngine(options) {
|
|
|
3665
4950
|
const abortRace = createAbortRace(abortController.signal, options.model.id);
|
|
3666
4951
|
let complete = false;
|
|
3667
4952
|
let lastRunId = "";
|
|
4953
|
+
let rootRunId;
|
|
3668
4954
|
let pendingFinalEvent;
|
|
4955
|
+
let activeAbortDrain;
|
|
4956
|
+
const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
|
|
3669
4957
|
let status = "running";
|
|
3670
4958
|
let resolveResult;
|
|
3671
4959
|
let rejectResult;
|
|
@@ -3711,6 +4999,8 @@ function createEngine(options) {
|
|
|
3711
4999
|
async function execute() {
|
|
3712
5000
|
if (status !== "running") return;
|
|
3713
5001
|
try {
|
|
5002
|
+
const streamStartedAtMs = Date.now();
|
|
5003
|
+
const streamParentDeadlineMs = options.budget?.timeoutMs !== void 0 ? streamStartedAtMs + options.budget.timeoutMs : void 0;
|
|
3714
5004
|
const baseResult = await abortRace.run(runProtocol({
|
|
3715
5005
|
intent,
|
|
3716
5006
|
protocol,
|
|
@@ -3723,17 +5013,32 @@ function createEngine(options) {
|
|
|
3723
5013
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
3724
5014
|
signal: abortController.signal,
|
|
3725
5015
|
...terminate ? { terminate } : {},
|
|
5016
|
+
currentDepth: 0,
|
|
5017
|
+
effectiveMaxDepth,
|
|
5018
|
+
effectiveMaxConcurrentChildren,
|
|
5019
|
+
onChildFailure,
|
|
5020
|
+
...streamParentDeadlineMs !== void 0 ? { parentDeadlineMs: streamParentDeadlineMs } : {},
|
|
5021
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {},
|
|
5022
|
+
streamEvents: true,
|
|
3726
5023
|
emit(event) {
|
|
3727
5024
|
if (status !== "running") return;
|
|
5025
|
+
const parentRunIds = event.parentRunIds;
|
|
5026
|
+
if (rootRunId === void 0 && parentRunIds === void 0) rootRunId = event.runId;
|
|
3728
5027
|
lastRunId = event.runId;
|
|
3729
|
-
if (event.type === "final") {
|
|
5028
|
+
if (event.type === "final" && event.runId === rootRunId) {
|
|
3730
5029
|
pendingFinalEvent = event;
|
|
3731
5030
|
return;
|
|
3732
5031
|
}
|
|
3733
5032
|
publish(event);
|
|
3734
|
-
}
|
|
5033
|
+
},
|
|
5034
|
+
registerAbortDrain(drain) {
|
|
5035
|
+
activeAbortDrain = drain;
|
|
5036
|
+
},
|
|
5037
|
+
failureInstancesByChildRunId
|
|
3735
5038
|
}));
|
|
3736
5039
|
if (status !== "running") return;
|
|
5040
|
+
const terminalThrow = resolveRuntimeTerminalThrow(baseResult.trace, failureInstancesByChildRunId);
|
|
5041
|
+
if (terminalThrow) throw terminalThrow;
|
|
3737
5042
|
const finalizedResult = await abortRace.run(applyRunEvaluation(baseResult, options.evaluate));
|
|
3738
5043
|
if (status !== "running") return;
|
|
3739
5044
|
const finalEvent = finalizedResult.trace.events.at(-1);
|
|
@@ -3746,6 +5051,10 @@ function createEngine(options) {
|
|
|
3746
5051
|
if (isStreamHandleStatus(status, "cancelled")) return;
|
|
3747
5052
|
const runtimeError = timeoutLifecycle.translateError(error);
|
|
3748
5053
|
status = isCancellationError(runtimeError) ? "cancelled" : "failed";
|
|
5054
|
+
if (shouldPublishAborted(runtimeError)) {
|
|
5055
|
+
activeAbortDrain?.(runtimeError);
|
|
5056
|
+
publish(createStreamAbortedEvent(runtimeError, lastRunId));
|
|
5057
|
+
}
|
|
3749
5058
|
publish(createStreamErrorEvent(runtimeError, lastRunId));
|
|
3750
5059
|
closeStream();
|
|
3751
5060
|
rejectResult(runtimeError);
|
|
@@ -3754,15 +5063,18 @@ function createEngine(options) {
|
|
|
3754
5063
|
function cancelRun(cause) {
|
|
3755
5064
|
if (status !== "running") return;
|
|
3756
5065
|
const error = createStreamCancellationError(options.model.id, cause);
|
|
3757
|
-
status = "cancelled";
|
|
3758
5066
|
abortController.abort(error);
|
|
5067
|
+
activeAbortDrain?.(error);
|
|
5068
|
+
publish(createStreamAbortedEvent(error, lastRunId));
|
|
3759
5069
|
publish(createStreamErrorEvent(error, lastRunId));
|
|
5070
|
+
status = "cancelled";
|
|
3760
5071
|
closeStream();
|
|
3761
5072
|
rejectResult(error);
|
|
3762
5073
|
}
|
|
3763
5074
|
function closeStream() {
|
|
3764
5075
|
if (complete) return;
|
|
3765
5076
|
complete = true;
|
|
5077
|
+
failureInstancesByChildRunId.clear();
|
|
3766
5078
|
removeCallerAbortListener();
|
|
3767
5079
|
timeoutLifecycle.cleanup();
|
|
3768
5080
|
abortRace.cleanup();
|
|
@@ -3818,7 +5130,8 @@ function createNonStreamingAbortLifecycle(options) {
|
|
|
3818
5130
|
const timeoutLifecycle = createTimeoutAbortLifecycle({
|
|
3819
5131
|
abortController,
|
|
3820
5132
|
timeoutMs: options.timeoutMs,
|
|
3821
|
-
providerId: options.providerId
|
|
5133
|
+
providerId: options.providerId,
|
|
5134
|
+
timeoutErrorSource: options.timeoutErrorSource ?? "runtime"
|
|
3822
5135
|
});
|
|
3823
5136
|
const abortRace = createAbortRace(abortController.signal, options.providerId);
|
|
3824
5137
|
const removeCallerAbortListener = wireCallerAbortSignal(options.callerSignal, abortController, () => {
|
|
@@ -3846,7 +5159,11 @@ function createTimeoutAbortLifecycle(options) {
|
|
|
3846
5159
|
},
|
|
3847
5160
|
cleanup() {}
|
|
3848
5161
|
};
|
|
3849
|
-
const
|
|
5162
|
+
const timeoutSource = classifyChildTimeoutSource(void 0, {
|
|
5163
|
+
...options.timeoutErrorSource === "engine" ? { engineDefaultTimeoutMs: options.timeoutMs } : {},
|
|
5164
|
+
isProviderError: false
|
|
5165
|
+
});
|
|
5166
|
+
const timeoutError = options.timeoutErrorSource === "engine" && timeoutSource === "engine" ? createEngineDeadlineTimeoutError(options.providerId, options.timeoutMs) : createTimeoutError(options.providerId, options.timeoutMs);
|
|
3850
5167
|
const timeoutId = setTimeout(() => {
|
|
3851
5168
|
options.abortController.abort(timeoutError);
|
|
3852
5169
|
}, options.timeoutMs);
|
|
@@ -3914,6 +5231,23 @@ function timeoutMsFromTermination(condition) {
|
|
|
3914
5231
|
function readAbortSignalReason(signal) {
|
|
3915
5232
|
return signal?.aborted ? signal.reason : void 0;
|
|
3916
5233
|
}
|
|
5234
|
+
function createStreamAbortedEvent(error, runId) {
|
|
5235
|
+
return {
|
|
5236
|
+
type: "aborted",
|
|
5237
|
+
runId,
|
|
5238
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5239
|
+
reason: streamAbortedReason(error)
|
|
5240
|
+
};
|
|
5241
|
+
}
|
|
5242
|
+
function shouldPublishAborted(error) {
|
|
5243
|
+
return DogpileError.isInstance(error) && (error.code === "aborted" || error.code === "timeout");
|
|
5244
|
+
}
|
|
5245
|
+
function streamAbortedReason(error) {
|
|
5246
|
+
if (DogpileError.isInstance(error)) {
|
|
5247
|
+
if (error.code === "timeout" || error.detail?.["reason"] === "timeout") return "timeout";
|
|
5248
|
+
}
|
|
5249
|
+
return "parent-aborted";
|
|
5250
|
+
}
|
|
3917
5251
|
function createStreamErrorEvent(error, runId) {
|
|
3918
5252
|
if (DogpileError.isInstance(error)) return {
|
|
3919
5253
|
type: "error",
|
|
@@ -3946,10 +5280,12 @@ function dogpileErrorStreamDetail(error) {
|
|
|
3946
5280
|
return detail;
|
|
3947
5281
|
}
|
|
3948
5282
|
async function runNonStreamingProtocol(options) {
|
|
5283
|
+
const failureInstancesByChildRunId = /* @__PURE__ */ new Map();
|
|
3949
5284
|
const abortLifecycle = createNonStreamingAbortLifecycle({
|
|
3950
5285
|
callerSignal: options.signal,
|
|
3951
5286
|
timeoutMs: runtimeTimeoutMs(options),
|
|
3952
|
-
providerId: options.model.id
|
|
5287
|
+
providerId: options.model.id,
|
|
5288
|
+
timeoutErrorSource: options.currentDepth !== void 0 && options.currentDepth > 0 && options.parentDeadlineMs === void 0 ? "engine" : "runtime"
|
|
3953
5289
|
});
|
|
3954
5290
|
try {
|
|
3955
5291
|
const emittedEvents = [];
|
|
@@ -3958,7 +5294,8 @@ async function runNonStreamingProtocol(options) {
|
|
|
3958
5294
|
...abortLifecycle.signal !== void 0 ? { signal: abortLifecycle.signal } : {},
|
|
3959
5295
|
emit(event) {
|
|
3960
5296
|
emittedEvents.push(event);
|
|
3961
|
-
}
|
|
5297
|
+
},
|
|
5298
|
+
failureInstancesByChildRunId
|
|
3962
5299
|
}));
|
|
3963
5300
|
const events = emittedEvents.length > 0 ? emittedEvents : result.trace.events;
|
|
3964
5301
|
const trace = {
|
|
@@ -3979,10 +5316,13 @@ async function runNonStreamingProtocol(options) {
|
|
|
3979
5316
|
eventLog: createRunEventLog(trace.runId, trace.protocol, events),
|
|
3980
5317
|
trace
|
|
3981
5318
|
};
|
|
5319
|
+
const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
|
|
5320
|
+
if (terminalThrow) throw terminalThrow;
|
|
3982
5321
|
return canonicalizeRunResult(await abortLifecycle.run(applyRunEvaluation(runResult, options.evaluate)));
|
|
3983
5322
|
} catch (error) {
|
|
3984
5323
|
throw abortLifecycle.translateError(error);
|
|
3985
5324
|
} finally {
|
|
5325
|
+
failureInstancesByChildRunId.clear();
|
|
3986
5326
|
abortLifecycle.cleanup();
|
|
3987
5327
|
}
|
|
3988
5328
|
}
|
|
@@ -4057,7 +5397,20 @@ function runProtocol(options) {
|
|
|
4057
5397
|
...options.signal !== void 0 ? { signal: options.signal } : {},
|
|
4058
5398
|
...options.terminate ? { terminate: options.terminate } : {},
|
|
4059
5399
|
...options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {},
|
|
4060
|
-
...options.emit ? { emit: options.emit } : {}
|
|
5400
|
+
...options.emit ? { emit: options.emit } : {},
|
|
5401
|
+
...options.streamEvents !== void 0 ? { streamEvents: options.streamEvents } : {},
|
|
5402
|
+
currentDepth: options.currentDepth ?? 0,
|
|
5403
|
+
effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
|
|
5404
|
+
effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN,
|
|
5405
|
+
onChildFailure: options.onChildFailure ?? "continue",
|
|
5406
|
+
...options.parentDeadlineMs !== void 0 ? { parentDeadlineMs: options.parentDeadlineMs } : {},
|
|
5407
|
+
...options.defaultSubRunTimeoutMs !== void 0 ? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs } : {},
|
|
5408
|
+
...options.registerAbortDrain !== void 0 ? { registerAbortDrain: options.registerAbortDrain } : {},
|
|
5409
|
+
...options.failureInstancesByChildRunId !== void 0 ? { failureInstancesByChildRunId: options.failureInstancesByChildRunId } : {},
|
|
5410
|
+
runProtocol: (childInput) => runProtocol({
|
|
5411
|
+
...childInput,
|
|
5412
|
+
protocol: normalizeProtocol(childInput.protocol)
|
|
5413
|
+
})
|
|
4061
5414
|
});
|
|
4062
5415
|
case "shared": return runShared({
|
|
4063
5416
|
intent: options.intent,
|
|
@@ -4124,6 +5477,9 @@ function stream(options) {
|
|
|
4124
5477
|
function replay(trace) {
|
|
4125
5478
|
const cost = trace.finalOutput.cost;
|
|
4126
5479
|
const lastEvent = trace.events.at(-1);
|
|
5480
|
+
const accounting = recomputeAccountingFromTrace(trace);
|
|
5481
|
+
const replayThrow = resolveReplayTerminalThrow(trace);
|
|
5482
|
+
if (replayThrow) throw replayThrow;
|
|
4127
5483
|
const baseResult = {
|
|
4128
5484
|
output: trace.finalOutput.output,
|
|
4129
5485
|
eventLog: createRunEventLog(trace.runId, trace.protocol, trace.events),
|
|
@@ -4138,13 +5494,7 @@ function replay(trace) {
|
|
|
4138
5494
|
agentsUsed: trace.agentsUsed,
|
|
4139
5495
|
events: trace.events
|
|
4140
5496
|
}),
|
|
4141
|
-
accounting
|
|
4142
|
-
tier: trace.tier,
|
|
4143
|
-
...trace.budget.caps ? { budget: trace.budget.caps } : {},
|
|
4144
|
-
...trace.budget.termination ? { termination: trace.budget.termination } : {},
|
|
4145
|
-
cost,
|
|
4146
|
-
events: trace.events
|
|
4147
|
-
}),
|
|
5497
|
+
accounting,
|
|
4148
5498
|
cost
|
|
4149
5499
|
};
|
|
4150
5500
|
if (lastEvent?.type !== "final") return baseResult;
|
|
@@ -4154,6 +5504,64 @@ function replay(trace) {
|
|
|
4154
5504
|
...lastEvent.evaluation !== void 0 ? { evaluation: lastEvent.evaluation } : {}
|
|
4155
5505
|
};
|
|
4156
5506
|
}
|
|
5507
|
+
function resolveRuntimeTerminalThrow(trace, failureInstancesByChildRunId) {
|
|
5508
|
+
if (trace.triggeringFailureForAbortMode !== void 0) return failureInstancesByChildRunId.get(trace.triggeringFailureForAbortMode.childRunId) ?? null;
|
|
5509
|
+
const finalEvent = trace.events.at(-1);
|
|
5510
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === void 0) return null;
|
|
5511
|
+
const lastFailure = findLastRealFailure(trace.events, failureInstancesByChildRunId);
|
|
5512
|
+
if (lastFailure === null) return null;
|
|
5513
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) return null;
|
|
5514
|
+
return lastFailure.error;
|
|
5515
|
+
}
|
|
5516
|
+
function findLastRealFailure(events, failureInstancesByChildRunId) {
|
|
5517
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
5518
|
+
const event = events[index];
|
|
5519
|
+
if (event?.type !== "sub-run-failed") continue;
|
|
5520
|
+
const instance = failureInstancesByChildRunId.get(event.childRunId);
|
|
5521
|
+
if (instance) return {
|
|
5522
|
+
error: instance,
|
|
5523
|
+
eventIndex: index
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5526
|
+
return null;
|
|
5527
|
+
}
|
|
5528
|
+
function resolveReplayTerminalThrow(trace) {
|
|
5529
|
+
if (trace.triggeringFailureForAbortMode !== void 0) return dogpileErrorFromSerializedPayload(trace.triggeringFailureForAbortMode.error);
|
|
5530
|
+
const finalEvent = trace.events.at(-1);
|
|
5531
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === void 0) return null;
|
|
5532
|
+
const lastFailure = reconstructLastRealFailure(trace.events);
|
|
5533
|
+
if (lastFailure === null) return null;
|
|
5534
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) return null;
|
|
5535
|
+
return lastFailure.error;
|
|
5536
|
+
}
|
|
5537
|
+
function reconstructLastRealFailure(events) {
|
|
5538
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
5539
|
+
const event = events[index];
|
|
5540
|
+
if (event?.type !== "sub-run-failed" || isSyntheticSubRunFailure(event)) continue;
|
|
5541
|
+
return {
|
|
5542
|
+
error: dogpileErrorFromSerializedPayload(event.error),
|
|
5543
|
+
eventIndex: index
|
|
5544
|
+
};
|
|
5545
|
+
}
|
|
5546
|
+
return null;
|
|
5547
|
+
}
|
|
5548
|
+
function hasFinalSynthesisAfterEvent(trace, eventIndex) {
|
|
5549
|
+
return trace.protocolDecisions.some((decision) => {
|
|
5550
|
+
return decision.phase === "final-synthesis" && decision.eventIndex > eventIndex;
|
|
5551
|
+
});
|
|
5552
|
+
}
|
|
5553
|
+
function isSyntheticSubRunFailure(event) {
|
|
5554
|
+
const reason = event.error.detail?.["reason"];
|
|
5555
|
+
return reason === "sibling-failed" || reason === "parent-aborted";
|
|
5556
|
+
}
|
|
5557
|
+
function dogpileErrorFromSerializedPayload(input) {
|
|
5558
|
+
return new DogpileError({
|
|
5559
|
+
code: input.code,
|
|
5560
|
+
message: input.message,
|
|
5561
|
+
...input.providerId !== void 0 ? { providerId: input.providerId } : {},
|
|
5562
|
+
...input.detail !== void 0 ? { detail: input.detail } : {}
|
|
5563
|
+
});
|
|
5564
|
+
}
|
|
4157
5565
|
/**
|
|
4158
5566
|
* Replay a saved completed trace as a stream without invoking a model provider.
|
|
4159
5567
|
*
|
|
@@ -4164,20 +5572,22 @@ function replay(trace) {
|
|
|
4164
5572
|
* replay remains storage-free and provider-free.
|
|
4165
5573
|
*/
|
|
4166
5574
|
function replayStream(trace) {
|
|
5575
|
+
const result = Promise.resolve(replay(trace));
|
|
5576
|
+
const replayEvents = replayStreamEvents(trace);
|
|
4167
5577
|
return {
|
|
4168
5578
|
get status() {
|
|
4169
5579
|
return "completed";
|
|
4170
5580
|
},
|
|
4171
|
-
result
|
|
5581
|
+
result,
|
|
4172
5582
|
cancel() {},
|
|
4173
5583
|
subscribe(subscriber) {
|
|
4174
|
-
for (const event of
|
|
5584
|
+
for (const event of replayEvents) subscriber(event);
|
|
4175
5585
|
return { unsubscribe() {} };
|
|
4176
5586
|
},
|
|
4177
5587
|
[Symbol.asyncIterator]() {
|
|
4178
5588
|
let index = 0;
|
|
4179
5589
|
return { next() {
|
|
4180
|
-
const event =
|
|
5590
|
+
const event = replayEvents[index];
|
|
4181
5591
|
if (event) {
|
|
4182
5592
|
index += 1;
|
|
4183
5593
|
return Promise.resolve({
|
|
@@ -4193,6 +5603,22 @@ function replayStream(trace) {
|
|
|
4193
5603
|
}
|
|
4194
5604
|
};
|
|
4195
5605
|
}
|
|
5606
|
+
function replayStreamEvents(trace, parentRunIds = []) {
|
|
5607
|
+
const events = [];
|
|
5608
|
+
for (const event of trace.events) {
|
|
5609
|
+
if (event.type === "sub-run-completed") events.push(...replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]));
|
|
5610
|
+
events.push(wrapReplayStreamEvent(event, parentRunIds));
|
|
5611
|
+
}
|
|
5612
|
+
return events;
|
|
5613
|
+
}
|
|
5614
|
+
function wrapReplayStreamEvent(event, parentRunIds) {
|
|
5615
|
+
if (parentRunIds.length === 0) return event;
|
|
5616
|
+
const inbound = event.parentRunIds;
|
|
5617
|
+
return {
|
|
5618
|
+
...event,
|
|
5619
|
+
parentRunIds: [...parentRunIds, ...inbound ?? []]
|
|
5620
|
+
};
|
|
5621
|
+
}
|
|
4196
5622
|
function wireCallerAbortSignal(callerSignal, abortController, cancelRun) {
|
|
4197
5623
|
if (!callerSignal) return () => {};
|
|
4198
5624
|
const cancelFromCaller = () => {
|
|
@@ -4216,7 +5642,10 @@ function createStreamCancellationError(providerId, cause) {
|
|
|
4216
5642
|
retryable: false,
|
|
4217
5643
|
providerId,
|
|
4218
5644
|
...cause !== void 0 ? { cause } : {},
|
|
4219
|
-
detail: {
|
|
5645
|
+
detail: {
|
|
5646
|
+
status: "cancelled",
|
|
5647
|
+
reason: "parent-aborted"
|
|
5648
|
+
}
|
|
4220
5649
|
});
|
|
4221
5650
|
}
|
|
4222
5651
|
function isCancellationError(error) {
|
|
@@ -4230,6 +5659,20 @@ function withHighLevelDefaults(options) {
|
|
|
4230
5659
|
tier: options.tier ?? defaultHighLevelTier
|
|
4231
5660
|
};
|
|
4232
5661
|
}
|
|
5662
|
+
function assertRunDoesNotRaiseEngineMax(path, runValue, engineValue) {
|
|
5663
|
+
if (runValue === void 0 || runValue <= engineValue) return;
|
|
5664
|
+
throw new DogpileError({
|
|
5665
|
+
code: "invalid-configuration",
|
|
5666
|
+
message: `${path} cannot raise the engine ceiling (${engineValue}).`,
|
|
5667
|
+
retryable: false,
|
|
5668
|
+
detail: {
|
|
5669
|
+
kind: "configuration-validation",
|
|
5670
|
+
path,
|
|
5671
|
+
expected: `integer <= ${engineValue}`,
|
|
5672
|
+
actual: runValue
|
|
5673
|
+
}
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
4233
5676
|
/**
|
|
4234
5677
|
* Branded high-level SDK namespace.
|
|
4235
5678
|
*
|
|
@@ -4256,6 +5699,8 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4256
5699
|
validateOptions(options);
|
|
4257
5700
|
const providerId = options.id ?? `openai-compatible:${options.model}`;
|
|
4258
5701
|
const fetchImplementation = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
5702
|
+
const detectedLocality = classifyHostLocality(new URL(String(options.baseURL ?? defaultBaseURL)).hostname);
|
|
5703
|
+
const resolvedLocality = options.locality === "local" ? "local" : options.locality === "remote" ? "remote" : detectedLocality;
|
|
4259
5704
|
if (!fetchImplementation) throw new DogpileError({
|
|
4260
5705
|
code: "invalid-configuration",
|
|
4261
5706
|
message: "createOpenAICompatibleProvider() requires a fetch implementation in this runtime.",
|
|
@@ -4269,6 +5714,7 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4269
5714
|
});
|
|
4270
5715
|
return {
|
|
4271
5716
|
id: providerId,
|
|
5717
|
+
metadata: { locality: resolvedLocality },
|
|
4272
5718
|
async generate(request) {
|
|
4273
5719
|
let response;
|
|
4274
5720
|
try {
|
|
@@ -4281,9 +5727,11 @@ function createOpenAICompatibleProvider(options) {
|
|
|
4281
5727
|
} catch (error) {
|
|
4282
5728
|
throw normalizeFetchError(error, providerId);
|
|
4283
5729
|
}
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
5730
|
+
if (!response.ok) {
|
|
5731
|
+
const payload = await readJsonLenient(response);
|
|
5732
|
+
throw createProviderError(response, payload, providerId);
|
|
5733
|
+
}
|
|
5734
|
+
const completion = asChatCompletionResponse(await readJson(response, providerId), providerId);
|
|
4287
5735
|
const text = readAssistantText(completion, providerId);
|
|
4288
5736
|
const usage = normalizeUsage(completion.usage);
|
|
4289
5737
|
const finishReason = normalizeFinishReason(completion.choices?.[0]?.finish_reason);
|
|
@@ -4312,6 +5760,22 @@ function validateOptions(options) {
|
|
|
4312
5760
|
if (options.fetch !== void 0 && typeof options.fetch !== "function") throwInvalid("fetch", "a fetch-compatible function when provided");
|
|
4313
5761
|
if (options.maxOutputTokens !== void 0 && (!Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens <= 0)) throwInvalid("maxOutputTokens", "a positive integer when provided");
|
|
4314
5762
|
if (options.costEstimator !== void 0 && typeof options.costEstimator !== "function") throwInvalid("costEstimator", "a function when provided");
|
|
5763
|
+
if (options.locality !== void 0 && options.locality !== "local" && options.locality !== "remote") throwInvalid("locality", "\"local\" | \"remote\" when provided");
|
|
5764
|
+
if (options.locality === "remote") {
|
|
5765
|
+
const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
|
|
5766
|
+
if (classifyHostLocality(baseURL.hostname) === "local") throw new DogpileError({
|
|
5767
|
+
code: "invalid-configuration",
|
|
5768
|
+
message: `locality "remote" cannot be set when baseURL resolves to a local host (${baseURL.hostname}).`,
|
|
5769
|
+
retryable: false,
|
|
5770
|
+
detail: {
|
|
5771
|
+
kind: "configuration-validation",
|
|
5772
|
+
path: "locality",
|
|
5773
|
+
expected: "\"local\" (or omit to auto-detect)",
|
|
5774
|
+
reason: "remote-override-on-local-host",
|
|
5775
|
+
host: baseURL.hostname
|
|
5776
|
+
}
|
|
5777
|
+
});
|
|
5778
|
+
}
|
|
4315
5779
|
}
|
|
4316
5780
|
function throwInvalid(path, expected) {
|
|
4317
5781
|
throw new DogpileError({
|
|
@@ -4325,6 +5789,38 @@ function throwInvalid(path, expected) {
|
|
|
4325
5789
|
}
|
|
4326
5790
|
});
|
|
4327
5791
|
}
|
|
5792
|
+
/**
|
|
5793
|
+
* Classify a URL hostname as "local" or "remote" per Phase 3 D-02.
|
|
5794
|
+
* Local: localhost, *.local mDNS, IPv4 loopback (127.0.0.0/8), RFC1918
|
|
5795
|
+
* (10/8, 172.16/12, 192.168/16), link-local (169.254/16), IPv6 loopback (::1),
|
|
5796
|
+
* IPv6 ULA (fc00::/7), IPv6 link-local (fe80::/10).
|
|
5797
|
+
*
|
|
5798
|
+
* Pure function: no I/O, no side effects. Exported for tests and future reuse.
|
|
5799
|
+
*/
|
|
5800
|
+
function classifyHostLocality(host) {
|
|
5801
|
+
const lower = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
5802
|
+
const mappedIpv4 = ipv4MappedToDottedQuad(lower);
|
|
5803
|
+
if (mappedIpv4 !== void 0) return classifyHostLocality(mappedIpv4);
|
|
5804
|
+
if (lower === "localhost") return "local";
|
|
5805
|
+
if (lower.endsWith(".local")) return "local";
|
|
5806
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(lower)) return "local";
|
|
5807
|
+
if (/^10(?:\.\d{1,3}){3}$/.test(lower)) return "local";
|
|
5808
|
+
if (/^172\.(?:1[6-9]|2\d|3[01])(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
5809
|
+
if (/^192\.168(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
5810
|
+
if (/^169\.254(?:\.\d{1,3}){2}$/.test(lower)) return "local";
|
|
5811
|
+
if (lower === "::1") return "local";
|
|
5812
|
+
if (/^f[cd][0-9a-f]{2}:/.test(lower)) return "local";
|
|
5813
|
+
if (/^fe[89ab][0-9a-f]?:/.test(lower)) return "local";
|
|
5814
|
+
return "remote";
|
|
5815
|
+
}
|
|
5816
|
+
function ipv4MappedToDottedQuad(host) {
|
|
5817
|
+
const match = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
|
|
5818
|
+
if (match === null) return;
|
|
5819
|
+
const high = Number.parseInt(match[1] ?? "", 16);
|
|
5820
|
+
const low = Number.parseInt(match[2] ?? "", 16);
|
|
5821
|
+
if (!Number.isFinite(high) || !Number.isFinite(low)) return;
|
|
5822
|
+
return `${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`;
|
|
5823
|
+
}
|
|
4328
5824
|
function createURL(options) {
|
|
4329
5825
|
const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
|
|
4330
5826
|
const path = options.path ?? defaultPath;
|
|
@@ -4374,6 +5870,13 @@ async function readJson(response, providerId) {
|
|
|
4374
5870
|
});
|
|
4375
5871
|
}
|
|
4376
5872
|
}
|
|
5873
|
+
async function readJsonLenient(response) {
|
|
5874
|
+
try {
|
|
5875
|
+
return await response.json();
|
|
5876
|
+
} catch {
|
|
5877
|
+
return;
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
4377
5880
|
function asChatCompletionResponse(payload, providerId) {
|
|
4378
5881
|
if (!isRecord(payload)) throw new DogpileError({
|
|
4379
5882
|
code: "provider-invalid-response",
|
|
@@ -4444,14 +5947,17 @@ function responseMetadata(response) {
|
|
|
4444
5947
|
});
|
|
4445
5948
|
}
|
|
4446
5949
|
function createProviderError(response, payload, providerId) {
|
|
5950
|
+
const code = codeForStatus(response.status);
|
|
5951
|
+
const timeoutSource = code === "provider-timeout" ? { source: "provider" } : {};
|
|
4447
5952
|
return new DogpileError({
|
|
4448
|
-
code
|
|
5953
|
+
code,
|
|
4449
5954
|
message: providerResponseErrorMessage(response, payload),
|
|
4450
5955
|
retryable: response.status === 408 || response.status === 429 || response.status >= 500,
|
|
4451
5956
|
providerId,
|
|
4452
5957
|
detail: removeUndefined({
|
|
4453
5958
|
statusCode: response.status,
|
|
4454
5959
|
statusText: response.statusText,
|
|
5960
|
+
...timeoutSource,
|
|
4455
5961
|
response: isJsonValue(payload) ? payload : void 0
|
|
4456
5962
|
})
|
|
4457
5963
|
});
|
|
@@ -4515,6 +6021,263 @@ function isJsonValue(value) {
|
|
|
4515
6021
|
return false;
|
|
4516
6022
|
}
|
|
4517
6023
|
//#endregion
|
|
4518
|
-
|
|
6024
|
+
//#region src/runtime/logger.ts
|
|
6025
|
+
/** Logger that drops every call. The default when no logger is supplied. */
|
|
6026
|
+
var noopLogger = {
|
|
6027
|
+
debug() {},
|
|
6028
|
+
info() {},
|
|
6029
|
+
warn() {},
|
|
6030
|
+
error() {}
|
|
6031
|
+
};
|
|
6032
|
+
/**
|
|
6033
|
+
* Build a console-backed logger respecting a minimum level.
|
|
6034
|
+
*
|
|
6035
|
+
* The output format is JSON-on-one-line so it can be piped straight into log
|
|
6036
|
+
* collectors. Use `loggerFromEvents` to bridge it to a Dogpile stream handle.
|
|
6037
|
+
*/
|
|
6038
|
+
function consoleLogger(options = {}) {
|
|
6039
|
+
const minLevel = options.level ?? "info";
|
|
6040
|
+
const allowed = (level) => LEVEL_ORDER[level] >= LEVEL_ORDER[minLevel];
|
|
6041
|
+
const emit = (level, message, fields) => {
|
|
6042
|
+
if (!allowed(level)) return;
|
|
6043
|
+
const payload = {
|
|
6044
|
+
level,
|
|
6045
|
+
message
|
|
6046
|
+
};
|
|
6047
|
+
if (fields !== void 0) payload.fields = fields;
|
|
6048
|
+
(level === "error" ? console.error : level === "warn" ? console.warn : console.log)(JSON.stringify(payload));
|
|
6049
|
+
};
|
|
6050
|
+
return {
|
|
6051
|
+
debug: (message, fields) => emit("debug", message, fields),
|
|
6052
|
+
info: (message, fields) => emit("info", message, fields),
|
|
6053
|
+
warn: (message, fields) => emit("warn", message, fields),
|
|
6054
|
+
error: (message, fields) => emit("error", message, fields)
|
|
6055
|
+
};
|
|
6056
|
+
}
|
|
6057
|
+
var LEVEL_ORDER = {
|
|
6058
|
+
debug: 0,
|
|
6059
|
+
info: 1,
|
|
6060
|
+
warn: 2,
|
|
6061
|
+
error: 3
|
|
6062
|
+
};
|
|
6063
|
+
/**
|
|
6064
|
+
* Bridge a `Logger` to a Dogpile stream handle by returning a
|
|
6065
|
+
* `StreamEventSubscriber`. Pass it to `handle.subscribe(...)`.
|
|
6066
|
+
*
|
|
6067
|
+
* Logger throws are caught and routed to `logger.error` so a misbehaving
|
|
6068
|
+
* logger can never crash an in-flight run.
|
|
6069
|
+
*
|
|
6070
|
+
* @example
|
|
6071
|
+
* ```ts
|
|
6072
|
+
* const handle = Dogpile.stream({ intent, model });
|
|
6073
|
+
* handle.subscribe(loggerFromEvents(consoleLogger({ level: "info" })));
|
|
6074
|
+
* const result = await handle.result;
|
|
6075
|
+
* ```
|
|
6076
|
+
*/
|
|
6077
|
+
function loggerFromEvents(logger, options = {}) {
|
|
6078
|
+
const includeSet = options.include ? new Set(options.include) : void 0;
|
|
6079
|
+
return (event) => {
|
|
6080
|
+
const eventType = event.type;
|
|
6081
|
+
if (includeSet && !includeSet.has(eventType)) return;
|
|
6082
|
+
const level = options.levelFor?.(event) ?? defaultLevel(event);
|
|
6083
|
+
const message = describeEvent(event);
|
|
6084
|
+
const fields = summarizeEvent(event);
|
|
6085
|
+
try {
|
|
6086
|
+
logger[level](message, fields);
|
|
6087
|
+
} catch (cause) {
|
|
6088
|
+
try {
|
|
6089
|
+
logger.error("dogpile logger threw while handling event", {
|
|
6090
|
+
eventType,
|
|
6091
|
+
error: cause instanceof Error ? cause.message : String(cause)
|
|
6092
|
+
});
|
|
6093
|
+
} catch {}
|
|
6094
|
+
}
|
|
6095
|
+
};
|
|
6096
|
+
}
|
|
6097
|
+
function defaultLevel(event) {
|
|
6098
|
+
switch (event.type) {
|
|
6099
|
+
case "model-output-chunk": return "debug";
|
|
6100
|
+
case "budget-stop": return "warn";
|
|
6101
|
+
case "error": return "error";
|
|
6102
|
+
case "tool-result": return event.result?.type === "error" ? "warn" : "info";
|
|
6103
|
+
default: return "info";
|
|
6104
|
+
}
|
|
6105
|
+
}
|
|
6106
|
+
function describeEvent(event) {
|
|
6107
|
+
return `dogpile:${event.type}`;
|
|
6108
|
+
}
|
|
6109
|
+
function summarizeEvent(event) {
|
|
6110
|
+
const fields = { eventType: event.type };
|
|
6111
|
+
const at = event.at;
|
|
6112
|
+
if (typeof at === "string") fields.at = at;
|
|
6113
|
+
const runId = event.runId;
|
|
6114
|
+
if (typeof runId === "string") fields.runId = runId;
|
|
6115
|
+
const agentId = event.agentId;
|
|
6116
|
+
if (typeof agentId === "string") fields.agentId = agentId;
|
|
6117
|
+
const role = event.role;
|
|
6118
|
+
if (typeof role === "string") fields.role = role;
|
|
6119
|
+
return fields;
|
|
6120
|
+
}
|
|
6121
|
+
//#endregion
|
|
6122
|
+
//#region src/runtime/retry.ts
|
|
6123
|
+
/**
|
|
6124
|
+
* Default DogpileError codes that `withRetry` retries when no `retryOn`
|
|
6125
|
+
* predicate is supplied. These map to the transient provider failures listed
|
|
6126
|
+
* in `docs/developer-usage.md`.
|
|
6127
|
+
*/
|
|
6128
|
+
var DEFAULT_RETRYABLE_DOGPILE_CODES = [
|
|
6129
|
+
"provider-rate-limited",
|
|
6130
|
+
"provider-timeout",
|
|
6131
|
+
"provider-unavailable"
|
|
6132
|
+
];
|
|
6133
|
+
var DEFAULTS = {
|
|
6134
|
+
maxAttempts: 3,
|
|
6135
|
+
baseDelayMs: 250,
|
|
6136
|
+
maxDelayMs: 4e3,
|
|
6137
|
+
jitter: "full"
|
|
6138
|
+
};
|
|
6139
|
+
/**
|
|
6140
|
+
* Wrap a `ConfiguredModelProvider` with a retry policy. The wrapper:
|
|
6141
|
+
*
|
|
6142
|
+
* - Preserves the provider `id` so traces remain stable.
|
|
6143
|
+
* - Retries `generate()` calls when the policy says the error is retryable.
|
|
6144
|
+
* - Propagates `AbortSignal` cancellation immediately — never retries after
|
|
6145
|
+
* the caller cancels.
|
|
6146
|
+
* - Honors a `Retry-After`-style hint exposed via `error.detail.retryAfterMs`
|
|
6147
|
+
* when present and the policy did not provide its own `delayForError`.
|
|
6148
|
+
* - Forwards `stream()` calls through unchanged — streaming retries cannot be
|
|
6149
|
+
* safely automated because partial output may have already been observed.
|
|
6150
|
+
*
|
|
6151
|
+
* @example
|
|
6152
|
+
* ```ts
|
|
6153
|
+
* const robustProvider = withRetry(rawProvider, {
|
|
6154
|
+
* maxAttempts: 4,
|
|
6155
|
+
* baseDelayMs: 500,
|
|
6156
|
+
* onRetry: ({ attempt, delayMs, error }) => {
|
|
6157
|
+
* logger.warn("provider retry", { attempt, delayMs, error });
|
|
6158
|
+
* }
|
|
6159
|
+
* });
|
|
6160
|
+
* ```
|
|
6161
|
+
*/
|
|
6162
|
+
function withRetry(provider, policy = {}) {
|
|
6163
|
+
const settings = {
|
|
6164
|
+
maxAttempts: policy.maxAttempts ?? DEFAULTS.maxAttempts,
|
|
6165
|
+
baseDelayMs: policy.baseDelayMs ?? DEFAULTS.baseDelayMs,
|
|
6166
|
+
maxDelayMs: policy.maxDelayMs ?? DEFAULTS.maxDelayMs,
|
|
6167
|
+
jitter: policy.jitter ?? DEFAULTS.jitter,
|
|
6168
|
+
retryOn: policy.retryOn ?? defaultRetryOn,
|
|
6169
|
+
random: policy.random ?? Math.random,
|
|
6170
|
+
sleep: policy.sleep ?? defaultSleep
|
|
6171
|
+
};
|
|
6172
|
+
if (settings.maxAttempts < 1) throw new DogpileError({
|
|
6173
|
+
code: "invalid-configuration",
|
|
6174
|
+
message: "withRetry: maxAttempts must be >= 1.",
|
|
6175
|
+
detail: { maxAttempts: settings.maxAttempts }
|
|
6176
|
+
});
|
|
6177
|
+
if (settings.baseDelayMs < 0 || settings.maxDelayMs < 0) throw new DogpileError({
|
|
6178
|
+
code: "invalid-configuration",
|
|
6179
|
+
message: "withRetry: delay fields must be non-negative.",
|
|
6180
|
+
detail: {
|
|
6181
|
+
baseDelayMs: settings.baseDelayMs,
|
|
6182
|
+
maxDelayMs: settings.maxDelayMs
|
|
6183
|
+
}
|
|
6184
|
+
});
|
|
6185
|
+
const wrapped = {
|
|
6186
|
+
id: provider.id,
|
|
6187
|
+
async generate(request) {
|
|
6188
|
+
let lastError;
|
|
6189
|
+
for (let attempt = 1; attempt <= settings.maxAttempts; attempt++) {
|
|
6190
|
+
if (request.signal?.aborted) throw abortReason(request.signal);
|
|
6191
|
+
try {
|
|
6192
|
+
return await provider.generate(request);
|
|
6193
|
+
} catch (error) {
|
|
6194
|
+
lastError = error;
|
|
6195
|
+
if (isAbortError(error) || request.signal?.aborted) throw error;
|
|
6196
|
+
if (attempt >= settings.maxAttempts || !settings.retryOn(error)) throw error;
|
|
6197
|
+
const delayMs = chooseDelay({
|
|
6198
|
+
attempt,
|
|
6199
|
+
error,
|
|
6200
|
+
settings,
|
|
6201
|
+
policy
|
|
6202
|
+
});
|
|
6203
|
+
policy.onRetry?.({
|
|
6204
|
+
attempt,
|
|
6205
|
+
maxAttempts: settings.maxAttempts,
|
|
6206
|
+
delayMs,
|
|
6207
|
+
error,
|
|
6208
|
+
providerId: provider.id
|
|
6209
|
+
});
|
|
6210
|
+
await settings.sleep(delayMs, request.signal);
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
throw lastError ?? new DogpileError({
|
|
6214
|
+
code: "unknown",
|
|
6215
|
+
message: "withRetry: exhausted attempts without throwing or returning."
|
|
6216
|
+
});
|
|
6217
|
+
}
|
|
6218
|
+
};
|
|
6219
|
+
if (typeof provider.stream === "function") {
|
|
6220
|
+
const upstreamStream = provider.stream.bind(provider);
|
|
6221
|
+
wrapped.stream = (request) => upstreamStream(request);
|
|
6222
|
+
}
|
|
6223
|
+
return wrapped;
|
|
6224
|
+
}
|
|
6225
|
+
function chooseDelay(args) {
|
|
6226
|
+
const override = args.policy.delayForError?.(args.error) ?? retryAfterFromError(args.error);
|
|
6227
|
+
if (override !== void 0 && Number.isFinite(override) && override >= 0) return Math.min(args.settings.maxDelayMs, override);
|
|
6228
|
+
const exponential = args.settings.baseDelayMs * 2 ** (args.attempt - 1);
|
|
6229
|
+
const capped = Math.min(args.settings.maxDelayMs, exponential);
|
|
6230
|
+
if (args.settings.jitter === "none") return capped;
|
|
6231
|
+
return Math.floor(capped * args.settings.random());
|
|
6232
|
+
}
|
|
6233
|
+
function defaultRetryOn(error) {
|
|
6234
|
+
if (isAbortError(error)) return false;
|
|
6235
|
+
if (DogpileError.isInstance(error)) {
|
|
6236
|
+
if (error.code === "aborted" || error.code === "invalid-configuration") return false;
|
|
6237
|
+
return DEFAULT_RETRYABLE_DOGPILE_CODES.includes(error.code);
|
|
6238
|
+
}
|
|
6239
|
+
if (error instanceof TypeError) return true;
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
function isAbortError(error) {
|
|
6243
|
+
if (DogpileError.isInstance(error) && error.code === "aborted") return true;
|
|
6244
|
+
if (typeof error === "object" && error !== null) {
|
|
6245
|
+
if (error.name === "AbortError") return true;
|
|
6246
|
+
}
|
|
6247
|
+
return false;
|
|
6248
|
+
}
|
|
6249
|
+
function abortReason(signal) {
|
|
6250
|
+
return signal.reason ?? new DogpileError({
|
|
6251
|
+
code: "aborted",
|
|
6252
|
+
message: "Request aborted."
|
|
6253
|
+
});
|
|
6254
|
+
}
|
|
6255
|
+
function retryAfterFromError(error) {
|
|
6256
|
+
if (!DogpileError.isInstance(error)) return void 0;
|
|
6257
|
+
const detail = error.detail;
|
|
6258
|
+
if (!detail || typeof detail !== "object") return void 0;
|
|
6259
|
+
const candidate = detail.retryAfterMs;
|
|
6260
|
+
if (typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0) return candidate;
|
|
6261
|
+
}
|
|
6262
|
+
function defaultSleep(ms, signal) {
|
|
6263
|
+
if (ms <= 0) return Promise.resolve();
|
|
6264
|
+
return new Promise((resolve, reject) => {
|
|
6265
|
+
if (signal?.aborted) {
|
|
6266
|
+
reject(abortReason(signal));
|
|
6267
|
+
return;
|
|
6268
|
+
}
|
|
6269
|
+
const timer = setTimeout(() => {
|
|
6270
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6271
|
+
resolve();
|
|
6272
|
+
}, ms);
|
|
6273
|
+
const onAbort = () => {
|
|
6274
|
+
clearTimeout(timer);
|
|
6275
|
+
reject(abortReason(signal));
|
|
6276
|
+
};
|
|
6277
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
6278
|
+
});
|
|
6279
|
+
}
|
|
6280
|
+
//#endregion
|
|
6281
|
+
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 };
|
|
4519
6282
|
|
|
4520
6283
|
//# sourceMappingURL=index.js.map
|