deepline 0.1.243 → 0.1.244
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/README.md +1 -1
- package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +5 -25
- package/dist/bundling-sources/sdk/src/release.ts +23 -21
- package/dist/bundling-sources/sdk/src/types.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/backend.ts +2 -21
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +6 -13
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +9 -26
- package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +1 -2
- package/dist/bundling-sources/shared_libs/play-runtime/map-row-identity.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +191 -29
- package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +1 -36
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +2 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +0 -7
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +1 -12
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +2 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver.ts +5 -15
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +6 -21
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +3 -4
- package/dist/bundling-sources/shared_libs/plays/artifact-kind-guard.ts +16 -29
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +4 -8
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +21 -712
- package/dist/bundling-sources/shared_libs/plays/bundling/limits.ts +1 -18
- package/dist/cli/index.js +29 -71
- package/dist/cli/index.mjs +29 -71
- package/dist/{compiler-manifest-CYcwzSOJ.d.ts → compiler-manifest-DlE7dnRm.d.mts} +2 -8
- package/dist/{compiler-manifest-CYcwzSOJ.d.mts → compiler-manifest-DlE7dnRm.d.ts} +2 -8
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +24 -22
- package/dist/index.mjs +24 -22
- package/dist/plays/bundle-play-file.d.mts +7 -22
- package/dist/plays/bundle-play-file.d.ts +7 -22
- package/dist/plays/bundle-play-file.mjs +26 -614
- package/package.json +2 -2
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +0 -4504
- package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +0 -1386
- package/dist/bundling-sources/apps/play-runner-workers/src/durable-object-deploy-handoff.ts +0 -30
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +0 -8385
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +0 -21
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/batching.ts +0 -251
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/coordinator-progress.ts +0 -289
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/csv-rows.ts +0 -85
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +0 -23
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/dataset-handles.ts +0 -528
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +0 -229
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/ledger-event-batches.ts +0 -81
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/live-progress.ts +0 -22
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +0 -920
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-latency-profile.ts +0 -90
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +0 -475
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +0 -570
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/result-dataset-persistence.ts +0 -50
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/row-isolation.ts +0 -5
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +0 -519
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -107
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +0 -2541
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-http-errors.ts +0 -7
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +0 -227
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +0 -130
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +0 -39
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/workflow-preview.ts +0 -26
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime-api-retry.ts +0 -23
- package/dist/bundling-sources/apps/play-runner-workers/src/workflow-instance-create.ts +0 -47
- package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +0 -167
- package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry.ts +0 -91
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +0 -329
- package/dist/bundling-sources/sdk/src/worker-play-entry.ts +0 -240
- package/dist/bundling-sources/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +0 -252
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -217
|
@@ -1,920 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
chooseMapChunkSize,
|
|
3
|
-
EXECUTION_PLAN_DEFAULTS,
|
|
4
|
-
type ExecutionPlan,
|
|
5
|
-
} from '../../../../shared_libs/play-runtime/execution-plan';
|
|
6
|
-
import { TOOL_CALLING_MAP_CHUNK_SIZE } from '../../../../shared_libs/play-runtime/map-chunk-limits';
|
|
7
|
-
import { getPlayRuntimeBatchStrategy } from '../../../../shared_libs/play-runtime/play-runtime-batching-registry';
|
|
8
|
-
import {
|
|
9
|
-
WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL,
|
|
10
|
-
WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT,
|
|
11
|
-
} from './worker-platform-budget';
|
|
12
|
-
import {
|
|
13
|
-
flattenStaticSubsteps,
|
|
14
|
-
getTopLevelPipelineSubsteps,
|
|
15
|
-
type PlayStaticColumnProducer,
|
|
16
|
-
type PlayStaticPipeline,
|
|
17
|
-
type PlayStaticSubstep,
|
|
18
|
-
} from '../../../../shared_libs/plays/static-pipeline';
|
|
19
|
-
|
|
20
|
-
export const CACHE_ENABLED_SIMPLE_MAP_CHUNK_SIZE = 10_000;
|
|
21
|
-
export { TOOL_CALLING_MAP_CHUNK_SIZE };
|
|
22
|
-
export const WORKER_SUBREQUEST_SAFE_TOOL_CALLS_PER_CHUNK =
|
|
23
|
-
TOOL_CALLING_MAP_CHUNK_SIZE;
|
|
24
|
-
export const UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET =
|
|
25
|
-
WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT;
|
|
26
|
-
export const SUBREQUESTS_PER_UNBATCHED_TOOL_CALL =
|
|
27
|
-
WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL;
|
|
28
|
-
|
|
29
|
-
// A batched tool over a chunk of N rows does not spend the unbatched provider
|
|
30
|
-
// budget for every row. Durable receipt claim/complete/fail are bulk RPCs for
|
|
31
|
-
// the whole drained tool group, but provider execution still happens once per
|
|
32
|
-
// compiled batch. Static planning cannot know input-dependent bucket keys, so
|
|
33
|
-
// it must assume worst-case provider fanout (one compiled batch per row) unless
|
|
34
|
-
// the batching registry grows a proof that all rows share a bucket.
|
|
35
|
-
export const BATCHED_TOOL_SUBREQUESTS_PER_BATCH = 2;
|
|
36
|
-
export const BULK_RECEIPT_SUBREQUESTS_PER_CHUNK = 3;
|
|
37
|
-
// Conservative batch size assumed only when a batchable tool's registry
|
|
38
|
-
// maxBatchSize is unknown. Smaller = more batches = more subrequests = safer.
|
|
39
|
-
export const ASSUMED_BATCH_SIZE_FLOOR = 100;
|
|
40
|
-
const MAX_BATCHABLE_SUBREQUEST_SAFE_ROWS = 1_000_000;
|
|
41
|
-
|
|
42
|
-
function assumedBatchSizeFor(minBatchableBatchSize: number): number {
|
|
43
|
-
return minBatchableBatchSize > 0
|
|
44
|
-
? minBatchableBatchSize
|
|
45
|
-
: ASSUMED_BATCH_SIZE_FLOOR;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Per-chunk subrequest cost of the batchable tool work in a map chunk. Shared
|
|
49
|
-
// by the chunk sizer (inverts it to find safe rows/chunk) and the live budget
|
|
50
|
-
// projection in entry.ts so the two never drift.
|
|
51
|
-
export function estimateBatchedToolChunkSubrequests(input: {
|
|
52
|
-
batchableToolCallsPerRow: number;
|
|
53
|
-
coalescedBatchableToolCallsPerRow?: number;
|
|
54
|
-
rows: number;
|
|
55
|
-
assumedBatchSize: number;
|
|
56
|
-
}): number {
|
|
57
|
-
if (input.batchableToolCallsPerRow <= 0 || input.rows <= 0) return 0;
|
|
58
|
-
const toolCallsPerRow = Math.max(1, input.batchableToolCallsPerRow);
|
|
59
|
-
const coalescedToolCallsPerRow = Math.min(
|
|
60
|
-
toolCallsPerRow,
|
|
61
|
-
Math.max(0, input.coalescedBatchableToolCallsPerRow ?? 0),
|
|
62
|
-
);
|
|
63
|
-
const worstCaseToolCallsPerRow = Math.max(
|
|
64
|
-
0,
|
|
65
|
-
toolCallsPerRow - coalescedToolCallsPerRow,
|
|
66
|
-
);
|
|
67
|
-
const coalescedCompiledBatches =
|
|
68
|
-
Math.ceil(input.rows / Math.max(1, input.assumedBatchSize)) *
|
|
69
|
-
coalescedToolCallsPerRow;
|
|
70
|
-
const worstCaseCompiledBatches = input.rows * worstCaseToolCallsPerRow;
|
|
71
|
-
return (
|
|
72
|
-
BATCHED_TOOL_SUBREQUESTS_PER_BATCH *
|
|
73
|
-
(coalescedCompiledBatches + worstCaseCompiledBatches) +
|
|
74
|
-
BULK_RECEIPT_SUBREQUESTS_PER_CHUNK * toolCallsPerRow
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
export type WorkerMapChunkPlanInput = {
|
|
78
|
-
mapName: string;
|
|
79
|
-
rowCountHint: number | null;
|
|
80
|
-
executionPlan?: ExecutionPlan | null;
|
|
81
|
-
staticPipeline?: PlayStaticPipeline | null;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
type PlanMap = ExecutionPlan['maps'][number];
|
|
85
|
-
type ChunkSizingMap = Pick<
|
|
86
|
-
PlanMap,
|
|
87
|
-
| 'mapName'
|
|
88
|
-
| 'tableNamespace'
|
|
89
|
-
| 'outputFields'
|
|
90
|
-
| 'defaultChunkSize'
|
|
91
|
-
| 'stepsPerChunk'
|
|
92
|
-
> &
|
|
93
|
-
Partial<
|
|
94
|
-
Pick<PlanMap, 'externalStepFields' | 'stepFields' | 'waterfallStages'>
|
|
95
|
-
>;
|
|
96
|
-
type ChunkSizingPlan = {
|
|
97
|
-
maps: ChunkSizingMap[];
|
|
98
|
-
toolDeclarations?: ExecutionPlan['toolDeclarations'];
|
|
99
|
-
softWorkflowStepBudget?: number | null;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
type MapToolStats = [
|
|
103
|
-
totalToolCount: number,
|
|
104
|
-
unbatchedToolCount: number,
|
|
105
|
-
unbatchedSubrequestCount: number,
|
|
106
|
-
// Smallest registry maxBatchSize across the batchable tools counted so far
|
|
107
|
-
// (0 when no batchable tool has been seen). Smallest = most batches = most
|
|
108
|
-
// subrequests, so it is the conservative choice for chunk sizing.
|
|
109
|
-
minBatchableBatchSize: number,
|
|
110
|
-
coalescedBatchableToolCount: number,
|
|
111
|
-
];
|
|
112
|
-
type MapControlWorkStats = {
|
|
113
|
-
childPlaySubmits: number;
|
|
114
|
-
eventWaits: number;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const EMPTY_CONTROL_WORK_STATS: MapControlWorkStats = {
|
|
118
|
-
childPlaySubmits: 0,
|
|
119
|
-
eventWaits: 0,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
export type WorkerMapDispatchPlan = {
|
|
123
|
-
rowsPerChunk: number;
|
|
124
|
-
estimatedExternalStepsPerRow: number;
|
|
125
|
-
workEstimate: WorkerMapWorkEstimate;
|
|
126
|
-
reason:
|
|
127
|
-
| 'external-pressure'
|
|
128
|
-
| 'tool-free-large-cache'
|
|
129
|
-
| 'tool-free-plan'
|
|
130
|
-
| 'default-plan';
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
export type WorkerMapWorkEstimate = {
|
|
134
|
-
cpuRowsPerRow: number;
|
|
135
|
-
runtimeSheetReadsPerChunk: number;
|
|
136
|
-
runtimeSheetWritesPerChunk: number;
|
|
137
|
-
durableReceiptOpsPerRow: number;
|
|
138
|
-
providerToolCallsPerRow: number;
|
|
139
|
-
unbatchedProviderToolCallsPerRow: number;
|
|
140
|
-
// Registry batch size assumed for the batchable tools in this map (smallest
|
|
141
|
-
// maxBatchSize across them, or ASSUMED_BATCH_SIZE_FLOOR when unknown). Drives
|
|
142
|
-
// the batched-tool subrequest projection.
|
|
143
|
-
assumedBatchSize: number;
|
|
144
|
-
coalescedBatchableProviderToolCallsPerRow: number;
|
|
145
|
-
childPlaySubmitsPerRow: number;
|
|
146
|
-
eventWaitsPerRow: number;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Keep one externally-effectful Workflow chunk inside the same bounded row
|
|
151
|
-
* window used by runtime admission. The static planner may choose a larger
|
|
152
|
-
* chunk when provider calls coalesce, but a chunk is still the smallest point
|
|
153
|
-
* where the dispatcher can cross a durable sleep boundary. Capping only the
|
|
154
|
-
* native Workflow execution chunk prevents a slow 5k provider chunk from
|
|
155
|
-
* outliving one invocation and being retried before the dispatcher can yield.
|
|
156
|
-
*/
|
|
157
|
-
export function workflowSafeMapRowsPerChunk(input: {
|
|
158
|
-
plannedRowsPerChunk: number;
|
|
159
|
-
rowAdmissionWindow: number;
|
|
160
|
-
hasWorkflowStep: boolean;
|
|
161
|
-
hasExternalSideEffects: boolean;
|
|
162
|
-
}): number {
|
|
163
|
-
const plannedRowsPerChunk = Math.max(
|
|
164
|
-
1,
|
|
165
|
-
Math.floor(input.plannedRowsPerChunk),
|
|
166
|
-
);
|
|
167
|
-
if (!input.hasWorkflowStep || !input.hasExternalSideEffects) {
|
|
168
|
-
return plannedRowsPerChunk;
|
|
169
|
-
}
|
|
170
|
-
const rowAdmissionWindow = Math.max(1, Math.floor(input.rowAdmissionWindow));
|
|
171
|
-
return Math.min(plannedRowsPerChunk, rowAdmissionWindow);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function fieldRoot(field: string | null | undefined): string {
|
|
175
|
-
return String(field ?? '').split('.', 1)[0] ?? '';
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function declarationBelongsToMapOutput(
|
|
179
|
-
declaration: { field?: string | null },
|
|
180
|
-
outputFields: readonly string[],
|
|
181
|
-
externalStepFields: readonly string[] = [],
|
|
182
|
-
): boolean {
|
|
183
|
-
const fieldName = declaration.field;
|
|
184
|
-
if (fieldName == null) return true;
|
|
185
|
-
if (
|
|
186
|
-
outputFields.some(
|
|
187
|
-
(field) => fieldName === field || fieldName.startsWith(`${field}.`),
|
|
188
|
-
)
|
|
189
|
-
) {
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
return externalStepFields.some(
|
|
193
|
-
(field) => fieldName === field || fieldName.endsWith(`.${field}`),
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function countTool(
|
|
198
|
-
toolId: string,
|
|
199
|
-
options?: { staticSingleBucket?: boolean },
|
|
200
|
-
): MapToolStats {
|
|
201
|
-
const batchStrategy = getPlayRuntimeBatchStrategy(toolId);
|
|
202
|
-
if (batchStrategy !== null) {
|
|
203
|
-
return [
|
|
204
|
-
1,
|
|
205
|
-
0,
|
|
206
|
-
0,
|
|
207
|
-
Math.max(1, Math.floor(batchStrategy.maxBatchSize)),
|
|
208
|
-
options?.staticSingleBucket === true ? 1 : 0,
|
|
209
|
-
];
|
|
210
|
-
}
|
|
211
|
-
return [1, 1, SUBREQUESTS_PER_UNBATCHED_TOOL_CALL, 0, 0];
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function minPositiveBatchSize(a: number, b: number): number {
|
|
215
|
-
if (a <= 0) return Math.max(0, b);
|
|
216
|
-
if (b <= 0) return Math.max(0, a);
|
|
217
|
-
return Math.min(a, b);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function minProviderBranchCoalescedCount(
|
|
221
|
-
branchStats: readonly MapToolStats[],
|
|
222
|
-
): number {
|
|
223
|
-
let minCoalesced: number | null = null;
|
|
224
|
-
for (const [total, , , , coalesced] of branchStats) {
|
|
225
|
-
if (total <= 0) continue;
|
|
226
|
-
minCoalesced =
|
|
227
|
-
minCoalesced === null ? coalesced : Math.min(minCoalesced, coalesced);
|
|
228
|
-
}
|
|
229
|
-
return minCoalesced ?? 0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function escapeRegExp(value: string): string {
|
|
233
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function hasStaticSingleBatchBucket(input: {
|
|
237
|
-
toolId: string;
|
|
238
|
-
paramsSource?: string | null;
|
|
239
|
-
sourceText?: string | null;
|
|
240
|
-
}): boolean {
|
|
241
|
-
const strategy = getPlayRuntimeBatchStrategy(input.toolId);
|
|
242
|
-
const bucketFields = strategy?.bucketKeyPayloadFields;
|
|
243
|
-
if (!bucketFields || bucketFields.length === 0) return false;
|
|
244
|
-
const source = [input.paramsSource, input.sourceText]
|
|
245
|
-
.filter((value): value is string => typeof value === 'string')
|
|
246
|
-
.join('\n');
|
|
247
|
-
if (!source) return false;
|
|
248
|
-
return bucketFields.every((field) => {
|
|
249
|
-
const pattern = new RegExp(
|
|
250
|
-
`(^|[^A-Za-z0-9_$])${escapeRegExp(field)}([^A-Za-z0-9_$]|$)`,
|
|
251
|
-
);
|
|
252
|
-
return !pattern.test(source);
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function addStats(
|
|
257
|
-
[total, unbatched, subrequests, batchSize, coalesced]: MapToolStats,
|
|
258
|
-
stats: MapToolStats,
|
|
259
|
-
): MapToolStats {
|
|
260
|
-
return [
|
|
261
|
-
total + stats[0],
|
|
262
|
-
unbatched + stats[1],
|
|
263
|
-
subrequests + stats[2],
|
|
264
|
-
minPositiveBatchSize(batchSize, stats[3]),
|
|
265
|
-
coalesced + stats[4],
|
|
266
|
-
];
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function countToolSubsteps(
|
|
270
|
-
substeps: readonly PlayStaticSubstep[],
|
|
271
|
-
): MapToolStats {
|
|
272
|
-
let stats: MapToolStats = [0, 0, 0, 0, 0];
|
|
273
|
-
|
|
274
|
-
for (const substep of substeps) {
|
|
275
|
-
stats = addStats(stats, countToolSubstep(substep));
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return stats;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function countToolSubstep(substep: PlayStaticSubstep): MapToolStats {
|
|
282
|
-
if (substep.type === 'tool') {
|
|
283
|
-
return countTool(substep.toolId, {
|
|
284
|
-
staticSingleBucket: hasStaticSingleBatchBucket({
|
|
285
|
-
toolId: substep.toolId,
|
|
286
|
-
paramsSource: substep.paramsSource,
|
|
287
|
-
sourceText: substep.sourceText,
|
|
288
|
-
}),
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (substep.type === 'waterfall') {
|
|
293
|
-
let stats: MapToolStats = [0, 0, 0, 0, 0];
|
|
294
|
-
for (const step of substep.steps ?? []) {
|
|
295
|
-
if (!step.toolId) continue;
|
|
296
|
-
stats = addStats(
|
|
297
|
-
stats,
|
|
298
|
-
countTool(step.toolId, {
|
|
299
|
-
staticSingleBucket: hasStaticSingleBatchBucket({
|
|
300
|
-
toolId: step.toolId,
|
|
301
|
-
paramsSource: step.paramsSource,
|
|
302
|
-
sourceText: substep.sourceText,
|
|
303
|
-
}),
|
|
304
|
-
}),
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
return stats;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (substep.type === 'step_suite') {
|
|
311
|
-
return countStepSuiteTools(substep);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (substep.type === 'control_flow') {
|
|
315
|
-
if (substep.kind === 'conditional' && substep.branches?.length) {
|
|
316
|
-
const branchStats = substep.branches.map((branch) =>
|
|
317
|
-
countToolSubsteps(branch.steps),
|
|
318
|
-
);
|
|
319
|
-
const maxBranchStats = branchStats.reduce<MapToolStats>(
|
|
320
|
-
(
|
|
321
|
-
[totalMax, unbatchedMax, subrequestsMax, batchSizeMin, coalescedMax],
|
|
322
|
-
[total, unbatched, subrequests, batchSize, coalesced],
|
|
323
|
-
) => [
|
|
324
|
-
Math.max(totalMax, total),
|
|
325
|
-
Math.max(unbatchedMax, unbatched),
|
|
326
|
-
Math.max(subrequestsMax, subrequests),
|
|
327
|
-
minPositiveBatchSize(batchSizeMin, batchSize),
|
|
328
|
-
Math.max(coalescedMax, coalesced),
|
|
329
|
-
],
|
|
330
|
-
[0, 0, 0, 0, 0],
|
|
331
|
-
);
|
|
332
|
-
return [
|
|
333
|
-
maxBranchStats[0],
|
|
334
|
-
maxBranchStats[1],
|
|
335
|
-
maxBranchStats[2],
|
|
336
|
-
maxBranchStats[3],
|
|
337
|
-
minProviderBranchCoalescedCount(branchStats),
|
|
338
|
-
];
|
|
339
|
-
}
|
|
340
|
-
if (substep.kind === 'loop') {
|
|
341
|
-
// The static extractor records every switch arm in `steps` when a loop
|
|
342
|
-
// walks a fixed provider list. Each arm can execute on a different
|
|
343
|
-
// iteration, so using the conditional's max-one-branch estimate would
|
|
344
|
-
// budget a seven-provider waterfall as one provider call per row.
|
|
345
|
-
return substep.steps.reduce<MapToolStats>((stats, step) => {
|
|
346
|
-
const stepStats =
|
|
347
|
-
step.type === 'control_flow' &&
|
|
348
|
-
step.kind === 'conditional' &&
|
|
349
|
-
step.steps.length > 0
|
|
350
|
-
? countToolSubsteps(step.steps)
|
|
351
|
-
: countToolSubstep(step);
|
|
352
|
-
return addStats(stats, stepStats);
|
|
353
|
-
}, [0, 0, 0, 0, 0]);
|
|
354
|
-
}
|
|
355
|
-
return countToolSubsteps(substep.steps);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (substep.type === 'dataset') {
|
|
359
|
-
return countToolSubsteps(substep.steps ?? []);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return [0, 0, 0, 0, 0];
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function addControlWorkStats(
|
|
366
|
-
left: MapControlWorkStats,
|
|
367
|
-
right: MapControlWorkStats,
|
|
368
|
-
): MapControlWorkStats {
|
|
369
|
-
return {
|
|
370
|
-
childPlaySubmits: left.childPlaySubmits + right.childPlaySubmits,
|
|
371
|
-
eventWaits: left.eventWaits + right.eventWaits,
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function maxControlWorkStats(
|
|
376
|
-
left: MapControlWorkStats,
|
|
377
|
-
right: MapControlWorkStats,
|
|
378
|
-
): MapControlWorkStats {
|
|
379
|
-
return {
|
|
380
|
-
childPlaySubmits: Math.max(left.childPlaySubmits, right.childPlaySubmits),
|
|
381
|
-
eventWaits: Math.max(left.eventWaits, right.eventWaits),
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
function countControlWorkSubsteps(
|
|
386
|
-
substeps: readonly PlayStaticSubstep[],
|
|
387
|
-
): MapControlWorkStats {
|
|
388
|
-
return substeps.reduce<MapControlWorkStats>(
|
|
389
|
-
(acc, substep) =>
|
|
390
|
-
addControlWorkStats(acc, countControlWorkSubstep(substep)),
|
|
391
|
-
EMPTY_CONTROL_WORK_STATS,
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
function countControlWorkSubstep(
|
|
396
|
-
substep: PlayStaticSubstep,
|
|
397
|
-
): MapControlWorkStats {
|
|
398
|
-
if (substep.type === 'play_call') {
|
|
399
|
-
return { childPlaySubmits: 1, eventWaits: 0 };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (substep.type === 'tool') {
|
|
403
|
-
return {
|
|
404
|
-
childPlaySubmits: 0,
|
|
405
|
-
eventWaits:
|
|
406
|
-
substep.isEventWait === true || substep.toolId === 'test_wait_for_event'
|
|
407
|
-
? 1
|
|
408
|
-
: 0,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (substep.type === 'step_suite') {
|
|
413
|
-
return countControlWorkSubsteps(substep.steps);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (substep.type === 'control_flow') {
|
|
417
|
-
if (substep.kind === 'conditional' && substep.branches?.length) {
|
|
418
|
-
return substep.branches
|
|
419
|
-
.map((branch) => countControlWorkSubsteps(branch.steps))
|
|
420
|
-
.reduce<MapControlWorkStats>(
|
|
421
|
-
(acc, stats) => maxControlWorkStats(acc, stats),
|
|
422
|
-
EMPTY_CONTROL_WORK_STATS,
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
return countControlWorkSubsteps(substep.steps);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
if (substep.type === 'dataset') {
|
|
429
|
-
return countControlWorkSubsteps(substep.steps ?? []);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return EMPTY_CONTROL_WORK_STATS;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function countStepSuiteTools(
|
|
436
|
-
substep: Extract<PlayStaticSubstep, { type: 'step_suite' }>,
|
|
437
|
-
): MapToolStats {
|
|
438
|
-
const childStats = substep.steps.map((step) => ({
|
|
439
|
-
step,
|
|
440
|
-
stats: countToolSubstep(step),
|
|
441
|
-
}));
|
|
442
|
-
const conditionalChildren = childStats.filter(
|
|
443
|
-
({ step }) =>
|
|
444
|
-
step.conditional ||
|
|
445
|
-
(step.type === 'control_flow' && step.kind === 'conditional'),
|
|
446
|
-
);
|
|
447
|
-
if (
|
|
448
|
-
conditionalChildren.length === 0 ||
|
|
449
|
-
conditionalChildren.length < childStats.length / 2
|
|
450
|
-
) {
|
|
451
|
-
return childStats.reduce<MapToolStats>(
|
|
452
|
-
(acc, { stats }) => addStats(acc, stats),
|
|
453
|
-
[0, 0, 0, 0, 0],
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const suiteRoot = fieldRoot(substep.field);
|
|
458
|
-
const sameRoot = childStats.every(({ step }) => {
|
|
459
|
-
if (!('field' in step)) return true;
|
|
460
|
-
return fieldRoot(step.field) === suiteRoot;
|
|
461
|
-
});
|
|
462
|
-
if (!sameRoot) {
|
|
463
|
-
return childStats.reduce<MapToolStats>(
|
|
464
|
-
(acc, { stats }) => addStats(acc, stats),
|
|
465
|
-
[0, 0, 0, 0, 0],
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const unconditionalStats = childStats
|
|
470
|
-
.filter(
|
|
471
|
-
({ step }) =>
|
|
472
|
-
!step.conditional &&
|
|
473
|
-
!(step.type === 'control_flow' && step.kind === 'conditional'),
|
|
474
|
-
)
|
|
475
|
-
.reduce<MapToolStats>(
|
|
476
|
-
(acc, { stats }) => addStats(acc, stats),
|
|
477
|
-
[0, 0, 0, 0, 0],
|
|
478
|
-
);
|
|
479
|
-
const conditionalStats = conditionalChildren.reduce<MapToolStats>(
|
|
480
|
-
(acc, { stats }) => addStats(acc, stats),
|
|
481
|
-
[0, 0, 0, 0, 0],
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
return addStats(unconditionalStats, conditionalStats);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function countProducerTools(
|
|
488
|
-
producers: readonly PlayStaticColumnProducer[],
|
|
489
|
-
): MapToolStats {
|
|
490
|
-
let stats: MapToolStats = [0, 0, 0, 0, 0];
|
|
491
|
-
|
|
492
|
-
for (const producer of producers) {
|
|
493
|
-
if (producer.kind === 'tool' && producer.toolId) {
|
|
494
|
-
stats = addStats(
|
|
495
|
-
stats,
|
|
496
|
-
countTool(producer.toolId, {
|
|
497
|
-
staticSingleBucket: hasStaticSingleBatchBucket({
|
|
498
|
-
toolId: producer.toolId,
|
|
499
|
-
paramsSource:
|
|
500
|
-
producer.substep.type === 'tool'
|
|
501
|
-
? producer.substep.paramsSource
|
|
502
|
-
: undefined,
|
|
503
|
-
sourceText:
|
|
504
|
-
producer.substep.type === 'tool'
|
|
505
|
-
? producer.substep.sourceText
|
|
506
|
-
: undefined,
|
|
507
|
-
}),
|
|
508
|
-
}),
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
|
-
if (producer.substep.type === 'waterfall') {
|
|
512
|
-
stats = addStats(stats, countToolSubstep(producer.substep));
|
|
513
|
-
}
|
|
514
|
-
if (producer.steps?.length) {
|
|
515
|
-
stats = addStats(stats, countProducerTools(producer.steps));
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return stats;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
function datasetBelongsToPlanMap(
|
|
523
|
-
dataset: Extract<PlayStaticSubstep, { type: 'dataset' }>,
|
|
524
|
-
planMap: ChunkSizingMap,
|
|
525
|
-
mapName: string,
|
|
526
|
-
): boolean {
|
|
527
|
-
return [dataset.name, dataset.tableNamespace, dataset.field].some(
|
|
528
|
-
(datasetName) =>
|
|
529
|
-
typeof datasetName === 'string' &&
|
|
530
|
-
datasetName.length > 0 &&
|
|
531
|
-
(datasetName === planMap.mapName ||
|
|
532
|
-
datasetName === planMap.tableNamespace ||
|
|
533
|
-
datasetName === mapName),
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function countMapLocalTools(input: {
|
|
538
|
-
staticPipeline: PlayStaticPipeline | null | undefined;
|
|
539
|
-
planMap: ChunkSizingMap | null | undefined;
|
|
540
|
-
mapName: string;
|
|
541
|
-
}): MapToolStats | null {
|
|
542
|
-
if (!input.staticPipeline || !input.planMap) return null;
|
|
543
|
-
const planMap = input.planMap;
|
|
544
|
-
const dataset = flattenStaticSubsteps(
|
|
545
|
-
getTopLevelPipelineSubsteps(input.staticPipeline),
|
|
546
|
-
).find(
|
|
547
|
-
(substep): substep is Extract<PlayStaticSubstep, { type: 'dataset' }> =>
|
|
548
|
-
substep.type === 'dataset' &&
|
|
549
|
-
datasetBelongsToPlanMap(substep, planMap, input.mapName),
|
|
550
|
-
);
|
|
551
|
-
if (!dataset) return null;
|
|
552
|
-
|
|
553
|
-
if (dataset.steps?.length) return countToolSubsteps(dataset.steps);
|
|
554
|
-
if (dataset.columns?.length) {
|
|
555
|
-
return countProducerTools(
|
|
556
|
-
dataset.columns.flatMap((column) => column.producers),
|
|
557
|
-
);
|
|
558
|
-
}
|
|
559
|
-
return [0, 0, 0, 0, 0];
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function countMapLocalControlWork(input: {
|
|
563
|
-
staticPipeline: PlayStaticPipeline | null | undefined;
|
|
564
|
-
planMap: ChunkSizingMap | null | undefined;
|
|
565
|
-
mapName: string;
|
|
566
|
-
}): MapControlWorkStats | null {
|
|
567
|
-
if (!input.staticPipeline || !input.planMap) return null;
|
|
568
|
-
const planMap = input.planMap;
|
|
569
|
-
const dataset = flattenStaticSubsteps(
|
|
570
|
-
getTopLevelPipelineSubsteps(input.staticPipeline),
|
|
571
|
-
).find(
|
|
572
|
-
(substep): substep is Extract<PlayStaticSubstep, { type: 'dataset' }> =>
|
|
573
|
-
substep.type === 'dataset' &&
|
|
574
|
-
datasetBelongsToPlanMap(substep, planMap, input.mapName),
|
|
575
|
-
);
|
|
576
|
-
if (!dataset) return null;
|
|
577
|
-
|
|
578
|
-
if (dataset.steps?.length) return countControlWorkSubsteps(dataset.steps);
|
|
579
|
-
if (dataset.columns?.length) {
|
|
580
|
-
return dataset.columns
|
|
581
|
-
.flatMap((column) => column.producers)
|
|
582
|
-
.reduce<MapControlWorkStats>(
|
|
583
|
-
(acc, producer) =>
|
|
584
|
-
addControlWorkStats(acc, countControlWorkSubstep(producer.substep)),
|
|
585
|
-
EMPTY_CONTROL_WORK_STATS,
|
|
586
|
-
);
|
|
587
|
-
}
|
|
588
|
-
return EMPTY_CONTROL_WORK_STATS;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function countFallbackDeclarationTools(input: {
|
|
592
|
-
declarations: readonly { toolId: string; field?: string | null }[];
|
|
593
|
-
outputFields: readonly string[];
|
|
594
|
-
externalStepFields?: readonly string[];
|
|
595
|
-
}): MapToolStats {
|
|
596
|
-
let stats: MapToolStats = [0, 0, 0, 0, 0];
|
|
597
|
-
|
|
598
|
-
for (const declaration of input.declarations) {
|
|
599
|
-
if (
|
|
600
|
-
!declarationBelongsToMapOutput(
|
|
601
|
-
declaration,
|
|
602
|
-
input.outputFields,
|
|
603
|
-
input.externalStepFields ?? [],
|
|
604
|
-
)
|
|
605
|
-
) {
|
|
606
|
-
continue;
|
|
607
|
-
}
|
|
608
|
-
stats = addStats(stats, countTool(declaration.toolId));
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
return stats;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
function countDeclarationTools(
|
|
615
|
-
declarations: readonly { toolId: string; field?: string | null }[],
|
|
616
|
-
): MapToolStats {
|
|
617
|
-
return declarations.reduce<MapToolStats>(
|
|
618
|
-
(stats, declaration) => addStats(stats, countTool(declaration.toolId)),
|
|
619
|
-
[0, 0, 0, 0, 0],
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function countNestedExecutionSteps(
|
|
624
|
-
substeps: readonly PlayStaticSubstep[] | undefined,
|
|
625
|
-
): number {
|
|
626
|
-
let stepsPerChunk = 1;
|
|
627
|
-
for (const substep of substeps ?? []) {
|
|
628
|
-
if (substep.type === 'waterfall') {
|
|
629
|
-
stepsPerChunk = Math.max(stepsPerChunk, substep.steps?.length ?? 1);
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
|
-
if (substep.type === 'step_suite') {
|
|
633
|
-
stepsPerChunk = Math.max(stepsPerChunk, substep.steps.length);
|
|
634
|
-
continue;
|
|
635
|
-
}
|
|
636
|
-
if (substep.type === 'control_flow' || substep.type === 'dataset') {
|
|
637
|
-
stepsPerChunk = Math.max(
|
|
638
|
-
stepsPerChunk,
|
|
639
|
-
countNestedExecutionSteps(substep.steps),
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return stepsPerChunk;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function buildStaticChunkSizingPlan(
|
|
647
|
-
staticPipeline: PlayStaticPipeline | null,
|
|
648
|
-
): ChunkSizingPlan | null {
|
|
649
|
-
if (!staticPipeline) return null;
|
|
650
|
-
const datasets = flattenStaticSubsteps(
|
|
651
|
-
getTopLevelPipelineSubsteps(staticPipeline),
|
|
652
|
-
).filter(
|
|
653
|
-
(substep): substep is Extract<PlayStaticSubstep, { type: 'dataset' }> =>
|
|
654
|
-
substep.type === 'dataset',
|
|
655
|
-
);
|
|
656
|
-
if (datasets.length === 0) return null;
|
|
657
|
-
return {
|
|
658
|
-
maps: datasets.map((dataset) => {
|
|
659
|
-
const stepsPerChunk = countNestedExecutionSteps(dataset.steps);
|
|
660
|
-
return {
|
|
661
|
-
mapName: dataset.name ?? dataset.field,
|
|
662
|
-
tableNamespace: dataset.tableNamespace ?? dataset.field,
|
|
663
|
-
outputFields: dataset.outputFields ?? [],
|
|
664
|
-
defaultChunkSize: stepsPerChunk > 1 ? 1_000 : 5_000,
|
|
665
|
-
stepsPerChunk,
|
|
666
|
-
};
|
|
667
|
-
}),
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
export function chooseWorkerMapRowsPerChunk(
|
|
672
|
-
input: WorkerMapChunkPlanInput,
|
|
673
|
-
): number {
|
|
674
|
-
return chooseWorkerMapDispatchPlan(input).rowsPerChunk;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
export function chooseWorkerMapDispatchPlan(
|
|
678
|
-
input: WorkerMapChunkPlanInput,
|
|
679
|
-
): WorkerMapDispatchPlan {
|
|
680
|
-
const plan: ChunkSizingPlan | null = input.executionPlan
|
|
681
|
-
? {
|
|
682
|
-
maps: input.executionPlan.maps,
|
|
683
|
-
toolDeclarations: input.executionPlan.toolDeclarations,
|
|
684
|
-
softWorkflowStepBudget:
|
|
685
|
-
input.executionPlan.chunkPlan.softWorkflowStepBudget,
|
|
686
|
-
}
|
|
687
|
-
: buildStaticChunkSizingPlan(input.staticPipeline ?? null);
|
|
688
|
-
const planMap = plan?.maps.find(
|
|
689
|
-
(candidate) =>
|
|
690
|
-
candidate.mapName === input.mapName ||
|
|
691
|
-
candidate.tableNamespace === input.mapName,
|
|
692
|
-
);
|
|
693
|
-
const staticMapToolStats = countMapLocalTools({
|
|
694
|
-
staticPipeline: input.staticPipeline,
|
|
695
|
-
planMap,
|
|
696
|
-
mapName: input.mapName,
|
|
697
|
-
});
|
|
698
|
-
const staticMapControlWorkStats = countMapLocalControlWork({
|
|
699
|
-
staticPipeline: input.staticPipeline,
|
|
700
|
-
planMap,
|
|
701
|
-
mapName: input.mapName,
|
|
702
|
-
});
|
|
703
|
-
const mapToolStats =
|
|
704
|
-
staticMapToolStats ??
|
|
705
|
-
(plan && planMap
|
|
706
|
-
? countFallbackDeclarationTools({
|
|
707
|
-
declarations: plan.toolDeclarations ?? [],
|
|
708
|
-
outputFields: planMap.outputFields,
|
|
709
|
-
externalStepFields: planMap.externalStepFields ?? [],
|
|
710
|
-
})
|
|
711
|
-
: plan
|
|
712
|
-
? countDeclarationTools(plan.toolDeclarations ?? [])
|
|
713
|
-
: [0, 0, 0, 0, 0]);
|
|
714
|
-
const estimatedExternalStepsPerRow = estimateExternalStepsForMap({
|
|
715
|
-
planMap,
|
|
716
|
-
mapToolStats,
|
|
717
|
-
});
|
|
718
|
-
const workEstimate = estimateMapWork({
|
|
719
|
-
estimatedExternalStepsPerRow,
|
|
720
|
-
mapToolStats,
|
|
721
|
-
controlWorkStats: staticMapControlWorkStats ?? EMPTY_CONTROL_WORK_STATS,
|
|
722
|
-
});
|
|
723
|
-
const controlSubrequestsPerRow =
|
|
724
|
-
workEstimate.childPlaySubmitsPerRow + workEstimate.eventWaitsPerRow;
|
|
725
|
-
const allProviderCallsAreCoalescedBatchable =
|
|
726
|
-
workEstimate.providerToolCallsPerRow > 0 &&
|
|
727
|
-
workEstimate.unbatchedProviderToolCallsPerRow === 0 &&
|
|
728
|
-
workEstimate.coalescedBatchableProviderToolCallsPerRow >=
|
|
729
|
-
workEstimate.providerToolCallsPerRow &&
|
|
730
|
-
controlSubrequestsPerRow === 0;
|
|
731
|
-
const preferredChunkSize = allProviderCallsAreCoalescedBatchable
|
|
732
|
-
? Math.max(
|
|
733
|
-
planMap?.defaultChunkSize ?? 0,
|
|
734
|
-
EXECUTION_PLAN_DEFAULTS.largeMapChunkSize,
|
|
735
|
-
)
|
|
736
|
-
: planMap?.defaultChunkSize;
|
|
737
|
-
const rowsPerChunk = chooseMapChunkSize({
|
|
738
|
-
totalRows: input.rowCountHint,
|
|
739
|
-
mapCount: Math.max(1, plan?.maps.length ?? 1),
|
|
740
|
-
stepsPerChunk:
|
|
741
|
-
estimatedExternalStepsPerRow > 0
|
|
742
|
-
? Math.max(estimatedExternalStepsPerRow, planMap?.stepsPerChunk ?? 1)
|
|
743
|
-
: 1,
|
|
744
|
-
preferredChunkSize,
|
|
745
|
-
softWorkflowStepBudget: plan?.softWorkflowStepBudget,
|
|
746
|
-
});
|
|
747
|
-
if (estimatedExternalStepsPerRow > 0 || controlSubrequestsPerRow > 0) {
|
|
748
|
-
const subrequestSafeRows = subrequestSafeRowsForMap({
|
|
749
|
-
planMap,
|
|
750
|
-
mapToolStats,
|
|
751
|
-
staticMapToolStats,
|
|
752
|
-
estimatedExternalStepsPerRow,
|
|
753
|
-
workEstimate,
|
|
754
|
-
});
|
|
755
|
-
if (subrequestSafeRows < rowsPerChunk) {
|
|
756
|
-
const externalPressurePlan: WorkerMapDispatchPlan = {
|
|
757
|
-
rowsPerChunk: subrequestSafeRows,
|
|
758
|
-
estimatedExternalStepsPerRow,
|
|
759
|
-
workEstimate,
|
|
760
|
-
reason: 'external-pressure',
|
|
761
|
-
};
|
|
762
|
-
return externalPressurePlan;
|
|
763
|
-
}
|
|
764
|
-
return {
|
|
765
|
-
rowsPerChunk,
|
|
766
|
-
estimatedExternalStepsPerRow,
|
|
767
|
-
workEstimate,
|
|
768
|
-
reason: 'external-pressure',
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const toolFreeMap = !!planMap && mapToolStats[0] === 0;
|
|
773
|
-
const toolFreeSimpleMap = toolFreeMap && planMap.stepsPerChunk === 1;
|
|
774
|
-
if (
|
|
775
|
-
toolFreeSimpleMap &&
|
|
776
|
-
(input.rowCountHint === null ||
|
|
777
|
-
input.rowCountHint >= CACHE_ENABLED_SIMPLE_MAP_CHUNK_SIZE)
|
|
778
|
-
) {
|
|
779
|
-
const largeRowsPerChunk = Math.max(
|
|
780
|
-
rowsPerChunk,
|
|
781
|
-
CACHE_ENABLED_SIMPLE_MAP_CHUNK_SIZE,
|
|
782
|
-
);
|
|
783
|
-
return {
|
|
784
|
-
rowsPerChunk: largeRowsPerChunk,
|
|
785
|
-
estimatedExternalStepsPerRow: 0,
|
|
786
|
-
workEstimate,
|
|
787
|
-
reason: 'tool-free-large-cache',
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
return {
|
|
792
|
-
rowsPerChunk,
|
|
793
|
-
estimatedExternalStepsPerRow: 0,
|
|
794
|
-
workEstimate,
|
|
795
|
-
reason: toolFreeMap ? 'tool-free-plan' : 'default-plan',
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
function estimateMapWork(input: {
|
|
800
|
-
estimatedExternalStepsPerRow: number;
|
|
801
|
-
mapToolStats: MapToolStats;
|
|
802
|
-
controlWorkStats: MapControlWorkStats;
|
|
803
|
-
}): WorkerMapWorkEstimate {
|
|
804
|
-
return {
|
|
805
|
-
cpuRowsPerRow: 1,
|
|
806
|
-
runtimeSheetReadsPerChunk: 1,
|
|
807
|
-
runtimeSheetWritesPerChunk: 1,
|
|
808
|
-
durableReceiptOpsPerRow: input.estimatedExternalStepsPerRow,
|
|
809
|
-
providerToolCallsPerRow: input.mapToolStats[0],
|
|
810
|
-
unbatchedProviderToolCallsPerRow: input.mapToolStats[1],
|
|
811
|
-
assumedBatchSize: assumedBatchSizeFor(input.mapToolStats[3]),
|
|
812
|
-
coalescedBatchableProviderToolCallsPerRow: input.mapToolStats[4],
|
|
813
|
-
childPlaySubmitsPerRow: input.controlWorkStats.childPlaySubmits,
|
|
814
|
-
eventWaitsPerRow: input.controlWorkStats.eventWaits,
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
function estimateExternalStepsForMap(input: {
|
|
819
|
-
planMap: ChunkSizingMap | undefined;
|
|
820
|
-
mapToolStats: MapToolStats;
|
|
821
|
-
}): number {
|
|
822
|
-
if (!input.planMap) return input.mapToolStats[0];
|
|
823
|
-
const explicitExternalSteps = Array.isArray(input.planMap.externalStepFields)
|
|
824
|
-
? input.planMap.externalStepFields.length
|
|
825
|
-
: 0;
|
|
826
|
-
return Math.max(explicitExternalSteps, input.mapToolStats[0]);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
function subrequestSafeRowsForMap(input: {
|
|
830
|
-
planMap: ChunkSizingMap | undefined;
|
|
831
|
-
mapToolStats: MapToolStats;
|
|
832
|
-
staticMapToolStats: MapToolStats | null;
|
|
833
|
-
estimatedExternalStepsPerRow: number;
|
|
834
|
-
workEstimate: WorkerMapWorkEstimate;
|
|
835
|
-
}): number {
|
|
836
|
-
const hasToolWork = input.mapToolStats[0] > 0;
|
|
837
|
-
const unbatchedToolCount = input.mapToolStats[1];
|
|
838
|
-
const unbatchedSubrequestCount = input.mapToolStats[2];
|
|
839
|
-
const controlSubrequestsPerRow =
|
|
840
|
-
input.workEstimate.childPlaySubmitsPerRow +
|
|
841
|
-
input.workEstimate.eventWaitsPerRow;
|
|
842
|
-
const controlRows =
|
|
843
|
-
controlSubrequestsPerRow > 0
|
|
844
|
-
? Math.max(
|
|
845
|
-
1,
|
|
846
|
-
Math.floor(
|
|
847
|
-
(UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET -
|
|
848
|
-
input.workEstimate.runtimeSheetReadsPerChunk -
|
|
849
|
-
input.workEstimate.runtimeSheetWritesPerChunk) /
|
|
850
|
-
controlSubrequestsPerRow,
|
|
851
|
-
),
|
|
852
|
-
)
|
|
853
|
-
: Number.POSITIVE_INFINITY;
|
|
854
|
-
// Largest chunk whose batched tool work stays under the subrequest ceiling.
|
|
855
|
-
// Invert estimateBatchedToolChunkSubrequests. Static planning does not know
|
|
856
|
-
// bucket cardinality, so it assumes worst-case provider fanout (one compiled
|
|
857
|
-
// batch per row per batchable tool call) while keeping durable receipt RPCs
|
|
858
|
-
// fixed per drained tool group.
|
|
859
|
-
const batchableToolRows =
|
|
860
|
-
hasToolWork && unbatchedToolCount <= 0
|
|
861
|
-
? (() => {
|
|
862
|
-
const batchableToolCallsPerRow = input.mapToolStats[0];
|
|
863
|
-
const costForRows = (rows: number) =>
|
|
864
|
-
estimateBatchedToolChunkSubrequests({
|
|
865
|
-
batchableToolCallsPerRow,
|
|
866
|
-
coalescedBatchableToolCallsPerRow:
|
|
867
|
-
input.workEstimate.coalescedBatchableProviderToolCallsPerRow,
|
|
868
|
-
rows,
|
|
869
|
-
assumedBatchSize: input.workEstimate.assumedBatchSize,
|
|
870
|
-
}) +
|
|
871
|
-
input.workEstimate.runtimeSheetReadsPerChunk +
|
|
872
|
-
input.workEstimate.runtimeSheetWritesPerChunk;
|
|
873
|
-
if (costForRows(1) > UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET) {
|
|
874
|
-
return 1;
|
|
875
|
-
}
|
|
876
|
-
let low = 1;
|
|
877
|
-
let high = MAX_BATCHABLE_SUBREQUEST_SAFE_ROWS;
|
|
878
|
-
while (low < high) {
|
|
879
|
-
const mid = Math.ceil((low + high) / 2);
|
|
880
|
-
if (
|
|
881
|
-
costForRows(mid) <= UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET
|
|
882
|
-
) {
|
|
883
|
-
low = mid;
|
|
884
|
-
} else {
|
|
885
|
-
high = mid - 1;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
return low;
|
|
889
|
-
})()
|
|
890
|
-
: Number.POSITIVE_INFINITY;
|
|
891
|
-
if (hasToolWork && unbatchedToolCount <= 0) {
|
|
892
|
-
return Math.min(batchableToolRows, controlRows);
|
|
893
|
-
}
|
|
894
|
-
const genericRows =
|
|
895
|
-
input.estimatedExternalStepsPerRow > 0
|
|
896
|
-
? Math.max(
|
|
897
|
-
1,
|
|
898
|
-
Math.floor(
|
|
899
|
-
WORKER_SUBREQUEST_SAFE_TOOL_CALLS_PER_CHUNK /
|
|
900
|
-
input.estimatedExternalStepsPerRow,
|
|
901
|
-
),
|
|
902
|
-
)
|
|
903
|
-
: Number.POSITIVE_INFINITY;
|
|
904
|
-
if (unbatchedToolCount <= 0) return Math.min(genericRows, controlRows);
|
|
905
|
-
const unbatchedSubrequestsPerRow =
|
|
906
|
-
input.staticMapToolStats !== null
|
|
907
|
-
? unbatchedSubrequestCount
|
|
908
|
-
: Math.max(
|
|
909
|
-
(input.planMap?.stepsPerChunk ?? 1) *
|
|
910
|
-
SUBREQUESTS_PER_UNBATCHED_TOOL_CALL,
|
|
911
|
-
unbatchedSubrequestCount,
|
|
912
|
-
);
|
|
913
|
-
const unbatchedRows = Math.max(
|
|
914
|
-
1,
|
|
915
|
-
Math.floor(
|
|
916
|
-
UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET / unbatchedSubrequestsPerRow,
|
|
917
|
-
),
|
|
918
|
-
);
|
|
919
|
-
return Math.min(unbatchedRows, controlRows);
|
|
920
|
-
}
|