@dogpile/sdk 0.3.1 → 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 +136 -0
- package/README.md +1 -0
- package/dist/browser/index.js +1595 -54
- package/dist/browser/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.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/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 +932 -25
- 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/sequential.d.ts.map +1 -1
- package/dist/runtime/sequential.js +8 -1
- package/dist/runtime/sequential.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/events.d.ts +329 -8
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/replay.d.ts +5 -1
- package/dist/types/replay.d.ts.map +1 -1
- package/dist/types.d.ts +131 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +10 -0
- package/src/providers/openai-compatible.ts +82 -3
- package/src/runtime/cancellation.ts +59 -1
- package/src/runtime/coordinator.ts +1170 -25
- package/src/runtime/decisions.ts +307 -4
- package/src/runtime/defaults.ts +376 -0
- package/src/runtime/engine.ts +363 -24
- package/src/runtime/sequential.ts +9 -1
- package/src/runtime/validation.ts +81 -0
- package/src/types/events.ts +359 -8
- package/src/types/replay.ts +12 -1
- package/src/types.ts +147 -3
package/src/runtime/engine.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { DogpileError } from "../types.js";
|
|
2
2
|
import type {
|
|
3
|
+
AbortedEvent,
|
|
3
4
|
BudgetTier,
|
|
5
|
+
DogpileErrorCode,
|
|
4
6
|
DogpileOptions,
|
|
5
7
|
Engine,
|
|
6
8
|
EngineOptions,
|
|
@@ -8,9 +10,11 @@ import type {
|
|
|
8
10
|
JsonObject,
|
|
9
11
|
JsonValue,
|
|
10
12
|
ProtocolSelection,
|
|
13
|
+
RunCallOptions,
|
|
11
14
|
RunEvaluation,
|
|
12
15
|
RunEvent,
|
|
13
16
|
RunResult,
|
|
17
|
+
SubRunFailedEvent,
|
|
14
18
|
StreamErrorEvent,
|
|
15
19
|
StreamEvent,
|
|
16
20
|
StreamEventSubscriber,
|
|
@@ -19,7 +23,7 @@ import type {
|
|
|
19
23
|
Trace
|
|
20
24
|
} from "../types.js";
|
|
21
25
|
import { runBroadcast } from "./broadcast.js";
|
|
22
|
-
import { runCoordinator } from "./coordinator.js";
|
|
26
|
+
import { runCoordinator, type AbortDrainFn } from "./coordinator.js";
|
|
23
27
|
import {
|
|
24
28
|
createReplayTraceFinalOutput,
|
|
25
29
|
createReplayTraceBudgetStateChanges,
|
|
@@ -32,13 +36,29 @@ import {
|
|
|
32
36
|
defaultAgents,
|
|
33
37
|
normalizeProtocol,
|
|
34
38
|
orderAgentsForTemperature,
|
|
39
|
+
recomputeAccountingFromTrace,
|
|
40
|
+
resolveOnChildFailure,
|
|
35
41
|
tierTemperature
|
|
36
42
|
} from "./defaults.js";
|
|
37
43
|
import { runSequential } from "./sequential.js";
|
|
38
44
|
import { runShared } from "./shared.js";
|
|
39
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
classifyChildTimeoutSource,
|
|
47
|
+
createAbortErrorFromSignal,
|
|
48
|
+
createEngineDeadlineTimeoutError,
|
|
49
|
+
createTimeoutError
|
|
50
|
+
} from "./cancellation.js";
|
|
40
51
|
import { budget as budgetCondition } from "./termination.js";
|
|
41
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
validateDogpileOptions,
|
|
54
|
+
validateEngineOptions,
|
|
55
|
+
validateMissionIntent,
|
|
56
|
+
validateProviderLocality,
|
|
57
|
+
validateRunCallOptions
|
|
58
|
+
} from "./validation.js";
|
|
59
|
+
|
|
60
|
+
const DEFAULT_MAX_DEPTH = 4;
|
|
61
|
+
const DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
|
|
42
62
|
|
|
43
63
|
const defaultHighLevelProtocol = "sequential";
|
|
44
64
|
const defaultHighLevelTier = "balanced";
|
|
@@ -67,10 +87,34 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
67
87
|
const temperature = options.temperature ?? tierTemperature(options.tier);
|
|
68
88
|
const agents = orderAgentsForTemperature(options.agents ?? defaultAgents(), temperature, options.seed);
|
|
69
89
|
const terminate = options.terminate ?? (options.budget ? conditionFromBudget(options.budget) : undefined);
|
|
90
|
+
const engineMaxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
91
|
+
const engineMaxConcurrentChildren = options.maxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN;
|
|
92
|
+
const engineOnChildFailure = options.onChildFailure;
|
|
70
93
|
|
|
71
94
|
return {
|
|
72
|
-
run(intent: string): Promise<RunResult> {
|
|
95
|
+
run(intent: string, runOptions?: RunCallOptions): Promise<RunResult> {
|
|
73
96
|
validateMissionIntent(intent);
|
|
97
|
+
validateRunCallOptions(runOptions);
|
|
98
|
+
validateProviderLocality(options.model, "model");
|
|
99
|
+
|
|
100
|
+
const effectiveMaxDepth = Math.min(
|
|
101
|
+
engineMaxDepth,
|
|
102
|
+
runOptions?.maxDepth ?? Number.POSITIVE_INFINITY
|
|
103
|
+
);
|
|
104
|
+
assertRunDoesNotRaiseEngineMax(
|
|
105
|
+
"maxConcurrentChildren",
|
|
106
|
+
runOptions?.maxConcurrentChildren,
|
|
107
|
+
engineMaxConcurrentChildren
|
|
108
|
+
);
|
|
109
|
+
const effectiveMaxConcurrentChildren = Math.min(
|
|
110
|
+
engineMaxConcurrentChildren,
|
|
111
|
+
runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY
|
|
112
|
+
);
|
|
113
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
114
|
+
|
|
115
|
+
const startedAtMs = Date.now();
|
|
116
|
+
const parentDeadlineMs =
|
|
117
|
+
options.budget?.timeoutMs !== undefined ? startedAtMs + options.budget.timeoutMs : undefined;
|
|
74
118
|
|
|
75
119
|
return runNonStreamingProtocol({
|
|
76
120
|
intent,
|
|
@@ -85,12 +129,37 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
85
129
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
86
130
|
...(terminate ? { terminate } : {}),
|
|
87
131
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
88
|
-
...(options.evaluate ? { evaluate: options.evaluate } : {})
|
|
132
|
+
...(options.evaluate ? { evaluate: options.evaluate } : {}),
|
|
133
|
+
currentDepth: 0,
|
|
134
|
+
effectiveMaxDepth,
|
|
135
|
+
effectiveMaxConcurrentChildren,
|
|
136
|
+
onChildFailure,
|
|
137
|
+
...(parentDeadlineMs !== undefined ? { parentDeadlineMs } : {}),
|
|
138
|
+
...(options.defaultSubRunTimeoutMs !== undefined
|
|
139
|
+
? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs }
|
|
140
|
+
: {})
|
|
89
141
|
});
|
|
90
142
|
},
|
|
91
143
|
|
|
92
|
-
stream(intent: string): StreamHandle {
|
|
144
|
+
stream(intent: string, runOptions?: RunCallOptions): StreamHandle {
|
|
93
145
|
validateMissionIntent(intent);
|
|
146
|
+
validateRunCallOptions(runOptions);
|
|
147
|
+
validateProviderLocality(options.model, "model");
|
|
148
|
+
|
|
149
|
+
const effectiveMaxDepth = Math.min(
|
|
150
|
+
engineMaxDepth,
|
|
151
|
+
runOptions?.maxDepth ?? Number.POSITIVE_INFINITY
|
|
152
|
+
);
|
|
153
|
+
assertRunDoesNotRaiseEngineMax(
|
|
154
|
+
"maxConcurrentChildren",
|
|
155
|
+
runOptions?.maxConcurrentChildren,
|
|
156
|
+
engineMaxConcurrentChildren
|
|
157
|
+
);
|
|
158
|
+
const effectiveMaxConcurrentChildren = Math.min(
|
|
159
|
+
engineMaxConcurrentChildren,
|
|
160
|
+
runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY
|
|
161
|
+
);
|
|
162
|
+
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
94
163
|
|
|
95
164
|
const pendingEvents: StreamEvent[] = [];
|
|
96
165
|
const pendingResolvers: Array<(value: IteratorResult<StreamEvent>) => void> = [];
|
|
@@ -105,7 +174,10 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
105
174
|
const abortRace = createAbortRace(abortController.signal, options.model.id);
|
|
106
175
|
let complete = false;
|
|
107
176
|
let lastRunId = "";
|
|
177
|
+
let rootRunId: string | undefined;
|
|
108
178
|
let pendingFinalEvent: FinalEvent | undefined;
|
|
179
|
+
let activeAbortDrain: AbortDrainFn | undefined;
|
|
180
|
+
const failureInstancesByChildRunId = new Map<string, DogpileError>();
|
|
109
181
|
let status: StreamHandleStatus = "running";
|
|
110
182
|
let resolveResult!: (result: RunResult) => void;
|
|
111
183
|
let rejectResult!: (error: unknown) => void;
|
|
@@ -163,6 +235,9 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
163
235
|
}
|
|
164
236
|
|
|
165
237
|
try {
|
|
238
|
+
const streamStartedAtMs = Date.now();
|
|
239
|
+
const streamParentDeadlineMs =
|
|
240
|
+
options.budget?.timeoutMs !== undefined ? streamStartedAtMs + options.budget.timeoutMs : undefined;
|
|
166
241
|
const baseResult = await abortRace.run(runProtocol({
|
|
167
242
|
intent,
|
|
168
243
|
protocol,
|
|
@@ -175,22 +250,44 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
175
250
|
...(options.seed !== undefined ? { seed: options.seed } : {}),
|
|
176
251
|
signal: abortController.signal,
|
|
177
252
|
...(terminate ? { terminate } : {}),
|
|
253
|
+
currentDepth: 0,
|
|
254
|
+
effectiveMaxDepth,
|
|
255
|
+
effectiveMaxConcurrentChildren,
|
|
256
|
+
onChildFailure,
|
|
257
|
+
...(streamParentDeadlineMs !== undefined ? { parentDeadlineMs: streamParentDeadlineMs } : {}),
|
|
258
|
+
...(options.defaultSubRunTimeoutMs !== undefined
|
|
259
|
+
? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs }
|
|
260
|
+
: {}),
|
|
261
|
+
streamEvents: true,
|
|
178
262
|
emit(event: RunEvent): void {
|
|
179
263
|
if (status !== "running") {
|
|
180
264
|
return;
|
|
181
265
|
}
|
|
182
266
|
|
|
267
|
+
const parentRunIds = (event as { readonly parentRunIds?: readonly string[] }).parentRunIds;
|
|
268
|
+
if (rootRunId === undefined && parentRunIds === undefined) {
|
|
269
|
+
rootRunId = event.runId;
|
|
270
|
+
}
|
|
271
|
+
|
|
183
272
|
lastRunId = event.runId;
|
|
184
|
-
if (event.type === "final") {
|
|
273
|
+
if (event.type === "final" && event.runId === rootRunId) {
|
|
185
274
|
pendingFinalEvent = event;
|
|
186
275
|
return;
|
|
187
276
|
}
|
|
188
277
|
publish(event);
|
|
189
|
-
}
|
|
278
|
+
},
|
|
279
|
+
registerAbortDrain(drain: AbortDrainFn): void {
|
|
280
|
+
activeAbortDrain = drain;
|
|
281
|
+
},
|
|
282
|
+
failureInstancesByChildRunId
|
|
190
283
|
}));
|
|
191
284
|
if (status !== "running") {
|
|
192
285
|
return;
|
|
193
286
|
}
|
|
287
|
+
const terminalThrow = resolveRuntimeTerminalThrow(baseResult.trace, failureInstancesByChildRunId);
|
|
288
|
+
if (terminalThrow) {
|
|
289
|
+
throw terminalThrow;
|
|
290
|
+
}
|
|
194
291
|
|
|
195
292
|
const finalizedResult = await abortRace.run(applyRunEvaluation(baseResult, options.evaluate));
|
|
196
293
|
if (status !== "running") {
|
|
@@ -213,6 +310,10 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
213
310
|
|
|
214
311
|
const runtimeError = timeoutLifecycle.translateError(error);
|
|
215
312
|
status = isCancellationError(runtimeError) ? "cancelled" : "failed";
|
|
313
|
+
if (shouldPublishAborted(runtimeError)) {
|
|
314
|
+
activeAbortDrain?.(runtimeError);
|
|
315
|
+
publish(createStreamAbortedEvent(runtimeError, lastRunId));
|
|
316
|
+
}
|
|
216
317
|
publish(createStreamErrorEvent(runtimeError, lastRunId));
|
|
217
318
|
closeStream();
|
|
218
319
|
rejectResult(runtimeError);
|
|
@@ -225,9 +326,11 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
225
326
|
}
|
|
226
327
|
|
|
227
328
|
const error = createStreamCancellationError(options.model.id, cause);
|
|
228
|
-
status = "cancelled";
|
|
229
329
|
abortController.abort(error);
|
|
330
|
+
activeAbortDrain?.(error);
|
|
331
|
+
publish(createStreamAbortedEvent(error, lastRunId));
|
|
230
332
|
publish(createStreamErrorEvent(error, lastRunId));
|
|
333
|
+
status = "cancelled";
|
|
231
334
|
closeStream();
|
|
232
335
|
rejectResult(error);
|
|
233
336
|
}
|
|
@@ -238,6 +341,7 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
238
341
|
}
|
|
239
342
|
|
|
240
343
|
complete = true;
|
|
344
|
+
failureInstancesByChildRunId.clear();
|
|
241
345
|
removeCallerAbortListener();
|
|
242
346
|
timeoutLifecycle.cleanup();
|
|
243
347
|
abortRace.cleanup();
|
|
@@ -303,6 +407,7 @@ function createNonStreamingAbortLifecycle(options: {
|
|
|
303
407
|
readonly callerSignal?: AbortSignal | undefined;
|
|
304
408
|
readonly timeoutMs?: number | undefined;
|
|
305
409
|
readonly providerId: string;
|
|
410
|
+
readonly timeoutErrorSource?: "runtime" | "engine";
|
|
306
411
|
}): AbortLifecycle {
|
|
307
412
|
if (options.timeoutMs === undefined) {
|
|
308
413
|
return {
|
|
@@ -321,7 +426,8 @@ function createNonStreamingAbortLifecycle(options: {
|
|
|
321
426
|
const timeoutLifecycle = createTimeoutAbortLifecycle({
|
|
322
427
|
abortController,
|
|
323
428
|
timeoutMs: options.timeoutMs,
|
|
324
|
-
providerId: options.providerId
|
|
429
|
+
providerId: options.providerId,
|
|
430
|
+
timeoutErrorSource: options.timeoutErrorSource ?? "runtime"
|
|
325
431
|
});
|
|
326
432
|
const abortRace = createAbortRace(abortController.signal, options.providerId);
|
|
327
433
|
const removeCallerAbortListener = wireCallerAbortSignal(options.callerSignal, abortController, () => {
|
|
@@ -348,6 +454,7 @@ function createTimeoutAbortLifecycle(options: {
|
|
|
348
454
|
readonly abortController: AbortController;
|
|
349
455
|
readonly timeoutMs?: number | undefined;
|
|
350
456
|
readonly providerId: string;
|
|
457
|
+
readonly timeoutErrorSource?: "runtime" | "engine";
|
|
351
458
|
}): TimeoutAbortLifecycle {
|
|
352
459
|
if (options.timeoutMs === undefined) {
|
|
353
460
|
return {
|
|
@@ -358,7 +465,14 @@ function createTimeoutAbortLifecycle(options: {
|
|
|
358
465
|
};
|
|
359
466
|
}
|
|
360
467
|
|
|
361
|
-
const
|
|
468
|
+
const timeoutSource = classifyChildTimeoutSource(undefined, {
|
|
469
|
+
...(options.timeoutErrorSource === "engine" ? { engineDefaultTimeoutMs: options.timeoutMs } : {}),
|
|
470
|
+
isProviderError: false
|
|
471
|
+
});
|
|
472
|
+
const timeoutError =
|
|
473
|
+
options.timeoutErrorSource === "engine" && timeoutSource === "engine"
|
|
474
|
+
? createEngineDeadlineTimeoutError(options.providerId, options.timeoutMs)
|
|
475
|
+
: createTimeoutError(options.providerId, options.timeoutMs);
|
|
362
476
|
const timeoutId = setTimeout(() => {
|
|
363
477
|
options.abortController.abort(timeoutError);
|
|
364
478
|
}, options.timeoutMs);
|
|
@@ -454,6 +568,28 @@ function readAbortSignalReason(signal: AbortSignal | undefined): unknown {
|
|
|
454
568
|
return signal?.aborted ? signal.reason : undefined;
|
|
455
569
|
}
|
|
456
570
|
|
|
571
|
+
function createStreamAbortedEvent(error: unknown, runId: string): AbortedEvent {
|
|
572
|
+
return {
|
|
573
|
+
type: "aborted",
|
|
574
|
+
runId,
|
|
575
|
+
at: new Date().toISOString(),
|
|
576
|
+
reason: streamAbortedReason(error)
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function shouldPublishAborted(error: unknown): boolean {
|
|
581
|
+
return DogpileError.isInstance(error) && (error.code === "aborted" || error.code === "timeout");
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function streamAbortedReason(error: unknown): AbortedEvent["reason"] {
|
|
585
|
+
if (DogpileError.isInstance(error)) {
|
|
586
|
+
if (error.code === "timeout" || error.detail?.["reason"] === "timeout") {
|
|
587
|
+
return "timeout";
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return "parent-aborted";
|
|
591
|
+
}
|
|
592
|
+
|
|
457
593
|
function createStreamErrorEvent(error: unknown, runId: string): StreamErrorEvent {
|
|
458
594
|
if (DogpileError.isInstance(error)) {
|
|
459
595
|
return {
|
|
@@ -519,15 +655,49 @@ interface RunProtocolOptions {
|
|
|
519
655
|
readonly terminate?: EngineOptions["terminate"];
|
|
520
656
|
readonly wrapUpHint?: EngineOptions["wrapUpHint"];
|
|
521
657
|
readonly emit?: (event: RunEvent) => void;
|
|
658
|
+
readonly streamEvents?: boolean;
|
|
659
|
+
/**
|
|
660
|
+
* Current recursion depth. Top-level runs use 0; the coordinator dispatch
|
|
661
|
+
* loop increments before invoking {@link runProtocol} for a child run.
|
|
662
|
+
* Plan 04 will wire `effectiveMaxDepth` validation around this value.
|
|
663
|
+
*/
|
|
664
|
+
readonly currentDepth?: number;
|
|
665
|
+
/**
|
|
666
|
+
* Effective max recursion depth. Plan 04 enforces; Plan 03 plumbs the param.
|
|
667
|
+
*/
|
|
668
|
+
readonly effectiveMaxDepth?: number;
|
|
669
|
+
/** Effective max delegated child concurrency resolved at run start. */
|
|
670
|
+
readonly effectiveMaxConcurrentChildren?: number;
|
|
671
|
+
readonly onChildFailure?: EngineOptions["onChildFailure"];
|
|
672
|
+
/**
|
|
673
|
+
* Root-run deadline (epoch ms) threaded through every recursive coordinator
|
|
674
|
+
* dispatch (BUDGET-02 / D-12). Children inherit `parentDeadlineMs - now()`
|
|
675
|
+
* as their default timeout window.
|
|
676
|
+
*/
|
|
677
|
+
readonly parentDeadlineMs?: number;
|
|
678
|
+
/**
|
|
679
|
+
* Engine-level fallback sub-run timeout (BUDGET-02 / D-14). Applied only when
|
|
680
|
+
* neither the parent nor the decision specifies a `budget.timeoutMs`.
|
|
681
|
+
*/
|
|
682
|
+
readonly defaultSubRunTimeoutMs?: number;
|
|
683
|
+
readonly registerAbortDrain?: (drain: AbortDrainFn) => void;
|
|
684
|
+
readonly failureInstancesByChildRunId?: Map<string, DogpileError>;
|
|
522
685
|
}
|
|
523
686
|
|
|
524
687
|
type NonStreamingProtocolOptions = Omit<RunProtocolOptions, "emit"> & Pick<EngineOptions, "evaluate">;
|
|
525
688
|
|
|
526
689
|
async function runNonStreamingProtocol(options: NonStreamingProtocolOptions): Promise<RunResult> {
|
|
690
|
+
const failureInstancesByChildRunId = new Map<string, DogpileError>();
|
|
527
691
|
const abortLifecycle = createNonStreamingAbortLifecycle({
|
|
528
692
|
callerSignal: options.signal,
|
|
529
693
|
timeoutMs: runtimeTimeoutMs(options),
|
|
530
|
-
providerId: options.model.id
|
|
694
|
+
providerId: options.model.id,
|
|
695
|
+
timeoutErrorSource:
|
|
696
|
+
options.currentDepth !== undefined &&
|
|
697
|
+
options.currentDepth > 0 &&
|
|
698
|
+
options.parentDeadlineMs === undefined
|
|
699
|
+
? "engine"
|
|
700
|
+
: "runtime"
|
|
531
701
|
});
|
|
532
702
|
|
|
533
703
|
try {
|
|
@@ -537,7 +707,8 @@ async function runNonStreamingProtocol(options: NonStreamingProtocolOptions): Pr
|
|
|
537
707
|
...(abortLifecycle.signal !== undefined ? { signal: abortLifecycle.signal } : {}),
|
|
538
708
|
emit(event: RunEvent): void {
|
|
539
709
|
emittedEvents.push(event);
|
|
540
|
-
}
|
|
710
|
+
},
|
|
711
|
+
failureInstancesByChildRunId
|
|
541
712
|
}));
|
|
542
713
|
const events = emittedEvents.length > 0 ? emittedEvents : result.trace.events;
|
|
543
714
|
const trace = {
|
|
@@ -559,10 +730,15 @@ async function runNonStreamingProtocol(options: NonStreamingProtocolOptions): Pr
|
|
|
559
730
|
eventLog: createRunEventLog(trace.runId, trace.protocol, events),
|
|
560
731
|
trace
|
|
561
732
|
};
|
|
733
|
+
const terminalThrow = resolveRuntimeTerminalThrow(runResult.trace, failureInstancesByChildRunId);
|
|
734
|
+
if (terminalThrow) {
|
|
735
|
+
throw terminalThrow;
|
|
736
|
+
}
|
|
562
737
|
return canonicalizeRunResult(await abortLifecycle.run(applyRunEvaluation(runResult, options.evaluate)));
|
|
563
738
|
} catch (error: unknown) {
|
|
564
739
|
throw abortLifecycle.translateError(error);
|
|
565
740
|
} finally {
|
|
741
|
+
failureInstancesByChildRunId.clear();
|
|
566
742
|
abortLifecycle.cleanup();
|
|
567
743
|
}
|
|
568
744
|
}
|
|
@@ -653,7 +829,25 @@ function runProtocol(options: RunProtocolOptions): Promise<RunResult> {
|
|
|
653
829
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
654
830
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
655
831
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
656
|
-
...(options.emit ? { emit: options.emit } : {})
|
|
832
|
+
...(options.emit ? { emit: options.emit } : {}),
|
|
833
|
+
...(options.streamEvents !== undefined ? { streamEvents: options.streamEvents } : {}),
|
|
834
|
+
currentDepth: options.currentDepth ?? 0,
|
|
835
|
+
effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
|
|
836
|
+
effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN,
|
|
837
|
+
onChildFailure: options.onChildFailure ?? "continue",
|
|
838
|
+
...(options.parentDeadlineMs !== undefined ? { parentDeadlineMs: options.parentDeadlineMs } : {}),
|
|
839
|
+
...(options.defaultSubRunTimeoutMs !== undefined
|
|
840
|
+
? { defaultSubRunTimeoutMs: options.defaultSubRunTimeoutMs }
|
|
841
|
+
: {}),
|
|
842
|
+
...(options.registerAbortDrain !== undefined ? { registerAbortDrain: options.registerAbortDrain } : {}),
|
|
843
|
+
...(options.failureInstancesByChildRunId !== undefined
|
|
844
|
+
? { failureInstancesByChildRunId: options.failureInstancesByChildRunId }
|
|
845
|
+
: {}),
|
|
846
|
+
runProtocol: (childInput) =>
|
|
847
|
+
runProtocol({
|
|
848
|
+
...childInput,
|
|
849
|
+
protocol: normalizeProtocol(childInput.protocol)
|
|
850
|
+
})
|
|
657
851
|
});
|
|
658
852
|
case "shared":
|
|
659
853
|
return runShared({
|
|
@@ -726,6 +920,15 @@ export function stream(options: DogpileOptions): StreamHandle {
|
|
|
726
920
|
export function replay(trace: Trace): RunResult {
|
|
727
921
|
const cost = trace.finalOutput.cost;
|
|
728
922
|
const lastEvent = trace.events.at(-1);
|
|
923
|
+
// D-08 / D-10: rebuild accounting recursively from the saved trace and
|
|
924
|
+
// verify every embedded sub-run's recorded accounting matches what the
|
|
925
|
+
// child trace recomputes. Mismatches throw `invalid-configuration` with
|
|
926
|
+
// `detail.reason: "trace-accounting-mismatch"`. No provider invocation.
|
|
927
|
+
const accounting = recomputeAccountingFromTrace(trace);
|
|
928
|
+
const replayThrow = resolveReplayTerminalThrow(trace);
|
|
929
|
+
if (replayThrow) {
|
|
930
|
+
throw replayThrow;
|
|
931
|
+
}
|
|
729
932
|
const baseResult = {
|
|
730
933
|
output: trace.finalOutput.output,
|
|
731
934
|
eventLog: createRunEventLog(trace.runId, trace.protocol, trace.events),
|
|
@@ -740,13 +943,7 @@ export function replay(trace: Trace): RunResult {
|
|
|
740
943
|
agentsUsed: trace.agentsUsed,
|
|
741
944
|
events: trace.events
|
|
742
945
|
}),
|
|
743
|
-
accounting
|
|
744
|
-
tier: trace.tier,
|
|
745
|
-
...(trace.budget.caps ? { budget: trace.budget.caps } : {}),
|
|
746
|
-
...(trace.budget.termination ? { termination: trace.budget.termination } : {}),
|
|
747
|
-
cost,
|
|
748
|
-
events: trace.events
|
|
749
|
-
}),
|
|
946
|
+
accounting,
|
|
750
947
|
cost
|
|
751
948
|
};
|
|
752
949
|
|
|
@@ -761,6 +958,104 @@ export function replay(trace: Trace): RunResult {
|
|
|
761
958
|
};
|
|
762
959
|
}
|
|
763
960
|
|
|
961
|
+
function resolveRuntimeTerminalThrow(
|
|
962
|
+
trace: Trace,
|
|
963
|
+
failureInstancesByChildRunId: ReadonlyMap<string, DogpileError>
|
|
964
|
+
): DogpileError | null {
|
|
965
|
+
if (trace.triggeringFailureForAbortMode !== undefined) {
|
|
966
|
+
return failureInstancesByChildRunId.get(trace.triggeringFailureForAbortMode.childRunId) ?? null;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const finalEvent = trace.events.at(-1);
|
|
970
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === undefined) {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const lastFailure = findLastRealFailure(trace.events, failureInstancesByChildRunId);
|
|
975
|
+
if (lastFailure === null) {
|
|
976
|
+
return null;
|
|
977
|
+
}
|
|
978
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) {
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
return lastFailure.error;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
function findLastRealFailure(
|
|
985
|
+
events: readonly RunEvent[],
|
|
986
|
+
failureInstancesByChildRunId: ReadonlyMap<string, DogpileError>
|
|
987
|
+
): { readonly error: DogpileError; readonly eventIndex: number } | null {
|
|
988
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
989
|
+
const event = events[index];
|
|
990
|
+
if (event?.type !== "sub-run-failed") {
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const instance = failureInstancesByChildRunId.get(event.childRunId);
|
|
994
|
+
if (instance) {
|
|
995
|
+
return { error: instance, eventIndex: index };
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
function resolveReplayTerminalThrow(trace: Trace): DogpileError | null {
|
|
1002
|
+
if (trace.triggeringFailureForAbortMode !== undefined) {
|
|
1003
|
+
return dogpileErrorFromSerializedPayload(trace.triggeringFailureForAbortMode.error);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const finalEvent = trace.events.at(-1);
|
|
1007
|
+
if (finalEvent?.type !== "final" || finalEvent.termination === undefined) {
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const lastFailure = reconstructLastRealFailure(trace.events);
|
|
1012
|
+
if (lastFailure === null) {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
if (hasFinalSynthesisAfterEvent(trace, lastFailure.eventIndex)) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
return lastFailure.error;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function reconstructLastRealFailure(
|
|
1022
|
+
events: readonly RunEvent[]
|
|
1023
|
+
): { readonly error: DogpileError; readonly eventIndex: number } | null {
|
|
1024
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
1025
|
+
const event = events[index];
|
|
1026
|
+
if (event?.type !== "sub-run-failed" || isSyntheticSubRunFailure(event)) {
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
return { error: dogpileErrorFromSerializedPayload(event.error), eventIndex: index };
|
|
1030
|
+
}
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
function hasFinalSynthesisAfterEvent(trace: Trace, eventIndex: number): boolean {
|
|
1035
|
+
return trace.protocolDecisions.some((decision) => {
|
|
1036
|
+
return decision.phase === "final-synthesis" && decision.eventIndex > eventIndex;
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function isSyntheticSubRunFailure(event: SubRunFailedEvent): boolean {
|
|
1041
|
+
const reason = event.error.detail?.["reason"];
|
|
1042
|
+
return reason === "sibling-failed" || reason === "parent-aborted";
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function dogpileErrorFromSerializedPayload(input: {
|
|
1046
|
+
readonly code: string;
|
|
1047
|
+
readonly message: string;
|
|
1048
|
+
readonly providerId?: string;
|
|
1049
|
+
readonly detail?: JsonObject;
|
|
1050
|
+
}): DogpileError {
|
|
1051
|
+
return new DogpileError({
|
|
1052
|
+
code: input.code as DogpileErrorCode,
|
|
1053
|
+
message: input.message,
|
|
1054
|
+
...(input.providerId !== undefined ? { providerId: input.providerId } : {}),
|
|
1055
|
+
...(input.detail !== undefined ? { detail: input.detail } : {})
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
764
1059
|
/**
|
|
765
1060
|
* Replay a saved completed trace as a stream without invoking a model provider.
|
|
766
1061
|
*
|
|
@@ -772,6 +1067,7 @@ export function replay(trace: Trace): RunResult {
|
|
|
772
1067
|
*/
|
|
773
1068
|
export function replayStream(trace: Trace): StreamHandle {
|
|
774
1069
|
const result = Promise.resolve(replay(trace));
|
|
1070
|
+
const replayEvents = replayStreamEvents(trace);
|
|
775
1071
|
|
|
776
1072
|
return {
|
|
777
1073
|
get status(): StreamHandleStatus {
|
|
@@ -782,7 +1078,7 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
782
1078
|
// Replay streams are already completed snapshots, so cancellation is a no-op.
|
|
783
1079
|
},
|
|
784
1080
|
subscribe(subscriber: StreamEventSubscriber) {
|
|
785
|
-
for (const event of
|
|
1081
|
+
for (const event of replayEvents) {
|
|
786
1082
|
subscriber(event);
|
|
787
1083
|
}
|
|
788
1084
|
|
|
@@ -797,7 +1093,7 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
797
1093
|
|
|
798
1094
|
return {
|
|
799
1095
|
next(): Promise<IteratorResult<StreamEvent>> {
|
|
800
|
-
const event =
|
|
1096
|
+
const event = replayEvents[index];
|
|
801
1097
|
if (event) {
|
|
802
1098
|
index += 1;
|
|
803
1099
|
return Promise.resolve({ done: false, value: event });
|
|
@@ -810,6 +1106,31 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
810
1106
|
};
|
|
811
1107
|
}
|
|
812
1108
|
|
|
1109
|
+
function replayStreamEvents(trace: Trace, parentRunIds: readonly string[] = []): StreamEvent[] {
|
|
1110
|
+
const events: StreamEvent[] = [];
|
|
1111
|
+
|
|
1112
|
+
for (const event of trace.events) {
|
|
1113
|
+
if (event.type === "sub-run-completed") {
|
|
1114
|
+
events.push(...replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]));
|
|
1115
|
+
}
|
|
1116
|
+
events.push(wrapReplayStreamEvent(event, parentRunIds));
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
return events;
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
function wrapReplayStreamEvent(event: RunEvent, parentRunIds: readonly string[]): StreamEvent {
|
|
1123
|
+
if (parentRunIds.length === 0) {
|
|
1124
|
+
return event;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const inbound = (event as { readonly parentRunIds?: readonly string[] }).parentRunIds;
|
|
1128
|
+
return {
|
|
1129
|
+
...event,
|
|
1130
|
+
parentRunIds: [...parentRunIds, ...(inbound ?? [])]
|
|
1131
|
+
} as StreamEvent;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
813
1134
|
function wireCallerAbortSignal(
|
|
814
1135
|
callerSignal: AbortSignal | undefined,
|
|
815
1136
|
abortController: AbortController,
|
|
@@ -844,7 +1165,8 @@ function createStreamCancellationError(providerId: string, cause?: unknown): Dog
|
|
|
844
1165
|
providerId,
|
|
845
1166
|
...(cause !== undefined ? { cause } : {}),
|
|
846
1167
|
detail: {
|
|
847
|
-
status: "cancelled"
|
|
1168
|
+
status: "cancelled",
|
|
1169
|
+
reason: "parent-aborted"
|
|
848
1170
|
}
|
|
849
1171
|
});
|
|
850
1172
|
}
|
|
@@ -865,6 +1187,23 @@ function withHighLevelDefaults(options: DogpileOptions): NormalizedDogpileOption
|
|
|
865
1187
|
};
|
|
866
1188
|
}
|
|
867
1189
|
|
|
1190
|
+
function assertRunDoesNotRaiseEngineMax(path: string, runValue: number | undefined, engineValue: number): void {
|
|
1191
|
+
if (runValue === undefined || runValue <= engineValue) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
throw new DogpileError({
|
|
1195
|
+
code: "invalid-configuration",
|
|
1196
|
+
message: `${path} cannot raise the engine ceiling (${engineValue}).`,
|
|
1197
|
+
retryable: false,
|
|
1198
|
+
detail: {
|
|
1199
|
+
kind: "configuration-validation",
|
|
1200
|
+
path,
|
|
1201
|
+
expected: `integer <= ${engineValue}`,
|
|
1202
|
+
actual: runValue
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
|
|
868
1207
|
/**
|
|
869
1208
|
* Branded high-level SDK namespace.
|
|
870
1209
|
*
|
|
@@ -218,7 +218,15 @@ export async function runSequential(options: SequentialRunOptions): Promise<RunR
|
|
|
218
218
|
}
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
// Preferred: most recent entry with an explicit participating decision.
|
|
222
|
+
// Fallback: most recent entry that has no parsed decision at all (preserves
|
|
223
|
+
// pre-discriminated-union behavior where unparsed turns were treated as
|
|
224
|
+
// participating). Delegate decisions are explicitly non-participating.
|
|
225
|
+
const reversed = [...transcript].reverse();
|
|
226
|
+
const output =
|
|
227
|
+
reversed.find((entry) => isParticipatingDecision(entry.decision))?.output ??
|
|
228
|
+
reversed.find((entry) => entry.decision === undefined)?.output ??
|
|
229
|
+
"";
|
|
222
230
|
throwIfAborted(options.signal, options.model.id);
|
|
223
231
|
const final: RunEvent = {
|
|
224
232
|
type: "final",
|