deepline 0.1.166 → 0.1.168
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/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +296 -120
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +12 -2
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +80 -6
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +77 -8
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +1 -1
|
@@ -148,10 +148,13 @@ import {
|
|
|
148
148
|
} from './runtime/receipts';
|
|
149
149
|
import {
|
|
150
150
|
RuntimeReceiptWaitTimeoutError,
|
|
151
|
+
resolveRuntimeToolReceiptWaitMaxAttempts,
|
|
152
|
+
resolveRuntimeToolReceiptWaitTimeoutMs,
|
|
151
153
|
waitForCompletedRuntimeReceipt,
|
|
152
154
|
} from '../../../shared_libs/play-runtime/durable-receipt-execution';
|
|
153
155
|
import type { RuntimeStepReceipt } from '../../../shared_libs/play-runtime/ctx-types';
|
|
154
156
|
import {
|
|
157
|
+
canReclaimFailedWorkerToolReceipt,
|
|
155
158
|
canReclaimTimedOutWorkerToolReceipt,
|
|
156
159
|
markWorkerToolReceiptResultCached,
|
|
157
160
|
markWorkerToolReceiptResultExecution,
|
|
@@ -531,20 +534,74 @@ const RUNTIME_API_INTEGRATION_EXECUTE_TIMEOUT_MS = 180_000;
|
|
|
531
534
|
const RUNTIME_API_RETRY_DELAYS_MS = [
|
|
532
535
|
250, 750, 1500, 3000, 5000, 10000,
|
|
533
536
|
] as const;
|
|
537
|
+
const STANDARD_PLAY_RUNTIME_LIMIT_MS =
|
|
538
|
+
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS * 1000;
|
|
539
|
+
const STANDARD_PLAY_RUNTIME_LIMIT_MESSAGE = `Based on this plan, max runtime is ${STANDARD_PLAY_RUNTIME_LIMIT_LABEL}. Use smaller batches; ask for runtime.`;
|
|
540
|
+
|
|
541
|
+
function resolveRuntimeDeadlineRemainingMs(runtimeDeadlineMs?: number): number {
|
|
542
|
+
if (
|
|
543
|
+
typeof runtimeDeadlineMs !== 'number' ||
|
|
544
|
+
!Number.isFinite(runtimeDeadlineMs)
|
|
545
|
+
) {
|
|
546
|
+
return Number.POSITIVE_INFINITY;
|
|
547
|
+
}
|
|
548
|
+
const remainingMs = Math.floor(runtimeDeadlineMs - nowMs());
|
|
549
|
+
if (remainingMs <= 0) {
|
|
550
|
+
throw new WorkflowAbortError(STANDARD_PLAY_RUNTIME_LIMIT_MESSAGE);
|
|
551
|
+
}
|
|
552
|
+
return remainingMs;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function resolveToolRuntimeApiTimeout(input: {
|
|
556
|
+
requestInput: Record<string, unknown>;
|
|
557
|
+
runtimeDeadlineMs?: number;
|
|
558
|
+
}): { timeoutMs: number; timeoutErrorMessage?: string } {
|
|
559
|
+
const toolTimeoutMs = resolveRuntimeToolReceiptWaitTimeoutMs(
|
|
560
|
+
input.requestInput,
|
|
561
|
+
);
|
|
562
|
+
const remainingMs = resolveRuntimeDeadlineRemainingMs(
|
|
563
|
+
input.runtimeDeadlineMs,
|
|
564
|
+
);
|
|
565
|
+
if (remainingMs < toolTimeoutMs) {
|
|
566
|
+
return {
|
|
567
|
+
timeoutMs: Math.max(1, remainingMs),
|
|
568
|
+
timeoutErrorMessage: STANDARD_PLAY_RUNTIME_LIMIT_MESSAGE,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return { timeoutMs: toolTimeoutMs };
|
|
572
|
+
}
|
|
534
573
|
|
|
535
574
|
async function fetchRuntimeApi(
|
|
536
575
|
baseUrl: string,
|
|
537
576
|
path: string,
|
|
538
577
|
init: RequestInit,
|
|
578
|
+
options?: {
|
|
579
|
+
timeoutMsOverride?: number;
|
|
580
|
+
timeoutErrorMessage?: string;
|
|
581
|
+
abortSignal?: AbortSignal;
|
|
582
|
+
},
|
|
539
583
|
): Promise<Response> {
|
|
540
584
|
const timeoutMs =
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
585
|
+
typeof options?.timeoutMsOverride === 'number' &&
|
|
586
|
+
Number.isFinite(options.timeoutMsOverride)
|
|
587
|
+
? Math.max(1, Math.ceil(options.timeoutMsOverride))
|
|
588
|
+
: path === '/api/v2/plays/run'
|
|
589
|
+
? RUNTIME_API_PLAY_RUN_TIMEOUT_MS
|
|
590
|
+
: path === '/api/v2/plays/internal/egress-fetch'
|
|
591
|
+
? RUNTIME_API_EGRESS_FETCH_TIMEOUT_MS
|
|
592
|
+
: /^\/api\/v2\/integrations\/[^/]+\/execute$/.test(path)
|
|
593
|
+
? RUNTIME_API_INTEGRATION_EXECUTE_TIMEOUT_MS
|
|
594
|
+
: RUNTIME_API_TIMEOUT_MS;
|
|
595
|
+
const abortSignal = options?.abortSignal;
|
|
596
|
+
const abortError = () =>
|
|
597
|
+
abortSignal?.reason instanceof Error
|
|
598
|
+
? abortSignal.reason
|
|
599
|
+
: new Error(
|
|
600
|
+
`[play-harness] runtime API call aborted. path=${path} baseUrl=${baseUrl}`,
|
|
601
|
+
);
|
|
602
|
+
if (abortSignal?.aborted) {
|
|
603
|
+
throw abortError();
|
|
604
|
+
}
|
|
548
605
|
const controller = new AbortController();
|
|
549
606
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
550
607
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
@@ -552,7 +609,8 @@ async function fetchRuntimeApi(
|
|
|
552
609
|
controller.abort();
|
|
553
610
|
reject(
|
|
554
611
|
new Error(
|
|
555
|
-
|
|
612
|
+
options?.timeoutErrorMessage ??
|
|
613
|
+
`[play-harness] runtime API call timed out after ${timeoutMs}ms. path=${path} baseUrl=${baseUrl}`,
|
|
556
614
|
),
|
|
557
615
|
);
|
|
558
616
|
}, timeoutMs);
|
|
@@ -566,6 +624,9 @@ async function fetchRuntimeApi(
|
|
|
566
624
|
if (!cachedRuntimeApiBinding) {
|
|
567
625
|
throw new Error('[play-harness] RUNTIME_API service binding is required');
|
|
568
626
|
}
|
|
627
|
+
if (abortSignal?.aborted) {
|
|
628
|
+
throw abortError();
|
|
629
|
+
}
|
|
569
630
|
const responsePromise = callRuntimeApiRpcBinding(
|
|
570
631
|
cachedRuntimeApiBinding,
|
|
571
632
|
mergedInit,
|
|
@@ -575,6 +636,10 @@ async function fetchRuntimeApi(
|
|
|
575
636
|
timeoutMs,
|
|
576
637
|
},
|
|
577
638
|
);
|
|
639
|
+
// RUNTIME_API service bindings do not consume RequestInit.signal once the RPC
|
|
640
|
+
// is in flight. After dispatch, wait for the owner call to settle so the
|
|
641
|
+
// durable receipt records the real provider result instead of failing early
|
|
642
|
+
// and inviting duplicate provider work on retry.
|
|
578
643
|
const response = await Promise.race([responsePromise, timeoutPromise]);
|
|
579
644
|
if (await isRuntimeApiBindingNotFoundResponse(response)) {
|
|
580
645
|
throw new Error(
|
|
@@ -585,7 +650,8 @@ async function fetchRuntimeApi(
|
|
|
585
650
|
} catch (err) {
|
|
586
651
|
if (err instanceof Error && err.name === 'AbortError') {
|
|
587
652
|
throw new Error(
|
|
588
|
-
|
|
653
|
+
options?.timeoutErrorMessage ??
|
|
654
|
+
`[play-harness] runtime API call timed out after ${timeoutMs}ms. path=${path} baseUrl=${baseUrl}`,
|
|
589
655
|
);
|
|
590
656
|
}
|
|
591
657
|
throw err;
|
|
@@ -1188,6 +1254,8 @@ async function executeTool(
|
|
|
1188
1254
|
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
1189
1255
|
onRetryAttempt?: () => void,
|
|
1190
1256
|
transientHttpRetrySafe = false,
|
|
1257
|
+
abortSignal?: AbortSignal,
|
|
1258
|
+
runtimeDeadlineMs?: number,
|
|
1191
1259
|
): Promise<ToolExecuteResult> {
|
|
1192
1260
|
if (args.toolId === 'test_wait_for_event' && workflowStep) {
|
|
1193
1261
|
const result = await waitForSyntheticIntegrationEvent(
|
|
@@ -1208,6 +1276,8 @@ async function executeTool(
|
|
|
1208
1276
|
onProviderBackpressure,
|
|
1209
1277
|
onRetryAttempt,
|
|
1210
1278
|
transientHttpRetrySafe,
|
|
1279
|
+
abortSignal,
|
|
1280
|
+
runtimeDeadlineMs,
|
|
1211
1281
|
);
|
|
1212
1282
|
}
|
|
1213
1283
|
|
|
@@ -1219,6 +1289,8 @@ async function executeToolWithLifecycle(
|
|
|
1219
1289
|
onProviderBackpressure?: (retryAfterMs: number) => void,
|
|
1220
1290
|
onRetryAttempt?: () => void,
|
|
1221
1291
|
transientHttpRetrySafe = false,
|
|
1292
|
+
abortSignal?: AbortSignal,
|
|
1293
|
+
runtimeDeadlineMs?: number,
|
|
1222
1294
|
): Promise<ToolExecuteResult> {
|
|
1223
1295
|
callbacks?.onToolCalled?.(args.toolId, nowMs());
|
|
1224
1296
|
try {
|
|
@@ -1229,6 +1301,8 @@ async function executeToolWithLifecycle(
|
|
|
1229
1301
|
onProviderBackpressure,
|
|
1230
1302
|
onRetryAttempt,
|
|
1231
1303
|
transientHttpRetrySafe,
|
|
1304
|
+
abortSignal,
|
|
1305
|
+
runtimeDeadlineMs,
|
|
1232
1306
|
);
|
|
1233
1307
|
} catch (error) {
|
|
1234
1308
|
callbacks?.onToolFailed?.(args.toolId, nowMs());
|
|
@@ -1365,6 +1439,8 @@ async function callToolDirect(
|
|
|
1365
1439
|
// policy.budgets.maxRetryCount effectively unenforced.
|
|
1366
1440
|
onRetryAttempt?: () => void,
|
|
1367
1441
|
transientHttpRetrySafe = false,
|
|
1442
|
+
abortSignal?: AbortSignal,
|
|
1443
|
+
runtimeDeadlineMs?: number,
|
|
1368
1444
|
): Promise<ToolExecuteResult> {
|
|
1369
1445
|
const { id, toolId, input } = args;
|
|
1370
1446
|
const path = `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`;
|
|
@@ -1377,20 +1453,33 @@ async function callToolDirect(
|
|
|
1377
1453
|
requestAttempt += 1;
|
|
1378
1454
|
let res: Response;
|
|
1379
1455
|
try {
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
'content-type': 'application/json',
|
|
1384
|
-
authorization: `Bearer ${req.executorToken}`,
|
|
1385
|
-
'x-deepline-request-id': `${req.runId}:${toolId}:${id}:attempt:${requestAttempt}`,
|
|
1386
|
-
[EXECUTE_RESPONSE_CONTRACT_HEADER]: V2_EXECUTE_RESPONSE_CONTRACT,
|
|
1387
|
-
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
1388
|
-
},
|
|
1389
|
-
body: JSON.stringify({
|
|
1390
|
-
payload: input,
|
|
1391
|
-
metadata: { parent_run_id: req.runId },
|
|
1392
|
-
}),
|
|
1456
|
+
const runtimeApiTimeout = resolveToolRuntimeApiTimeout({
|
|
1457
|
+
requestInput: input,
|
|
1458
|
+
runtimeDeadlineMs,
|
|
1393
1459
|
});
|
|
1460
|
+
res = await fetchRuntimeApi(
|
|
1461
|
+
req.baseUrl,
|
|
1462
|
+
path,
|
|
1463
|
+
{
|
|
1464
|
+
method: 'POST',
|
|
1465
|
+
headers: {
|
|
1466
|
+
'content-type': 'application/json',
|
|
1467
|
+
authorization: `Bearer ${req.executorToken}`,
|
|
1468
|
+
'x-deepline-request-id': `${req.runId}:${toolId}:${id}:attempt:${requestAttempt}`,
|
|
1469
|
+
[EXECUTE_RESPONSE_CONTRACT_HEADER]: V2_EXECUTE_RESPONSE_CONTRACT,
|
|
1470
|
+
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
1471
|
+
},
|
|
1472
|
+
body: JSON.stringify({
|
|
1473
|
+
payload: input,
|
|
1474
|
+
metadata: { parent_run_id: req.runId },
|
|
1475
|
+
}),
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
timeoutMsOverride: runtimeApiTimeout.timeoutMs,
|
|
1479
|
+
timeoutErrorMessage: runtimeApiTimeout.timeoutErrorMessage,
|
|
1480
|
+
abortSignal,
|
|
1481
|
+
},
|
|
1482
|
+
);
|
|
1394
1483
|
} catch (error) {
|
|
1395
1484
|
transportAttempt += 1;
|
|
1396
1485
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1538,6 +1627,7 @@ type WorkerToolBatchRequest = {
|
|
|
1538
1627
|
cacheKey: string;
|
|
1539
1628
|
receiptKey: string | null;
|
|
1540
1629
|
force: boolean;
|
|
1630
|
+
receiptWaitMaxAttempts: number;
|
|
1541
1631
|
toolId: string;
|
|
1542
1632
|
input: Record<string, unknown>;
|
|
1543
1633
|
workflowStep?: WorkflowStep;
|
|
@@ -1641,6 +1731,7 @@ class WorkerToolBatchScheduler {
|
|
|
1641
1731
|
private readonly callbacks?: WorkerCtxCallbacks,
|
|
1642
1732
|
private readonly receiptStore?: WorkerRuntimeReceiptStore,
|
|
1643
1733
|
private readonly allowLocalRetryReceipts = false,
|
|
1734
|
+
private readonly runtimeDeadlineMs?: number,
|
|
1644
1735
|
) {}
|
|
1645
1736
|
|
|
1646
1737
|
/**
|
|
@@ -1685,6 +1776,7 @@ class WorkerToolBatchScheduler {
|
|
|
1685
1776
|
cacheKey: receiptKey,
|
|
1686
1777
|
receiptKey,
|
|
1687
1778
|
force: options?.force === true,
|
|
1779
|
+
receiptWaitMaxAttempts: resolveRuntimeToolReceiptWaitMaxAttempts(input),
|
|
1688
1780
|
toolId,
|
|
1689
1781
|
input,
|
|
1690
1782
|
workflowStep,
|
|
@@ -1747,19 +1839,19 @@ class WorkerToolBatchScheduler {
|
|
|
1747
1839
|
}
|
|
1748
1840
|
}
|
|
1749
1841
|
|
|
1750
|
-
private async waitForDurableToolReceipt(
|
|
1751
|
-
receiptKey: string
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
}
|
|
1756
|
-
if (!this.receiptStore.getReceipt) {
|
|
1842
|
+
private async waitForDurableToolReceipt(input: {
|
|
1843
|
+
receiptKey: string;
|
|
1844
|
+
maxAttempts: number;
|
|
1845
|
+
}): Promise<WorkerRuntimeReceipt> {
|
|
1846
|
+
if (!this.receiptStore?.getReceipt) {
|
|
1757
1847
|
throw new Error(
|
|
1758
|
-
'Worker durable tool receipt wait requires
|
|
1848
|
+
'Worker durable tool receipt wait requires receipt lookup.',
|
|
1759
1849
|
);
|
|
1760
1850
|
}
|
|
1761
1851
|
const receipt = await waitForCompletedRuntimeReceipt({
|
|
1762
|
-
receiptKey,
|
|
1852
|
+
receiptKey: input.receiptKey,
|
|
1853
|
+
maxAttempts: input.maxAttempts,
|
|
1854
|
+
abortSignal: this.abortSignal,
|
|
1763
1855
|
store: {
|
|
1764
1856
|
getMany: async (receiptKeys) => {
|
|
1765
1857
|
const receipts = new Map<string, RuntimeStepReceipt>();
|
|
@@ -1783,11 +1875,7 @@ class WorkerToolBatchScheduler {
|
|
|
1783
1875
|
},
|
|
1784
1876
|
},
|
|
1785
1877
|
});
|
|
1786
|
-
return
|
|
1787
|
-
deserializeDurableStepValue(receipt.output),
|
|
1788
|
-
receiptKey,
|
|
1789
|
-
receiptKey,
|
|
1790
|
-
);
|
|
1878
|
+
return receipt;
|
|
1791
1879
|
}
|
|
1792
1880
|
|
|
1793
1881
|
private settleRequests(
|
|
@@ -1830,24 +1918,73 @@ class WorkerToolBatchScheduler {
|
|
|
1830
1918
|
this.onRequestsSettled?.(requests.length);
|
|
1831
1919
|
}
|
|
1832
1920
|
|
|
1833
|
-
private async
|
|
1921
|
+
private async resolveCompletedDurableToolReceiptGroup(input: {
|
|
1922
|
+
group: WorkerToolBatchRequest[];
|
|
1923
|
+
receiptKey: string;
|
|
1924
|
+
receipt: WorkerRuntimeReceipt;
|
|
1925
|
+
source: 'cache' | 'in_flight';
|
|
1926
|
+
}): Promise<void> {
|
|
1927
|
+
const [first] = input.group;
|
|
1928
|
+
if (!first) return;
|
|
1929
|
+
const value = deserializeDurableStepValue(input.receipt.output);
|
|
1930
|
+
const result =
|
|
1931
|
+
input.source === 'cache'
|
|
1932
|
+
? markWorkerToolReceiptResultCached(
|
|
1933
|
+
value,
|
|
1934
|
+
first.cacheKey,
|
|
1935
|
+
input.receiptKey,
|
|
1936
|
+
)
|
|
1937
|
+
: markWorkerToolReceiptResultExecution(value, {
|
|
1938
|
+
kind: 'in_flight',
|
|
1939
|
+
receiptKey: input.receiptKey,
|
|
1940
|
+
attachedToReceiptKey: input.receiptKey,
|
|
1941
|
+
});
|
|
1942
|
+
for (const request of input.group) {
|
|
1943
|
+
request.resolve(result);
|
|
1944
|
+
}
|
|
1945
|
+
this.onRequestsSettled?.(input.group.length);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
private async reclaimRunningDurableToolReceiptGroupAfterWait(input: {
|
|
1834
1949
|
group: WorkerToolBatchRequest[];
|
|
1835
1950
|
receiptKey: string;
|
|
1836
1951
|
runningReceipt: WorkerRuntimeReceipt;
|
|
1837
|
-
waitError: unknown;
|
|
1838
1952
|
}): Promise<ClaimedWorkerToolBatchRequest[]> {
|
|
1839
1953
|
const [request, ...followers] = input.group;
|
|
1840
1954
|
if (!request || !this.receiptStore) {
|
|
1841
|
-
this.rejectRawRequests(
|
|
1955
|
+
this.rejectRawRequests(
|
|
1956
|
+
input.group,
|
|
1957
|
+
new RuntimeReceiptWaitTimeoutError(input.receiptKey),
|
|
1958
|
+
);
|
|
1842
1959
|
return [];
|
|
1843
1960
|
}
|
|
1961
|
+
let waitError: unknown = null;
|
|
1962
|
+
try {
|
|
1963
|
+
const receipt = await this.waitForDurableToolReceipt({
|
|
1964
|
+
receiptKey: input.receiptKey,
|
|
1965
|
+
maxAttempts: request.receiptWaitMaxAttempts,
|
|
1966
|
+
});
|
|
1967
|
+
await this.resolveCompletedDurableToolReceiptGroup({
|
|
1968
|
+
group: input.group,
|
|
1969
|
+
receiptKey: input.receiptKey,
|
|
1970
|
+
receipt,
|
|
1971
|
+
source: 'in_flight',
|
|
1972
|
+
});
|
|
1973
|
+
return [];
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
if (!(error instanceof RuntimeReceiptWaitTimeoutError)) {
|
|
1976
|
+
this.rejectRawRequests(input.group, error);
|
|
1977
|
+
return [];
|
|
1978
|
+
}
|
|
1979
|
+
waitError = error;
|
|
1980
|
+
}
|
|
1844
1981
|
if (
|
|
1845
1982
|
!canReclaimTimedOutWorkerToolReceipt({
|
|
1846
1983
|
ownerRunId: input.runningReceipt.runId,
|
|
1847
1984
|
currentRunId: this.req.runId,
|
|
1848
1985
|
})
|
|
1849
1986
|
) {
|
|
1850
|
-
this.rejectRawRequests(input.group,
|
|
1987
|
+
this.rejectRawRequests(input.group, waitError);
|
|
1851
1988
|
return [];
|
|
1852
1989
|
}
|
|
1853
1990
|
let claim: WorkerRuntimeReceiptClaim;
|
|
@@ -1866,27 +2003,92 @@ class WorkerToolBatchScheduler {
|
|
|
1866
2003
|
return [{ request, receiptKey: input.receiptKey, followers }];
|
|
1867
2004
|
}
|
|
1868
2005
|
if (claim.disposition === 'reused') {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
pending.resolve(result);
|
|
1876
|
-
}
|
|
1877
|
-
this.onRequestsSettled?.(input.group.length);
|
|
2006
|
+
await this.resolveCompletedDurableToolReceiptGroup({
|
|
2007
|
+
group: input.group,
|
|
2008
|
+
receiptKey: input.receiptKey,
|
|
2009
|
+
receipt: claim.receipt,
|
|
2010
|
+
source: 'cache',
|
|
2011
|
+
});
|
|
1878
2012
|
return [];
|
|
1879
2013
|
}
|
|
1880
2014
|
if (claim.disposition === 'failed') {
|
|
1881
|
-
this.
|
|
1882
|
-
input.group,
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2015
|
+
return await this.reclaimFailedDurableToolReceiptGroup({
|
|
2016
|
+
group: input.group,
|
|
2017
|
+
receiptKey: input.receiptKey,
|
|
2018
|
+
failedReceipt: claim.receipt,
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
this.rejectRawRequests(input.group, waitError);
|
|
2022
|
+
return [];
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
private async reclaimFailedDurableToolReceiptGroup(input: {
|
|
2026
|
+
group: WorkerToolBatchRequest[];
|
|
2027
|
+
receiptKey: string;
|
|
2028
|
+
failedReceipt: WorkerRuntimeReceipt;
|
|
2029
|
+
}): Promise<ClaimedWorkerToolBatchRequest[]> {
|
|
2030
|
+
const failedError = new Error(
|
|
2031
|
+
`Durable tool call ${input.receiptKey} failed: ${input.failedReceipt.error ?? 'unknown error'}`,
|
|
2032
|
+
);
|
|
2033
|
+
const [request, ...followers] = input.group;
|
|
2034
|
+
if (!request || !this.receiptStore) {
|
|
2035
|
+
this.rejectRawRequests(input.group, failedError);
|
|
2036
|
+
return [];
|
|
2037
|
+
}
|
|
2038
|
+
if (
|
|
2039
|
+
!canReclaimFailedWorkerToolReceipt({
|
|
2040
|
+
error: input.failedReceipt.error,
|
|
2041
|
+
})
|
|
2042
|
+
) {
|
|
2043
|
+
this.rejectRawRequests(input.group, failedError);
|
|
2044
|
+
return [];
|
|
2045
|
+
}
|
|
2046
|
+
let claim: WorkerRuntimeReceiptClaim;
|
|
2047
|
+
try {
|
|
2048
|
+
claim = await this.receiptStore.claimReceipt({
|
|
2049
|
+
playName: this.req.playName,
|
|
2050
|
+
runId: this.req.runId,
|
|
2051
|
+
key: input.receiptKey,
|
|
2052
|
+
});
|
|
2053
|
+
} catch (error) {
|
|
2054
|
+
this.rejectRawRequests(input.group, error);
|
|
2055
|
+
return [];
|
|
2056
|
+
}
|
|
2057
|
+
if (claim.disposition === 'claimed') {
|
|
2058
|
+
return [{ request, receiptKey: input.receiptKey, followers }];
|
|
2059
|
+
}
|
|
2060
|
+
if (claim.disposition === 'reused') {
|
|
2061
|
+
await this.resolveCompletedDurableToolReceiptGroup({
|
|
2062
|
+
group: input.group,
|
|
2063
|
+
receiptKey: input.receiptKey,
|
|
2064
|
+
receipt: claim.receipt,
|
|
2065
|
+
source: 'cache',
|
|
2066
|
+
});
|
|
2067
|
+
return [];
|
|
2068
|
+
}
|
|
2069
|
+
if (claim.disposition === 'running') {
|
|
2070
|
+
try {
|
|
2071
|
+
const receipt = await this.waitForDurableToolReceipt({
|
|
2072
|
+
receiptKey: input.receiptKey,
|
|
2073
|
+
maxAttempts: request.receiptWaitMaxAttempts,
|
|
2074
|
+
});
|
|
2075
|
+
await this.resolveCompletedDurableToolReceiptGroup({
|
|
2076
|
+
group: input.group,
|
|
2077
|
+
receiptKey: input.receiptKey,
|
|
2078
|
+
receipt,
|
|
2079
|
+
source: 'in_flight',
|
|
2080
|
+
});
|
|
2081
|
+
} catch (error) {
|
|
2082
|
+
this.rejectRawRequests(input.group, error);
|
|
2083
|
+
}
|
|
1887
2084
|
return [];
|
|
1888
2085
|
}
|
|
1889
|
-
this.rejectRawRequests(
|
|
2086
|
+
this.rejectRawRequests(
|
|
2087
|
+
input.group,
|
|
2088
|
+
new Error(
|
|
2089
|
+
`Durable tool call ${input.receiptKey} failed: ${claim.receipt.error ?? 'unknown error'}`,
|
|
2090
|
+
),
|
|
2091
|
+
);
|
|
1890
2092
|
return [];
|
|
1891
2093
|
}
|
|
1892
2094
|
|
|
@@ -2025,45 +2227,22 @@ class WorkerToolBatchScheduler {
|
|
|
2025
2227
|
continue;
|
|
2026
2228
|
}
|
|
2027
2229
|
if (claim.disposition === 'failed') {
|
|
2028
|
-
|
|
2029
|
-
|
|
2230
|
+
claimedRequests.push(
|
|
2231
|
+
...(await this.reclaimFailedDurableToolReceiptGroup({
|
|
2232
|
+
group,
|
|
2233
|
+
receiptKey,
|
|
2234
|
+
failedReceipt: claim.receipt,
|
|
2235
|
+
})),
|
|
2030
2236
|
);
|
|
2031
|
-
this.rejectRawRequests(group, error);
|
|
2032
2237
|
continue;
|
|
2033
2238
|
}
|
|
2034
2239
|
if (claim.disposition === 'running') {
|
|
2035
2240
|
deferredClaimedRequests.push(
|
|
2036
|
-
(
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
const result = await this.waitForDurableToolReceipt(receiptKey);
|
|
2042
|
-
for (const request of group) {
|
|
2043
|
-
request.resolve(
|
|
2044
|
-
markWorkerToolReceiptResultExecution(result, {
|
|
2045
|
-
kind: 'in_flight',
|
|
2046
|
-
receiptKey,
|
|
2047
|
-
attachedToReceiptKey: receiptKey,
|
|
2048
|
-
}),
|
|
2049
|
-
);
|
|
2050
|
-
}
|
|
2051
|
-
this.onRequestsSettled?.(group.length);
|
|
2052
|
-
return [];
|
|
2053
|
-
} catch (error) {
|
|
2054
|
-
waitError = error;
|
|
2055
|
-
if (!(error instanceof RuntimeReceiptWaitTimeoutError)) {
|
|
2056
|
-
this.rejectRawRequests(group, error);
|
|
2057
|
-
return [];
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
return await this.reclaimTimedOutDurableToolReceiptGroup({
|
|
2061
|
-
group,
|
|
2062
|
-
receiptKey,
|
|
2063
|
-
runningReceipt: claim.receipt,
|
|
2064
|
-
waitError,
|
|
2065
|
-
});
|
|
2066
|
-
})(),
|
|
2241
|
+
this.reclaimRunningDurableToolReceiptGroupAfterWait({
|
|
2242
|
+
group,
|
|
2243
|
+
receiptKey,
|
|
2244
|
+
runningReceipt: claim.receipt,
|
|
2245
|
+
}),
|
|
2067
2246
|
);
|
|
2068
2247
|
continue;
|
|
2069
2248
|
}
|
|
@@ -2288,6 +2467,8 @@ class WorkerToolBatchScheduler {
|
|
|
2288
2467
|
(retryAfterMs) => this.reportBackpressure(toolId, retryAfterMs),
|
|
2289
2468
|
() => this.governor.chargeBudget('retry'),
|
|
2290
2469
|
toolContract?.retrySafeTransientHttp === true,
|
|
2470
|
+
this.abortSignal,
|
|
2471
|
+
this.runtimeDeadlineMs,
|
|
2291
2472
|
);
|
|
2292
2473
|
this.settleRequests(
|
|
2293
2474
|
claimed,
|
|
@@ -2328,6 +2509,7 @@ class WorkerToolBatchScheduler {
|
|
|
2328
2509
|
WORKER_TOOL_BATCH_DEFAULT_PARALLELISM,
|
|
2329
2510
|
),
|
|
2330
2511
|
abortSignal: this.abortSignal,
|
|
2512
|
+
runtimeDeadlineMs: this.runtimeDeadlineMs,
|
|
2331
2513
|
reportBackpressure: (retryAfterMs) =>
|
|
2332
2514
|
this.reportBackpressure(toolId, retryAfterMs),
|
|
2333
2515
|
resolveToolContract: this.resolvePacing,
|
|
@@ -2376,6 +2558,7 @@ async function executeBatchedWorkerToolGroup(input: {
|
|
|
2376
2558
|
governor: PlayExecutionGovernor;
|
|
2377
2559
|
suggestedParallelism: number;
|
|
2378
2560
|
abortSignal?: AbortSignal;
|
|
2561
|
+
runtimeDeadlineMs?: number;
|
|
2379
2562
|
reportBackpressure: (retryAfterMs: number) => void;
|
|
2380
2563
|
resolveToolContract: WorkerPacingResolver;
|
|
2381
2564
|
onRequestsSettled?: (count: number) => void;
|
|
@@ -2444,6 +2627,8 @@ async function executeBatchedWorkerToolGroup(input: {
|
|
|
2444
2627
|
input.reportBackpressure,
|
|
2445
2628
|
() => input.governor.chargeBudget('retry'),
|
|
2446
2629
|
toolContract?.retrySafeTransientHttp === true,
|
|
2630
|
+
input.abortSignal,
|
|
2631
|
+
input.runtimeDeadlineMs,
|
|
2447
2632
|
);
|
|
2448
2633
|
} catch (error) {
|
|
2449
2634
|
input.callbacks?.onToolFailed?.(batch.batchOperation, nowMs());
|
|
@@ -3684,21 +3869,13 @@ async function persistCompletedMapRows(input: {
|
|
|
3684
3869
|
const retry = await harnessPersistCompletedSheetRows(persistRequest);
|
|
3685
3870
|
retryWritten = retry.rowsWritten;
|
|
3686
3871
|
retryVisible = await readVisibleRowCount();
|
|
3687
|
-
if (retryVisible
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
visible: visibleRows,
|
|
3692
|
-
retryWritten,
|
|
3693
|
-
retryVisible,
|
|
3694
|
-
};
|
|
3695
|
-
}
|
|
3696
|
-
throw new Error(
|
|
3697
|
-
`Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; visible ${visibleRows}; retry wrote ${retryWritten}/${rows.length}; retry visible ${retryVisible}; run ${req.runId}.`,
|
|
3698
|
-
);
|
|
3872
|
+
if (retryVisible < rows.length)
|
|
3873
|
+
throw new Error(
|
|
3874
|
+
`Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; visible ${visibleRows}; retry wrote ${retryWritten}/${rows.length}; retry visible ${retryVisible}; run ${req.runId}.`,
|
|
3875
|
+
);
|
|
3699
3876
|
}
|
|
3700
3877
|
}
|
|
3701
|
-
if (result.rowsWritten !== rows.length) {
|
|
3878
|
+
if (result.rowsWritten !== rows.length && visibleRows < rows.length) {
|
|
3702
3879
|
throw new Error(
|
|
3703
3880
|
`Runtime sheet persistence mismatch for ${tableNamespace}: wrote ${result.rowsWritten}/${rows.length}; run ${req.runId}.`,
|
|
3704
3881
|
);
|
|
@@ -4137,6 +4314,7 @@ function createMinimalWorkerCtx(
|
|
|
4137
4314
|
workflowStep?: WorkflowStep,
|
|
4138
4315
|
abortSignal?: AbortSignal,
|
|
4139
4316
|
callbacks?: WorkerCtxCallbacks,
|
|
4317
|
+
runtimeDeadlineMs?: number,
|
|
4140
4318
|
): unknown {
|
|
4141
4319
|
const { governor, resolvePacing: resolveToolPacing } =
|
|
4142
4320
|
createGovernorForRun(req);
|
|
@@ -4258,6 +4436,7 @@ function createMinimalWorkerCtx(
|
|
|
4258
4436
|
callbacks,
|
|
4259
4437
|
receiptStore,
|
|
4260
4438
|
true,
|
|
4439
|
+
runtimeDeadlineMs,
|
|
4261
4440
|
);
|
|
4262
4441
|
// Local ancestry chain that always ENDS with the currently-executing play
|
|
4263
4442
|
// (req.playName). The /api/v2/plays/run lineage validator requires the
|
|
@@ -4397,9 +4576,7 @@ function createMinimalWorkerCtx(
|
|
|
4397
4576
|
row: Record<string, unknown>,
|
|
4398
4577
|
): number | null => {
|
|
4399
4578
|
const value = row[MAP_ROW_OUTCOME_RUNTIME_FIELDS.inputIndex];
|
|
4400
|
-
return typeof value === 'number' && Number.isFinite(value)
|
|
4401
|
-
? value
|
|
4402
|
-
: null;
|
|
4579
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
4403
4580
|
};
|
|
4404
4581
|
const deriveDefaultMapRowIdentity = (
|
|
4405
4582
|
row: Record<string, unknown>,
|
|
@@ -4461,12 +4638,7 @@ function createMinimalWorkerCtx(
|
|
|
4461
4638
|
): string => {
|
|
4462
4639
|
const explicitKeyValue = resolveExplicitKeyValue(row, index);
|
|
4463
4640
|
return explicitKeyValue == null
|
|
4464
|
-
? deriveDefaultMapRowIdentity(
|
|
4465
|
-
row,
|
|
4466
|
-
index,
|
|
4467
|
-
chunkIndex,
|
|
4468
|
-
chunkLocalIndex,
|
|
4469
|
-
)
|
|
4641
|
+
? deriveDefaultMapRowIdentity(row, index, chunkIndex, chunkLocalIndex)
|
|
4470
4642
|
: derivePlayRowIdentityFromKey(explicitKeyValue, name);
|
|
4471
4643
|
};
|
|
4472
4644
|
// Cross-chunk dedupe accumulators for the single end-of-map log line.
|
|
@@ -4743,6 +4915,7 @@ function createMinimalWorkerCtx(
|
|
|
4743
4915
|
callbacks,
|
|
4744
4916
|
receiptStore,
|
|
4745
4917
|
false,
|
|
4918
|
+
runtimeDeadlineMs,
|
|
4746
4919
|
);
|
|
4747
4920
|
let stepCellsCompleted = 0;
|
|
4748
4921
|
let stepCellsSkipped = 0;
|
|
@@ -7165,6 +7338,7 @@ async function executeRunRequest(
|
|
|
7165
7338
|
|
|
7166
7339
|
stepLifecycle?.markPreDatasetStepsStarted(startedAt);
|
|
7167
7340
|
flushLedgerEvents(false);
|
|
7341
|
+
const runtimeDeadlineMs = nowMs() + STANDARD_PLAY_RUNTIME_LIMIT_MS;
|
|
7168
7342
|
const ctx = createMinimalWorkerCtx(
|
|
7169
7343
|
req,
|
|
7170
7344
|
wrappedEmit,
|
|
@@ -7172,6 +7346,7 @@ async function executeRunRequest(
|
|
|
7172
7346
|
workflowStep,
|
|
7173
7347
|
abortSignal,
|
|
7174
7348
|
workerCallbacks,
|
|
7349
|
+
runtimeDeadlineMs,
|
|
7175
7350
|
);
|
|
7176
7351
|
// Hard wall-clock cap on active user-code runtime. CF Workflows does not
|
|
7177
7352
|
// impose a play-level execution ceiling on this substrate, so without this a
|
|
@@ -7179,14 +7354,15 @@ async function executeRunRequest(
|
|
|
7179
7354
|
// token expires. Aborting the controller surfaces cooperatively through the
|
|
7180
7355
|
// same assertNotAborted checks used for harness cancellation.
|
|
7181
7356
|
let runtimeLimitExceeded = false;
|
|
7182
|
-
const runtimeDeadlineTimer = setTimeout(
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
}
|
|
7189
|
-
|
|
7357
|
+
const runtimeDeadlineTimer = setTimeout(
|
|
7358
|
+
() => {
|
|
7359
|
+
runtimeLimitExceeded = true;
|
|
7360
|
+
if (!abortSignal.aborted) {
|
|
7361
|
+
abortController.abort(STANDARD_PLAY_RUNTIME_LIMIT_MESSAGE);
|
|
7362
|
+
}
|
|
7363
|
+
},
|
|
7364
|
+
Math.max(1, runtimeDeadlineMs - nowMs()),
|
|
7365
|
+
);
|
|
7190
7366
|
try {
|
|
7191
7367
|
const playStartedAt = nowMs();
|
|
7192
7368
|
const result = await (
|
|
@@ -23,9 +23,19 @@ export function canReclaimTimedOutWorkerToolReceipt(input: {
|
|
|
23
23
|
ownerRunId?: string | null;
|
|
24
24
|
currentRunId: string;
|
|
25
25
|
}): boolean {
|
|
26
|
-
const ownerRunId = input.ownerRunId?.trim();
|
|
27
26
|
const currentRunId = input.currentRunId.trim();
|
|
28
|
-
return Boolean(
|
|
27
|
+
return Boolean(currentRunId);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function canReclaimFailedWorkerToolReceipt(input: {
|
|
31
|
+
error?: string | null;
|
|
32
|
+
}): boolean {
|
|
33
|
+
const error = input.error?.trim();
|
|
34
|
+
return Boolean(
|
|
35
|
+
error &&
|
|
36
|
+
(/\b(cancell?ed|aborted|terminate[d]?)\b/i.test(error) ||
|
|
37
|
+
/\bmax runtime\b/i.test(error)),
|
|
38
|
+
);
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
export function selectWorkerToolReceipt(input: {
|
|
@@ -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.
|
|
107
|
+
version: '0.1.168',
|
|
108
108
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
109
109
|
supportPolicy: {
|
|
110
|
-
latest: '0.1.
|
|
110
|
+
latest: '0.1.168',
|
|
111
111
|
minimumSupported: '0.1.53',
|
|
112
112
|
deprecatedBelow: '0.1.53',
|
|
113
113
|
commandMinimumSupported: [
|
|
@@ -4335,7 +4335,7 @@ export class PlayContextImpl {
|
|
|
4335
4335
|
}
|
|
4336
4336
|
};
|
|
4337
4337
|
|
|
4338
|
-
const
|
|
4338
|
+
const durableExistingRunningHandlers: Promise<void>[] = [];
|
|
4339
4339
|
if (
|
|
4340
4340
|
pendingRequests.length > 0 &&
|
|
4341
4341
|
(this.#options.claimRuntimeStepReceipts ||
|
|
@@ -4448,7 +4448,7 @@ export class PlayContextImpl {
|
|
|
4448
4448
|
liveFollowersByOwnerCallId.set(owner.callId, followers);
|
|
4449
4449
|
}
|
|
4450
4450
|
};
|
|
4451
|
-
const
|
|
4451
|
+
const reclaimExistingRunningReceiptAfterWait = async (
|
|
4452
4452
|
receiptKey: string,
|
|
4453
4453
|
requestsForKey: ToolCallRequest[],
|
|
4454
4454
|
): Promise<void> => {
|
|
@@ -4469,6 +4469,65 @@ export class PlayContextImpl {
|
|
|
4469
4469
|
return;
|
|
4470
4470
|
}
|
|
4471
4471
|
}
|
|
4472
|
+
const reclaimed = (
|
|
4473
|
+
await this.claimRuntimeStepReceipts(
|
|
4474
|
+
[receiptKey],
|
|
4475
|
+
this.currentRunId,
|
|
4476
|
+
true,
|
|
4477
|
+
)
|
|
4478
|
+
).get(receiptKey);
|
|
4479
|
+
if (
|
|
4480
|
+
reclaimed?.status === 'completed' ||
|
|
4481
|
+
reclaimed?.status === 'skipped'
|
|
4482
|
+
) {
|
|
4483
|
+
await resolveRequestsFromReceipt(
|
|
4484
|
+
requestsForKey,
|
|
4485
|
+
reclaimed,
|
|
4486
|
+
'cache',
|
|
4487
|
+
);
|
|
4488
|
+
durableRecoveredRequests.push(...requestsForKey);
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
if (reclaimed?.status === 'failed') {
|
|
4492
|
+
for (const request of requestsForKey) {
|
|
4493
|
+
await this.rejectToolCall(
|
|
4494
|
+
toolId,
|
|
4495
|
+
request,
|
|
4496
|
+
new Error(reclaimed.error ?? 'Durable tool call failed.'),
|
|
4497
|
+
);
|
|
4498
|
+
}
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4501
|
+
if (
|
|
4502
|
+
reclaimed?.status === 'running' &&
|
|
4503
|
+
reclaimed.claimState !== 'existing'
|
|
4504
|
+
) {
|
|
4505
|
+
const [owner, ...waiters] = requestsForKey;
|
|
4506
|
+
if (!owner) return;
|
|
4507
|
+
if (waiters.length > 0) {
|
|
4508
|
+
liveFollowersByOwnerCallId.set(owner.callId, waiters);
|
|
4509
|
+
}
|
|
4510
|
+
try {
|
|
4511
|
+
const execution = await this.callToolExecutionAPI(
|
|
4512
|
+
toolId,
|
|
4513
|
+
owner.input,
|
|
4514
|
+
);
|
|
4515
|
+
const result = await this.resolveToolCall(
|
|
4516
|
+
toolId,
|
|
4517
|
+
owner,
|
|
4518
|
+
execution?.result ?? null,
|
|
4519
|
+
execution?.metadata ?? null,
|
|
4520
|
+
execution?.jobId,
|
|
4521
|
+
execution?.meta,
|
|
4522
|
+
);
|
|
4523
|
+
resolveLiveFollowers(owner, result);
|
|
4524
|
+
recordToolStep([owner]);
|
|
4525
|
+
this.#options.onBatchComplete?.(this.checkpoint);
|
|
4526
|
+
} catch (error) {
|
|
4527
|
+
await rejectWithLiveFollowers(owner, error);
|
|
4528
|
+
}
|
|
4529
|
+
return;
|
|
4530
|
+
}
|
|
4472
4531
|
for (const request of requestsForKey) {
|
|
4473
4532
|
await this.rejectToolCall(
|
|
4474
4533
|
toolId,
|
|
@@ -4510,8 +4569,23 @@ export class PlayContextImpl {
|
|
|
4510
4569
|
claimOwnerWithLiveFollowers(owner, waiters);
|
|
4511
4570
|
continue;
|
|
4512
4571
|
}
|
|
4513
|
-
|
|
4514
|
-
|
|
4572
|
+
if (
|
|
4573
|
+
claim?.status === 'running' &&
|
|
4574
|
+
claim.claimState === 'existing'
|
|
4575
|
+
) {
|
|
4576
|
+
durableExistingRunningHandlers.push(
|
|
4577
|
+
reclaimExistingRunningReceiptAfterWait(
|
|
4578
|
+
receiptKey,
|
|
4579
|
+
requestsForKey,
|
|
4580
|
+
),
|
|
4581
|
+
);
|
|
4582
|
+
continue;
|
|
4583
|
+
}
|
|
4584
|
+
durableExistingRunningHandlers.push(
|
|
4585
|
+
reclaimExistingRunningReceiptAfterWait(
|
|
4586
|
+
receiptKey,
|
|
4587
|
+
requestsForKey,
|
|
4588
|
+
),
|
|
4515
4589
|
);
|
|
4516
4590
|
}
|
|
4517
4591
|
|
|
@@ -4621,8 +4695,8 @@ export class PlayContextImpl {
|
|
|
4621
4695
|
}
|
|
4622
4696
|
}
|
|
4623
4697
|
}
|
|
4624
|
-
if (
|
|
4625
|
-
await Promise.allSettled(
|
|
4698
|
+
if (durableExistingRunningHandlers.length > 0) {
|
|
4699
|
+
await Promise.allSettled(durableExistingRunningHandlers);
|
|
4626
4700
|
}
|
|
4627
4701
|
}),
|
|
4628
4702
|
);
|
|
@@ -10,6 +10,9 @@ import type { DurableReceiptRecoverySource } from './tool-execution-outcome';
|
|
|
10
10
|
|
|
11
11
|
const DURABLE_RECEIPT_WAIT_MAX_ATTEMPTS = 240;
|
|
12
12
|
const DURABLE_RECEIPT_WAIT_DELAY_MS = 250;
|
|
13
|
+
const TOOL_RECEIPT_DEFAULT_WAIT_MS = 300_000;
|
|
14
|
+
const TOOL_RECEIPT_COMPLETION_BUFFER_MS = 30_000;
|
|
15
|
+
const TOOL_RECEIPT_MAX_WAIT_MS = 30 * 60_000;
|
|
13
16
|
|
|
14
17
|
export class RuntimeReceiptWaitTimeoutError extends Error {
|
|
15
18
|
constructor(key: string) {
|
|
@@ -18,6 +21,76 @@ export class RuntimeReceiptWaitTimeoutError extends Error {
|
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
export function resolveRuntimeToolReceiptWaitTimeoutMs(
|
|
25
|
+
requestInput: Record<string, unknown>,
|
|
26
|
+
): number {
|
|
27
|
+
const explicitTimeoutCandidate =
|
|
28
|
+
requestInput.timeoutMs ??
|
|
29
|
+
requestInput.timeout_ms ??
|
|
30
|
+
requestInput.max_wait_ms;
|
|
31
|
+
const explicitTimeoutMs =
|
|
32
|
+
typeof explicitTimeoutCandidate === 'number' &&
|
|
33
|
+
isFinite(explicitTimeoutCandidate) &&
|
|
34
|
+
explicitTimeoutCandidate > 0
|
|
35
|
+
? explicitTimeoutCandidate
|
|
36
|
+
: undefined;
|
|
37
|
+
const toolTimeoutMs = explicitTimeoutMs ?? TOOL_RECEIPT_DEFAULT_WAIT_MS;
|
|
38
|
+
return Math.min(
|
|
39
|
+
TOOL_RECEIPT_MAX_WAIT_MS,
|
|
40
|
+
Math.max(60_000, toolTimeoutMs + TOOL_RECEIPT_COMPLETION_BUFFER_MS),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveRuntimeToolReceiptWaitMaxAttempts(
|
|
45
|
+
requestInput: Record<string, unknown>,
|
|
46
|
+
): number {
|
|
47
|
+
return Math.ceil(
|
|
48
|
+
resolveRuntimeToolReceiptWaitTimeoutMs(requestInput) /
|
|
49
|
+
DURABLE_RECEIPT_WAIT_DELAY_MS,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function throwIfReceiptWaitAborted(signal?: AbortSignal): void {
|
|
54
|
+
if (!signal?.aborted) return;
|
|
55
|
+
throw signal.reason instanceof Error
|
|
56
|
+
? signal.reason
|
|
57
|
+
: new Error(
|
|
58
|
+
typeof signal.reason === 'string' && signal.reason.trim()
|
|
59
|
+
? signal.reason
|
|
60
|
+
: 'Durable receipt wait aborted.',
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function sleepReceiptWait(
|
|
65
|
+
delayMs: number,
|
|
66
|
+
signal?: AbortSignal,
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
throwIfReceiptWaitAborted(signal);
|
|
69
|
+
if (delayMs <= 0) return;
|
|
70
|
+
await new Promise<void>((resolve, reject) => {
|
|
71
|
+
const timeout = setTimeout(finish, delayMs);
|
|
72
|
+
const abort = () => {
|
|
73
|
+
clearTimeout(timeout);
|
|
74
|
+
reject(
|
|
75
|
+
signal?.reason instanceof Error
|
|
76
|
+
? signal.reason
|
|
77
|
+
: new Error(
|
|
78
|
+
typeof signal?.reason === 'string' && signal.reason.trim()
|
|
79
|
+
? signal.reason
|
|
80
|
+
: 'Durable receipt wait aborted.',
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
function finish() {
|
|
85
|
+
signal?.removeEventListener('abort', abort);
|
|
86
|
+
resolve();
|
|
87
|
+
}
|
|
88
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
89
|
+
if (signal?.aborted) abort();
|
|
90
|
+
});
|
|
91
|
+
throwIfReceiptWaitAborted(signal);
|
|
92
|
+
}
|
|
93
|
+
|
|
21
94
|
export type DurableReceiptOperation = 'step' | 'tool' | 'fetch' | 'runPlay';
|
|
22
95
|
|
|
23
96
|
export type DurableReceiptExecutionStore = {
|
|
@@ -55,12 +128,14 @@ export async function waitForCompletedRuntimeReceipt(input: {
|
|
|
55
128
|
store: Pick<DurableReceiptExecutionStore, 'getMany'>;
|
|
56
129
|
maxAttempts?: number;
|
|
57
130
|
delayMs?: number;
|
|
131
|
+
abortSignal?: AbortSignal;
|
|
58
132
|
}): Promise<RuntimeStepReceipt> {
|
|
59
133
|
const maxAttempts = input.maxAttempts ?? DURABLE_RECEIPT_WAIT_MAX_ATTEMPTS;
|
|
60
134
|
const delayMs = input.delayMs ?? DURABLE_RECEIPT_WAIT_DELAY_MS;
|
|
61
135
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
136
|
+
throwIfReceiptWaitAborted(input.abortSignal);
|
|
62
137
|
if (attempt > 0) {
|
|
63
|
-
await
|
|
138
|
+
await sleepReceiptWait(delayMs, input.abortSignal);
|
|
64
139
|
}
|
|
65
140
|
const receipt = (await input.store.getMany([input.receiptKey])).get(
|
|
66
141
|
input.receiptKey,
|
|
@@ -189,13 +264,7 @@ export async function executeWithDurableRuntimeReceipt<T>(input: {
|
|
|
189
264
|
try {
|
|
190
265
|
return await waitForRunningReceiptOrTimeout();
|
|
191
266
|
} catch (error) {
|
|
192
|
-
if (
|
|
193
|
-
input.repairRunningReceiptForSameRunAfterWaitTimeout === true &&
|
|
194
|
-
error instanceof RuntimeReceiptWaitTimeoutError &&
|
|
195
|
-
receipt.status === 'running' &&
|
|
196
|
-
typeof receipt.runId === 'string' &&
|
|
197
|
-
receipt.runId.trim() === input.runId
|
|
198
|
-
) {
|
|
267
|
+
if (error instanceof RuntimeReceiptWaitTimeoutError) {
|
|
199
268
|
const recovered = await reclaimRunningReceipt();
|
|
200
269
|
if (recovered.kind === 'recovered') return recovered;
|
|
201
270
|
return { kind: 'claimed' };
|
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.
|
|
625
|
+
version: "0.1.168",
|
|
626
626
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
627
627
|
supportPolicy: {
|
|
628
|
-
latest: "0.1.
|
|
628
|
+
latest: "0.1.168",
|
|
629
629
|
minimumSupported: "0.1.53",
|
|
630
630
|
deprecatedBelow: "0.1.53",
|
|
631
631
|
commandMinimumSupported: [
|
package/dist/cli/index.mjs
CHANGED
|
@@ -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.
|
|
610
|
+
version: "0.1.168",
|
|
611
611
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
612
612
|
supportPolicy: {
|
|
613
|
-
latest: "0.1.
|
|
613
|
+
latest: "0.1.168",
|
|
614
614
|
minimumSupported: "0.1.53",
|
|
615
615
|
deprecatedBelow: "0.1.53",
|
|
616
616
|
commandMinimumSupported: [
|
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.
|
|
424
|
+
version: "0.1.168",
|
|
425
425
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
426
426
|
supportPolicy: {
|
|
427
|
-
latest: "0.1.
|
|
427
|
+
latest: "0.1.168",
|
|
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.
|
|
354
|
+
version: "0.1.168",
|
|
355
355
|
apiContract: "2026-06-dataset-handle-results-hard-cutover",
|
|
356
356
|
supportPolicy: {
|
|
357
|
-
latest: "0.1.
|
|
357
|
+
latest: "0.1.168",
|
|
358
358
|
minimumSupported: "0.1.53",
|
|
359
359
|
deprecatedBelow: "0.1.53",
|
|
360
360
|
commandMinimumSupported: [
|