deepline 0.3.141 → 0.3.143

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 (25) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/sdk/src/types.ts +65 -2
  3. package/dist/bundling-sources/shared_libs/observability/dlq.ts +25 -0
  4. package/dist/bundling-sources/shared_libs/observability/queue-health.ts +111 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/bettercontact-batching.ts +34 -8
  6. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +271 -44
  7. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +25 -6
  8. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +132 -8
  9. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +3 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +49 -54
  11. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +14 -7
  12. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +118 -29
  13. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +183 -36
  14. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +4 -1
  15. package/dist/cli/index.js +242 -12
  16. package/dist/cli/index.mjs +242 -12
  17. package/dist/index.d.mts +54 -2
  18. package/dist/index.d.ts +54 -2
  19. package/dist/index.js +1 -1
  20. package/dist/index.mjs +1 -1
  21. package/dist/release.d.mts +1 -1
  22. package/dist/release.d.ts +1 -1
  23. package/dist/release.js +1 -1
  24. package/dist/release.mjs +1 -1
  25. package/package.json +1 -1
@@ -2712,6 +2712,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2712
2712
  private toolDispatchGroupRoundTripEwmaMs: number | null = null;
2713
2713
  private readonly toolDispatcherWakeWaiters = new Set<() => void>();
2714
2714
  private toolDispatcherFailure: unknown | null = null;
