deepline 0.1.130 → 0.1.132

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.
@@ -168,6 +168,9 @@ const MAP_FRAME_FLUSH_ROW_INTERVAL = 100;
168
168
  /** Executed-row sheet persistence chunking: rows AND bytes, whichever first. */
169
169
  const MAP_PERSIST_CHUNK_ROWS = 2_000;
170
170
  const MAP_PERSIST_CHUNK_BYTES = 8 * 1024 * 1024;
171
+ const MAP_INCREMENTAL_PERSIST_CHUNK_ROWS = 100;
172
+ const MAP_INCREMENTAL_PERSIST_CHUNK_BYTES = 1 * 1024 * 1024;
173
+ const MAP_INCREMENTAL_PERSIST_INTERVAL_MS = 100;
171
174
  const MAP_FRAME_FLUSH_INTERVAL_MS = 250;
172
175
  const TOOL_RETRY_AFTER_FALLBACK_MS = 1_000;
173
176
  const TOOL_RETRY_HEARTBEAT_INTERVAL_MS = 30_000;
@@ -219,11 +222,143 @@ function comparePersistableMapRowsByInputIndex(
219
222
  return leftIndex - rightIndex;
220
223
  }
221
224
 
225
+ function persistableMapRowIdentity(row: PersistableMapRow): string | null {
226
+ if (row.key) return `key:${row.key}`;
227
+ return typeof row.inputIndex === 'number' && Number.isFinite(row.inputIndex)
228
+ ? `index:${Math.floor(row.inputIndex)}`
229
+ : null;
230
+ }
231
+
232
+ function persistableMapRowBytes(row: PersistableMapRow): number {
233
+ return JSON.stringify(row).length;
234
+ }
235
+
222
236
  type FieldMapRunResult = {
223
237
  completedRows: PersistableMapRow[];
224
238
  failedRows: PersistableMapRow[];
225
239
  };
226
240
 
