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.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +5 -25
  3. package/dist/bundling-sources/sdk/src/release.ts +23 -21
  4. package/dist/bundling-sources/sdk/src/types.ts +1 -1
  5. package/dist/bundling-sources/shared_libs/play-runtime/backend.ts +2 -21
  6. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -6
  7. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +6 -13
  8. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +9 -26
  9. package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +1 -2
  10. package/dist/bundling-sources/shared_libs/play-runtime/map-row-identity.ts +1 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +191 -29
  12. package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +1 -1
  13. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +1 -36
  14. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +2 -3
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +0 -7
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +1 -12
  17. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +2 -3
  18. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver.ts +5 -15
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +6 -21
  20. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +3 -4
  21. package/dist/bundling-sources/shared_libs/plays/artifact-kind-guard.ts +16 -29
  22. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +4 -8
  23. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +21 -712
  24. package/dist/bundling-sources/shared_libs/plays/bundling/limits.ts +1 -18
  25. package/dist/cli/index.js +29 -71
  26. package/dist/cli/index.mjs +29 -71
  27. package/dist/{compiler-manifest-CYcwzSOJ.d.ts → compiler-manifest-DlE7dnRm.d.mts} +2 -8
  28. package/dist/{compiler-manifest-CYcwzSOJ.d.mts → compiler-manifest-DlE7dnRm.d.ts} +2 -8
  29. package/dist/index.d.mts +2 -2
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +24 -22
  32. package/dist/index.mjs +24 -22
  33. package/dist/plays/bundle-play-file.d.mts +7 -22
  34. package/dist/plays/bundle-play-file.d.ts +7 -22
  35. package/dist/plays/bundle-play-file.mjs +26 -614
  36. package/package.json +2 -2
  37. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +0 -4504
  38. package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +0 -1386
  39. package/dist/bundling-sources/apps/play-runner-workers/src/durable-object-deploy-handoff.ts +0 -30
  40. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +0 -8385
  41. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +0 -21
  42. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/batching.ts +0 -251
  43. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/coordinator-progress.ts +0 -289
  44. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/csv-rows.ts +0 -85
  45. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/customer-console.ts +0 -23
  46. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/dataset-handles.ts +0 -528
  47. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +0 -229
  48. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/ledger-event-batches.ts +0 -81
  49. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/live-progress.ts +0 -22
  50. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +0 -920
  51. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-latency-profile.ts +0 -90
  52. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +0 -475
  53. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +0 -570
  54. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/result-dataset-persistence.ts +0 -50
  55. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/row-isolation.ts +0 -5
  56. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +0 -519
  57. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -107
  58. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +0 -2541
  59. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-http-errors.ts +0 -7
  60. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +0 -227
  61. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +0 -130
  62. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +0 -39
  63. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/workflow-preview.ts +0 -26
  64. package/dist/bundling-sources/apps/play-runner-workers/src/runtime-api-retry.ts +0 -23
  65. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-instance-create.ts +0 -47
  66. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +0 -167
  67. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry.ts +0 -91
  68. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +0 -329
  69. package/dist/bundling-sources/sdk/src/worker-play-entry.ts +0 -240
  70. package/dist/bundling-sources/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +0 -252
  71. 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
- }