2715
+ /** Active provider HTTP attempts in this Play execution, grouped by provider. */
2716
+ private readonly inFlightProviderRequests = new Map<string, number>();
2715
2717
  /**
2716
2718
  * Deepline's own zero-credit denial is run-fatal. Keep it independently of
2717
2719
  * customer control flow so a broad `try/catch` cannot turn it into a
@@ -8457,6 +8459,52 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8457
8459
  : derivePlayRowIdentity(stableInputRow(row), resolvedTableNamespace);
8458
8460
  };
8459
8461
 
8462
+ // A terminal write can be durably accepted into this run's history while
8463
+ // losing the mutable latest-view race to newer intent. That outcome must
8464
+ // not leak into the dataset returned by this execution. A later same-run
8465
+ // reconciliation can explicitly recover a key; only that positive fact
8466
+ // makes it visible again.
8467
+ const staleCompletionKeys = new Set<string>();
8468
+ const staleCompletedKeys = new Set<string>();
8469
+ const staleFailedKeys = new Set<string>();
8470
+ const recoveredStaleCompletionKeys = new Set<string>();
8471
+ const observeMapWriteResult = (
8472
+ writeResult: unknown,
8473
+ writtenRows: readonly PersistableMapRow[] = [],
8474
+ ): void => {
8475
+ if (!writeResult || typeof writeResult !== 'object') return;
8476
+ const result = writeResult as {
8477
+ staleDroppedKeys?: unknown;
8478
+ recoveredStaleKeys?: unknown;
8479
+ };
8480
+ const rowsByKey = new Map(writtenRows.map((row) => [row.key, row]));
8481
+ if (Array.isArray(result.staleDroppedKeys)) {
8482
+ for (const key of result.staleDroppedKeys) {
8483
+ if (typeof key !== 'string' || !key) continue;
8484
+ staleCompletionKeys.add(key);
8485
+ if (rowsByKey.get(key)?.status === 'failed') {
8486
+ staleFailedKeys.add(key);
8487
+ staleCompletedKeys.delete(key);
8488
+ } else {
8489
+ staleCompletedKeys.add(key);
8490
+ staleFailedKeys.delete(key);
8491
+ }
8492
+ }
8493
+ }
8494
+ if (Array.isArray(result.recoveredStaleKeys)) {
8495
+ for (const key of result.recoveredStaleKeys) {
8496
+ if (typeof key !== 'string' || !key) continue;
8497
+ recoveredStaleCompletionKeys.add(key);
8498
+ staleCompletionKeys.delete(key);
8499
+ staleCompletedKeys.delete(key);
8500
+ staleFailedKeys.delete(key);
8501
+ }
8502
+ }
8503
+ };
8504
+ const isVisibleTerminalRow = (row: PersistableMapRow): boolean =>
8505
+ !staleCompletionKeys.has(row.key) ||
8506
+ recoveredStaleCompletionKeys.has(row.key);
8507
+
8460
8508
  // Identity for SEED rows (pre-execution raw inputs), matching the server's
8461
8509
  // own derivation in startRuntimeSheetDataset: hash the FULL cleaned input
8462
8510
  // row, output-named columns INCLUDED. Two distinct input rows that differ
@@ -8604,6 +8652,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8604
8652
  ),
8605
8653
  staticPipeline: this.currentStaticPipeline ?? null,
8606
8654
  });
8655
+ observeMapWriteResult(writeResult, chunk);
8607
8656
  const supersededKeys =
8608
8657
  typeof writeResult === 'object' && writeResult !== null
8609
8658
  ? (writeResult.supersededKeys ?? [])
@@ -8831,9 +8880,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8831
8880
  totalRows: totalInputCount,
8832
8881
  completedRowKeys: [],
8833
8882
  pendingRowKeys: execution.rowFeed ? [] : [...execution.rowKeys],
8834
- completedRowsCount: Math.max(0, processedCount - failedCount),
8883
+ completedRowsCount: Math.max(
8884
+ 0,
8885
+ processedCount - failedCount - staleCompletedKeys.size,
8886
+ ),
8835
8887
  pendingRowsCount: Math.max(0, totalInputCount - processedCount),
8836
- failedRowsCount: failedCount,
8888
+ failedRowsCount: Math.max(0, failedCount - staleFailedKeys.size),
8837
8889
  startedAt:
8838
8890
  this.checkpoint.mapFrames?.[mapScope.mapInvocationId]?.startedAt ??
8839
8891
  Date.now(),
@@ -8857,8 +8909,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8857
8909
  executionRowKeys: execution.rowKeys,
8858
8910
  executionRowIndexes: execution.rowIndexes,
8859
8911
  progressCompletedOffset: () =>
8860
- Math.max(0, processedCount - failedCount),
8861
- progressFailedOffset: () => failedCount,
8912
+ Math.max(
8913
+ 0,
8914
+ processedCount - failedCount - staleCompletedKeys.size,
8915
+ ),
8916
+ progressFailedOffset: () =>
8917
+ Math.max(0, failedCount - staleFailedKeys.size),
8862
8918
  progressTotalRows: () => totalInputCount,
8863
8919
  rowFeed: execution.rowFeed,
8864
8920
  retainedRowsMemoryBudgetBytes:
@@ -8885,9 +8941,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8885
8941
  }
8886
8942
 
8887
8943
  await incrementalPersistence.flush();
8944
+ const visibleCompletedRows =
8945
+ mapResult.completedRows.filter(isVisibleTerminalRow);
8946
+ const visibleFailedRows =
8947
+ mapResult.failedRows.filter(isVisibleTerminalRow);
8888
8948
  const mapCellMeta = this.activeMapCellMeta;
8889
8949
  const persistRows: PersistableMapRow[] = [];
8890
- for (const row of mapResult.completedRows) {
8950
+ for (const row of visibleCompletedRows) {
8891
8951
  if (incrementalPersistence.isPersisted(row)) continue;
8892
8952
  const meta = mapCellMeta?.get(row.key);
8893
8953
  persistRows.push(
@@ -8903,7 +8963,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8903
8963
  );
8904
8964
  }
8905
8965
  persistRows.push(
8906
- ...mapResult.failedRows.filter(
8966
+ ...visibleFailedRows.filter(
8907
8967
  (row) => !incrementalPersistence.isPersisted(row),
8908
8968
  ),
8909
8969
  );
@@ -8919,7 +8979,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
8919
8979
  'the current execution page exceeded the in-memory result budget',
8920
8980
  );
8921
8981
  }
8922
- for (const row of mapResult.completedRows) {
8982
+ for (const row of visibleCompletedRows) {
8923
8983
  const materializedRow = this.toMaterializedOutputRow(row.data);
8924
8984
  if (
8925
8985
  immediateMaterializedCacheEnabled &&
@@ -9060,6 +9120,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9060
9120
  }
9061
9121
  }
9062
9122
 
9123
+ successfulCount = Math.max(0, successfulCount - staleCompletedKeys.size);
9124
+ failedCount = Math.max(0, failedCount - staleFailedKeys.size);
9125
+
9063
9126
  // Isolated failures are a partial result only when at least one admitted
9064
9127
  // row completed. A fully failed admission run is systemic: preserve the
9065
9128
  // failed sheet rows for recovery, but fail the enclosing play loudly.
@@ -9110,7 +9173,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9110
9173
  totalRows: totalInputCount,
9111
9174
  completedRowKeys: [],
9112
9175
  pendingRowKeys: [],
9113
- completedRowsCount: Math.max(0, processedCount - failedCount),
9176
+ completedRowsCount: Math.max(
9177
+ 0,
9178
+ processedCount -
9179
+ failedCount -
9180
+ staleFailedKeys.size -
9181
+ staleCompletedKeys.size,
9182
+ ),
9114
9183
  pendingRowsCount: 0,
9115
9184
  failedRowsCount: failedCount,
9116
9185
  startedAt:
@@ -9124,7 +9193,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9124
9193
  mapNodeId: mapScope.mapNodeId ?? null,
9125
9194
  logicalNamespace: mapScope.logicalNamespace,
9126
9195
  artifactTableNamespace: resolvedTableNamespace,
9127
- completedRows: Math.max(0, processedCount - failedCount),
9196
+ completedRows: Math.max(
9197
+ 0,
9198
+ processedCount -
9199
+ failedCount -
9200
+ staleFailedKeys.size -
9201
+ staleCompletedKeys.size,
9202
+ ),
9128
9203
  failedRows: failedCount,
9129
9204
  totalRows: totalInputCount,
9130
9205
  at: Date.now(),
@@ -9500,7 +9575,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9500
9575
 
9501
9576
  this.beginActiveMapCellMeta();
9502
9577
  this.beginActiveMapCheckpointUpdates();
9503
- const staleCompletionKeys = new Set<string>();
9504
9578
  const persistMapRows = async (rows: PersistableMapRow[]) => {
9505
9579
  if (!this.#options.onMapRowsCompleted || rows.length === 0) {
9506
9580
  return;
@@ -9529,15 +9603,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9529
9603
  ),
9530
9604
  staticPipeline: this.currentStaticPipeline ?? null,
9531
9605
  });
9532
- if (writeResult) {
9533
- const recoveredStaleKeys = new Set(
9534
- writeResult.recoveredStaleKeys ?? [],
9535
- );
9536
- for (const key of writeResult.staleDroppedKeys ?? []) {
9537
- if (recoveredStaleKeys.has(key)) continue;
9538
- staleCompletionKeys.add(key);
9539
- }
9540
- }
9606
+ observeMapWriteResult(writeResult, chunk);
9541
9607
  this.resourceGovernor.observe({
9542
9608
  sheetFlushBytes: chunkBytes,
9543
9609
  sheetFlushLatencyMs: Date.now() - flushStartedAt,
@@ -9623,23 +9689,31 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9623
9689
  this.clearActiveMapCheckpointUpdates();
9624
9690
  }
9625
9691
  }
9692
+ const visibleCompletedRows =
9693
+ mapResult.completedRows.filter(isVisibleTerminalRow);
9694
+ const visibleFailedRows = mapResult.failedRows.filter(isVisibleTerminalRow);
9626
9695
  const resultsByKey = new Map<string, Record<string, unknown>>();
9627
- for (const row of mapResult.completedRows) {
9696
+ for (const row of visibleCompletedRows) {
9628
9697
  if (row.key) resultsByKey.set(row.key, row.data);
9629
9698
  }
9630
9699
  const failedRowKeys = new Set(
9631
- mapResult.failedRows.map((row) => row.key).filter(Boolean),
9700
+ visibleFailedRows.map((row) => row.key).filter(Boolean),
9632
9701
  );
9633
- const directCompletedResults = mapResult.completedRows.map((row) =>
9702
+ const directCompletedResults = visibleCompletedRows.map((row) =>
9634
9703
  this.toPublicOutputRow(row.data),
9635
9704
  );
9636
9705
  let results =
9637
- mapResult.failedRows.length === 0 &&
9706
+ visibleFailedRows.length === 0 &&
9638
9707
  directCompletedResults.length === rawItems.length
9639
9708
  ? directCompletedResults
9640
9709
  : rawItems.flatMap((rawItem, index) => {
9641
9710
  const rowKey = rowIdentity(rawItem, index);
9642
- if (rowKey && failedRowKeys.has(rowKey)) {
9711
+ if (
9712
+ rowKey &&
9713
+ (failedRowKeys.has(rowKey) ||
9714
+ (staleCompletionKeys.has(rowKey) &&
9715
+ !recoveredStaleCompletionKeys.has(rowKey)))
9716
+ ) {
9643
9717
  return [];
9644
9718
  }
9645
9719
  return [
@@ -9652,11 +9726,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9652
9726
  // Persist executed rows to the tenant runtime sheet — the sheet is the
9653
9727
  // source of truth, not this in-memory results array. Chunked by rows AND
9654
9728
  // bytes so large cells (scraped pages) never produce oversized writes.
9655
- if (resultsByKey.size > 0 || mapResult.failedRows.length > 0) {
9729
+ if (resultsByKey.size > 0 || visibleFailedRows.length > 0) {
9656
9730
  await incrementalPersistence?.flush();
9657
9731
  const mapCellMeta = this.activeMapCellMeta;
9658
9732
  const persistRows: PersistableMapRow[] = [];
9659
- for (const row of mapResult.completedRows) {
9733
+ for (const row of visibleCompletedRows) {
9660
9734
  if (incrementalPersistence?.isPersisted(row)) continue;
9661
9735
  const rowKey = row.key;
9662
9736
  const meta = mapCellMeta?.get(rowKey);
@@ -9672,7 +9746,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9672
9746
  );
9673
9747
  }
9674
9748
  persistRows.push(
9675
- ...mapResult.failedRows.filter(
9749
+ ...visibleFailedRows.filter(
9676
9750
  (row) => !incrementalPersistence?.isPersisted(row),
9677
9751
  ),
9678
9752
  );
@@ -9687,18 +9761,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9687
9761
  this.clearActiveMapCellMeta();
9688
9762
  this.clearActiveMapCheckpointUpdates();
9689
9763
 
9690
- if (staleCompletionKeys.size > 0) {
9691
- results = results.filter((row, index) => {
9692
- const key = rowIdentity(row, itemOriginalIndexes[index] ?? index);
9693
- return !staleCompletionKeys.has(key);
9694
- });
9695
- }
9696
-
9697
9764
  const durableCompletedRows = Math.max(
9698
9765
  0,
9699
- reusedCount + mapResult.completedRows.length - staleCompletionKeys.size,
9766
+ reusedCount + visibleCompletedRows.length,
9700
9767
  );
9701
- const durableFailedRows = mapResult.failedRows.length;
9768
+ const durableFailedRows = visibleFailedRows.length;
9702
9769
  const terminalFrame = this.checkpoint.mapFrames?.[mapScope.mapInvocationId];
9703
9770
  if (terminalFrame) {
9704
9771
  this.setMapFrame({
@@ -9730,7 +9797,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9730
9797
  at: Date.now(),
9731
9798
  });
9732
9799
 
9733
- if (this.#options.onMapRowsCompleted && staleCompletionKeys.size === 0) {
9800
+ if (this.#options.onMapRowsCompleted) {
9734
9801
  const finalizationPageReader = createRuntimeSheetDatasetPageReader({
9735
9802
  context: {
9736
9803
  baseUrl: this.#options.baseUrl!,
@@ -9748,9 +9815,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9748
9815
  mapName: normalizedMapNamespace,
9749
9816
  tableNamespace: resolvedTableNamespace,
9750
9817
  runId: this.#options.runId,
9751
- expectedRows: totalInputCount,
9818
+ expectedRows: Math.max(0, totalInputCount - staleCompletionKeys.size),
9752
9819
  currentRows: results,
9753
- failedRowCount: mapResult.failedRows.length,
9820
+ failedRowCount: visibleFailedRows.length,
9754
9821
  log: (line) => this.runtimeLog(line),
9755
9822
  readPersistedRows: async (readInput) => {
9756
9823
  if (
@@ -9870,7 +9937,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9870
9937
  reused: reusedCount,
9871
9938
  skipped: reusedCount,
9872
9939
  pending: 0,
9873
- failed: mapResult.failedRows.length,
9940
+ failed: visibleFailedRows.length,
9874
9941
  ...(duplicateReuseCount > 0
9875
9942
  ? { duplicates: { exact: duplicateReuseCount } }
9876
9943
  : {}),
@@ -12350,6 +12417,31 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12350
12417
  );
12351
12418
  }
12352
12419
 
12420
+ private beginProviderRequest(provider: string): {
12421
+ inFlightCount: () => number;
12422
+ release: () => void;
12423
+ } {
12424
+ const providerKey = provider.trim().toLowerCase() || 'unknown';
12425
+ this.inFlightProviderRequests.set(
12426
+ providerKey,
12427
+ (this.inFlightProviderRequests.get(providerKey) ?? 0) + 1,
12428
+ );
12429
+ let released = false;
12430
+ return {
12431
+ inFlightCount: () => this.inFlightProviderRequests.get(providerKey) ?? 0,
12432
+ release: () => {
12433
+ if (released) return;
12434
+ released = true;
12435
+ const remaining = this.inFlightProviderRequests.get(providerKey) ?? 0;
12436
+ if (remaining <= 1) {
12437
+ this.inFlightProviderRequests.delete(providerKey);
12438
+ } else {
12439
+ this.inFlightProviderRequests.set(providerKey, remaining - 1);
12440
+ }
12441
+ },
12442
+ };
12443
+ }
12444
+
12353
12445
  private async executeTool(
12354
12446
  key: string,
12355
12447
  toolId: string,
@@ -12408,6 +12500,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12408
12500
  (await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null;
12409
12501
  const eventWaitHandler =
12410
12502
  (await this.#options.getIntegrationEventWaitHandler?.(toolId)) ?? null;
12503
+ const explicitForce =
12504
+ options?.force === true ||
12505
+ toolExecutionOverrides.getStore()?.force === true;
12411
12506
  const cacheableToolResult =
12412
12507
  !eventWaitHandler &&
12413
12508
  !isQueryResultDatasetReadRequest(toolId, input) &&
@@ -12462,6 +12557,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12462
12557
  toolId,
12463
12558
  input,
12464
12559
  eventWaitHandler,
12560
+ explicitForce,
12465
12561
  );
12466
12562
  }
12467
12563
  if (toolId === 'run_javascript') {
@@ -13226,6 +13322,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13226
13322
  toolId: string,
13227
13323
  input: Record<string, unknown>,
13228
13324
  handler: IntegrationEventWaitHandler,
13325
+ force = false,
13229
13326
  ): Promise<unknown> {
13230
13327
  this.assertInlineChildContract('suspending_child');
13231
13328
  if (!this.#options.durableBoundaries) {
@@ -13295,6 +13392,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13295
13392
  ...(existing.messageRef
13296
13393
  ? { messageRef: existing.messageRef }
13297
13394
  : {}),
13395
+ ...(existing.requestKey
13396
+ ? { requestKey: existing.requestKey }
13397
+ : {}),
13398
+ ...(existing.interactionId
13399
+ ? { interactionId: existing.interactionId }
13400
+ : {}),
13298
13401
  ...(timeoutPolicy ? { timeoutPolicy } : {}),
13299
13402
  },
13300
13403
  });
@@ -13344,8 +13447,52 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13344
13447
  payload: input,
13345
13448
  context: eventContext,
13346
13449
  boundary: preparedBoundary,
13450
+ ...(force ? { force: true } : {}),
13347
13451
  });
13348
13452
 
13453
+ if (boundary.resolvedOutput) {
13454
+ this.checkpoint.resolvedBoundaries = {
13455
+ ...(this.checkpoint.resolvedBoundaries ?? {}),
13456
+ [boundary.boundaryId]: {
13457
+ kind: 'integration_event',
13458
+ eventKey: boundary.eventKey,
13459
+ timeoutMs: boundary.timeoutMs,
13460
+ provider: boundary.provider,
13461
+ toolId: boundary.toolId,
13462
+ output: boundary.resolvedOutput,
13463
+ ...(boundary.timeoutPolicy
13464
+ ? { timeoutPolicy: boundary.timeoutPolicy }
13465
+ : {}),
13466
+ ...(rowScope
13467
+ ? {
13468
+ scope: {
13469
+ type: 'map_row' as const,
13470
+ tableNamespace: rowScope.tableNamespace,
13471
+ rowKey: rowScope.rowKey,
13472
+ rowIndex: rowScope.rowId,
13473
+ fieldName: rowScope.fieldName,
13474
+ },
13475
+ }
13476
+ : { scope: { type: 'workflow' as const } }),
13477
+ ...(boundary.messageRef ? { messageRef: boundary.messageRef } : {}),
13478
+ ...(boundary.requestKey ? { requestKey: boundary.requestKey } : {}),
13479
+ ...(boundary.interactionId
13480
+ ? { interactionId: boundary.interactionId }
13481
+ : {}),
13482
+ },
13483
+ };
13484
+ this.#options.onBatchComplete?.(this.checkpoint);
13485
+ return await this.wrapToolExecutionResult({
13486
+ toolId,
13487
+ status: 'completed',
13488
+ result: boundary.resolvedOutput,
13489
+ execution: toolExecutionMetadataForOutcome({
13490
+ kind: 'checkpoint',
13491
+ cacheKey: `integration_event:${boundary.boundaryId}`,
13492
+ }),
13493
+ });
13494
+ }
13495
+
13349
13496
  this.runtimeLog(
13350
13497
  `Armed ${handler.provider} integration event wait: ${boundary.eventKey}`,
13351
13498
  );
@@ -13357,6 +13504,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13357
13504
  timeoutMs: boundary.timeoutMs,
13358
13505
  provider: boundary.provider,
13359
13506
  toolId: boundary.toolId,
13507
+ ...(boundary.requestKey ? { requestKey: boundary.requestKey } : {}),
13508
+ ...(boundary.interactionId
13509
+ ? { interactionId: boundary.interactionId }
13510
+ : {}),
13360
13511
  ...(boundary.timeoutPolicy
13361
13512
  ? { timeoutPolicy: boundary.timeoutPolicy }
13362
13513
  : {}),
@@ -13954,7 +14105,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13954
14105
  ? `${rowStore.mapScope.mapInvocationId}:${rowStore.rowKey ?? rowStore.rowId}`
13955
14106
  : `row:${rowStore.rowKey ?? rowStore.rowId}`;
13956
14107
  const scopedIndexKey = `${this.currentExecutionScope.receipt.namespace}:${rowScope}`;
13957
- const scopedIndex = this.sleepBoundaryIndexByScope.get(scopedIndexKey) ?? 0;
14108
+ const scopedIndex =
14109
+ this.sleepBoundaryIndexByScope.get(scopedIndexKey) ?? 0;
13958
14110
  this.sleepBoundaryIndexByScope.set(scopedIndexKey, scopedIndex + 1);
13959
14111
  const scopedBoundaryId = this.durableBoundaryId(
13960
14112
  `sleep-${stableDigest(`${rowScope}:${scopedIndex}`)}-${delayMs}`,
@@ -14409,9 +14561,22 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14409
14561
  this.runtimeLog(
14410
14562
  `ctx.step(${normalizedKey}): recovered result from checkpoint`,
14411
14563
  );
14564
+ // The step callback is not evaluated on replay, so any sleeps that it
14565
+ // contained would otherwise disappear from the positional sleep
14566
+ // cursor. Preserve the authored boundary sequence before evaluating
14567
+ // the next sibling operation. This is deliberately run-local state;
14568
+ // it is not a cross-run sleep cache or an input-derived identity.
14569
+ if (
14570
+ Number.isSafeInteger(existing.sleepCount) &&
14571
+ existing.sleepCount !== undefined &&
14572
+ existing.sleepCount > 0
14573
+ ) {
14574
+ this.sleepBoundaryIndex += existing.sleepCount;
14575
+ }
14412
14576
  return existing.output as T;
14413
14577
  }
14414
14578
 
14579
+ const sleepBoundaryIndexBefore = this.sleepBoundaryIndex;
14415
14580
  const output = await run();
14416
14581
  assertJsonSerializableStepOutput(normalizedKey, output);
14417
14582
  // A caught durable suspension is still the invocation's outcome. Never
@@ -14424,6 +14589,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14424
14589
  kind: 'step',
14425
14590
  stepId: normalizedKey,
14426
14591
  output,
14592
+ sleepCount: this.sleepBoundaryIndex - sleepBoundaryIndexBefore,
14427
14593
  completedAt: Date.now(),
14428
14594
  },
14429
14595
  };
@@ -18077,6 +18243,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18077
18243
  let providerCallStartedAt: number | null = null;
18078
18244
  let providerCallElapsedMs: number | null = null;
18079
18245
  let integrationFetchStartedAt: number | null = null;
18246
+ let providerRequestsInFlightAt429: number | null = null;
18247
+ let activeProviderRequest: {
18248
+ inFlightCount: () => number;
18249
+ release: () => void;
18250
+ } | null = null;
18080
18251
  let fetchDispatched = false;
18081
18252
  // A provider 429 must be reported while its server-minted
18082
18253
  // admission lease is still live. Keep its parsed outcome here so
@@ -18342,6 +18513,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18342
18513
  providerCallStartedAt =
18343
18514
  dispatchedProviderCallStartedAt;
18344
18515
  fetchDispatched = true;
18516
+ activeProviderRequest =
18517
+ this.beginProviderRequest(provider);
18345
18518
  const dispatchedResponse = await fetch(url, {
18346
18519
  method: 'POST',
18347
18520
  signal: abortController?.signal,
@@ -18386,6 +18559,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18386
18559
  // headers arrive; decoded body data stays scoped until
18387
18560
  // it has been read and verified below.
18388
18561
  response = dispatchedResponse;
18562
+ if (dispatchedResponse.status === 429) {
18563
+ providerRequestsInFlightAt429 =
18564
+ activeProviderRequest?.inFlightCount() ?? null;
18565
+ }
18389
18566
  const dispatchedFetchToHeadersMs =
18390
18567
  Date.now() - fetchedAt;
18391
18568
  // The response writer owns these named wall times.
@@ -18533,6 +18710,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18533
18710
  }),
18534
18711
  };
18535
18712
  }
18713
+ activeProviderRequest?.release();
18714
+ activeProviderRequest = null;
18536
18715
  return {
18537
18716
  response: dispatchedResponse,
18538
18717
  responseData: dispatchedResponseData,
@@ -18599,6 +18778,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18599
18778
  } catch (error) {
18600
18779
  // `request()` can fail while waiting for its transport
18601
18780
  // lease, before its beforeRelease callback runs.
18781
+ activeProviderRequest?.release();
18782
+ activeProviderRequest = null;
18602
18783
  releaseProviderExecution();
18603
18784
  throw error;
18604
18785
  }
@@ -18657,6 +18838,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18657
18838
  }
18658
18839
  if (error instanceof ProviderExhaustedError) {
18659
18840
  const retryAtMs = Date.parse(error.retryAt);
18841
+ const providerFailure =
18842
+ error.cause instanceof ToolExecutionError
18843
+ ? error.cause
18844
+ : null;
18660
18845
  throw createToolHttpError(
18661
18846
  toolErrorSchemaVersion,
18662
18847
  error.message,
@@ -18678,6 +18863,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18678
18863
  : null,
18679
18864
  networkKind: null,
18680
18865
  networkScope: null,
18866
+ ...(providerFailure?.publicDetails
18867
+ ? { publicDetails: providerFailure.publicDetails }
18868
+ : {}),
18681
18869
  },
18682
18870
  );
18683
18871
  }
@@ -18852,6 +19040,45 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18852
19040
  ? { providerLatencyMs: providerCallElapsedMs }
18853
19041
  : {}),
18854
19042
  });
19043
+ let surfacedError = failure.error;
19044
+ if (
19045
+ response.status === 429 &&
19046
+ providerRequestsInFlightAt429 !== null &&
19047
+ failure.error instanceof ToolExecutionError &&
19048
+ failure.error.origin === 'provider'
19049
+ ) {
19050
+ const providerFailure = failure.error as typeof failure.error &
19051
+ ToolExecutionError;
19052
+ surfacedError = createToolHttpError(
19053
+ toolErrorSchemaVersion,
19054
+ providerFailure.message,
19055
+ providerFailure.billing,
19056
+ providerFailure.status,
19057
+ providerFailure.receiptFailureKind,
19058
+ {
19059
+ toolId: providerFailure.toolId,
19060
+ provider: providerFailure.provider,
19061
+ operation: providerFailure.operation,
19062
+ code: providerFailure.code ?? null,
19063
+ origin: providerFailure.origin,
19064
+ category: providerFailure.category,
19065
+ retryable: providerFailure.retryable,
19066
+ statusCode: providerFailure.statusCode ?? null,
19067
+ requestId: providerFailure.requestId,
19068
+ retryAfterMs: providerFailure.retryAfterMs,
19069
+ networkKind: providerFailure.networkKind,
19070
+ networkScope: providerFailure.networkScope,
19071
+ publicDetails: {
19072
+ ...(providerFailure.publicDetails ?? {}),
19073
+ inFlightProviderRequestsAt429:
19074
+ providerRequestsInFlightAt429,
19075
+ providerConcurrencyScope:
19076
+ 'same_provider_in_play_execution',
19077
+ includesFailedRequest: true,
19078
+ },
19079
+ },
19080
+ );
19081
+ }
18855
19082
  if (failure.reason === 'concurrency_backpressure') {
18856
19083
  // Our own relay shed. The classifier keeps it out of provider
18857
19084
  // backpressure (origin is not the provider); count it apart
@@ -18887,7 +19114,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18887
19114
  throw new ProviderExhaustedError({
18888
19115
  provider,
18889
19116
  retryAtMs: Date.now() + failure.backpressureDelayMs,
18890
- cause: failure.error,
19117
+ cause: surfacedError,
18891
19118
  });
18892
19119
  }
18893
19120
  }
@@ -18991,11 +19218,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
18991
19218
  // or operator sees; carry the identifiers that locate the
18992
19219
  // upstream request without the debug transport trace.
18993
19220
  this.runtimeLog(
18994
- `${failure.error.message} (request_id=${deeplineRequestId ?? 'none'} ` +
19221
+ `${surfacedError.message} (request_id=${deeplineRequestId ?? 'none'} ` +
18995
19222
  `gateway=${transportGatewayOriginForDiagnostic(url)} attempt=${httpFailureAttempt})`,
18996
19223
  { level: 'error' },
18997
19224
  );
18998
- throw failure.error;
19225
+ throw surfacedError;
18999
19226
  }
19000
19227
 
19001
19228
  if (!responseData) {
@@ -675,6 +675,11 @@ export type IntegrationEventWaitBoundary = {
675
675
  channel: string;
676
676
  ts: string;
677
677
  };
678
+ /** Provider identity used to finalize a durable wait after replay. */
679
+ requestKey?: string;
680
+ interactionId?: string;
681
+ /** A previously completed identical request can resolve without parking. */
682
+ resolvedOutput?: Record<string, unknown>;
678
683
  /**
679
684
  * Optional control-plane timeout behavior. Missing preserves the legacy
680
685
  * "return the timed_out result" replay behavior for older app deployments.
@@ -703,6 +708,7 @@ export type IntegrationEventWaitHandler = {
703
708
  payload: Record<string, unknown>;
704
709
  context: IntegrationEventWaitRuntimeContext;
705
710
  boundary: IntegrationEventWaitBoundary;
711
+ force?: boolean;
706
712
  }) => Promise<IntegrationEventWaitBoundary>;
707
713
  /**
708
714
  * Runs during the resumed execution only when this boundary reached its
@@ -884,6 +890,10 @@ export interface ContextOptions {
884
890
  staticPipeline?: PlayStaticPipeline | null;
885
891
  }) => Promise<void | {
886
892
  updated: number;
893
+ /**
894
+ * Row-level latest-view conflicts. These rows are omitted from this map's
895
+ * returned dataset, while the Play and independent rows continue.
896
+ */
887
897
  staleDroppedKeys?: string[];
