deepline 0.3.73 → 0.3.75

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 (37) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +279 -21
  2. package/dist/bundling-sources/sdk/src/config.ts +21 -3
  3. package/dist/bundling-sources/sdk/src/index.ts +1 -0
  4. package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +1 -1
  5. package/dist/bundling-sources/sdk/src/play.ts +3 -6
  6. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  7. package/dist/bundling-sources/sdk/src/types.ts +3 -2
  8. package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +186 -0
  9. package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +46 -3
  10. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +213 -55
  11. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +243 -356
  12. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +18 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +13 -32
  14. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +62 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +122 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +7 -2
  18. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +11 -2
  19. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +1 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +16 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +1 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/testkit.ts +5 -4
  23. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +12 -2
  24. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +1 -1
  25. package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +74 -0
  26. package/dist/cli/index.js +919 -168
  27. package/dist/cli/index.mjs +919 -168
  28. package/dist/{compiler-manifest-CMNgA_JQ.d.mts → compiler-manifest-Dbov16F7.d.mts} +1 -1
  29. package/dist/{compiler-manifest-CMNgA_JQ.d.ts → compiler-manifest-Dbov16F7.d.ts} +1 -1
  30. package/dist/index.d.mts +177 -24
  31. package/dist/index.d.ts +177 -24
  32. package/dist/index.js +110 -13
  33. package/dist/index.mjs +110 -13
  34. package/dist/install-integrity.json +6 -2
  35. package/dist/plays/bundle-play-file.d.mts +2 -2
  36. package/dist/plays/bundle-play-file.d.ts +2 -2
  37. package/package.json +1 -1
