deepline 0.3.392 → 0.3.393

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.
Files changed (145) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +156 -24
  2. package/dist/bundling-sources/sdk/src/compat.ts +9 -0
  3. package/dist/bundling-sources/sdk/src/http.ts +5 -1
  4. package/dist/bundling-sources/sdk/src/index.ts +5 -0
  5. package/dist/bundling-sources/sdk/src/plays/run-request.ts +182 -6
  6. package/dist/bundling-sources/sdk/src/release.ts +4 -1
  7. package/dist/bundling-sources/sdk/src/types.ts +13 -0
  8. package/dist/bundling-sources/shared_libs/integration-contracts/request-warnings.ts +64 -0
  9. package/dist/bundling-sources/shared_libs/play-data-plane/runtime-sheet-storage-contract.ts +315 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +41 -8
  11. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +418 -44
  12. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +36 -3
  13. package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
  15. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +19 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +158 -10
  19. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +235 -2
  20. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
  21. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
  25. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
  26. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +31 -0
  27. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +37 -4
  28. package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
  29. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
  30. package/dist/cli/{auth-6JSA2ZCW.mjs → auth-5PXHA232.mjs} +4 -4
  31. package/dist/cli/auth-P3MVDTXM.js +39 -0
  32. package/dist/cli/{billing-Q5BLQHIU.js → billing-JFB2XLSO.js} +101 -74
  33. package/dist/cli/{billing-MRRO643H.mjs → billing-Z2N7ZAGM.mjs} +32 -5
  34. package/dist/cli/{chunk-CVLM3WMJ.js → chunk-2HAD745V.js} +242 -183
  35. package/dist/cli/{chunk-HGYBEAQP.mjs → chunk-2LGC4EFT.mjs} +3 -3
  36. package/dist/cli/{chunk-A3APGZFP.mjs → chunk-4PT2VA3A.mjs} +2 -2
  37. package/dist/cli/{chunk-6DBOU7UH.js → chunk-4XX6443T.js} +176 -87
  38. package/dist/cli/{chunk-2QLML6G4.mjs → chunk-5KWKZIQR.mjs} +4 -4
  39. package/dist/cli/{chunk-2G7DXFHI.mjs → chunk-5PRVMXKB.mjs} +27 -2
  40. package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-6I73X254.mjs} +1 -1
  41. package/dist/cli/{chunk-ZTACINO5.mjs → chunk-7HR7BGZL.mjs} +13 -3
  42. package/dist/cli/{chunk-BB3FNYFY.js → chunk-AARX5L4B.js} +2 -2
  43. package/dist/cli/{chunk-WMS5U6YB.js → chunk-CDFH2DYL.js} +20 -19
  44. package/dist/cli/{chunk-R3O2CZ4H.mjs → chunk-FQ6VHPW4.mjs} +2 -2
  45. package/dist/cli/{chunk-MU6M3URX.mjs → chunk-HLEESTCH.mjs} +1 -1
  46. package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
  47. package/dist/cli/{chunk-2Q3GEFL4.mjs → chunk-KIYU2TGL.mjs} +2 -2
  48. package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
  49. package/dist/cli/{chunk-UCKJQG25.js → chunk-L2SYLGRP.js} +8 -8
  50. package/dist/cli/{chunk-2GAHHSLB.mjs → chunk-LMMEAMA7.mjs} +3 -2
  51. package/dist/cli/{chunk-YAZCDBXX.mjs → chunk-LZPCDB7U.mjs} +3 -2
  52. package/dist/cli/{chunk-RV6ALZIX.js → chunk-M7Y7JYZX.js} +32 -7
  53. package/dist/cli/{chunk-QGKPTBRR.js → chunk-MS6GUPBB.js} +3 -3
  54. package/dist/cli/{chunk-4SFPVW7A.js → chunk-OXNOAD6Y.js} +26 -26
  55. package/dist/cli/{chunk-JOFPQGXW.js → chunk-PTDECJGP.js} +50 -50
  56. package/dist/cli/{chunk-5O4B6AUN.js → chunk-PWSWAN7Q.js} +25 -2
  57. package/dist/cli/{chunk-YN6YA7ZZ.mjs → chunk-Q26LUSQ2.mjs} +79 -20
  58. package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
  59. package/dist/cli/{chunk-7E66A24V.mjs → chunk-RKMB3N7K.mjs} +25 -2
  60. package/dist/cli/{chunk-JSLPHFP7.js → chunk-RWHWF5ZJ.js} +3 -2
  61. package/dist/cli/{chunk-3FV4GBRK.mjs → chunk-STNYIMIV.mjs} +97 -8
  62. package/dist/cli/chunk-UBKA33KY.mjs +310 -0
  63. package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
  64. package/dist/cli/{chunk-24VEN5PQ.mjs → chunk-UOGZUA24.mjs} +179 -17
  65. package/dist/cli/{chunk-T7OKJ2T6.js → chunk-VIQJIX2P.js} +73 -63
  66. package/dist/cli/{chunk-RIV2GS5E.js → chunk-VIWE6WRK.js} +627 -465
  67. package/dist/cli/{chunk-EY7RFC6A.js → chunk-W4WOMXR3.js} +15 -15
  68. package/dist/cli/chunk-XDVT3GOM.js +310 -0
  69. package/dist/cli/{chunk-2FQG2HU4.js → chunk-Z3OOZM5L.js} +8 -8
  70. package/dist/cli/{csv-E4QGUIGI.mjs → csv-KJWZHKCH.mjs} +3 -3
  71. package/dist/cli/{csv-26U5T24Z.js → csv-P3Z3YE7H.js} +12 -12
  72. package/dist/cli/{db-JTRJIAQW.mjs → db-NCM4CQIS.mjs} +6 -5
  73. package/dist/cli/{db-QHBFLMVL.js → db-NUZGJNQC.js} +31 -30
  74. package/dist/cli/{describe-E3DE3ZK3.mjs → describe-WT6MM2GO.mjs} +17 -16
  75. package/dist/cli/{describe-FDV2V74F.js → describe-YGSEN5WC.js} +24 -23
  76. package/dist/cli/{executions-VZYOGVQY.js → executions-PCOLHN7Q.js} +8 -7
  77. package/dist/cli/{executions-B3GTNPRE.mjs → executions-XS2IPJ4W.mjs} +6 -5
  78. package/dist/cli/{feedback-CR55FJA2.js → feedback-4MSH7TFU.js} +7 -7
  79. package/dist/cli/{feedback-4CEBR3OV.mjs → feedback-CCLTS5SV.mjs} +3 -3
  80. package/dist/cli/{git-NM2PHHZ4.mjs → git-K2DHPMPC.mjs} +4 -4
  81. package/dist/cli/git-RNM45FX4.js +23 -0
  82. package/dist/cli/index.js +96 -92
  83. package/dist/cli/index.mjs +46 -42
  84. package/dist/cli/{legacy-noops-A7MSW5ND.mjs → legacy-noops-B5S4AR3P.mjs} +4 -4
  85. package/dist/cli/{legacy-noops-ZUA4YFIH.js → legacy-noops-GPNOPFXV.js} +7 -7
  86. package/dist/cli/{monitors-NVPRQYP3.mjs → monitors-JLMAIPRG.mjs} +6 -5
  87. package/dist/cli/{monitors-ZE3TLZ4H.js → monitors-ZAWD7NSN.js} +87 -86
  88. package/dist/cli/{org-U7A6DV5D.js → org-P2KN4CES.js} +66 -65
  89. package/dist/cli/{org-KUBILNQV.mjs → org-T3JTHP4G.mjs} +6 -5
  90. package/dist/cli/{play-HRDXQ4QE.mjs → play-NK6BLRGL.mjs} +11 -10
  91. package/dist/cli/play-PDXR6JFB.js +158 -0
  92. package/dist/cli/{providers-OHIWI453.mjs → providers-3NQRA7IT.mjs} +6 -5
  93. package/dist/cli/{providers-VD6YTVQZ.js → providers-5QRZN3DT.js} +9 -8
  94. package/dist/cli/{quickstart-UBV2FONF.mjs → quickstart-PIA2JGPV.mjs} +3 -3
  95. package/dist/cli/{quickstart-ETTCWPA4.js → quickstart-R7NUZGNW.js} +5 -5
  96. package/dist/cli/run-identity-G257GQED.js +25 -0
  97. package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
  98. package/dist/cli/{search-F44IZRT7.mjs → search-AIDIIGLY.mjs} +7 -6
  99. package/dist/cli/search-QRWL7MFM.js +27 -0
  100. package/dist/cli/{secrets-CAEMRORF.js → secrets-BT3PGMWN.js} +15 -14
  101. package/dist/cli/{secrets-R6XOJZKU.mjs → secrets-OQNHL45O.mjs} +6 -5
  102. package/dist/cli/{sessions-6GC7OFTT.js → sessions-EYXWOSWK.js} +5 -5
  103. package/dist/cli/{sessions-RLN6UQS3.mjs → sessions-SF7WJPJP.mjs} +4 -4
  104. package/dist/cli/{settings-DPNVUAV2.js → settings-BA3GHBOD.js} +76 -75
  105. package/dist/cli/{settings-KIPAMRPP.mjs → settings-Y57JTFDD.mjs} +9 -8
  106. package/dist/cli/{setup-TU2F7PU5.mjs → setup-7GR5GBHA.mjs} +6 -6
  107. package/dist/cli/{setup-DTL3L5SG.js → setup-RERXYC66.js} +41 -41
  108. package/dist/cli/{skills-UV5MYJ46.mjs → skills-SJWIQAV5.mjs} +4 -4
  109. package/dist/cli/skills-V7EY75HB.js +28 -0
  110. package/dist/cli/tools-H5HLBCHH.js +41 -0
  111. package/dist/cli/{tools-QRRNDCQ7.mjs → tools-ZF3JXPNL.mjs} +10 -9
  112. package/dist/cli/update-472HA3LT.js +28 -0
  113. package/dist/cli/{update-4426TF35.mjs → update-FFFYQAOJ.mjs} +6 -6
  114. package/dist/cli/{workflow-HNLOVISB.js → workflow-SZJVQH4T.js} +17 -16
  115. package/dist/cli/{workflow-N4U5VLZ3.mjs → workflow-XVFEEGAS.mjs} +7 -6
  116. package/dist/{client-e6Qh5ZIg.d.mts → client-DU5dtHDy.d.mts} +153 -4
  117. package/dist/{client-Uxuy-xQ2.d.ts → client-mCE88hC4.d.ts} +153 -4
  118. package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DE4RdAiY.d.mts} +61 -1
  119. package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DE4RdAiY.d.ts} +61 -1
  120. package/dist/{index-B9fBZH1g.d.mts → index-BI6nWgaV.d.mts} +1 -1
  121. package/dist/{index-BZ6uzZJl.d.ts → index-Bi-f7HFT.d.ts} +1 -1
  122. package/dist/index.d.mts +5 -5
  123. package/dist/index.d.ts +5 -5
  124. package/dist/index.js +120 -29
  125. package/dist/index.mjs +111 -20
  126. package/dist/install-integrity.json +91 -86
  127. package/dist/plays/bundle-play-file.d.mts +3 -3
  128. package/dist/plays/bundle-play-file.d.ts +3 -3
  129. package/dist/plays/bundle-play-file.mjs +2 -2
  130. package/dist/plays/run-request.d.mts +2 -2
  131. package/dist/plays/run-request.d.ts +2 -2
  132. package/dist/plays/run-request.js +5 -2
  133. package/dist/plays/run-request.mjs +4 -1
  134. package/dist/release.d.mts +4 -1
  135. package/dist/release.d.ts +4 -1
  136. package/dist/release.js +2 -2
  137. package/dist/release.mjs +1 -1
  138. package/package.json +1 -1
  139. package/dist/cli/auth-67TAELQL.js +0 -39
  140. package/dist/cli/git-I5JTK6YU.js +0 -23
  141. package/dist/cli/play-A2V7YDU7.js +0 -157
  142. package/dist/cli/search-WWXMBVPH.js +0 -26
  143. package/dist/cli/skills-SWOSEZLB.js +0 -28
  144. package/dist/cli/tools-IL2YCDQ6.js +0 -40
  145. 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 { stringifyPostgresJson } from './postgres-json';
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,
@@ -304,9 +315,11 @@ import {
304
315
  classifyAsyncOperationResult,
305
316
  executeAsyncOperationLifecycle,
306
317
  extractAsyncOperationJobId,
318
+ formatAsyncOperationTimeout,
307
319
  readAsyncOperationPath,
308
320
  asyncOperationShouldWait,
309
321
  hasExplicitAsyncOperationTerminalMatch,
322
+ resolveAsyncOperationPolling,
310
323
  type AsyncOperationContractWire,
311
324
  } from './async-operation';
