deepline 0.1.169 → 0.1.170

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.
@@ -1153,7 +1153,6 @@ export class PlayDedup implements DurableObject {
1153
1153
  if (!state && timeoutMs > 0) {
1154
1154
  await new Promise<void>((resolve) => {
1155
1155
  let settled = false;
1156
- let timeout: ReturnType<typeof setTimeout>;
1157
1156
  const finish = () => {
1158
1157
  if (settled) return;
1159
1158
  settled = true;
@@ -1164,7 +1163,7 @@ export class PlayDedup implements DurableObject {
1164
1163
  clearTimeout(timeout);
1165
1164
  resolve();
1166
1165
  };
1167
- timeout = setTimeout(finish, timeoutMs);
1166
+ const timeout = setTimeout(finish, timeoutMs);
1168
1167
  if (!this.childTerminalWaiters.has(eventKey)) {
1169
1168
  this.childTerminalWaiters.set(eventKey, new Set());
1170
1169
  }
@@ -159,6 +159,8 @@ import {
159
159
  markWorkerToolReceiptResultCached,
160
160
  markWorkerToolReceiptResultExecution,
161
161
  planWorkerToolReceiptGroups,
162
+ resolveWorkerToolReceiptGroupWaitMaxAttempts,
163
+ resolveWorkerToolRuntimeTimeoutMs,
162
164
  } from './runtime/tool-receipts';
163
165
  // The harness stub forwards leaf calls (validation, runtime-api HTTP) into
164
166
  // the long-lived Play Harness Worker via env.HARNESS. We import the
@@ -563,11 +565,13 @@ function resolveRuntimeDeadlineRemainingMs(runtimeDeadlineMs?: number): number {
563
565
 
564
566
  function resolveToolRuntimeApiTimeout(input: {
565
567
  requestInput: Record<string, unknown>;
568
+ timeoutMs?: number;
566
569
  runtimeDeadlineMs?: number;
567
570
  }): { timeoutMs: number; timeoutErrorMessage?: string } {
568
- const toolTimeoutMs = resolveRuntimeToolReceiptWaitTimeoutMs(
569
- input.requestInput,
570
- );
571
+ const toolTimeoutMs =
572
+ typeof input.timeoutMs === 'number' && Number.isFinite(input.timeoutMs)
573
+ ? Math.max(1, Math.ceil(input.timeoutMs))
574
+ : resolveRuntimeToolReceiptWaitTimeoutMs(input.requestInput);
571
575
  const remainingMs = resolveRuntimeDeadlineRemainingMs(
572
576
  input.runtimeDeadlineMs,
573
577
  );
@@ -1265,6 +1269,7 @@ async function executeTool(
1265
1269
  transientHttpRetrySafe = false,
1266
1270
  abortSignal?: AbortSignal,
1267
1271
  runtimeDeadlineMs?: number,
1272
+ runtimeTimeoutMs?: number,
1268
1273
  ): Promise<ToolExecuteResult> {
1269
1274
  if (args.toolId === 'test_wait_for_event' && workflowStep) {
1270
1275
  const result = await waitForSyntheticIntegrationEvent(
@@ -1287,6 +1292,7 @@ async function executeTool(
1287
1292
  transientHttpRetrySafe,
1288
1293
  abortSignal,
1289
1294
  runtimeDeadlineMs,
1295
+ runtimeTimeoutMs,
1290
1296
  );
1291
1297
  }
1292
1298
 
@@ -1300,6 +1306,7 @@ async function executeToolWithLifecycle(
1300
1306
  transientHttpRetrySafe = false,
1301
1307
  abortSignal?: AbortSignal,
1302
1308
  runtimeDeadlineMs?: number,
1309
+ runtimeTimeoutMs?: number,
1303
1310
  ): Promise<ToolExecuteResult> {
1304
1311
  callbacks?.onToolCalled?.(args.toolId, nowMs());
1305
1312
  try {
@@ -1312,6 +1319,7 @@ async function executeToolWithLifecycle(
1312
1319
  transientHttpRetrySafe,
1313
1320
  abortSignal,
1314
1321
  runtimeDeadlineMs,
1322
+ runtimeTimeoutMs,
1315
1323
  );
1316
1324
  } catch (error) {
1317
1325
  callbacks?.onToolFailed?.(args.toolId, nowMs());
@@ -1329,6 +1337,8 @@ function normalizeToolExecuteArgs(request: unknown): {
1329
1337
  input: Record<string, unknown>;
1330
1338
  force?: boolean;
1331
1339
  staleAfterSeconds?: number;
1340
+ timeoutMs?: number;
1341
+ receiptWaitMs?: number;
1332
1342
  } {
1333
1343
  if (!isToolExecuteRecord(request)) {
1334
1344
  throw new Error(
@@ -1354,6 +1364,14 @@ function normalizeToolExecuteArgs(request: unknown): {
1354
1364
  ...(typeof request.staleAfterSeconds === 'number'
1355
1365
  ? { staleAfterSeconds: request.staleAfterSeconds }
1356
1366
  : {}),
1367
+ ...(typeof request.timeoutMs === 'number' &&
1368
+ Number.isFinite(request.timeoutMs)
1369
+ ? { timeoutMs: request.timeoutMs }
1370
+ : {}),
1371
+ ...(typeof request.receiptWaitMs === 'number' &&
1372
+ Number.isFinite(request.receiptWaitMs)
1373
+ ? { receiptWaitMs: request.receiptWaitMs }
1374
+ : {}),
1357
1375
  };
1358
1376
  }
1359
1377
 
@@ -1450,6 +1468,7 @@ async function callToolDirect(
1450
1468
  transientHttpRetrySafe = false,
1451
1469
  abortSignal?: AbortSignal,
1452
1470
  runtimeDeadlineMs?: number,
1471
+ runtimeTimeoutMs?: number,
1453
1472
  ): Promise<ToolExecuteResult> {
1454
1473
  const { id, toolId, input } = args;
1455
1474
  const path = `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
@@ -1464,6 +1483,7 @@ async function callToolDirect(
1464
1483
  try {
1465
1484
  const runtimeApiTimeout = resolveToolRuntimeApiTimeout({
1466
1485
  requestInput: input,
1486
+ timeoutMs: runtimeTimeoutMs,
1467
1487
  runtimeDeadlineMs,
1468
1488
  });
1469
1489
  res = await fetchRuntimeApi(
@@ -1640,6 +1660,7 @@ type WorkerToolBatchRequest = {
1640
1660
  receiptKey: string | null;
1641
1661
  force: boolean;
1642
1662
  receiptWaitMaxAttempts: number;
1663
+ runtimeTimeoutMs?: number;
1643
1664
  toolId: string;
1644
1665
  input: Record<string, unknown>;
1645
1666
  workflowStep?: WorkflowStep;
@@ -1658,6 +1679,20 @@ type PreparedWorkerToolBatchRequests = {
1658
1679
  deferredClaimedRequests: Promise<ClaimedWorkerToolBatchRequest[]>[];
1659
1680
  };
1660
1681
 
1682
+ function resolveClaimedWorkerToolRuntimeTimeoutMs(
1683
+ claimedRequests: ClaimedWorkerToolBatchRequest[],
1684
+ input: { runtimeDeadlineMs?: number },
1685
+ ): number | undefined {
1686
+ return resolveWorkerToolRuntimeTimeoutMs(claimedRequests, {
1687
+ resolveOwnerTimeoutMs: (request) =>
1688
+ resolveToolRuntimeApiTimeout({
1689
+ requestInput: request.input,
1690
+ timeoutMs: request.runtimeTimeoutMs,
1691
+ runtimeDeadlineMs: input.runtimeDeadlineMs,
1692
+ }).timeoutMs,
1693
+ });
1694
+ }
1695
+
1661
1696
  const WORKER_TOOL_BATCH_GRACE_MS = 250;
1662
1697
  const MAP_EXECUTION_HEARTBEAT_INTERVAL_MS = 5_000;
1663
1698
  const MAP_INCREMENTAL_PERSIST_CHUNK_ROWS = 100;
@@ -1802,6 +1837,7 @@ class WorkerToolBatchScheduler {
1802
1837
  input: Record<string, unknown>,
1803
1838
  workflowStep?: WorkflowStep,
1804
1839
  options?: { force?: boolean; staleAfterSeconds?: number | null },
1840
+ runtimeOptions?: { timeoutMs?: number; receiptWaitMs?: number },
1805
1841
  ): Promise<unknown> {
1806
1842
  const providerActionVersion =
1807
1843
  await this.resolveToolActionCacheVersion(toolId);
@@ -1819,7 +1855,14 @@ class WorkerToolBatchScheduler {
1819
1855
  cacheKey: receiptKey,
1820
1856
  receiptKey,
1821
1857
  force: options?.force === true,
1822
- receiptWaitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(input),
1858
+ receiptWaitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(
1859
+ typeof runtimeOptions?.receiptWaitMs === 'number'
1860
+ ? { max_wait_ms: runtimeOptions.receiptWaitMs }
1861
+ : input,
1862
+ ),
1863
+ ...(typeof runtimeOptions?.timeoutMs === 'number'
1864
+ ? { runtimeTimeoutMs: runtimeOptions.timeoutMs }
1865
+ : {}),
1823
1866
  toolId,
1824
1867
  input,
1825
1868
  workflowStep,
@@ -2005,7 +2048,10 @@ class WorkerToolBatchScheduler {
2005
2048
  try {
2006
2049
  const receipt = await this.waitForDurableToolReceipt({
2007
2050
  receiptKey: input.receiptKey,
2008
- maxAttempts: request.receiptWaitMaxAttempts,
2051
+ maxAttempts: resolveWorkerToolReceiptGroupWaitMaxAttempts(
2052
+ input.group,
2053
+ (groupRequest) => groupRequest.receiptWaitMaxAttempts,
2054
+ ),
2009
2055
  });
2010
2056
  await this.resolveCompletedDurableToolReceiptGroup({
2011
2057
  group: input.group,
@@ -2114,7 +2160,10 @@ class WorkerToolBatchScheduler {
2114
2160
  try {
2115
2161
  const receipt = await this.waitForDurableToolReceipt({
2116
2162
  receiptKey: input.receiptKey,
2117
- maxAttempts: request.receiptWaitMaxAttempts,
2163
+ maxAttempts: resolveWorkerToolReceiptGroupWaitMaxAttempts(
2164
+ input.group,
2165
+ (groupRequest) => groupRequest.receiptWaitMaxAttempts,
2166
+ ),
2118
2167
  });
2119
2168
  await this.resolveCompletedDurableToolReceiptGroup({
2120
2169
  group: input.group,
@@ -2516,6 +2565,9 @@ class WorkerToolBatchScheduler {
2516
2565
  toolContract?.retrySafeTransientHttp === true,
2517
2566
  this.abortSignal,
2518
2567
  this.runtimeDeadlineMs,
2568
+ resolveClaimedWorkerToolRuntimeTimeoutMs([claimed], {
2569
+ runtimeDeadlineMs: this.runtimeDeadlineMs,
2570
+ }),
2519
2571
  );
2520
2572
  } catch (error) {
2521
2573
  this.rejectRequests(
@@ -2686,6 +2738,9 @@ async function executeBatchedWorkerToolGroup(input: {
2686
2738
  toolContract?.retrySafeTransientHttp === true,
2687
2739
  input.abortSignal,
2688
2740
  input.runtimeDeadlineMs,
2741
+ resolveClaimedWorkerToolRuntimeTimeoutMs(batch.memberRequests, {
2742
+ runtimeDeadlineMs: input.runtimeDeadlineMs,
2743
+ }),
2689
2744
  );
2690
2745
  } catch (error) {
2691
2746
  input.callbacks?.onToolFailed?.(batch.batchOperation, nowMs());
@@ -6280,6 +6335,10 @@ function createMinimalWorkerCtx(
6280
6335
  force: request.force === true,
6281
6336
  staleAfterSeconds: request.staleAfterSeconds,
6282
6337
  },
6338
+ {
6339
+ timeoutMs: request.timeoutMs,
6340
+ receiptWaitMs: request.receiptWaitMs,
6341
+ },
6283
6342
  );
6284
6343
  },
6285
6344
  },
@@ -27,8 +27,11 @@ type RuntimeReceiptContext = {
27
27
  receiptStore: WorkerRuntimeReceiptStore;
28
28
  };
29
29
 
30
- const WORKER_RECEIPT_WAIT_MAX_ATTEMPTS = 240;
30
+ const WORKER_RECEIPT_DEFAULT_WAIT_MS = 300_000;
31
31
  const WORKER_RECEIPT_WAIT_DELAY_MS = 250;
32
+ const WORKER_RECEIPT_WAIT_MAX_ATTEMPTS = Math.ceil(
33
+ WORKER_RECEIPT_DEFAULT_WAIT_MS / WORKER_RECEIPT_WAIT_DELAY_MS,
34
+ );
32
35
 
33
36
  class RuntimeReceiptWaitTimeoutError extends Error {
34
37
  constructor(key: string) {
@@ -95,6 +95,60 @@ export function planWorkerToolReceiptGroups<TRequest>(
95
95
  };
96
96
  }
97
97
 
98
+ export function resolveWorkerToolReceiptGroupWaitMaxAttempts<TRequest>(
99
+ requests: TRequest[],
100
+ getWaitMaxAttempts: (request: TRequest) => number,
101
+ ): number {
102
+ return requests.reduce((maxAttempts, request) => {
103
+ const attempts = getWaitMaxAttempts(request);
104
+ return Number.isFinite(attempts) && attempts > maxAttempts
105
+ ? attempts
106
+ : maxAttempts;
107
+ }, 1);
108
+ }
109
+
110
+ export type WorkerToolRuntimeTimeoutRequest = {
111
+ input: Record<string, unknown>;
112
+ runtimeTimeoutMs?: number;
113
+ };
114
+
115
+ export type WorkerToolRuntimeTimeoutClaim = {
116
+ request: WorkerToolRuntimeTimeoutRequest;
117
+ followers: WorkerToolRuntimeTimeoutRequest[];
118
+ };
119
+
120
+ export function resolveWorkerToolRuntimeTimeoutMs(
121
+ claimedRequests: WorkerToolRuntimeTimeoutClaim[],
122
+ input: {
123
+ resolveOwnerTimeoutMs: (
124
+ request: WorkerToolRuntimeTimeoutRequest,
125
+ ) => number | undefined;
126
+ },
127
+ ): number | undefined {
128
+ let timeoutMs = 0;
129
+ for (const claimed of claimedRequests) {
130
+ const ownerTimeoutMs = input.resolveOwnerTimeoutMs(claimed.request);
131
+ if (
132
+ typeof ownerTimeoutMs === 'number' &&
133
+ Number.isFinite(ownerTimeoutMs) &&
134
+ ownerTimeoutMs > timeoutMs
135
+ ) {
136
+ timeoutMs = ownerTimeoutMs;
137
+ }
138
+ for (const follower of claimed.followers) {
139
+ const followerTimeoutMs = follower.runtimeTimeoutMs;
140
+ if (
141
+ typeof followerTimeoutMs === 'number' &&
142
+ Number.isFinite(followerTimeoutMs) &&
143
+ followerTimeoutMs > timeoutMs
144
+ ) {
145
+ timeoutMs = followerTimeoutMs;
146
+ }
147
+ }
148
+ }
149
+ return timeoutMs > 0 ? timeoutMs : undefined;
150
+ }
151
+
98
152
  export function markWorkerToolReceiptResultCached(
99
153
  value: unknown,
100
154
  cacheKey: string,
@@ -300,6 +300,10 @@ export type ToolExecutionRequest = {
300
300
  force?: boolean;
301
301
  /** Numeric TTL in seconds for this tool checkpoint. */
302
302
  staleAfterSeconds?: number;
303
+ /** Runtime transport timeout in milliseconds. This is not sent to the provider. */
304
+ timeoutMs?: number;
305
+ /** Follower wait budget in milliseconds before a running receipt is reclaimable. */
306
+ receiptWaitMs?: number;
303
307
  };
304
308
 
305
309
  export type StepResolver<Row, Value> = (
@@ -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.169',
107
+ version: '0.1.170',
108
108
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
109
109
  supportPolicy: {
110
- latest: '0.1.169',
110
+ latest: '0.1.170',
111
111
  minimumSupported: '0.1.53',
112
112
  deprecatedBelow: '0.1.53',
113
113
  commandMinimumSupported: [
@@ -72,6 +72,7 @@ import {
72
72
  import {
73
73
  RuntimeReceiptWaitTimeoutError,
74
74
  executeWithDurableRuntimeReceipt,
75
+ resolveRuntimeToolReceiptWaitMaxAttempts,
75
76
  runtimeReceiptOutput as durableRuntimeReceiptOutput,
76
77
  waitForCompletedRuntimeReceipt,
77
78
  type DurableReceiptExecutionStore,
@@ -709,6 +710,8 @@ export class PlayContextImpl {
709
710
  description?: string;
710
711
  force?: boolean;
711
712
  staleAfterSeconds?: number;
713
+ timeoutMs?: number;
714
+ receiptWaitMs?: number;
712
715
  }): Promise<TOutput> => {
713
716
  if (!request || typeof request !== 'object' || Array.isArray(request)) {
714
717
  throw new Error(
@@ -735,7 +738,9 @@ export class PlayContextImpl {
735
738
  request.input,
736
739
  request.description ||
737
740
  request.force === true ||
738
- request.staleAfterSeconds !== undefined
741
+ request.staleAfterSeconds !== undefined ||
742
+ request.timeoutMs !== undefined ||
743
+ request.receiptWaitMs !== undefined
739
744
  ? {
740
745
  ...(request.description
741
746
  ? { description: request.description }
@@ -744,6 +749,12 @@ export class PlayContextImpl {
744
749
  ...(request.staleAfterSeconds !== undefined
745
750
  ? { staleAfterSeconds: request.staleAfterSeconds }
746
751
  : {}),
752
+ ...(request.timeoutMs !== undefined
753
+ ? { timeoutMs: request.timeoutMs }
754
+ : {}),
755
+ ...(request.receiptWaitMs !== undefined
756
+ ? { receiptWaitMs: request.receiptWaitMs }
757
+ : {}),
747
758
  }
748
759
  : undefined,
749
760
  ) as Promise<TOutput>;
@@ -1283,10 +1294,12 @@ export class PlayContextImpl {
1283
1294
 
1284
1295
  private async waitForCompletedRuntimeToolReceipt(
1285
1296
  key: string,
1297
+ maxAttempts?: number,
1286
1298
  ): Promise<RuntimeStepReceipt> {
1287
1299
  return await waitForCompletedRuntimeReceipt({
1288
1300
  receiptKey: key,
1289
1301
  store: this.durableReceiptExecutionStore(),
1302
+ maxAttempts,
1290
1303
  });
1291
1304
  }
1292
1305
 
@@ -1301,6 +1314,7 @@ export class PlayContextImpl {
1301
1314
  staleAfterSeconds?: number | null;
1302
1315
  repairRunningReceiptForSameRun?: boolean;
1303
1316
  repairRunningReceiptForSameRunAfterWaitTimeout?: boolean;
1317
+ runningReceiptWaitMaxAttempts?: number;
1304
1318
  reclaimRunning?: boolean;
1305
1319
  markSkipped?: (output: T) => Promise<void> | void;
1306
1320
  onRecovered?: (
@@ -1332,6 +1346,7 @@ export class PlayContextImpl {
1332
1346
  repairRunningReceiptForSameRun: opts.repairRunningReceiptForSameRun,
1333
1347
  repairRunningReceiptForSameRunAfterWaitTimeout:
1334
1348
  opts.repairRunningReceiptForSameRunAfterWaitTimeout,
1349
+ runningReceiptWaitMaxAttempts: opts.runningReceiptWaitMaxAttempts,
1335
1350
  reclaimRunning: opts.reclaimRunning,
1336
1351
  markSkipped: opts.markSkipped,
1337
1352
  onRecovered: opts.onRecovered,
@@ -3449,7 +3464,9 @@ export class PlayContextImpl {
3449
3464
  });
3450
3465
  }
3451
3466
  this.log(`Calling tool: ${toolId}`);
3452
- const execution = await this.callToolExecutionAPI(toolId, input);
3467
+ const execution = await this.callToolExecutionAPI(toolId, input, {
3468
+ timeoutMs: options?.timeoutMs,
3469
+ });
3453
3470
  const wrapped = await this.wrapToolExecutionResult({
3454
3471
  toolId,
3455
3472
  status: execution.status,
@@ -3535,6 +3552,12 @@ export class PlayContextImpl {
3535
3552
  fieldName,
3536
3553
  toolId,
3537
3554
  input,
3555
+ ...(typeof options?.timeoutMs === 'number'
3556
+ ? { timeoutMs: options.timeoutMs }
3557
+ : {}),
3558
+ ...(typeof options?.receiptWaitMs === 'number'
3559
+ ? { receiptWaitMs: options.receiptWaitMs }
3560
+ : {}),
3538
3561
  tableNamespace: store.tableNamespace,
3539
3562
  rowKey: store.rowKey ?? null,
3540
3563
  description: normalizeStepDescription(options?.description),
@@ -3573,6 +3596,11 @@ export class PlayContextImpl {
3573
3596
  receiptKey: durableCacheKey,
3574
3597
  }),
3575
3598
  ),
3599
+ runningReceiptWaitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(
3600
+ typeof options?.receiptWaitMs === 'number'
3601
+ ? { max_wait_ms: options.receiptWaitMs }
3602
+ : input,
3603
+ ),
3576
3604
  execute: executeTool,
3577
3605
  },
3578
3606
  );
@@ -4334,6 +4362,49 @@ export class PlayContextImpl {
4334
4362
  await this.rejectToolCall(toolId, request, error);
4335
4363
  }
4336
4364
  };
4365
+ const requestsWithLiveFollowers = (
4366
+ owner: ToolCallRequest,
4367
+ ): ToolCallRequest[] => [
4368
+ owner,
4369
+ ...(liveFollowersByOwnerCallId.get(owner.callId) ?? []),
4370
+ ];
4371
+ const resolveRuntimeTimeoutMsForRequests = (
4372
+ requests: ToolCallRequest[],
4373
+ ): number | undefined => {
4374
+ const timeoutMs = Math.max(
4375
+ ...requests
4376
+ .map((request) => request.timeoutMs)
4377
+ .filter(
4378
+ (candidate): candidate is number =>
4379
+ typeof candidate === 'number' &&
4380
+ Number.isFinite(candidate) &&
4381
+ candidate > 0,
4382
+ ),
4383
+ 0,
4384
+ );
4385
+ return timeoutMs > 0 ? timeoutMs : undefined;
4386
+ };
4387
+ const resolveRuntimeTimeoutMsForClaimedOwners = (
4388
+ owners: ToolCallRequest[],
4389
+ ): number | undefined => {
4390
+ let timeoutMs = 0;
4391
+ for (const owner of owners) {
4392
+ if (
4393
+ typeof owner.timeoutMs !== 'number' ||
4394
+ !Number.isFinite(owner.timeoutMs) ||
4395
+ owner.timeoutMs <= 0
4396
+ ) {
4397
+ return undefined;
4398
+ }
4399
+ timeoutMs = Math.max(
4400
+ timeoutMs,
4401
+ resolveRuntimeTimeoutMsForRequests(
4402
+ requestsWithLiveFollowers(owner),
4403
+ ) ?? 0,
4404
+ );
4405
+ }
4406
+ return timeoutMs > 0 ? timeoutMs : undefined;
4407
+ };
4337
4408
 
4338
4409
  const durableExistingRunningHandlers: Promise<void>[] = [];
4339
4410
  if (
@@ -4452,10 +4523,25 @@ export class PlayContextImpl {
4452
4523
  receiptKey: string,
4453
4524
  requestsForKey: ToolCallRequest[],
4454
4525
  ): Promise<void> => {
4526
+ const waitMaxAttempts =
4527
+ requestsForKey.length > 0
4528
+ ? Math.max(
4529
+ ...requestsForKey.map((request) =>
4530
+ resolveRuntimeToolReceiptWaitMaxAttempts(
4531
+ typeof request.receiptWaitMs === 'number'
4532
+ ? { max_wait_ms: request.receiptWaitMs }
4533
+ : request.input,
4534
+ ),
4535
+ ),
4536
+ )
4537
+ : undefined;
4455
4538
  try {
4456
4539
  await resolveRequestsFromReceipt(
4457
4540
  requestsForKey,
4458
- await this.waitForCompletedRuntimeToolReceipt(receiptKey),
4541
+ await this.waitForCompletedRuntimeToolReceipt(
4542
+ receiptKey,
4543
+ waitMaxAttempts,
4544
+ ),
4459
4545
  'in_flight',
4460
4546
  );
4461
4547
  return;
@@ -4511,6 +4597,9 @@ export class PlayContextImpl {
4511
4597
  const execution = await this.callToolExecutionAPI(
4512
4598
  toolId,
4513
4599
  owner.input,
4600
+ {
4601
+ timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([owner]),
4602
+ },
4514
4603
  );
4515
4604
  const result = await this.resolveToolCall(
4516
4605
  toolId,
@@ -4619,6 +4708,11 @@ export class PlayContextImpl {
4619
4708
  await this.callToolAPI(
4620
4709
  batch.batchOperation,
4621
4710
  batch.batchPayload,
4711
+ {
4712
+ timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners(
4713
+ batch.memberRequests,
4714
+ ),
4715
+ },
4622
4716
  ),
4623
4717
  onChunkComplete: async (chunkResults) => {
4624
4718
  for (const entry of chunkResults) {
@@ -4674,6 +4768,11 @@ export class PlayContextImpl {
4674
4768
  const execution = await this.callToolExecutionAPI(
4675
4769
  toolId,
4676
4770
  request.input,
4771
+ {
4772
+ timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([
4773
+ request,
4774
+ ]),
4775
+ },
4677
4776
  );
4678
4777
  const result = await this.resolveToolCall(
4679
4778
  toolId,
@@ -4778,8 +4877,9 @@ export class PlayContextImpl {
4778
4877
  private async callToolAPI(
4779
4878
  toolId: string,
4780
4879
  input: Record<string, unknown>,
4880
+ options?: { timeoutMs?: number },
4781
4881
  ): Promise<unknown> {
4782
- const execution = await this.callToolExecutionAPI(toolId, input);
4882
+ const execution = await this.callToolExecutionAPI(toolId, input, options);
4783
4883
  if (execution.toolResponse && 'raw' in execution.toolResponse) {
4784
4884
  return execution.toolResponse.raw;
4785
4885
  }
@@ -4794,6 +4894,7 @@ export class PlayContextImpl {
4794
4894
  private async callToolExecutionAPI(
4795
4895
  toolId: string,
4796
4896
  input: Record<string, unknown>,
4897
+ options?: { timeoutMs?: number },
4797
4898
  ): Promise<ParsedToolExecuteResponse> {
4798
4899
  if (!this.#options.executorToken || !this.#options.baseUrl) {
4799
4900
  throw new Error(
@@ -4801,6 +4902,11 @@ export class PlayContextImpl {
4801
4902
  );
4802
4903
  }
4803
4904
  const url = `${this.#options.baseUrl}/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
4905
+ const timeoutMs =
4906
+ typeof options?.timeoutMs === 'number' &&
4907
+ Number.isFinite(options.timeoutMs)
4908
+ ? Math.max(1, Math.ceil(options.timeoutMs))
4909
+ : undefined;
4804
4910
 
4805
4911
  // The Governor's tool slot is the single seam for tool-call budget + the
4806
4912
  // global tool-concurrency backstop + per-(org, provider) pacing. It blocks
@@ -4831,9 +4937,20 @@ export class PlayContextImpl {
4831
4937
 
4832
4938
  while (true) {
4833
4939
  let response: Response;
4940
+ const abortController = timeoutMs ? new AbortController() : null;
4941
+ const timeoutHandle = timeoutMs
4942
+ ? setTimeout(() => {
4943
+ abortController?.abort(
4944
+ new Error(
4945
+ `Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
4946
+ ),
4947
+ );
4948
+ }, timeoutMs)
4949
+ : null;
4834
4950
  try {
4835
4951
  response = await fetch(url, {
4836
4952
  method: 'POST',
4953
+ signal: abortController?.signal,
4837
4954
  headers: {
4838
4955
  'Content-Type': 'application/json',
4839
4956
  Authorization: `Bearer ${this.#options.executorToken}`,
@@ -4856,6 +4973,13 @@ export class PlayContextImpl {
4856
4973
  }),
4857
4974
  });
4858
4975
  } catch (error) {
4976
+ if (abortController?.signal.aborted) {
4977
+ throw abortController.signal.reason instanceof Error
4978
+ ? abortController.signal.reason
4979
+ : new Error(
4980
+ `Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
4981
+ );
4982
+ }
4859
4983
  transportAttempt += 1;
4860
4984
  const message =
4861
4985
  error instanceof Error ? error.message : String(error);
@@ -4876,6 +5000,10 @@ export class PlayContextImpl {
4876
5000
  throw new Error(
4877
5001
  `Tool ${toolId} transport failed calling ${url} after ${transportAttempt}/${TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS} attempts: ${message}`,
4878
5002
  );
5003
+ } finally {
5004
+ if (timeoutHandle) {
5005
+ clearTimeout(timeoutHandle);
5006
+ }
4879
5007
  }
4880
5008
 
4881
5009
  span.setAttribute('plays.http_status_code', response.status);
@@ -28,6 +28,8 @@ export interface ToolCallRequest {
28
28
  fieldName?: string;
29
29
  toolId: string;
30
30
  input: Record<string, unknown>;
31
+ timeoutMs?: number;
32
+ receiptWaitMs?: number;
31
33
  tableNamespace?: string;
32
34
  rowKey?: string | null;
33
35
  description?: string;
@@ -169,6 +171,8 @@ export interface ToolCallOptions {
169
171
  description?: string;
170
172
  force?: boolean;
171
173
  staleAfterSeconds?: number;
174
+ timeoutMs?: number;
175
+ receiptWaitMs?: number;
172
176
  }
173
177
 
174
178
  export interface ToolExecutionRequest {
@@ -178,6 +182,8 @@ export interface ToolExecutionRequest {
178
182
  description?: string;
179
183
  force?: boolean;
180
184
  staleAfterSeconds?: number;
185
+ timeoutMs?: number;
186
+ receiptWaitMs?: number;
181
187
  }
182
188
 
183
189
  export type SqlQuery = {
@@ -453,9 +459,7 @@ export interface ContextOptions {
453
459
  getToolRetryPolicy?: (toolId: string) => Promise<{
454
460
  retrySafeTransientHttp?: boolean;
455
461
  } | null>;
456
- getToolActionCacheVersion?: (
457
- toolId: string,
458
- ) => Promise<string> | string;
462
+ getToolActionCacheVersion?: (toolId: string) => Promise<string> | string;
459
463
  getToolTargetGetters?: (
460
464
  toolId: string,
461
465
  output: string,
package/dist/cli/index.js CHANGED
@@ -622,10 +622,10 @@ var SDK_RELEASE = {
622
622
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
623
623
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
624
624
  // fields shipped in 0.1.153.
625
- version: "0.1.169",
625
+ version: "0.1.170",
626
626
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
627
627
  supportPolicy: {
628
- latest: "0.1.169",
628
+ latest: "0.1.170",
629
629
  minimumSupported: "0.1.53",
630
630
  deprecatedBelow: "0.1.53",
631
631
  commandMinimumSupported: [
@@ -607,10 +607,10 @@ var SDK_RELEASE = {
607
607
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
608
608
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
609
609
  // fields shipped in 0.1.153.
610
- version: "0.1.169",
610
+ version: "0.1.170",
611
611
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
612
612
  supportPolicy: {
613
- latest: "0.1.169",
613
+ latest: "0.1.170",
614
614
  minimumSupported: "0.1.53",
615
615
  deprecatedBelow: "0.1.53",
616
616
  commandMinimumSupported: [
package/dist/index.d.mts CHANGED
@@ -2948,6 +2948,10 @@ type ToolExecutionRequest = {
2948
2948
  force?: boolean;
2949
2949
  /** Numeric TTL in seconds for this tool checkpoint. */
2950
2950
  staleAfterSeconds?: number;
2951
+ /** Runtime transport timeout in milliseconds. This is not sent to the provider. */
2952
+ timeoutMs?: number;
2953
+ /** Follower wait budget in milliseconds before a running receipt is reclaimable. */
2954
+ receiptWaitMs?: number;
2951
2955
  };
2952
2956
  type StepResolver<Row, Value> = (row: Row, ctx: DeeplinePlayRuntimeContext, index: number, previousCell?: PreviousCell<Value>) => Value | Promise<Value>;
2953
2957
  /**
package/dist/index.d.ts CHANGED
@@ -2948,6 +2948,10 @@ type ToolExecutionRequest = {
2948
2948
  force?: boolean;
2949
2949
  /** Numeric TTL in seconds for this tool checkpoint. */
2950
2950
  staleAfterSeconds?: number;
2951
+ /** Runtime transport timeout in milliseconds. This is not sent to the provider. */
2952
+ timeoutMs?: number;
2953
+ /** Follower wait budget in milliseconds before a running receipt is reclaimable. */
2954
+ receiptWaitMs?: number;
2951
2955
  };
2952
2956
  type StepResolver<Row, Value> = (row: Row, ctx: DeeplinePlayRuntimeContext, index: number, previousCell?: PreviousCell<Value>) => Value | Promise<Value>;
2953
2957
  /**
package/dist/index.js CHANGED
@@ -421,10 +421,10 @@ var SDK_RELEASE = {
421
421
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
422
422
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
423
423
  // fields shipped in 0.1.153.
424
- version: "0.1.169",
424
+ version: "0.1.170",
425
425
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
426
426
  supportPolicy: {
427
- latest: "0.1.169",
427
+ latest: "0.1.170",
428
428
  minimumSupported: "0.1.53",
429
429
  deprecatedBelow: "0.1.53",
430
430
  commandMinimumSupported: [
package/dist/index.mjs CHANGED
@@ -351,10 +351,10 @@ var SDK_RELEASE = {
351
351
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
352
352
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
353
353
  // fields shipped in 0.1.153.
354
- version: "0.1.169",
354
+ version: "0.1.170",
355
355
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
356
356
  supportPolicy: {
357
- latest: "0.1.169",
357
+ latest: "0.1.170",
358
358
  minimumSupported: "0.1.53",
359
359
  deprecatedBelow: "0.1.53",
360
360
  commandMinimumSupported: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.169",
3
+ "version": "0.1.170",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {