deepline 0.1.182 → 0.1.183
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/child-play-await.ts +203 -6
- package/dist/bundling-sources/apps/play-runner-workers/src/child-play-submit.ts +8 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +633 -107
- package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +3 -2
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +1747 -1890
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +116 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +542 -78
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +292 -11
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +519 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +2381 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +18 -5
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +130 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +39 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +2 -0
- package/dist/bundling-sources/sdk/src/client.ts +41 -0
- package/dist/bundling-sources/sdk/src/http.ts +23 -8
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +56 -3
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +132 -10
- package/dist/bundling-sources/shared_libs/play-runtime/auth-scope-resolver.ts +92 -0
- package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +4 -4
- package/dist/bundling-sources/shared_libs/play-runtime/batching-types.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +101 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1605 -281
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +105 -6
- package/dist/bundling-sources/shared_libs/play-runtime/dedup-backend.ts +0 -0
- package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +116 -16
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +186 -20
- package/dist/bundling-sources/shared_libs/play-runtime/dynamic-worker-version.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +133 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +245 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -5
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +14 -0
- package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +52 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +124 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +6 -2
- package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +48 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +158 -15
- package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +45 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2037 -262
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +183 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +88 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +8 -1
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +19 -1
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +343 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +437 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +83 -6
- package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +24 -17
- package/dist/cli/index.js +76 -17
- package/dist/cli/index.mjs +76 -17
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +63 -9
- package/dist/index.mjs +63 -9
- package/dist/plays/bundle-play-file.mjs +2 -2
- package/package.json +1 -1
|
@@ -23,12 +23,24 @@ import {
|
|
|
23
23
|
} from './batch-runtime';
|
|
24
24
|
import type { PlayQueueHint } from './governor/rate-state-backend';
|
|
25
25
|
import type { MapRowOutcome } from './durability-store';
|
|
26
|
+
import type { WorkReceiptFailureKind } from './work-receipts';
|
|
26
27
|
import {
|
|
27
28
|
completedMapRowOutcome,
|
|
28
29
|
failedMapRowOutcome,
|
|
29
30
|
mapRowOutcomeRuntimeFields,
|
|
30
31
|
resolveMapRowOutcomeKey,
|
|
31
32
|
} from './map-row-outcome';
|
|
33
|
+
import { RuntimeSheetRowsBlockedError } from './runtime-sheet-errors';
|
|
34
|
+
import { buildChildRunId } from './child-run-id';
|
|
35
|
+
import type { PlayCallGovernanceSnapshot } from './scheduler-backend';
|
|
36
|
+
import {
|
|
37
|
+
RUNTIME_SCHEDULER_SCHEMA_OVERRIDE_HEADER,
|
|
38
|
+
SYNTHETIC_RUN_HEADER,
|
|
39
|
+
} from './coordinator-headers';
|
|
40
|
+
export {
|
|
41
|
+
RuntimeSheetRowsBlockedError,
|
|
42
|
+
type RuntimeSheetBlockedRowDetail,
|
|
43
|
+
} from './runtime-sheet-errors';
|
|
32
44
|
import {
|
|
33
45
|
createDefaultGovernanceSnapshot,
|
|
34
46
|
createPlayExecutionGovernor,
|
|
@@ -64,30 +76,38 @@ import {
|
|
|
64
76
|
TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS,
|
|
65
77
|
classifyToolExecuteHttpFailure,
|
|
66
78
|
createToolExecuteHttpFailureAttemptTracker,
|
|
79
|
+
parseToolExecuteAuthScopeChangedError,
|
|
80
|
+
ToolExecuteAuthScopeChangedError,
|
|
67
81
|
} from './tool-execute-retry-policy';
|
|
68
82
|
import {
|
|
69
83
|
buildDurableCtxCallCacheKey,
|
|
84
|
+
buildDurableRunPlaySemanticKey,
|
|
85
|
+
buildDurableToolAggregateProviderIdempotencyKey,
|
|
86
|
+
buildDurableToolAggregateReceiptKey,
|
|
70
87
|
buildDurableToolCallAuthScopeDigest,
|
|
71
88
|
buildDurableToolCallCacheKey,
|
|
89
|
+
buildDurableToolProviderIdempotencyKey,
|
|
90
|
+
buildDurableToolReceiptPrefix,
|
|
72
91
|
} from './durable-call-cache';
|
|
73
92
|
import {
|
|
74
|
-
|
|
75
|
-
isCustomerDbDatasetTool,
|
|
76
|
-
isQueryResultDatasetReadRequest,
|
|
77
|
-
isQueryResultDatasetTool,
|
|
78
|
-
} from './query-result-dataset';
|
|
79
|
-
import {
|
|
93
|
+
RuntimeReceiptLeaseLostError,
|
|
80
94
|
RuntimeReceiptWaitTimeoutError,
|
|
81
95
|
executeWithDurableRuntimeReceipt,
|
|
82
96
|
resolveRuntimeToolReceiptWaitMaxAttempts,
|
|
97
|
+
runtimeReceiptFailureKindForError,
|
|
83
98
|
runtimeReceiptOutput as durableRuntimeReceiptOutput,
|
|
84
99
|
waitForCompletedRuntimeReceipt,
|
|
85
100
|
type DurableReceiptExecutionStore,
|
|
86
101
|
} from './durable-receipt-execution';
|
|
102
|
+
import {
|
|
103
|
+
QUERY_RESULT_DATASET_PAGE_SIZE,
|
|
104
|
+
isCustomerDbDatasetTool,
|
|
105
|
+
isQueryResultDatasetReadRequest,
|
|
106
|
+
isQueryResultDatasetTool,
|
|
107
|
+
} from './query-result-dataset';
|
|
87
108
|
import { isRowIsolationExemptError } from './row-isolation';
|
|
88
109
|
import {
|
|
89
110
|
createRuntimePersistenceLatch,
|
|
90
|
-
PROVIDER_DISPATCH_WAVE_SIZE,
|
|
91
111
|
RuntimePersistenceCircuitOpenError,
|
|
92
112
|
tripRuntimePersistenceLatch,
|
|
93
113
|
type RuntimePersistenceLatch,
|
|
@@ -119,7 +139,9 @@ import { DISALLOWED_RUN_JAVASCRIPT_TOOL_MESSAGE } from './runtime-constraints';
|
|
|
119
139
|
import {
|
|
120
140
|
PlayExecutionSuspendedError,
|
|
121
141
|
PlayRowExecutionSuspendedError,
|
|
142
|
+
isPlayExecutionSuspendedError,
|
|
122
143
|
isPlayRowExecutionSuspendedError,
|
|
144
|
+
type PlayExecutionSuspension,
|
|
123
145
|
} from './suspension';
|
|
124
146
|
import {
|
|
125
147
|
createSecretRedactionContext,
|
|
@@ -171,6 +193,7 @@ import {
|
|
|
171
193
|
type StepProgramDatasetColumnInput,
|
|
172
194
|
type StepProgramDatasetOptions,
|
|
173
195
|
} from './step-program-dataset-builder';
|
|
196
|
+
import { readRuntimeSheetDatasetRows } from './runtime-api';
|
|
174
197
|
|
|
175
198
|
/**
|
|
176
199
|
* SECURITY: AsyncLocalStorage is per async execution, not a cross-run cache.
|
|
@@ -207,9 +230,118 @@ const TOOL_RETRY_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
|
207
230
|
const DEEPLINEAGENT_TOOL_RUNTIME_TIMEOUT_MS = 15 * 60 * 1000;
|
|
208
231
|
const FETCH_TRANSPORT_MAX_ATTEMPTS = 3;
|
|
209
232
|
const FETCH_TRANSPORT_RETRY_DELAY_MS = 100;
|
|
233
|
+
const NODE_RUNTIME_MAP_VISIBILITY_MAX_ATTEMPTS = 100;
|
|
234
|
+
const NODE_RUNTIME_MAP_VISIBILITY_RETRY_MS = 25;
|
|
210
235
|
type SafeFetchModule = typeof import('@shared_libs/security/safe-fetch');
|
|
211
236
|
let safeFetchModule: Promise<SafeFetchModule> | null = null;
|
|
212
237
|
|
|
238
|
+
export async function waitForNodeRuntimeMapRowsVisible(input: {
|
|
239
|
+
mapName: string;
|
|
240
|
+
tableNamespace: string;
|
|
241
|
+
runId?: string | null;
|
|
242
|
+
expectedRows: number;
|
|
243
|
+
updatedRows: number;
|
|
244
|
+
readVisibleRowCount: () => Promise<number>;
|
|
245
|
+
sleep?: (ms: number) => Promise<void>;
|
|
246
|
+
log?: (line: string) => void;
|
|
247
|
+
}): Promise<number> {
|
|
248
|
+
if (input.expectedRows <= 0) return 0;
|
|
249
|
+
|
|
250
|
+
const sleep =
|
|
251
|
+
input.sleep ??
|
|
252
|
+
((ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms)));
|
|
253
|
+
let lastVisibleRows = -1;
|
|
254
|
+
for (
|
|
255
|
+
let attempt = 1;
|
|
256
|
+
attempt <= NODE_RUNTIME_MAP_VISIBILITY_MAX_ATTEMPTS;
|
|
257
|
+
attempt += 1
|
|
258
|
+
) {
|
|
259
|
+
lastVisibleRows = await input.readVisibleRowCount();
|
|
260
|
+
if (lastVisibleRows >= input.expectedRows) {
|
|
261
|
+
if (attempt > 1) {
|
|
262
|
+
input.log?.(
|
|
263
|
+
`Runtime sheet visibility barrier satisfied ctx.dataset("${input.mapName}") ` +
|
|
264
|
+
`after ${attempt} read(s): visible ${lastVisibleRows}/${input.expectedRows}; ` +
|
|
265
|
+
`wrote ${input.updatedRows}/${input.expectedRows}; run ${input.runId ?? 'unknown'}.`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return lastVisibleRows;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (attempt === 1) {
|
|
272
|
+
input.log?.(
|
|
273
|
+
`Runtime sheet visibility barrier waiting ctx.dataset("${input.mapName}"): ` +
|
|
274
|
+
`visible ${lastVisibleRows}/${input.expectedRows}; ` +
|
|
275
|
+
`wrote ${input.updatedRows}/${input.expectedRows}; run ${input.runId ?? 'unknown'}.`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (attempt < NODE_RUNTIME_MAP_VISIBILITY_MAX_ATTEMPTS) {
|
|
279
|
+
await sleep(NODE_RUNTIME_MAP_VISIBILITY_RETRY_MS);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
throw new Error(
|
|
284
|
+
`Runtime sheet visibility mismatch for ctx.dataset("${input.mapName}"): ` +
|
|
285
|
+
`expected ${input.expectedRows} terminal visible row(s), saw ${lastVisibleRows}; ` +
|
|
286
|
+
`write reported ${input.updatedRows}; run ${input.runId ?? 'unknown'}.`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function reconcileNodeRuntimeMapResultsWithPersistedSheet(input: {
|
|
291
|
+
mapName: string;
|
|
292
|
+
tableNamespace: string;
|
|
293
|
+
runId?: string | null;
|
|
294
|
+
expectedRows: number;
|
|
295
|
+
currentRows: Record<string, unknown>[];
|
|
296
|
+
failedRowCount: number;
|
|
297
|
+
readPersistedRows: (input: {
|
|
298
|
+
limit: number;
|
|
299
|
+
offset: number;
|
|
300
|
+
rowMode: 'all';
|
|
301
|
+
}) => Promise<Record<string, unknown>[]>;
|
|
302
|
+
log?: (line: string) => void;
|
|
303
|
+
}): Promise<Record<string, unknown>[]> {
|
|
304
|
+
if (
|
|
305
|
+
input.failedRowCount > 0 ||
|
|
306
|
+
input.expectedRows <= 0 ||
|
|
307
|
+
input.currentRows.length >= input.expectedRows
|
|
308
|
+
) {
|
|
309
|
+
return input.currentRows;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const persistedRows: Record<string, unknown>[] = [];
|
|
313
|
+
let offset = 0;
|
|
314
|
+
while (persistedRows.length < input.expectedRows) {
|
|
315
|
+
const page = await input.readPersistedRows({
|
|
316
|
+
limit: Math.min(10_000, input.expectedRows - persistedRows.length),
|
|
317
|
+
offset,
|
|
318
|
+
rowMode: 'all',
|
|
319
|
+
});
|
|
320
|
+
if (page.length === 0) break;
|
|
321
|
+
persistedRows.push(...page);
|
|
322
|
+
offset += page.length;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (persistedRows.length < input.expectedRows) {
|
|
326
|
+
throw new Error(
|
|
327
|
+
`Runtime sheet finalization mismatch for ctx.dataset("${input.mapName}"): ` +
|
|
328
|
+
`expected ${input.expectedRows} terminal persisted row(s), saw ${persistedRows.length}; ` +
|
|
329
|
+
`in-memory Node map results reported ${input.currentRows.length}; run ${input.runId ?? 'unknown'}.`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (persistedRows.length > input.currentRows.length) {
|
|
334
|
+
input.log?.(
|
|
335
|
+
`Runtime sheet finalization reconciled ctx.dataset("${input.mapName}") ` +
|
|
336
|
+
`from ${input.currentRows.length}/${input.expectedRows} in-memory Node row(s) ` +
|
|
337
|
+
`to ${persistedRows.length} terminal persisted row(s).`,
|
|
338
|
+
);
|
|
339
|
+
return persistedRows;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return input.currentRows;
|
|
343
|
+
}
|
|
344
|
+
|
|
213
345
|
export function resolveToolRuntimeTimeoutMs(
|
|
214
346
|
toolId: string,
|
|
215
347
|
requestedTimeoutMs?: number,
|
|
@@ -304,6 +436,9 @@ function finitePositiveInteger(value: unknown): number | null {
|
|
|
304
436
|
|
|
305
437
|
type ToolExecutionApiOptions = {
|
|
306
438
|
timeoutMs?: number;
|
|
439
|
+
durableCallReceiptKey?: string | null;
|
|
440
|
+
executionAuthScopeDigest?: string | null;
|
|
441
|
+
providerIdempotencyKey?: string | null;
|
|
307
442
|
customerDbDataset?: {
|
|
308
443
|
limit: number;
|
|
309
444
|
offset: number;
|
|
@@ -347,6 +482,16 @@ function persistableMapRowBytes(row: PersistableMapRow): number {
|
|
|
347
482
|
return JSON.stringify(row).length;
|
|
348
483
|
}
|
|
349
484
|
|
|
485
|
+
function failedMapRowLogLabel(row: PersistableMapRow): string {
|
|
486
|
+
const rowId = row.data?.row_id;
|
|
487
|
+
if (typeof rowId === 'string' && rowId.trim()) return rowId.trim();
|
|
488
|
+
if (row.key?.trim()) return row.key.trim();
|
|
489
|
+
if (typeof row.inputIndex === 'number' && Number.isFinite(row.inputIndex)) {
|
|
490
|
+
return `input:${Math.floor(row.inputIndex)}`;
|
|
491
|
+
}
|
|
492
|
+
return 'unknown';
|
|
493
|
+
}
|
|
494
|
+
|
|
350
495
|
type FieldMapRunResult = {
|
|
351
496
|
completedRows: PersistableMapRow[];
|
|
352
497
|
failedRows: PersistableMapRow[];
|
|
@@ -508,9 +653,7 @@ function durableCtxKey(input: {
|
|
|
508
653
|
staleAfterSeconds?: number | null;
|
|
509
654
|
}): string {
|
|
510
655
|
if (input.operation === 'tool') {
|
|
511
|
-
throw new Error(
|
|
512
|
-
'Durable ctx key is only for non-tool call boundaries; tools use buildDurableToolCallCacheKey.',
|
|
513
|
-
);
|
|
656
|
+
throw new Error('Tool calls use tool receipt keys.');
|
|
514
657
|
}
|
|
515
658
|
return buildDurableCtxCallCacheKey({
|
|
516
659
|
orgId: input.orgId,
|
|
@@ -756,6 +899,13 @@ export class PlayContextImpl {
|
|
|
756
899
|
eventKey: string;
|
|
757
900
|
timeoutMs: number;
|
|
758
901
|
}> = [];
|
|
902
|
+
private pendingChildPlayBoundaries: Array<{
|
|
903
|
+
boundaryId: string;
|
|
904
|
+
childRunId: string;
|
|
905
|
+
childPlayName: string;
|
|
906
|
+
}> = [];
|
|
907
|
+
private activeScheduledChildPlayCalls = 0;
|
|
908
|
+
private scheduledChildPlayQuiescenceWaiters: Array<() => void> = [];
|
|
759
909
|
private processedRowCount = 0;
|
|
760
910
|
private sleepBoundaryIndex = 0;
|
|
761
911
|
private fetchCallIndex = 0;
|
|
@@ -891,13 +1041,18 @@ export class PlayContextImpl {
|
|
|
891
1041
|
options.workflowId ||
|
|
892
1042
|
'anonymous-play';
|
|
893
1043
|
const rootRunId = options.runId ?? options.workflowId ?? 'anonymous-run';
|
|
1044
|
+
if (options.requireSharedRateState && !options.rateState) {
|
|
1045
|
+
throw new Error(
|
|
1046
|
+
'Shared rate-state backend is required for this Node runtime substrate.',
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
894
1049
|
this.governor = createPlayExecutionGovernor({
|
|
895
1050
|
adapter: 'cjs_node20',
|
|
896
1051
|
scope: {
|
|
897
1052
|
orgId: options.orgId ?? 'org',
|
|
898
1053
|
rootRunId: options.runId ?? options.workflowId ?? 'run',
|
|
899
1054
|
},
|
|
900
|
-
rateState: new InMemoryRateStateBackend(),
|
|
1055
|
+
rateState: options.rateState ?? new InMemoryRateStateBackend(),
|
|
901
1056
|
resolvePacing: createPacingResolver(options.getToolQueueHints),
|
|
902
1057
|
// A root run keeps depth 0 (its first child play is depth 1) but still
|
|
903
1058
|
// seeds its own play id into the ancestry/currentPlayId so the cycle guard
|
|
@@ -923,6 +1078,263 @@ export class PlayContextImpl {
|
|
|
923
1078
|
return `${this.governance.currentRunId}:${localId}`;
|
|
924
1079
|
}
|
|
925
1080
|
|
|
1081
|
+
private shouldLaunchChildPlaysThroughScheduler(): boolean {
|
|
1082
|
+
return (
|
|
1083
|
+
this.#options.requireSharedRateState === true &&
|
|
1084
|
+
this.#options.durableBoundaries === true
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
private childPlayBoundaryId(childRunId: string): string {
|
|
1089
|
+
return this.durableBoundaryId(`runPlay:${childRunId}`);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private recordPendingChildPlayBoundary(input: {
|
|
1093
|
+
boundaryId: string;
|
|
1094
|
+
childRunId: string;
|
|
1095
|
+
childPlayName: string;
|
|
1096
|
+
}): void {
|
|
1097
|
+
this.pendingChildPlayBoundaries.push(input);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
private beginScheduledChildPlayCall(): { release: () => void } {
|
|
1101
|
+
this.activeScheduledChildPlayCalls += 1;
|
|
1102
|
+
let released = false;
|
|
1103
|
+
return {
|
|
1104
|
+
release: () => {
|
|
1105
|
+
if (released) return;
|
|
1106
|
+
released = true;
|
|
1107
|
+
this.activeScheduledChildPlayCalls = Math.max(
|
|
1108
|
+
0,
|
|
1109
|
+
this.activeScheduledChildPlayCalls - 1,
|
|
1110
|
+
);
|
|
1111
|
+
if (this.activeScheduledChildPlayCalls !== 0) return;
|
|
1112
|
+
for (const resolve of this.scheduledChildPlayQuiescenceWaiters.splice(
|
|
1113
|
+
0,
|
|
1114
|
+
)) {
|
|
1115
|
+
resolve();
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
private async waitForScheduledChildPlayQuiescence(): Promise<void> {
|
|
1122
|
+
if (this.activeScheduledChildPlayCalls === 0) return;
|
|
1123
|
+
await new Promise<void>((resolve) => {
|
|
1124
|
+
this.scheduledChildPlayQuiescenceWaiters.push(resolve);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
private consumePendingChildPlaySuspension(): PlayExecutionSuspension | null {
|
|
1129
|
+
if (this.pendingChildPlayBoundaries.length === 0) return null;
|
|
1130
|
+
const boundaries = [
|
|
1131
|
+
...new Map(
|
|
1132
|
+
this.pendingChildPlayBoundaries.map((boundary) => [
|
|
1133
|
+
boundary.boundaryId,
|
|
1134
|
+
boundary,
|
|
1135
|
+
]),
|
|
1136
|
+
).values(),
|
|
1137
|
+
];
|
|
1138
|
+
this.pendingChildPlayBoundaries = [];
|
|
1139
|
+
if (boundaries.length === 1) {
|
|
1140
|
+
const boundary = boundaries[0]!;
|
|
1141
|
+
return {
|
|
1142
|
+
kind: 'child_play',
|
|
1143
|
+
boundaryId: boundary.boundaryId,
|
|
1144
|
+
childRunId: boundary.childRunId,
|
|
1145
|
+
childPlayName: boundary.childPlayName,
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
kind: 'child_play_batch',
|
|
1150
|
+
boundaries,
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
private childPlayBoundaryOutput<TOutput>(input: {
|
|
1155
|
+
boundaryId: string;
|
|
1156
|
+
childRunId: string;
|
|
1157
|
+
childPlayName: string;
|
|
1158
|
+
}): { found: true; output: TOutput } | { found: false } {
|
|
1159
|
+
const existing = this.checkpoint.resolvedBoundaries?.[input.boundaryId];
|
|
1160
|
+
if (!existing || existing.kind !== 'child_play') return { found: false };
|
|
1161
|
+
if (existing.childRunId !== input.childRunId) return { found: false };
|
|
1162
|
+
if (existing.status === 'completed') {
|
|
1163
|
+
return { found: true, output: existing.output as TOutput };
|
|
1164
|
+
}
|
|
1165
|
+
const message =
|
|
1166
|
+
typeof existing.error === 'string' && existing.error.trim()
|
|
1167
|
+
? existing.error.trim()
|
|
1168
|
+
: `Child play ${input.childPlayName} (${input.childRunId}) ${existing.status}.`;
|
|
1169
|
+
throw new Error(message);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
private recordPlayCallStep(input: {
|
|
1173
|
+
playId: string;
|
|
1174
|
+
description?: string | null;
|
|
1175
|
+
nestedSteps?: PlayStep[];
|
|
1176
|
+
}): void {
|
|
1177
|
+
const step = {
|
|
1178
|
+
type: 'play_call' as const,
|
|
1179
|
+
playId: input.playId,
|
|
1180
|
+
nestedSteps: input.nestedSteps ?? [],
|
|
1181
|
+
description: normalizeStepDescription(input.description ?? undefined),
|
|
1182
|
+
};
|
|
1183
|
+
if (this.activeDatasetStep) {
|
|
1184
|
+
this.activeDatasetStep.substeps.push(step);
|
|
1185
|
+
} else {
|
|
1186
|
+
this.steps.push(step);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
private runtimeApiHeaders(): Record<string, string> {
|
|
1191
|
+
if (!this.#options.executorToken?.trim()) {
|
|
1192
|
+
throw new Error(
|
|
1193
|
+
'ctx.runPlay scheduled child launch requires an executor token.',
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
return {
|
|
1197
|
+
Authorization: `Bearer ${this.#options.executorToken.trim()}`,
|
|
1198
|
+
'Content-Type': 'application/json',
|
|
1199
|
+
...(this.#options.runtimeSchedulerSchema?.trim()
|
|
1200
|
+
? {
|
|
1201
|
+
[RUNTIME_SCHEDULER_SCHEMA_OVERRIDE_HEADER]:
|
|
1202
|
+
this.#options.runtimeSchedulerSchema.trim(),
|
|
1203
|
+
[SYNTHETIC_RUN_HEADER]: '1',
|
|
1204
|
+
}
|
|
1205
|
+
: {}),
|
|
1206
|
+
...(this.#options.vercelProtectionBypassToken?.trim()
|
|
1207
|
+
? {
|
|
1208
|
+
'x-vercel-protection-bypass':
|
|
1209
|
+
this.#options.vercelProtectionBypassToken.trim(),
|
|
1210
|
+
}
|
|
1211
|
+
: {}),
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
private runtimeApiBaseUrl(): string {
|
|
1216
|
+
const baseUrl = this.#options.baseUrl?.trim();
|
|
1217
|
+
if (!baseUrl) {
|
|
1218
|
+
throw new Error('ctx.runPlay scheduled child launch requires baseUrl.');
|
|
1219
|
+
}
|
|
1220
|
+
return baseUrl.replace(/\/$/, '');
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
private async launchScheduledChildPlay(input: {
|
|
1224
|
+
key: string;
|
|
1225
|
+
childRunId: string;
|
|
1226
|
+
childPlayName: string;
|
|
1227
|
+
childGovernance: GovernanceSnapshot;
|
|
1228
|
+
childInput: Record<string, unknown>;
|
|
1229
|
+
description?: string | null;
|
|
1230
|
+
}): Promise<void> {
|
|
1231
|
+
const parentPlayName =
|
|
1232
|
+
this.governance.currentPlayId ||
|
|
1233
|
+
this.#options.playName ||
|
|
1234
|
+
this.#options.playId ||
|
|
1235
|
+
'anonymous-play';
|
|
1236
|
+
const governance: PlayCallGovernanceSnapshot = {
|
|
1237
|
+
rootRunId: this.governance.rootRunId,
|
|
1238
|
+
parentRunId: this.governance.currentRunId,
|
|
1239
|
+
parentPlayName,
|
|
1240
|
+
key: input.key,
|
|
1241
|
+
ancestryPlayIds: [...this.governance.ancestryPlayIds],
|
|
1242
|
+
callDepth: input.childGovernance.callDepth,
|
|
1243
|
+
playCallCount: input.childGovernance.playCallCount,
|
|
1244
|
+
toolCallCount: input.childGovernance.toolCallCount,
|
|
1245
|
+
retryCount: input.childGovernance.retryCount,
|
|
1246
|
+
descendantCount: input.childGovernance.descendantCount,
|
|
1247
|
+
waterfallStepExecutions: input.childGovernance.waterfallStepExecutions,
|
|
1248
|
+
};
|
|
1249
|
+
const response = await fetch(
|
|
1250
|
+
`${this.runtimeApiBaseUrl()}/api/v2/plays/run`,
|
|
1251
|
+
{
|
|
1252
|
+
method: 'POST',
|
|
1253
|
+
headers: this.runtimeApiHeaders(),
|
|
1254
|
+
body: JSON.stringify({
|
|
1255
|
+
name: input.childPlayName,
|
|
1256
|
+
workflowId: input.childRunId,
|
|
1257
|
+
input: input.childInput,
|
|
1258
|
+
profile: 'hatchet',
|
|
1259
|
+
waitForCompletionMs: 0,
|
|
1260
|
+
internalRunPlay: {
|
|
1261
|
+
...governance,
|
|
1262
|
+
description: input.description ?? null,
|
|
1263
|
+
},
|
|
1264
|
+
}),
|
|
1265
|
+
},
|
|
1266
|
+
);
|
|
1267
|
+
if (!response.ok) {
|
|
1268
|
+
const body = (await response.json().catch(() => null)) as {
|
|
1269
|
+
error?: unknown;
|
|
1270
|
+
} | null;
|
|
1271
|
+
const message =
|
|
1272
|
+
typeof body?.error === 'string' && body.error.trim()
|
|
1273
|
+
? body.error.trim()
|
|
1274
|
+
: response.statusText;
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
`ctx.runPlay(${input.key}) failed to launch child play "${input.childPlayName}": ${message} (status ${response.status}).`,
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
private async readScheduledChildTerminal<TOutput>(input: {
|
|
1282
|
+
childRunId: string;
|
|
1283
|
+
childPlayName: string;
|
|
1284
|
+
}): Promise<
|
|
1285
|
+
| { status: 'completed'; output: TOutput }
|
|
1286
|
+
| { status: 'failed' | 'cancelled'; error: string }
|
|
1287
|
+
| null
|
|
1288
|
+
> {
|
|
1289
|
+
const response = await fetch(
|
|
1290
|
+
`${this.runtimeApiBaseUrl()}/api/v2/internal/play-runtime`,
|
|
1291
|
+
{
|
|
1292
|
+
method: 'POST',
|
|
1293
|
+
headers: this.runtimeApiHeaders(),
|
|
1294
|
+
body: JSON.stringify({
|
|
1295
|
+
action: 'read_child_run_terminal_snapshot',
|
|
1296
|
+
parentRunId: this.governance.currentRunId,
|
|
1297
|
+
childRunId: input.childRunId,
|
|
1298
|
+
childPlayName: input.childPlayName,
|
|
1299
|
+
}),
|
|
1300
|
+
},
|
|
1301
|
+
);
|
|
1302
|
+
const body = (await response.json().catch(() => null)) as {
|
|
1303
|
+
state?: {
|
|
1304
|
+
data?: {
|
|
1305
|
+
status?: unknown;
|
|
1306
|
+
result?: unknown;
|
|
1307
|
+
error?: unknown;
|
|
1308
|
+
};
|
|
1309
|
+
};
|
|
1310
|
+
error?: unknown;
|
|
1311
|
+
} | null;
|
|
1312
|
+
if (!response.ok) {
|
|
1313
|
+
const message =
|
|
1314
|
+
typeof body?.error === 'string' && body.error.trim()
|
|
1315
|
+
? body.error.trim()
|
|
1316
|
+
: response.statusText;
|
|
1317
|
+
throw new Error(
|
|
1318
|
+
`ctx.runPlay failed to read child terminal snapshot for "${input.childPlayName}": ${message} (status ${response.status}).`,
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
const data = body?.state?.data;
|
|
1322
|
+
const status = String(data?.status ?? '').toLowerCase();
|
|
1323
|
+
if (status === 'completed') {
|
|
1324
|
+
return { status, output: data?.result as TOutput };
|
|
1325
|
+
}
|
|
1326
|
+
if (status === 'failed' || status === 'cancelled') {
|
|
1327
|
+
return {
|
|
1328
|
+
status,
|
|
1329
|
+
error:
|
|
1330
|
+
typeof data?.error === 'string' && data.error.trim()
|
|
1331
|
+
? data.error.trim()
|
|
1332
|
+
: `Child play ${input.childPlayName} (${input.childRunId}) ${status}.`,
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
return null;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
926
1338
|
private emitScopedRowUpdate(
|
|
927
1339
|
key: string | null,
|
|
928
1340
|
tableNamespace: string | null,
|
|
@@ -990,7 +1402,7 @@ export class PlayContextImpl {
|
|
|
990
1402
|
this.#options.runId
|
|
991
1403
|
) {
|
|
992
1404
|
const response = await fetch(
|
|
993
|
-
`${this.#options.baseUrl.replace(/\/$/, '')}/api/v2/
|
|
1405
|
+
`${this.#options.baseUrl.replace(/\/$/, '')}/api/v2/internal/play-runtime`,
|
|
994
1406
|
{
|
|
995
1407
|
method: 'POST',
|
|
996
1408
|
headers: {
|
|
@@ -1102,6 +1514,7 @@ export class PlayContextImpl {
|
|
|
1102
1514
|
runId: string,
|
|
1103
1515
|
reclaimRunning = false,
|
|
1104
1516
|
forceRefresh = false,
|
|
1517
|
+
forceFailedRefresh = false,
|
|
1105
1518
|
): Promise<RuntimeStepReceipt | null> {
|
|
1106
1519
|
if (!this.#options.claimRuntimeStepReceipt) {
|
|
1107
1520
|
return null;
|
|
@@ -1109,8 +1522,11 @@ export class PlayContextImpl {
|
|
|
1109
1522
|
const claimed = await this.#options.claimRuntimeStepReceipt({
|
|
1110
1523
|
key,
|
|
1111
1524
|
runId,
|
|
1525
|
+
runAttempt: this.currentRunAttempt,
|
|
1526
|
+
leaseAware: true,
|
|
1112
1527
|
...(reclaimRunning ? { reclaimRunning: true } : {}),
|
|
1113
1528
|
...(forceRefresh ? { forceRefresh: true } : {}),
|
|
1529
|
+
...(forceFailedRefresh ? { forceFailedRefresh: true } : {}),
|
|
1114
1530
|
});
|
|
1115
1531
|
if (!claimed || typeof claimed.key !== 'string' || !claimed.key.trim()) {
|
|
1116
1532
|
return null;
|
|
@@ -1126,6 +1542,7 @@ export class PlayContextImpl {
|
|
|
1126
1542
|
key: string,
|
|
1127
1543
|
runId: string,
|
|
1128
1544
|
output: unknown | null,
|
|
1545
|
+
leaseId?: string | null,
|
|
1129
1546
|
): Promise<RuntimeStepReceipt | null> {
|
|
1130
1547
|
assertNoSecretTaint(output, 'ctx.step receipt output');
|
|
1131
1548
|
if (!this.#options.completeRuntimeStepReceipt) {
|
|
@@ -1134,6 +1551,8 @@ export class PlayContextImpl {
|
|
|
1134
1551
|
const completed = await this.#options.completeRuntimeStepReceipt({
|
|
1135
1552
|
key,
|
|
1136
1553
|
runId,
|
|
1554
|
+
runAttempt: this.currentRunAttempt,
|
|
1555
|
+
...(leaseId ? { leaseId } : {}),
|
|
1137
1556
|
output,
|
|
1138
1557
|
});
|
|
1139
1558
|
if (
|
|
@@ -1150,10 +1569,167 @@ export class PlayContextImpl {
|
|
|
1150
1569
|
};
|
|
1151
1570
|
}
|
|
1152
1571
|
|
|
1572
|
+
private async releaseRuntimeStepReceipt(
|
|
1573
|
+
key: string,
|
|
1574
|
+
runId: string,
|
|
1575
|
+
leaseId?: string | null,
|
|
1576
|
+
): Promise<RuntimeStepReceipt | null> {
|
|
1577
|
+
if (!this.#options.releaseRuntimeStepReceipt) {
|
|
1578
|
+
return null;
|
|
1579
|
+
}
|
|
1580
|
+
const released = await this.#options.releaseRuntimeStepReceipt({
|
|
1581
|
+
key,
|
|
1582
|
+
runId,
|
|
1583
|
+
runAttempt: this.currentRunAttempt,
|
|
1584
|
+
...(leaseId ? { leaseId } : {}),
|
|
1585
|
+
});
|
|
1586
|
+
if (!released || typeof released.key !== 'string' || !released.key.trim()) {
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1589
|
+
return {
|
|
1590
|
+
...released,
|
|
1591
|
+
key: released.key.trim(),
|
|
1592
|
+
runId: released.runId ?? null,
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
private async heartbeatRuntimeStepReceipt(
|
|
1597
|
+
key: string,
|
|
1598
|
+
runId: string,
|
|
1599
|
+
leaseId: string,
|
|
1600
|
+
): Promise<RuntimeStepReceipt | null> {
|
|
1601
|
+
if (!this.#options.heartbeatRuntimeStepReceipts) {
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
const [receipt] = await this.#options.heartbeatRuntimeStepReceipts({
|
|
1605
|
+
runId,
|
|
1606
|
+
runAttempt: this.currentRunAttempt,
|
|
1607
|
+
leaseId,
|
|
1608
|
+
keys: [key],
|
|
1609
|
+
});
|
|
1610
|
+
if (!receipt || typeof receipt.key !== 'string' || !receipt.key.trim()) {
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
return {
|
|
1614
|
+
...receipt,
|
|
1615
|
+
key: receipt.key.trim(),
|
|
1616
|
+
runId: receipt.runId ?? null,
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
private async assertRuntimeToolReceiptOwnership(
|
|
1621
|
+
requests: ToolCallRequest[],
|
|
1622
|
+
): Promise<void> {
|
|
1623
|
+
const targets = requests.flatMap((request) => {
|
|
1624
|
+
const receiptKey = request.receiptKey?.trim() || null;
|
|
1625
|
+
if (!receiptKey) return [];
|
|
1626
|
+
const leaseId = request.receiptLeaseId?.trim() || null;
|
|
1627
|
+
if (!leaseId) return [];
|
|
1628
|
+
return [{ receiptKey, leaseId }];
|
|
1629
|
+
});
|
|
1630
|
+
if (targets.length === 0) return;
|
|
1631
|
+
|
|
1632
|
+
const byLeaseId = new Map<string, string[]>();
|
|
1633
|
+
for (const target of targets) {
|
|
1634
|
+
const keys = byLeaseId.get(target.leaseId) ?? [];
|
|
1635
|
+
keys.push(target.receiptKey);
|
|
1636
|
+
byLeaseId.set(target.leaseId, keys);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
if (this.#options.heartbeatRuntimeStepReceipts) {
|
|
1640
|
+
for (const [leaseId, keys] of byLeaseId) {
|
|
1641
|
+
const receipts = await this.#options.heartbeatRuntimeStepReceipts({
|
|
1642
|
+
runId: this.currentRunId,
|
|
1643
|
+
runAttempt: this.currentRunAttempt,
|
|
1644
|
+
leaseId,
|
|
1645
|
+
keys,
|
|
1646
|
+
});
|
|
1647
|
+
this.assertRuntimeToolReceiptHeartbeatResult(keys, leaseId, receipts);
|
|
1648
|
+
}
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
if (
|
|
1653
|
+
!this.#options.getRuntimeStepReceipt &&
|
|
1654
|
+
!this.#options.getRuntimeStepReceipts
|
|
1655
|
+
) {
|
|
1656
|
+
throw new RuntimeReceiptLeaseLostError({
|
|
1657
|
+
receiptKey: targets[0]?.receiptKey ?? 'unknown',
|
|
1658
|
+
runId: this.currentRunId,
|
|
1659
|
+
leaseId: targets[0]?.leaseId ?? 'unknown',
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const latest = await this.getRuntimeStepReceipts(
|
|
1664
|
+
targets.map((target) => target.receiptKey),
|
|
1665
|
+
);
|
|
1666
|
+
for (const target of targets) {
|
|
1667
|
+
const receipt = latest.get(target.receiptKey);
|
|
1668
|
+
if (!this.runtimeToolReceiptStillOwned(receipt, target.leaseId)) {
|
|
1669
|
+
throw new RuntimeReceiptLeaseLostError({
|
|
1670
|
+
receiptKey: target.receiptKey,
|
|
1671
|
+
runId: this.currentRunId,
|
|
1672
|
+
leaseId: target.leaseId,
|
|
1673
|
+
});
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
private assertRuntimeToolReceiptHeartbeatResult(
|
|
1679
|
+
keys: string[],
|
|
1680
|
+
leaseId: string,
|
|
1681
|
+
receipts: Array<RuntimeStepReceipt | null>,
|
|
1682
|
+
): void {
|
|
1683
|
+
for (let index = 0; index < keys.length; index += 1) {
|
|
1684
|
+
const receipt = this.normalizeRuntimeStepReceipt(
|
|
1685
|
+
keys[index] ?? '',
|
|
1686
|
+
receipts[index],
|
|
1687
|
+
);
|
|
1688
|
+
if (!this.runtimeToolReceiptStillOwned(receipt, leaseId)) {
|
|
1689
|
+
throw new RuntimeReceiptLeaseLostError({
|
|
1690
|
+
receiptKey: keys[index] ?? 'unknown',
|
|
1691
|
+
runId: this.currentRunId,
|
|
1692
|
+
leaseId,
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
private runtimeToolReceiptStillOwned(
|
|
1699
|
+
receipt: RuntimeStepReceipt | null | undefined,
|
|
1700
|
+
leaseId: string,
|
|
1701
|
+
): boolean {
|
|
1702
|
+
if (!receipt || receipt.status !== 'running') return false;
|
|
1703
|
+
if (receipt.leaseId !== leaseId) return false;
|
|
1704
|
+
const ownerRunId = receipt.leaseOwnerRunId ?? receipt.runId ?? null;
|
|
1705
|
+
return !ownerRunId || ownerRunId === this.currentRunId;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
private runtimeToolReceiptStillOwnedByCurrentAttempt(
|
|
1709
|
+
receipt: RuntimeStepReceipt | null | undefined,
|
|
1710
|
+
leaseId: string | null | undefined,
|
|
1711
|
+
): boolean {
|
|
1712
|
+
if (!receipt || receipt.status !== 'running') return false;
|
|
1713
|
+
if (leaseId?.trim()) {
|
|
1714
|
+
if (receipt.leaseId !== leaseId.trim()) return false;
|
|
1715
|
+
} else if (receipt.leaseId != null) {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
const ownerRunId = receipt.leaseOwnerRunId ?? receipt.runId ?? null;
|
|
1719
|
+
if (ownerRunId !== this.currentRunId) return false;
|
|
1720
|
+
const ownerAttempt =
|
|
1721
|
+
typeof receipt.leaseOwnerAttempt === 'number'
|
|
1722
|
+
? receipt.leaseOwnerAttempt
|
|
1723
|
+
: 0;
|
|
1724
|
+
return ownerAttempt === this.currentRunAttempt;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1153
1727
|
private async failRuntimeStepReceipt(
|
|
1154
1728
|
key: string,
|
|
1155
1729
|
runId: string,
|
|
1156
1730
|
error: string,
|
|
1731
|
+
leaseId?: string | null,
|
|
1732
|
+
failureKind?: WorkReceiptFailureKind | null,
|
|
1157
1733
|
): Promise<RuntimeStepReceipt | null> {
|
|
1158
1734
|
if (!this.#options.failRuntimeStepReceipt) {
|
|
1159
1735
|
return null;
|
|
@@ -1161,6 +1737,9 @@ export class PlayContextImpl {
|
|
|
1161
1737
|
const failed = await this.#options.failRuntimeStepReceipt({
|
|
1162
1738
|
key,
|
|
1163
1739
|
runId,
|
|
1740
|
+
runAttempt: this.currentRunAttempt,
|
|
1741
|
+
...(leaseId ? { leaseId } : {}),
|
|
1742
|
+
...(failureKind ? { failureKind } : {}),
|
|
1164
1743
|
error,
|
|
1165
1744
|
});
|
|
1166
1745
|
if (!failed || typeof failed.key !== 'string' || !failed.key.trim()) {
|
|
@@ -1220,6 +1799,7 @@ export class PlayContextImpl {
|
|
|
1220
1799
|
runId: string,
|
|
1221
1800
|
reclaimRunning = false,
|
|
1222
1801
|
forceRefresh = false,
|
|
1802
|
+
forceFailedRefresh = false,
|
|
1223
1803
|
): Promise<Map<string, RuntimeStepReceipt>> {
|
|
1224
1804
|
const uniqueKeys = [
|
|
1225
1805
|
...new Set(keys.map((key) => key.trim()).filter(Boolean)),
|
|
@@ -1229,8 +1809,11 @@ export class PlayContextImpl {
|
|
|
1229
1809
|
? await this.#options.claimRuntimeStepReceipts({
|
|
1230
1810
|
keys: uniqueKeys,
|
|
1231
1811
|
runId,
|
|
1812
|
+
runAttempt: this.currentRunAttempt,
|
|
1813
|
+
leaseAware: true,
|
|
1232
1814
|
...(reclaimRunning ? { reclaimRunning: true } : {}),
|
|
1233
1815
|
...(forceRefresh ? { forceRefresh: true } : {}),
|
|
1816
|
+
...(forceFailedRefresh ? { forceFailedRefresh: true } : {}),
|
|
1234
1817
|
})
|
|
1235
1818
|
: await Promise.all(
|
|
1236
1819
|
uniqueKeys.map((key) =>
|
|
@@ -1239,6 +1822,7 @@ export class PlayContextImpl {
|
|
|
1239
1822
|
runId,
|
|
1240
1823
|
reclaimRunning,
|
|
1241
1824
|
forceRefresh,
|
|
1825
|
+
forceFailedRefresh,
|
|
1242
1826
|
),
|
|
1243
1827
|
),
|
|
1244
1828
|
);
|
|
@@ -1254,26 +1838,44 @@ export class PlayContextImpl {
|
|
|
1254
1838
|
}
|
|
1255
1839
|
|
|
1256
1840
|
private async completeRuntimeStepReceipts(
|
|
1257
|
-
receipts: Array<{
|
|
1841
|
+
receipts: Array<{
|
|
1842
|
+
key: string;
|
|
1843
|
+
runId: string;
|
|
1844
|
+
runAttempt?: number | null;
|
|
1845
|
+
output: unknown | null;
|
|
1846
|
+
leaseId?: string | null;
|
|
1847
|
+
}>,
|
|
1258
1848
|
): Promise<Map<string, RuntimeStepReceipt>> {
|
|
1259
1849
|
const normalizedInputs = receipts.filter((receipt) => receipt.key.trim());
|
|
1260
1850
|
if (normalizedInputs.length === 0) return new Map();
|
|
1261
1851
|
for (const receipt of normalizedInputs) {
|
|
1262
1852
|
assertNoSecretTaint(receipt.output, 'ctx.tool receipt output');
|
|
1263
1853
|
}
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1854
|
+
let completed: Array<RuntimeStepReceipt | null>;
|
|
1855
|
+
try {
|
|
1856
|
+
completed = this.#options.completeRuntimeStepReceipts
|
|
1857
|
+
? await this.#options.completeRuntimeStepReceipts({
|
|
1858
|
+
receipts: normalizedInputs.map((receipt) => ({
|
|
1859
|
+
...receipt,
|
|
1860
|
+
runAttempt: receipt.runAttempt ?? this.currentRunAttempt,
|
|
1861
|
+
})),
|
|
1862
|
+
})
|
|
1863
|
+
: await Promise.all(
|
|
1864
|
+
normalizedInputs.map((receipt) =>
|
|
1865
|
+
this.completeRuntimeStepReceipt(
|
|
1866
|
+
receipt.key,
|
|
1867
|
+
receipt.runId,
|
|
1868
|
+
receipt.output,
|
|
1869
|
+
receipt.leaseId,
|
|
1870
|
+
),
|
|
1274
1871
|
),
|
|
1275
|
-
)
|
|
1276
|
-
|
|
1872
|
+
);
|
|
1873
|
+
} catch (error) {
|
|
1874
|
+
await this.recoverRuntimeStepReceiptCompletionsAfterBulkError(
|
|
1875
|
+
normalizedInputs,
|
|
1876
|
+
);
|
|
1877
|
+
throw error;
|
|
1878
|
+
}
|
|
1277
1879
|
const byKey = new Map<string, RuntimeStepReceipt>();
|
|
1278
1880
|
for (let index = 0; index < completed.length; index += 1) {
|
|
1279
1881
|
const normalized = this.normalizeRuntimeStepReceipt(
|
|
@@ -1282,17 +1884,123 @@ export class PlayContextImpl {
|
|
|
1282
1884
|
);
|
|
1283
1885
|
if (normalized) byKey.set(normalized.key, normalized);
|
|
1284
1886
|
}
|
|
1887
|
+
await this.reconcileMissingRuntimeStepReceiptCompletions(
|
|
1888
|
+
normalizedInputs,
|
|
1889
|
+
byKey,
|
|
1890
|
+
);
|
|
1285
1891
|
return byKey;
|
|
1286
1892
|
}
|
|
1287
1893
|
|
|
1894
|
+
private async reconcileMissingRuntimeStepReceiptCompletions(
|
|
1895
|
+
receipts: Array<{
|
|
1896
|
+
key: string;
|
|
1897
|
+
runId: string;
|
|
1898
|
+
runAttempt?: number | null;
|
|
1899
|
+
output: unknown | null;
|
|
1900
|
+
leaseId?: string | null;
|
|
1901
|
+
}>,
|
|
1902
|
+
completedByKey: Map<string, RuntimeStepReceipt>,
|
|
1903
|
+
): Promise<void> {
|
|
1904
|
+
if (
|
|
1905
|
+
receipts.length === completedByKey.size ||
|
|
1906
|
+
(!this.#options.getRuntimeStepReceipt &&
|
|
1907
|
+
!this.#options.getRuntimeStepReceipts)
|
|
1908
|
+
) {
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
const missingKeys = receipts
|
|
1912
|
+
.map((receipt) => receipt.key.trim())
|
|
1913
|
+
.filter((key) => key && !completedByKey.has(key));
|
|
1914
|
+
if (missingKeys.length === 0) return;
|
|
1915
|
+
const latest = await this.getRuntimeStepReceipts(missingKeys);
|
|
1916
|
+
for (const key of missingKeys) {
|
|
1917
|
+
const recovered = latest.get(key);
|
|
1918
|
+
if (
|
|
1919
|
+
recovered?.status === 'completed' ||
|
|
1920
|
+
recovered?.status === 'skipped'
|
|
1921
|
+
) {
|
|
1922
|
+
completedByKey.set(key, recovered);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
const recoveredCount = missingKeys.filter((key) =>
|
|
1926
|
+
completedByKey.has(key),
|
|
1927
|
+
).length;
|
|
1928
|
+
if (recoveredCount > 0) {
|
|
1929
|
+
this.log(
|
|
1930
|
+
`Runtime tool receipt completion response reconciled ${recoveredCount}/${missingKeys.length} missing receipt(s) from durable store read-back.`,
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
private async recoverRuntimeStepReceiptCompletionsAfterBulkError(
|
|
1936
|
+
receipts: Array<{
|
|
1937
|
+
key: string;
|
|
1938
|
+
runId: string;
|
|
1939
|
+
runAttempt?: number | null;
|
|
1940
|
+
output: unknown | null;
|
|
1941
|
+
leaseId?: string | null;
|
|
1942
|
+
}>,
|
|
1943
|
+
): Promise<void> {
|
|
1944
|
+
if (
|
|
1945
|
+
receipts.length === 0 ||
|
|
1946
|
+
(!this.#options.getRuntimeStepReceipt &&
|
|
1947
|
+
!this.#options.getRuntimeStepReceipts)
|
|
1948
|
+
) {
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
let latest: Map<string, RuntimeStepReceipt>;
|
|
1952
|
+
try {
|
|
1953
|
+
latest = await this.getRuntimeStepReceipts(
|
|
1954
|
+
receipts.map((receipt) => receipt.key),
|
|
1955
|
+
);
|
|
1956
|
+
} catch {
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
await Promise.allSettled(
|
|
1960
|
+
receipts.map(async (receipt) => {
|
|
1961
|
+
const recovered = latest.get(receipt.key);
|
|
1962
|
+
if (
|
|
1963
|
+
recovered?.status === 'completed' ||
|
|
1964
|
+
recovered?.status === 'skipped'
|
|
1965
|
+
) {
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (
|
|
1969
|
+
!this.runtimeToolReceiptStillOwnedByCurrentAttempt(
|
|
1970
|
+
recovered,
|
|
1971
|
+
receipt.leaseId,
|
|
1972
|
+
)
|
|
1973
|
+
) {
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
await this.completeRuntimeStepReceipt(
|
|
1977
|
+
receipt.key,
|
|
1978
|
+
receipt.runId,
|
|
1979
|
+
receipt.output,
|
|
1980
|
+
receipt.leaseId,
|
|
1981
|
+
);
|
|
1982
|
+
}),
|
|
1983
|
+
);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1288
1986
|
private async failRuntimeStepReceipts(
|
|
1289
|
-
receipts: Array<{
|
|
1987
|
+
receipts: Array<{
|
|
1988
|
+
key: string;
|
|
1989
|
+
runId: string;
|
|
1990
|
+
runAttempt?: number | null;
|
|
1991
|
+
error: string;
|
|
1992
|
+
leaseId?: string | null;
|
|
1993
|
+
failureKind?: WorkReceiptFailureKind | null;
|
|
1994
|
+
}>,
|
|
1290
1995
|
): Promise<Map<string, RuntimeStepReceipt>> {
|
|
1291
1996
|
const normalizedInputs = receipts.filter((receipt) => receipt.key.trim());
|
|
1292
1997
|
if (normalizedInputs.length === 0) return new Map();
|
|
1293
1998
|
const failed = this.#options.failRuntimeStepReceipts
|
|
1294
1999
|
? await this.#options.failRuntimeStepReceipts({
|
|
1295
|
-
receipts: normalizedInputs
|
|
2000
|
+
receipts: normalizedInputs.map((receipt) => ({
|
|
2001
|
+
...receipt,
|
|
2002
|
+
runAttempt: receipt.runAttempt ?? this.currentRunAttempt,
|
|
2003
|
+
})),
|
|
1296
2004
|
})
|
|
1297
2005
|
: await Promise.all(
|
|
1298
2006
|
normalizedInputs.map((receipt) =>
|
|
@@ -1300,6 +2008,8 @@ export class PlayContextImpl {
|
|
|
1300
2008
|
receipt.key,
|
|
1301
2009
|
receipt.runId,
|
|
1302
2010
|
receipt.error,
|
|
2011
|
+
receipt.leaseId,
|
|
2012
|
+
receipt.failureKind,
|
|
1303
2013
|
),
|
|
1304
2014
|
),
|
|
1305
2015
|
);
|
|
@@ -1327,45 +2037,98 @@ export class PlayContextImpl {
|
|
|
1327
2037
|
);
|
|
1328
2038
|
}
|
|
1329
2039
|
|
|
1330
|
-
private async
|
|
2040
|
+
private async durableToolCallCacheKeyForScope(input: {
|
|
1331
2041
|
toolId: string;
|
|
1332
2042
|
requestInput: Record<string, unknown>;
|
|
2043
|
+
executionAuthScopeDigest?: string | null;
|
|
1333
2044
|
staleAfterSeconds?: number | null;
|
|
2045
|
+
playLocalScope?: string | null;
|
|
1334
2046
|
}): Promise<string> {
|
|
1335
2047
|
const providerActionVersion =
|
|
1336
2048
|
(await this.#options.getToolActionCacheVersion?.(input.toolId))?.trim() ??
|
|
1337
2049
|
'';
|
|
2050
|
+
const executionAuthScopeDigest =
|
|
2051
|
+
input.executionAuthScopeDigest?.trim() ||
|
|
2052
|
+
(await this.resolveToolAuthScopeDigest(input.toolId))?.trim() ||
|
|
2053
|
+
null;
|
|
1338
2054
|
return buildDurableToolCallCacheKey({
|
|
1339
2055
|
orgId: this.#options.orgId,
|
|
1340
|
-
|
|
2056
|
+
playLocalScope: input.playLocalScope,
|
|
1341
2057
|
toolId: input.toolId,
|
|
1342
2058
|
requestInput: input.requestInput,
|
|
1343
2059
|
authScopeDigest: buildDurableToolCallAuthScopeDigest({
|
|
1344
2060
|
orgId: this.#options.orgId,
|
|
1345
|
-
userEmail: this.#options.userEmail,
|
|
1346
2061
|
toolId: input.toolId,
|
|
2062
|
+
executionAuthScopeDigest,
|
|
1347
2063
|
}),
|
|
1348
2064
|
providerActionVersion,
|
|
1349
2065
|
staleAfterSeconds: input.staleAfterSeconds,
|
|
1350
2066
|
});
|
|
1351
2067
|
}
|
|
1352
2068
|
|
|
2069
|
+
private async durableToolCallCacheKey(input: {
|
|
2070
|
+
toolId: string;
|
|
2071
|
+
requestInput: Record<string, unknown>;
|
|
2072
|
+
executionAuthScopeDigest?: string | null;
|
|
2073
|
+
staleAfterSeconds?: number | null;
|
|
2074
|
+
}): Promise<string> {
|
|
2075
|
+
return await this.durableToolCallCacheKeyForScope({
|
|
2076
|
+
...input,
|
|
2077
|
+
playLocalScope: this.governance.currentPlayId,
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
private async resolveToolAuthScopeDigest(
|
|
2082
|
+
toolId: string,
|
|
2083
|
+
): Promise<string | null> {
|
|
2084
|
+
const configured = await this.#options.getToolAuthScopeDigest?.(toolId);
|
|
2085
|
+
if (typeof configured === 'string' && configured.trim()) {
|
|
2086
|
+
return configured.trim();
|
|
2087
|
+
}
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
private invalidateToolAuthScopeDigest(toolId: string): void {
|
|
2092
|
+
this.#options.invalidateToolAuthScopeDigest?.(toolId);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
1353
2095
|
private durableReceiptExecutionStore(): DurableReceiptExecutionStore {
|
|
1354
2096
|
return {
|
|
1355
2097
|
enabled: Boolean(this.#options.claimRuntimeStepReceipt),
|
|
1356
2098
|
get: (receiptKey) => this.getRuntimeStepReceipt(receiptKey),
|
|
1357
2099
|
getMany: (receiptKeys) => this.getRuntimeStepReceipts(receiptKeys),
|
|
1358
|
-
claim: (
|
|
2100
|
+
claim: (
|
|
2101
|
+
receiptKey,
|
|
2102
|
+
runId,
|
|
2103
|
+
reclaimRunning,
|
|
2104
|
+
forceRefresh,
|
|
2105
|
+
forceFailedRefresh,
|
|
2106
|
+
) =>
|
|
1359
2107
|
this.claimRuntimeStepReceipt(
|
|
1360
2108
|
receiptKey,
|
|
1361
2109
|
runId,
|
|
1362
2110
|
reclaimRunning,
|
|
1363
2111
|
forceRefresh,
|
|
2112
|
+
forceFailedRefresh,
|
|
2113
|
+
),
|
|
2114
|
+
complete: (receiptKey, runId, output, leaseId) =>
|
|
2115
|
+
this.completeRuntimeStepReceipt(receiptKey, runId, output, leaseId),
|
|
2116
|
+
release: (receiptKey, runId, leaseId) =>
|
|
2117
|
+
this.releaseRuntimeStepReceipt(receiptKey, runId, leaseId),
|
|
2118
|
+
...(this.#options.heartbeatRuntimeStepReceipts
|
|
2119
|
+
? {
|
|
2120
|
+
heartbeat: (receiptKey, runId, leaseId) =>
|
|
2121
|
+
this.heartbeatRuntimeStepReceipt(receiptKey, runId, leaseId),
|
|
2122
|
+
}
|
|
2123
|
+
: {}),
|
|
2124
|
+
fail: (receiptKey, runId, error, leaseId, failureKind) =>
|
|
2125
|
+
this.failRuntimeStepReceipt(
|
|
2126
|
+
receiptKey,
|
|
2127
|
+
runId,
|
|
2128
|
+
error,
|
|
2129
|
+
leaseId,
|
|
2130
|
+
failureKind,
|
|
1364
2131
|
),
|
|
1365
|
-
complete: (receiptKey, runId, output) =>
|
|
1366
|
-
this.completeRuntimeStepReceipt(receiptKey, runId, output),
|
|
1367
|
-
fail: (receiptKey, runId, error) =>
|
|
1368
|
-
this.failRuntimeStepReceipt(receiptKey, runId, error),
|
|
1369
2132
|
canPersistFailure: Boolean(this.#options.failRuntimeStepReceipt),
|
|
1370
2133
|
canPersistCompletion: Boolean(this.#options.completeRuntimeStepReceipt),
|
|
1371
2134
|
};
|
|
@@ -1407,7 +2170,8 @@ export class PlayContextImpl {
|
|
|
1407
2170
|
source: DurableReceiptRecoverySource,
|
|
1408
2171
|
) => T;
|
|
1409
2172
|
onClaimedResult?: (output: T, receiptKey: string) => T;
|
|
1410
|
-
|
|
2173
|
+
shouldPersistFailure?: (error: unknown) => boolean;
|
|
2174
|
+
execute: (context: { leaseId: string | null }) => Promise<T>;
|
|
1411
2175
|
},
|
|
1412
2176
|
): Promise<T> {
|
|
1413
2177
|
const receiptKey =
|
|
@@ -1436,6 +2200,7 @@ export class PlayContextImpl {
|
|
|
1436
2200
|
markSkipped: opts.markSkipped,
|
|
1437
2201
|
onRecovered: opts.onRecovered,
|
|
1438
2202
|
onClaimedResult: opts.onClaimedResult,
|
|
2203
|
+
shouldPersistFailure: opts.shouldPersistFailure,
|
|
1439
2204
|
formatError: (error) => this.formatRuntimeError(error),
|
|
1440
2205
|
log: (message) => this.log(message),
|
|
1441
2206
|
execute: opts.execute,
|
|
@@ -1446,6 +2211,13 @@ export class PlayContextImpl {
|
|
|
1446
2211
|
return this.#options.runId ?? this.governance.currentRunId ?? 'unknown';
|
|
1447
2212
|
}
|
|
1448
2213
|
|
|
2214
|
+
private get currentRunAttempt(): number {
|
|
2215
|
+
const attempt = this.#options.runAttempt;
|
|
2216
|
+
return typeof attempt === 'number' && Number.isFinite(attempt)
|
|
2217
|
+
? Math.max(0, Math.floor(attempt))
|
|
2218
|
+
: 0;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
1449
2221
|
private emitScopedFieldMetaUpdate(input: {
|
|
1450
2222
|
rowId: number;
|
|
1451
2223
|
key: string | null;
|
|
@@ -1659,12 +2431,17 @@ export class PlayContextImpl {
|
|
|
1659
2431
|
|
|
1660
2432
|
private effectiveToolCallCachePolicy(options?: ToolCallOptions): {
|
|
1661
2433
|
force: boolean;
|
|
2434
|
+
forceFailedRefresh: boolean;
|
|
1662
2435
|
staleAfterSeconds?: number | null;
|
|
1663
2436
|
} {
|
|
1664
2437
|
return {
|
|
1665
2438
|
force:
|
|
1666
2439
|
options?.force === true ||
|
|
1667
2440
|
this.#options.cachePolicy?.forceToolRefresh === true,
|
|
2441
|
+
forceFailedRefresh:
|
|
2442
|
+
options?.force === true ||
|
|
2443
|
+
this.#options.cachePolicy?.forceToolRefresh === true ||
|
|
2444
|
+
this.#options.cachePolicy?.forceFailedToolRefresh === true,
|
|
1668
2445
|
staleAfterSeconds: options?.staleAfterSeconds ?? null,
|
|
1669
2446
|
};
|
|
1670
2447
|
}
|
|
@@ -1685,6 +2462,19 @@ export class PlayContextImpl {
|
|
|
1685
2462
|
return this.checkpoint.completedToolBatches[toolId]?.[rowCacheKey];
|
|
1686
2463
|
}
|
|
1687
2464
|
|
|
2465
|
+
private getCachedToolResultCandidate(
|
|
2466
|
+
toolId: string,
|
|
2467
|
+
rowCacheKeys: readonly string[],
|
|
2468
|
+
): { cacheKey: string; result: ToolBatchResult } | null {
|
|
2469
|
+
for (const rowCacheKey of rowCacheKeys) {
|
|
2470
|
+
const cached = this.getCachedToolResult(toolId, rowCacheKey);
|
|
2471
|
+
if (cached?.done) {
|
|
2472
|
+
return { cacheKey: rowCacheKey, result: cached };
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
return null;
|
|
2476
|
+
}
|
|
2477
|
+
|
|
1688
2478
|
private cacheToolResult(
|
|
1689
2479
|
toolId: string,
|
|
1690
2480
|
rowCacheKey: string,
|
|
@@ -1914,6 +2704,7 @@ export class PlayContextImpl {
|
|
|
1914
2704
|
{
|
|
1915
2705
|
key: receiptKey,
|
|
1916
2706
|
runId: this.currentRunId,
|
|
2707
|
+
leaseId: request.receiptLeaseId ?? null,
|
|
1917
2708
|
output: serializeToolExecuteResult(wrapped),
|
|
1918
2709
|
},
|
|
1919
2710
|
])
|
|
@@ -1969,12 +2760,13 @@ export class PlayContextImpl {
|
|
|
1969
2760
|
{
|
|
1970
2761
|
key: receiptKey,
|
|
1971
2762
|
runId: this.currentRunId,
|
|
2763
|
+
leaseId: entry.request.receiptLeaseId ?? null,
|
|
1972
2764
|
output: serializeToolExecuteResult(entry.wrapped),
|
|
1973
2765
|
},
|
|
1974
2766
|
]
|
|
1975
2767
|
: [];
|
|
1976
2768
|
});
|
|
1977
|
-
let completedByKey
|
|
2769
|
+
let completedByKey = new Map<string, RuntimeStepReceipt>();
|
|
1978
2770
|
if (receiptInputs.length > 0) {
|
|
1979
2771
|
try {
|
|
1980
2772
|
completedByKey = await this.completeRuntimeStepReceipts(receiptInputs);
|
|
@@ -1982,10 +2774,15 @@ export class PlayContextImpl {
|
|
|
1982
2774
|
// Completion failed AFTER successful (billed) tool calls: trip the
|
|
1983
2775
|
// breaker so remaining rows stop dispatching new provider calls.
|
|
1984
2776
|
tripRuntimePersistenceLatch(this.persistenceLatch, receiptError);
|
|
2777
|
+
await Promise.all(
|
|
2778
|
+
wrappedEntries.map((entry) =>
|
|
2779
|
+
this.rejectToolCall(toolId, entry.request, receiptError, {
|
|
2780
|
+
persistReceiptFailure: false,
|
|
2781
|
+
}),
|
|
2782
|
+
),
|
|
2783
|
+
);
|
|
1985
2784
|
throw receiptError;
|
|
1986
2785
|
}
|
|
1987
|
-
} else {
|
|
1988
|
-
completedByKey = new Map<string, RuntimeStepReceipt>();
|
|
1989
2786
|
}
|
|
1990
2787
|
const results: unknown[] = [];
|
|
1991
2788
|
for (const entry of wrappedEntries) {
|
|
@@ -2009,9 +2806,23 @@ export class PlayContextImpl {
|
|
|
2009
2806
|
completed: RuntimeStepReceipt | null | undefined,
|
|
2010
2807
|
): Promise<unknown> {
|
|
2011
2808
|
const cacheKey = request.cacheKey;
|
|
2809
|
+
const receiptKey = request.receiptKey?.trim() || null;
|
|
2810
|
+
const canPersistReceiptCompletion = Boolean(
|
|
2811
|
+
this.#options.completeRuntimeStepReceipt ||
|
|
2812
|
+
this.#options.completeRuntimeStepReceipts,
|
|
2813
|
+
);
|
|
2814
|
+
if (
|
|
2815
|
+
receiptKey &&
|
|
2816
|
+
canPersistReceiptCompletion &&
|
|
2817
|
+
completed?.status !== 'completed' &&
|
|
2818
|
+
completed?.status !== 'skipped'
|
|
2819
|
+
) {
|
|
2820
|
+
throw new Error(
|
|
2821
|
+
`Durable tool call ${receiptKey} lost receipt ownership before completion.`,
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2012
2824
|
const finalWrapped =
|
|
2013
|
-
|
|
2014
|
-
completed.runId !== this.currentRunId
|
|
2825
|
+
completed?.status === 'completed' || completed?.status === 'skipped'
|
|
2015
2826
|
? await this.wrapToolExecutionResult({
|
|
2016
2827
|
toolId,
|
|
2017
2828
|
status:
|
|
@@ -2030,8 +2841,8 @@ export class PlayContextImpl {
|
|
|
2030
2841
|
: {
|
|
2031
2842
|
kind: 'cache',
|
|
2032
2843
|
cacheKey,
|
|
2033
|
-
receiptKey:
|
|
2034
|
-
attachedToReceiptKey:
|
|
2844
|
+
receiptKey: receiptKey ?? '',
|
|
2845
|
+
attachedToReceiptKey: receiptKey,
|
|
2035
2846
|
},
|
|
2036
2847
|
),
|
|
2037
2848
|
})
|
|
@@ -2076,7 +2887,9 @@ export class PlayContextImpl {
|
|
|
2076
2887
|
{
|
|
2077
2888
|
key: receiptKey,
|
|
2078
2889
|
runId: this.currentRunId,
|
|
2890
|
+
leaseId: request.receiptLeaseId ?? null,
|
|
2079
2891
|
error: message,
|
|
2892
|
+
failureKind: runtimeReceiptFailureKindForError(error),
|
|
2080
2893
|
},
|
|
2081
2894
|
]);
|
|
2082
2895
|
} catch (receiptError) {
|
|
@@ -2360,6 +3173,7 @@ export class PlayContextImpl {
|
|
|
2360
3173
|
this.toOutputRow(item as Record<string, unknown>),
|
|
2361
3174
|
);
|
|
2362
3175
|
itemOriginalIndexes = materializedItems.map((_item, index) => index);
|
|
3176
|
+
const mapStartSeedRowsByKey = new Map<string, Record<string, unknown>>();
|
|
2363
3177
|
if (this.#options.onMapStart) {
|
|
2364
3178
|
const shouldPassRowKey = explicitKeyResolver != null;
|
|
2365
3179
|
// toSerializableCsvAliasedRow (not a plain spread): projected CSV
|
|
@@ -2381,6 +3195,7 @@ export class PlayContextImpl {
|
|
|
2381
3195
|
playName: this.#options.playName,
|
|
2382
3196
|
playId: this.#options.playId,
|
|
2383
3197
|
runId: this.#options.runId,
|
|
3198
|
+
executorToken: this.#options.executorToken,
|
|
2384
3199
|
staticPipeline: this.#options.staticPipeline,
|
|
2385
3200
|
forceRefresh: this.#options.cachePolicy?.forceToolRefresh === true,
|
|
2386
3201
|
},
|
|
@@ -2388,13 +3203,14 @@ export class PlayContextImpl {
|
|
|
2388
3203
|
resolvedTableNamespace = normalizeTableNamespace(
|
|
2389
3204
|
mapStartResult.tableNamespace,
|
|
2390
3205
|
);
|
|
3206
|
+
if ((mapStartResult.blockedRows?.length ?? 0) > 0) {
|
|
3207
|
+
throw new RuntimeSheetRowsBlockedError({
|
|
3208
|
+
tableNamespace: resolvedTableNamespace,
|
|
3209
|
+
blockedRows: mapStartResult.blockedRows ?? [],
|
|
3210
|
+
});
|
|
3211
|
+
}
|
|
2391
3212
|
const persistedRowIdentity = (row: Record<string, unknown>, index = 0) =>
|
|
2392
3213
|
resolveMapRowOutcomeKey(row) ?? rowIdentity(row, index);
|
|
2393
|
-
const pendingKeys = new Set(
|
|
2394
|
-
mapStartResult.pendingRows.map((row, index) =>
|
|
2395
|
-
persistedRowIdentity(row, index),
|
|
2396
|
-
),
|
|
2397
|
-
);
|
|
2398
3214
|
const pendingRowsByKey = new Map<string, Record<string, unknown>>();
|
|
2399
3215
|
for (
|
|
2400
3216
|
let index = 0;
|
|
@@ -2405,15 +3221,20 @@ export class PlayContextImpl {
|
|
|
2405
3221
|
const rowKey = persistedRowIdentity(row, index);
|
|
2406
3222
|
if (rowKey) pendingRowsByKey.set(rowKey, row);
|
|
2407
3223
|
}
|
|
2408
|
-
const
|
|
3224
|
+
for (const row of mapStartResult.completedRows ?? []) {
|
|
3225
|
+
const rowKey = persistedRowIdentity(row);
|
|
3226
|
+
if (!rowKey) continue;
|
|
3227
|
+
mapStartSeedRowsByKey.set(rowKey, row);
|
|
3228
|
+
}
|
|
3229
|
+
const seededItems = materializedItems
|
|
2409
3230
|
.map((item, index) => ({
|
|
2410
3231
|
row: this.toOutputRow(item as Record<string, unknown>),
|
|
2411
3232
|
index,
|
|
2412
3233
|
}))
|
|
2413
|
-
.filter(({ row, index }) => pendingKeys.has(rowIdentity(row, index)))
|
|
2414
3234
|
.map(({ row, index }) => {
|
|
2415
3235
|
const rowKey = rowIdentity(row, index);
|
|
2416
|
-
const persisted =
|
|
3236
|
+
const persisted =
|
|
3237
|
+
pendingRowsByKey.get(rowKey) ?? mapStartSeedRowsByKey.get(rowKey);
|
|
2417
3238
|
// Persisted rows lose the non-enumerable CSV alias projections on
|
|
2418
3239
|
// the sheet round-trip. Merge the persisted data/meta over the
|
|
2419
3240
|
// in-memory row so key functions and column resolvers keep seeing
|
|
@@ -2423,8 +3244,8 @@ export class PlayContextImpl {
|
|
|
2423
3244
|
index,
|
|
2424
3245
|
};
|
|
2425
3246
|
});
|
|
2426
|
-
itemsToProcess =
|
|
2427
|
-
itemOriginalIndexes =
|
|
3247
|
+
itemsToProcess = seededItems.map((item) => item.row);
|
|
3248
|
+
itemOriginalIndexes = seededItems.map((item) => item.index);
|
|
2428
3249
|
}
|
|
2429
3250
|
|
|
2430
3251
|
const mapScope = this.createMapExecutionScope({
|
|
@@ -2495,6 +3316,7 @@ export class PlayContextImpl {
|
|
|
2495
3316
|
playName: this.#options.playName,
|
|
2496
3317
|
playId: this.#options.playId,
|
|
2497
3318
|
runId: this.#options.runId,
|
|
3319
|
+
executorToken: this.#options.executorToken,
|
|
2498
3320
|
tableNamespace: resolvedTableNamespace,
|
|
2499
3321
|
rows: chunk,
|
|
2500
3322
|
outputFields: datasetColumnNames.filter((field) =>
|
|
@@ -2580,7 +3402,7 @@ export class PlayContextImpl {
|
|
|
2580
3402
|
const directCompletedResults = mapResult.completedRows.map((row) =>
|
|
2581
3403
|
this.toPublicOutputRow(row.data),
|
|
2582
3404
|
);
|
|
2583
|
-
|
|
3405
|
+
let results =
|
|
2584
3406
|
mapResult.failedRows.length === 0 &&
|
|
2585
3407
|
directCompletedResults.length === rawItems.length
|
|
2586
3408
|
? directCompletedResults
|
|
@@ -2633,6 +3455,49 @@ export class PlayContextImpl {
|
|
|
2633
3455
|
}
|
|
2634
3456
|
this.activeMapCellMeta = null;
|
|
2635
3457
|
|
|
3458
|
+
if (this.#options.onMapRowsCompleted) {
|
|
3459
|
+
results = await reconcileNodeRuntimeMapResultsWithPersistedSheet({
|
|
3460
|
+
mapName: normalizedMapNamespace,
|
|
3461
|
+
tableNamespace: resolvedTableNamespace,
|
|
3462
|
+
runId: this.#options.runId,
|
|
3463
|
+
expectedRows: totalInputCount,
|
|
3464
|
+
currentRows: results,
|
|
3465
|
+
failedRowCount: mapResult.failedRows.length,
|
|
3466
|
+
log: (line) => this.log(line),
|
|
3467
|
+
readPersistedRows: async (readInput) => {
|
|
3468
|
+
if (
|
|
3469
|
+
!this.#options.baseUrl ||
|
|
3470
|
+
!this.#options.executorToken ||
|
|
3471
|
+
!this.#options.playName ||
|
|
3472
|
+
!this.#options.runId
|
|
3473
|
+
) {
|
|
3474
|
+
throw new Error(
|
|
3475
|
+
`Runtime sheet finalization mismatch for ctx.dataset("${normalizedMapNamespace}"): ` +
|
|
3476
|
+
'cannot verify terminal persisted rows because the Node runtime context is missing ' +
|
|
3477
|
+
'baseUrl, executorToken, playName, or runId.',
|
|
3478
|
+
);
|
|
3479
|
+
}
|
|
3480
|
+
const result = await readRuntimeSheetDatasetRows(
|
|
3481
|
+
{
|
|
3482
|
+
baseUrl: this.#options.baseUrl,
|
|
3483
|
+
executorToken: this.#options.executorToken,
|
|
3484
|
+
playName: this.#options.playName,
|
|
3485
|
+
userEmail: this.#options.userEmail,
|
|
3486
|
+
runId: this.#options.runId,
|
|
3487
|
+
},
|
|
3488
|
+
{
|
|
3489
|
+
tableNamespace: resolvedTableNamespace,
|
|
3490
|
+
runId: this.#options.runId,
|
|
3491
|
+
rowMode: readInput.rowMode,
|
|
3492
|
+
limit: readInput.limit,
|
|
3493
|
+
offset: readInput.offset,
|
|
3494
|
+
},
|
|
3495
|
+
);
|
|
3496
|
+
return result.rows;
|
|
3497
|
+
},
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3500
|
+
|
|
2636
3501
|
return createDeferredPlayDataset({
|
|
2637
3502
|
datasetKind: 'map',
|
|
2638
3503
|
datasetId: createRuntimeDatasetId(
|
|
@@ -2909,7 +3774,7 @@ export class PlayContextImpl {
|
|
|
2909
3774
|
this.lastDatasetStep = datasetStep;
|
|
2910
3775
|
this.activeDatasetStep = null;
|
|
2911
3776
|
this.log(
|
|
2912
|
-
`Map completed: ${results.length + completedRows} results (${results.length}
|
|
3777
|
+
`Map completed: ${results.length + completedRows} results (${results.length} succeeded, 0 failed, ${completedRows} duplicate keys skipped)`,
|
|
2913
3778
|
);
|
|
2914
3779
|
return {
|
|
2915
3780
|
completedRows: results.map((row, index) =>
|
|
@@ -3183,6 +4048,17 @@ export class PlayContextImpl {
|
|
|
3183
4048
|
}
|
|
3184
4049
|
return result.value;
|
|
3185
4050
|
});
|
|
4051
|
+
const accountedRowIndexes = new Set<number>();
|
|
4052
|
+
for (const row of completedRowsToPersist) {
|
|
4053
|
+
if (typeof row.inputIndex === 'number') {
|
|
4054
|
+
accountedRowIndexes.add(row.inputIndex);
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
for (const row of failedRowsToPersist) {
|
|
4058
|
+
if (typeof row.inputIndex === 'number') {
|
|
4059
|
+
accountedRowIndexes.add(row.inputIndex);
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
3186
4062
|
if (this.pendingRowEventBoundaries.length > 0) {
|
|
3187
4063
|
const completedRowKeys = new Set<string>();
|
|
3188
4064
|
for (let index = 0; index < settledResults.length; index += 1) {
|
|
@@ -3195,6 +4071,7 @@ export class PlayContextImpl {
|
|
|
3195
4071
|
if (key) {
|
|
3196
4072
|
completedRowKeys.add(key);
|
|
3197
4073
|
}
|
|
4074
|
+
accountedRowIndexes.add(executionRowIndex(index));
|
|
3198
4075
|
continue;
|
|
3199
4076
|
}
|
|
3200
4077
|
const row = result as Record<string, unknown>;
|
|
@@ -3238,6 +4115,19 @@ export class PlayContextImpl {
|
|
|
3238
4115
|
boundaries: uniqueBoundaries,
|
|
3239
4116
|
});
|
|
3240
4117
|
}
|
|
4118
|
+
if (accountedRowIndexes.size !== items.length) {
|
|
4119
|
+
const expectedRowIndexes = items.map((_item, index) =>
|
|
4120
|
+
executionRowIndex(index),
|
|
4121
|
+
);
|
|
4122
|
+
const missingRowIndexes = expectedRowIndexes.filter(
|
|
4123
|
+
(index) => !accountedRowIndexes.has(index),
|
|
4124
|
+
);
|
|
4125
|
+
throw new Error(
|
|
4126
|
+
`Runtime map execution accounting mismatch for ctx.dataset("${normalizedTableNamespace}"): ` +
|
|
4127
|
+
`expected ${items.length} row(s), accounted for ${accountedRowIndexes.size}; ` +
|
|
4128
|
+
`missing input index(es): ${missingRowIndexes.slice(0, 10).join(', ') || 'unknown'}.`,
|
|
4129
|
+
);
|
|
4130
|
+
}
|
|
3241
4131
|
const results = settledResults.filter(
|
|
3242
4132
|
(result): result is Record<string, unknown> =>
|
|
3243
4133
|
result !== WAITING_ROW && result !== FAILED_ROW,
|
|
@@ -3249,8 +4139,18 @@ export class PlayContextImpl {
|
|
|
3249
4139
|
emitEventType: 'map.completed',
|
|
3250
4140
|
});
|
|
3251
4141
|
}
|
|
4142
|
+
for (const failedRow of failedRowsToPersist.slice(0, 3)) {
|
|
4143
|
+
this.log(
|
|
4144
|
+
`row ${failedMapRowLogLabel(failedRow)} failed: ${failedRow.error ?? 'unknown error'}`,
|
|
4145
|
+
);
|
|
4146
|
+
}
|
|
4147
|
+
if (failedRowsToPersist.length > 3) {
|
|
4148
|
+
this.log(
|
|
4149
|
+
`${failedRowsToPersist.length - 3} additional row failure(s) omitted from map log`,
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
3252
4152
|
this.log(
|
|
3253
|
-
`Map completed: ${results.length + completedRows} results (${results.length}
|
|
4153
|
+
`Map completed: ${results.length + completedRows} results (${results.length} succeeded, ${failedRowsToPersist.length} failed, ${completedRows} duplicate keys skipped)`,
|
|
3254
4154
|
);
|
|
3255
4155
|
return {
|
|
3256
4156
|
completedRows: [...completedRowsToPersist].sort(
|
|
@@ -3560,6 +4460,13 @@ export class PlayContextImpl {
|
|
|
3560
4460
|
if (!hasPendingWork) {
|
|
3561
4461
|
const status = await raceSettled();
|
|
3562
4462
|
if (status === 'done' && this.toolCallQueue.length === 0) {
|
|
4463
|
+
if (!this.activeDatasetStep) {
|
|
4464
|
+
const childPlaySuspension =
|
|
4465
|
+
this.consumePendingChildPlaySuspension();
|
|
4466
|
+
if (childPlaySuspension) {
|
|
4467
|
+
throw new PlayExecutionSuspendedError(childPlaySuspension);
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
3563
4470
|
break;
|
|
3564
4471
|
}
|
|
3565
4472
|
|
|
@@ -3656,18 +4563,23 @@ export class PlayContextImpl {
|
|
|
3656
4563
|
toolId,
|
|
3657
4564
|
requestInput: input,
|
|
3658
4565
|
});
|
|
4566
|
+
let executionAuthScopeDigest =
|
|
4567
|
+
(await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null;
|
|
3659
4568
|
const cacheableToolResult = !isQueryResultDatasetReadRequest(toolId, input);
|
|
3660
|
-
|
|
4569
|
+
let durableCacheKey = await this.durableToolCallCacheKey({
|
|
3661
4570
|
toolId,
|
|
3662
4571
|
requestInput: input,
|
|
4572
|
+
executionAuthScopeDigest,
|
|
3663
4573
|
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
3664
4574
|
});
|
|
3665
|
-
const
|
|
4575
|
+
const checkpointCacheKeys = [durableCacheKey];
|
|
3666
4576
|
const eventWaitHandler =
|
|
3667
4577
|
(await this.#options.getIntegrationEventWaitHandler?.(toolId)) ?? null;
|
|
3668
4578
|
const store = rowContext.getStore();
|
|
3669
4579
|
|
|
3670
|
-
const executeTool = async (
|
|
4580
|
+
const executeTool = async (context?: {
|
|
4581
|
+
leaseId?: string | null;
|
|
4582
|
+
}): Promise<unknown> => {
|
|
3671
4583
|
if (eventWaitHandler) {
|
|
3672
4584
|
return this.executeIntegrationEventWaitTool(
|
|
3673
4585
|
toolId,
|
|
@@ -3694,17 +4606,17 @@ export class PlayContextImpl {
|
|
|
3694
4606
|
? null
|
|
3695
4607
|
: toolCachePolicy.force
|
|
3696
4608
|
? null
|
|
3697
|
-
: this.
|
|
3698
|
-
if (cached
|
|
3699
|
-
this.log(`
|
|
4609
|
+
: this.getCachedToolResultCandidate(toolId, checkpointCacheKeys);
|
|
4610
|
+
if (cached) {
|
|
4611
|
+
this.log(`Calling tool: ${toolId} recovered from checkpoint`);
|
|
3700
4612
|
return await this.wrapToolExecutionResult({
|
|
3701
4613
|
toolId,
|
|
3702
|
-
status: cached.result == null ? 'no_result' : 'completed',
|
|
3703
|
-
result: cached.result,
|
|
4614
|
+
status: cached.result.result == null ? 'no_result' : 'completed',
|
|
4615
|
+
result: cached.result.result,
|
|
3704
4616
|
requestInput: input,
|
|
3705
4617
|
execution: toolExecutionMetadataForOutcome({
|
|
3706
4618
|
kind: 'checkpoint',
|
|
3707
|
-
cacheKey:
|
|
4619
|
+
cacheKey: cached.cacheKey,
|
|
3708
4620
|
}),
|
|
3709
4621
|
});
|
|
3710
4622
|
}
|
|
@@ -3714,6 +4626,17 @@ export class PlayContextImpl {
|
|
|
3714
4626
|
: `Calling tool: ${toolId}`,
|
|
3715
4627
|
);
|
|
3716
4628
|
const execution = await this.callToolExecutionAPI(toolId, input, {
|
|
4629
|
+
...(cacheableToolResult
|
|
4630
|
+
? {
|
|
4631
|
+
durableCallReceiptKey: directCacheKey,
|
|
4632
|
+
executionAuthScopeDigest,
|
|
4633
|
+
providerIdempotencyKey: this.providerIdempotencyKeyForToolCall({
|
|
4634
|
+
cacheKey: directCacheKey,
|
|
4635
|
+
force: toolCachePolicy.force,
|
|
4636
|
+
leaseId: context?.leaseId ?? null,
|
|
4637
|
+
}),
|
|
4638
|
+
}
|
|
4639
|
+
: {}),
|
|
3717
4640
|
timeoutMs: resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs),
|
|
3718
4641
|
});
|
|
3719
4642
|
const wrapped = await this.wrapToolExecutionResult({
|
|
@@ -3763,17 +4686,17 @@ export class PlayContextImpl {
|
|
|
3763
4686
|
? null
|
|
3764
4687
|
: toolCachePolicy.force
|
|
3765
4688
|
? null
|
|
3766
|
-
: this.
|
|
3767
|
-
if (cached
|
|
3768
|
-
this.log(` Row ${rowId} ${toolId}:
|
|
4689
|
+
: this.getCachedToolResultCandidate(toolId, checkpointCacheKeys);
|
|
4690
|
+
if (cached) {
|
|
4691
|
+
this.log(` Row ${rowId} ${toolId}: recovered from checkpoint`);
|
|
3769
4692
|
return await this.wrapToolExecutionResult({
|
|
3770
4693
|
toolId,
|
|
3771
|
-
status: cached.result == null ? 'no_result' : 'completed',
|
|
3772
|
-
result: cached.result,
|
|
4694
|
+
status: cached.result.result == null ? 'no_result' : 'completed',
|
|
4695
|
+
result: cached.result.result,
|
|
3773
4696
|
requestInput: input,
|
|
3774
4697
|
execution: toolExecutionMetadataForOutcome({
|
|
3775
4698
|
kind: 'checkpoint',
|
|
3776
|
-
cacheKey:
|
|
4699
|
+
cacheKey: cached.cacheKey,
|
|
3777
4700
|
}),
|
|
3778
4701
|
});
|
|
3779
4702
|
}
|
|
@@ -3806,8 +4729,10 @@ export class PlayContextImpl {
|
|
|
3806
4729
|
callId,
|
|
3807
4730
|
cacheKey: toolResultCacheKey,
|
|
3808
4731
|
cacheable: cacheableToolResult,
|
|
3809
|
-
receiptKey:
|
|
4732
|
+
receiptKey: cacheableToolResult ? durableCacheKey : null,
|
|
4733
|
+
executionAuthScopeDigest,
|
|
3810
4734
|
force: toolCachePolicy.force,
|
|
4735
|
+
forceFailedRefresh: toolCachePolicy.forceFailedRefresh,
|
|
3811
4736
|
rowId,
|
|
3812
4737
|
fieldName,
|
|
3813
4738
|
toolId,
|
|
@@ -3827,41 +4752,81 @@ export class PlayContextImpl {
|
|
|
3827
4752
|
return await executeTool();
|
|
3828
4753
|
}
|
|
3829
4754
|
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
4755
|
+
for (
|
|
4756
|
+
let authScopeAttempt = 0;
|
|
4757
|
+
authScopeAttempt < 2;
|
|
4758
|
+
authScopeAttempt += 1
|
|
4759
|
+
) {
|
|
4760
|
+
try {
|
|
4761
|
+
return await this.executeWithRuntimeReceipt(
|
|
4762
|
+
'tool',
|
|
4763
|
+
normalizedKey,
|
|
4764
|
+
this.currentRunId,
|
|
4765
|
+
{
|
|
4766
|
+
receiptKey: durableCacheKey,
|
|
4767
|
+
semanticKey: toolRequestIdentity,
|
|
4768
|
+
force: toolCachePolicy.force,
|
|
4769
|
+
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
4770
|
+
markSkipped: () => {
|
|
4771
|
+
this.log(
|
|
4772
|
+
`ctx.tools.execute(${toolId}): no-op due completed receipt ${toolRequestIdentity} (label: ${normalizedKey})`,
|
|
4773
|
+
);
|
|
4774
|
+
},
|
|
4775
|
+
onClaimedResult: (output, receiptKey) =>
|
|
4776
|
+
markToolExecuteResultExecutionOutcome(output, {
|
|
4777
|
+
kind: 'live',
|
|
4778
|
+
receiptKey,
|
|
4779
|
+
}),
|
|
4780
|
+
onRecovered: (output, _receipt, source) =>
|
|
4781
|
+
markToolExecuteResultExecutionOutcome(
|
|
4782
|
+
output,
|
|
4783
|
+
toolExecutionOutcomeForDurableReceipt({
|
|
4784
|
+
source,
|
|
4785
|
+
receiptKey: durableCacheKey,
|
|
4786
|
+
}),
|
|
4787
|
+
),
|
|
4788
|
+
shouldPersistFailure: (error) =>
|
|
4789
|
+
!(error instanceof ToolExecuteAuthScopeChangedError),
|
|
4790
|
+
execute: ({ leaseId }) => executeTool({ leaseId }),
|
|
4791
|
+
runningReceiptWaitMaxAttempts:
|
|
4792
|
+
resolveRuntimeToolReceiptWaitMaxAttempts(
|
|
4793
|
+
typeof options?.receiptWaitMs === 'number'
|
|
4794
|
+
? { max_wait_ms: options.receiptWaitMs }
|
|
4795
|
+
: input,
|
|
4796
|
+
),
|
|
4797
|
+
},
|
|
4798
|
+
);
|
|
4799
|
+
} catch (error) {
|
|
4800
|
+
if (
|
|
4801
|
+
!(error instanceof ToolExecuteAuthScopeChangedError) ||
|
|
4802
|
+
authScopeAttempt > 0
|
|
4803
|
+
) {
|
|
4804
|
+
throw error;
|
|
4805
|
+
}
|
|
4806
|
+
// Make the bounded auth-scope re-claim observable in run logs instead
|
|
4807
|
+
// of a silent continuation: the credential identity changed after the
|
|
4808
|
+
// receipt key was prepared, so we evict the cached digest, re-resolve,
|
|
4809
|
+
// and re-claim a fresh receipt under the new scope before retrying once.
|
|
4810
|
+
this.log(
|
|
4811
|
+
`ctx.tools.execute(${toolId}): auth_scope_changed_reclaim ` +
|
|
4812
|
+
`(label: ${normalizedKey}); credential identity changed mid-run, ` +
|
|
4813
|
+
`re-resolving auth scope and re-claiming a fresh receipt under the ` +
|
|
4814
|
+
`new scope (attempt ${authScopeAttempt + 1}/2).`,
|
|
4815
|
+
);
|
|
4816
|
+
this.invalidateToolAuthScopeDigest(toolId);
|
|
4817
|
+
executionAuthScopeDigest =
|
|
4818
|
+
(await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null;
|
|
4819
|
+
durableCacheKey = await this.durableToolCallCacheKey({
|
|
4820
|
+
toolId,
|
|
4821
|
+
requestInput: input,
|
|
4822
|
+
executionAuthScopeDigest,
|
|
4823
|
+
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
4824
|
+
});
|
|
4825
|
+
checkpointCacheKeys[0] = durableCacheKey;
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
throw new ToolExecuteAuthScopeChangedError();
|
|
3865
4830
|
}
|
|
3866
4831
|
|
|
3867
4832
|
private async executeIntegrationEventWaitTool(
|
|
@@ -3987,163 +4952,349 @@ export class PlayContextImpl {
|
|
|
3987
4952
|
if (!resolvedName.trim()) {
|
|
3988
4953
|
throw new Error('ctx.runPlay(...) requires a resolvable play name.');
|
|
3989
4954
|
}
|
|
4955
|
+
const scheduledChildPlayCall = this.shouldLaunchChildPlaysThroughScheduler()
|
|
4956
|
+
? this.beginScheduledChildPlayCall()
|
|
4957
|
+
: null;
|
|
3990
4958
|
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
// Reserve depth + per-parent + descendant/play-call budget on the parent
|
|
4009
|
-
// and fork the lineage-global snapshot for the child. forkChild owns the
|
|
4010
|
-
// cycle guard, depth cap, per-parent cap, and play/descendant charges, so
|
|
4011
|
-
// a recovered receipt (which skips this body) never consumes budget. The
|
|
4012
|
-
// child run id stays deterministic — it matches the prior post-increment
|
|
4013
|
-
// playCallCount — so receipt keys and replay are stable.
|
|
4014
|
-
const childRunId = `${this.governance.rootRunId}:${resolvedName}:${this.governor.snapshot().playCallCount + 1}`;
|
|
4015
|
-
const childGovernance = this.governor.forkChild({
|
|
4959
|
+
try {
|
|
4960
|
+
if (!this.#options.resolvePlay) {
|
|
4961
|
+
throw new Error(
|
|
4962
|
+
'ctx.runPlay(...) is unavailable because no play resolver was configured.',
|
|
4963
|
+
);
|
|
4964
|
+
}
|
|
4965
|
+
const resolvePlay = this.#options.resolvePlay;
|
|
4966
|
+
const resolvedPlay = await resolvePlay(resolvedName);
|
|
4967
|
+
if (!resolvedPlay) {
|
|
4968
|
+
throw new Error(
|
|
4969
|
+
`Unable to resolve play "${resolvedName}" for ctx.runPlay(...).`,
|
|
4970
|
+
);
|
|
4971
|
+
}
|
|
4972
|
+
const childRevisionFingerprint =
|
|
4973
|
+
resolvedPlayRevisionFingerprint(resolvedPlay);
|
|
4974
|
+
const runPlayRowScope = rowContext.getStore();
|
|
4975
|
+
const runPlaySemanticKey = buildDurableRunPlaySemanticKey({
|
|
4016
4976
|
childPlayName: resolvedName,
|
|
4017
|
-
|
|
4977
|
+
childRevisionFingerprint,
|
|
4978
|
+
input,
|
|
4979
|
+
rowScope: runPlayRowScope
|
|
4980
|
+
? {
|
|
4981
|
+
fieldName: runPlayRowScope.fieldName,
|
|
4982
|
+
rowId: runPlayRowScope.rowId,
|
|
4983
|
+
rowKey: runPlayRowScope.rowKey ?? null,
|
|
4984
|
+
tableNamespace: runPlayRowScope.tableNamespace ?? null,
|
|
4985
|
+
}
|
|
4986
|
+
: null,
|
|
4018
4987
|
});
|
|
4019
|
-
const childPlaySlot = await this.governor.acquireChildPlaySlot();
|
|
4020
|
-
const rowStore = rowContext.getStore();
|
|
4021
|
-
const producer = {
|
|
4022
|
-
kind: 'play' as const,
|
|
4023
|
-
id: normalizedKey,
|
|
4024
|
-
playId: resolvedName,
|
|
4025
|
-
displayName: displayNameFromProducerId(resolvedName),
|
|
4026
|
-
runId: childRunId,
|
|
4027
|
-
};
|
|
4028
4988
|
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
runId: childGovernance.currentRunId,
|
|
4050
|
-
staticPipeline: resolvedPlay.staticPipeline ?? null,
|
|
4051
|
-
checkpoint: this.checkpoint,
|
|
4052
|
-
governance: childGovernance,
|
|
4053
|
-
getRuntimeStepReceipt: undefined,
|
|
4054
|
-
getRuntimeStepReceipts: undefined,
|
|
4055
|
-
claimRuntimeStepReceipt: undefined,
|
|
4056
|
-
claimRuntimeStepReceipts: undefined,
|
|
4057
|
-
completeRuntimeStepReceipt: undefined,
|
|
4058
|
-
completeRuntimeStepReceipts: undefined,
|
|
4059
|
-
failRuntimeStepReceipt: undefined,
|
|
4060
|
-
failRuntimeStepReceipts: undefined,
|
|
4061
|
-
skipRuntimeStepReceipt: undefined,
|
|
4062
|
-
});
|
|
4063
|
-
const childExecution = this.executeResolvedPlay(
|
|
4064
|
-
resolvedPlay,
|
|
4065
|
-
childContext,
|
|
4989
|
+
const executePlayCall = async (): Promise<TOutput> => {
|
|
4990
|
+
// Reserve depth + per-parent + descendant/play-call budget on the parent
|
|
4991
|
+
// and fork the lineage-global snapshot for the child. forkChild owns the
|
|
4992
|
+
// cycle guard, depth cap, per-parent cap, and play/descendant charges, so
|
|
4993
|
+
// a recovered receipt (which skips this body) never consumes budget.
|
|
4994
|
+
//
|
|
4995
|
+
// The child run id is derived through the shared canonical builder so the
|
|
4996
|
+
// in-process runtime and the workers_edge coordinator produce the same
|
|
4997
|
+
// FORMAT (`play/<slug>/run/child-<digest>`) via the same code. The digest
|
|
4998
|
+
// is deterministic across replay because every input (parent lineage,
|
|
4999
|
+
// call key, input) is stable. Per-substrate difference: in-process has no
|
|
5000
|
+
// coordinator idempotency key and no compiled child graphHash, so it uses
|
|
5001
|
+
// the semantic fallback with graphHash omitted; workers_edge feeds the
|
|
5002
|
+
// durable child idempotency key. See child-run-id.ts.
|
|
5003
|
+
const childRunId = buildChildRunId({
|
|
5004
|
+
childPlayName: resolvedName,
|
|
5005
|
+
parentRunId: this.currentRunId,
|
|
5006
|
+
parentPlayName: this.#options.playName,
|
|
5007
|
+
key: normalizedKey,
|
|
5008
|
+
runPlaySemanticKey,
|
|
4066
5009
|
input,
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
const
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
stage: resolvedName,
|
|
4079
|
-
provider: 'deepline_native',
|
|
4080
|
-
error: null,
|
|
4081
|
-
producer,
|
|
4082
|
-
dataPatch: {},
|
|
4083
|
-
});
|
|
4084
|
-
}
|
|
4085
|
-
const step = {
|
|
4086
|
-
type: 'play_call' as const,
|
|
5010
|
+
graphHash: null,
|
|
5011
|
+
});
|
|
5012
|
+
const childGovernance = this.governor.forkChild({
|
|
5013
|
+
childPlayName: resolvedName,
|
|
5014
|
+
childRunId,
|
|
5015
|
+
});
|
|
5016
|
+
const childPlaySlot = await this.governor.acquireChildSubmitSlot();
|
|
5017
|
+
const rowStore = rowContext.getStore();
|
|
5018
|
+
const producer = {
|
|
5019
|
+
kind: 'play' as const,
|
|
5020
|
+
id: normalizedKey,
|
|
4087
5021
|
playId: resolvedName,
|
|
4088
|
-
|
|
4089
|
-
|
|
5022
|
+
displayName: displayNameFromProducerId(resolvedName),
|
|
5023
|
+
runId: childRunId,
|
|
4090
5024
|
};
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
5025
|
+
|
|
5026
|
+
try {
|
|
5027
|
+
if (rowStore) {
|
|
5028
|
+
this.emitScopedFieldMetaUpdate({
|
|
5029
|
+
rowId: rowStore.rowId,
|
|
5030
|
+
key: rowStore.rowKey ?? null,
|
|
5031
|
+
tableNamespace: rowStore.tableNamespace ?? null,
|
|
5032
|
+
fieldName: rowStore.fieldName,
|
|
5033
|
+
status: 'running',
|
|
5034
|
+
rowStatus: 'running',
|
|
5035
|
+
stage: resolvedName,
|
|
5036
|
+
provider: 'deepline_native',
|
|
5037
|
+
error: null,
|
|
5038
|
+
producer,
|
|
5039
|
+
dataPatch: {},
|
|
5040
|
+
});
|
|
5041
|
+
}
|
|
5042
|
+
if (this.shouldLaunchChildPlaysThroughScheduler()) {
|
|
5043
|
+
const boundaryId = this.childPlayBoundaryId(
|
|
5044
|
+
childGovernance.currentRunId,
|
|
5045
|
+
);
|
|
5046
|
+
const checkpointResult = this.childPlayBoundaryOutput<TOutput>({
|
|
5047
|
+
boundaryId,
|
|
5048
|
+
childRunId: childGovernance.currentRunId,
|
|
5049
|
+
childPlayName: resolvedName,
|
|
5050
|
+
});
|
|
5051
|
+
if (checkpointResult.found) {
|
|
5052
|
+
if (rowStore) {
|
|
5053
|
+
this.emitScopedFieldMetaUpdate({
|
|
5054
|
+
rowId: rowStore.rowId,
|
|
5055
|
+
key: rowStore.rowKey ?? null,
|
|
5056
|
+
tableNamespace: rowStore.tableNamespace ?? null,
|
|
5057
|
+
fieldName: rowStore.fieldName,
|
|
5058
|
+
status: 'completed',
|
|
5059
|
+
rowStatus: 'running',
|
|
5060
|
+
stage: resolvedName,
|
|
5061
|
+
provider: 'deepline_native',
|
|
5062
|
+
error: null,
|
|
5063
|
+
producer,
|
|
5064
|
+
dataPatch: {},
|
|
5065
|
+
});
|
|
5066
|
+
}
|
|
5067
|
+
this.recordPlayCallStep({
|
|
5068
|
+
playId: resolvedName,
|
|
5069
|
+
description: options?.description,
|
|
5070
|
+
});
|
|
5071
|
+
return checkpointResult.output;
|
|
5072
|
+
}
|
|
5073
|
+
|
|
5074
|
+
await this.launchScheduledChildPlay({
|
|
5075
|
+
key: normalizedKey,
|
|
5076
|
+
childRunId: childGovernance.currentRunId,
|
|
5077
|
+
childPlayName: resolvedName,
|
|
5078
|
+
childGovernance,
|
|
5079
|
+
childInput: input,
|
|
5080
|
+
description: options?.description ?? null,
|
|
5081
|
+
});
|
|
5082
|
+
childPlaySlot.release();
|
|
5083
|
+
const terminal = await this.readScheduledChildTerminal<TOutput>({
|
|
5084
|
+
childRunId: childGovernance.currentRunId,
|
|
5085
|
+
childPlayName: resolvedName,
|
|
5086
|
+
});
|
|
5087
|
+
if (terminal?.status === 'completed') {
|
|
5088
|
+
this.checkpoint.resolvedBoundaries = {
|
|
5089
|
+
...(this.checkpoint.resolvedBoundaries ?? {}),
|
|
5090
|
+
[boundaryId]: {
|
|
5091
|
+
kind: 'child_play',
|
|
5092
|
+
childRunId: childGovernance.currentRunId,
|
|
5093
|
+
childPlayName: resolvedName,
|
|
5094
|
+
status: 'completed',
|
|
5095
|
+
output: terminal.output,
|
|
5096
|
+
completedAt: Date.now(),
|
|
5097
|
+
},
|
|
5098
|
+
};
|
|
5099
|
+
if (rowStore) {
|
|
5100
|
+
this.emitScopedFieldMetaUpdate({
|
|
5101
|
+
rowId: rowStore.rowId,
|
|
5102
|
+
key: rowStore.rowKey ?? null,
|
|
5103
|
+
tableNamespace: rowStore.tableNamespace ?? null,
|
|
5104
|
+
fieldName: rowStore.fieldName,
|
|
5105
|
+
status: 'completed',
|
|
5106
|
+
rowStatus: 'running',
|
|
5107
|
+
stage: resolvedName,
|
|
5108
|
+
provider: 'deepline_native',
|
|
5109
|
+
error: null,
|
|
5110
|
+
producer,
|
|
5111
|
+
dataPatch: {},
|
|
5112
|
+
});
|
|
5113
|
+
}
|
|
5114
|
+
this.recordPlayCallStep({
|
|
5115
|
+
playId: resolvedName,
|
|
5116
|
+
description: options?.description,
|
|
5117
|
+
});
|
|
5118
|
+
return terminal.output;
|
|
5119
|
+
}
|
|
5120
|
+
if (terminal) {
|
|
5121
|
+
throw new Error(terminal.error);
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
const suspension = {
|
|
5125
|
+
kind: 'child_play',
|
|
5126
|
+
boundaryId,
|
|
5127
|
+
childRunId: childGovernance.currentRunId,
|
|
5128
|
+
childPlayName: resolvedName,
|
|
5129
|
+
} as const;
|
|
5130
|
+
this.recordPendingChildPlayBoundary(suspension);
|
|
5131
|
+
scheduledChildPlayCall?.release();
|
|
5132
|
+
await this.waitForScheduledChildPlayQuiescence();
|
|
5133
|
+
throw new PlayExecutionSuspendedError(
|
|
5134
|
+
this.consumePendingChildPlaySuspension() ?? suspension,
|
|
5135
|
+
);
|
|
5136
|
+
}
|
|
5137
|
+
const childContext = createPlayContext({
|
|
5138
|
+
...this.#options,
|
|
5139
|
+
playId: resolvedName,
|
|
5140
|
+
playName: resolvedName,
|
|
5141
|
+
runId: childGovernance.currentRunId,
|
|
5142
|
+
executorToken: await this.mintChildExecutorToken({
|
|
5143
|
+
parentRunId: this.currentRunId,
|
|
5144
|
+
parentPlayName: this.#options.playName,
|
|
5145
|
+
childRunId: childGovernance.currentRunId,
|
|
5146
|
+
childPlayName: resolvedName,
|
|
5147
|
+
}),
|
|
5148
|
+
staticPipeline: resolvedPlay.staticPipeline ?? null,
|
|
5149
|
+
checkpoint: this.checkpoint,
|
|
5150
|
+
governance: childGovernance,
|
|
5151
|
+
getRuntimeStepReceipt: undefined,
|
|
5152
|
+
getRuntimeStepReceipts: undefined,
|
|
5153
|
+
claimRuntimeStepReceipt: undefined,
|
|
5154
|
+
claimRuntimeStepReceipts: undefined,
|
|
5155
|
+
completeRuntimeStepReceipt: undefined,
|
|
5156
|
+
completeRuntimeStepReceipts: undefined,
|
|
5157
|
+
failRuntimeStepReceipt: undefined,
|
|
5158
|
+
failRuntimeStepReceipts: undefined,
|
|
5159
|
+
heartbeatRuntimeStepReceipts: undefined,
|
|
5160
|
+
skipRuntimeStepReceipt: undefined,
|
|
4111
5161
|
});
|
|
5162
|
+
const childExecution = this.executeResolvedPlay(
|
|
5163
|
+
resolvedPlay,
|
|
5164
|
+
childContext,
|
|
5165
|
+
input,
|
|
5166
|
+
);
|
|
5167
|
+
await childContext.drainQueuedWork([childExecution]);
|
|
5168
|
+
const result = await childExecution;
|
|
5169
|
+
if (rowStore) {
|
|
5170
|
+
this.emitScopedFieldMetaUpdate({
|
|
5171
|
+
rowId: rowStore.rowId,
|
|
5172
|
+
key: rowStore.rowKey ?? null,
|
|
5173
|
+
tableNamespace: rowStore.tableNamespace ?? null,
|
|
5174
|
+
fieldName: rowStore.fieldName,
|
|
5175
|
+
status: 'completed',
|
|
5176
|
+
rowStatus: 'running',
|
|
5177
|
+
stage: resolvedName,
|
|
5178
|
+
provider: 'deepline_native',
|
|
5179
|
+
error: null,
|
|
5180
|
+
producer,
|
|
5181
|
+
dataPatch: {},
|
|
5182
|
+
});
|
|
5183
|
+
}
|
|
5184
|
+
const step = {
|
|
5185
|
+
type: 'play_call' as const,
|
|
5186
|
+
playId: resolvedName,
|
|
5187
|
+
nestedSteps: childContext.getSteps(),
|
|
5188
|
+
description: normalizeStepDescription(options?.description),
|
|
5189
|
+
};
|
|
5190
|
+
if (this.activeDatasetStep) {
|
|
5191
|
+
this.activeDatasetStep.substeps.push(step);
|
|
5192
|
+
} else {
|
|
5193
|
+
this.steps.push(step);
|
|
5194
|
+
}
|
|
5195
|
+
return result as TOutput;
|
|
5196
|
+
} catch (error) {
|
|
5197
|
+
if (isPlayExecutionSuspendedError(error)) {
|
|
5198
|
+
throw error;
|
|
5199
|
+
}
|
|
5200
|
+
if (rowStore) {
|
|
5201
|
+
this.emitScopedFieldMetaUpdate({
|
|
5202
|
+
rowId: rowStore.rowId,
|
|
5203
|
+
key: rowStore.rowKey ?? null,
|
|
5204
|
+
tableNamespace: rowStore.tableNamespace ?? null,
|
|
5205
|
+
fieldName: rowStore.fieldName,
|
|
5206
|
+
status: 'failed',
|
|
5207
|
+
rowStatus: 'running',
|
|
5208
|
+
stage: resolvedName,
|
|
5209
|
+
provider: 'deepline_native',
|
|
5210
|
+
error: this.formatRuntimeError(error),
|
|
5211
|
+
producer,
|
|
5212
|
+
dataPatch: {},
|
|
5213
|
+
});
|
|
5214
|
+
}
|
|
5215
|
+
throw error;
|
|
5216
|
+
} finally {
|
|
5217
|
+
childPlaySlot.release();
|
|
4112
5218
|
}
|
|
4113
|
-
|
|
4114
|
-
} finally {
|
|
4115
|
-
childPlaySlot.release();
|
|
4116
|
-
}
|
|
4117
|
-
};
|
|
5219
|
+
};
|
|
4118
5220
|
|
|
4119
|
-
|
|
4120
|
-
|
|
5221
|
+
return await this.executeWithRuntimeReceipt<TOutput>(
|
|
5222
|
+
'runPlay',
|
|
5223
|
+
normalizedKey,
|
|
5224
|
+
this.currentRunId,
|
|
5225
|
+
{
|
|
5226
|
+
semanticKey: runPlaySemanticKey,
|
|
5227
|
+
staleAfterSeconds: options?.staleAfterSeconds,
|
|
5228
|
+
markSkipped: () => {
|
|
5229
|
+
this.log(
|
|
5230
|
+
`ctx.runPlay(${normalizedKey}): no-op due completed receipt`,
|
|
5231
|
+
);
|
|
5232
|
+
},
|
|
5233
|
+
repairRunningReceiptForSameRunAfterWaitTimeout: true,
|
|
5234
|
+
runningReceiptWaitMaxAttempts:
|
|
5235
|
+
resolveRuntimeToolReceiptWaitMaxAttempts(input),
|
|
5236
|
+
execute: executePlayCall,
|
|
5237
|
+
},
|
|
5238
|
+
);
|
|
5239
|
+
} finally {
|
|
5240
|
+
scheduledChildPlayCall?.release();
|
|
4121
5241
|
}
|
|
5242
|
+
}
|
|
4122
5243
|
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
5244
|
+
private async mintChildExecutorToken(input: {
|
|
5245
|
+
parentRunId: string;
|
|
5246
|
+
parentPlayName?: string | null;
|
|
5247
|
+
childRunId: string;
|
|
5248
|
+
childPlayName: string;
|
|
5249
|
+
}): Promise<string | undefined> {
|
|
5250
|
+
if (!this.#options.executorToken || !this.#options.baseUrl) {
|
|
5251
|
+
return this.#options.executorToken;
|
|
5252
|
+
}
|
|
5253
|
+
const parentPlayName = input.parentPlayName?.trim();
|
|
5254
|
+
if (!parentPlayName) {
|
|
5255
|
+
return this.#options.executorToken;
|
|
5256
|
+
}
|
|
5257
|
+
const response = await fetch(
|
|
5258
|
+
`${this.#options.baseUrl.replace(/\/$/, '')}/api/v2/internal/play-runtime/child-executor-token`,
|
|
4127
5259
|
{
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
`ctx.runPlay(${normalizedKey}): no-op due completed receipt`,
|
|
4139
|
-
);
|
|
5260
|
+
method: 'POST',
|
|
5261
|
+
headers: {
|
|
5262
|
+
Authorization: `Bearer ${this.#options.executorToken}`,
|
|
5263
|
+
'Content-Type': 'application/json',
|
|
5264
|
+
...(this.#options.vercelProtectionBypassToken?.trim()
|
|
5265
|
+
? {
|
|
5266
|
+
'x-vercel-protection-bypass':
|
|
5267
|
+
this.#options.vercelProtectionBypassToken.trim(),
|
|
5268
|
+
}
|
|
5269
|
+
: {}),
|
|
4140
5270
|
},
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
5271
|
+
body: JSON.stringify({
|
|
5272
|
+
parentRunId: input.parentRunId,
|
|
5273
|
+
parentPlayName,
|
|
5274
|
+
childRunId: input.childRunId,
|
|
5275
|
+
childPlayName: input.childPlayName,
|
|
5276
|
+
}),
|
|
4145
5277
|
},
|
|
4146
5278
|
);
|
|
5279
|
+
const body = (await response.json().catch(() => null)) as {
|
|
5280
|
+
executorToken?: unknown;
|
|
5281
|
+
error?: unknown;
|
|
5282
|
+
} | null;
|
|
5283
|
+
if (!response.ok) {
|
|
5284
|
+
const message =
|
|
5285
|
+
typeof body?.error === 'string' && body.error.trim()
|
|
5286
|
+
? body.error.trim()
|
|
5287
|
+
: response.statusText;
|
|
5288
|
+
throw new Error(
|
|
5289
|
+
`ctx.runPlay failed to mint child executor token for "${input.childPlayName}": ${message} (status ${response.status}).`,
|
|
5290
|
+
);
|
|
5291
|
+
}
|
|
5292
|
+
if (typeof body?.executorToken !== 'string' || !body.executorToken.trim()) {
|
|
5293
|
+
throw new Error(
|
|
5294
|
+
`ctx.runPlay failed to mint child executor token for "${input.childPlayName}": response did not include executorToken.`,
|
|
5295
|
+
);
|
|
5296
|
+
}
|
|
5297
|
+
return body.executorToken.trim();
|
|
4147
5298
|
}
|
|
4148
5299
|
|
|
4149
5300
|
/**
|
|
@@ -4630,7 +5781,11 @@ export class PlayContextImpl {
|
|
|
4630
5781
|
const followers = liveFollowersByOwnerCallId.get(owner.callId) ?? [];
|
|
4631
5782
|
liveFollowersByOwnerCallId.delete(owner.callId);
|
|
4632
5783
|
for (const request of [owner, ...followers]) {
|
|
4633
|
-
await this.rejectToolCall(toolId, request, error
|
|
5784
|
+
await this.rejectToolCall(toolId, request, error, {
|
|
5785
|
+
persistReceiptFailure: !(
|
|
5786
|
+
error instanceof ToolExecuteAuthScopeChangedError
|
|
5787
|
+
),
|
|
5788
|
+
});
|
|
4634
5789
|
}
|
|
4635
5790
|
};
|
|
4636
5791
|
const requestsWithLiveFollowers = (
|
|
@@ -4685,7 +5840,11 @@ export class PlayContextImpl {
|
|
|
4685
5840
|
) {
|
|
4686
5841
|
const requestsByReceiptKey = new Map<
|
|
4687
5842
|
string,
|
|
4688
|
-
{
|
|
5843
|
+
{
|
|
5844
|
+
requests: ToolCallRequest[];
|
|
5845
|
+
forceRefresh: boolean;
|
|
5846
|
+
forceFailedRefresh: boolean;
|
|
5847
|
+
}
|
|
4689
5848
|
>();
|
|
4690
5849
|
const localOnlyRequests: ToolCallRequest[] = [];
|
|
4691
5850
|
for (const request of pendingRequests) {
|
|
@@ -4697,17 +5856,37 @@ export class PlayContextImpl {
|
|
|
4697
5856
|
const group = requestsByReceiptKey.get(receiptKey) ?? {
|
|
4698
5857
|
requests: [],
|
|
4699
5858
|
forceRefresh: false,
|
|
5859
|
+
forceFailedRefresh: false,
|
|
4700
5860
|
};
|
|
4701
5861
|
group.requests.push(request);
|
|
4702
5862
|
group.forceRefresh ||= request.force === true;
|
|
5863
|
+
group.forceFailedRefresh ||= request.forceFailedRefresh === true;
|
|
4703
5864
|
requestsByReceiptKey.set(receiptKey, group);
|
|
4704
5865
|
}
|
|
4705
5866
|
const normalReceiptKeys = [...requestsByReceiptKey]
|
|
4706
|
-
.filter(
|
|
5867
|
+
.filter(
|
|
5868
|
+
([, group]) => !group.forceRefresh && !group.forceFailedRefresh,
|
|
5869
|
+
)
|
|
4707
5870
|
.map(([receiptKey]) => receiptKey);
|
|
4708
5871
|
const forcedReceiptKeys = [...requestsByReceiptKey]
|
|
4709
5872
|
.filter(([, group]) => group.forceRefresh)
|
|
4710
5873
|
.map(([receiptKey]) => receiptKey);
|
|
5874
|
+
const failedRefreshReceiptKeys = [...requestsByReceiptKey]
|
|
5875
|
+
.filter(
|
|
5876
|
+
([, group]) => !group.forceRefresh && group.forceFailedRefresh,
|
|
5877
|
+
)
|
|
5878
|
+
.map(([receiptKey]) => receiptKey);
|
|
5879
|
+
const forcedExisting = forcedReceiptKeys.length
|
|
5880
|
+
? await this.getRuntimeStepReceipts(forcedReceiptKeys)
|
|
5881
|
+
: new Map<string, RuntimeStepReceipt>();
|
|
5882
|
+
const forcedRunningReceiptKeys = new Set(
|
|
5883
|
+
[...forcedExisting]
|
|
5884
|
+
.filter(([, receipt]) => receipt.status === 'running')
|
|
5885
|
+
.map(([receiptKey]) => receiptKey),
|
|
5886
|
+
);
|
|
5887
|
+
const claimableForcedReceiptKeys = forcedReceiptKeys.filter(
|
|
5888
|
+
(receiptKey) => !forcedRunningReceiptKeys.has(receiptKey),
|
|
5889
|
+
);
|
|
4711
5890
|
const claims =
|
|
4712
5891
|
normalReceiptKeys.length > 0
|
|
4713
5892
|
? await this.claimRuntimeStepReceipts(
|
|
@@ -4716,17 +5895,30 @@ export class PlayContextImpl {
|
|
|
4716
5895
|
)
|
|
4717
5896
|
: new Map<string, RuntimeStepReceipt>();
|
|
4718
5897
|
const forcedClaims =
|
|
4719
|
-
|
|
5898
|
+
claimableForcedReceiptKeys.length > 0
|
|
4720
5899
|
? await this.claimRuntimeStepReceipts(
|
|
4721
|
-
|
|
5900
|
+
claimableForcedReceiptKeys,
|
|
4722
5901
|
this.currentRunId,
|
|
5902
|
+
false,
|
|
4723
5903
|
true,
|
|
5904
|
+
)
|
|
5905
|
+
: new Map<string, RuntimeStepReceipt>();
|
|
5906
|
+
const failedRefreshClaims =
|
|
5907
|
+
failedRefreshReceiptKeys.length > 0
|
|
5908
|
+
? await this.claimRuntimeStepReceipts(
|
|
5909
|
+
failedRefreshReceiptKeys,
|
|
5910
|
+
this.currentRunId,
|
|
5911
|
+
false,
|
|
5912
|
+
false,
|
|
4724
5913
|
true,
|
|
4725
5914
|
)
|
|
4726
5915
|
: new Map<string, RuntimeStepReceipt>();
|
|
4727
5916
|
for (const [receiptKey, claim] of forcedClaims) {
|
|
4728
5917
|
claims.set(receiptKey, claim);
|
|
4729
5918
|
}
|
|
5919
|
+
for (const [receiptKey, claim] of failedRefreshClaims) {
|
|
5920
|
+
claims.set(receiptKey, claim);
|
|
5921
|
+
}
|
|
4730
5922
|
const claimedRequests: ToolCallRequest[] = [...localOnlyRequests];
|
|
4731
5923
|
const durableRecoveredRequests: ToolCallRequest[] = [];
|
|
4732
5924
|
const resolveRequestsFromReceipt = async (
|
|
@@ -4738,9 +5930,7 @@ export class PlayContextImpl {
|
|
|
4738
5930
|
if (!request) return;
|
|
4739
5931
|
const receiptKey = request.receiptKey?.trim() || null;
|
|
4740
5932
|
if (!receiptKey) {
|
|
4741
|
-
throw new Error(
|
|
4742
|
-
`Durable tool receipt ${source} recovery requires a receipt key.`,
|
|
4743
|
-
);
|
|
5933
|
+
throw new Error(`${source} tool recovery needs receipt key.`);
|
|
4744
5934
|
}
|
|
4745
5935
|
const wrapped = await this.wrapToolExecutionResult({
|
|
4746
5936
|
toolId,
|
|
@@ -4794,6 +5984,7 @@ export class PlayContextImpl {
|
|
|
4794
5984
|
const reclaimExistingRunningReceiptAfterWait = async (
|
|
4795
5985
|
receiptKey: string,
|
|
4796
5986
|
requestsForKey: ToolCallRequest[],
|
|
5987
|
+
forceAfterWait = false,
|
|
4797
5988
|
): Promise<void> => {
|
|
4798
5989
|
const waitMaxAttempts =
|
|
4799
5990
|
requestsForKey.length > 0
|
|
@@ -4808,15 +5999,18 @@ export class PlayContextImpl {
|
|
|
4808
5999
|
)
|
|
4809
6000
|
: undefined;
|
|
4810
6001
|
try {
|
|
4811
|
-
await
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
receiptKey,
|
|
4815
|
-
waitMaxAttempts,
|
|
4816
|
-
),
|
|
4817
|
-
'in_flight',
|
|
6002
|
+
const completed = await this.waitForCompletedRuntimeToolReceipt(
|
|
6003
|
+
receiptKey,
|
|
6004
|
+
waitMaxAttempts,
|
|
4818
6005
|
);
|
|
4819
|
-
|
|
6006
|
+
if (!forceAfterWait) {
|
|
6007
|
+
await resolveRequestsFromReceipt(
|
|
6008
|
+
requestsForKey,
|
|
6009
|
+
completed,
|
|
6010
|
+
'in_flight',
|
|
6011
|
+
);
|
|
6012
|
+
return;
|
|
6013
|
+
}
|
|
4820
6014
|
} catch (error) {
|
|
4821
6015
|
if (!(error instanceof RuntimeReceiptWaitTimeoutError)) {
|
|
4822
6016
|
for (const request of requestsForKey) {
|
|
@@ -4832,6 +6026,7 @@ export class PlayContextImpl {
|
|
|
4832
6026
|
[receiptKey],
|
|
4833
6027
|
this.currentRunId,
|
|
4834
6028
|
true,
|
|
6029
|
+
forceAfterWait,
|
|
4835
6030
|
)
|
|
4836
6031
|
).get(receiptKey);
|
|
4837
6032
|
if (
|
|
@@ -4862,14 +6057,31 @@ export class PlayContextImpl {
|
|
|
4862
6057
|
) {
|
|
4863
6058
|
const [owner, ...waiters] = requestsForKey;
|
|
4864
6059
|
if (!owner) return;
|
|
6060
|
+
if (!reclaimed.leaseId) {
|
|
6061
|
+
throw new RuntimeReceiptLeaseLostError({
|
|
6062
|
+
receiptKey,
|
|
6063
|
+
runId: this.currentRunId,
|
|
6064
|
+
leaseId: 'missing',
|
|
6065
|
+
});
|
|
6066
|
+
}
|
|
6067
|
+
owner.receiptLeaseId = reclaimed.leaseId ?? null;
|
|
4865
6068
|
if (waiters.length > 0) {
|
|
4866
6069
|
liveFollowersByOwnerCallId.set(owner.callId, waiters);
|
|
4867
6070
|
}
|
|
4868
6071
|
try {
|
|
6072
|
+
await this.assertRuntimeToolReceiptOwnership([owner]);
|
|
4869
6073
|
const execution = await this.callToolExecutionAPI(
|
|
4870
6074
|
toolId,
|
|
4871
6075
|
owner.input,
|
|
4872
6076
|
{
|
|
6077
|
+
durableCallReceiptKey: receiptKey,
|
|
6078
|
+
executionAuthScopeDigest: owner.executionAuthScopeDigest,
|
|
6079
|
+
providerIdempotencyKey:
|
|
6080
|
+
this.providerIdempotencyKeyForToolCall({
|
|
6081
|
+
cacheKey: owner.cacheKey,
|
|
6082
|
+
force: owner.force === true,
|
|
6083
|
+
leaseId: owner.receiptLeaseId,
|
|
6084
|
+
}),
|
|
4873
6085
|
timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([owner]),
|
|
4874
6086
|
},
|
|
4875
6087
|
);
|
|
@@ -4904,8 +6116,13 @@ export class PlayContextImpl {
|
|
|
4904
6116
|
const requestsForKey = group.requests;
|
|
4905
6117
|
const claim = claims.get(receiptKey);
|
|
4906
6118
|
if (!claim) {
|
|
4907
|
-
|
|
4908
|
-
|
|
6119
|
+
durableExistingRunningHandlers.push(
|
|
6120
|
+
reclaimExistingRunningReceiptAfterWait(
|
|
6121
|
+
receiptKey,
|
|
6122
|
+
requestsForKey,
|
|
6123
|
+
group.forceRefresh,
|
|
6124
|
+
),
|
|
6125
|
+
);
|
|
4909
6126
|
continue;
|
|
4910
6127
|
}
|
|
4911
6128
|
if (claim?.status === 'completed' || claim?.status === 'skipped') {
|
|
@@ -4928,6 +6145,16 @@ export class PlayContextImpl {
|
|
|
4928
6145
|
claim.claimState !== 'existing'
|
|
4929
6146
|
) {
|
|
4930
6147
|
const [owner, ...waiters] = requestsForKey;
|
|
6148
|
+
if (!claim.leaseId) {
|
|
6149
|
+
throw new RuntimeReceiptLeaseLostError({
|
|
6150
|
+
receiptKey,
|
|
6151
|
+
runId: this.currentRunId,
|
|
6152
|
+
leaseId: 'missing',
|
|
6153
|
+
});
|
|
6154
|
+
}
|
|
6155
|
+
if (owner) {
|
|
6156
|
+
owner.receiptLeaseId = claim.leaseId ?? null;
|
|
6157
|
+
}
|
|
4931
6158
|
claimOwnerWithLiveFollowers(owner, waiters);
|
|
4932
6159
|
continue;
|
|
4933
6160
|
}
|
|
@@ -4939,6 +6166,7 @@ export class PlayContextImpl {
|
|
|
4939
6166
|
reclaimExistingRunningReceiptAfterWait(
|
|
4940
6167
|
receiptKey,
|
|
4941
6168
|
requestsForKey,
|
|
6169
|
+
group.forceRefresh,
|
|
4942
6170
|
),
|
|
4943
6171
|
);
|
|
4944
6172
|
continue;
|
|
@@ -4947,6 +6175,7 @@ export class PlayContextImpl {
|
|
|
4947
6175
|
reclaimExistingRunningReceiptAfterWait(
|
|
4948
6176
|
receiptKey,
|
|
4949
6177
|
requestsForKey,
|
|
6178
|
+
group.forceRefresh,
|
|
4950
6179
|
),
|
|
4951
6180
|
);
|
|
4952
6181
|
}
|
|
@@ -4971,18 +6200,13 @@ export class PlayContextImpl {
|
|
|
4971
6200
|
compiledBatches.length > 0
|
|
4972
6201
|
? await this.governor.suggestedParallelism(
|
|
4973
6202
|
compiledBatches[0]!.batchOperation,
|
|
4974
|
-
|
|
6203
|
+
this.governor.policy.pacing
|
|
6204
|
+
.workerToolBatchDefaultParallelism,
|
|
4975
6205
|
)
|
|
4976
|
-
:
|
|
6206
|
+
: this.governor.policy.pacing.workerToolBatchDefaultParallelism;
|
|
4977
6207
|
await executeChunkedRequests({
|
|
4978
6208
|
requests: compiledBatches,
|
|
4979
6209
|
batchSize,
|
|
4980
|
-
// Bounded dispatch wave (postgres_fast parity with workers_edge):
|
|
4981
|
-
// cap the provider calls (counted by batch member size) a single
|
|
4982
|
-
// chunk dispatches before the latch is read again, so a
|
|
4983
|
-
// persistence failure prevents the rest of a large batch group.
|
|
4984
|
-
maxChunkWeight: PROVIDER_DISPATCH_WAVE_SIZE,
|
|
4985
|
-
weightOf: (batch) => batch.memberRequests.length,
|
|
4986
6210
|
execute: async (batch) => {
|
|
4987
6211
|
// Circuit breaker: skip dispatching this batch's provider call
|
|
4988
6212
|
// once a persistence failure has occurred in this run.
|
|
@@ -4993,10 +6217,42 @@ export class PlayContextImpl {
|
|
|
4993
6217
|
this.persistenceLatch,
|
|
4994
6218
|
);
|
|
4995
6219
|
}
|
|
6220
|
+
await this.assertRuntimeToolReceiptOwnership(
|
|
6221
|
+
batch.memberRequests,
|
|
6222
|
+
);
|
|
6223
|
+
const receiptKeys = batch.memberRequests.map(
|
|
6224
|
+
(request) => request.cacheKey,
|
|
6225
|
+
);
|
|
6226
|
+
const aggregateReceiptKey = buildDurableToolAggregateReceiptKey(
|
|
6227
|
+
{
|
|
6228
|
+
receiptKeys,
|
|
6229
|
+
prefix: 'batch',
|
|
6230
|
+
aggregateReceiptPrefix: buildDurableToolReceiptPrefix({
|
|
6231
|
+
orgId: this.#options.orgId,
|
|
6232
|
+
toolId: batch.batchOperation,
|
|
6233
|
+
}),
|
|
6234
|
+
},
|
|
6235
|
+
);
|
|
4996
6236
|
return await this.callToolAPI(
|
|
4997
6237
|
batch.batchOperation,
|
|
4998
6238
|
batch.batchPayload,
|
|
4999
6239
|
{
|
|
6240
|
+
durableCallReceiptKey: aggregateReceiptKey,
|
|
6241
|
+
executionAuthScopeDigest:
|
|
6242
|
+
batch.memberRequests[0]?.executionAuthScopeDigest ?? null,
|
|
6243
|
+
providerIdempotencyKey:
|
|
6244
|
+
buildDurableToolAggregateProviderIdempotencyKey({
|
|
6245
|
+
aggregateReceiptKey,
|
|
6246
|
+
receiptKeys,
|
|
6247
|
+
providerIdempotencyKeys: batch.memberRequests.map(
|
|
6248
|
+
(request) =>
|
|
6249
|
+
this.providerIdempotencyKeyForToolCall({
|
|
6250
|
+
cacheKey: request.cacheKey,
|
|
6251
|
+
force: request.force === true,
|
|
6252
|
+
leaseId: request.receiptLeaseId,
|
|
6253
|
+
}),
|
|
6254
|
+
),
|
|
6255
|
+
}),
|
|
5000
6256
|
timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners(
|
|
5001
6257
|
batch.memberRequests,
|
|
5002
6258
|
),
|
|
@@ -5042,13 +6298,9 @@ export class PlayContextImpl {
|
|
|
5042
6298
|
},
|
|
5043
6299
|
});
|
|
5044
6300
|
} else {
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
// wave size. Respect a lower provider-pacing suggestion when present.
|
|
5049
|
-
const batchSize = Math.min(
|
|
5050
|
-
await this.governor.suggestedParallelism(toolId, 50),
|
|
5051
|
-
PROVIDER_DISPATCH_WAVE_SIZE,
|
|
6301
|
+
const batchSize = await this.governor.suggestedParallelism(
|
|
6302
|
+
toolId,
|
|
6303
|
+
this.governor.policy.pacing.suggestedMaxParallelism,
|
|
5052
6304
|
);
|
|
5053
6305
|
for (
|
|
5054
6306
|
let start = 0;
|
|
@@ -5071,10 +6323,24 @@ export class PlayContextImpl {
|
|
|
5071
6323
|
return;
|
|
5072
6324
|
}
|
|
5073
6325
|
try {
|
|
6326
|
+
await this.assertRuntimeToolReceiptOwnership([request]);
|
|
5074
6327
|
const execution = await this.callToolExecutionAPI(
|
|
5075
6328
|
toolId,
|
|
5076
6329
|
request.input,
|
|
5077
6330
|
{
|
|
6331
|
+
...(request.receiptKey
|
|
6332
|
+
? {
|
|
6333
|
+
durableCallReceiptKey: request.receiptKey,
|
|
6334
|
+
executionAuthScopeDigest:
|
|
6335
|
+
request.executionAuthScopeDigest,
|
|
6336
|
+
providerIdempotencyKey:
|
|
6337
|
+
this.providerIdempotencyKeyForToolCall({
|
|
6338
|
+
cacheKey: request.receiptKey,
|
|
6339
|
+
force: request.force === true,
|
|
6340
|
+
leaseId: request.receiptLeaseId,
|
|
6341
|
+
}),
|
|
6342
|
+
}
|
|
6343
|
+
: {}),
|
|
5078
6344
|
timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([
|
|
5079
6345
|
request,
|
|
5080
6346
|
]),
|
|
@@ -5198,6 +6464,23 @@ export class PlayContextImpl {
|
|
|
5198
6464
|
: execution.result;
|
|
5199
6465
|
}
|
|
5200
6466
|
|
|
6467
|
+
private providerIdempotencyKeyForToolCall(input: {
|
|
6468
|
+
cacheKey: string;
|
|
6469
|
+
force?: boolean;
|
|
6470
|
+
leaseId?: string | null;
|
|
6471
|
+
}): string {
|
|
6472
|
+
const fallbackAttemptId =
|
|
6473
|
+
input.force === true && !input.leaseId
|
|
6474
|
+
? `${this.currentRunId}:${crypto.randomUUID()}`
|
|
6475
|
+
: null;
|
|
6476
|
+
return buildDurableToolProviderIdempotencyKey({
|
|
6477
|
+
receiptKey: input.cacheKey,
|
|
6478
|
+
force: input.force,
|
|
6479
|
+
receiptLeaseId: input.leaseId,
|
|
6480
|
+
fallbackAttemptId,
|
|
6481
|
+
});
|
|
6482
|
+
}
|
|
6483
|
+
|
|
5201
6484
|
private async callToolExecutionAPI(
|
|
5202
6485
|
toolId: string,
|
|
5203
6486
|
input: Record<string, unknown>,
|
|
@@ -5240,6 +6523,20 @@ export class PlayContextImpl {
|
|
|
5240
6523
|
|
|
5241
6524
|
while (true) {
|
|
5242
6525
|
let response: Response;
|
|
6526
|
+
const durableCallReceiptKey =
|
|
6527
|
+
options?.durableCallReceiptKey?.trim() || null;
|
|
6528
|
+
const executionAuthScopeDigest =
|
|
6529
|
+
durableCallReceiptKey && options?.executionAuthScopeDigest
|
|
6530
|
+
? options.executionAuthScopeDigest.trim() || null
|
|
6531
|
+
: durableCallReceiptKey
|
|
6532
|
+
? ((await this.resolveToolAuthScopeDigest(toolId))?.trim() ??
|
|
6533
|
+
null)
|
|
6534
|
+
: null;
|
|
6535
|
+
const providerIdempotencyKey =
|
|
6536
|
+
options?.providerIdempotencyKey?.trim() || durableCallReceiptKey;
|
|
6537
|
+
const deeplineRequestId = providerIdempotencyKey
|
|
6538
|
+
? `ctx-tool-${stableDigest(providerIdempotencyKey).slice(0, 32)}`
|
|
6539
|
+
: null;
|
|
5243
6540
|
const abortController = timeoutMs ? new AbortController() : null;
|
|
5244
6541
|
const timeoutHandle = timeoutMs
|
|
5245
6542
|
? setTimeout(() => {
|
|
@@ -5261,6 +6558,12 @@ export class PlayContextImpl {
|
|
|
5261
6558
|
V2_EXECUTE_RESPONSE_CONTRACT,
|
|
5262
6559
|
[EXECUTE_RESPONSE_INTENT_HEADER]: 'dataset',
|
|
5263
6560
|
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
6561
|
+
...(deeplineRequestId
|
|
6562
|
+
? {
|
|
6563
|
+
'x-deepline-request-id': deeplineRequestId,
|
|
6564
|
+
'x-deepline-idempotency-key': providerIdempotencyKey!,
|
|
6565
|
+
}
|
|
6566
|
+
: {}),
|
|
5264
6567
|
...(this.#options.vercelProtectionBypassToken?.trim()
|
|
5265
6568
|
? {
|
|
5266
6569
|
'x-vercel-protection-bypass':
|
|
@@ -5272,6 +6575,17 @@ export class PlayContextImpl {
|
|
|
5272
6575
|
payload: input,
|
|
5273
6576
|
metadata: {
|
|
5274
6577
|
parent_run_id: this.#options.runId,
|
|
6578
|
+
...(durableCallReceiptKey
|
|
6579
|
+
? {
|
|
6580
|
+
durable_call_receipt_key: durableCallReceiptKey,
|
|
6581
|
+
...(executionAuthScopeDigest
|
|
6582
|
+
? {
|
|
6583
|
+
execution_auth_scope_digest:
|
|
6584
|
+
executionAuthScopeDigest,
|
|
6585
|
+
}
|
|
6586
|
+
: {}),
|
|
6587
|
+
}
|
|
6588
|
+
: {}),
|
|
5275
6589
|
...(options?.customerDbDataset
|
|
5276
6590
|
? {
|
|
5277
6591
|
query_result_dataset: {
|
|
@@ -5293,6 +6607,9 @@ export class PlayContextImpl {
|
|
|
5293
6607
|
: {}),
|
|
5294
6608
|
}
|
|
5295
6609
|
: {}),
|
|
6610
|
+
...(providerIdempotencyKey
|
|
6611
|
+
? { provider_idempotency_key: providerIdempotencyKey }
|
|
6612
|
+
: {}),
|
|
5296
6613
|
},
|
|
5297
6614
|
...(this.#options.integrationMode
|
|
5298
6615
|
? { integration_mode: this.#options.integrationMode }
|
|
@@ -5337,6 +6654,13 @@ export class PlayContextImpl {
|
|
|
5337
6654
|
|
|
5338
6655
|
if (!response.ok) {
|
|
5339
6656
|
const text = await response.text();
|
|
6657
|
+
const authScopeChanged = parseToolExecuteAuthScopeChangedError({
|
|
6658
|
+
status: response.status,
|
|
6659
|
+
bodyText: text,
|
|
6660
|
+
});
|
|
6661
|
+
if (authScopeChanged) {
|
|
6662
|
+
throw authScopeChanged;
|
|
6663
|
+
}
|
|
5340
6664
|
const httpFailureAttempt = httpFailureAttempts.next({
|
|
5341
6665
|
toolId,
|
|
5342
6666
|
status: response.status,
|