@@ -100,7 +100,6 @@ import {
100
100
  import { CTX_FETCH_EGRESS_TOOL_ID } from './builtin-pacing';
101
101
  import {
102
102
  legacyRawFromToolResponseRawV2,
103
- normalizeToolResponseContract,
104
103
  providerMetaFromToolResponseRawV2,
105
104
  RAW_V2_TOOL_RESPONSE_CONTRACT,
106
105
  type ToolResponseContract,
@@ -115,7 +114,6 @@ import {
115
114
  isProviderUnavailable,
116
115
  serializeToolExecutionFailure,
117
116
  ToolExecutionError,
118
- TOOL_EXECUTION_ERROR_SCHEMA_VERSION,
119
117
  TOOL_EXECUTION_ERROR_SCHEMA_HEADER,
120
118
  type ToolExecutionErrorSchemaVersion,
121
119
  type ToolExecutionFailureV1,
@@ -177,7 +175,7 @@ import {
177
175
  buildDurableToolCallCacheKey,
178
176
  buildLegacyRevisionedDurableToolCallCacheKey,
179
177
  buildDurableToolPayloadProviderIdempotencyKey,
180
- buildDurableToolProviderIdempotencyKey,
178
+ buildProviderOperationIdempotencyKey,
181
179
  buildDurableToolReceiptPrefix,
182
180
  resolveDurableCallCachePolicy,
183
181
  } from './durable-call-cache';
@@ -195,6 +193,10 @@ import {
195
193
  type PlayAuthoringRunScope,
196
194
  type PlayAuthoringRuntimeContext,
197
195
  } from '../plays/authoring-contract';
196
+ import {
197
+ resolvePlayArtifactRuntimeContract,
198
+ type PlayArtifactRuntimeContract,
199
+ } from './play-artifact-runtime-contract';
198
200
  import {
199
201
  LOG_LEVELS,
200
202
  formatCtxFetchHttpFailureDiagnostic,
@@ -217,11 +219,7 @@ import {
217
219
  type DurableReceiptExecutionStore,
218
220
  } from './durable-receipt-execution';
219
221
  import {
220
- asyncOperationDurableStartInput,
221
222
  asyncOperationShouldWait,
222
- classifyAsyncOperationResult,
223
- executeAsyncOperationLifecycle,
224
- resolveAsyncOperationPolling,
225
223
  type AsyncOperationContractWire,
226
224
  } from './async-operation';
227
225
  import {
@@ -407,14 +405,8 @@ type InlineCompositionStore = {
407
405
  governor: PlayExecutionGovernor;
408
406
  playName: string;
409
407
  staticPipeline: ContextOptions['staticPipeline'];
410
- /** Immutable error contract pinned by the child play artifact. */
411
- toolErrorSchemaVersion: ToolExecutionErrorSchemaVersion;
412
- /** Immutable authoring semantics pinned by the child play artifact. */
413
- authoringContractEdition: PlayAuthoringContractEdition;
414
- /** Execute response contract pinned by the child artifact. */
415
- toolResponseContract: ToolResponseContract;
416
- /** Explicit response-transform revision used for durable receipt identity. */
417
- toolResponseReceiptRevision?: string;
408
+ /** Immutable compatibility projection of the child Play artifact. */
409
+ runtimeContract: PlayArtifactRuntimeContract;
418
410
  };
419
411
  const inlineCompositionContext =
420
412
  new AsyncLocalStorage<InlineCompositionStore>();
@@ -1089,7 +1081,10 @@ export function resolveToolRuntimeTimeoutMs(
1089
1081
  authoringContractEdition: PlayAuthoringContractEdition = PLAY_AUTHORING_CONTRACT_EDITION,
1090
1082
  ): number | undefined {
1091
1083
  if (requestedTimeoutMs !== undefined) {
1092
- if (authoringContractEdition >= 2) {
1084
+ if (
1085
+ resolvePlayArtifactRuntimeContract({ authoringContractEdition }).semantics
1086
+ .validation === 'strict'
1087
+ ) {
1093
1088
  validatePlayAuthoringField(
1094
1089
  'ctx.tools.execute.timeoutMs',
1095
1090
  requestedTimeoutMs,
@@ -1249,27 +1244,6 @@ function publicToolResponseEnvelope(value: unknown): {
1249
1244
  };
1250
1245
  }
1251
1246
 
1252
- function materializedAsyncToolResponse(
1253
- response: ParsedToolExecuteResponse['toolResponse'] | undefined,
1254
- result: unknown,
1255
- ): ParsedToolExecuteResponse['toolResponse'] {
1256
- const hasRawV2 =
1257
- response !== undefined &&
1258
- Object.prototype.hasOwnProperty.call(response, 'rawV2');
1259
- const materializedRawV2 =
1260
- response?.view === 'data'
1261
- ? {
1262
- ...(recordOrNull(response.rawV2) ?? {}),
1263
- data: result,
1264
- }
1265
- : result;
1266
- return {
1267
- ...(response ?? {}),
1268
- raw: result,
1269
- ...(hasRawV2 ? { rawV2: materializedRawV2 } : {}),
1270
- };
1271
- }
1272
-
1273
1247
  /**
1274
1248
  * A batched provider request returns one envelope for several logical source
1275
1249
  * tool calls. Each source call persists only its own item-shaped canonical
@@ -1399,13 +1373,11 @@ type ToolExecutionApiOptions = {
1399
1373
  /** The receipt key is a completed-result cache for this read-only call. */
1400
1374
  completedResultCache?: boolean;
1401
1375
  executionAuthScopeDigest?: string | null;
1402
- /**
1403
- * Historical receipt identity used for the API's provider/billing
1404
- * idempotency receipt. This can differ from the worker-owned durable cache
1405
- * receipt while a response contract migrates.
1406
- */
1407
- providerIdempotencyReceiptKey?: string | null;
1408
1376
  providerIdempotencyKey?: string | null;
1377
+ /** Exact forced-member identities that participate in a native batch key. */
1378
+ providerOperationForceRefreshIdentities?: readonly string[];
1379
+ /** Presentation-only cache identity paired with a separate execution fence. */
1380
+ providerIdempotencyReceiptKey?: string | null;
1409
1381
  receiptLeaseExpiresAt?: string | null;
1410
1382
  beforeProviderCall?: () => Promise<void> | void;
1411
1383
  heartbeatReceipt?: () => Promise<void> | void;
@@ -1430,6 +1402,13 @@ type ToolExecutionApiOptions = {
1430
1402
  };
1431
1403
  /** Internal recursion guard for lifecycle poll/finish calls. */
1432
1404
  skipAsyncOperationResolution?: boolean;
1405
+ /**
1406
+ * A status/result request belonging to a live async lifecycle. It must not
1407
+ * create a result receipt or provider idempotency identity from a parent
1408
+ * cache entry: the job handle is process-local, while the outer tool-call
1409
+ * cache stores only its terminal materialized output.
1410
+ */
1411
+ uncachedAsyncLifecycleCall?: boolean;
1433
1412
  };
1434
1413
  const IN_MEMORY_STEP_RESULT_PREVIEW_LIMIT = 25;
1435
1414
  /**
@@ -2110,11 +2089,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2110
2089
  string,
2111
2090
  { resolve: (value: unknown) => void; reject: (reason: unknown) => void }
2112
2091
  >();
2113
- private readonly asyncOperationCapacity = new Map<
2114
- string,
2115
- { active: number; waiters: Array<() => void> }
2116
- >();
2117
2092
  #options: ContextOptions;
2093
+ private readonly rootRuntimeContract: PlayArtifactRuntimeContract;
2118
2094
  private readonly executionScope: RunExecutionScope;
2119
2095
  private logBuffer: string[] = [];
2120
2096
  private readonly logRateLimitState = new Map<
@@ -2160,6 +2136,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2160
2136
  boundaryId: string;
2161
2137
  eventKey: string;
2162
2138
  timeoutMs: number;
2139
+ messageRef?: { channel: string; ts: string };
2163
2140
  }> = [];
2164
2141
  private processedRowCount = 0;
2165
2142
  private sleepBoundaryIndex = 0;
@@ -2253,7 +2230,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2253
2230
  assertNoSecretTaint(request.input, 'ctx.tools.execute input');
2254
2231
  if (
2255
2232
  request.timeoutMs !== undefined &&
2256
- this.currentAuthoringContractEdition >= 2
2233
+ this.currentRuntimeSemantics.validation === 'strict'
2257
2234
  ) {
2258
2235
  validatePlayAuthoringField(
2259
2236
  'ctx.tools.execute.timeoutMs',
@@ -2262,7 +2239,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2262
2239
  }
2263
2240
  if (
2264
2241
  request.receiptWaitMs !== undefined &&
2265
- this.currentAuthoringContractEdition >= 2
2242
+ this.currentRuntimeSemantics.validation === 'strict'
2266
2243
  ) {
2267
2244
  validatePlayAuthoringField(
2268
2245
  'ctx.tools.execute.receiptWaitMs',
@@ -2406,7 +2383,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2406
2383
  }
2407
2384
  const handle = createSecretHandle(name.trim());
2408
2385
  const value =
2409
- this.currentAuthoringContractEdition >= 4
2386
+ this.currentRuntimeSemantics.secrets.value === 'plaintext_promise'
2410
2387
  ? createPlaintextSecretPromise(handle.name, () =>
2411
2388
  this.resolveSecretHandle(handle),
2412
2389
  )
@@ -2430,6 +2407,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2430
2407
  };
2431
2408
 
2432
2409
  constructor(options: ContextOptions) {
2410
+ this.rootRuntimeContract =
2411
+ options.artifactRuntimeContract ??
2412
+ resolvePlayArtifactRuntimeContract({
2413
+ authoringContractEdition: options.authoringContractEdition,
2414
+ toolResponseContract: options.toolResponseContract,
2415
+ toolResponseReceiptRevision: options.toolResponseReceiptRevision,
2416
+ });
2433
2417
  const fixtureBehavior = validateFixtureBehavior(options.fixtureBehavior);
2434
2418
  if (fixtureBehavior.ok === false) {
2435
2419
  throw new Error(fixtureBehavior.error);
@@ -2749,20 +2733,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2749
2733
 
2750
2734
  /**
2751
2735
  * Inline children share this context's queues, but not its artifact contract.
2752
- * Each child tool call must preserve the error shape pinned when that child
2753
- * was published, even when the parent was published under another schema.
2736
+ * Each child uses its own admitted semantics and response projection even
2737
+ * when the parent was published under another artifact contract.
2754
2738
  */
2755
2739
  private get currentToolErrorSchemaVersion(): ToolExecutionErrorSchemaVersion {
2756
- // Historical artifacts must not turn durable structured failures back into
2757
- // strings. Every runtime tool call and receipt rehydration uses v1.
2758
- return TOOL_EXECUTION_ERROR_SCHEMA_VERSION;
2740
+ return this.currentRuntimeContract.toolErrorSchemaVersion;
2759
2741
  }
2760
2742
 
2761
2743
  private get currentToolResponseContract(): ToolResponseContract {
2762
- return normalizeToolResponseContract(
2763
- this.activeInlineComposition?.toolResponseContract ??
2764
- this.#options.toolResponseContract,
2765
- );
2744
+ return this.currentRuntimeContract.toolResponseContract;
2766
2745
  }
2767
2746
 
2768
2747
  /**
@@ -2775,6 +2754,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2775
2754
  private get hasPinnedToolResponseContract(): boolean {
2776
2755
  return (
2777
2756
  this.activeInlineComposition !== null ||
2757
+ this.#options.artifactRuntimeContract !== undefined ||
2778
2758
  this.#options.toolResponseContract !== undefined
2779
2759
  );
2780
2760
  }
@@ -2784,20 +2764,23 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2784
2764
  * changes the completed-result cache identity.
2785
2765
  */
2786
2766
  private get currentToolResponseReceiptRevision(): string | undefined {
2787
- if (this.activeInlineComposition) {
2788
- return this.activeInlineComposition.toolResponseReceiptRevision;
2789
- }
2790
- return this.#options.toolResponseReceiptRevision;
2767
+ return this.currentRuntimeContract.toolResponseReceiptRevision;
2791
2768
  }
2792
2769
 
2793
2770
  private get currentAuthoringContractEdition(): PlayAuthoringContractEdition {
2771
+ return this.currentRuntimeContract.semantics.authoringContractEdition;
2772
+ }
2773
+
2774
+ private get currentRuntimeContract(): PlayArtifactRuntimeContract {
2794
2775
  return (
2795
- this.activeInlineComposition?.authoringContractEdition ??
2796
- this.#options.authoringContractEdition ??
2797
- PLAY_AUTHORING_CONTRACT_EDITION
2776
+ this.activeInlineComposition?.runtimeContract ?? this.rootRuntimeContract
2798
2777
  );
2799
2778
  }
2800
2779
 
2780
+ private get currentRuntimeSemantics() {
2781
+ return this.currentRuntimeContract.semantics;
2782
+ }
2783
+
2801
2784
  private recordPlayCallStep(input: {
2802
2785
  playId: string;
2803
2786
  execution?: 'inline';
@@ -9326,7 +9309,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9326
9309
  }
9327
9310
  if (
9328
9311
  options?.timeoutMs !== undefined &&
9329
- this.currentAuthoringContractEdition >= 2
9312
+ this.currentRuntimeSemantics.validation === 'strict'
9330
9313
  ) {
9331
9314
  validatePlayAuthoringField(
9332
9315
  'ctx.tools.execute.timeoutMs',
@@ -9335,7 +9318,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9335
9318
  }
9336
9319
  if (
9337
9320
  options?.receiptWaitMs !== undefined &&
9338
- this.currentAuthoringContractEdition >= 2
9321
+ this.currentRuntimeSemantics.validation === 'strict'
9339
9322
  ) {
9340
9323
  validatePlayAuthoringField(
9341
9324
  'ctx.tools.execute.receiptWaitMs',
@@ -9382,9 +9365,18 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9382
9365
  executionAuthScopeDigest,
9383
9366
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
9384
9367
  });
9385
- // Only explicitly fenced operations use this key as a provider identity.
9386
- // Ordinary calls use it solely as their completed-result cache key.
9387
- let providerIdempotencyKeyBase = durableCacheKey;
9368
+ const providerActionVersion =
9369
+ (await this.#options.getToolActionCacheVersion?.(toolId))?.trim() ?? '';
9370
+ // A provider operation is keyed by its requested semantic payload. The
9371
+ // durable receipt below remains a completed-result cache only.
9372
+ let providerIdempotencyKeyBase =
9373
+ buildDurableToolPayloadProviderIdempotencyKey({
9374
+ orgId: this.#options.orgId,
9375
+ toolId,
9376
+ executionAuthScopeDigest,
9377
+ providerActionVersion,
9378
+ payload: input,
9379
+ });
9388
9380
  let directResultReceiptKey = durableCacheKey;
9389
9381
  // A raw-v2 artifact must never read an older receipt that cannot render
9390
9382
  // raw-v2. If both the primary and its revisioned recovery receipt are
@@ -9664,17 +9656,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9664
9656
  ...(toolRetryPolicy?.requiresExecutionFence === true
9665
9657
  ? {
9666
9658
  providerIdempotencyReceiptKey:
9667
- cacheableToolResult && operationKey
9668
- ? operationKey
9659
+ cacheableToolResult && resultReceiptKey
9660
+ ? resultReceiptKey
9669
9661
  : physicalDirectKey,
9670
9662
  providerIdempotencyKey:
9671
9663
  this.providerIdempotencyKeyForToolCall({
9672
- cacheKey:
9664
+ operationKey:
9673
9665
  useDirectResultReceipts && operationKey
9674
9666
  ? providerIdempotencyKeyBase
9675
9667
  : physicalDirectKey,
9676
9668
  force: cacheableToolResult && toolCachePolicy.force,
9677
- leaseId: directReceiptLeaseId,
9678
9669
  logicalCallId,
9679
9670
  }),
9680
9671
  }
@@ -9756,9 +9747,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9756
9747
  dispatcher,
9757
9748
  })
9758
9749
  : null;
9759
- const providerActionVersion =
9760
- (await this.#options.getToolActionCacheVersion?.(toolId))?.trim() ??
9761
- '';
9762
9750
  const call: Omit<ToolCallInput, 'compatibility'> = {
9763
9751
  scope: {
9764
9752
  organizationId: this.#options.orgId ?? 'unknown-org',
@@ -9987,7 +9975,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9987
9975
  executionAuthScopeDigest,
9988
9976
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
9989
9977
  });
9990
- providerIdempotencyKeyBase = durableCacheKey;
9978
+ providerIdempotencyKeyBase =
9979
+ buildDurableToolPayloadProviderIdempotencyKey({
9980
+ orgId: this.#options.orgId,
9981
+ toolId,
9982
+ executionAuthScopeDigest,
9983
+ providerActionVersion,
9984
+ payload: input,
9985
+ });
9991
9986
  directResultReceiptKey = durableCacheKey;
9992
9987
  bypassDirectResultCache = false;
9993
9988
  checkpointCacheKeys.splice(
@@ -10042,6 +10037,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10042
10037
  const existing =
10043
10038
  this.checkpoint.resolvedBoundaries?.[preparedBoundary.boundaryId];
10044
10039
  if (existing?.kind === 'integration_event' && 'output' in existing) {
10040
+ const output = recordOrNull(existing.output);
10041
+ if (output?.timed_out === true) {
10042
+ await handler.onTimeout?.({
10043
+ payload: input,
10044
+ context: eventContext,
10045
+ boundary: {
10046
+ boundaryId: preparedBoundary.boundaryId,
10047
+ eventKey: preparedBoundary.eventKey,
10048
+ timeoutMs: preparedBoundary.timeoutMs,
10049
+ provider: existing.provider ?? handler.provider,
10050
+ toolId: existing.toolId ?? handler.toolId,
10051
+ ...(existing.messageRef ? { messageRef: existing.messageRef } : {}),
10052
+ },
10053
+ });
10054
+ }
10045
10055
  this.runtimeLog(
10046
10056
  `Integration event ${preparedBoundary.boundaryId}: recovered response from checkpoint`,
10047
10057
  );
@@ -10097,6 +10107,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10097
10107
  boundaryId: boundary.boundaryId,
10098
10108
  eventKey: boundary.eventKey,
10099
10109
  timeoutMs: boundary.timeoutMs,
10110
+ ...(boundary.messageRef ? { messageRef: boundary.messageRef } : {}),
10100
10111
  });
10101
10112
  }
10102
10113
 
@@ -10105,6 +10116,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10105
10116
  boundaryId: boundary.boundaryId,
10106
10117
  eventKey: boundary.eventKey,
10107
10118
  timeoutMs: boundary.timeoutMs,
10119
+ ...(boundary.messageRef ? { messageRef: boundary.messageRef } : {}),
10108
10120
  });
10109
10121
  }
10110
10122
 
@@ -10164,8 +10176,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10164
10176
  const childCompatibility = normalizePlayContractCompatibility(
10165
10177
  childCompatibilitySnapshot,
10166
10178
  );
10167
- const childToolErrorSchemaVersion =
10168
- childCompatibility.toolErrorSchemaVersion;
10169
10179
  const childToolResponseContract = childCompatibility.toolResponseContract;
10170
10180
  const childToolResponseReceiptRevision =
10171
10181
  childCompatibility.toolResponseReceiptRevision;
@@ -10240,11 +10250,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10240
10250
  governor: inlineChildGovernor,
10241
10251
  playName: resolvedName,
10242
10252
  staticPipeline: resolvedPlay.staticPipeline ?? null,
10243
- toolErrorSchemaVersion: childToolErrorSchemaVersion,
10244
- toolResponseContract: childToolResponseContract,
10245
- toolResponseReceiptRevision: childToolResponseReceiptRevision,
10246
- authoringContractEdition:
10247
- childCompatibility.authoringContractEdition,
10253
+ runtimeContract: resolvePlayArtifactRuntimeContract({
10254
+ authoringContractEdition:
10255
+ childCompatibility.authoringContractEdition,
10256
+ toolResponseContract: childToolResponseContract,
10257
+ toolResponseReceiptRevision: childToolResponseReceiptRevision,
10258
+ }),
10248
10259
  },
10249
10260
  () => this.executeResolvedPlay(resolvedPlay, this, input),
10250
10261
  );
@@ -10553,7 +10564,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10553
10564
  async sleep(ms: number): Promise<void> {
10554
10565
  this.assertInlineChildContract('suspending_child');
10555
10566
  const delayMs =
10556
- this.currentAuthoringContractEdition >= 2
10567
+ this.currentRuntimeSemantics.validation === 'strict'
10557
10568
  ? (validatePlayAuthoringField('ctx.sleep.ms', ms), ms)
10558
10569
  : Math.max(0, Math.round(ms));
10559
10570
  if (this.#options.durableBoundaries) {
@@ -10679,14 +10690,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10679
10690
  ...secretHeaderMarkers,
10680
10691
  },
10681
10692
  row: rowFetchScope,
10682
- // Edition 5 changes non-2xx responses from cached values to
10683
- // non-cacheable errors. Partition those receipts from editions
10684
- // 1–4 so a republished artifact makes a fresh request instead of
10685
- // inheriting a legacy error record.
10686
- ...(this.currentAuthoringContractEdition >= 5
10693
+ // A pinned fetch policy that turns non-2xx responses into typed,
10694
+ // non-cacheable errors needs its own receipt epoch. Preserve the
10695
+ // historical epoch selected by the artifact rather than deriving a
10696
+ // new cache key from every future authoring edition.
10697
+ ...(this.currentRuntimeSemantics.fetch.receiptIdentityEdition !==
10698
+ null
10687
10699
  ? {
10688
10700
  authoringContractEdition:
10689
- this.currentAuthoringContractEdition,
10701
+ this.currentRuntimeSemantics.fetch.receiptIdentityEdition,
10690
10702
  }
10691
10703
  : {}),
10692
10704
  }),
@@ -10702,7 +10714,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10702
10714
  httpStatus: output.status,
10703
10715
  });
10704
10716
  }
10705
- if (!output.ok && this.currentAuthoringContractEdition >= 5) {
10717
+ if (
10718
+ !output.ok &&
10719
+ this.currentRuntimeSemantics.fetch.httpFailure ===
10720
+ 'throw_typed_error'
10721
+ ) {
10706
10722
  throw new CtxFetchHttpError(output);
10707
10723
  }
10708
10724
  return output;
@@ -10710,7 +10726,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10710
10726
  shouldPersistFailure: (error) =>
10711
10727
  !(
10712
10728
  error instanceof CtxFetchHttpError &&
10713
- this.currentAuthoringContractEdition >= 5
10729
+ !this.currentRuntimeSemantics.fetch.persistHttpFailure
10714
10730
  ),
10715
10731
  execute: async ({ retainExternalCallSlot }) => {
10716
10732
  const secretHeaders = await this.resolveSecretAuth(secretAuth);
@@ -10914,11 +10930,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10914
10930
  });
10915
10931
  }
10916
10932
 
10917
- // Edition 5 adopts normal fetch semantics: a non-2xx response is
10918
- // a failed durable operation. Throw before checkpoint/receipt
10919
- // completion so the failure is never cached. Editions 1–4 retain
10920
- // their response-record behavior for the same upstream response.
10921
- if (!output.ok && this.currentAuthoringContractEdition >= 5) {
10933
+ // The artifact-pinned fetch policy decides whether a non-2xx
10934
+ // response is a returned record or a failed durable operation.
10935
+ // Throw before checkpoint/receipt completion so the throwing
10936
+ // policy never caches its failure.
10937
+ if (
10938
+ !output.ok &&
10939
+ this.currentRuntimeSemantics.fetch.httpFailure ===
10940
+ 'throw_typed_error'
10941
+ ) {
10922
10942
  throw new CtxFetchHttpError(output);
10923
10943
  }
10924
10944
 
@@ -11310,15 +11330,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
11310
11330
  async () =>
11311
11331
  await recordsFor(
11312
11332
  resultReceiptKeys,
11313
- await this.getRuntimeStepReceipts(
11314
- [...new Set(
11333
+ await this.getRuntimeStepReceipts([
11334
+ ...new Set(
11315
11335
  resultReceiptKeys.flatMap((receiptKey) => [
11316
11336
  receiptKey,
11317
11337
  ...(input.requestForReceipt.get(receiptKey)
11318
11338
  ?.legacyReceiptKeys ?? []),
11319
11339
  ]),
11320
- )],
11321
- ),
11340
+ ),
11341
+ ]),
11322
11342
  ),
11323
11343
  ),
11324
11344
  complete: async ({ receipts }) => {
@@ -12575,7 +12595,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12575
12595
  dispatchPayload: owner.input,
12576
12596
  resultReceiptKey: receiptKey,
12577
12597
  providerOperationKey:
12578
- owner.providerIdempotencyKeyBase ?? owner.cacheKey,
12598
+ this.providerOperationKeyForQueuedToolCall(owner),
12579
12599
  apiOptions: {
12580
12600
  beforeProviderCall: () =>
12581
12601
  this.assertRuntimeToolReceiptOwnership([owner]),
@@ -12583,18 +12603,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12583
12603
  playNodeScope: playNodeScopeForToolCallRequest(owner),
12584
12604
  executionAuthScopeDigest:
12585
12605
  owner.executionAuthScopeDigest,
12586
- providerIdempotencyReceiptKey:
12587
- owner.providerIdempotencyKeyBase ?? owner.cacheKey,
12588
12606
  receiptLeaseExpiresAt: owner.receiptLeaseExpiresAt,
12589
12607
  heartbeatReceipt: () =>
12590
12608
  this.renewRuntimeToolReceiptOwnership([owner]),
12591
12609
  providerIdempotencyKey:
12592
12610
  this.providerIdempotencyKeyForToolCall({
12593
- cacheKey:
12594
- owner.providerIdempotencyKeyBase ??
12595
- owner.cacheKey,
12611
+ operationKey:
12612
+ this.providerOperationKeyForQueuedToolCall(owner),
12596
12613
  force: owner.force === true,
12597
- leaseId: owner.receiptLeaseId,
12598
12614
  logicalCallId: owner.logicalCallId,
12599
12615
  }),
12600
12616
  timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([
@@ -12928,7 +12944,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12928
12944
  toolId: batch.batchOperation,
12929
12945
  }),
12930
12946
  });
12931
- const aggregateProviderIdempotencyReceiptKey =
12947
+ const aggregateForceRefreshIdentities = batch.memberRequests
12948
+ .filter((request) => request.force === true)
12949
+ .map((request) =>
12950
+ this.providerIdempotencyKeyForToolCall({
12951
+ operationKey:
12952
+ this.providerOperationKeyForQueuedToolCall(request),
12953
+ force: true,
12954
+ logicalCallId: request.logicalCallId,
12955
+ }),
12956
+ );
12957
+ const aggregateProviderOperationKey =
12932
12958
  buildDurableToolPayloadProviderIdempotencyKey({
12933
12959
  orgId: this.#options.orgId,
12934
12960
  toolId: batch.batchOperation,
@@ -12942,18 +12968,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12942
12968
  )
12943
12969
  )?.trim() ?? 'unversioned',
12944
12970
  payload: batch.batchPayload,
12945
- forceRefreshIdentities: batch.memberRequests
12946
- .filter((request) => request.force === true)
12947
- .map((request) =>
12948
- this.providerIdempotencyKeyForToolCall({
12949
- cacheKey:
12950
- request.providerIdempotencyKeyBase ??
12951
- request.cacheKey,
12952
- force: true,
12953
- leaseId: request.receiptLeaseId,
12954
- logicalCallId: request.logicalCallId,
12955
- }),
12956
- ),
12971
+ forceRefreshIdentities: aggregateForceRefreshIdentities,
12957
12972
  });
12958
12973
  let releaseToolSlot: () => void = () => undefined;
12959
12974
  try {
@@ -12963,8 +12978,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12963
12978
  dispatchToolId: batch.batchOperation,
12964
12979
  dispatchPayload: batch.batchPayload,
12965
12980
  resultReceiptKey: aggregateReceiptKey,
12966
- providerOperationKey:
12967
- aggregateProviderIdempotencyReceiptKey,
12981
+ providerOperationKey: aggregateProviderOperationKey,
12968
12982
  apiOptions: {
12969
12983
  durableCallReceiptKey: aggregateReceiptKey,
12970
12984
  // A native batch is one physical provider request for
@@ -12977,10 +12991,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12977
12991
  executionAuthScopeDigest:
12978
12992
  batch.memberRequests[0]?.executionAuthScopeDigest ??
12979
12993
  null,
12980
- providerIdempotencyReceiptKey:
12981
- aggregateProviderIdempotencyReceiptKey,
12982
- providerIdempotencyKey:
12983
- aggregateProviderIdempotencyReceiptKey,
12994
+ providerIdempotencyKey: aggregateProviderOperationKey,
12995
+ providerOperationForceRefreshIdentities:
12996
+ aggregateForceRefreshIdentities,
12984
12997
  receiptLeaseExpiresAt: batch.memberRequests.reduce<
12985
12998
  string | null
12986
12999
  >((earliest, request) => {
@@ -13183,15 +13196,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13183
13196
  request,
13184
13197
  dispatchToolId: toolId,
13185
13198
  dispatchPayload: request.input,
13186
- // Always-fresh map calls intentionally have no durable
13187
- // receipt or provider idempotency header. Their cache
13188
- // key is only a local ToolCall contract identity.
13199
+ // Always-fresh map calls intentionally have no
13200
+ // result cache. Their provider-operation identity is
13201
+ // a unique forced semantic request, never a cache key.
13189
13202
  resultReceiptKey:
13190
13203
  request.receiptKey ?? request.cacheKey,
13191
13204
  providerOperationKey:
13192
- request.providerIdempotencyKeyBase ??
13193
- request.receiptKey ??
13194
- request.cacheKey,
13205
+ this.providerOperationKeyForQueuedToolCall(request),
13195
13206
  apiOptions: {
13196
13207
  beforeProviderCall: () =>
13197
13208
  pendingReceiptHeartbeat
@@ -13207,16 +13218,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13207
13218
  completedResultCache: true,
13208
13219
  executionAuthScopeDigest:
13209
13220
  request.executionAuthScopeDigest,
13210
- providerIdempotencyReceiptKey:
13211
- request.providerIdempotencyKeyBase ??
13212
- request.receiptKey,
13213
13221
  providerIdempotencyKey:
13214
13222
  this.providerIdempotencyKeyForToolCall({
13215
- cacheKey:
13216
- request.providerIdempotencyKeyBase ??
13217
- request.receiptKey,
13223
+ operationKey:
13224
+ this.providerOperationKeyForQueuedToolCall(
13225
+ request,
13226
+ ),
13218
13227
  force: request.force === true,
13219
- leaseId: request.receiptLeaseId,
13220
13228
  logicalCallId: request.logicalCallId,
13221
13229
  }),
13222
13230
  receiptLeaseExpiresAt:
@@ -13226,7 +13234,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13226
13234
  request,
13227
13235
  ]),
13228
13236
  }
13229
- : {}),
13237
+ : {
13238
+ providerIdempotencyKey:
13239
+ this.providerIdempotencyKeyForToolCall({
13240
+ operationKey:
13241
+ this.providerOperationKeyForQueuedToolCall(
13242
+ request,
13243
+ ),
13244
+ force: true,
13245
+ logicalCallId: request.logicalCallId,
13246
+ }),
13247
+ }),
13230
13248
  timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([
13231
13249
  request,
13232
13250
  ]),