312
325
  import {
@@ -8206,6 +8219,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8206
8219
  options.onRowError,
8207
8220
  );
8208
8221
  validateOptionalPlayAuthoringField('ctx.dataset.run.mode', options.mode);
8222
+ validateOptionalPlayAuthoringField(
8223
+ 'ctx.dataset.run.rowIdentity',
8224
+ options.rowIdentity,
8225
+ );
8209
8226
  validateOptionalPlayAuthoringField(
8210
8227
  'ctx.dataset.run.undrawnColumns',
8211
8228
  options.undrawnColumns,
@@ -8260,11 +8277,53 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8260
8277
  ),
8261
8278
  );
8262
8279
 
8280
+ const residentSourceRows = isPlayDataset(items)
8281
+ ? residentPlayDatasetRows(items)
8282
+ : Array.isArray(items)
8283
+ ? items
8284
+ : null;
8285
+ const csvSource =
8286
+ (isPlayDataset(items) && items.datasetKind === 'csv') ||
8287
+ residentSourceRows?.some(
8288
+ (_row, index) =>
8289
+ getPlayDatasetSourceRowIdentityAt(residentSourceRows, index) !==
8290
+ undefined,
8291
+ ) === true;
8292
+ const csvOccurrenceIdentityRequired =
8293
+ csvSource &&
8294
+ this.currentRuntimeSemantics.datasets.csvRowIdentity === 'source_row';
8295
+ const rowIdentityMode = csvOccurrenceIdentityRequired
8296
+ ? 'source_row'
8297
+ : (options?.rowIdentity ??
8298
+ (options?.key !== undefined
8299
+ ? 'key'
8300
+ : csvSource
8301
+ ? this.currentRuntimeSemantics.datasets.csvRowIdentity
8302
+ : 'key'));
8303
+ const useSourceRowIdentity = rowIdentityMode === 'source_row';
8263
8304
  const userKeyOption = options?.key;