888
898
  /**
889
899
  * Keys initially fenced by a terminal write but verified by the
@@ -892,12 +902,10 @@ export interface ContextOptions {
892
902
  */
893
903
  recoveredStaleKeys?: string[];
894
904
  /**
895
- * Stale keys that prove this attempt no longer owns the sheet: the rows
896
- * carry a different write version than the one this attempt admitted them
897
- * under. A handler that returns any (instead of throwing
898
- * `RuntimeSheetAttemptSupersededError` itself) makes the Context throw it:
899
- * the map fails and the persistence latch trips, so no further provider
900
- * call is spent on rows that can never be persisted.
905
+ * Legacy attempt-wide stop signal. Ordinary row-level latest-view
906
+ * conflicts must use staleDroppedKeys; returning supersededKeys explicitly
907
+ * means this attempt cannot continue and makes the Context throw
908
+ * RuntimeSheetAttemptSupersededError to trip the persistence latch.
901
909
  */
902
910
  supersededKeys?: string[];
903
911
  }>;
@@ -1174,6 +1182,8 @@ export interface PlayCheckpoint {
1174
1182
  channel: string;
1175
1183
  ts: string;
1176
1184
  };
1185
+ requestKey?: string;
1186
+ interactionId?: string;
1177
1187
  /**
1178
1188
  * Control-plane timeout behavior captured when the wait was armed.
1179
1189
  * Missing fields preserve legacy checkpoint replay behavior.
@@ -1193,6 +1203,15 @@ export interface PlayCheckpoint {
1193
1203
  kind: 'step';
1194
1204
  stepId: string;
1195
1205
  output?: unknown;
1206
+ /**
1207
+ * Number of authored ctx.sleep calls evaluated inside this step.
1208
+ *
1209
+ * A replay can recover the step output without executing its callback.
1210
+ * Keeping this count lets the surrounding execution advance its
1211
+ * run-local sleep cursor, so a later sleep cannot accidentally reuse a
1212
+ * nested sleep boundary that was hidden by the cached step.
1213
+ */
1214
+ sleepCount?: number;
1196
1215
  completedAt?: number;
1197
1216
  }
1198
1217
  >;