deepline 0.1.170 → 0.1.172

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 CHANGED
@@ -195,7 +195,7 @@ Full sequence from zero to hitting + surviving rate limits via v2 plays.
195
195
 
196
196
  ```bash
197
197
  # Production
198
- bash <(curl -sS https://code.deepline.com/api/v2/sdk/install)
198
+ bash <(curl -fsSL https://deepline.com/api/v2/sdk/install)
199
199
 
200
200
  # Local dev (no install needed)
201
201
  bun run dev # start server
@@ -1719,7 +1719,10 @@ class RuntimeReceiptPersistenceError extends Error {
1719
1719
  function isRuntimeReceiptPersistenceError(error: unknown): boolean {
1720
1720
  if (!error || typeof error !== 'object') return false;
1721
1721
  if (error instanceof RuntimeReceiptPersistenceError) return true;
1722
- if (error instanceof Error && error.name === 'RuntimeReceiptPersistenceError') {
1722
+ if (
1723
+ error instanceof Error &&
1724
+ error.name === 'RuntimeReceiptPersistenceError'
1725
+ ) {
1723
1726
  return true;
1724
1727
  }
1725
1728
  const nestedErrors = (error as { errors?: unknown }).errors;
@@ -2559,8 +2562,7 @@ class WorkerToolBatchScheduler {
2559
2562
  { id: request.id, toolId, input: request.input },
2560
2563
  request.workflowStep,
2561
2564
  this.callbacks,
2562
- (retryAfterMs) =>
2563
- this.reportBackpressure(toolId, retryAfterMs),
2565
+ (retryAfterMs) => this.reportBackpressure(toolId, retryAfterMs),
2564
2566
  () => this.governor.chargeBudget('retry'),
2565
2567
  toolContract?.retrySafeTransientHttp === true,
2566
2568
  this.abortSignal,
@@ -4529,9 +4531,12 @@ function createMinimalWorkerCtx(
4529
4531
  const executeWithRuntimeReceipt = async <T>(
4530
4532
  key: string,
4531
4533
  execute: () => Promise<T> | T,
4532
- repairRunningReceiptForSameRun = false,
4533
- reclaimRunning = false,
4534
- repairRunningReceiptForSameRunAfterWaitTimeout = false,
4534
+ options: {
4535
+ repairRunningReceiptForSameRun?: boolean;
4536
+ reclaimRunning?: boolean;
4537
+ repairRunningReceiptForSameRunAfterWaitTimeout?: boolean;
4538
+ runningReceiptWaitMaxAttempts?: number;
4539
+ } = {},
4535
4540
  ): Promise<T> => {
4536
4541
  const serialized = await runWorkerRuntimeReceiptBoundary<unknown>({
4537
4542
  orgId: req.orgId,
@@ -4540,9 +4545,12 @@ function createMinimalWorkerCtx(
4540
4545
  key,
4541
4546
  receiptStore,
4542
4547
  execute: async () => serializeDurableStepValue(await execute()),
4543
- repairRunningReceiptForSameRun,
4544
- reclaimRunning,
4545
- repairRunningReceiptForSameRunAfterWaitTimeout,
4548
+ repairRunningReceiptForSameRun:
4549
+ options.repairRunningReceiptForSameRun ?? false,
4550
+ reclaimRunning: options.reclaimRunning ?? false,
4551
+ repairRunningReceiptForSameRunAfterWaitTimeout:
4552
+ options.repairRunningReceiptForSameRunAfterWaitTimeout ?? false,
4553
+ runningReceiptWaitMaxAttempts: options.runningReceiptWaitMaxAttempts,
4546
4554
  });
4547
4555
  return deserializeDurableStepValue(serialized) as T;
4548
4556
  };
@@ -4553,19 +4561,15 @@ function createMinimalWorkerCtx(
4553
4561
  if (!workflowStep) {
4554
4562
  return await executeWithRuntimeReceipt(name, execute);
4555
4563
  }
4556
- return await executeWithRuntimeReceipt(
4557
- name,
4558
- async () => {
4559
- const serialized = await (
4560
- workflowStep.do as unknown as (
4561
- name: string,
4562
- callback: () => Promise<unknown>,
4563
- ) => Promise<unknown>
4564
- )(name, async () => serializeDurableStepValue(await execute()));
4565
- return deserializeDurableStepValue(serialized) as T;
4566
- },
4567
- false,
4568
- );
4564
+ return await executeWithRuntimeReceipt(name, async () => {
4565
+ const serialized = await (
4566
+ workflowStep.do as unknown as (
4567
+ name: string,
4568
+ callback: () => Promise<unknown>,
4569
+ ) => Promise<unknown>
4570
+ )(name, async () => serializeDurableStepValue(await execute()));
4571
+ return deserializeDurableStepValue(serialized) as T;
4572
+ });
4569
4573
  };
4570
4574
  const nextCtxStepReceiptKey = (
4571
4575
  name: string,
@@ -4648,7 +4652,6 @@ function createMinimalWorkerCtx(
4648
4652
  nodeId: mapNodeId,
4649
4653
  progress: {
4650
4654
  artifactTableNamespace: name,
4651
- failed: 0,
4652
4655
  ...progress,
4653
4656
  updatedAt: progress.updatedAt ?? nowMs(),
4654
4657
  },
@@ -5032,6 +5035,7 @@ function createMinimalWorkerCtx(
5032
5035
  {
5033
5036
  completed,
5034
5037
  total: progressTotalRows,
5038
+ failed: failedExecutedRows,
5035
5039
  startedAt: mapStartedAt,
5036
5040
  message: formatMapProgressMessage(completed, progressTotalRows),
5037
5041
  },
@@ -5076,7 +5080,7 @@ function createMinimalWorkerCtx(
5076
5080
  let stepCellsCompleted = 0;
5077
5081
  let stepCellsSkipped = 0;
5078
5082
  const generatedOutputFields = new Set<string>();
5079
- const pendingLiveRowUpdates: Array<
5083
+ const pendingLiveUpdates: Array<
5080
5084
  Omit<PlayRowUpdate, 'rowId'> & { runId?: string }
5081
5085
  > = [];
5082
5086
  const persistedExecutedIndexes = new Set<number>();
@@ -5086,9 +5090,9 @@ function createMinimalWorkerCtx(
5086
5090
  let scheduledPersistTimer: ReturnType<typeof setTimeout> | null = null;
5087
5091
  let persistFlushChain: Promise<void> = Promise.resolve();
5088
5092
  let persistFailure: unknown = null;
5089
- let scheduledLiveUpdateTimer: ReturnType<typeof setTimeout> | null = null;
5090
- let liveUpdateFlushChain: Promise<void> = Promise.resolve();
5091
- let liveUpdateFailureCount = 0;
5093
+ let liveUpdateTimer: ReturnType<typeof setTimeout> | null = null;
5094
+ let liveFlushChain: Promise<void> = Promise.resolve();
5095
+ let liveUpdateFailures = 0;
5092
5096
 
5093
5097
  const clearScheduledPersistTimer = () => {
5094
5098
  if (scheduledPersistTimer) {
@@ -5097,10 +5101,10 @@ function createMinimalWorkerCtx(
5097
5101
  }
5098
5102
  };
5099
5103
 
5100
- const clearScheduledLiveUpdateTimer = () => {
5101
- if (scheduledLiveUpdateTimer) {
5102
- clearTimeout(scheduledLiveUpdateTimer);
5103
- scheduledLiveUpdateTimer = null;
5104
+ const clearLiveUpdateTimer = () => {
5105
+ if (liveUpdateTimer) {
5106
+ clearTimeout(liveUpdateTimer);
5107
+ liveUpdateTimer = null;
5104
5108
  }
5105
5109
  };
5106
5110
 
@@ -5218,14 +5222,36 @@ function createMinimalWorkerCtx(
5218
5222
  return task;
5219
5223
  };
5220
5224
 
5225
+ const coalesceLiveUpdates = (
5226
+ updates: Array<Omit<PlayRowUpdate, 'rowId'> & { runId?: string }>,
5227
+ ): Array<Omit<PlayRowUpdate, 'rowId'> & { runId?: string }> => {
5228
+ const mergedByKey = new Map<
5229
+ string,
5230
+ Omit<PlayRowUpdate, 'rowId'> & { runId?: string }
5231
+ >();
5232
+ for (const update of updates) {
5233
+ const existing = mergedByKey.get(update.key);
5234
+ mergedByKey.set(update.key, {
5235
+ ...existing,
5236
+ ...update,
5237
+ dataPatch: { ...existing?.dataPatch, ...update.dataPatch },
5238
+ cellMetaPatch: {
5239
+ ...existing?.cellMetaPatch,
5240
+ ...update.cellMetaPatch,
5241
+ },
5242
+ });
5243
+ }
5244
+ return [...mergedByKey.values()];
5245
+ };
5246
+
5221
5247
  const flushLiveRowUpdates = (): Promise<void> => {
5222
- clearScheduledLiveUpdateTimer();
5223
- if (pendingLiveRowUpdates.length === 0) {
5224
- return liveUpdateFlushChain;
5248
+ clearLiveUpdateTimer();
5249
+ if (pendingLiveUpdates.length === 0) {
5250
+ return liveFlushChain;
5225
5251
  }
5226
- const updates = pendingLiveRowUpdates.splice(0);
5252
+ const updates = coalesceLiveUpdates(pendingLiveUpdates.splice(0));
5227
5253
  const extraOutputFields = Array.from(generatedOutputFields);
5228
- const task = liveUpdateFlushChain.then(async () => {
5254
+ const task = liveFlushChain.then(async () => {
5229
5255
  try {
5230
5256
  await applyLiveMapRowUpdates({
5231
5257
  req,
@@ -5235,33 +5261,29 @@ function createMinimalWorkerCtx(
5235
5261
  updates,
5236
5262
  });
5237
5263
  } catch (error) {
5238
- liveUpdateFailureCount += 1;
5239
- if (liveUpdateFailureCount <= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
5264
+ liveUpdateFailures += 1;
5265
+ if (liveUpdateFailures <= MAP_ROW_FAILURE_SAMPLE_LIMIT) {
5240
5266
  emitEvent({
5241
5267
  type: 'log',
5242
5268
  level: 'warn',
5243
- message:
5244
- `Live row update flush failed for ctx.dataset("${name}") ` +
5245
- `(non-fatal; terminal rows still persist): ${formatWorkerRowFailureMessage(error)}`,
5269
+ message: formatWorkerRowFailureMessage(error),
5246
5270
  ts: nowMs(),
5247
5271
  });
5248
5272
  }
5249
5273
  }
5250
5274
  });
5251
- liveUpdateFlushChain = task.catch(() => undefined);
5275
+ liveFlushChain = task.catch(() => undefined);
5252
5276
  return task;
5253
5277
  };
5254
5278
 
5255
5279
  const scheduleLiveRowUpdates = () => {
5256
- if (
5257
- pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5258
- ) {
5280
+ if (pendingLiveUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS) {
5259
5281
  void flushLiveRowUpdates().catch(() => undefined);
5260
5282
  return;
5261
5283
  }
5262
- if (scheduledLiveUpdateTimer) return;
5263
- scheduledLiveUpdateTimer = setTimeout(() => {
5264
- scheduledLiveUpdateTimer = null;
5284
+ if (liveUpdateTimer) return;
5285
+ liveUpdateTimer = setTimeout(() => {
5286
+ liveUpdateTimer = null;
5265
5287
  void flushLiveRowUpdates().catch(() => undefined);
5266
5288
  }, MAP_INCREMENTAL_PERSIST_INTERVAL_MS);
5267
5289
  };
@@ -5269,10 +5291,8 @@ function createMinimalWorkerCtx(
5269
5291
  const enqueueLiveRowUpdate = (
5270
5292
  update: Omit<PlayRowUpdate, 'rowId'> & { runId?: string },
5271
5293
  ): Promise<void> => {
5272
- pendingLiveRowUpdates.push(update);
5273
- if (
5274
- pendingLiveRowUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS
5275
- ) {
5294
+ pendingLiveUpdates.push(update);
5295
+ if (pendingLiveUpdates.length >= MAP_LIVE_UPDATE_FLUSH_CHUNK_ROWS) {
5276
5296
  void flushLiveRowUpdates().catch(() => undefined);
5277
5297
  return Promise.resolve();
5278
5298
  }
@@ -5397,10 +5417,6 @@ function createMinimalWorkerCtx(
5397
5417
  else stepCellsCompleted += 1;
5398
5418
  await enqueueLiveRowUpdate({
5399
5419
  key: entry.rowKey,
5400
- tableNamespace: name,
5401
- runId: req.runId,
5402
- status: 'running',
5403
- stage: stepOutput.stepId,
5404
5420
  dataPatch: {
5405
5421
  [stepOutput.columnName]: stepOutput.value,
5406
5422
  },
@@ -5431,6 +5447,12 @@ function createMinimalWorkerCtx(
5431
5447
  completedAt: nowMs(),
5432
5448
  };
5433
5449
  }
5450
+ if (fieldEntries.length > 1) {
5451
+ await enqueueLiveRowUpdate({
5452
+ key: entry.rowKey,
5453
+ dataPatch: { [key]: resolved.value },
5454
+ });
5455
+ }
5434
5456
  activeField = null;
5435
5457
  }
5436
5458
  for (const stepOutput of stepProgramOutputs) {
@@ -5607,7 +5629,7 @@ function createMinimalWorkerCtx(
5607
5629
  });
5608
5630
  try {
5609
5631
  await flushLiveRowUpdates();
5610
- await liveUpdateFlushChain;
5632
+ await liveFlushChain;
5611
5633
  await enqueuePersistExecutedRows();
5612
5634
  await persistFlushChain;
5613
5635
  if (persistFailure) throw persistFailure;
@@ -6733,9 +6755,11 @@ function createMinimalWorkerCtx(
6733
6755
  childPlaySlot?.release();
6734
6756
  }
6735
6757
  },
6736
- false,
6737
- false,
6738
- true,
6758
+ {
6759
+ repairRunningReceiptForSameRunAfterWaitTimeout: true,
6760
+ runningReceiptWaitMaxAttempts:
6761
+ resolveRuntimeToolReceiptWaitMaxAttempts(input),
6762
+ },
6739
6763
  );
6740
6764
  },
6741
6765
  async fetch(
@@ -7857,7 +7881,9 @@ function extractMaxCreditsPerRun(contractSnapshot: unknown): number | null {
7857
7881
  }
7858
7882
 
7859
7883
  function shouldSkipWorkerComputeBilling(req: RunRequest): boolean {
7860
- return req.integrationMode === 'fixture' || req.integrationMode === 'eval_stub';
7884
+ return (
7885
+ req.integrationMode === 'fixture' || req.integrationMode === 'eval_stub'
7886
+ );
7861
7887
  }
7862
7888
 
7863
7889
  async function finalizeWorkerComputeBilling(input: {
@@ -17,11 +17,10 @@ export { TOOL_CALLING_MAP_CHUNK_SIZE };
17
17
  // Paid Cloudflare Workers support a much higher configured subrequest limit.
18
18
  // Keep a large buffer for coordinator/storage calls around row-level unbatched
19
19
  // tool RPCs, but avoid pathological one-row chunks for provider waterfalls.
20
- export const UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET = 500;
20
+ export const UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET = 200;
21
21
  // Fresh unbatched tool calls use one RUNTIME_API integration execute RPC and
22
22
  // one HARNESS durable-receipt completion RPC. Batch-cap rows by both.
23
23
  export const SUBREQUESTS_PER_UNBATCHED_TOOL_CALL = 2;
24
-
25
24
  export type WorkerMapChunkPlanInput = {
26
25
  mapName: string;
27
26
  rowCountHint: number | null;
@@ -45,7 +44,7 @@ type ChunkSizingPlan = {
45
44
  softWorkflowStepBudget?: number | null;
46
45
  };
47
46
 
48
- type MapToolStats = [totalToolCount: number, unbatchedToolCount: number];
47
+ type MapToolStats = [totalToolCount: number, unbatchedSubrequestCount: number];
49
48
 
50
49
  function fieldRoot(field: string | null | undefined): string {
51
50
  return String(field ?? '').split('.', 1)[0] ?? '';
@@ -70,41 +69,42 @@ function declarationBelongsToMapOutput(
70
69
  );
71
70
  }
72
71
 
73
- function isUnbatchedTool(toolId: string): boolean {
74
- return getPlayRuntimeBatchStrategy(toolId) === null;
72
+ function countTool(toolId: string): MapToolStats {
73
+ if (getPlayRuntimeBatchStrategy(toolId) !== null) return [1, 0];
74
+ return [1, toolId === 'test_high_single' ? 5 : 2];
75
+ }
76
+
77
+ function addStats(
78
+ [total, subrequests]: MapToolStats,
79
+ stats: MapToolStats,
80
+ ): MapToolStats {
81
+ return [total + stats[0], subrequests + stats[1]];
75
82
  }
76
83
 
77
84
  function countToolSubsteps(
78
85
  substeps: readonly PlayStaticSubstep[],
79
86
  ): MapToolStats {
80
- let totalToolCount = 0;
81
- let unbatchedToolCount = 0;
87
+ let stats: MapToolStats = [0, 0];
82
88
 
83
89
  for (const substep of substeps) {
84
- const [nestedTotal, nestedUnbatched] = countToolSubstep(substep);
85
- totalToolCount += nestedTotal;
86
- unbatchedToolCount += nestedUnbatched;
90
+ stats = addStats(stats, countToolSubstep(substep));
87
91
  }
88
92
 
89
- return [totalToolCount, unbatchedToolCount];
93
+ return stats;
90
94
  }
91
95
 
92
96
  function countToolSubstep(substep: PlayStaticSubstep): MapToolStats {
93
97
  if (substep.type === 'tool') {
94
- return [1, isUnbatchedTool(substep.toolId) ? 1 : 0];
98
+ return countTool(substep.toolId);
95
99
  }
96
100
 
97
101
  if (substep.type === 'waterfall') {
98
- let totalToolCount = 0;
99
- let unbatchedToolCount = 0;
102
+ let stats: MapToolStats = [0, 0];
100
103
  for (const step of substep.steps ?? []) {
101
104
  if (!step.toolId) continue;
102
- totalToolCount += 1;
103
- if (isUnbatchedTool(step.toolId)) {
104
- unbatchedToolCount += 1;
105
- }
105
+ stats = addStats(stats, countTool(step.toolId));
106
106
  }
107
- return [totalToolCount, unbatchedToolCount];
107
+ return stats;
108
108
  }
109
109
 
110
110
  if (substep.type === 'step_suite') {
@@ -117,9 +117,9 @@ function countToolSubstep(substep: PlayStaticSubstep): MapToolStats {
117
117
  countToolSubsteps(branch.steps),
118
118
  );
119
119
  return branchStats.reduce<MapToolStats>(
120
- ([totalMax, unbatchedMax], [total, unbatched]) => [
120
+ ([totalMax, subrequestsMax], [total, subrequests]) => [
121
121
  Math.max(totalMax, total),
122
- Math.max(unbatchedMax, unbatched),
122
+ Math.max(subrequestsMax, subrequests),
123
123
  ],
124
124
  [0, 0],
125
125
  );
@@ -151,9 +151,9 @@ function countStepSuiteTools(
151
151
  conditionalChildren.length < childStats.length / 2
152
152
  ) {
153
153
  return childStats.reduce<MapToolStats>(
154
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
154
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
155
155
  totalSum + total,
156
- unbatchedSum + unbatched,
156
+ subrequestsSum + subrequests,
157
157
  ],
158
158
  [0, 0],
159
159
  );
@@ -166,9 +166,9 @@ function countStepSuiteTools(
166
166
  });
167
167
  if (!sameRoot) {
168
168
  return childStats.reduce<MapToolStats>(
169
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
169
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
170
170
  totalSum + total,
171
- unbatchedSum + unbatched,
171
+ subrequestsSum + subrequests,
172
172
  ],
173
173
  [0, 0],
174
174
  );
@@ -181,52 +181,41 @@ function countStepSuiteTools(
181
181
  !(step.type === 'control_flow' && step.kind === 'conditional'),
182
182
  )
183
183
  .reduce<MapToolStats>(
184
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
184
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
185
185
  totalSum + total,
186
- unbatchedSum + unbatched,
186
+ subrequestsSum + subrequests,
187
187
  ],
188
188
  [0, 0],
189
189
  );
190
190
  const conditionalStats = conditionalChildren.reduce<MapToolStats>(
191
- ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
191
+ ([totalSum, subrequestsSum], { stats: [total, subrequests] }) => [
192
192
  totalSum + total,
193
- unbatchedSum + unbatched,
193
+ subrequestsSum + subrequests,
194
194
  ],
195
195
  [0, 0],
196
196
  );
197
197
 
198
- return [
199
- unconditionalStats[0] + conditionalStats[0],
200
- unconditionalStats[1] + conditionalStats[1],
201
- ];
198
+ return addStats(unconditionalStats, conditionalStats);
202
199
  }
203
200
 
204
201
  function countProducerTools(
205
202
  producers: readonly PlayStaticColumnProducer[],
206
203
  ): MapToolStats {
207
- let totalToolCount = 0;
208
- let unbatchedToolCount = 0;
204
+ let stats: MapToolStats = [0, 0];
209
205
 
210
206
  for (const producer of producers) {
211
207
  if (producer.kind === 'tool' && producer.toolId) {
212
- totalToolCount += 1;
213
- if (isUnbatchedTool(producer.toolId)) {
214
- unbatchedToolCount += 1;
215
- }
208
+ stats = addStats(stats, countTool(producer.toolId));
216
209
  }
217
210
  if (producer.substep.type === 'waterfall') {
218
- const [nestedTotal, nestedUnbatched] = countToolSubstep(producer.substep);
219
- totalToolCount += nestedTotal;
220
- unbatchedToolCount += nestedUnbatched;
211
+ stats = addStats(stats, countToolSubstep(producer.substep));
221
212
  }
222
213
  if (producer.steps?.length) {
223
- const [nestedTotal, nestedUnbatched] = countProducerTools(producer.steps);
224
- totalToolCount += nestedTotal;
225
- unbatchedToolCount += nestedUnbatched;
214
+ stats = addStats(stats, countProducerTools(producer.steps));
226
215
  }
227
216
  }
228
217
 
229
- return [totalToolCount, unbatchedToolCount];
218
+ return stats;
230
219
  }
231
220
 
232
221
  function datasetBelongsToPlanMap(
@@ -278,8 +267,7 @@ function countFallbackDeclarationTools(input: {
278
267
  outputFields: readonly string[];
279
268
  externalStepFields?: readonly string[];
280
269
  }): MapToolStats {
281
- let totalToolCount = 0;
282
- let unbatchedToolCount = 0;
270
+ let stats: MapToolStats = [0, 0];
283
271
 
284
272
  for (const declaration of input.declarations) {
285
273
  if (
@@ -291,23 +279,19 @@ function countFallbackDeclarationTools(input: {
291
279
  ) {
292
280
  continue;
293
281
  }
294
- totalToolCount += 1;
295
- if (isUnbatchedTool(declaration.toolId)) {
296
- unbatchedToolCount += 1;
297
- }
282
+ stats = addStats(stats, countTool(declaration.toolId));
298
283
  }
299
284
 
300
- return [totalToolCount, unbatchedToolCount];
285
+ return stats;
301
286
  }
302
287
 
303
288
  function countDeclarationTools(
304
289
  declarations: readonly { toolId: string; field?: string | null }[],
305
290
  ): MapToolStats {
306
- return [
307
- declarations.length,
308
- declarations.filter((declaration) => isUnbatchedTool(declaration.toolId))
309
- .length,
310
- ];
291
+ return declarations.reduce<MapToolStats>(
292
+ (stats, declaration) => addStats(stats, countTool(declaration.toolId)),
293
+ [0, 0],
294
+ );
311
295
  }
312
296
 
313
297
  function countNestedExecutionSteps(
@@ -409,12 +393,13 @@ export function chooseWorkerMapRowsPerChunk(
409
393
  }
410
394
 
411
395
  if (mapToolStats[1] > 0) {
412
- const unbatchedToolCallsPerRow =
396
+ const unbatchedSubrequestsPerRow =
413
397
  staticMapToolStats !== null
414
398
  ? mapToolStats[1]
415
- : Math.max(planMap?.stepsPerChunk ?? 1, mapToolStats[1]);
416
- const unbatchedSubrequestsPerRow =
417
- unbatchedToolCallsPerRow * SUBREQUESTS_PER_UNBATCHED_TOOL_CALL;
399
+ : Math.max(
400
+ (planMap?.stepsPerChunk ?? 1) * SUBREQUESTS_PER_UNBATCHED_TOOL_CALL,
401
+ mapToolStats[1],
402
+ );
418
403
  const unbatchedToolRowsPerChunk = Math.max(
419
404
  1,
420
405
  Math.floor(
@@ -104,10 +104,10 @@ export const SDK_RELEASE = {
104
104
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
105
105
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
106
106
  // fields shipped in 0.1.153.
107
- version: '0.1.170',
107
+ version: '0.1.172',
108
108
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
109
109
  supportPolicy: {
110
- latest: '0.1.170',
110
+ latest: '0.1.172',
111
111
  minimumSupported: '0.1.53',
112
112
  deprecatedBelow: '0.1.53',
113
113
  commandMinimumSupported: [
@@ -1315,6 +1315,7 @@ export class PlayContextImpl {
1315
1315
  repairRunningReceiptForSameRun?: boolean;
1316
1316
  repairRunningReceiptForSameRunAfterWaitTimeout?: boolean;
1317
1317
  runningReceiptWaitMaxAttempts?: number;
1318
+ runningReceiptWaitDelayMs?: number;
1318
1319
  reclaimRunning?: boolean;
1319
1320
  markSkipped?: (output: T) => Promise<void> | void;
1320
1321
  onRecovered?: (
@@ -1347,6 +1348,7 @@ export class PlayContextImpl {
1347
1348
  repairRunningReceiptForSameRunAfterWaitTimeout:
1348
1349
  opts.repairRunningReceiptForSameRunAfterWaitTimeout,
1349
1350
  runningReceiptWaitMaxAttempts: opts.runningReceiptWaitMaxAttempts,
1351
+ runningReceiptWaitDelayMs: opts.runningReceiptWaitDelayMs,
1350
1352
  reclaimRunning: opts.reclaimRunning,
1351
1353
  markSkipped: opts.markSkipped,
1352
1354
  onRecovered: opts.onRecovered,
@@ -3881,6 +3883,8 @@ export class PlayContextImpl {
3881
3883
  );
3882
3884
  },
3883
3885
  repairRunningReceiptForSameRunAfterWaitTimeout: true,
3886
+ runningReceiptWaitMaxAttempts:
3887
+ resolveRuntimeToolReceiptWaitMaxAttempts(input),
3884
3888
  execute: executePlayCall,
3885
3889
  },
3886
3890
  );
@@ -114,6 +114,9 @@ const RUNTIME_DB_SESSION_ROW_LIMIT_FLOOR = DIRECT_POSTGRES_BATCH_SIZE;
114
114
  const APPEND_KEY_SUFFIX_LENGTH = 12;
115
115
  const RUNTIME_DB_SESSION_TTL_SECONDS = 10 * 60;
116
116
  const RUNTIME_SHEET_ENSURE_CACHE_TTL_MS = 10 * 60_000;
117
+ const RUNTIME_SHEET_READY_AFTER_ENSURE_RETRY_DELAYS_MS = [
118
+ 100, 250, 500, 1_000, 2_000,
119
+ ] as const;
117
120
  const RUNTIME_DB_SESSION_RENEWAL_WINDOW_MS = 60_000;
118
121
  const RUNTIME_API_RETRY_DELAYS_MS = [
119
122
  250, 500, 1_000, 2_000, 4_000, 8_000, 8_000,
@@ -529,6 +532,41 @@ async function isRuntimeSheetSchemaReady(
529
532
  }
530
533
  }
531
534
 
535
+ async function waitForRuntimeSheetSchemaReadyAfterEnsure(
536
+ session: RuntimePostgresSession,
537
+ input: {
538
+ sheetContract: PlaySheetContract;
539
+ timings?: RuntimeSheetTiming[];
540
+ },
541
+ ): Promise<boolean> {
542
+ for (
543
+ let attempt = 0;
544
+ attempt <= RUNTIME_SHEET_READY_AFTER_ENSURE_RETRY_DELAYS_MS.length;
545
+ attempt += 1
546
+ ) {
547
+ const recheckStartedAt = Date.now();
548
+ const ready = await isRuntimeSheetSchemaReady(session, {
549
+ sheetContract: input.sheetContract,
550
+ });
551
+ input.timings?.push({
552
+ phase: 'schema_check_after_preloaded_session_ensure',
553
+ ms: Date.now() - recheckStartedAt,
554
+ ready,
555
+ retried: attempt > 0,
556
+ });
557
+ if (ready) {
558
+ return true;
559
+ }
560
+ const delay =
561
+ RUNTIME_SHEET_READY_AFTER_ENSURE_RETRY_DELAYS_MS[attempt] ?? null;
562
+ if (delay === null) {
563
+ return false;
564
+ }
565
+ await sleep(delay);
566
+ }
567
+ return false;
568
+ }
569
+
532
570
  async function ensureRuntimeSheetForPreloadedSession(
533
571
  context: RuntimeApiContext & { playName: string },
534
572
  input: {
@@ -597,38 +635,35 @@ async function ensureRuntimeSheetForPreloadedSession(
597
635
  return;
598
636
  }
599
637
  const ensureStartedAt = Date.now();
600
- const promise = ensureRuntimeSheet(context, {
601
- playName: context.playName,
602
- tableNamespace: input.tableNamespace,
603
- sheetContract: input.sheetContract,
604
- });
605
- runtimeSheetEnsureCache.set(cacheKey, {
606
- expiresAt: now + RUNTIME_SHEET_ENSURE_CACHE_TTL_MS,
607
- promise,
608
- });
609
- try {
610
- await promise;
611
- input.timings?.push({
612
- phase: 'ensure_sheet_for_preloaded_session',
613
- ms: Date.now() - ensureStartedAt,
614
- });
615
- const recheckStartedAt = Date.now();
616
- const readyAfterEnsure = await isRuntimeSheetSchemaReady(input.session, {
638
+ const promise = (async () => {
639
+ await ensureRuntimeSheet(context, {
640
+ playName: context.playName,
641
+ tableNamespace: input.tableNamespace,
617
642
  sheetContract: input.sheetContract,
618
643
  });
619
644
  input.timings?.push({
620
- phase: 'schema_check_after_preloaded_session_ensure',
621
- ms: Date.now() - recheckStartedAt,
622
- ready: readyAfterEnsure,
645
+ phase: 'ensure_sheet_for_preloaded_session',
646
+ ms: Date.now() - ensureStartedAt,
623
647
  });
648
+ const readyAfterEnsure = await waitForRuntimeSheetSchemaReadyAfterEnsure(
649
+ input.session,
650
+ {
651
+ sheetContract: input.sheetContract,
652
+ timings: input.timings,
653
+ },
654
+ );
624
655
  if (!readyAfterEnsure) {
625
- if (runtimeSheetEnsureCache.get(cacheKey)?.promise === promise) {
626
- runtimeSheetEnsureCache.delete(cacheKey);
627
- }
628
656
  throw new Error(
629
657
  `Runtime sheet schema for ctx.dataset("${input.tableNamespace}") is still not ready after ensure_sheet.`,
630
658
  );
631
659
  }
660
+ })();
661
+ runtimeSheetEnsureCache.set(cacheKey, {
662
+ expiresAt: now + RUNTIME_SHEET_ENSURE_CACHE_TTL_MS,
663
+ promise,
664
+ });
665
+ try {
666
+ await promise;
632
667
  } catch (error) {
633
668
  if (runtimeSheetEnsureCache.get(cacheKey)?.promise === promise) {
634
669
  runtimeSheetEnsureCache.delete(cacheKey);