8264
8305
  let explicitKeyResolver:
8265
8306
  | ((row: Record<string, unknown>, index: number) => string)
8266
8307
  | null = null;
8267
- if (userKeyOption !== undefined) {
8308
+ if (useSourceRowIdentity) {
8309
+ explicitKeyResolver = (row, index) => {
8310
+ const source = getPlayDatasetSourceRowIdentity(row);
8311
+ const inputRow = Object.fromEntries(
8312
+ Object.entries(toSerializableCsvAliasedRow(row)).filter(
8313
+ ([field]) => !field.startsWith('__deepline'),
8314
+ ),
8315
+ );
8316
+ // Position distinguishes equal records; the input digest prevents a
8317
+ // changed file at the same path from replaying an earlier row's output.
8318
+ return JSON.stringify([
8319
+ 'source-row-v1',
8320
+ source?.datasetId ??
8321
+ (isPlayDataset(items) ? items.datasetId : normalizedMapNamespace),
8322
+ source?.rowIndex ?? index,
8323
+ stableDigest(stableStringify(inputRow)),
8324
+ ]);
8325
+ };
8326
+ } else if (userKeyOption !== undefined) {
8268
8327
  explicitKeyResolver = (row, index) => {
8269
8328
  const inputRow = stripFieldOutputs(row);
8270
8329
  const raw =
@@ -8469,6 +8528,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8469
8528
  let failedCount = 0;
8470
8529
  let duplicateReuseCount = 0;
8471
8530
  let executedCount = 0;
8531
+ let sourceRowsCount = 0;
8472
8532
  const failureSamples: Array<{ rowLabel: string; error: string }> = [];
8473
8533
  const failureSampleErrors = new Set<string>();
8474
8534
  const previewRows: Record<string, unknown>[] = [];
@@ -8572,9 +8632,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8572
8632
  offset: number;
8573
8633
  }): Promise<PreparedPage> => {
8574
8634
  this.assertRowAdmissionOpen();
8575
- const pageRawItems = page.rows.map((item) =>
8576
- this.toOutputRow(item as Record<string, unknown>),
8577
- );
8635
+ sourceRowsCount += page.rows.length;
8636
+ const pageRawItems = page.rows.map((item, index) => {
8637
+ const row = this.toOutputRow(item as Record<string, unknown>);
8638
+ const sourceIdentity = getPlayDatasetSourceRowIdentityAt(
8639
+ page.rows,
8640
+ index,
8641
+ );
8642
+ return sourceIdentity
8643
+ ? setPlayDatasetSourceRowIdentity(row, sourceIdentity)
8644
+ : row;
8645
+ });
8578
8646
  const pageMemoryEstimate = estimateRuntimeMapRowsMemory(pageRawItems);
8579
8647
  if (pageMemoryEstimate.estimatedResidentBytes > pageBudgetBytes) {
8580
8648
  throw new RuntimeMapMemoryLimitError({
@@ -8588,6 +8656,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8588
8656
  const originalIndex = page.offset + index;
8589
8657
  return {
8590
8658
  ...toSerializableCsvAliasedRow(row),
8659
+ ...(getPlayDatasetSourceRowIdentity(row)
8660
+ ? { __deeplineSourceRow: getPlayDatasetSourceRowIdentity(row) }
8661
+ : {}),
8591
8662
  ...mapRowOutcomeRuntimeFields({
8592
8663
  key: seedRowIdentity(row, originalIndex),
8593
8664
  inputIndex: originalIndex,
@@ -8630,9 +8701,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8630
8701
  completedRowsByKey.set(rowKey, row);
8631
8702
  }
8632
8703
 
8633
- const seededItems = page.rows.map((item, index) => {
8704
+ const seededItems = pageRawItems.map((row, index) => {
8634
8705
  const originalIndex = page.offset + index;
8635
- const row = this.toOutputRow(item as Record<string, unknown>);
8636
8706
  const rowKey = seedRowIdentity(row, originalIndex);
8637
8707
  const persisted =
8638
8708
  pendingRowsByKey.get(rowKey) ?? completedRowsByKey.get(rowKey);
@@ -8661,7 +8731,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8661
8731
  { row: Record<string, unknown>; originalIndex: number }
8662
8732
  >();
8663
8733
  for (const { row, index } of admittedSeededItems) {
8664
- if (explicitKeyResolver) {
8734
+ if (explicitKeyResolver && !useSourceRowIdentity) {
8665
8735
  const explicitKey = explicitKeyResolver(row, index);
8666
8736
  if (seenExplicitKeys.has(explicitKey)) {
8667
8737
  duplicateReuseCount += 1;
@@ -8990,12 +9060,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8990
9060
  );
8991
9061
  successfulCount = visibleResults.completedRows;
8992
9062
  failedCount = visibleResults.failedRows;
8993
- this.#options.onMapRowsSettled?.({
9063
+ await this.#options.onMapRowsSettled?.({
8994
9064
  playName: this.#options.playName,
8995
9065
  runId: this.#options.runId,
8996
9066
  tableNamespace: resolvedTableNamespace,
8997
9067
  succeededRows: successfulCount,
8998
9068
  failedRows: failedCount,
9069
+ expectedRows: totalInputCount - duplicateReuseCount,
9070
+ staticPipeline: this.currentStaticPipeline,
8999
9071
  });
9000
9072
 
9001
9073
  // Isolated failures are a partial result only when at least one admitted
@@ -9181,6 +9253,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9181
9253
  skipped: duplicateReuseCount,
9182
9254
  pending: 0,
9183
9255
  failed: failedCount,
9256
+ cardinality: {
9257
+ sourceRows: sourceRowsCount,
9258
+ uniqueInputRows: processedCount - duplicateReuseCount,
9259
+ duplicateOccurrences: duplicateReuseCount,
9260
+ outputRows: successfulCount,
9261
+ rowIdentity: rowIdentityMode,
9262
+ },
9184
9263
  ...(duplicateReuseCount > 0
9185
9264
  ? { duplicates: { exact: duplicateReuseCount } }
9186
9265
  : {}),
@@ -9228,24 +9307,66 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9228
9307
  },
9229
9308
  });
9230
9309
  }
9231
- const rawMaterializedEntries =
9310
+ const rawMaterializedEntries: {
9311
+ item: T;
9312
+ inputIndex: number;
9313
+ sourceIdentity?: ReturnType<typeof getPlayDatasetSourceRowIdentityAt>;
9314
+ }[] =
9232
9315
  resumedPendingRowsOnly && suspendedInputRows
9233
- ? suspendedInputRows.map((row) => ({
9234
- item: cloneCsvAliasedRow(row.data) as T,
9235
- inputIndex: row.inputIndex,
9236
- }))
9237
- : (
9238
- await materializePlayDatasetInput(items, {
9316
+ ? suspendedInputRows.map((row) => {
9317
+ const retainedKey = resolveMapRowOutcomeKey(row.data);
9318
+ return {
9319
+ // Older checkpoints contain only execution data. New checkpoints
9320
+ // also retain the original admission input before sheet outputs
9321
+ // were restored; keep the already-admitted key across either path.
9322
+ item: cloneCsvAliasedRow(
9323
+ row.authoredInput?.data ?? row.data,
9324
+ retainedKey
9325
+ ? mapRowOutcomeRuntimeFields({ key: retainedKey })
9326
+ : {},
9327
+ ) as T,
9328
+ inputIndex: row.inputIndex,
9329
+ sourceIdentity: row.authoredInput?.source,
9330
+ };
9331
+ })
9332
+ : await (async () => {
9333
+ const materialized = await materializePlayDatasetInput(items, {
9239
9334
  onRow: (row) => {
9240
9335
  materializationMemoryTracker.track(row);
9241
9336
  },
9242
- })
9243
- ).map((item, inputIndex) => ({ item, inputIndex }));
9337
+ });
9338
+ return materialized.map((item, inputIndex) => ({
9339
+ item,
9340
+ inputIndex,
9341
+ sourceIdentity: getPlayDatasetSourceRowIdentityAt(
9342
+ materialized,
9343
+ inputIndex,
9344
+ ),
9345
+ }));
9346
+ })();
9244
9347
  const rawMaterializedItems = rawMaterializedEntries.map(
9245
- (entry) => entry.item,
9348
+ // Occurrence identity also applies to an explicitly selected array whose
9349
+ // author reused the same object reference. Give each occurrence its own
9350
+ // execution object so the original-index lookup cannot alias the last row.
9351
+ (entry) => {
9352
+ const sourceIdentity =
9353
+ 'sourceIdentity' in entry
9354
+ ? entry.sourceIdentity
9355
+ : getPlayDatasetSourceRowIdentity(entry.item);
9356
+ if (!useSourceRowIdentity && !sourceIdentity) return entry.item;
9357
+ const row = this.toOutputRow(entry.item);
9358
+ return (
9359
+ sourceIdentity
9360
+ ? setPlayDatasetSourceRowIdentity(row, sourceIdentity)
9361
+ : row
9362
+ ) as T;
9363
+ },
9246
9364
  );
9247
9365
  const originalInputIndexByItem = new Map<T, number>(
9248
- rawMaterializedEntries.map(({ item, inputIndex }) => [item, inputIndex]),
9366
+ rawMaterializedItems.map((item, index) => [
9367
+ item,
9368
+ rawMaterializedEntries[index]!.inputIndex,
9369
+ ]),
9249
9370
  );
9250
9371
  // Silent dedupe of duplicate explicit map keys: keep the first row per
9251
9372
  // canonical key and drop subsequent duplicates. Deduping the materialized
@@ -9253,24 +9374,75 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9253
9374
  // counts, sheet writes, resumable keys) consistent — the resolver derives
9254
9375
  // keys from row content, so dropped rows are excluded everywhere with no
9255
9376
  // index mismatch. Replaces the prior throw-on-duplicate behavior.
9256
- const dedupedMaterialized = explicitKeyResolver
9257
- ? dedupeExplicitMapKeyRows({
9258
- rows: rawMaterializedItems,
9259
- resolver: (item, index) =>
9260
- explicitKeyResolver(
9261
- this.toOutputRow(item as Record<string, unknown>),
9262
- originalInputIndexByItem.get(item) ?? index,
9263
- ),
9264
- })
9265
- : { rows: rawMaterializedItems, droppedCount: 0, duplicateKeys: [] };
9377
+ const dedupedMaterialized =
9378
+ explicitKeyResolver && !useSourceRowIdentity
9379
+ ? dedupeExplicitMapKeyRows({
9380
+ rows: rawMaterializedItems,
9381
+ resolver: (item, index) =>
9382
+ explicitKeyResolver(
9383
+ this.toOutputRow(item as Record<string, unknown>),
9384
+ originalInputIndexByItem.get(item) ?? index,
9385
+ ),
9386
+ })
9387
+ : { rows: rawMaterializedItems, droppedCount: 0, duplicateKeys: [] };
9266
9388
  if (dedupedMaterialized.droppedCount > 0) {
9267
- const keySample = dedupedMaterialized.duplicateKeys.join(', ');
9268
9389
  this.runtimeLog(
9269
- `deduped ${dedupedMaterialized.droppedCount} duplicate dataset key(s) for ctx.dataset("${normalizedMapNamespace}"); keeping first occurrence` +
9270
- (keySample ? ` (e.g. ${keySample})` : ''),
9390
+ `[runtime.dataset_input_cardinality] ${JSON.stringify({
9391
+ dataset: normalizedMapNamespace,
9392
+ rowIdentity: rowIdentityMode,
9393
+ sourceRows: rawMaterializedEntries.length,
9394
+ uniqueInputRows: dedupedMaterialized.rows.length,
9395
+ duplicateOccurrences: dedupedMaterialized.droppedCount,
9396
+ duplicatePolicy: 'first_wins',
9397
+ })}`,
9271
9398
  );
9272
9399
  }
9273
9400
  const materializedItems = dedupedMaterialized.rows;
9401
+ // A legacy checkpoint does not contain its pre-dedupe source cardinality.
9402
+ // Leave the optional summary absent rather than report its pending subset
9403
+ // as the original source. New checkpoints retain only fixed-size counts.
9404
+ const inputCardinality = resumedPendingRowsOnly
9405
+ ? replayMapFrame?.inputCardinality
9406
+ : {
9407
+ sourceRows: rawMaterializedEntries.length,
9408
+ uniqueInputRows: materializedItems.length,
9409
+ duplicateOccurrences: dedupedMaterialized.droppedCount,
9410
+ rowIdentity: rowIdentityMode,
9411
+ };
9412
+ // Admission provenance is captured before onMapStart restores output
9413
+ // cells. The execution row stays intact; only pending inputs enter a
9414
+ // checkpoint, and their sheet outputs are restored by the existing owner
9415
+ // after the next admission. Never infer original input from enriched data.
9416
+ const retainedAuthoredInputsByIndex = new Map(
9417
+ (suspendedInputRows ?? []).flatMap((row) =>
9418
+ row.authoredInput ? [[row.inputIndex, row.authoredInput] as const] : [],
9419
+ ),
9420
+ );
9421
+ const authoredInputsByIndex = new Map(
9422
+ canReadPersistedMapResults
9423
+ ? materializedItems.flatMap((item, index) => {
9424
+ const inputIndex = originalInputIndexByItem.get(item) ?? index;
9425
+ if (resumedPendingRowsOnly) {
9426
+ const retained = retainedAuthoredInputsByIndex.get(inputIndex);
9427
+ // An older pending checkpoint has execution data only. Keep its
9428
+ // optional provenance absent rather than relabel enriched cells.
9429
+ return retained ? [[inputIndex, retained] as const] : [];
9430
+ }
9431
+ const row = this.toOutputRow(item as Record<string, unknown>);
9432
+ const source = getPlayDatasetSourceRowIdentity(row);
9433
+ const data = sanitizePostgresJsonValue(
9434
+ toSerializableCsvAliasedRow(row),
9435
+ );
9436
+ materializationMemoryTracker.track(data);
9437
+ return [
9438
+ [
9439
+ inputIndex,
9440
+ { data, ...(source ? { source: { ...source } } : {}) },
9441
+ ] as const,
9442
+ ];
9443
+ })
9444
+ : [],
9445
+ );
9274
9446
  const priorAuthoredDigestsByIndex = new Map(
9275
9447
  (suspendedInputRows ?? []).flatMap((row) =>
9276
9448
  row.authoredInputDigest
@@ -9405,6 +9577,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9405
9577
  const inputIndex = itemOriginalIndexes[index] ?? index;
9406
9578
  return {
9407
9579
  ...toSerializableCsvAliasedRow(row),
9580
+ ...(getPlayDatasetSourceRowIdentity(row)
9581
+ ? { __deeplineSourceRow: getPlayDatasetSourceRowIdentity(row) }
9582
+ : {}),
9408
9583
  ...mapRowOutcomeRuntimeFields({
9409
9584
  key: seedRowIdentity(row, inputIndex),
9410
9585
  inputIndex,
@@ -9513,6 +9688,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9513
9688
  artifactTableNamespace: mapScope.artifactTableNamespace,
9514
9689
  status: 'running',
9515
9690
  totalRows: totalInputCount,
9691
+ inputCardinality,
9516
9692
  completedRowKeys,
9517
9693
  pendingRowKeys,
9518
9694
  completedRowsCount: previouslyCompletedRowsCount,
@@ -9663,13 +9839,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9663
9839
  executionRowIndexes: rowsToExecuteEntries.map(
9664
9840
  (entry) => entry.originalIndex,
9665
9841
  ),
9666
- ...(!canReadPersistedMapResults
9842
+ ...(canReadPersistedMapResults
9667
9843
  ? {
9844
+ authoredInputs: rowsToExecuteEntries.map((entry) =>
9845
+ authoredInputsByIndex.get(entry.originalIndex),
9846
+ ),
9847
+ }
9848
+ : {
9668
9849
  authoredInputDigests: rowsToExecuteEntries.map((entry) =>
9669
9850
  authoredInputDigestsByIndex.get(entry.originalIndex),
9670
9851
  ),
9671
- }
9672
- : {}),
9852
+ }),
9673
9853
  retainedRowsMemoryBudgetBytes:
9674
9854
  mapMemoryLimits.materializedBudgetBytes,
9675
9855
  progressCompletedOffset:
@@ -9854,13 +10034,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9854
10034
  });
9855
10035
  }
9856
10036
 
9857
- this.#options.onMapRowsSettled?.({
10037
+ await this.#options.onMapRowsSettled?.({
9858
10038
  playName: this.#options.playName,
9859
10039
  runId: this.#options.runId,
9860
10040
  tableNamespace: resolvedTableNamespace,
9861
10041
  succeededRows:
9862
10042
  previouslyCompletedRowKeys.length + visibleCompletedRows.length,
9863
10043
  failedRows: durableFailedRows,
10044
+ expectedRows: totalInputCount - duplicateReuseCount,
10045
+ staticPipeline: this.currentStaticPipeline,
9864
10046
  });
9865
10047
 
9866
10048
  // This map's terminal rows have already crossed the persistence barrier
@@ -9950,8 +10132,24 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9950
10132
  skipped: reusedCount,
9951
10133
  pending: 0,
9952
10134
  failed: durableFailedRows,
9953
- ...(duplicateReuseCount > 0
9954
- ? { duplicates: { exact: duplicateReuseCount } }
10135
+ ...(inputCardinality
10136
+ ? {
10137
+ cardinality: {
10138
+ ...inputCardinality,
10139
+ uniqueInputRows:
10140
+ inputCardinality.uniqueInputRows - duplicateReuseCount,
10141
+ duplicateOccurrences:
10142
+ inputCardinality.duplicateOccurrences + duplicateReuseCount,
10143
+ outputRows: results.length,
10144
+ },
10145
+ }
10146
+ : {}),
10147
+ ...(dedupedMaterialized.droppedCount + duplicateReuseCount > 0
10148
+ ? {
10149
+ duplicates: {
10150
+ exact: dedupedMaterialized.droppedCount + duplicateReuseCount,
10151
+ },
10152
+ }
9955
10153
  : {}),
9956
10154
  },
9957
10155
  resolvers: {
@@ -9989,6 +10187,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9989
10187
  onRowError?: 'isolate' | 'fail';
9990
10188
  executionRowKeys?: string[];
9991
10189
  executionRowIndexes?: number[];
10190
+ authoredInputs?: Array<
10191
+ NonNullable<
10192
+ MapExecutionFrame['suspendedInputRows']
10193
+ >[number]['authoredInput']
10194
+ >;
9992
10195
  authoredInputDigests?: Array<string | undefined>;
9993
10196
  /**
9994
10197
  * Opt-in cap on how many row resolvers run their body concurrently. When
@@ -10796,6 +10999,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10796
10999
  ),
10797
11000
  ...mapRowOutcomeRuntimeFields({ key: rowKey }),
10798
11001
  },
11002
+ ...(runtimeOptions?.authoredInputs?.[index]
11003
+ ? { authoredInput: runtimeOptions.authoredInputs[index] }
11004
+ : {}),
10799
11005
  ...(runtimeOptions?.authoredInputDigests?.[index]
10800
11006
  ? {
10801
11007
  authoredInputDigest:
@@ -11204,6 +11410,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11204
11410
  key: keyForIndex(index),
11205
11411
  }),
11206
11412
  },
11413
+ ...(runtimeOptions?.authoredInputs?.[index]
11414
+ ? { authoredInput: runtimeOptions.authoredInputs[index] }
11415
+ : {}),
11207
11416
  ...(runtimeOptions?.authoredInputDigests?.[index]
11208
11417
  ? {
11209
11418
  authoredInputDigest:
@@ -12530,7 +12739,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12530
12739
 
12531
12740
  private toOutputRow<T>(item: T): Record<string, unknown> {
12532
12741
  if (item != null && typeof item === 'object' && !Array.isArray(item)) {
12533
- return stripCsvProjectionMetadata(item as Record<string, unknown>);
12742
+ return copyPlayDatasetSourceRowIdentity(
12743
+ item,
12744
+ stripCsvProjectionMetadata(item as Record<string, unknown>),
12745
+ );
12534
12746
  }
12535
12747
 
12536
12748
  return { value: item };
@@ -17746,6 +17958,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
17746
17958
  );
17747
17959
  const shouldWaitAtExecutionBoundary =
17748
17960
  shouldWaitForAsyncOperation && !shouldResolveAsyncOperationInRuntime;
17961
+ const asyncExecutionRecovery = shouldWaitAtExecutionBoundary
17962
+ ? await this.#options.getToolAsyncExecutionRecovery?.(toolId)
17963
+ : null;
17964
+ if (
17965
+ asyncExecutionRecovery &&
17966
+ !isImmutableRuntimeExecutionRecoveryOrigin(asyncExecutionRecovery.origin)
17967
+ ) {
17968
+ throw new Error(
17969
+ 'Async execution recovery requires the serving app immutable origin. No provider request was sent.',
17970
+ );
17971
+ }
17749
17972
  // An explicitly opted-in caller-managed launch gives the Play author the
17750
17973
  // provider handle and leaves polling to the authored graph. Do not let a
17751
17974
  // provider's legacy synchronous helper turn a wide start wave into
@@ -18106,6 +18329,70 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18106
18329
  // map cells, batched owners) supply the fuller scope.
18107
18330
  const playNodeScope =
18108
18331
  options?.playNodeScope ?? buildPlayNodeScope({ toolId });
18332
+ const asyncRecoveryInvocationKey = asyncExecutionRecovery
18333
+ ? stableDigest(
18334
+ stableStringify({
18335
+ toolId,
18336
+ // A queued call/chunk supplies its exact durable transport
18337
+ // identity. A direct invocation owns a fresh call identity.
18338
+ callIdentity:
18339
+ options?.durableCallReceiptKey ??
18340
+ (options?.providerOperationForceRefreshIdentities?.length
18341
+ ? options.providerOperationForceRefreshIdentities
18342
+ : (options?.providerIdempotencyKey ??
18343
+ executionRequestNonce)),
18344
+ payload: toolInputSnapshot,
18345
+ authScope: executionAuthScopeDigest,
18346
+ playNodeScope,
18347
+ forceRefreshIdentities:
18348
+ options?.providerOperationForceRefreshIdentities ?? [],
18349
+ }),
18350
+ )
18351
+ : null;
18352
+ const asyncRecoveryPolling = asyncExecutionRecovery
18353
+ ? resolveAsyncOperationPolling({
18354
+ contract: asyncContract!,
18355
+ startInput: toolInputSnapshot,
18356
+ playRuntimeWait: true,
18357
+ })
18358
+ : null;
18359
+ const asyncRecoveryDeadlineAtMs = asyncRecoveryPolling
18360
+ ? Date.now() + asyncRecoveryPolling.timeoutMs
18361
+ : null;
18362
+ let confirmedAsyncRecoveryPending = false;
18363
+ let asyncRecoveryWaitTimeout: ReturnType<
18364
+ typeof createToolHttpError
18365
+ > | null = null;
18366
+ const asyncRecoveryWaitTimeoutError = () =>
18367
+ (asyncRecoveryWaitTimeout ??= createToolHttpError(
18368
+ toolErrorSchemaVersion,
18369
+ formatAsyncOperationTimeout({
18370
+ startAction: asyncContract!.lifecycle.startAction,
18371
+ jobId: null,
18372
+ timeoutDescription: `${asyncRecoveryPolling!.timeoutMs}ms`,
18373
+ }) +
18374
+ ' The execution remains recoverable under its original idempotency key; it will not be relaunched.',
18375
+ null,
18376
+ 504,
18377
+ 'repairable',
18378
+ {
18379
+ toolId,
18380
+ provider,
18381
+ operation: toolId,
18382
+ code: 'ASYNC_OPERATION_TIMEOUT',
18383
+ origin: 'provider',
18384
+ category: 'upstream',
18385
+ retryable: false,
18386
+ statusCode: 504,
18387
+ },
18388
+ ));
18389
+ const asyncRecoveryKey = (attempt: number) =>
18390
+ asyncRecoveryInvocationKey
18391
+ ? buildRuntimeExecutionRecoveryKey({
18392
+ parentRunId: this.currentRunId,
18393
+ invocationKey: `${asyncRecoveryInvocationKey}:attempt:${attempt}`,
18394
+ })
18395
+ : null;
18109
18396
  // This is deliberately a claim about only the immediately preceding
18110
18397
  // *durable* provider attempt. The execution gateway validates and
18111
18398
  // attests it before a retried call can use its fresh charge
@@ -18361,6 +18648,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18361
18648
  transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS &&
18362
18649
  (input.knownNotDispatched === true ||
18363
18650
  usesDurableInvocationFence ||
18651
+ asyncExecutionRecovery ||
18364
18652
  ambiguousTransportReplayAllowed ||
18365
18653
  fixtureReplaySafe)
18366
18654
  ) {
@@ -18368,7 +18656,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18368
18656
  // attempt and any prior provider-429 proof belong to the logical
18369
18657
  // invocation; only a definite retryable HTTP outcome advances it.
18370
18658
  // Ordinary transport retries retain their physical attempt count.
18371
- if (!usesDurableInvocationFence) {
18659
+ if (!usesDurableInvocationFence && !asyncExecutionRecovery) {
18372
18660
  providerRateLimitRetryFromInvocationAttempt = null;
18373
18661
  invocationAttempt += 1;
18374
18662
  }
@@ -18454,6 +18742,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18454
18742
 
18455
18743
  while (true) {
18456
18744
  toolAbortSignal?.throwIfAborted();
18745
+ if (
18746
+ confirmedAsyncRecoveryPending &&
18747
+ Date.now() >= asyncRecoveryDeadlineAtMs!
18748
+ ) {
18749
+ throw asyncRecoveryWaitTimeoutError();
18750
+ }
18457
18751
  if (
18458
18752
  providerNoProgressExpired({
18459
18753
  startedAtMsByScope:
@@ -18806,6 +19100,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18806
19100
  // A stale permit can take arbitrarily long to
18807
19101
  // replace. Only fixture residence and the physical
18808
19102
  // HTTP exchange spend the tool execution deadline.
19103
+ if (
19104
+ confirmedAsyncRecoveryPending &&
19105
+ Date.now() >= asyncRecoveryDeadlineAtMs!
19106
+ ) {
19107
+ throw asyncRecoveryWaitTimeoutError();
19108
+ }
18809
19109
  armProviderTimeout();
18810
19110
  beginProviderExecution();
18811
19111
  const fetchedAt = Date.now();
@@ -18865,6 +19165,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18865
19165
  }
18866
19166
  : {}),
18867
19167
  'x-deepline-request-id': deeplineRequestId,
19168
+ ...(asyncRecoveryKey(invocationAttempt)
19169
+ ? {
19170
+ 'idempotency-key':
19171
+ asyncRecoveryKey(invocationAttempt)!,
19172
+ }
19173
+ : {}),
18868
19174
  ...(providerExecutionFenceKey
18869
19175
  ? {
18870
19176
  'x-deepline-idempotency-key':
@@ -19247,6 +19553,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19247
19553
  receiptHeartbeatSupervisor?.stop();
19248
19554
  }
19249
19555
  } catch (error) {
19556
+ if (
19557
+ asyncRecoveryWaitTimeout !== null &&
19558
+ error === asyncRecoveryWaitTimeout
19559
+ ) {
19560
+ throw error;
19561
+ }
19250
19562
  if (heartbeatFailure) {
19251
19563
  throw heartbeatFailure;
19252
19564
  }
@@ -19366,6 +19678,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19366
19678
  const transportReplaySafe =
19367
19679
  !ambiguousDispatchedFailure ||
19368
19680
  usesDurableInvocationFence ||
19681
+ Boolean(asyncExecutionRecovery) ||
19369
19682
  ambiguousTransportReplayAllowed ||
19370
19683
  fixtureReplaySafe;
19371
19684
  if (!transportReplaySafe) {
@@ -19444,8 +19757,62 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19444
19757
  continue;
19445
19758
  }
19446
19759
 
19760
+ if (
19761
+ asyncExecutionRecovery &&
19762
+ response.ok &&
19763
+ response.headers.get(
19764
+ RUNTIME_EXECUTION_RECOVERY_SUPPORTED_HEADER,
19765
+ ) !== 'true'
19766
+ ) {
19767
+ throw createToolHttpError(
19768
+ toolErrorSchemaVersion,
19769
+ `Tool ${toolId} did not confirm the declared async recovery reader. The execution will not be relaunched.`,
19770
+ null,
19771
+ response.status,
19772
+ 'repairable',
19773
+ );
19774
+ }
19775
+
19447
19776
  if (!response.ok) {
19448
19777
  const text = responseErrorText ?? '';
19778
+ const recoveryKey = asyncRecoveryKey(invocationAttempt);
19779
+ if (
19780
+ recoveryKey &&
19781
+ response.status === 409 &&
19782
+ response.headers.get(
19783
+ RUNTIME_EXECUTION_RECOVERY_SUPPORTED_HEADER,
19784
+ ) === 'true'
19785
+ ) {
19786
+ const pending = (() => {
19787
+ try {
19788
+ return JSON.parse(text) as Record<string, unknown>;
19789
+ } catch {
19790
+ return null;
19791
+ }
19792
+ })();
19793
+ const recovery = pending?.executionRecovery as
19794
+ | Record<string, unknown>
19795
+ | undefined;
19796
+ if (
19797
+ pending?.code === 'EXECUTION_IN_PROGRESS' &&
19798
+ pending.retryable === true &&
19799
+ recovery?.idempotencyKey === recoveryKey &&
19800
+ (recovery.state === 'running' ||
19801
+ recovery.state === 'completed')
19802
+ ) {
19803
+ confirmedAsyncRecoveryPending = true;
19804
+ if (Date.now() >= asyncRecoveryDeadlineAtMs!) {
19805
+ throw asyncRecoveryWaitTimeoutError();
19806
+ }
19807
+ // This is continuation of already accepted work. Keep the
19808
+ // original body/key and let the route attach its saved job.
19809
+ await waitForToolRetry(
19810
+ Math.min(1_000, asyncRecoveryDeadlineAtMs! - Date.now()),
19811
+ false,
19812
+ );
19813
+ continue;
19814
+ }
19815
+ }
19449
19816
  const developerTunnelOrigin502 =
19450
19817
  isDeeplineDeveloperTunnelOrigin502({
19451
19818
  url,
@@ -19487,7 +19854,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19487
19854
  status: response.status,
19488
19855
  bodyText: text,
19489
19856
  transientHttpRetrySafe:
19490
- retrySafeTransientHttp || fixtureReplaySafe,
19857
+ retrySafeTransientHttp ||
19858
+ fixtureReplaySafe ||
19859
+ Boolean(asyncExecutionRecovery),
19491
19860
  executionFenced: usesDurableInvocationFence,
19492
19861
  });
19493
19862
  const failure =
@@ -19501,7 +19870,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19501
19870
  retryAfterHeader: response.headers.get('retry-after'),
19502
19871
  retryAtMs: parseToolExecuteExactProviderRetryAtMs(response),
19503
19872
  transientHttpRetrySafe:
19504
- retrySafeTransientHttp || fixtureReplaySafe,
19873
+ retrySafeTransientHttp ||
19874
+ fixtureReplaySafe ||
19875
+ Boolean(asyncExecutionRecovery),
19505
19876
  executionFenced: usesDurableInvocationFence,
19506
19877
  ...(providerCallElapsedMs !== null
19507
19878
  ? { providerLatencyMs: providerCallElapsedMs }
@@ -19643,6 +20014,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19643
20014
  (failure.knownNotDispatched ||
19644
20015
  failure.reason === 'gateway_invocation_in_progress' ||
19645
20016
  ambiguousTransportReplayAllowed ||
20017
+ Boolean(asyncExecutionRecovery) ||
19646
20018
  retrySafeTransientHttp ||
19647
20019
  fixtureReplaySafe ||
19648
20020
  // An explicit provider-owned 429 is a rejected operation,
@@ -19659,7 +20031,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
19659
20031
  // prior-attempt sweep see an intentionally absent receipt.
19660
20032
  if (
19661
20033
  failure.reason !== 'gateway_invocation_in_progress' &&
19662
- !shedKnownNotDispatched
20034
+ !shedKnownNotDispatched &&
20035
+ (!asyncExecutionRecovery ||
20036
+ failure.backpressureDelayMs !== null)
19663
20037
  ) {
19664
20038
  const previousInvocationAttempt = invocationAttempt;
19665
20039
  if (failure.backpressureDelayMs !== null) {