@@ -13369,173 +13387,36 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13369
13387
  }
13370
13388
 
13371
13389
  private providerIdempotencyKeyForToolCall(input: {
13372
- cacheKey: string;
13390
+ operationKey: string;
13373
13391
  force?: boolean;
13374
- leaseId?: string | null;
13375
13392
  logicalCallId?: string;
13376
13393
  }): string {
13377
13394
  let fallbackAttemptId: string | null = null;
13378
- if (input.force === true && !input.leaseId) {
13395
+ if (input.force === true) {
13379
13396
  if (!input.logicalCallId) {
13380
13397
  throw new Error(
13381
- 'Forced tool execution without a receipt lease requires a stable logical call identity.',
13398
+ 'Forced tool execution requires a stable logical call identity.',
13382
13399
  );
13383
13400
  }
13384
13401
  fallbackAttemptId = `${this.currentRunId}:${input.logicalCallId}`;
13385
13402
  }
13386
- return buildDurableToolProviderIdempotencyKey({
13387
- receiptKey: input.cacheKey,
13403
+ return buildProviderOperationIdempotencyKey({
13404
+ operationKey: input.operationKey,
13388
13405
  force: input.force,
13389
- receiptLeaseId: input.leaseId,
13390
13406
  fallbackAttemptId,
13391
13407
  });
13392
13408
  }
13393
13409
 
13394
- private async acquireAsyncOperationCapacity(input: {
13395
- toolId: string;
13396
- provider: string;
13397
- contract: AsyncOperationContractWire;
13398
- authScopeDigest: string | null;
13399
- }): Promise<() => void> {
13400
- const scopeSuffix =
13401
- input.contract.capacity.scope === 'organization_action'
13402
- ? input.toolId
13403
- : input.contract.capacity.scope === 'organization_connection'
13404
- ? (input.authScopeDigest ?? 'default-connection')
13405
- : input.provider;
13406
- const key = `${this.#options.orgId ?? 'unknown-org'}:${input.provider}:${input.contract.capacity.scope}:${scopeSuffix}`;
13407
- let limit = 1;
13408
- if (input.contract.capacity.limit.kind === 'fixed') {
13409
- limit = input.contract.capacity.limit.maxConcurrentJobs;
13410
- } else {
13411
- const hints =
13412
- (await this.#options.getToolQueueHints?.(input.toolId)) ?? [];
13413
- const declaredLimits = hints
13414
- .map((hint) => hint.maxConcurrency)
13415
- .filter(
13416
- (value): value is number =>
13417
- typeof value === 'number' && Number.isFinite(value) && value > 0,
13418
- );
13419
- limit = declaredLimits.length > 0 ? Math.min(...declaredLimits) : 1;
13420
- }
13421
- const state = this.asyncOperationCapacity.get(key) ?? {
13422
- active: 0,
13423
- waiters: [],
13424
- };
13425
- this.asyncOperationCapacity.set(key, state);
13426
- if (state.active >= limit) {
13427
- if (input.contract.capacity.onConflict === 'fail') {
13428
- throw new Error(
13429
- `Async provider capacity is full for ${input.provider}; the contract allows ${limit} active job(s).`,
13430
- );
13431
- }
13432
- await new Promise<void>((resolve) => state.waiters.push(resolve));
13433
- }
13434
- state.active += 1;
13435
- let released = false;
13436
- return () => {
13437
- if (released) return;
13438
- released = true;
13439
- state.active = Math.max(0, state.active - 1);
13440
- state.waiters.shift()?.();
13441
- if (state.active === 0 && state.waiters.length === 0) {
13442
- this.asyncOperationCapacity.delete(key);
13443
- }
13444
- };
13445
- }
13446
-
13447
- private asyncOperationActionReceiptKey(input: {
13448
- action: string;
13449
- parentReceiptKey: string | null;
13450
- jobId: string;
13451
- phase: string;
13452
- }): string | null {
13453
- if (!input.parentReceiptKey) return null;
13454
- return `${buildDurableToolReceiptPrefix({
13455
- orgId: this.#options.orgId,
13456
- toolId: input.action,
13457
- })}${stableDigest(
13458
- stableStringify({
13459
- parentReceiptKey: input.parentReceiptKey,
13460
- jobId: input.jobId,
13461
- phase: input.phase,
13462
- }),
13463
- )}`;
13464
- }
13465
-
13466
- private async resolveAsyncOperation(input: {
13467
- toolId: string;
13468
- startInput: Record<string, unknown>;
13469
- startResponse: ParsedToolExecuteResponse;
13470
- contract: AsyncOperationContractWire;
13471
- receiptKey: string | null;
13472
- }): Promise<ParsedToolExecuteResponse> {
13473
- const responseValue = (response: ParsedToolExecuteResponse): unknown => {
13474
- if (response.toolResponse && 'raw' in response.toolResponse) {
13475
- return response.toolResponse.raw;
13476
- }
13477
- const result = recordOrNull(response.result);
13478
- return result && Object.prototype.hasOwnProperty.call(result, 'data')
13479
- ? result.data
13480
- : response.result;
13481
- };
13482
- const startResult = responseValue(input.startResponse);
13483
- const lifecycle = await executeAsyncOperationLifecycle({
13484
- contract: input.contract,
13485
- startInput: input.startInput,
13486
- startResult,
13487
- jobId: input.startResponse.jobId,
13488
- sleep: async (delayMs) =>
13489
- await this.sleepWithCheckpointHeartbeat(delayMs),
13490
- responseValue,
13491
- executeAction: async (request) => {
13492
- const receiptKey = this.asyncOperationActionReceiptKey({
13493
- action: request.action,
13494
- parentReceiptKey: input.receiptKey,
13495
- jobId: request.jobId,
13496
- phase: `${request.phase}:${request.pollAttempt}:${request.page ?? ''}`,
13497
- });
13498
- return await this.callToolExecutionAPI(request.action, request.input, {
13499
- skipAsyncOperationResolution: true,
13500
- durableCallReceiptKey: receiptKey,
13501
- providerIdempotencyReceiptKey: receiptKey,
13502
- providerIdempotencyKey: receiptKey,
13503
- });
13504
- },
13505
- });
13506
- if (lifecycle.kind === 'timed_out') {
13507
- const polling = resolveAsyncOperationPolling({
13508
- contract: input.contract,
13509
- startInput: input.startInput,
13510
- });
13511
- throw new Error(
13512
- `Async operation ${input.toolId} (${lifecycle.jobId}) did not reach a terminal state within ${polling.timeoutMs}ms.`,
13513
- );
13514
- }
13515
- if (
13516
- lifecycle.classification.outcome === 'failed' ||
13517
- lifecycle.classification.outcome === 'cancelled'
13518
- ) {
13410
+ private providerOperationKeyForQueuedToolCall(
13411
+ request: ToolCallRequest,
13412
+ ): string {
13413
+ const operationKey = request.providerIdempotencyKeyBase?.trim();
13414
+ if (!operationKey) {
13519
13415
  throw new Error(
13520
- lifecycle.classification.message ??
13521
- `Async operation ${input.toolId} (${lifecycle.jobId}) ended ${lifecycle.classification.outcome}.`,
13416
+ `Queued tool call ${request.callId} is missing its semantic provider operation identity.`,
13522
13417
  );
13523
13418
  }
13524
- const materialized = lifecycle.materialized!;
13525
- const selectedResponse = lifecycle.finishResponse ?? lifecycle.pollResponse;
13526
- return {
13527
- ...selectedResponse,
13528
- status:
13529
- lifecycle.classification.outcome === 'no_result' ||
13530
- materialized.outcome === 'no_result'
13531
- ? 'no_result'
13532
- : 'completed',
13533
- result: { data: materialized.result },
13534
- toolResponse: materializedAsyncToolResponse(
13535
- selectedResponse.toolResponse,
13536
- materialized.result,
13537
- ),
13538
- };
13419
+ return operationKey;
13539
13420
  }
13540
13421
 
13541
13422
  private async callToolExecutionAPI(
@@ -13551,7 +13432,29 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13551
13432
  const requestsDurableInvocationFence =
13552
13433
  this.#options.requestDurableInvocationFence === true ||
13553
13434
  this.#options.durableInvocationFence === true;
13554
- const executeSuffix = requestsDurableInvocationFence
13435
+ const asyncContract = options?.skipAsyncOperationResolution
13436
+ ? null
13437
+ : ((await this.#options.getToolAsyncOperation?.(toolId)) ?? null);
13438
+ const shouldWaitForAsyncOperation = Boolean(
13439
+ asyncContract && asyncOperationShouldWait(asyncContract, input),
13440
+ );
13441
+ const executionAuthScopeDigest = options?.executionAuthScopeDigest
13442
+ ? options.executionAuthScopeDigest.trim() || null
13443
+ : ((await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null);
13444
+ const provider =
13445
+ (await this.#options.getToolProvider?.(toolId))?.trim() ||
13446
+ toolId.split(/[._]/)[0]?.trim() ||
13447
+ 'provider';
13448
+ // A receipt is a terminal-output cache, never an async provider-job
13449
+ // record. Launch/poll/finish transport must therefore remain receiptless:
13450
+ // if this process stops, a later cache miss starts a new provider request
13451
+ // rather than attaching itself to this one.
13452
+ const uncachedAsyncLifecycleCall =
13453
+ options?.uncachedAsyncLifecycleCall === true ||
13454
+ shouldWaitForAsyncOperation;
13455
+ const usesDurableInvocationFence =
13456
+ requestsDurableInvocationFence && !uncachedAsyncLifecycleCall;
13457
+ const executeSuffix = usesDurableInvocationFence
13555
13458
  ? 'execute-fenced-v1'
13556
13459
  : 'execute';
13557
13460
  // Keep receipts, runner heartbeats, terminals, sheets, and runtime control
@@ -13566,17 +13469,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13566
13469
  options?.timeoutMs,
13567
13470
  this.currentAuthoringContractEdition,
13568
13471
  );
13569
- const provider = toolId.split(/[._]/)[0]?.trim() || 'provider';
13570
- const asyncContract = options?.skipAsyncOperationResolution
13571
- ? null
13572
- : ((await this.#options.getToolAsyncOperation?.(toolId)) ?? null);
13573
- const shouldWaitForAsyncOperation = Boolean(
13574
- asyncContract && asyncOperationShouldWait(asyncContract, input),
13575
- );
13576
- const effectiveInput =
13577
- asyncContract && shouldWaitForAsyncOperation
13578
- ? asyncOperationDurableStartInput(asyncContract, input)
13579
- : input;
13580
13472
  const activityId = `provider:${toolId}`;
13581
13473
  let retryActivityEmitted = false;
13582
13474
  let toolCallSucceeded = false;
@@ -13585,16 +13477,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13585
13477
  // execution. Per-provider rate admission is intentionally deferred until
13586
13478
  // each physical fetch is ready to leave this process.
13587
13479
  const admissionStartedAt = Date.now();
13588
- const asyncCapacityRelease =
13589
- asyncContract && shouldWaitForAsyncOperation
13590
- ? await this.acquireAsyncOperationCapacity({
13591
- toolId,
13592
- provider,
13593
- contract: asyncContract,
13594
- authScopeDigest:
13595
- (await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null,
13596
- })
13597
- : null;
13598
13480
  let toolSlot: RuntimeResourceLease;
13599
13481
  try {
13600
13482
  toolSlot = await this.resourceGovernor.acquireTool({
@@ -13603,7 +13485,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13603
13485
  toolId,
13604
13486
  });
13605
13487
  } catch (error) {
13606
- asyncCapacityRelease?.();
13607
13488
  if (!(error instanceof ProviderExhaustedError)) throw error;
13608
13489
  const retryAtMs = Date.parse(error.retryAt);
13609
13490
  throw createToolHttpError(
@@ -13659,9 +13540,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13659
13540
  // Snapshot the caller-controlled payload exactly once. The replay
13660
13541
  // decision and every physical attempt must refer to the same bytes,
13661
13542
  // even if the original object has getters or is later mutated.
13662
- const toolInputSnapshot = JSON.parse(
13663
- JSON.stringify(effectiveInput),
13664
- ) as Record<string, unknown>;
13543
+ const toolInputSnapshot = JSON.parse(JSON.stringify(input)) as Record<
13544
+ string,
13545
+ unknown
13546
+ >;
13665
13547
  const retryPolicy = await this.#options
13666
13548
  .getToolRetryPolicy?.(toolId, toolInputSnapshot)
13667
13549
  .catch(() => null);
@@ -13681,19 +13563,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13681
13563
  const ambiguousTransportReplayAllowed =
13682
13564
  readOnlyQuery || retrySafeTransientHttp;
13683
13565
  const invocationOrgId = this.#options.orgId?.trim() ?? '';
13684
- const durableCallReceiptKey =
13685
- options?.durableCallReceiptKey?.trim() ||
13686
- (requestsDurableInvocationFence && invocationOrgId
13687
- ? `${buildDurableToolReceiptPrefix({
13688
- orgId: invocationOrgId,
13689
- toolId,
13690
- })}${stableDigest(
13691
- `transport:${this.currentRunId}:${toolId}:${crypto.randomUUID()}`,
13692
- )}`
13693
- : null);
13694
- const executionAuthScopeDigest = options?.executionAuthScopeDigest
13695
- ? options.executionAuthScopeDigest.trim() || null
13696
- : ((await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null);
13566
+ const durableCallReceiptKey = uncachedAsyncLifecycleCall
13567
+ ? null
13568
+ : options?.durableCallReceiptKey?.trim() ||
13569
+ (usesDurableInvocationFence && invocationOrgId
13570
+ ? `${buildDurableToolReceiptPrefix({
13571
+ orgId: invocationOrgId,
13572
+ toolId,
13573
+ })}${stableDigest(
13574
+ `transport:${this.currentRunId}:${toolId}:${crypto.randomUUID()}`,
13575
+ )}`
13576
+ : null);
13697
13577
  const executionRequestNonce = crypto.randomUUID();
13698
13578
  // This identifies one logical tool call to billing. Physical retry
13699
13579
  // count remains in request metadata, so a transport retry cannot
@@ -13713,6 +13593,29 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13713
13593
  `Tool ${toolId} requires a provider execution fence but no provider operation identity was supplied.`,
13714
13594
  );
13715
13595
  }
13596
+ const canonicalOperationActionVersion = providerExecutionFenceKey
13597
+ ? (
13598
+ await this.#options.getToolActionCacheVersion?.(toolId)
13599
+ )?.trim() || null
13600
+ : null;
13601
+ const canonicalOperation =
13602
+ providerExecutionFenceKey && canonicalOperationActionVersion
13603
+ ? {
13604
+ version: 1,
13605
+ tool_id: toolId,
13606
+ action_version: canonicalOperationActionVersion,
13607
+ play_scope: this.currentGovernance.currentPlayId || 'play',
13608
+ output_intent: options?.completedResultCache
13609
+ ? 'dataset'
13610
+ : 'value',
13611
+ ...(options?.providerOperationForceRefreshIdentities?.length
13612
+ ? {
13613
+ force_refresh_identities:
13614
+ options.providerOperationForceRefreshIdentities,
13615
+ }
13616
+ : {}),
13617
+ }
13618
+ : null;
13716
13619
  // Every runtime provider call carries at least a tool-scoped
13717
13620
  // attribution. Callers that know the execution location (row-scoped
13718
13621
  // map cells, batched owners) supply the fuller scope.
@@ -13724,7 +13627,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13724
13627
  metadata: {
13725
13628
  parent_run_id: this.#options.runId,
13726
13629
  invocation_attempt: invocationAttempt,
13727
- ...(requestsDurableInvocationFence
13630
+ ...(shouldWaitForAsyncOperation
13631
+ ? { async_operation_wait: true }
13632
+ : {}),
13633
+ ...(usesDurableInvocationFence
13728
13634
  ? { invocation_fence_version: 1 }
13729
13635
  : {}),
13730
13636
  ...(durableCallReceiptKey
@@ -13749,6 +13655,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13749
13655
  : {}),
13750
13656
  }
13751
13657
  : {}),
13658
+ ...(canonicalOperation
13659
+ ? { canonical_operation: canonicalOperation }
13660
+ : {}),
13752
13661
  ...(options?.customerDbDataset
13753
13662
  ? {
13754
13663
  query_result_dataset: {
@@ -14377,29 +14286,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14377
14286
  ? 'array'
14378
14287
  : typeof parsed.result,
14379
14288
  });
14380
- const parsedValue =
14381
- parsed.toolResponse && 'raw' in parsed.toolResponse
14382
- ? parsed.toolResponse.raw
14383
- : (recordOrNull(parsed.result)?.data ?? parsed.result);
14384
- const asyncStartPending =
14385
- asyncContract && shouldWaitForAsyncOperation
14386
- ? classifyAsyncOperationResult(asyncContract, parsedValue)
14387
- .outcome === 'running'
14388
- : false;
14389
- const resolved =
14390
- asyncContract &&
14391
- shouldWaitForAsyncOperation &&
14392
- (parsed.status === 'running' || asyncStartPending)
14393
- ? await this.resolveAsyncOperation({
14394
- toolId,
14395
- startInput: input,
14396
- startResponse: parsed,
14397
- contract: asyncContract,
14398
- receiptKey: durableCallReceiptKey,
14399
- })
14400
- : parsed;
14401
14289
  toolCallSucceeded = true;
14402
- return resolved;
14290
+ return parsed;
14403
14291
  }
14404
14292
  },
14405
14293
  );
@@ -14426,7 +14314,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14426
14314
  if (!toolSlotTransferred) {
14427
14315
  toolSlot.release();
14428
14316
  }
14429
- asyncCapacityRelease?.();
14430
14317
  }
14431
14318
  }
14432
14319