241
+ type IncrementalMapRowPersistence = {
242
+ persistRows: (rows: PersistableMapRow[]) => Promise<void>;
243
+ isPersisted: (row: PersistableMapRow) => boolean;
244
+ flush: () => Promise<void>;
245
+ };
246
+
247
+ function createIncrementalMapRowPersistence(
248
+ persistRows: (rows: PersistableMapRow[]) => Promise<void>,
249
+ ): IncrementalMapRowPersistence {
250
+ const persisted = new Set<string>();
251
+ const queued = new Set<string>();
252
+ let pendingRows: PersistableMapRow[] = [];
253
+ let pendingBytes = 0;
254
+ let pendingResolvers: Array<{
255
+ resolve: () => void;
256
+ reject: (error: unknown) => void;
257
+ }> = [];
258
+ let scheduledTimer: ReturnType<typeof setTimeout> | null = null;
259
+ let flushChain: Promise<void> = Promise.resolve();
260
+
261
+ const clearScheduledTimer = (): void => {
262
+ if (scheduledTimer) {
263
+ clearTimeout(scheduledTimer);
264
+ scheduledTimer = null;
265
+ }
266
+ };
267
+
268
+ const flushPendingRows = (): Promise<void> => {
269
+ clearScheduledTimer();
270
+ const rows = pendingRows;
271
+ const resolvers = pendingResolvers;
272
+ pendingRows = [];
273
+ pendingBytes = 0;
274
+ pendingResolvers = [];
275
+ if (rows.length === 0) {
276
+ resolvers.forEach(({ resolve }) => resolve());
277
+ return flushChain;
278
+ }
279
+ flushChain = flushChain
280
+ .then(async () => {
281
+ await persistRows(rows);
282
+ for (const row of rows) {
283
+ const identity = persistableMapRowIdentity(row);
284
+ if (identity) {
285
+ persisted.add(identity);
286
+ queued.delete(identity);
287
+ }
288
+ }
289
+ })
290
+ .then(
291
+ () => {
292
+ resolvers.forEach(({ resolve }) => resolve());
293
+ },
294
+ (error) => {
295
+ for (const row of rows) {
296
+ const identity = persistableMapRowIdentity(row);
297
+ if (identity) queued.delete(identity);
298
+ }
299
+ resolvers.forEach(({ reject }) => reject(error));
300
+ throw error;
301
+ },
302
+ );
303
+ return flushChain;
304
+ };
305
+
306
+ const scheduleFlush = (): void => {
307
+ if (scheduledTimer) return;
308
+ scheduledTimer = setTimeout(() => {
309
+ scheduledTimer = null;
310
+ void flushPendingRows().catch(() => {
311
+ // Each queued caller is rejected by flushPendingRows; this scheduled
312
+ // fire-and-forget path must not create a second unhandled rejection.
313
+ });
314
+ }, MAP_INCREMENTAL_PERSIST_INTERVAL_MS);
315
+ };
316
+
317
+ return {
318
+ persistRows: async (rows) => {
319
+ const freshRows: PersistableMapRow[] = [];
320
+ for (const row of rows) {
321
+ const identity = persistableMapRowIdentity(row);
322
+ if (identity && (persisted.has(identity) || queued.has(identity))) {
323
+ continue;
324
+ }
325
+ if (identity) queued.add(identity);
326
+ freshRows.push(row);
327
+ }
328
+ if (freshRows.length === 0) return;
329
+ await new Promise<void>((resolve, reject) => {
330
+ pendingRows.push(...freshRows);
331
+ pendingBytes += freshRows.reduce(
332
+ (total, row) => total + persistableMapRowBytes(row),
333
+ 0,
334
+ );
335
+ pendingResolvers.push({ resolve, reject });
336
+ if (
337
+ pendingRows.length >= MAP_INCREMENTAL_PERSIST_CHUNK_ROWS ||
338
+ pendingBytes >= MAP_INCREMENTAL_PERSIST_CHUNK_BYTES
339
+ ) {
340
+ void flushPendingRows().catch(() => {
341
+ // Each queued caller is rejected by flushPendingRows.
342
+ });
343
+ return;
344
+ }
345
+ scheduleFlush();
346
+ });
347
+ },
348
+ isPersisted: (row) => {
349
+ const identity = persistableMapRowIdentity(row);
350
+ return identity ? persisted.has(identity) : false;
351
+ },
352
+ flush: async () => {
353
+ if (pendingRows.length > 0) {
354
+ clearScheduledTimer();
355
+ await flushPendingRows();
356
+ }
357
+ await flushChain;
358
+ },
359
+ };
360
+ }
361
+
227
362
  class FailFastMapRowsError extends Error {
228
363
  readonly completedRows: PersistableMapRow[];
229
364
  readonly failedRows: PersistableMapRow[];
@@ -1819,6 +1954,7 @@ export class PlayContextImpl {
1819
1954
  let totalInputCount = 0;
1820
1955
  let rawItems: Record<string, unknown>[] = [];
1821
1956
  let itemsToProcess: Array<Record<string, unknown>> = [];
1957
+ let itemOriginalIndexes: number[] = [];
1822
1958
  let completedItemsByKey: Map<string, Record<string, unknown>> | null = null;
1823
1959
  const datasetColumnNames = Object.keys(input);
1824
1960
  const stripFieldOutputs = (row: Record<string, unknown>) => {
@@ -1923,6 +2059,7 @@ export class PlayContextImpl {
1923
2059
  itemsToProcess = materializedItems.map((item) =>
1924
2060
  this.toOutputRow(item as Record<string, unknown>),
1925
2061
  );
2062
+ itemOriginalIndexes = materializedItems.map((_item, index) => index);
1926
2063
  if (this.#options.onMapStart) {
1927
2064
  const shouldPassRowKey = explicitKeyResolver != null;
1928
2065
  // toSerializableCsvAliasedRow (not a plain spread): projected CSV
@@ -1968,7 +2105,7 @@ export class PlayContextImpl {
1968
2105
  const rowKey = persistedRowIdentity(row, index);
1969
2106
  if (rowKey) pendingRowsByKey.set(rowKey, row);
1970
2107
  }
1971
- itemsToProcess = materializedItems
2108
+ const pendingItems = materializedItems
1972
2109
  .map((item, index) => ({
1973
2110
  row: this.toOutputRow(item as Record<string, unknown>),
1974
2111
  index,
@@ -1981,8 +2118,13 @@ export class PlayContextImpl {
1981
2118
  // the sheet round-trip. Merge the persisted data/meta over the
1982
2119
  // in-memory row so key functions and column resolvers keep seeing
1983
2120
  // projected aliases.
1984
- return persisted ? cloneCsvAliasedRow(row, persisted) : row;
2121
+ return {
2122
+ row: persisted ? cloneCsvAliasedRow(row, persisted) : row,
2123
+ index,
2124
+ };
1985
2125
  });
2126
+ itemsToProcess = pendingItems.map((item) => item.row);
2127
+ itemOriginalIndexes = pendingItems.map((item) => item.index);
1986
2128
  if (mapStartResult.completedRows.length > 0) {
1987
2129
  completedItemsByKey = new Map();
1988
2130
  for (
@@ -2008,7 +2150,7 @@ export class PlayContextImpl {
2008
2150
  const completedRowKeys =
2009
2151
  completedItemsByKey != null ? [...completedItemsByKey.keys()] : [];
2010
2152
  const pendingRowKeys = itemsToProcess.map((item, index) =>
2011
- rowIdentity(this.toOutputRow(item), index),
2153
+ rowIdentity(this.toOutputRow(item), itemOriginalIndexes[index] ?? index),
2012
2154
  );
2013
2155
  this.setMapFrame({
2014
2156
  mapInvocationId: mapScope.mapInvocationId,
@@ -2040,9 +2182,10 @@ export class PlayContextImpl {
2040
2182
  >();
2041
2183
  for (let index = 0; index < itemsToProcess.length; index += 1) {
2042
2184
  const row = itemsToProcess[index]!;
2043
- const rowKey = rowIdentity(row, index);
2185
+ const originalIndex = itemOriginalIndexes[index] ?? index;
2186
+ const rowKey = rowIdentity(row, originalIndex);
2044
2187
  if (!rowsToExecuteByKey.has(rowKey)) {
2045
- rowsToExecuteByKey.set(rowKey, { row, originalIndex: index });
2188
+ rowsToExecuteByKey.set(rowKey, { row, originalIndex });
2046
2189
  }
2047
2190
  }
2048
2191
  const rowsToExecuteEntries = [...rowsToExecuteByKey.entries()].map(
@@ -2090,6 +2233,9 @@ export class PlayContextImpl {
2090
2233
  }
2091
2234
  await flushChunk();
2092
2235
  };
2236
+ const incrementalPersistence = this.#options.onMapRowsCompleted
2237
+ ? createIncrementalMapRowPersistence(persistMapRows)
2238
+ : null;
2093
2239
 
2094
2240
  let mapResult: FieldMapRunResult;
2095
2241
  try {
@@ -2108,6 +2254,7 @@ export class PlayContextImpl {
2108
2254
  executionRowIndexes: rowsToExecuteEntries.map(
2109
2255
  (entry) => entry.originalIndex,
2110
2256
  ),
2257
+ incrementalPersistence,
2111
2258
  },
2112
2259
  );
2113
2260
  } catch (error) {
@@ -2116,11 +2263,17 @@ export class PlayContextImpl {
2116
2263
  error.completedRows.length > 0
2117
2264
  ? error.completedRows
2118
2265
  : error.failedRows;
2266
+ await incrementalPersistence?.flush();
2267
+ const unpersistedRows = incrementalPersistence
2268
+ ? rowsToPersist.filter(
2269
+ (row) => !incrementalPersistence.isPersisted(row),
2270
+ )
2271
+ : rowsToPersist;
2119
2272
  const persistStartedAt = Date.now();
2120
- await persistMapRows(rowsToPersist);
2121
- if (rowsToPersist.length > 0) {
2273
+ await persistMapRows(unpersistedRows);
2274
+ if (unpersistedRows.length > 0) {
2122
2275
  this.log(
2123
- `Persisted ${rowsToPersist.length} fail-fast rows to sheet ${resolvedTableNamespace} in ${Date.now() - persistStartedAt}ms`,
2276
+ `Persisted ${unpersistedRows.length} fail-fast rows to sheet ${resolvedTableNamespace} in ${Date.now() - persistStartedAt}ms`,
2124
2277
  );
2125
2278
  }
2126
2279
  this.activeMapCellMeta = null;
@@ -2164,9 +2317,11 @@ export class PlayContextImpl {
2164
2317
  // source of truth, not this in-memory results array. Chunked by rows AND
2165
2318
  // bytes so large cells (scraped pages) never produce oversized writes.
2166
2319
  if (resultsByKey.size > 0 || mapResult.failedRows.length > 0) {
2320
+ await incrementalPersistence?.flush();
2167
2321
  const mapCellMeta = this.activeMapCellMeta;
2168
2322
  const persistRows: PersistableMapRow[] = [];
2169
2323
  for (const row of mapResult.completedRows) {
2324
+ if (incrementalPersistence?.isPersisted(row)) continue;
2170
2325
  const rowKey = row.key;
2171
2326
  const meta = mapCellMeta?.get(rowKey);
2172
2327
  persistRows.push({
@@ -2178,12 +2333,18 @@ export class PlayContextImpl {
2178
2333
  },
2179
2334
  });
2180
2335
  }
2181
- persistRows.push(...mapResult.failedRows);
2336
+ persistRows.push(
2337
+ ...mapResult.failedRows.filter(
2338
+ (row) => !incrementalPersistence?.isPersisted(row),
2339
+ ),
2340
+ );
2182
2341
  const persistStartedAt = Date.now();
2183
2342
  await persistMapRows(persistRows);
2184
- this.log(
2185
- `Persisted ${persistRows.length} executed rows to sheet ${resolvedTableNamespace} in ${Date.now() - persistStartedAt}ms`,
2186
- );
2343
+ if (persistRows.length > 0) {
2344
+ this.log(
2345
+ `Persisted ${persistRows.length} executed rows to sheet ${resolvedTableNamespace} in ${Date.now() - persistStartedAt}ms`,
2346
+ );
2347
+ }
2187
2348
  }
2188
2349
  this.activeMapCellMeta = null;
2189
2350
 
@@ -2255,6 +2416,7 @@ export class PlayContextImpl {
2255
2416
  * want to throttle a wide map.
2256
2417
  */
2257
2418
  concurrency?: number;
2419
+ incrementalPersistence?: IncrementalMapRowPersistence | null;
2258
2420
  },
2259
2421
  ): Promise<FieldMapRunResult> {
2260
2422
  const fieldEntries = Object.entries(definition);
@@ -2299,6 +2461,14 @@ export class PlayContextImpl {
2299
2461
  const failFastOnRowError = runtimeOptions?.onRowError === 'fail';
2300
2462
  const completedRowsToPersist: PersistableMapRow[] = [];
2301
2463
  const failedRowsToPersist: PersistableMapRow[] = [];
2464
+ const incrementalPersistence =
2465
+ runtimeOptions?.incrementalPersistence ?? null;
2466
+ const enqueueIncrementalPersist = (row: PersistableMapRow): void => {
2467
+ void incrementalPersistence?.persistRows([row]).catch(() => {
2468
+ // The final map-level flush awaits the same chain and surfaces the
2469
+ // persistence failure loudly without holding a completed row slot open.
2470
+ });
2471
+ };
2302
2472
 
2303
2473
  if (completedRows > 0 || pendingRows !== totalRows) {
2304
2474
  this.log(
@@ -2605,7 +2775,7 @@ export class PlayContextImpl {
2605
2775
  : {},
2606
2776
  });
2607
2777
  if (failFastOnRowError) {
2608
- failedRowsToPersist.push({
2778
+ const failedRow: PersistableMapRow = {
2609
2779
  key: rowKey,
2610
2780
  inputIndex: rowIndex,
2611
2781
  data: this.toPersistedOutputRow(
@@ -2616,21 +2786,23 @@ export class PlayContextImpl {
2616
2786
  : {}),
2617
2787
  status: 'failed',
2618
2788
  error: formattedError,
2619
- });
2789
+ };
2790
+ failedRowsToPersist.push(failedRow);
2620
2791
  throw error;
2621
2792
  }
2622
2793
  const failedData = this.toPersistedOutputRow(
2623
2794
  cloneCsvAliasedRow(baseRow, computedFields),
2624
2795
  );
2625
2796
  const cellMetaPatch = this.activeMapCellMeta?.get(rowKey);
2626
- failedRowsToPersist.push({
2797
+ const failedRow: PersistableMapRow = {
2627
2798
  key: rowKey,
2628
2799
  inputIndex: rowIndex,
2629
2800
  data: failedData,
2630
2801
  ...(cellMetaPatch ? { cellMetaPatch } : {}),
2631
2802
  status: 'failed',
2632
2803
  error: formattedError,
2633
- });
2804
+ };
2805
+ failedRowsToPersist.push(failedRow);
2634
2806
  updateMapFrameProgress({
2635
2807
  failedRowKey: rowKey,
2636
2808
  });
@@ -2642,6 +2814,7 @@ export class PlayContextImpl {
2642
2814
  error: formattedError,
2643
2815
  dataPatch: {},
2644
2816
  });
2817
+ enqueueIncrementalPersist(failedRow);
2645
2818
  return FAILED_ROW;
2646
2819
  }
2647
2820
  const cellValue = this.serializeCellValue(value);
@@ -2682,14 +2855,16 @@ export class PlayContextImpl {
2682
2855
  dataPatch: {},
2683
2856
  });
2684
2857
  const publicRow = this.toPublicOutputRow(merged);
2685
- completedRowsToPersist.push({
2858
+ const completedRow: PersistableMapRow = {
2686
2859
  key: rowKey,
2687
2860
  inputIndex: rowIndex,
2688
2861
  data: this.toPersistedOutputRow(merged),
2689
2862
  ...(this.activeMapCellMeta?.get(rowKey)
2690
2863
  ? { cellMetaPatch: this.activeMapCellMeta.get(rowKey) }
2691
2864
  : {}),
2692
- });
2865
+ };
2866
+ completedRowsToPersist.push(completedRow);
2867
+ enqueueIncrementalPersist(completedRow);
2693
2868
  return publicRow;
2694
2869
  } catch (error) {
2695
2870
  if (isPlayRowExecutionSuspendedError(error)) {
@@ -2807,6 +2982,7 @@ export class PlayContextImpl {
2807
2982
  ).values(),
2808
2983
  ];
2809
2984
  this.pendingRowEventBoundaries = [];
2985
+ await incrementalPersistence?.flush();
2810
2986
  this.#options.onBatchComplete?.(this.checkpoint);
2811
2987
  throw new PlayExecutionSuspendedError({
2812
2988
  kind: 'integration_event_batch',
@@ -4960,7 +5136,42 @@ export class PlayContextImpl {
4960
5136
  [...byTool.entries()].map(async ([toolId, requests]) => {
4961
5137
  this.log(`Executing tool batch ${toolId}: ${requests.length} calls`);
4962
5138
 
5139
+ const recordToolStep = (stepRequests: ToolCallRequest[]): void => {
5140
+ if (stepRequests.length === 0) return;
5141
+ const stepResults: PlayStepRowResult[] = stepRequests.map((req) => {
5142
+ const cachedResult = this.getCachedToolResult(
5143
+ toolId,
5144
+ this.buildToolResultCacheKey({
5145
+ rowId: req.rowId,
5146
+ tableNamespace: req.tableNamespace,
5147
+ rowKey: req.rowKey ?? undefined,
5148
+ callId: req.callId,
5149
+ }),
5150
+ );
5151
+ return {
5152
+ rowId: req.rowId,
5153
+ status: cachedResult?.result != null ? 'completed' : 'failed',
5154
+ success: cachedResult?.result != null,
5155
+ value: cachedResult?.result,
5156
+ error: cachedResult?.result != null ? null : 'Tool call failed',
5157
+ };
5158
+ });
5159
+ const toolStep = {
5160
+ type: 'tool' as const,
5161
+ toolId,
5162
+ // Keep the step trace preview-sized for large map pages.
5163
+ results: compactRowResultsPreview(stepResults),
5164
+ description: normalizeStepDescription(stepRequests[0]?.description),
5165
+ };
5166
+ if (this.activeDatasetStep) {
5167
+ this.activeDatasetStep.substeps.push(toolStep);
5168
+ } else {
5169
+ this.steps.push(toolStep);
5170
+ }
5171
+ };
5172
+
4963
5173
  const pendingRequests: ToolCallRequest[] = [];
5174
+ const recoveredRequests: ToolCallRequest[] = [];
4964
5175
  for (const req of requests) {
4965
5176
  const cached = this.getCachedToolResult(
4966
5177
  toolId,
@@ -4978,10 +5189,12 @@ export class PlayContextImpl {
4978
5189
  resolver.resolve(cached.result);
4979
5190
  this.toolCallResolvers.delete(req.callId);
4980
5191
  }
5192
+ recoveredRequests.push(req);
4981
5193
  } else {
4982
5194
  pendingRequests.push(req);
4983
5195
  }
4984
5196
  }
5197
+ recordToolStep(recoveredRequests);
4985
5198
 
4986
5199
  if (pendingRequests.length > 0) {
4987
5200
  const strategy =
@@ -5001,49 +5214,47 @@ export class PlayContextImpl {
5001
5214
  4,
5002
5215
  )
5003
5216
  : 4;
5004
- for (
5005
- let start = 0;
5006
- start < compiledBatches.length;
5007
- start += batchSize
5008
- ) {
5009
- const chunk = compiledBatches.slice(start, start + batchSize);
5010
- const settled = await Promise.allSettled(
5011
- chunk.map((batch) =>
5012
- this.callToolAPI(batch.batchOperation, batch.batchPayload),
5217
+ await executeChunkedRequests({
5218
+ requests: compiledBatches,
5219
+ batchSize,
5220
+ execute: async (batch) =>
5221
+ await this.callToolAPI(
5222
+ batch.batchOperation,
5223
+ batch.batchPayload,
5013
5224
  ),
5014
- );
5015
-
5016
- for (let index = 0; index < chunk.length; index += 1) {
5017
- const requestBatch = chunk[index]!;
5018
- const outcome = settled[index]!;
5019
- if (outcome.status === 'fulfilled') {
5225
+ onChunkComplete: async (chunkResults) => {
5226
+ for (const entry of chunkResults) {
5227
+ if (entry.error !== undefined) {
5228
+ for (const request of entry.request.memberRequests) {
5229
+ this.rejectToolCall(toolId, request, entry.error);
5230
+ }
5231
+ continue;
5232
+ }
5020
5233
  const splitResults =
5021
- outcome.value != null
5022
- ? requestBatch.splitResults(outcome.value)
5023
- : requestBatch.memberRequests.map(() => null);
5234
+ entry.result != null
5235
+ ? entry.request.splitResults(entry.result)
5236
+ : entry.request.memberRequests.map(() => null);
5024
5237
 
5025
5238
  for (
5026
5239
  let index = 0;
5027
- index < requestBatch.memberRequests.length;
5240
+ index < entry.request.memberRequests.length;
5028
5241
  index += 1
5029
5242
  ) {
5030
- const request = requestBatch.memberRequests[index]!;
5243
+ const request = entry.request.memberRequests[index]!;
5031
5244
  await this.resolveToolCall(
5032
5245
  toolId,
5033
5246
  request,
5034
5247
  splitResults[index] ?? null,
5035
5248
  );
5036
5249
  }
5037
- continue;
5038
5250
  }
5039
5251
 
5040
- for (const request of requestBatch.memberRequests) {
5041
- this.rejectToolCall(toolId, request, outcome.reason);
5042
- }
5043
- }
5044
-
5045
- this.#options.onBatchComplete?.(this.checkpoint);
5046
- }
5252
+ recordToolStep(
5253
+ chunkResults.flatMap((entry) => entry.request.memberRequests),
5254
+ );
5255
+ this.#options.onBatchComplete?.(this.checkpoint);
5256
+ },
5257
+ });
5047
5258
  } else {
5048
5259
  const batchSize = await this.governor.suggestedParallelism(
5049
5260
  toolId,
@@ -5055,66 +5266,32 @@ export class PlayContextImpl {
5055
5266
  start += batchSize
5056
5267
  ) {
5057
5268
  const chunk = pendingRequests.slice(start, start + batchSize);
5058
- const settled = await Promise.allSettled(
5059
- chunk.map((request) =>
5060
- this.callToolExecutionAPI(toolId, request.input),
5061
- ),
5269
+ await Promise.allSettled(
5270
+ chunk.map(async (request) => {
5271
+ try {
5272
+ const execution = await this.callToolExecutionAPI(
5273
+ toolId,
5274
+ request.input,
5275
+ );
5276
+ await this.resolveToolCall(
5277
+ toolId,
5278
+ request,
5279
+ execution?.result ?? null,
5280
+ execution?.metadata ?? null,
5281
+ execution?.jobId,
5282
+ execution?.meta,
5283
+ );
5284
+ } catch (error) {
5285
+ this.rejectToolCall(toolId, request, error);
5286
+ }
5287
+ }),
5062
5288
  );
5063
5289
 
5064
- for (let index = 0; index < chunk.length; index += 1) {
5065
- const request = chunk[index]!;
5066
- const outcome = settled[index]!;
5067
- if (outcome.status === 'fulfilled') {
5068
- await this.resolveToolCall(
5069
- toolId,
5070
- request,
5071
- outcome.value?.result ?? null,
5072
- outcome.value?.metadata ?? null,
5073
- outcome.value?.jobId,
5074
- outcome.value?.meta,
5075
- );
5076
- continue;
5077
- }
5078
-
5079
- this.rejectToolCall(toolId, request, outcome.reason);
5080
- }
5081
-
5290
+ recordToolStep(chunk);
5082
5291
  this.#options.onBatchComplete?.(this.checkpoint);
5083
5292
  }
5084
5293
  }
5085
5294
  }
5086
-
5087
- // Record step — nest under map if inside one
5088
- const stepResults: PlayStepRowResult[] = requests.map((req) => {
5089
- const cachedResult = this.getCachedToolResult(
5090
- toolId,
5091
- this.buildToolResultCacheKey({
5092
- rowId: req.rowId,
5093
- tableNamespace: req.tableNamespace,
5094
- rowKey: req.rowKey ?? undefined,
5095
- callId: req.callId,
5096
- }),
5097
- );
5098
- return {
5099
- rowId: req.rowId,
5100
- status: cachedResult?.result != null ? 'completed' : 'failed',
5101
- success: cachedResult?.result != null,
5102
- value: cachedResult?.result,
5103
- error: cachedResult?.result != null ? null : 'Tool call failed',
5104
- };
5105
- });
5106
- const toolStep = {
5107
- type: 'tool' as const,
5108
- toolId,
5109
- // Keep the step trace preview-sized for large map pages.
5110
- results: compactRowResultsPreview(stepResults),
5111
- description: normalizeStepDescription(requests[0]?.description),
5112
- };
5113
- if (this.activeDatasetStep) {
5114
- this.activeDatasetStep.substeps.push(toolStep);
5115
- } else {
5116
- this.steps.push(toolStep);
5117
- }
5118
5295
  }),
5119
5296
  );
5120
5297
  }