deepline 0.3.392 → 0.3.394
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/sdk/src/client.ts +156 -24
- package/dist/bundling-sources/sdk/src/compat.ts +9 -0
- package/dist/bundling-sources/sdk/src/http.ts +5 -1
- package/dist/bundling-sources/sdk/src/index.ts +5 -0
- package/dist/bundling-sources/sdk/src/plays/run-request.ts +182 -6
- package/dist/bundling-sources/sdk/src/release.ts +4 -1
- package/dist/bundling-sources/sdk/src/types.ts +13 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/request-warnings.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/runtime-sheet-storage-contract.ts +315 -0
- package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +41 -8
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +895 -54
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-policy.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/freshness-receipt-execution.ts +251 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +30 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +168 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +252 -10
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +32 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +103 -23
- package/dist/bundling-sources/shared_libs/plays/core.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +18 -0
- package/dist/cli/auth-IHKISLJQ.js +39 -0
- package/dist/cli/{auth-6JSA2ZCW.mjs → auth-OSXDAVCF.mjs} +4 -4
- package/dist/cli/{billing-Q5BLQHIU.js → billing-EKLYOXUY.js} +101 -74
- package/dist/cli/{billing-MRRO643H.mjs → billing-ZNNKX3UR.mjs} +32 -5
- package/dist/cli/{chunk-4SFPVW7A.js → chunk-2MDDA7A4.js} +26 -26
- package/dist/cli/{chunk-JOFPQGXW.js → chunk-354JCXA2.js} +50 -50
- package/dist/cli/{chunk-7E66A24V.mjs → chunk-3HU47EGZ.mjs} +71 -9
- package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-3HWALOL2.mjs} +1 -1
- package/dist/cli/{chunk-T7OKJ2T6.js → chunk-4P27AXEA.js} +73 -63
- package/dist/cli/{chunk-24VEN5PQ.mjs → chunk-5BB2RGAX.mjs} +190 -18
- package/dist/cli/{chunk-JSLPHFP7.js → chunk-6YAUMG27.js} +3 -2
- package/dist/cli/{chunk-MU6M3URX.mjs → chunk-BP33SIZF.mjs} +1 -1
- package/dist/cli/{chunk-WMS5U6YB.js → chunk-CS6RAIVY.js} +20 -19
- package/dist/cli/{chunk-CVLM3WMJ.js → chunk-E23BQ5SC.js} +242 -183
- package/dist/cli/{chunk-2GAHHSLB.mjs → chunk-FJGW5GXU.mjs} +3 -2
- package/dist/cli/{chunk-YN6YA7ZZ.mjs → chunk-JHIJIHFB.mjs} +79 -20
- package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
- package/dist/cli/{chunk-5O4B6AUN.js → chunk-KB4SDX5S.js} +71 -9
- package/dist/cli/{chunk-RV6ALZIX.js → chunk-KGNTKMRG.js} +32 -7
- package/dist/cli/{chunk-6DBOU7UH.js → chunk-KI635JN4.js} +176 -87
- package/dist/cli/{chunk-2FQG2HU4.js → chunk-KIUMEOM5.js} +8 -8
- package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
- package/dist/cli/{chunk-2Q3GEFL4.mjs → chunk-M57J2SK4.mjs} +2 -2
- package/dist/cli/{chunk-R3O2CZ4H.mjs → chunk-MFB2VKBO.mjs} +2 -2
- package/dist/cli/{chunk-QGKPTBRR.js → chunk-MH73HMPV.js} +3 -3
- package/dist/cli/{chunk-BB3FNYFY.js → chunk-MLPU5SMZ.js} +2 -2
- package/dist/cli/{chunk-2G7DXFHI.mjs → chunk-NR3DFKNF.mjs} +27 -2
- package/dist/cli/{chunk-EY7RFC6A.js → chunk-QIQBYKLB.js} +15 -15
- package/dist/cli/{chunk-RIV2GS5E.js → chunk-RBJYWRCF.js} +638 -466
- package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
- package/dist/cli/{chunk-YAZCDBXX.mjs → chunk-TJI2EN6K.mjs} +3 -2
- package/dist/cli/chunk-UBKA33KY.mjs +310 -0
- package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
- package/dist/cli/{chunk-4TCT77UF.js → chunk-URWF7NGQ.js} +6 -1
- package/dist/cli/{chunk-2QLML6G4.mjs → chunk-UXFSWG3Q.mjs} +4 -4
- package/dist/cli/{chunk-UCKJQG25.js → chunk-VK6FYZLP.js} +8 -8
- package/dist/cli/{chunk-ZTACINO5.mjs → chunk-X63FDZJZ.mjs} +13 -3
- package/dist/cli/chunk-XDVT3GOM.js +310 -0
- package/dist/cli/{chunk-A3APGZFP.mjs → chunk-XRQ2AQL7.mjs} +2 -2
- package/dist/cli/{chunk-3FV4GBRK.mjs → chunk-XXUDVEXK.mjs} +97 -8
- package/dist/cli/{chunk-PQSA443V.mjs → chunk-ZI2ZNDJK.mjs} +6 -1
- package/dist/cli/{chunk-HGYBEAQP.mjs → chunk-ZYBGA4AR.mjs} +3 -3
- package/dist/cli/command-docs.js +2 -2
- package/dist/cli/command-docs.mjs +1 -1
- package/dist/cli/{csv-E4QGUIGI.mjs → csv-5AXRB35H.mjs} +3 -3
- package/dist/cli/{csv-26U5T24Z.js → csv-LU7O75WE.js} +12 -12
- package/dist/cli/{db-JTRJIAQW.mjs → db-7XP5UWEW.mjs} +6 -5
- package/dist/cli/{db-QHBFLMVL.js → db-F7YTFKVM.js} +31 -30
- package/dist/cli/{describe-FDV2V74F.js → describe-FRH32RDV.js} +24 -23
- package/dist/cli/{describe-E3DE3ZK3.mjs → describe-K4ES5LIL.mjs} +17 -16
- package/dist/cli/{executions-B3GTNPRE.mjs → executions-IEHEHJUO.mjs} +6 -5
- package/dist/cli/{executions-VZYOGVQY.js → executions-RZV5XPT2.js} +8 -7
- package/dist/cli/{feedback-CR55FJA2.js → feedback-3P65QTX3.js} +7 -7
- package/dist/cli/{feedback-4CEBR3OV.mjs → feedback-FPUVZBAN.mjs} +3 -3
- package/dist/cli/{git-NM2PHHZ4.mjs → git-GLVVP7TY.mjs} +4 -4
- package/dist/cli/git-MFFAWVVX.js +23 -0
- package/dist/cli/index.js +100 -96
- package/dist/cli/index.mjs +47 -43
- package/dist/cli/{legacy-noops-A7MSW5ND.mjs → legacy-noops-3MOI6NBM.mjs} +4 -4
- package/dist/cli/{legacy-noops-ZUA4YFIH.js → legacy-noops-XKSGZR7Q.js} +7 -7
- package/dist/cli/{monitors-NVPRQYP3.mjs → monitors-FTBGNEFG.mjs} +6 -5
- package/dist/cli/{monitors-ZE3TLZ4H.js → monitors-NZ2DYNST.js} +87 -86
- package/dist/cli/{org-U7A6DV5D.js → org-MCKDQXIV.js} +66 -65
- package/dist/cli/{org-KUBILNQV.mjs → org-V2J5RBCN.mjs} +6 -5
- package/dist/cli/{play-HRDXQ4QE.mjs → play-AJFGME3B.mjs} +11 -10
- package/dist/cli/play-WLSN2EO2.js +158 -0
- package/dist/cli/{providers-VD6YTVQZ.js → providers-2JEZ2264.js} +9 -8
- package/dist/cli/{providers-OHIWI453.mjs → providers-I6RQJZFZ.mjs} +6 -5
- package/dist/cli/{quickstart-UBV2FONF.mjs → quickstart-BIDQDMO4.mjs} +3 -3
- package/dist/cli/{quickstart-ETTCWPA4.js → quickstart-GJ2RPDU3.js} +5 -5
- package/dist/cli/run-identity-G257GQED.js +25 -0
- package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
- package/dist/cli/search-5VN4YD4I.js +27 -0
- package/dist/cli/{search-F44IZRT7.mjs → search-ELRXFWG5.mjs} +7 -6
- package/dist/cli/{secrets-CAEMRORF.js → secrets-53IT6VXD.js} +15 -14
- package/dist/cli/{secrets-R6XOJZKU.mjs → secrets-SQXGZXIC.mjs} +6 -5
- package/dist/cli/{sessions-RLN6UQS3.mjs → sessions-2X5QF7SL.mjs} +4 -4
- package/dist/cli/{sessions-6GC7OFTT.js → sessions-CAV5M77Y.js} +5 -5
- package/dist/cli/{settings-DPNVUAV2.js → settings-532WNAZO.js} +76 -75
- package/dist/cli/{settings-KIPAMRPP.mjs → settings-7P67VGCJ.mjs} +9 -8
- package/dist/cli/{setup-DTL3L5SG.js → setup-6L2VSUWJ.js} +41 -41
- package/dist/cli/{setup-TU2F7PU5.mjs → setup-QGGBV2LE.mjs} +6 -6
- package/dist/cli/{skills-UV5MYJ46.mjs → skills-3FE62WBG.mjs} +4 -4
- package/dist/cli/skills-S6NPZAN7.js +28 -0
- package/dist/cli/tools-647RLXQS.js +41 -0
- package/dist/cli/{tools-QRRNDCQ7.mjs → tools-DMR25DEF.mjs} +10 -9
- package/dist/cli/{update-4426TF35.mjs → update-6RZMHJZP.mjs} +6 -6
- package/dist/cli/update-WVTSC3EM.js +28 -0
- package/dist/cli/{workflow-N4U5VLZ3.mjs → workflow-L2DQRE6R.mjs} +7 -6
- package/dist/cli/{workflow-HNLOVISB.js → workflow-TX4F2PIX.js} +17 -16
- package/dist/{client-e6Qh5ZIg.d.mts → client-BktIPdIj.d.mts} +153 -4
- package/dist/{client-Uxuy-xQ2.d.ts → client-C3O1Ipf3.d.ts} +153 -4
- package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DLxUY5kI.d.mts} +124 -10
- package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DLxUY5kI.d.ts} +124 -10
- package/dist/{index-BZ6uzZJl.d.ts → index-B_GM9E3q.d.ts} +1 -1
- package/dist/{index-B9fBZH1g.d.mts → index-SCb0NvDb.d.mts} +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +120 -29
- package/dist/index.mjs +111 -20
- package/dist/install-integrity.json +94 -88
- package/dist/plays/bundle-play-file.d.mts +3 -3
- package/dist/plays/bundle-play-file.d.ts +3 -3
- package/dist/plays/bundle-play-file.mjs +2 -2
- package/dist/plays/run-request.d.mts +2 -2
- package/dist/plays/run-request.d.ts +2 -2
- package/dist/plays/run-request.js +5 -2
- package/dist/plays/run-request.mjs +4 -1
- package/dist/release.d.mts +4 -1
- package/dist/release.d.ts +4 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-67TAELQL.js +0 -39
- package/dist/cli/git-I5JTK6YU.js +0 -23
- package/dist/cli/play-A2V7YDU7.js +0 -157
- package/dist/cli/search-WWXMBVPH.js +0 -26
- package/dist/cli/skills-SWOSEZLB.js +0 -28
- package/dist/cli/tools-IL2YCDQ6.js +0 -40
- package/dist/cli/update-BERDPEYR.js +0 -28
|
@@ -25,6 +25,10 @@ import { AsyncLocalStorage } from 'async_hooks';
|
|
|
25
25
|
import { isDatasetPayloadField } from '../play-data-plane/sheet-contract';
|
|
26
26
|
import {
|
|
27
27
|
createDeferredPlayDataset,
|
|
28
|
+
copyPlayDatasetSourceRowIdentity,
|
|
29
|
+
getPlayDatasetSourceRowIdentity,
|
|
30
|
+
getPlayDatasetSourceRowIdentityAt,
|
|
31
|
+
setPlayDatasetSourceRowIdentity,
|
|
28
32
|
deserializeLegacyPlayDataset,
|
|
29
33
|
deserializePlayDatasetCell,
|
|
30
34
|
isPlayDataset,
|
|
@@ -32,6 +36,7 @@ import {
|
|
|
32
36
|
isSerializedPlayDatasetCell,
|
|
33
37
|
iteratePlayDatasetInputPages,
|
|
34
38
|
materializePlayDatasetInput,
|
|
39
|
+
residentPlayDatasetRows,
|
|
35
40
|
resolveMaterializeLimitCap,
|
|
36
41
|
serializePlayDatasetCell,
|
|
37
42
|
} from '../plays/dataset';
|
|
@@ -69,7 +74,10 @@ import type {
|
|
|
69
74
|
ProviderPacingGrant,
|
|
70
75
|
} from './governor/rate-state-backend';
|
|
71
76
|
import type { MapRowOutcome } from './durability-store';
|
|
72
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
sanitizePostgresJsonValue,
|
|
79
|
+
stringifyPostgresJson,
|
|
80
|
+
} from './postgres-json';
|
|
73
81
|
import { providerCapacityLabel } from './activity-observation';
|
|
74
82
|
import { RUNTIME_ROW_OUTPUT_NOT_JSON_SERIALIZABLE } from './runtime-sheet-row-transition';
|
|
75
83
|
import {
|
|
@@ -91,7 +99,10 @@ import {
|
|
|
91
99
|
import {
|
|
92
100
|
DURABLE_INVOCATION_FENCE_VERSION,
|
|
93
101
|
durableInvocationFenceExecutePath,
|
|
102
|
+
buildRuntimeExecutionRecoveryKey,
|
|
103
|
+
RUNTIME_EXECUTION_RECOVERY_SUPPORTED_HEADER,
|
|
94
104
|
} from './durable-invocation-fence';
|
|
105
|
+
import { isImmutableRuntimeExecutionRecoveryOrigin } from './scheduler-backends/runtime-api-origin';
|
|
95
106
|
import {
|
|
96
107
|
isRetryableSecretResolutionStatus,
|
|
97
108
|
secretResolutionRetryDecision,
|
|
@@ -184,6 +195,7 @@ import {
|
|
|
184
195
|
type ToolExecuteResult,
|
|
185
196
|
type ToolResultExecutionMetadata,
|
|
186
197
|
type ToolResultMetadataInput,
|
|
198
|
+
type CallFreshnessMetadata,
|
|
187
199
|
} from './tool-result';
|
|
188
200
|
import {
|
|
189
201
|
EXECUTION_GATEWAY_RECEIPT_OUTPUT_REFERENCE_HEADER,
|
|
@@ -258,9 +270,12 @@ import {
|
|
|
258
270
|
scopeToolReceiptToInvocation,
|
|
259
271
|
resolveDurableCallCachePolicy,
|
|
260
272
|
} from './durable-call-cache';
|
|
273
|
+
import { resolveExplicitDurableCallPolicy } from './durable-call-policy';
|
|
274
|
+
import { executeWithFreshnessReceipt } from './freshness-receipt-execution';
|
|
261
275
|
import {
|
|
262
276
|
CtxFetchHttpError,
|
|
263
277
|
PLAY_AUTHORING_CONTRACT_EDITION,
|
|
278
|
+
PlayAuthoringFieldValidationError,
|
|
264
279
|
normalizePlayAuthoringCustomerDbStatement,
|
|
265
280
|
validateOptionalPlayAuthoringField,
|
|
266
281
|
validatePlayAuthoringField,
|
|
@@ -304,9 +319,11 @@ import {
|
|
|
304
319
|
classifyAsyncOperationResult,
|
|
305
320
|
executeAsyncOperationLifecycle,
|
|
306
321
|
extractAsyncOperationJobId,
|
|
322
|
+
formatAsyncOperationTimeout,
|
|
307
323
|
readAsyncOperationPath,
|
|
308
324
|
asyncOperationShouldWait,
|
|
309
325
|
hasExplicitAsyncOperationTerminalMatch,
|
|
326
|
+
resolveAsyncOperationPolling,
|
|
310
327
|
type AsyncOperationContractWire,
|
|
311
328
|
} from './async-operation';
|
|
312
329
|
import {
|
|
@@ -626,6 +643,7 @@ const inlineCompositionContext =
|
|
|
626
643
|
const authoredStepContext = new AsyncLocalStorage<{
|
|
627
644
|
context: PlayContextImpl;
|
|
628
645
|
stepId: string;
|
|
646
|
+
invocationId?: string;
|
|
629
647
|
}>();
|
|
630
648
|
const toolExecutionOverrides = new AsyncLocalStorage<{ force: boolean }>();
|
|
631
649
|
const PROGRESS_HEARTBEAT_INTERVAL_MS = 1_000;
|
|
@@ -2918,6 +2936,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2918
2936
|
private mapInvocationIndex = 0;
|
|
2919
2937
|
private readonly stepCallIndexByKey = new Map<string, number>();
|
|
2920
2938
|
private readonly toolCallIndexByKey = new Map<string, number>();
|
|
2939
|
+
private readonly freshnessCallIndexByKey = new Map<string, number>();
|
|
2921
2940
|
/**
|
|
2922
2941
|
* Parent-level inline-child aggregates. Maintained only by the single-writer
|
|
2923
2942
|
* progress path (never per child) so concurrent fan-out cannot contend. See
|
|
@@ -3195,7 +3214,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
3195
3214
|
key: string,
|
|
3196
3215
|
toolId: string,
|
|
3197
3216
|
input: Record<string, unknown>,
|
|
3198
|
-
options?: {
|
|
3217
|
+
options?: {
|
|
3218
|
+
description?: string;
|
|
3219
|
+
staleAfterSeconds?: number | null;
|
|
3220
|
+
},
|
|
3199
3221
|
): Promise<ToolExecuteResult<TOutput>> {
|
|
3200
3222
|
validatePlayAuthoringField('ctx.tool.key', key);
|
|
3201
3223
|
validatePlayAuthoringField('ctx.tool.tool', toolId);
|
|
@@ -3211,6 +3233,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
3211
3233
|
tool: toolId,
|
|
3212
3234
|
input,
|
|
3213
3235
|
...(options?.description ? { description: options.description } : {}),
|
|
3236
|
+
...(options?.staleAfterSeconds !== undefined
|
|
3237
|
+
? { staleAfterSeconds: options.staleAfterSeconds }
|
|
3238
|
+
: {}),
|
|
3214
3239
|
})) as ToolExecuteResult<TOutput>;
|
|
3215
3240
|
}
|
|
3216
3241
|
|
|
@@ -3220,9 +3245,41 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
3220
3245
|
readonly customerDb = {
|
|
3221
3246
|
query: async <TRow extends object = Record<string, unknown>>(
|
|
3222
3247
|
statement: PlaySqlQuery | string,
|
|
3223
|
-
options?: {
|
|
3248
|
+
options?: {
|
|
3249
|
+
id?: string;
|
|
3250
|
+
staleAfterSeconds?: number | null;
|
|
3251
|
+
maxRows?: number;
|
|
3252
|
+
timeoutMs?: number;
|
|
3253
|
+
},
|
|
3224
3254
|
): Promise<TRow[]> => {
|
|
3225
3255
|
const sql = normalizePlayAuthoringCustomerDbStatement(statement);
|
|
3256
|
+
if (this.usesExplicitFreshness) {
|
|
3257
|
+
validatePlayAuthoringField(
|
|
3258
|
+
'ctx.customerDb.query.options.id',
|
|
3259
|
+
options?.id,
|
|
3260
|
+
);
|
|
3261
|
+
} else if (options?.id !== undefined) {
|
|
3262
|
+
validatePlayAuthoringField(
|
|
3263
|
+
'ctx.customerDb.query.options.id',
|
|
3264
|
+
options.id,
|
|
3265
|
+
);
|
|
3266
|
+
}
|
|
3267
|
+
if (options?.staleAfterSeconds !== undefined) {
|
|
3268
|
+
validatePlayAuthoringField(
|
|
3269
|
+
'ctx.customerDb.query.options.staleAfterSeconds',
|
|
3270
|
+
options.staleAfterSeconds,
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
if (
|
|
3274
|
+
this.usesExplicitFreshness &&
|
|
3275
|
+
options?.staleAfterSeconds === undefined
|
|
3276
|
+
) {
|
|
3277
|
+
throw new PlayAuthoringFieldValidationError(
|
|
3278
|
+
'ctx.customerDb.query.options.staleAfterSeconds',
|
|
3279
|
+
'play_authoring_durable_policy_invalid',
|
|
3280
|
+
'Edition 11 Customer DB queries require staleAfterSeconds: 0.',
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3226
3283
|
if (options?.maxRows !== undefined) {
|
|
3227
3284
|
validatePlayAuthoringField(
|
|
3228
3285
|
'ctx.customerDb.query.options.maxRows',
|
|
@@ -3236,7 +3293,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
3236
3293
|
);
|
|
3237
3294
|
}
|
|
3238
3295
|
const result = (await this.tools.execute({
|
|
3239
|
-
id: `customer_db_query_${this.customerDbQueryIndex++}`,
|
|
3296
|
+
id: options?.id ?? `customer_db_query_${this.customerDbQueryIndex++}`,
|
|
3240
3297
|
tool: 'query_customer_db',
|
|
3241
3298
|
input: {
|
|
3242
3299
|
sql,
|
|
@@ -3247,7 +3304,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
3247
3304
|
description: 'Customer DB query',
|
|
3248
3305
|
// The direct executor never reused a prior SQL result. Preserve that
|
|
3249
3306
|
// imperative behavior for reads and mutations alike.
|
|
3250
|
-
|
|
3307
|
+
...(this.usesExplicitFreshness
|
|
3308
|
+
? { staleAfterSeconds: options?.staleAfterSeconds }
|
|
3309
|
+
: { force: true }),
|
|
3251
3310
|
...(options?.timeoutMs !== undefined
|
|
3252
3311
|
? { timeoutMs: options.timeoutMs }
|
|
3253
3312
|
: {}),
|
|
@@ -3853,6 +3912,95 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
3853
3912
|
return this.currentRuntimeContract.semantics;
|
|
3854
3913
|
}
|
|
3855
3914
|
|
|
3915
|
+
private get usesExplicitFreshness(): boolean {
|
|
3916
|
+
return (
|
|
3917
|
+
this.currentRuntimeSemantics.freshness ===
|
|
3918
|
+
'actual_age_with_operation_recovery'
|
|
3919
|
+
);
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
private freshnessInvocationId(operation: string, id: string): string {
|
|
3923
|
+
const row = rowContext.getStore();
|
|
3924
|
+
const step = authoredStepContext.getStore();
|
|
3925
|
+
const scope = stableStringify({
|
|
3926
|
+
namespace: this.currentExecutionScope.receipt.namespace,
|
|
3927
|
+
operation,
|
|
3928
|
+
id,
|
|
3929
|
+
step: step?.context === this ? (step.invocationId ?? step.stepId) : null,
|
|
3930
|
+
row: row
|
|
3931
|
+
? {
|
|
3932
|
+
table: row.tableNamespace ?? null,
|
|
3933
|
+
key: row.rowKey ?? row.rowId,
|
|
3934
|
+
field: row.fieldName ?? null,
|
|
3935
|
+
}
|
|
3936
|
+
: null,
|
|
3937
|
+
});
|
|
3938
|
+
const freshIndex = this.freshnessCallIndexByKey.get(scope) ?? 0;
|
|
3939
|
+
this.freshnessCallIndexByKey.set(scope, freshIndex + 1);
|
|
3940
|
+
// Invocation identity follows authored scope and call order. Keep it
|
|
3941
|
+
// separate from the request/auth/action fingerprint used by reusable
|
|
3942
|
+
// result caching, so credential or response-contract changes cannot mint
|
|
3943
|
+
// a second provider operation key for the same authored effect.
|
|
3944
|
+
// A mapped row can be re-entered after provider backpressure; its replay
|
|
3945
|
+
// cursor restores the original call position.
|
|
3946
|
+
const index = nextReplayableCallIndex(`freshness:${scope}`, freshIndex);
|
|
3947
|
+
return sha256Hex(`${scope}:${index}`);
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
private async executeFreshnessControlledCall<T>(input: {
|
|
3951
|
+
operation: DurableCtxOperation;
|
|
3952
|
+
id: string;
|
|
3953
|
+
semanticKey: string;
|
|
3954
|
+
invocationId: string;
|
|
3955
|
+
policy: { requested: number | null; effective: number | null };
|
|
3956
|
+
execute: () => Promise<T>;
|
|
3957
|
+
completedAt?: (value: T) => number | undefined;
|
|
3958
|
+
onOutcome?: (value: T, metadata: CallFreshnessMetadata) => T;
|
|
3959
|
+
}): Promise<T> {
|
|
3960
|
+
const orgId = this.#options.orgId?.trim();
|
|
3961
|
+
if (!orgId) {
|
|
3962
|
+
throw new Error(
|
|
3963
|
+
'Freshness-controlled calls require a resolved organization identity.',
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
return executeWithFreshnessReceipt({
|
|
3967
|
+
orgId,
|
|
3968
|
+
semanticKey: input.semanticKey,
|
|
3969
|
+
invocationId: input.invocationId,
|
|
3970
|
+
runId: this.currentRunId,
|
|
3971
|
+
requestedStaleAfterSeconds: input.policy.requested,
|
|
3972
|
+
effectiveStaleAfterSeconds: input.policy.effective,
|
|
3973
|
+
store: this.durableReceiptExecutionStore(),
|
|
3974
|
+
completedAt: input.completedAt,
|
|
3975
|
+
withHydration: (read) =>
|
|
3976
|
+
this.withRuntimeReceiptHydrationTurn(
|
|
3977
|
+
(turn) =>
|
|
3978
|
+
read((receipt) =>
|
|
3979
|
+
turn.settleBytes(
|
|
3980
|
+
runtimeStepReceiptPayloadBytes(receipt ? [receipt] : []),
|
|
3981
|
+
),
|
|
3982
|
+
),
|
|
3983
|
+
{ source: 'durable_operation' },
|
|
3984
|
+
),
|
|
3985
|
+
execute: async () => {
|
|
3986
|
+
const value = await input.execute();
|
|
3987
|
+
this.assertNoPendingDurableSuspension();
|
|
3988
|
+
return value;
|
|
3989
|
+
},
|
|
3990
|
+
onOutcome: (value, metadata) => {
|
|
3991
|
+
this.runtimeLog(
|
|
3992
|
+
JSON.stringify({
|
|
3993
|
+
event: 'call_freshness',
|
|
3994
|
+
operation: input.operation,
|
|
3995
|
+
call_key: input.id,
|
|
3996
|
+
...metadata,
|
|
3997
|
+
}),
|
|
3998
|
+
);
|
|
3999
|
+
return input.onOutcome ? input.onOutcome(value, metadata) : value;
|
|
4000
|
+
},
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
|
|
3856
4004
|
private recordPlayCallStep(input: {
|
|
3857
4005
|
playId: string;
|
|
3858
4006
|
execution?: 'inline';
|
|
@@ -6284,6 +6432,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
6284
6432
|
force?: boolean;
|
|
6285
6433
|
receiptKey?: string | null;
|
|
6286
6434
|
semanticKey?: string | null;
|
|
6435
|
+
invocationId?: string;
|
|
6287
6436
|
staleAfterSeconds?: number | null;
|
|
6288
6437
|
repairRunningReceiptForSameRun?: boolean;
|
|
6289
6438
|
repairRunningReceiptForSameRunAfterWaitTimeout?: boolean;
|
|
@@ -6327,6 +6476,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
6327
6476
|
},
|
|
6328
6477
|
});
|
|
6329
6478
|
if (opts.transient === true) {
|
|
6479
|
+
if (
|
|
6480
|
+
this.usesExplicitFreshness &&
|
|
6481
|
+
opts.staleAfterSeconds !== undefined &&
|
|
6482
|
+
opts.staleAfterSeconds !== 0
|
|
6483
|
+
) {
|
|
6484
|
+
throw new Error(
|
|
6485
|
+
'Transient fetch does not support retained result reuse; omit freshness or choose staleAfterSeconds: 0.',
|
|
6486
|
+
);
|
|
6487
|
+
}
|
|
6330
6488
|
try {
|
|
6331
6489
|
return await execute(null);
|
|
6332
6490
|
} finally {
|
|
@@ -6334,6 +6492,44 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
6334
6492
|
release?.();
|
|
6335
6493
|
}
|
|
6336
6494
|
}
|
|
6495
|
+
if (this.usesExplicitFreshness && operation === 'fetch') {
|
|
6496
|
+
const policy = resolveExplicitDurableCallPolicy(opts);
|
|
6497
|
+
const semanticKey = stableStringify({
|
|
6498
|
+
orgId: this.#options.orgId,
|
|
6499
|
+
namespace: this.currentExecutionScope.receipt.namespace,
|
|
6500
|
+
operation,
|
|
6501
|
+
id,
|
|
6502
|
+
semanticKey: opts.semanticKey,
|
|
6503
|
+
});
|
|
6504
|
+
try {
|
|
6505
|
+
return await this.executeFreshnessControlledCall({
|
|
6506
|
+
operation,
|
|
6507
|
+
id,
|
|
6508
|
+
semanticKey,
|
|
6509
|
+
invocationId:
|
|
6510
|
+
opts.invocationId ?? this.freshnessInvocationId(operation, id),
|
|
6511
|
+
policy,
|
|
6512
|
+
execute: () => execute(null),
|
|
6513
|
+
onOutcome: (value, freshness) => {
|
|
6514
|
+
const output =
|
|
6515
|
+
opts.onRecovered && freshness.source !== 'executed'
|
|
6516
|
+
? opts.onRecovered(
|
|
6517
|
+
value,
|
|
6518
|
+
{
|
|
6519
|
+
key: freshness.operationReceiptKey!,
|
|
6520
|
+
status: 'completed',
|
|
6521
|
+
},
|
|
6522
|
+
'cache',
|
|
6523
|
+
)
|
|
6524
|
+
: value;
|
|
6525
|
+
return { ...output, freshness };
|
|
6526
|
+
},
|
|
6527
|
+
});
|
|
6528
|
+
} finally {
|
|
6529
|
+
const release = releaseExternalCallSlot as (() => void) | null;
|
|
6530
|
+
release?.();
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6337
6533
|
const stalePolicy = resolveDurableCallCachePolicy(
|
|
6338
6534
|
opts.staleAfterSeconds,
|
|
6339
6535
|
operation === 'step'
|
|
@@ -8206,6 +8402,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8206
8402
|
options.onRowError,
|
|
8207
8403
|
);
|
|
8208
8404
|
validateOptionalPlayAuthoringField('ctx.dataset.run.mode', options.mode);
|
|
8405
|
+
validateOptionalPlayAuthoringField(
|
|
8406
|
+
'ctx.dataset.run.rowIdentity',
|
|
8407
|
+
options.rowIdentity,
|
|
8408
|
+
);
|
|
8209
8409
|
validateOptionalPlayAuthoringField(
|
|
8210
8410
|
'ctx.dataset.run.undrawnColumns',
|
|
8211
8411
|
options.undrawnColumns,
|
|
@@ -8260,11 +8460,53 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8260
8460
|
),
|
|
8261
8461
|
);
|
|
8262
8462
|
|
|
8463
|
+
const residentSourceRows = isPlayDataset(items)
|
|
8464
|
+
? residentPlayDatasetRows(items)
|
|
8465
|
+
: Array.isArray(items)
|
|
8466
|
+
? items
|
|
8467
|
+
: null;
|
|
8468
|
+
const csvSource =
|
|
8469
|
+
(isPlayDataset(items) && items.datasetKind === 'csv') ||
|
|
8470
|
+
residentSourceRows?.some(
|
|
8471
|
+
(_row, index) =>
|
|
8472
|
+
getPlayDatasetSourceRowIdentityAt(residentSourceRows, index) !==
|
|
8473
|
+
undefined,
|
|
8474
|
+
) === true;
|
|
8475
|
+
const csvOccurrenceIdentityRequired =
|
|
8476
|
+
csvSource &&
|
|
8477
|
+
this.currentRuntimeSemantics.datasets.csvRowIdentity === 'source_row';
|
|
8478
|
+
const rowIdentityMode = csvOccurrenceIdentityRequired
|
|
8479
|
+
? 'source_row'
|
|
8480
|
+
: (options?.rowIdentity ??
|
|
8481
|
+
(options?.key !== undefined
|
|
8482
|
+
? 'key'
|
|
8483
|
+
: csvSource
|
|
8484
|
+
? this.currentRuntimeSemantics.datasets.csvRowIdentity
|
|
8485
|
+
: 'key'));
|
|
8486
|
+
const useSourceRowIdentity = rowIdentityMode === 'source_row';
|
|
8263
8487
|
const userKeyOption = options?.key;
|
|
8264
8488
|
let explicitKeyResolver:
|
|
8265
8489
|
| ((row: Record<string, unknown>, index: number) => string)
|
|
8266
8490
|
| null = null;
|
|
8267
|
-
if (
|
|
8491
|
+
if (useSourceRowIdentity) {
|
|
8492
|
+
explicitKeyResolver = (row, index) => {
|
|
8493
|
+
const source = getPlayDatasetSourceRowIdentity(row);
|
|
8494
|
+
const inputRow = Object.fromEntries(
|
|
8495
|
+
Object.entries(toSerializableCsvAliasedRow(row)).filter(
|
|
8496
|
+
([field]) => !field.startsWith('__deepline'),
|
|
8497
|
+
),
|
|
8498
|
+
);
|
|
8499
|
+
// Position distinguishes equal records; the input digest prevents a
|
|
8500
|
+
// changed file at the same path from replaying an earlier row's output.
|
|
8501
|
+
return JSON.stringify([
|
|
8502
|
+
'source-row-v1',
|
|
8503
|
+
source?.datasetId ??
|
|
8504
|
+
(isPlayDataset(items) ? items.datasetId : normalizedMapNamespace),
|
|
8505
|
+
source?.rowIndex ?? index,
|
|
8506
|
+
stableDigest(stableStringify(inputRow)),
|
|
8507
|
+
]);
|
|
8508
|
+
};
|
|
8509
|
+
} else if (userKeyOption !== undefined) {
|
|
8268
8510
|
explicitKeyResolver = (row, index) => {
|
|
8269
8511
|
const inputRow = stripFieldOutputs(row);
|
|
8270
8512
|
const raw =
|
|
@@ -8469,6 +8711,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8469
8711
|
let failedCount = 0;
|
|
8470
8712
|
let duplicateReuseCount = 0;
|
|
8471
8713
|
let executedCount = 0;
|
|
8714
|
+
let sourceRowsCount = 0;
|
|
8472
8715
|
const failureSamples: Array<{ rowLabel: string; error: string }> = [];
|
|
8473
8716
|
const failureSampleErrors = new Set<string>();
|
|
8474
8717
|
const previewRows: Record<string, unknown>[] = [];
|
|
@@ -8572,9 +8815,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8572
8815
|
offset: number;
|
|
8573
8816
|
}): Promise<PreparedPage> => {
|
|
8574
8817
|
this.assertRowAdmissionOpen();
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8818
|
+
sourceRowsCount += page.rows.length;
|
|
8819
|
+
const pageRawItems = page.rows.map((item, index) => {
|
|
8820
|
+
const row = this.toOutputRow(item as Record<string, unknown>);
|
|
8821
|
+
const sourceIdentity = getPlayDatasetSourceRowIdentityAt(
|
|
8822
|
+
page.rows,
|
|
8823
|
+
index,
|
|
8824
|
+
);
|
|
8825
|
+
return sourceIdentity
|
|
8826
|
+
? setPlayDatasetSourceRowIdentity(row, sourceIdentity)
|
|
8827
|
+
: row;
|
|
8828
|
+
});
|
|
8578
8829
|
const pageMemoryEstimate = estimateRuntimeMapRowsMemory(pageRawItems);
|
|
8579
8830
|
if (pageMemoryEstimate.estimatedResidentBytes > pageBudgetBytes) {
|
|
8580
8831
|
throw new RuntimeMapMemoryLimitError({
|
|
@@ -8588,6 +8839,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8588
8839
|
const originalIndex = page.offset + index;
|
|
8589
8840
|
return {
|
|
8590
8841
|
...toSerializableCsvAliasedRow(row),
|
|
8842
|
+
...(getPlayDatasetSourceRowIdentity(row)
|
|
8843
|
+
? { __deeplineSourceRow: getPlayDatasetSourceRowIdentity(row) }
|
|
8844
|
+
: {}),
|
|
8591
8845
|
...mapRowOutcomeRuntimeFields({
|
|
8592
8846
|
key: seedRowIdentity(row, originalIndex),
|
|
8593
8847
|
inputIndex: originalIndex,
|
|
@@ -8630,9 +8884,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8630
8884
|
completedRowsByKey.set(rowKey, row);
|
|
8631
8885
|
}
|
|
8632
8886
|
|
|
8633
|
-
const seededItems =
|
|
8887
|
+
const seededItems = pageRawItems.map((row, index) => {
|
|
8634
8888
|
const originalIndex = page.offset + index;
|
|
8635
|
-
const row = this.toOutputRow(item as Record<string, unknown>);
|
|
8636
8889
|
const rowKey = seedRowIdentity(row, originalIndex);
|
|
8637
8890
|
const persisted =
|
|
8638
8891
|
pendingRowsByKey.get(rowKey) ?? completedRowsByKey.get(rowKey);
|
|
@@ -8661,7 +8914,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8661
8914
|
{ row: Record<string, unknown>; originalIndex: number }
|
|
8662
8915
|
>();
|
|
8663
8916
|
for (const { row, index } of admittedSeededItems) {
|
|
8664
|
-
if (explicitKeyResolver) {
|
|
8917
|
+
if (explicitKeyResolver && !useSourceRowIdentity) {
|
|
8665
8918
|
const explicitKey = explicitKeyResolver(row, index);
|
|
8666
8919
|
if (seenExplicitKeys.has(explicitKey)) {
|
|
8667
8920
|
duplicateReuseCount += 1;
|
|
@@ -8990,12 +9243,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8990
9243
|
);
|
|
8991
9244
|
successfulCount = visibleResults.completedRows;
|
|
8992
9245
|
failedCount = visibleResults.failedRows;
|
|
8993
|
-
this.#options.onMapRowsSettled?.({
|
|
9246
|
+
await this.#options.onMapRowsSettled?.({
|
|
8994
9247
|
playName: this.#options.playName,
|
|
8995
9248
|
runId: this.#options.runId,
|
|
8996
9249
|
tableNamespace: resolvedTableNamespace,
|
|
8997
9250
|
succeededRows: successfulCount,
|
|
8998
9251
|
failedRows: failedCount,
|
|
9252
|
+
expectedRows: totalInputCount - duplicateReuseCount,
|
|
9253
|
+
staticPipeline: this.currentStaticPipeline,
|
|
8999
9254
|
});
|
|
9000
9255
|
|
|
9001
9256
|
// Isolated failures are a partial result only when at least one admitted
|
|
@@ -9181,6 +9436,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9181
9436
|
skipped: duplicateReuseCount,
|
|
9182
9437
|
pending: 0,
|
|
9183
9438
|
failed: failedCount,
|
|
9439
|
+
cardinality: {
|
|
9440
|
+
sourceRows: sourceRowsCount,
|
|
9441
|
+
uniqueInputRows: processedCount - duplicateReuseCount,
|
|
9442
|
+
duplicateOccurrences: duplicateReuseCount,
|
|
9443
|
+
outputRows: successfulCount,
|
|
9444
|
+
rowIdentity: rowIdentityMode,
|
|
9445
|
+
},
|
|
9184
9446
|
...(duplicateReuseCount > 0
|
|
9185
9447
|
? { duplicates: { exact: duplicateReuseCount } }
|
|
9186
9448
|
: {}),
|
|
@@ -9228,24 +9490,66 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9228
9490
|
},
|
|
9229
9491
|
});
|
|
9230
9492
|
}
|
|
9231
|
-
const rawMaterializedEntries
|
|
9493
|
+
const rawMaterializedEntries: {
|
|
9494
|
+
item: T;
|
|
9495
|
+
inputIndex: number;
|
|
9496
|
+
sourceIdentity?: ReturnType<typeof getPlayDatasetSourceRowIdentityAt>;
|
|
9497
|
+
}[] =
|
|
9232
9498
|
resumedPendingRowsOnly && suspendedInputRows
|
|
9233
|
-
? suspendedInputRows.map((row) =>
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9499
|
+
? suspendedInputRows.map((row) => {
|
|
9500
|
+
const retainedKey = resolveMapRowOutcomeKey(row.data);
|
|
9501
|
+
return {
|
|
9502
|
+
// Older checkpoints contain only execution data. New checkpoints
|
|
9503
|
+
// also retain the original admission input before sheet outputs
|
|
9504
|
+
// were restored; keep the already-admitted key across either path.
|
|
9505
|
+
item: cloneCsvAliasedRow(
|
|
9506
|
+
row.authoredInput?.data ?? row.data,
|
|
9507
|
+
retainedKey
|
|
9508
|
+
? mapRowOutcomeRuntimeFields({ key: retainedKey })
|
|
9509
|
+
: {},
|
|
9510
|
+
) as T,
|
|
9511
|
+
inputIndex: row.inputIndex,
|
|
9512
|
+
sourceIdentity: row.authoredInput?.source,
|
|
9513
|
+
};
|
|
9514
|
+
})
|
|
9515
|
+
: await (async () => {
|
|
9516
|
+
const materialized = await materializePlayDatasetInput(items, {
|
|
9239
9517
|
onRow: (row) => {
|
|
9240
9518
|
materializationMemoryTracker.track(row);
|
|
9241
9519
|
},
|
|
9242
|
-
})
|
|
9243
|
-
|
|
9520
|
+
});
|
|
9521
|
+
return materialized.map((item, inputIndex) => ({
|
|
9522
|
+
item,
|
|
9523
|
+
inputIndex,
|
|
9524
|
+
sourceIdentity: getPlayDatasetSourceRowIdentityAt(
|
|
9525
|
+
materialized,
|
|
9526
|
+
inputIndex,
|
|
9527
|
+
),
|
|
9528
|
+
}));
|
|
9529
|
+
})();
|
|
9244
9530
|
const rawMaterializedItems = rawMaterializedEntries.map(
|
|
9245
|
-
|
|
9531
|
+
// Occurrence identity also applies to an explicitly selected array whose
|
|
9532
|
+
// author reused the same object reference. Give each occurrence its own
|
|
9533
|
+
// execution object so the original-index lookup cannot alias the last row.
|
|
9534
|
+
(entry) => {
|
|
9535
|
+
const sourceIdentity =
|
|
9536
|
+
'sourceIdentity' in entry
|
|
9537
|
+
? entry.sourceIdentity
|
|
9538
|
+
: getPlayDatasetSourceRowIdentity(entry.item);
|
|
9539
|
+
if (!useSourceRowIdentity && !sourceIdentity) return entry.item;
|
|
9540
|
+
const row = this.toOutputRow(entry.item);
|
|
9541
|
+
return (
|
|
9542
|
+
sourceIdentity
|
|
9543
|
+
? setPlayDatasetSourceRowIdentity(row, sourceIdentity)
|
|
9544
|
+
: row
|
|
9545
|
+
) as T;
|
|
9546
|
+
},
|
|
9246
9547
|
);
|
|
9247
9548
|
const originalInputIndexByItem = new Map<T, number>(
|
|
9248
|
-
|
|
9549
|
+
rawMaterializedItems.map((item, index) => [
|
|
9550
|
+
item,
|
|
9551
|
+
rawMaterializedEntries[index]!.inputIndex,
|
|
9552
|
+
]),
|
|
9249
9553
|
);
|
|
9250
9554
|
// Silent dedupe of duplicate explicit map keys: keep the first row per
|
|
9251
9555
|
// canonical key and drop subsequent duplicates. Deduping the materialized
|
|
@@ -9253,24 +9557,75 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9253
9557
|
// counts, sheet writes, resumable keys) consistent — the resolver derives
|
|
9254
9558
|
// keys from row content, so dropped rows are excluded everywhere with no
|
|
9255
9559
|
// index mismatch. Replaces the prior throw-on-duplicate behavior.
|
|
9256
|
-
const dedupedMaterialized =
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9560
|
+
const dedupedMaterialized =
|
|
9561
|
+
explicitKeyResolver && !useSourceRowIdentity
|
|
9562
|
+
? dedupeExplicitMapKeyRows({
|
|
9563
|
+
rows: rawMaterializedItems,
|
|
9564
|
+
resolver: (item, index) =>
|
|
9565
|
+
explicitKeyResolver(
|
|
9566
|
+
this.toOutputRow(item as Record<string, unknown>),
|
|
9567
|
+
originalInputIndexByItem.get(item) ?? index,
|
|
9568
|
+
),
|
|
9569
|
+
})
|
|
9570
|
+
: { rows: rawMaterializedItems, droppedCount: 0, duplicateKeys: [] };
|
|
9266
9571
|
if (dedupedMaterialized.droppedCount > 0) {
|
|
9267
|
-
const keySample = dedupedMaterialized.duplicateKeys.join(', ');
|
|
9268
9572
|
this.runtimeLog(
|
|
9269
|
-
`
|
|
9270
|
-
|
|
9573
|
+
`[runtime.dataset_input_cardinality] ${JSON.stringify({
|
|
9574
|
+
dataset: normalizedMapNamespace,
|
|
9575
|
+
rowIdentity: rowIdentityMode,
|
|
9576
|
+
sourceRows: rawMaterializedEntries.length,
|
|
9577
|
+
uniqueInputRows: dedupedMaterialized.rows.length,
|
|
9578
|
+
duplicateOccurrences: dedupedMaterialized.droppedCount,
|
|
9579
|
+
duplicatePolicy: 'first_wins',
|
|
9580
|
+
})}`,
|
|
9271
9581
|
);
|
|
9272
9582
|
}
|
|
9273
9583
|
const materializedItems = dedupedMaterialized.rows;
|
|
9584
|
+
// A legacy checkpoint does not contain its pre-dedupe source cardinality.
|
|
9585
|
+
// Leave the optional summary absent rather than report its pending subset
|
|
9586
|
+
// as the original source. New checkpoints retain only fixed-size counts.
|
|
9587
|
+
const inputCardinality = resumedPendingRowsOnly
|
|
9588
|
+
? replayMapFrame?.inputCardinality
|
|
9589
|
+
: {
|
|
9590
|
+
sourceRows: rawMaterializedEntries.length,
|
|
9591
|
+
uniqueInputRows: materializedItems.length,
|
|
9592
|
+
duplicateOccurrences: dedupedMaterialized.droppedCount,
|
|
9593
|
+
rowIdentity: rowIdentityMode,
|
|
9594
|
+
};
|
|
9595
|
+
// Admission provenance is captured before onMapStart restores output
|
|
9596
|
+
// cells. The execution row stays intact; only pending inputs enter a
|
|
9597
|
+
// checkpoint, and their sheet outputs are restored by the existing owner
|
|
9598
|
+
// after the next admission. Never infer original input from enriched data.
|
|
9599
|
+
const retainedAuthoredInputsByIndex = new Map(
|
|
9600
|
+
(suspendedInputRows ?? []).flatMap((row) =>
|
|
9601
|
+
row.authoredInput ? [[row.inputIndex, row.authoredInput] as const] : [],
|
|
9602
|
+
),
|
|
9603
|
+
);
|
|
9604
|
+
const authoredInputsByIndex = new Map(
|
|
9605
|
+
canReadPersistedMapResults
|
|
9606
|
+
? materializedItems.flatMap((item, index) => {
|
|
9607
|
+
const inputIndex = originalInputIndexByItem.get(item) ?? index;
|
|
9608
|
+
if (resumedPendingRowsOnly) {
|
|
9609
|
+
const retained = retainedAuthoredInputsByIndex.get(inputIndex);
|
|
9610
|
+
// An older pending checkpoint has execution data only. Keep its
|
|
9611
|
+
// optional provenance absent rather than relabel enriched cells.
|
|
9612
|
+
return retained ? [[inputIndex, retained] as const] : [];
|
|
9613
|
+
}
|
|
9614
|
+
const row = this.toOutputRow(item as Record<string, unknown>);
|
|
9615
|
+
const source = getPlayDatasetSourceRowIdentity(row);
|
|
9616
|
+
const data = sanitizePostgresJsonValue(
|
|
9617
|
+
toSerializableCsvAliasedRow(row),
|
|
9618
|
+
);
|
|
9619
|
+
materializationMemoryTracker.track(data);
|
|
9620
|
+
return [
|
|
9621
|
+
[
|
|
9622
|
+
inputIndex,
|
|
9623
|
+
{ data, ...(source ? { source: { ...source } } : {}) },
|
|
9624
|
+
] as const,
|
|
9625
|
+
];
|
|
9626
|
+
})
|
|
9627
|
+
: [],
|
|
9628
|
+
);
|
|
9274
9629
|
const priorAuthoredDigestsByIndex = new Map(
|
|
9275
9630
|
(suspendedInputRows ?? []).flatMap((row) =>
|
|
9276
9631
|
row.authoredInputDigest
|
|
@@ -9405,6 +9760,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9405
9760
|
const inputIndex = itemOriginalIndexes[index] ?? index;
|
|
9406
9761
|
return {
|
|
9407
9762
|
...toSerializableCsvAliasedRow(row),
|
|
9763
|
+
...(getPlayDatasetSourceRowIdentity(row)
|
|
9764
|
+
? { __deeplineSourceRow: getPlayDatasetSourceRowIdentity(row) }
|
|
9765
|
+
: {}),
|
|
9408
9766
|
...mapRowOutcomeRuntimeFields({
|
|
9409
9767
|
key: seedRowIdentity(row, inputIndex),
|
|
9410
9768
|
inputIndex,
|
|
@@ -9513,6 +9871,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9513
9871
|
artifactTableNamespace: mapScope.artifactTableNamespace,
|
|
9514
9872
|
status: 'running',
|
|
9515
9873
|
totalRows: totalInputCount,
|
|
9874
|
+
inputCardinality,
|
|
9516
9875
|
completedRowKeys,
|
|
9517
9876
|
pendingRowKeys,
|
|
9518
9877
|
completedRowsCount: previouslyCompletedRowsCount,
|
|
@@ -9663,13 +10022,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9663
10022
|
executionRowIndexes: rowsToExecuteEntries.map(
|
|
9664
10023
|
(entry) => entry.originalIndex,
|
|
9665
10024
|
),
|
|
9666
|
-
...(
|
|
10025
|
+
...(canReadPersistedMapResults
|
|
9667
10026
|
? {
|
|
10027
|
+
authoredInputs: rowsToExecuteEntries.map((entry) =>
|
|
10028
|
+
authoredInputsByIndex.get(entry.originalIndex),
|
|
10029
|
+
),
|
|
10030
|
+
}
|
|
10031
|
+
: {
|
|
9668
10032
|
authoredInputDigests: rowsToExecuteEntries.map((entry) =>
|
|
9669
10033
|
authoredInputDigestsByIndex.get(entry.originalIndex),
|
|
9670
10034
|
),
|
|
9671
|
-
}
|
|
9672
|
-
: {}),
|
|
10035
|
+
}),
|
|
9673
10036
|
retainedRowsMemoryBudgetBytes:
|
|
9674
10037
|
mapMemoryLimits.materializedBudgetBytes,
|
|
9675
10038
|
progressCompletedOffset:
|
|
@@ -9854,13 +10217,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9854
10217
|
});
|
|
9855
10218
|
}
|
|
9856
10219
|
|
|
9857
|
-
this.#options.onMapRowsSettled?.({
|
|
10220
|
+
await this.#options.onMapRowsSettled?.({
|
|
9858
10221
|
playName: this.#options.playName,
|
|
9859
10222
|
runId: this.#options.runId,
|
|
9860
10223
|
tableNamespace: resolvedTableNamespace,
|
|
9861
10224
|
succeededRows:
|
|
9862
10225
|
previouslyCompletedRowKeys.length + visibleCompletedRows.length,
|
|
9863
10226
|
failedRows: durableFailedRows,
|
|
10227
|
+
expectedRows: totalInputCount - duplicateReuseCount,
|
|
10228
|
+
staticPipeline: this.currentStaticPipeline,
|
|
9864
10229
|
});
|
|
9865
10230
|
|
|
9866
10231
|
// This map's terminal rows have already crossed the persistence barrier
|
|
@@ -9950,8 +10315,24 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9950
10315
|
skipped: reusedCount,
|
|
9951
10316
|
pending: 0,
|
|
9952
10317
|
failed: durableFailedRows,
|
|
9953
|
-
...(
|
|
9954
|
-
? {
|
|
10318
|
+
...(inputCardinality
|
|
10319
|
+
? {
|
|
10320
|
+
cardinality: {
|
|
10321
|
+
...inputCardinality,
|
|
10322
|
+
uniqueInputRows:
|
|
10323
|
+
inputCardinality.uniqueInputRows - duplicateReuseCount,
|
|
10324
|
+
duplicateOccurrences:
|
|
10325
|
+
inputCardinality.duplicateOccurrences + duplicateReuseCount,
|
|
10326
|
+
outputRows: results.length,
|
|
10327
|
+
},
|
|
10328
|
+
}
|
|
10329
|
+
: {}),
|
|
10330
|
+
...(dedupedMaterialized.droppedCount + duplicateReuseCount > 0
|
|
10331
|
+
? {
|
|
10332
|
+
duplicates: {
|
|
10333
|
+
exact: dedupedMaterialized.droppedCount + duplicateReuseCount,
|
|
10334
|
+
},
|
|
10335
|
+
}
|
|
9955
10336
|
: {}),
|
|
9956
10337
|
},
|
|
9957
10338
|
resolvers: {
|
|
@@ -9989,6 +10370,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9989
10370
|
onRowError?: 'isolate' | 'fail';
|
|
9990
10371
|
executionRowKeys?: string[];
|
|
9991
10372
|
executionRowIndexes?: number[];
|
|
10373
|
+
authoredInputs?: Array<
|
|
10374
|
+
NonNullable<
|
|
10375
|
+
MapExecutionFrame['suspendedInputRows']
|
|
10376
|
+
>[number]['authoredInput']
|
|
10377
|
+
>;
|
|
9992
10378
|
authoredInputDigests?: Array<string | undefined>;
|
|
9993
10379
|
/**
|
|
9994
10380
|
* Opt-in cap on how many row resolvers run their body concurrently. When
|
|
@@ -10796,6 +11182,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10796
11182
|
),
|
|
10797
11183
|
...mapRowOutcomeRuntimeFields({ key: rowKey }),
|
|
10798
11184
|
},
|
|
11185
|
+
...(runtimeOptions?.authoredInputs?.[index]
|
|
11186
|
+
? { authoredInput: runtimeOptions.authoredInputs[index] }
|
|
11187
|
+
: {}),
|
|
10799
11188
|
...(runtimeOptions?.authoredInputDigests?.[index]
|
|
10800
11189
|
? {
|
|
10801
11190
|
authoredInputDigest:
|
|
@@ -11204,6 +11593,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11204
11593
|
key: keyForIndex(index),
|
|
11205
11594
|
}),
|
|
11206
11595
|
},
|
|
11596
|
+
...(runtimeOptions?.authoredInputs?.[index]
|
|
11597
|
+
? { authoredInput: runtimeOptions.authoredInputs[index] }
|
|
11598
|
+
: {}),
|
|
11207
11599
|
...(runtimeOptions?.authoredInputDigests?.[index]
|
|
11208
11600
|
? {
|
|
11209
11601
|
authoredInputDigest:
|
|
@@ -11453,6 +11845,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11453
11845
|
stepPath,
|
|
11454
11846
|
}),
|
|
11455
11847
|
),
|
|
11848
|
+
...(step.staleAfterSeconds !== undefined
|
|
11849
|
+
? { staleAfterSeconds: step.staleAfterSeconds }
|
|
11850
|
+
: {}),
|
|
11456
11851
|
},
|
|
11457
11852
|
)
|
|
11458
11853
|
: await this.executeStepProgramStep(
|
|
@@ -12530,7 +12925,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12530
12925
|
|
|
12531
12926
|
private toOutputRow<T>(item: T): Record<string, unknown> {
|
|
12532
12927
|
if (item != null && typeof item === 'object' && !Array.isArray(item)) {
|
|
12533
|
-
return
|
|
12928
|
+
return copyPlayDatasetSourceRowIdentity(
|
|
12929
|
+
item,
|
|
12930
|
+
stripCsvProjectionMetadata(item as Record<string, unknown>),
|
|
12931
|
+
);
|
|
12534
12932
|
}
|
|
12535
12933
|
|
|
12536
12934
|
return { value: item };
|
|
@@ -12739,6 +13137,165 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12739
13137
|
toolId: string,
|
|
12740
13138
|
input: Record<string, unknown>,
|
|
12741
13139
|
options?: ToolCallOptions,
|
|
13140
|
+
): Promise<unknown> {
|
|
13141
|
+
if (!this.usesExplicitFreshness) {
|
|
13142
|
+
return this.executeToolLegacyInternal(key, toolId, input, options);
|
|
13143
|
+
}
|
|
13144
|
+
const policy = resolveExplicitDurableCallPolicy(options ?? {});
|
|
13145
|
+
if (
|
|
13146
|
+
this.#options.cachePolicy?.forceToolRefresh === true ||
|
|
13147
|
+
toolExecutionOverrides.getStore()?.force === true
|
|
13148
|
+
) {
|
|
13149
|
+
policy.effective = 0;
|
|
13150
|
+
}
|
|
13151
|
+
const normalizedKey = this.normalizeContextKey(key, 'tool');
|
|
13152
|
+
// Reserve operation identity before asynchronous metadata lookup. Two
|
|
13153
|
+
// concurrent calls may share an authored id; assigning their identity
|
|
13154
|
+
// after auth/action metadata resolves would make recovery depend on lookup
|
|
13155
|
+
// completion order instead of authored call order.
|
|
13156
|
+
const invocationId = this.freshnessInvocationId('tool', normalizedKey);
|
|
13157
|
+
const sqlTool = isCustomerDbDatasetTool(toolId);
|
|
13158
|
+
const scalarCustomerDbQuery = sqlTool && input.result_mode === 'rows';
|
|
13159
|
+
const liveDataset =
|
|
13160
|
+
isQueryResultDatasetReadRequest(toolId, input) && !scalarCustomerDbQuery;
|
|
13161
|
+
const sqlMutation =
|
|
13162
|
+
sqlTool &&
|
|
13163
|
+
!isQueryResultDatasetReadRequest(toolId, {
|
|
13164
|
+
...input,
|
|
13165
|
+
result_mode: undefined,
|
|
13166
|
+
});
|
|
13167
|
+
const [eventWait, invocationScoped, retryPolicy, reusableSupport] =
|
|
13168
|
+
policy.effective === 0
|
|
13169
|
+
? ([null, false, null, null] as const)
|
|
13170
|
+
: await Promise.all([
|
|
13171
|
+
this.#options.getIntegrationEventWaitHandler?.(toolId) ?? null,
|
|
13172
|
+
this.#options
|
|
13173
|
+
.getToolIdempotencyScope?.(toolId)
|
|
13174
|
+
.then((scope) => scope === 'invocation') ?? false,
|
|
13175
|
+
this.#options.getToolRetryPolicy?.(toolId, input) ?? null,
|
|
13176
|
+
this.#options.getToolReusableResultSupport?.(toolId) ?? null,
|
|
13177
|
+
]);
|
|
13178
|
+
const requiresExecutionFence = retryPolicy?.requiresExecutionFence === true;
|
|
13179
|
+
if (policy.effective !== 0 && reusableSupport?.supported !== true) {
|
|
13180
|
+
throw new Error(
|
|
13181
|
+
`Tool ${toolId} does not support reusable freshness; use staleAfterSeconds: 0. ${reusableSupport?.unavailableReason ?? 'No read-result reuse capability was supplied by the action owner.'}`,
|
|
13182
|
+
);
|
|
13183
|
+
}
|
|
13184
|
+
if (
|
|
13185
|
+
policy.effective !== 0 &&
|
|
13186
|
+
(eventWait ||
|
|
13187
|
+
invocationScoped ||
|
|
13188
|
+
requiresExecutionFence ||
|
|
13189
|
+
liveDataset ||
|
|
13190
|
+
sqlMutation ||
|
|
13191
|
+
isAlwaysFreshIntegrationTool(toolId))
|
|
13192
|
+
) {
|
|
13193
|
+
throw new Error(
|
|
13194
|
+
`Tool ${toolId} does not support reusable freshness; use staleAfterSeconds: 0. Live datasets, event waits, invocation-scoped actions, writes, and Workspace operations cannot reuse an older result.`,
|
|
13195
|
+
);
|
|
13196
|
+
}
|
|
13197
|
+
|
|
13198
|
+
const authScope = await this.resolveToolAuthScopeDigest(toolId);
|
|
13199
|
+
const actionVersion =
|
|
13200
|
+
(await this.#options.getToolActionCacheVersion?.(toolId))?.trim() ?? '';
|
|
13201
|
+
if (!actionVersion) {
|
|
13202
|
+
throw new Error(`Tool ${toolId} has no action identity for freshness.`);
|
|
13203
|
+
}
|
|
13204
|
+
const semanticKey = stableStringify({
|
|
13205
|
+
kind: 'tool',
|
|
13206
|
+
orgId: this.#options.orgId,
|
|
13207
|
+
toolId,
|
|
13208
|
+
input,
|
|
13209
|
+
authScope,
|
|
13210
|
+
actionVersion,
|
|
13211
|
+
responseContract: this.currentToolResponseContract,
|
|
13212
|
+
responseRevision: this.currentToolResponseReceiptRevision,
|
|
13213
|
+
});
|
|
13214
|
+
if (sqlTool && input.operation_key !== undefined) {
|
|
13215
|
+
throw new Error(
|
|
13216
|
+
'Play SQL operation identity is assigned by the runtime. Use the authored call id to identify the operation.',
|
|
13217
|
+
);
|
|
13218
|
+
}
|
|
13219
|
+
const requestInput =
|
|
13220
|
+
sqlTool && (sqlMutation || scalarCustomerDbQuery)
|
|
13221
|
+
? { ...input, operation_key: `${this.currentRunId}:${invocationId}` }
|
|
13222
|
+
: input;
|
|
13223
|
+
|
|
13224
|
+
if (liveDataset || isAlwaysFreshIntegrationTool(toolId)) {
|
|
13225
|
+
const value = await this.executeToolLegacyInternal(
|
|
13226
|
+
key,
|
|
13227
|
+
toolId,
|
|
13228
|
+
requestInput,
|
|
13229
|
+
{ ...options, force: true, staleAfterSeconds: 0 },
|
|
13230
|
+
);
|
|
13231
|
+
const freshness: CallFreshnessMetadata = {
|
|
13232
|
+
requestedStaleAfterSeconds: policy.requested,
|
|
13233
|
+
effectiveStaleAfterSeconds: 0,
|
|
13234
|
+
source: 'live_observation',
|
|
13235
|
+
resultAgeMs: 0,
|
|
13236
|
+
completedAt: Date.now(),
|
|
13237
|
+
unavailableReason: liveDataset
|
|
13238
|
+
? 'Dataset rows are live observations; the dataset owner controls pagination and recovery.'
|
|
13239
|
+
: 'Workspace reviewer edits and conflict checks must be observed on every invocation.',
|
|
13240
|
+
};
|
|
13241
|
+
return isToolExecuteResult(value)
|
|
13242
|
+
? cloneToolExecuteResultWithExecution(value, {
|
|
13243
|
+
...value._metadata.execution,
|
|
13244
|
+
freshness,
|
|
13245
|
+
})
|
|
13246
|
+
: value;
|
|
13247
|
+
}
|
|
13248
|
+
|
|
13249
|
+
return this.executeFreshnessControlledCall({
|
|
13250
|
+
operation: 'tool',
|
|
13251
|
+
id: normalizedKey,
|
|
13252
|
+
semanticKey,
|
|
13253
|
+
invocationId,
|
|
13254
|
+
policy,
|
|
13255
|
+
completedAt: (value) => {
|
|
13256
|
+
if (!isToolExecuteResult(value)) return undefined;
|
|
13257
|
+
const raw = value.toolResponse.raw as {
|
|
13258
|
+
operation?: { completed_at_ms?: unknown };
|
|
13259
|
+
} | null;
|
|
13260
|
+
const completedAt = raw?.operation?.completed_at_ms;
|
|
13261
|
+
return typeof completedAt === 'number' &&
|
|
13262
|
+
Number.isSafeInteger(completedAt)
|
|
13263
|
+
? completedAt
|
|
13264
|
+
: value._metadata.execution.completedAt;
|
|
13265
|
+
},
|
|
13266
|
+
execute: async () => {
|
|
13267
|
+
const value = await this.executeToolLegacyInternal(
|
|
13268
|
+
key,
|
|
13269
|
+
toolId,
|
|
13270
|
+
requestInput,
|
|
13271
|
+
{ ...options, force: true, staleAfterSeconds: 0 },
|
|
13272
|
+
);
|
|
13273
|
+
if ((await this.resolveToolAuthScopeDigest(toolId)) !== authScope) {
|
|
13274
|
+
throw new Error(
|
|
13275
|
+
'Tool credential identity changed during execution. Resume under the current credential identity before retaining this result.',
|
|
13276
|
+
);
|
|
13277
|
+
}
|
|
13278
|
+
return value;
|
|
13279
|
+
},
|
|
13280
|
+
onOutcome: (value, freshness) =>
|
|
13281
|
+
isToolExecuteResult(value)
|
|
13282
|
+
? cloneToolExecuteResultWithExecution(value, {
|
|
13283
|
+
...value._metadata.execution,
|
|
13284
|
+
cached: freshness.source !== 'executed',
|
|
13285
|
+
source: freshness.source === 'executed' ? 'live' : 'cache',
|
|
13286
|
+
resultReceiptKey: freshness.operationReceiptKey,
|
|
13287
|
+
freshness,
|
|
13288
|
+
completedAt: freshness.completedAt,
|
|
13289
|
+
})
|
|
13290
|
+
: value,
|
|
13291
|
+
});
|
|
13292
|
+
}
|
|
13293
|
+
|
|
13294
|
+
private async executeToolLegacyInternal(
|
|
13295
|
+
key: string,
|
|
13296
|
+
toolId: string,
|
|
13297
|
+
input: Record<string, unknown>,
|
|
13298
|
+
options?: ToolCallOptions,
|
|
12742
13299
|
): Promise<unknown> {
|
|
12743
13300
|
if (!rowContext.getStore()) this.assertRowAdmissionOpen();
|
|
12744
13301
|
this.assertNoPendingDurableSuspension();
|
|
@@ -14873,6 +15430,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14873
15430
|
if (!rowContext.getStore()) this.assertRowAdmissionOpen();
|
|
14874
15431
|
validatePlayAuthoringField('ctx.fetch.key', key);
|
|
14875
15432
|
const normalizedKey = this.normalizeContextKey(key, 'fetch');
|
|
15433
|
+
const invocationId =
|
|
15434
|
+
this.usesExplicitFreshness && options?.transient !== true
|
|
15435
|
+
? this.freshnessInvocationId('fetch', normalizedKey)
|
|
15436
|
+
: undefined;
|
|
14876
15437
|
const rowStore = rowContext.getStore();
|
|
14877
15438
|
const rowFetchScope = rowStore
|
|
14878
15439
|
? {
|
|
@@ -14886,6 +15447,30 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14886
15447
|
const url = input.toString();
|
|
14887
15448
|
const parsedUrl = new URL(url);
|
|
14888
15449
|
const method = (init.method ?? 'GET').toUpperCase();
|
|
15450
|
+
const reusableReadMethod = ['GET', 'HEAD', 'OPTIONS'].includes(method);
|
|
15451
|
+
if (this.usesExplicitFreshness && options?.transient === true) {
|
|
15452
|
+
if (!reusableReadMethod) {
|
|
15453
|
+
throw new Error(
|
|
15454
|
+
'Edition 11 transient fetches are limited to read-only HTTP methods.',
|
|
15455
|
+
);
|
|
15456
|
+
}
|
|
15457
|
+
if (
|
|
15458
|
+
options.staleAfterSeconds !== undefined &&
|
|
15459
|
+
options.staleAfterSeconds !== 0
|
|
15460
|
+
) {
|
|
15461
|
+
throw new Error(
|
|
15462
|
+
'Transient fetch does not support retained result reuse; omit freshness or choose staleAfterSeconds: 0.',
|
|
15463
|
+
);
|
|
15464
|
+
}
|
|
15465
|
+
} else if (
|
|
15466
|
+
this.usesExplicitFreshness &&
|
|
15467
|
+
!reusableReadMethod &&
|
|
15468
|
+
options?.staleAfterSeconds !== 0
|
|
15469
|
+
) {
|
|
15470
|
+
throw new Error(
|
|
15471
|
+
'Edition 11 mutating or non-read-only fetches require staleAfterSeconds: 0.',
|
|
15472
|
+
);
|
|
15473
|
+
}
|
|
14889
15474
|
const urlContainsResolvedSecret =
|
|
14890
15475
|
this.secretRedactor.containsRegisteredSecret(url, {
|
|
14891
15476
|
includeEncoded: true,
|
|
@@ -14975,6 +15560,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14975
15560
|
: {}),
|
|
14976
15561
|
}),
|
|
14977
15562
|
),
|
|
15563
|
+
invocationId,
|
|
14978
15564
|
staleAfterSeconds: options?.staleAfterSeconds,
|
|
14979
15565
|
transient: options?.transient === true,
|
|
14980
15566
|
onRecovered: (output) => {
|
|
@@ -15026,6 +15612,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15026
15612
|
},
|
|
15027
15613
|
body: receiptBody,
|
|
15028
15614
|
row: rowFetchScope,
|
|
15615
|
+
...(invocationId ? { invocationId } : {}),
|
|
15029
15616
|
}),
|
|
15030
15617
|
)}`,
|
|
15031
15618
|
);
|
|
@@ -15174,6 +15761,24 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15174
15761
|
'ctx.step.semanticKey',
|
|
15175
15762
|
options?.semanticKey,
|
|
15176
15763
|
);
|
|
15764
|
+
const freshnessPolicy = this.usesExplicitFreshness
|
|
15765
|
+
? resolveExplicitDurableCallPolicy(options ?? {})
|
|
15766
|
+
: null;
|
|
15767
|
+
if (
|
|
15768
|
+
freshnessPolicy &&
|
|
15769
|
+
this.#options.cachePolicy?.forceStepRefresh === true
|
|
15770
|
+
) {
|
|
15771
|
+
freshnessPolicy.effective = 0;
|
|
15772
|
+
}
|
|
15773
|
+
if (
|
|
15774
|
+
freshnessPolicy &&
|
|
15775
|
+
freshnessPolicy.requested !== 0 &&
|
|
15776
|
+
!options?.semanticKey?.trim()
|
|
15777
|
+
) {
|
|
15778
|
+
throw new Error(
|
|
15779
|
+
'Reusable ctx.step results require a stable semanticKey.',
|
|
15780
|
+
);
|
|
15781
|
+
}
|
|
15177
15782
|
const normalizedKey = this.normalizeContextKey(key, 'step');
|
|
15178
15783
|
if (!normalizedKey.trim()) {
|
|
15179
15784
|
throw new Error('ctx.step(key, fn) requires a non-empty stable step id.');
|
|
@@ -15181,7 +15786,26 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15181
15786
|
|
|
15182
15787
|
const rowStore = rowContext.getStore();
|
|
15183
15788
|
const scope = rowStore ? `row-${rowStore.rowId}` : 'workflow';
|
|
15184
|
-
const
|
|
15789
|
+
const parentStep = authoredStepContext.getStore();
|
|
15790
|
+
const explicitStepScope = freshnessPolicy
|
|
15791
|
+
? stableDigest(
|
|
15792
|
+
stableStringify({
|
|
15793
|
+
semanticKey: options?.semanticKey ?? null,
|
|
15794
|
+
parent:
|
|
15795
|
+
parentStep?.context === this
|
|
15796
|
+
? (parentStep.invocationId ?? parentStep.stepId)
|
|
15797
|
+
: null,
|
|
15798
|
+
row: rowStore
|
|
15799
|
+
? {
|
|
15800
|
+
table: rowStore.tableNamespace,
|
|
15801
|
+
key: rowStore.rowKey ?? rowStore.rowId,
|
|
15802
|
+
field: rowStore.fieldName,
|
|
15803
|
+
}
|
|
15804
|
+
: null,
|
|
15805
|
+
}),
|
|
15806
|
+
)
|
|
15807
|
+
: null;
|
|
15808
|
+
const callIndexKey = `${this.currentExecutionScope.receipt.namespace}:${scope}:${normalizedKey}${explicitStepScope ? `:${explicitStepScope}` : ''}`;
|
|
15185
15809
|
const freshCallIndex = this.stepCallIndexByKey.get(callIndexKey) ?? 0;
|
|
15186
15810
|
const callIndex = nextReplayableCallIndex(
|
|
15187
15811
|
`step:${callIndexKey}`,
|
|
@@ -15193,7 +15817,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15193
15817
|
);
|
|
15194
15818
|
const boundarySuffix = callIndex === 0 ? '' : `:${callIndex}`;
|
|
15195
15819
|
const boundaryId = this.durableBoundaryId(
|
|
15196
|
-
`step-${scope}:${normalizedKey}${boundarySuffix}`,
|
|
15820
|
+
`step-${scope}:${normalizedKey}${explicitStepScope ? `:${explicitStepScope}` : ''}${boundarySuffix}`,
|
|
15197
15821
|
);
|
|
15198
15822
|
const advanceRecoveredSleepCursor = async (
|
|
15199
15823
|
receipt?: RuntimeStepReceipt,
|
|
@@ -15276,9 +15900,63 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15276
15900
|
});
|
|
15277
15901
|
try {
|
|
15278
15902
|
const output = await authoredStepContext.run(
|
|
15279
|
-
{
|
|
15280
|
-
|
|
15281
|
-
|
|
15903
|
+
{
|
|
15904
|
+
context: this,
|
|
15905
|
+
stepId: normalizedKey,
|
|
15906
|
+
...(freshnessPolicy ? { invocationId: boundaryId } : {}),
|
|
15907
|
+
},
|
|
15908
|
+
async () => {
|
|
15909
|
+
if (freshnessPolicy) {
|
|
15910
|
+
const semanticKey = stableStringify({
|
|
15911
|
+
operation: 'step',
|
|
15912
|
+
orgId: this.#options.orgId,
|
|
15913
|
+
namespace: this.currentExecutionScope.receipt.namespace,
|
|
15914
|
+
id: normalizedKey,
|
|
15915
|
+
semanticKey: options?.semanticKey,
|
|
15916
|
+
row: rowStore
|
|
15917
|
+
? {
|
|
15918
|
+
table: rowStore.tableNamespace,
|
|
15919
|
+
key: rowStore.rowKey ?? rowStore.rowId,
|
|
15920
|
+
field: rowStore.fieldName,
|
|
15921
|
+
}
|
|
15922
|
+
: null,
|
|
15923
|
+
});
|
|
15924
|
+
const completed = await this.executeFreshnessControlledCall({
|
|
15925
|
+
operation: 'step',
|
|
15926
|
+
id: normalizedKey,
|
|
15927
|
+
semanticKey,
|
|
15928
|
+
invocationId: boundaryId,
|
|
15929
|
+
policy: freshnessPolicy,
|
|
15930
|
+
completedAt: (value) => value.completedAt,
|
|
15931
|
+
execute: async () => {
|
|
15932
|
+
const before = this.sleepBoundaryIndex;
|
|
15933
|
+
const value = await executeStep();
|
|
15934
|
+
const boundary =
|
|
15935
|
+
this.checkpoint.resolvedBoundaries?.[boundaryId];
|
|
15936
|
+
return {
|
|
15937
|
+
output: value === undefined ? null : value,
|
|
15938
|
+
undefinedOutput: value === undefined,
|
|
15939
|
+
sleepCount: this.sleepBoundaryIndex - before,
|
|
15940
|
+
completedAt:
|
|
15941
|
+
(boundary?.kind === 'step'
|
|
15942
|
+
? boundary.completedAt
|
|
15943
|
+
: undefined) ?? Date.now(),
|
|
15944
|
+
};
|
|
15945
|
+
},
|
|
15946
|
+
onOutcome: (value, freshness) => {
|
|
15947
|
+
if (freshness.source !== 'executed') {
|
|
15948
|
+
this.sleepBoundaryIndex += value.sleepCount;
|
|
15949
|
+
}
|
|
15950
|
+
return value;
|
|
15951
|
+
},
|
|
15952
|
+
});
|
|
15953
|
+
const result = completed.undefinedOutput
|
|
15954
|
+
? undefined
|
|
15955
|
+
: completed.output;
|
|
15956
|
+
assertJsonSerializableStepOutput(normalizedKey, result);
|
|
15957
|
+
return result as T;
|
|
15958
|
+
}
|
|
15959
|
+
return await this.executeWithRuntimeReceipt<T>(
|
|
15282
15960
|
'step',
|
|
15283
15961
|
normalizedKey,
|
|
15284
15962
|
this.currentRunId,
|
|
@@ -15308,7 +15986,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
15308
15986
|
},
|
|
15309
15987
|
execute: executeStep,
|
|
15310
15988
|
},
|
|
15311
|
-
)
|
|
15989
|
+
);
|
|
15990
|
+
},
|
|
15312
15991
|
);
|
|
15313
15992
|
if (observe)
|
|
15314
15993
|
this.emitExecutionEvent({
|
|
@@ -17746,6 +18425,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
17746
18425
|
);
|
|
17747
18426
|
const shouldWaitAtExecutionBoundary =
|
|
17748
18427
|
shouldWaitForAsyncOperation && !shouldResolveAsyncOperationInRuntime;
|
|
18428
|
+
const asyncExecutionRecovery = shouldWaitAtExecutionBoundary
|
|
18429
|
+
? await this.#options.getToolAsyncExecutionRecovery?.(toolId)
|
|
18430
|
+
: null;
|
|
18431
|
+
if (
|
|
18432
|
+
asyncExecutionRecovery &&
|
|
18433
|
+
!isImmutableRuntimeExecutionRecoveryOrigin(asyncExecutionRecovery.origin)
|
|
18434
|
+
) {
|
|
18435
|
+
throw new Error(
|
|
18436
|
+
'Async execution recovery requires the serving app immutable origin. No provider request was sent.',
|
|
18437
|
+
);
|
|
18438
|
+
}
|
|
17749
18439
|
// An explicitly opted-in caller-managed launch gives the Play author the
|
|
17750
18440
|
// provider handle and leaves polling to the authored graph. Do not let a
|
|
17751
18441
|
// provider's legacy synchronous helper turn a wide start wave into
|
|
@@ -18106,6 +18796,70 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18106
18796
|
// map cells, batched owners) supply the fuller scope.
|
|
18107
18797
|
const playNodeScope =
|
|
18108
18798
|
options?.playNodeScope ?? buildPlayNodeScope({ toolId });
|
|
18799
|
+
const asyncRecoveryInvocationKey = asyncExecutionRecovery
|
|
18800
|
+
? stableDigest(
|
|
18801
|
+
stableStringify({
|
|
18802
|
+
toolId,
|
|
18803
|
+
// A queued call/chunk supplies its exact durable transport
|
|
18804
|
+
// identity. A direct invocation owns a fresh call identity.
|
|
18805
|
+
callIdentity:
|
|
18806
|
+
options?.durableCallReceiptKey ??
|
|
18807
|
+
(options?.providerOperationForceRefreshIdentities?.length
|
|
18808
|
+
? options.providerOperationForceRefreshIdentities
|
|
18809
|
+
: (options?.providerIdempotencyKey ??
|
|
18810
|
+
executionRequestNonce)),
|
|
18811
|
+
payload: toolInputSnapshot,
|
|
18812
|
+
authScope: executionAuthScopeDigest,
|
|
18813
|
+
playNodeScope,
|
|
18814
|
+
forceRefreshIdentities:
|
|
18815
|
+
options?.providerOperationForceRefreshIdentities ?? [],
|
|
18816
|
+
}),
|
|
18817
|
+
)
|
|
18818
|
+
: null;
|
|
18819
|
+
const asyncRecoveryPolling = asyncExecutionRecovery
|
|
18820
|
+
? resolveAsyncOperationPolling({
|
|
18821
|
+
contract: asyncContract!,
|
|
18822
|
+
startInput: toolInputSnapshot,
|
|
18823
|
+
playRuntimeWait: true,
|
|
18824
|
+
})
|
|
18825
|
+
: null;
|
|
18826
|
+
const asyncRecoveryDeadlineAtMs = asyncRecoveryPolling
|
|
18827
|
+
? Date.now() + asyncRecoveryPolling.timeoutMs
|
|
18828
|
+
: null;
|
|
18829
|
+
let confirmedAsyncRecoveryPending = false;
|
|
18830
|
+
let asyncRecoveryWaitTimeout: ReturnType<
|
|
18831
|
+
typeof createToolHttpError
|
|
18832
|
+
> | null = null;
|
|
18833
|
+
const asyncRecoveryWaitTimeoutError = () =>
|
|
18834
|
+
(asyncRecoveryWaitTimeout ??= createToolHttpError(
|
|
18835
|
+
toolErrorSchemaVersion,
|
|
18836
|
+
formatAsyncOperationTimeout({
|
|
18837
|
+
startAction: asyncContract!.lifecycle.startAction,
|
|
18838
|
+
jobId: null,
|
|
18839
|
+
timeoutDescription: `${asyncRecoveryPolling!.timeoutMs}ms`,
|
|
18840
|
+
}) +
|
|
18841
|
+
' The execution remains recoverable under its original idempotency key; it will not be relaunched.',
|
|
18842
|
+
null,
|
|
18843
|
+
504,
|
|
18844
|
+
'repairable',
|
|
18845
|
+
{
|
|
18846
|
+
toolId,
|
|
18847
|
+
provider,
|
|
18848
|
+
operation: toolId,
|
|
18849
|
+
code: 'ASYNC_OPERATION_TIMEOUT',
|
|
18850
|
+
origin: 'provider',
|
|
18851
|
+
category: 'upstream',
|
|
18852
|
+
retryable: false,
|
|
18853
|
+
statusCode: 504,
|
|
18854
|
+
},
|
|
18855
|
+
));
|
|
18856
|
+
const asyncRecoveryKey = (attempt: number) =>
|
|
18857
|
+
asyncRecoveryInvocationKey
|
|
18858
|
+
? buildRuntimeExecutionRecoveryKey({
|
|
18859
|
+
parentRunId: this.currentRunId,
|
|
18860
|
+
invocationKey: `${asyncRecoveryInvocationKey}:attempt:${attempt}`,
|
|
18861
|
+
})
|
|
18862
|
+
: null;
|
|
18109
18863
|
// This is deliberately a claim about only the immediately preceding
|
|
18110
18864
|
// *durable* provider attempt. The execution gateway validates and
|
|
18111
18865
|
// attests it before a retried call can use its fresh charge
|
|
@@ -18361,6 +19115,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18361
19115
|
transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS &&
|
|
18362
19116
|
(input.knownNotDispatched === true ||
|
|
18363
19117
|
usesDurableInvocationFence ||
|
|
19118
|
+
asyncExecutionRecovery ||
|
|
18364
19119
|
ambiguousTransportReplayAllowed ||
|
|
18365
19120
|
fixtureReplaySafe)
|
|
18366
19121
|
) {
|
|
@@ -18368,7 +19123,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18368
19123
|
// attempt and any prior provider-429 proof belong to the logical
|
|
18369
19124
|
// invocation; only a definite retryable HTTP outcome advances it.
|
|
18370
19125
|
// Ordinary transport retries retain their physical attempt count.
|
|
18371
|
-
if (!usesDurableInvocationFence) {
|
|
19126
|
+
if (!usesDurableInvocationFence && !asyncExecutionRecovery) {
|
|
18372
19127
|
providerRateLimitRetryFromInvocationAttempt = null;
|
|
18373
19128
|
invocationAttempt += 1;
|
|
18374
19129
|
}
|
|
@@ -18454,6 +19209,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18454
19209
|
|
|
18455
19210
|
while (true) {
|
|
18456
19211
|
toolAbortSignal?.throwIfAborted();
|
|
19212
|
+
if (
|
|
19213
|
+
confirmedAsyncRecoveryPending &&
|
|
19214
|
+
Date.now() >= asyncRecoveryDeadlineAtMs!
|
|
19215
|
+
) {
|
|
19216
|
+
throw asyncRecoveryWaitTimeoutError();
|
|
19217
|
+
}
|
|
18457
19218
|
if (
|
|
18458
19219
|
providerNoProgressExpired({
|
|
18459
19220
|
startedAtMsByScope:
|
|
@@ -18806,6 +19567,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18806
19567
|
// A stale permit can take arbitrarily long to
|
|
18807
19568
|
// replace. Only fixture residence and the physical
|
|
18808
19569
|
// HTTP exchange spend the tool execution deadline.
|
|
19570
|
+
if (
|
|
19571
|
+
confirmedAsyncRecoveryPending &&
|
|
19572
|
+
Date.now() >= asyncRecoveryDeadlineAtMs!
|
|
19573
|
+
) {
|
|
19574
|
+
throw asyncRecoveryWaitTimeoutError();
|
|
19575
|
+
}
|
|
18809
19576
|
armProviderTimeout();
|
|
18810
19577
|
beginProviderExecution();
|
|
18811
19578
|
const fetchedAt = Date.now();
|
|
@@ -18865,6 +19632,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
18865
19632
|
}
|
|
18866
19633
|
: {}),
|
|
18867
19634
|
'x-deepline-request-id': deeplineRequestId,
|
|
19635
|
+
...(asyncRecoveryKey(invocationAttempt)
|
|
19636
|
+
? {
|
|
19637
|
+
'idempotency-key':
|
|
19638
|
+
asyncRecoveryKey(invocationAttempt)!,
|
|
19639
|
+
}
|
|
19640
|
+
: {}),
|
|
18868
19641
|
...(providerExecutionFenceKey
|
|
18869
19642
|
? {
|
|
18870
19643
|
'x-deepline-idempotency-key':
|
|
@@ -19247,6 +20020,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19247
20020
|
receiptHeartbeatSupervisor?.stop();
|
|
19248
20021
|
}
|
|
19249
20022
|
} catch (error) {
|
|
20023
|
+
if (
|
|
20024
|
+
asyncRecoveryWaitTimeout !== null &&
|
|
20025
|
+
error === asyncRecoveryWaitTimeout
|
|
20026
|
+
) {
|
|
20027
|
+
throw error;
|
|
20028
|
+
}
|
|
19250
20029
|
if (heartbeatFailure) {
|
|
19251
20030
|
throw heartbeatFailure;
|
|
19252
20031
|
}
|
|
@@ -19366,6 +20145,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19366
20145
|
const transportReplaySafe =
|
|
19367
20146
|
!ambiguousDispatchedFailure ||
|
|
19368
20147
|
usesDurableInvocationFence ||
|
|
20148
|
+
Boolean(asyncExecutionRecovery) ||
|
|
19369
20149
|
ambiguousTransportReplayAllowed ||
|
|
19370
20150
|
fixtureReplaySafe;
|
|
19371
20151
|
if (!transportReplaySafe) {
|
|
@@ -19444,8 +20224,62 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19444
20224
|
continue;
|
|
19445
20225
|
}
|
|
19446
20226
|
|
|
20227
|
+
if (
|
|
20228
|
+
asyncExecutionRecovery &&
|
|
20229
|
+
response.ok &&
|
|
20230
|
+
response.headers.get(
|
|
20231
|
+
RUNTIME_EXECUTION_RECOVERY_SUPPORTED_HEADER,
|
|
20232
|
+
) !== 'true'
|
|
20233
|
+
) {
|
|
20234
|
+
throw createToolHttpError(
|
|
20235
|
+
toolErrorSchemaVersion,
|
|
20236
|
+
`Tool ${toolId} did not confirm the declared async recovery reader. The execution will not be relaunched.`,
|
|
20237
|
+
null,
|
|
20238
|
+
response.status,
|
|
20239
|
+
'repairable',
|
|
20240
|
+
);
|
|
20241
|
+
}
|
|
20242
|
+
|
|
19447
20243
|
if (!response.ok) {
|
|
19448
20244
|
const text = responseErrorText ?? '';
|
|
20245
|
+
const recoveryKey = asyncRecoveryKey(invocationAttempt);
|
|
20246
|
+
if (
|
|
20247
|
+
recoveryKey &&
|
|
20248
|
+
response.status === 409 &&
|
|
20249
|
+
response.headers.get(
|
|
20250
|
+
RUNTIME_EXECUTION_RECOVERY_SUPPORTED_HEADER,
|
|
20251
|
+
) === 'true'
|
|
20252
|
+
) {
|
|
20253
|
+
const pending = (() => {
|
|
20254
|
+
try {
|
|
20255
|
+
return JSON.parse(text) as Record<string, unknown>;
|
|
20256
|
+
} catch {
|
|
20257
|
+
return null;
|
|
20258
|
+
}
|
|
20259
|
+
})();
|
|
20260
|
+
const recovery = pending?.executionRecovery as
|
|
20261
|
+
| Record<string, unknown>
|
|
20262
|
+
| undefined;
|
|
20263
|
+
if (
|
|
20264
|
+
pending?.code === 'EXECUTION_IN_PROGRESS' &&
|
|
20265
|
+
pending.retryable === true &&
|
|
20266
|
+
recovery?.idempotencyKey === recoveryKey &&
|
|
20267
|
+
(recovery.state === 'running' ||
|
|
20268
|
+
recovery.state === 'completed')
|
|
20269
|
+
) {
|
|
20270
|
+
confirmedAsyncRecoveryPending = true;
|
|
20271
|
+
if (Date.now() >= asyncRecoveryDeadlineAtMs!) {
|
|
20272
|
+
throw asyncRecoveryWaitTimeoutError();
|
|
20273
|
+
}
|
|
20274
|
+
// This is continuation of already accepted work. Keep the
|
|
20275
|
+
// original body/key and let the route attach its saved job.
|
|
20276
|
+
await waitForToolRetry(
|
|
20277
|
+
Math.min(1_000, asyncRecoveryDeadlineAtMs! - Date.now()),
|
|
20278
|
+
false,
|
|
20279
|
+
);
|
|
20280
|
+
continue;
|
|
20281
|
+
}
|
|
20282
|
+
}
|
|
19449
20283
|
const developerTunnelOrigin502 =
|
|
19450
20284
|
isDeeplineDeveloperTunnelOrigin502({
|
|
19451
20285
|
url,
|
|
@@ -19487,7 +20321,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19487
20321
|
status: response.status,
|
|
19488
20322
|
bodyText: text,
|
|
19489
20323
|
transientHttpRetrySafe:
|
|
19490
|
-
retrySafeTransientHttp ||
|
|
20324
|
+
retrySafeTransientHttp ||
|
|
20325
|
+
fixtureReplaySafe ||
|
|
20326
|
+
Boolean(asyncExecutionRecovery),
|
|
19491
20327
|
executionFenced: usesDurableInvocationFence,
|
|
19492
20328
|
});
|
|
19493
20329
|
const failure =
|
|
@@ -19501,7 +20337,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19501
20337
|
retryAfterHeader: response.headers.get('retry-after'),
|
|
19502
20338
|
retryAtMs: parseToolExecuteExactProviderRetryAtMs(response),
|
|
19503
20339
|
transientHttpRetrySafe:
|
|
19504
|
-
retrySafeTransientHttp ||
|
|
20340
|
+
retrySafeTransientHttp ||
|
|
20341
|
+
fixtureReplaySafe ||
|
|
20342
|
+
Boolean(asyncExecutionRecovery),
|
|
19505
20343
|
executionFenced: usesDurableInvocationFence,
|
|
19506
20344
|
...(providerCallElapsedMs !== null
|
|
19507
20345
|
? { providerLatencyMs: providerCallElapsedMs }
|
|
@@ -19643,6 +20481,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19643
20481
|
(failure.knownNotDispatched ||
|
|
19644
20482
|
failure.reason === 'gateway_invocation_in_progress' ||
|
|
19645
20483
|
ambiguousTransportReplayAllowed ||
|
|
20484
|
+
Boolean(asyncExecutionRecovery) ||
|
|
19646
20485
|
retrySafeTransientHttp ||
|
|
19647
20486
|
fixtureReplaySafe ||
|
|
19648
20487
|
// An explicit provider-owned 429 is a rejected operation,
|
|
@@ -19659,7 +20498,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19659
20498
|
// prior-attempt sweep see an intentionally absent receipt.
|
|
19660
20499
|
if (
|
|
19661
20500
|
failure.reason !== 'gateway_invocation_in_progress' &&
|
|
19662
|
-
!shedKnownNotDispatched
|
|
20501
|
+
!shedKnownNotDispatched &&
|
|
20502
|
+
(!asyncExecutionRecovery ||
|
|
20503
|
+
failure.backpressureDelayMs !== null)
|
|
19663
20504
|
) {
|
|
19664
20505
|
const previousInvocationAttempt = invocationAttempt;
|
|
19665
20506
|
if (failure.backpressureDelayMs !== null) {
|