@dogpile/sdk 0.5.0 → 0.6.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 +27 -0
- package/dist/browser/index.js +3992 -4997
- package/dist/browser/index.js.map +1 -1
- package/dist/providers/openai-compatible.d.ts.map +1 -1
- package/dist/providers/openai-compatible.js +5 -1
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/runtime/broadcast.d.ts +1 -0
- package/dist/runtime/broadcast.d.ts.map +1 -1
- package/dist/runtime/broadcast.js +132 -69
- package/dist/runtime/broadcast.js.map +1 -1
- package/dist/runtime/coordinator.d.ts +4 -2
- package/dist/runtime/coordinator.d.ts.map +1 -1
- package/dist/runtime/coordinator.js +114 -39
- package/dist/runtime/coordinator.js.map +1 -1
- package/dist/runtime/defaults.d.ts.map +1 -1
- package/dist/runtime/defaults.js +2 -1
- package/dist/runtime/defaults.js.map +1 -1
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +54 -34
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/model.d.ts.map +1 -1
- package/dist/runtime/model.js +6 -3
- package/dist/runtime/model.js.map +1 -1
- package/dist/runtime/redaction.d.ts +13 -0
- package/dist/runtime/redaction.d.ts.map +1 -0
- package/dist/runtime/redaction.js +278 -0
- package/dist/runtime/redaction.js.map +1 -0
- package/dist/runtime/sanitization.d.ts +4 -0
- package/dist/runtime/sanitization.d.ts.map +1 -0
- package/dist/runtime/sanitization.js +63 -0
- package/dist/runtime/sanitization.js.map +1 -0
- package/dist/runtime/shared.d.ts +1 -0
- package/dist/runtime/shared.d.ts.map +1 -1
- package/dist/runtime/shared.js +128 -65
- package/dist/runtime/shared.js.map +1 -1
- package/dist/runtime/tools/built-in.d.ts +2 -0
- package/dist/runtime/tools/built-in.d.ts.map +1 -1
- package/dist/runtime/tools/built-in.js +153 -15
- package/dist/runtime/tools/built-in.js.map +1 -1
- package/dist/runtime/tools.d.ts.map +1 -1
- package/dist/runtime/tools.js +29 -7
- package/dist/runtime/tools.js.map +1 -1
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/runtime/validation.js +3 -0
- package/dist/runtime/validation.js.map +1 -1
- package/dist/types/events.d.ts +3 -3
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/replay.d.ts +3 -1
- package/dist/types/replay.d.ts.map +1 -1
- package/dist/types.d.ts +20 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -1
- package/src/providers/openai-compatible.ts +5 -1
- package/src/runtime/broadcast.ts +156 -72
- package/src/runtime/coordinator.ts +143 -47
- package/src/runtime/defaults.ts +2 -1
- package/src/runtime/engine.ts +77 -40
- package/src/runtime/model.ts +6 -3
- package/src/runtime/redaction.ts +355 -0
- package/src/runtime/sanitization.ts +81 -0
- package/src/runtime/shared.ts +152 -68
- package/src/runtime/tools/built-in.ts +168 -15
- package/src/runtime/tools.ts +39 -8
- package/src/runtime/validation.ts +3 -0
- package/src/types/events.ts +3 -3
- package/src/types/replay.ts +3 -1
- package/src/types.ts +20 -0
|
@@ -87,6 +87,7 @@ export type RunProtocolFn = (input: {
|
|
|
87
87
|
readonly currentDepth?: number;
|
|
88
88
|
readonly effectiveMaxDepth?: number;
|
|
89
89
|
readonly effectiveMaxConcurrentChildren?: number;
|
|
90
|
+
readonly effectiveMaxConcurrentAgentTurns?: number;
|
|
90
91
|
readonly onChildFailure?: DogpileOptions["onChildFailure"];
|
|
91
92
|
/**
|
|
92
93
|
* Root-run deadline (epoch ms). Children inherit `parentDeadlineMs - now()`
|
|
@@ -126,11 +127,12 @@ interface CoordinatorRunOptions {
|
|
|
126
127
|
*/
|
|
127
128
|
readonly currentDepth?: number;
|
|
128
129
|
/**
|
|
129
|
-
* Effective max recursion depth resolved at run start
|
|
130
|
-
*
|
|
130
|
+
* Effective max recursion depth resolved at run start and enforced before
|
|
131
|
+
* delegate dispatch.
|
|
131
132
|
*/
|
|
132
133
|
readonly effectiveMaxDepth?: number;
|
|
133
134
|
readonly effectiveMaxConcurrentChildren?: number;
|
|
135
|
+
readonly effectiveMaxConcurrentAgentTurns?: number;
|
|
134
136
|
readonly onChildFailure?: DogpileOptions["onChildFailure"];
|
|
135
137
|
/**
|
|
136
138
|
* Engine `runProtocol` callback used by the delegate dispatch loop to
|
|
@@ -162,6 +164,7 @@ interface CoordinatorRunOptions {
|
|
|
162
164
|
*/
|
|
163
165
|
const MAX_DISPATCH_PER_TURN = 8;
|
|
164
166
|
const DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
|
|
167
|
+
const DEFAULT_MAX_CONCURRENT_AGENT_TURNS = 4;
|
|
165
168
|
|
|
166
169
|
type DispatchWaveFailure = {
|
|
167
170
|
readonly childRunId: string;
|
|
@@ -183,7 +186,8 @@ interface Semaphore {
|
|
|
183
186
|
|
|
184
187
|
function createSemaphore(maxConcurrent: number): Semaphore {
|
|
185
188
|
let inFlight = 0;
|
|
186
|
-
const waiters: Array<() => void> = [];
|
|
189
|
+
const waiters: Array<(() => void) | undefined> = [];
|
|
190
|
+
let waiterHead = 0;
|
|
187
191
|
return {
|
|
188
192
|
acquire(): Promise<void> {
|
|
189
193
|
if (inFlight < maxConcurrent) {
|
|
@@ -199,8 +203,14 @@ function createSemaphore(maxConcurrent: number): Semaphore {
|
|
|
199
203
|
},
|
|
200
204
|
release(): void {
|
|
201
205
|
inFlight -= 1;
|
|
202
|
-
const next = waiters
|
|
206
|
+
const next = waiters[waiterHead];
|
|
203
207
|
if (next !== undefined) {
|
|
208
|
+
waiters[waiterHead] = undefined;
|
|
209
|
+
waiterHead += 1;
|
|
210
|
+
if (waiterHead > 32 && waiterHead * 2 > waiters.length) {
|
|
211
|
+
waiters.splice(0, waiterHead);
|
|
212
|
+
waiterHead = 0;
|
|
213
|
+
}
|
|
204
214
|
next();
|
|
205
215
|
}
|
|
206
216
|
},
|
|
@@ -208,7 +218,7 @@ function createSemaphore(maxConcurrent: number): Semaphore {
|
|
|
208
218
|
return inFlight;
|
|
209
219
|
},
|
|
210
220
|
get queued() {
|
|
211
|
-
return waiters.length;
|
|
221
|
+
return waiters.length - waiterHead;
|
|
212
222
|
}
|
|
213
223
|
};
|
|
214
224
|
}
|
|
@@ -218,15 +228,16 @@ function createSemaphore(maxConcurrent: number): Semaphore {
|
|
|
218
228
|
* whose metadata.locality === "local", or undefined if none found.
|
|
219
229
|
*
|
|
220
230
|
* Walk order (forward-compat): options.model first, then options.agents in
|
|
221
|
-
* declaration order. AgentSpec has no `model` field today
|
|
222
|
-
*
|
|
223
|
-
*
|
|
231
|
+
* declaration order. AgentSpec has no `model` field today; the agent walk
|
|
232
|
+
* uses optional chaining and effectively no-ops until a future caller-defined
|
|
233
|
+
* tree surface adds AgentSpec.model.
|
|
224
234
|
*/
|
|
225
235
|
function findFirstLocalProvider(options: CoordinatorRunOptions): ConfiguredModelProvider | undefined {
|
|
226
236
|
if (options.model.metadata?.locality === "local") {
|
|
227
237
|
return options.model;
|
|
228
238
|
}
|
|
229
|
-
// Forward-compat: AgentSpec.model not yet declared
|
|
239
|
+
// Forward-compat: AgentSpec.model is not yet declared; this no-ops today and
|
|
240
|
+
// is ready for a future caller-defined tree surface.
|
|
230
241
|
for (const agent of options.agents) {
|
|
231
242
|
const agentModel = (agent as { readonly model?: ConfiguredModelProvider }).model;
|
|
232
243
|
if (agentModel?.metadata?.locality === "local") {
|
|
@@ -363,13 +374,12 @@ export async function runCoordinator(options: CoordinatorRunOptions): Promise<Ru
|
|
|
363
374
|
|
|
364
375
|
if (coordinator) {
|
|
365
376
|
if (!stopIfNeeded()) {
|
|
366
|
-
// Delegate dispatch
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
// error contract is unchanged.
|
|
377
|
+
// Delegate dispatch is restricted to the coordinator's plan turn.
|
|
378
|
+
// Workers and final synthesis cannot delegate. The loop re-issues the
|
|
379
|
+
// coordinator plan turn after each successful sub-run with the tagged
|
|
380
|
+
// child result in the next prompt and a synthetic transcript entry
|
|
381
|
+
// already appended. Failed children use the local tee buffer for
|
|
382
|
+
// partialTrace capture; runProtocol's error contract stays unchanged.
|
|
373
383
|
let dispatchInput = buildCoordinatorPlanInput(options.intent, coordinator);
|
|
374
384
|
let dispatchCount = 0;
|
|
375
385
|
while (true) {
|
|
@@ -644,29 +654,39 @@ export async function runCoordinator(options: CoordinatorRunOptions): Promise<Ru
|
|
|
644
654
|
const workers = activeAgents.slice(1);
|
|
645
655
|
const providerCallSlots: ReplayTraceProviderCall[] = [];
|
|
646
656
|
const planTranscript = [...transcript];
|
|
647
|
-
const
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
657
|
+
const fanout = createFanoutAbortController(options.signal);
|
|
658
|
+
const workerResults = await (async () => {
|
|
659
|
+
try {
|
|
660
|
+
return await mapWithConcurrency(
|
|
661
|
+
workers,
|
|
662
|
+
options.effectiveMaxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS,
|
|
663
|
+
fanout,
|
|
664
|
+
(agent, index) =>
|
|
665
|
+
runCoordinatorWorkerTurn({
|
|
666
|
+
agent,
|
|
667
|
+
coordinator,
|
|
668
|
+
input: buildWorkerInput(options.intent, planTranscript, coordinator),
|
|
669
|
+
options,
|
|
670
|
+
runId,
|
|
671
|
+
turn: transcript.length + index + 1,
|
|
672
|
+
providerCallId: providerCallIdFor(runId, providerCalls.length + index + 1),
|
|
673
|
+
providerCallIndex: index,
|
|
674
|
+
providerCallSlots,
|
|
675
|
+
toolExecutor,
|
|
676
|
+
toolAvailability,
|
|
677
|
+
totalCost,
|
|
678
|
+
events,
|
|
679
|
+
transcript: planTranscript,
|
|
680
|
+
startedAtMs,
|
|
681
|
+
wrapUpHint,
|
|
682
|
+
emit,
|
|
683
|
+
fanoutSignal: fanout.signal
|
|
684
|
+
})
|
|
685
|
+
);
|
|
686
|
+
} finally {
|
|
687
|
+
fanout.cleanup();
|
|
688
|
+
}
|
|
689
|
+
})();
|
|
670
690
|
providerCalls.push(...providerCallSlots.filter((call): call is ReplayTraceProviderCall => call !== undefined));
|
|
671
691
|
|
|
672
692
|
for (const result of workerResults) {
|
|
@@ -721,11 +741,11 @@ export async function runCoordinator(options: CoordinatorRunOptions): Promise<Ru
|
|
|
721
741
|
recordProtocolDecision
|
|
722
742
|
});
|
|
723
743
|
totalCost = synthesisOutcome.totalCost;
|
|
724
|
-
//
|
|
744
|
+
// Final synthesis is terminal and cannot dispatch children.
|
|
725
745
|
if (Array.isArray(synthesisOutcome.decision) || synthesisOutcome.decision?.type === "delegate") {
|
|
726
746
|
throw new DogpileError({
|
|
727
747
|
code: "invalid-configuration",
|
|
728
|
-
message: "Coordinator final-synthesis turn cannot emit a delegate decision
|
|
748
|
+
message: "Coordinator final-synthesis turn cannot emit a delegate decision.",
|
|
729
749
|
retryable: false,
|
|
730
750
|
detail: {
|
|
731
751
|
kind: "delegate-validation",
|
|
@@ -1016,6 +1036,7 @@ interface CoordinatorWorkerTurnOptions {
|
|
|
1016
1036
|
readonly startedAtMs: number;
|
|
1017
1037
|
readonly wrapUpHint: ReturnType<typeof createWrapUpHintController>;
|
|
1018
1038
|
readonly emit: (event: RunEvent) => void;
|
|
1039
|
+
readonly fanoutSignal?: AbortSignal;
|
|
1019
1040
|
}
|
|
1020
1041
|
|
|
1021
1042
|
interface CoordinatorWorkerTurnResult {
|
|
@@ -1029,10 +1050,11 @@ interface CoordinatorWorkerTurnResult {
|
|
|
1029
1050
|
|
|
1030
1051
|
async function runCoordinatorWorkerTurn(turn: CoordinatorWorkerTurnOptions): Promise<CoordinatorWorkerTurnResult> {
|
|
1031
1052
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
1053
|
+
throwIfAborted(turn.fanoutSignal, turn.options.model.id);
|
|
1032
1054
|
|
|
1033
1055
|
const request: ModelRequest = {
|
|
1034
1056
|
temperature: turn.options.temperature,
|
|
1035
|
-
...(turn.options.signal !== undefined ? { signal: turn.options.signal } : {}),
|
|
1057
|
+
...(turn.fanoutSignal !== undefined ? { signal: turn.fanoutSignal } : turn.options.signal !== undefined ? { signal: turn.options.signal } : {}),
|
|
1036
1058
|
metadata: {
|
|
1037
1059
|
runId: turn.runId,
|
|
1038
1060
|
protocol: "coordinator",
|
|
@@ -1085,7 +1107,7 @@ async function runCoordinatorWorkerTurn(turn: CoordinatorWorkerTurnOptions): Pro
|
|
|
1085
1107
|
if (Array.isArray(decision) || decision?.type === "delegate") {
|
|
1086
1108
|
throw new DogpileError({
|
|
1087
1109
|
code: "invalid-configuration",
|
|
1088
|
-
message: "Workers cannot emit delegate decisions
|
|
1110
|
+
message: "Workers cannot emit delegate decisions.",
|
|
1089
1111
|
retryable: false,
|
|
1090
1112
|
detail: {
|
|
1091
1113
|
kind: "delegate-validation",
|
|
@@ -1105,6 +1127,7 @@ async function runCoordinatorWorkerTurn(turn: CoordinatorWorkerTurnOptions): Pro
|
|
|
1105
1127
|
}
|
|
1106
1128
|
});
|
|
1107
1129
|
throwIfAborted(turn.options.signal, turn.options.model.id);
|
|
1130
|
+
throwIfAborted(turn.fanoutSignal, turn.options.model.id);
|
|
1108
1131
|
|
|
1109
1132
|
return {
|
|
1110
1133
|
agent: turn.agent,
|
|
@@ -1193,6 +1216,76 @@ function responseCost(response: ModelResponse): CostSummary {
|
|
|
1193
1216
|
};
|
|
1194
1217
|
}
|
|
1195
1218
|
|
|
1219
|
+
interface FanoutAbortController {
|
|
1220
|
+
readonly signal: AbortSignal;
|
|
1221
|
+
abort(reason: unknown): void;
|
|
1222
|
+
cleanup(): void;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function createFanoutAbortController(parentSignal: AbortSignal | undefined): FanoutAbortController {
|
|
1226
|
+
const controller = new AbortController();
|
|
1227
|
+
let removeParentListener = (): void => {};
|
|
1228
|
+
|
|
1229
|
+
if (parentSignal?.aborted) {
|
|
1230
|
+
controller.abort(parentSignal.reason);
|
|
1231
|
+
} else if (parentSignal !== undefined) {
|
|
1232
|
+
const abortFromParent = (): void => {
|
|
1233
|
+
controller.abort(parentSignal.reason);
|
|
1234
|
+
};
|
|
1235
|
+
parentSignal.addEventListener("abort", abortFromParent, { once: true });
|
|
1236
|
+
removeParentListener = (): void => {
|
|
1237
|
+
parentSignal.removeEventListener("abort", abortFromParent);
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
return {
|
|
1242
|
+
signal: controller.signal,
|
|
1243
|
+
abort(reason: unknown): void {
|
|
1244
|
+
if (!controller.signal.aborted) {
|
|
1245
|
+
controller.abort(reason);
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
cleanup(): void {
|
|
1249
|
+
removeParentListener();
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
async function mapWithConcurrency<T, R>(
|
|
1255
|
+
items: readonly T[],
|
|
1256
|
+
maxConcurrent: number,
|
|
1257
|
+
fanout: FanoutAbortController,
|
|
1258
|
+
mapper: (item: T, index: number) => Promise<R>
|
|
1259
|
+
): Promise<R[]> {
|
|
1260
|
+
if (items.length === 0) {
|
|
1261
|
+
return [];
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const results: R[] = new Array(items.length);
|
|
1265
|
+
let nextIndex = 0;
|
|
1266
|
+
let firstError: unknown;
|
|
1267
|
+
const workerCount = Math.min(maxConcurrent, items.length);
|
|
1268
|
+
|
|
1269
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
1270
|
+
while (nextIndex < items.length && firstError === undefined) {
|
|
1271
|
+
const index = nextIndex;
|
|
1272
|
+
nextIndex += 1;
|
|
1273
|
+
try {
|
|
1274
|
+
results[index] = await mapper(items[index]!, index);
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
firstError ??= error;
|
|
1277
|
+
fanout.abort(error);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}));
|
|
1281
|
+
|
|
1282
|
+
if (firstError !== undefined) {
|
|
1283
|
+
throw firstError;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
return results;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1196
1289
|
interface DispatchDelegateOptions {
|
|
1197
1290
|
readonly decision: DelegateAgentDecision;
|
|
1198
1291
|
readonly childRunId?: string;
|
|
@@ -1238,7 +1331,7 @@ interface DispatchedChild {
|
|
|
1238
1331
|
startedAtMs: number;
|
|
1239
1332
|
childTimeoutMs: number | undefined;
|
|
1240
1333
|
failure: DispatchWaveFailure | undefined;
|
|
1241
|
-
/**
|
|
1334
|
+
/** Reserved child stream handle slot; do not use. */
|
|
1242
1335
|
readonly streamHandle?: never;
|
|
1243
1336
|
}
|
|
1244
1337
|
|
|
@@ -1324,7 +1417,7 @@ async function dispatchDelegate(input: DispatchDelegateOptions): Promise<Dispatc
|
|
|
1324
1417
|
});
|
|
1325
1418
|
}
|
|
1326
1419
|
|
|
1327
|
-
// Buffered tee
|
|
1420
|
+
// Buffered tee captures the child event prefix used for partialTrace.
|
|
1328
1421
|
const childEvents = input.dispatchedChild.childEvents;
|
|
1329
1422
|
const parentEmit = input.emit;
|
|
1330
1423
|
const teedEmit = (event: RunEvent): void => {
|
|
@@ -1381,7 +1474,7 @@ async function dispatchDelegate(input: DispatchDelegateOptions): Promise<Dispatc
|
|
|
1381
1474
|
// BUDGET-01 / D-07: derive a per-child AbortController so child engines see
|
|
1382
1475
|
// their own signal. Listener forwards parent.signal.reason verbatim, so
|
|
1383
1476
|
// detail.reason classification (parent-aborted vs timeout) is preserved.
|
|
1384
|
-
//
|
|
1477
|
+
// Reserved per-child stream cancel hook attaches here if that surface ships.
|
|
1385
1478
|
const parentSignal = options.signal;
|
|
1386
1479
|
let removeParentAbortListener: (() => void) | undefined;
|
|
1387
1480
|
if (parentSignal !== undefined) {
|
|
@@ -1429,6 +1522,9 @@ async function dispatchDelegate(input: DispatchDelegateOptions): Promise<Dispatc
|
|
|
1429
1522
|
...(options.effectiveMaxConcurrentChildren !== undefined
|
|
1430
1523
|
? { effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren }
|
|
1431
1524
|
: {}),
|
|
1525
|
+
...(options.effectiveMaxConcurrentAgentTurns !== undefined
|
|
1526
|
+
? { effectiveMaxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
|
|
1527
|
+
: {}),
|
|
1432
1528
|
...(options.onChildFailure !== undefined ? { onChildFailure: options.onChildFailure } : {}),
|
|
1433
1529
|
// BUDGET-02 / D-12: forward the ROOT deadline so depth-N grandchildren
|
|
1434
1530
|
// see the same `parentDeadlineMs` rather than a fresh per-level snapshot.
|
|
@@ -1644,7 +1740,7 @@ function eventTimestamp(event: RunEvent | undefined): string | undefined {
|
|
|
1644
1740
|
/**
|
|
1645
1741
|
* Build a JSON-serializable {@link Trace} for `sub-run-failed.partialTrace`
|
|
1646
1742
|
* from a buffered tee of child emits. Keeps `runProtocol`'s error contract
|
|
1647
|
-
* unchanged
|
|
1743
|
+
* unchanged.
|
|
1648
1744
|
*/
|
|
1649
1745
|
function buildPartialTrace(input: {
|
|
1650
1746
|
readonly childRunId: string;
|
package/src/runtime/defaults.ts
CHANGED
|
@@ -404,7 +404,8 @@ export function createReplayTraceProtocolDecision(
|
|
|
404
404
|
agentId: event.agentId,
|
|
405
405
|
role: event.role,
|
|
406
406
|
input: event.input,
|
|
407
|
-
output: event.
|
|
407
|
+
output: event.text,
|
|
408
|
+
outputLength: event.outputLength
|
|
408
409
|
};
|
|
409
410
|
case "tool-call":
|
|
410
411
|
return {
|
package/src/runtime/engine.ts
CHANGED
|
@@ -69,6 +69,7 @@ import type { MetricsHook, RunMetricsSnapshot } from "./metrics.js";
|
|
|
69
69
|
|
|
70
70
|
const DEFAULT_MAX_DEPTH = 4;
|
|
71
71
|
const DEFAULT_MAX_CONCURRENT_CHILDREN = 4;
|
|
72
|
+
const DEFAULT_MAX_CONCURRENT_AGENT_TURNS = 4;
|
|
72
73
|
|
|
73
74
|
const defaultHighLevelProtocol = "sequential";
|
|
74
75
|
const defaultHighLevelTier = "balanced";
|
|
@@ -99,6 +100,7 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
99
100
|
const terminate = options.terminate ?? (options.budget ? conditionFromBudget(options.budget) : undefined);
|
|
100
101
|
const engineMaxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
101
102
|
const engineMaxConcurrentChildren = options.maxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN;
|
|
103
|
+
const engineMaxConcurrentAgentTurns = options.maxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS;
|
|
102
104
|
const engineOnChildFailure = options.onChildFailure;
|
|
103
105
|
|
|
104
106
|
return {
|
|
@@ -120,6 +122,15 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
120
122
|
engineMaxConcurrentChildren,
|
|
121
123
|
runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY
|
|
122
124
|
);
|
|
125
|
+
assertRunDoesNotRaiseEngineMax(
|
|
126
|
+
"maxConcurrentAgentTurns",
|
|
127
|
+
runOptions?.maxConcurrentAgentTurns,
|
|
128
|
+
engineMaxConcurrentAgentTurns
|
|
129
|
+
);
|
|
130
|
+
const effectiveMaxConcurrentAgentTurns = Math.min(
|
|
131
|
+
engineMaxConcurrentAgentTurns,
|
|
132
|
+
runOptions?.maxConcurrentAgentTurns ?? Number.POSITIVE_INFINITY
|
|
133
|
+
);
|
|
123
134
|
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
124
135
|
|
|
125
136
|
const startedAtMs = Date.now();
|
|
@@ -146,6 +157,7 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
146
157
|
currentDepth: 0,
|
|
147
158
|
effectiveMaxDepth,
|
|
148
159
|
effectiveMaxConcurrentChildren,
|
|
160
|
+
effectiveMaxConcurrentAgentTurns,
|
|
149
161
|
onChildFailure,
|
|
150
162
|
...(parentDeadlineMs !== undefined ? { parentDeadlineMs } : {}),
|
|
151
163
|
...(options.defaultSubRunTimeoutMs !== undefined
|
|
@@ -172,10 +184,18 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
172
184
|
engineMaxConcurrentChildren,
|
|
173
185
|
runOptions?.maxConcurrentChildren ?? Number.POSITIVE_INFINITY
|
|
174
186
|
);
|
|
187
|
+
assertRunDoesNotRaiseEngineMax(
|
|
188
|
+
"maxConcurrentAgentTurns",
|
|
189
|
+
runOptions?.maxConcurrentAgentTurns,
|
|
190
|
+
engineMaxConcurrentAgentTurns
|
|
191
|
+
);
|
|
192
|
+
const effectiveMaxConcurrentAgentTurns = Math.min(
|
|
193
|
+
engineMaxConcurrentAgentTurns,
|
|
194
|
+
runOptions?.maxConcurrentAgentTurns ?? Number.POSITIVE_INFINITY
|
|
195
|
+
);
|
|
175
196
|
const onChildFailure = resolveOnChildFailure(runOptions?.onChildFailure, engineOnChildFailure);
|
|
176
197
|
|
|
177
|
-
const
|
|
178
|
-
const pendingResolvers: Array<(value: IteratorResult<StreamEvent>) => void> = [];
|
|
198
|
+
const pendingIteratorResolvers: Array<() => void> = [];
|
|
179
199
|
const emittedEvents: StreamEvent[] = [];
|
|
180
200
|
const subscribers = new Set<StreamEventSubscriber>();
|
|
181
201
|
const abortController = new AbortController();
|
|
@@ -212,12 +232,20 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
212
232
|
cancelRun();
|
|
213
233
|
},
|
|
214
234
|
subscribe(subscriber: StreamEventSubscriber) {
|
|
215
|
-
subscribers.add(subscriber);
|
|
216
|
-
|
|
217
235
|
for (const event of emittedEvents) {
|
|
218
236
|
subscriber(event);
|
|
219
237
|
}
|
|
220
238
|
|
|
239
|
+
if (complete) {
|
|
240
|
+
return {
|
|
241
|
+
unsubscribe(): void {
|
|
242
|
+
// Completed streams replay synchronously and have no live source.
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
subscribers.add(subscriber);
|
|
248
|
+
|
|
221
249
|
return {
|
|
222
250
|
unsubscribe(): void {
|
|
223
251
|
subscribers.delete(subscriber);
|
|
@@ -225,20 +253,29 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
225
253
|
};
|
|
226
254
|
},
|
|
227
255
|
[Symbol.asyncIterator](): AsyncIterator<StreamEvent> {
|
|
256
|
+
let cursor = 0;
|
|
257
|
+
|
|
228
258
|
return {
|
|
229
259
|
next(): Promise<IteratorResult<StreamEvent>> {
|
|
230
|
-
|
|
231
|
-
if (event) {
|
|
232
|
-
return Promise.resolve({ done: false, value: event });
|
|
233
|
-
}
|
|
234
|
-
if (complete) {
|
|
235
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
236
|
-
}
|
|
237
|
-
return new Promise<IteratorResult<StreamEvent>>((resolve) => {
|
|
238
|
-
pendingResolvers.push(resolve);
|
|
239
|
-
});
|
|
260
|
+
return readNext();
|
|
240
261
|
}
|
|
241
262
|
};
|
|
263
|
+
|
|
264
|
+
function readNext(): Promise<IteratorResult<StreamEvent>> {
|
|
265
|
+
const event = emittedEvents[cursor];
|
|
266
|
+
if (event !== undefined) {
|
|
267
|
+
cursor += 1;
|
|
268
|
+
return Promise.resolve({ done: false, value: event });
|
|
269
|
+
}
|
|
270
|
+
if (complete) {
|
|
271
|
+
return Promise.resolve({ done: true, value: undefined });
|
|
272
|
+
}
|
|
273
|
+
return new Promise<IteratorResult<StreamEvent>>((resolve) => {
|
|
274
|
+
pendingIteratorResolvers.push(() => {
|
|
275
|
+
void readNext().then(resolve);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
}
|
|
242
279
|
}
|
|
243
280
|
};
|
|
244
281
|
|
|
@@ -266,6 +303,7 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
266
303
|
currentDepth: 0,
|
|
267
304
|
effectiveMaxDepth,
|
|
268
305
|
effectiveMaxConcurrentChildren,
|
|
306
|
+
effectiveMaxConcurrentAgentTurns,
|
|
269
307
|
onChildFailure,
|
|
270
308
|
...(streamParentDeadlineMs !== undefined ? { parentDeadlineMs: streamParentDeadlineMs } : {}),
|
|
271
309
|
...(options.defaultSubRunTimeoutMs !== undefined
|
|
@@ -362,8 +400,8 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
362
400
|
timeoutLifecycle.cleanup();
|
|
363
401
|
abortRace.cleanup();
|
|
364
402
|
subscribers.clear();
|
|
365
|
-
for (const
|
|
366
|
-
|
|
403
|
+
for (const resolvePending of pendingIteratorResolvers.splice(0)) {
|
|
404
|
+
resolvePending();
|
|
367
405
|
}
|
|
368
406
|
}
|
|
369
407
|
|
|
@@ -383,12 +421,9 @@ export function createEngine(options: EngineOptions): Engine {
|
|
|
383
421
|
}
|
|
384
422
|
}
|
|
385
423
|
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
resolver({ done: false, value: canonicalEvent });
|
|
389
|
-
return;
|
|
424
|
+
for (const resolvePending of pendingIteratorResolvers.splice(0)) {
|
|
425
|
+
resolvePending();
|
|
390
426
|
}
|
|
391
|
-
pendingEvents.push(canonicalEvent);
|
|
392
427
|
}
|
|
393
428
|
}
|
|
394
429
|
};
|
|
@@ -675,15 +710,17 @@ interface RunProtocolOptions {
|
|
|
675
710
|
/**
|
|
676
711
|
* Current recursion depth. Top-level runs use 0; the coordinator dispatch
|
|
677
712
|
* loop increments before invoking {@link runProtocol} for a child run.
|
|
678
|
-
*
|
|
713
|
+
* Depth validation is enforced before delegate dispatch.
|
|
679
714
|
*/
|
|
680
715
|
readonly currentDepth?: number;
|
|
681
716
|
/**
|
|
682
|
-
* Effective max recursion depth
|
|
717
|
+
* Effective max recursion depth after engine and per-run ceilings are merged.
|
|
683
718
|
*/
|
|
684
719
|
readonly effectiveMaxDepth?: number;
|
|
685
720
|
/** Effective max delegated child concurrency resolved at run start. */
|
|
686
721
|
readonly effectiveMaxConcurrentChildren?: number;
|
|
722
|
+
/** Effective max agent-turn fan-out resolved at run start. */
|
|
723
|
+
readonly effectiveMaxConcurrentAgentTurns?: number;
|
|
687
724
|
readonly onChildFailure?: EngineOptions["onChildFailure"];
|
|
688
725
|
/**
|
|
689
726
|
* Root-run deadline (epoch ms) threaded through every recursive coordinator
|
|
@@ -1363,6 +1400,9 @@ function runProtocolInner(
|
|
|
1363
1400
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
1364
1401
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
1365
1402
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
1403
|
+
...(options.effectiveMaxConcurrentAgentTurns !== undefined
|
|
1404
|
+
? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
|
|
1405
|
+
: {}),
|
|
1366
1406
|
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
1367
1407
|
});
|
|
1368
1408
|
case "coordinator":
|
|
@@ -1379,11 +1419,16 @@ function runProtocolInner(
|
|
|
1379
1419
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
1380
1420
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
1381
1421
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
1422
|
+
...(options.effectiveMaxConcurrentAgentTurns !== undefined
|
|
1423
|
+
? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
|
|
1424
|
+
: {}),
|
|
1382
1425
|
...(emitForProtocol ? { emit: emitForProtocol } : {}),
|
|
1383
1426
|
...(options.streamEvents !== undefined ? { streamEvents: options.streamEvents } : {}),
|
|
1384
1427
|
currentDepth: options.currentDepth ?? 0,
|
|
1385
1428
|
effectiveMaxDepth: options.effectiveMaxDepth ?? Infinity,
|
|
1386
1429
|
effectiveMaxConcurrentChildren: options.effectiveMaxConcurrentChildren ?? DEFAULT_MAX_CONCURRENT_CHILDREN,
|
|
1430
|
+
effectiveMaxConcurrentAgentTurns:
|
|
1431
|
+
options.effectiveMaxConcurrentAgentTurns ?? DEFAULT_MAX_CONCURRENT_AGENT_TURNS,
|
|
1387
1432
|
onChildFailure: options.onChildFailure ?? "continue",
|
|
1388
1433
|
...(options.parentDeadlineMs !== undefined ? { parentDeadlineMs: options.parentDeadlineMs } : {}),
|
|
1389
1434
|
...(options.defaultSubRunTimeoutMs !== undefined
|
|
@@ -1419,6 +1464,9 @@ function runProtocolInner(
|
|
|
1419
1464
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
1420
1465
|
...(options.terminate ? { terminate: options.terminate } : {}),
|
|
1421
1466
|
...(options.wrapUpHint ? { wrapUpHint: options.wrapUpHint } : {}),
|
|
1467
|
+
...(options.effectiveMaxConcurrentAgentTurns !== undefined
|
|
1468
|
+
? { maxConcurrentAgentTurns: options.effectiveMaxConcurrentAgentTurns }
|
|
1469
|
+
: {}),
|
|
1422
1470
|
...(emitForProtocol ? { emit: emitForProtocol } : {})
|
|
1423
1471
|
});
|
|
1424
1472
|
}
|
|
@@ -1697,7 +1745,6 @@ function dogpileErrorFromSerializedPayload(input: {
|
|
|
1697
1745
|
// Tracing/metrics-free: replayStream never uses EngineOptions tracer or metricsHook.
|
|
1698
1746
|
export function replayStream(trace: Trace): StreamHandle {
|
|
1699
1747
|
const result = Promise.resolve(replay(trace));
|
|
1700
|
-
const replayEvents = replayStreamEvents(trace);
|
|
1701
1748
|
|
|
1702
1749
|
return {
|
|
1703
1750
|
get status(): StreamHandleStatus {
|
|
@@ -1708,7 +1755,7 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
1708
1755
|
// Replay streams are already completed snapshots, so cancellation is a no-op.
|
|
1709
1756
|
},
|
|
1710
1757
|
subscribe(subscriber: StreamEventSubscriber) {
|
|
1711
|
-
for (const event of
|
|
1758
|
+
for (const event of replayStreamEvents(trace)) {
|
|
1712
1759
|
subscriber(event);
|
|
1713
1760
|
}
|
|
1714
1761
|
|
|
@@ -1719,34 +1766,24 @@ export function replayStream(trace: Trace): StreamHandle {
|
|
|
1719
1766
|
};
|
|
1720
1767
|
},
|
|
1721
1768
|
[Symbol.asyncIterator](): AsyncIterator<StreamEvent> {
|
|
1722
|
-
|
|
1769
|
+
const iterator = replayStreamEvents(trace)[Symbol.iterator]();
|
|
1723
1770
|
|
|
1724
1771
|
return {
|
|
1725
1772
|
next(): Promise<IteratorResult<StreamEvent>> {
|
|
1726
|
-
|
|
1727
|
-
if (event) {
|
|
1728
|
-
index += 1;
|
|
1729
|
-
return Promise.resolve({ done: false, value: event });
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
return Promise.resolve({ done: true, value: undefined });
|
|
1773
|
+
return Promise.resolve(iterator.next());
|
|
1733
1774
|
}
|
|
1734
1775
|
};
|
|
1735
1776
|
}
|
|
1736
1777
|
};
|
|
1737
1778
|
}
|
|
1738
1779
|
|
|
1739
|
-
function replayStreamEvents(trace: Trace, parentRunIds: readonly string[] = []): StreamEvent
|
|
1740
|
-
const events: StreamEvent[] = [];
|
|
1741
|
-
|
|
1780
|
+
function* replayStreamEvents(trace: Trace, parentRunIds: readonly string[] = []): IterableIterator<StreamEvent> {
|
|
1742
1781
|
for (const event of synthesizeProviderEvents(trace, trace.providerCalls)) {
|
|
1743
1782
|
if (event.type === "sub-run-completed") {
|
|
1744
|
-
|
|
1783
|
+
yield* replayStreamEvents(event.subResult.trace, [...parentRunIds, trace.runId]);
|
|
1745
1784
|
}
|
|
1746
|
-
|
|
1785
|
+
yield wrapReplayStreamEvent(event, parentRunIds);
|
|
1747
1786
|
}
|
|
1748
|
-
|
|
1749
|
-
return events;
|
|
1750
1787
|
}
|
|
1751
1788
|
|
|
1752
1789
|
function wrapReplayStreamEvent(event: RunEvent, parentRunIds: readonly string[]): StreamEvent {
|
package/src/runtime/model.ts
CHANGED
|
@@ -49,7 +49,8 @@ export async function generateModelTurn(options: GenerateModelTurnOptions): Prom
|
|
|
49
49
|
return response;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
const chunks: string[] = [];
|
|
53
|
+
let outputLength = 0;
|
|
53
54
|
let chunkIndex = 0;
|
|
54
55
|
let usage: ModelUsage | undefined;
|
|
55
56
|
let costUsd: number | undefined;
|
|
@@ -59,7 +60,8 @@ export async function generateModelTurn(options: GenerateModelTurnOptions): Prom
|
|
|
59
60
|
|
|
60
61
|
for await (const chunk of options.model.stream(options.request)) {
|
|
61
62
|
throwIfAborted(options.request.signal, options.model.id);
|
|
62
|
-
|
|
63
|
+
chunks.push(chunk.text);
|
|
64
|
+
outputLength += chunk.text.length;
|
|
63
65
|
|
|
64
66
|
options.emit({
|
|
65
67
|
type: "model-output-chunk",
|
|
@@ -70,7 +72,7 @@ export async function generateModelTurn(options: GenerateModelTurnOptions): Prom
|
|
|
70
72
|
input: options.input,
|
|
71
73
|
chunkIndex,
|
|
72
74
|
text: chunk.text,
|
|
73
|
-
|
|
75
|
+
outputLength
|
|
74
76
|
});
|
|
75
77
|
chunkIndex += 1;
|
|
76
78
|
|
|
@@ -91,6 +93,7 @@ export async function generateModelTurn(options: GenerateModelTurnOptions): Prom
|
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
const text = chunks.join("");
|
|
94
97
|
response = {
|
|
95
98
|
text,
|
|
96
99
|
...(finishReason !== undefined ? { finishReason } : {}),
|