deepline 0.3.74 → 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 (34) 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/play.ts +3 -6
  5. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  6. package/dist/bundling-sources/sdk/src/types.ts +3 -2
  7. package/dist/bundling-sources/shared_libs/monitors/deactivation-reasons.ts +186 -0
  8. package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +41 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +213 -55
  10. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +172 -299
  11. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +13 -32
  13. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +7 -2
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +11 -2
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +1 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +16 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +1 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/testkit.ts +5 -4
  20. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +12 -2
  21. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +1 -1
  22. package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +74 -0
  23. package/dist/cli/index.js +918 -167
  24. package/dist/cli/index.mjs +918 -167
  25. package/dist/{compiler-manifest-CMNgA_JQ.d.mts → compiler-manifest-Dbov16F7.d.mts} +1 -1
  26. package/dist/{compiler-manifest-CMNgA_JQ.d.ts → compiler-manifest-Dbov16F7.d.ts} +1 -1
  27. package/dist/index.d.mts +176 -23
  28. package/dist/index.d.ts +176 -23
  29. package/dist/index.js +109 -12
  30. package/dist/index.mjs +109 -12
  31. package/dist/install-integrity.json +4 -2
  32. package/dist/plays/bundle-play-file.d.mts +2 -2
  33. package/dist/plays/bundle-play-file.d.ts +2 -2
  34. package/package.json +1 -1
@@ -175,7 +175,7 @@ import {
175
175
  buildDurableToolCallCacheKey,
176
176
  buildLegacyRevisionedDurableToolCallCacheKey,
177
177
  buildDurableToolPayloadProviderIdempotencyKey,
178
- buildDurableToolProviderIdempotencyKey,
178
+ buildProviderOperationIdempotencyKey,
179
179
  buildDurableToolReceiptPrefix,
180
180
  resolveDurableCallCachePolicy,
181
181
  } from './durable-call-cache';
@@ -219,11 +219,7 @@ import {
219
219
  type DurableReceiptExecutionStore,
220
220
  } from './durable-receipt-execution';
221
221
  import {
222
- asyncOperationDurableStartInput,
223
222
  asyncOperationShouldWait,
224
- classifyAsyncOperationResult,
225
- executeAsyncOperationLifecycle,
226
- resolveAsyncOperationPolling,
227
223
  type AsyncOperationContractWire,
228
224
  } from './async-operation';
229
225
  import {
@@ -1248,27 +1244,6 @@ function publicToolResponseEnvelope(value: unknown): {
1248
1244
  };
1249
1245
  }
1250
1246
 
1251
- function materializedAsyncToolResponse(
1252
- response: ParsedToolExecuteResponse['toolResponse'] | undefined,
1253
- result: unknown,
1254
- ): ParsedToolExecuteResponse['toolResponse'] {
1255
- const hasRawV2 =
1256
- response !== undefined &&
1257
- Object.prototype.hasOwnProperty.call(response, 'rawV2');
1258
- const materializedRawV2 =
1259
- response?.view === 'data'
1260
- ? {
1261
- ...(recordOrNull(response.rawV2) ?? {}),
1262
- data: result,
1263
- }
1264
- : result;
1265
- return {
1266
- ...(response ?? {}),
1267
- raw: result,
1268
- ...(hasRawV2 ? { rawV2: materializedRawV2 } : {}),
1269
- };
1270
- }
1271
-
1272
1247
  /**
1273
1248
  * A batched provider request returns one envelope for several logical source
1274
1249
  * tool calls. Each source call persists only its own item-shaped canonical
@@ -1398,13 +1373,11 @@ type ToolExecutionApiOptions = {
1398
1373
  /** The receipt key is a completed-result cache for this read-only call. */
1399
1374
  completedResultCache?: boolean;
1400
1375
  executionAuthScopeDigest?: string | null;
1401
- /**
1402
- * Historical receipt identity used for the API's provider/billing
1403
- * idempotency receipt. This can differ from the worker-owned durable cache
1404
- * receipt while a response contract migrates.
1405
- */
1406
- providerIdempotencyReceiptKey?: string | null;
1407
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;
1408
1381
  receiptLeaseExpiresAt?: string | null;
1409
1382
  beforeProviderCall?: () => Promise<void> | void;
1410
1383
  heartbeatReceipt?: () => Promise<void> | void;
@@ -1429,6 +1402,13 @@ type ToolExecutionApiOptions = {
1429
1402
  };
1430
1403
  /** Internal recursion guard for lifecycle poll/finish calls. */
1431
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;
1432
1412
  };
1433
1413
  const IN_MEMORY_STEP_RESULT_PREVIEW_LIMIT = 25;
1434
1414
  /**
@@ -2109,10 +2089,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
2109
2089
  string,
2110
2090
  { resolve: (value: unknown) => void; reject: (reason: unknown) => void }
2111
2091
  >();
2112
- private readonly asyncOperationCapacity = new Map<
2113
- string,
2114
- { active: number; waiters: Array<() => void> }
2115
- >();
2116
2092
  #options: ContextOptions;
2117
2093
  private readonly rootRuntimeContract: PlayArtifactRuntimeContract;
2118
2094
  private readonly executionScope: RunExecutionScope;
@@ -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;
@@ -9388,9 +9365,18 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9388
9365
  executionAuthScopeDigest,
9389
9366
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
9390
9367
  });
9391
- // Only explicitly fenced operations use this key as a provider identity.
9392
- // Ordinary calls use it solely as their completed-result cache key.
9393
- 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
+ });
9394
9380
  let directResultReceiptKey = durableCacheKey;
9395
9381
  // A raw-v2 artifact must never read an older receipt that cannot render
9396
9382
  // raw-v2. If both the primary and its revisioned recovery receipt are
@@ -9670,17 +9656,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9670
9656
  ...(toolRetryPolicy?.requiresExecutionFence === true
9671
9657
  ? {
9672
9658
  providerIdempotencyReceiptKey:
9673
- cacheableToolResult && operationKey
9674
- ? operationKey
9659
+ cacheableToolResult && resultReceiptKey
9660
+ ? resultReceiptKey
9675
9661
  : physicalDirectKey,
9676
9662
  providerIdempotencyKey:
9677
9663
  this.providerIdempotencyKeyForToolCall({
9678
- cacheKey:
9664
+ operationKey:
9679
9665
  useDirectResultReceipts && operationKey
9680
9666
  ? providerIdempotencyKeyBase
9681
9667
  : physicalDirectKey,
9682
9668
  force: cacheableToolResult && toolCachePolicy.force,
9683
- leaseId: directReceiptLeaseId,
9684
9669
  logicalCallId,
9685
9670
  }),
9686
9671
  }
@@ -9762,9 +9747,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9762
9747
  dispatcher,
9763
9748
  })
9764
9749
  : null;
9765
- const providerActionVersion =
9766
- (await this.#options.getToolActionCacheVersion?.(toolId))?.trim() ??
9767
- '';
9768
9750
  const call: Omit<ToolCallInput, 'compatibility'> = {
9769
9751
  scope: {
9770
9752
  organizationId: this.#options.orgId ?? 'unknown-org',
@@ -9993,7 +9975,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
9993
9975
  executionAuthScopeDigest,
9994
9976
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
9995
9977
  });
9996
- providerIdempotencyKeyBase = durableCacheKey;
9978
+ providerIdempotencyKeyBase =
9979
+ buildDurableToolPayloadProviderIdempotencyKey({
9980
+ orgId: this.#options.orgId,
9981
+ toolId,
9982
+ executionAuthScopeDigest,
9983
+ providerActionVersion,
9984
+ payload: input,
9985
+ });
9997
9986
  directResultReceiptKey = durableCacheKey;
9998
9987
  bypassDirectResultCache = false;
9999
9988
  checkpointCacheKeys.splice(
@@ -10048,6 +10037,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10048
10037
  const existing =
10049
10038
  this.checkpoint.resolvedBoundaries?.[preparedBoundary.boundaryId];
10050
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
+ }
10051
10055
  this.runtimeLog(
10052
10056
  `Integration event ${preparedBoundary.boundaryId}: recovered response from checkpoint`,
10053
10057
  );
@@ -10103,6 +10107,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10103
10107
  boundaryId: boundary.boundaryId,
10104
10108
  eventKey: boundary.eventKey,
10105
10109
  timeoutMs: boundary.timeoutMs,
10110
+ ...(boundary.messageRef ? { messageRef: boundary.messageRef } : {}),
10106
10111
  });
10107
10112
  }
10108
10113
 
@@ -10111,6 +10116,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
10111
10116
  boundaryId: boundary.boundaryId,
10112
10117
  eventKey: boundary.eventKey,
10113
10118
  timeoutMs: boundary.timeoutMs,
10119
+ ...(boundary.messageRef ? { messageRef: boundary.messageRef } : {}),
10114
10120
  });
10115
10121
  }
10116
10122
 
@@ -12589,7 +12595,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12589
12595
  dispatchPayload: owner.input,
12590
12596
  resultReceiptKey: receiptKey,
12591
12597
  providerOperationKey:
12592
- owner.providerIdempotencyKeyBase ?? owner.cacheKey,
12598
+ this.providerOperationKeyForQueuedToolCall(owner),
12593
12599
  apiOptions: {
12594
12600
  beforeProviderCall: () =>
12595
12601
  this.assertRuntimeToolReceiptOwnership([owner]),
@@ -12597,18 +12603,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12597
12603
  playNodeScope: playNodeScopeForToolCallRequest(owner),
12598
12604
  executionAuthScopeDigest:
12599
12605
  owner.executionAuthScopeDigest,
12600
- providerIdempotencyReceiptKey:
12601
- owner.providerIdempotencyKeyBase ?? owner.cacheKey,
12602
12606
  receiptLeaseExpiresAt: owner.receiptLeaseExpiresAt,
12603
12607
  heartbeatReceipt: () =>
12604
12608
  this.renewRuntimeToolReceiptOwnership([owner]),
12605
12609
  providerIdempotencyKey:
12606
12610
  this.providerIdempotencyKeyForToolCall({
12607
- cacheKey:
12608
- owner.providerIdempotencyKeyBase ??
12609
- owner.cacheKey,
12611
+ operationKey:
12612
+ this.providerOperationKeyForQueuedToolCall(owner),
12610
12613
  force: owner.force === true,
12611
- leaseId: owner.receiptLeaseId,
12612
12614
  logicalCallId: owner.logicalCallId,
12613
12615
  }),
12614
12616
  timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([
@@ -12942,7 +12944,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12942
12944
  toolId: batch.batchOperation,
12943
12945
  }),
12944
12946
  });
12945
- 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 =
12946
12958
  buildDurableToolPayloadProviderIdempotencyKey({
12947
12959
  orgId: this.#options.orgId,
12948
12960
  toolId: batch.batchOperation,
@@ -12956,18 +12968,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12956
12968
  )
12957
12969
  )?.trim() ?? 'unversioned',
12958
12970
  payload: batch.batchPayload,
12959
- forceRefreshIdentities: batch.memberRequests
12960
- .filter((request) => request.force === true)
12961
- .map((request) =>
12962
- this.providerIdempotencyKeyForToolCall({
12963
- cacheKey:
12964
- request.providerIdempotencyKeyBase ??
12965
- request.cacheKey,
12966
- force: true,
12967
- leaseId: request.receiptLeaseId,
12968
- logicalCallId: request.logicalCallId,
12969
- }),
12970
- ),
12971
+ forceRefreshIdentities: aggregateForceRefreshIdentities,
12971
12972
  });
12972
12973
  let releaseToolSlot: () => void = () => undefined;
12973
12974
  try {
@@ -12977,8 +12978,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12977
12978
  dispatchToolId: batch.batchOperation,
12978
12979
  dispatchPayload: batch.batchPayload,
12979
12980
  resultReceiptKey: aggregateReceiptKey,
12980
- providerOperationKey:
12981
- aggregateProviderIdempotencyReceiptKey,
12981
+ providerOperationKey: aggregateProviderOperationKey,
12982
12982
  apiOptions: {
12983
12983
  durableCallReceiptKey: aggregateReceiptKey,
12984
12984
  // A native batch is one physical provider request for
@@ -12991,10 +12991,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
12991
12991
  executionAuthScopeDigest:
12992
12992
  batch.memberRequests[0]?.executionAuthScopeDigest ??
12993
12993
  null,
12994
- providerIdempotencyReceiptKey:
12995
- aggregateProviderIdempotencyReceiptKey,
12996
- providerIdempotencyKey:
12997
- aggregateProviderIdempotencyReceiptKey,
12994
+ providerIdempotencyKey: aggregateProviderOperationKey,
12995
+ providerOperationForceRefreshIdentities:
12996
+ aggregateForceRefreshIdentities,
12998
12997
  receiptLeaseExpiresAt: batch.memberRequests.reduce<
12999
12998
  string | null
13000
12999
  >((earliest, request) => {
@@ -13197,15 +13196,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13197
13196
  request,
13198
13197
  dispatchToolId: toolId,
13199
13198
  dispatchPayload: request.input,
13200
- // Always-fresh map calls intentionally have no durable
13201
- // receipt or provider idempotency header. Their cache
13202
- // 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.
13203
13202
  resultReceiptKey:
13204
13203
  request.receiptKey ?? request.cacheKey,
13205
13204
  providerOperationKey:
13206
- request.providerIdempotencyKeyBase ??
13207
- request.receiptKey ??
13208
- request.cacheKey,
13205
+ this.providerOperationKeyForQueuedToolCall(request),
13209
13206
  apiOptions: {
13210
13207
  beforeProviderCall: () =>
13211
13208
  pendingReceiptHeartbeat
@@ -13221,16 +13218,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13221
13218
  completedResultCache: true,
13222
13219
  executionAuthScopeDigest:
13223
13220
  request.executionAuthScopeDigest,
13224
- providerIdempotencyReceiptKey:
13225
- request.providerIdempotencyKeyBase ??
13226
- request.receiptKey,
13227
13221
  providerIdempotencyKey:
13228
13222
  this.providerIdempotencyKeyForToolCall({
13229
- cacheKey:
13230
- request.providerIdempotencyKeyBase ??
13231
- request.receiptKey,
13223
+ operationKey:
13224
+ this.providerOperationKeyForQueuedToolCall(
13225
+ request,
13226
+ ),
13232
13227
  force: request.force === true,
13233
- leaseId: request.receiptLeaseId,
13234
13228
  logicalCallId: request.logicalCallId,
13235
13229
  }),
13236
13230
  receiptLeaseExpiresAt:
@@ -13240,7 +13234,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13240
13234
  request,
13241
13235
  ]),
13242
13236
  }
13243
- : {}),
13237
+ : {
13238
+ providerIdempotencyKey:
13239
+ this.providerIdempotencyKeyForToolCall({
13240
+ operationKey:
13241
+ this.providerOperationKeyForQueuedToolCall(
13242
+ request,
13243
+ ),
13244
+ force: true,
13245
+ logicalCallId: request.logicalCallId,
13246
+ }),
13247
+ }),
13244
13248
  timeoutMs: resolveRuntimeTimeoutMsForClaimedOwners([
13245
13249
  request,
13246
13250
  ]),
@@ -13383,173 +13387,36 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13383
13387
  }
13384
13388
 
13385
13389
  private providerIdempotencyKeyForToolCall(input: {
13386
- cacheKey: string;
13390
+ operationKey: string;
13387
13391
  force?: boolean;
13388
- leaseId?: string | null;
13389
13392
  logicalCallId?: string;
13390
13393
  }): string {
13391
13394
  let fallbackAttemptId: string | null = null;
13392
- if (input.force === true && !input.leaseId) {
13395
+ if (input.force === true) {
13393
13396
  if (!input.logicalCallId) {
13394
13397
  throw new Error(
13395
- 'Forced tool execution without a receipt lease requires a stable logical call identity.',
13398
+ 'Forced tool execution requires a stable logical call identity.',
13396
13399
  );
13397
13400
  }
13398
13401
  fallbackAttemptId = `${this.currentRunId}:${input.logicalCallId}`;
13399
13402
  }
13400
- return buildDurableToolProviderIdempotencyKey({
13401
- receiptKey: input.cacheKey,
13403
+ return buildProviderOperationIdempotencyKey({
13404
+ operationKey: input.operationKey,
13402
13405
  force: input.force,
13403
- receiptLeaseId: input.leaseId,
13404
13406
  fallbackAttemptId,
13405
13407
  });
13406
13408
  }
13407
13409
 
13408
- private async acquireAsyncOperationCapacity(input: {
13409
- toolId: string;
13410
- provider: string;
13411
- contract: AsyncOperationContractWire;
13412
- authScopeDigest: string | null;
13413
- }): Promise<() => void> {
13414
- const scopeSuffix =
13415
- input.contract.capacity.scope === 'organization_action'
13416
- ? input.toolId
13417
- : input.contract.capacity.scope === 'organization_connection'
13418
- ? (input.authScopeDigest ?? 'default-connection')
13419
- : input.provider;
13420
- const key = `${this.#options.orgId ?? 'unknown-org'}:${input.provider}:${input.contract.capacity.scope}:${scopeSuffix}`;
13421
- let limit = 1;
13422
- if (input.contract.capacity.limit.kind === 'fixed') {
13423
- limit = input.contract.capacity.limit.maxConcurrentJobs;
13424
- } else {
13425
- const hints =
13426
- (await this.#options.getToolQueueHints?.(input.toolId)) ?? [];
13427
- const declaredLimits = hints
13428
- .map((hint) => hint.maxConcurrency)
13429
- .filter(
13430
- (value): value is number =>
13431
- typeof value === 'number' && Number.isFinite(value) && value > 0,
13432
- );
13433
- limit = declaredLimits.length > 0 ? Math.min(...declaredLimits) : 1;
13434
- }
13435
- const state = this.asyncOperationCapacity.get(key) ?? {
13436
- active: 0,
13437
- waiters: [],
13438
- };
13439
- this.asyncOperationCapacity.set(key, state);
13440
- if (state.active >= limit) {
13441
- if (input.contract.capacity.onConflict === 'fail') {
13442
- throw new Error(
13443
- `Async provider capacity is full for ${input.provider}; the contract allows ${limit} active job(s).`,
13444
- );
13445
- }
13446
- await new Promise<void>((resolve) => state.waiters.push(resolve));
13447
- }
13448
- state.active += 1;
13449
- let released = false;
13450
- return () => {
13451
- if (released) return;
13452
- released = true;
13453
- state.active = Math.max(0, state.active - 1);
13454
- state.waiters.shift()?.();
13455
- if (state.active === 0 && state.waiters.length === 0) {
13456
- this.asyncOperationCapacity.delete(key);
13457
- }
13458
- };
13459
- }
13460
-
13461
- private asyncOperationActionReceiptKey(input: {
13462
- action: string;
13463
- parentReceiptKey: string | null;
13464
- jobId: string;
13465
- phase: string;
13466
- }): string | null {
13467
- if (!input.parentReceiptKey) return null;
13468
- return `${buildDurableToolReceiptPrefix({
13469
- orgId: this.#options.orgId,
13470
- toolId: input.action,
13471
- })}${stableDigest(
13472
- stableStringify({
13473
- parentReceiptKey: input.parentReceiptKey,
13474
- jobId: input.jobId,
13475
- phase: input.phase,
13476
- }),
13477
- )}`;
13478
- }
13479
-
13480
- private async resolveAsyncOperation(input: {
13481
- toolId: string;
13482
- startInput: Record<string, unknown>;
13483
- startResponse: ParsedToolExecuteResponse;
13484
- contract: AsyncOperationContractWire;
13485
- receiptKey: string | null;
13486
- }): Promise<ParsedToolExecuteResponse> {
13487
- const responseValue = (response: ParsedToolExecuteResponse): unknown => {
13488
- if (response.toolResponse && 'raw' in response.toolResponse) {
13489
- return response.toolResponse.raw;
13490
- }
13491
- const result = recordOrNull(response.result);
13492
- return result && Object.prototype.hasOwnProperty.call(result, 'data')
13493
- ? result.data
13494
- : response.result;
13495
- };
13496
- const startResult = responseValue(input.startResponse);
13497
- const lifecycle = await executeAsyncOperationLifecycle({
13498
- contract: input.contract,
13499
- startInput: input.startInput,
13500
- startResult,
13501
- jobId: input.startResponse.jobId,
13502
- sleep: async (delayMs) =>
13503
- await this.sleepWithCheckpointHeartbeat(delayMs),
13504
- responseValue,
13505
- executeAction: async (request) => {
13506
- const receiptKey = this.asyncOperationActionReceiptKey({
13507
- action: request.action,
13508
- parentReceiptKey: input.receiptKey,
13509
- jobId: request.jobId,
13510
- phase: `${request.phase}:${request.pollAttempt}:${request.page ?? ''}`,
13511
- });
13512
- return await this.callToolExecutionAPI(request.action, request.input, {
13513
- skipAsyncOperationResolution: true,
13514
- durableCallReceiptKey: receiptKey,
13515
- providerIdempotencyReceiptKey: receiptKey,
13516
- providerIdempotencyKey: receiptKey,
13517
- });
13518
- },
13519
- });
13520
- if (lifecycle.kind === 'timed_out') {
13521
- const polling = resolveAsyncOperationPolling({
13522
- contract: input.contract,
13523
- startInput: input.startInput,
13524
- });
13525
- throw new Error(
13526
- `Async operation ${input.toolId} (${lifecycle.jobId}) did not reach a terminal state within ${polling.timeoutMs}ms.`,
13527
- );
13528
- }
13529
- if (
13530
- lifecycle.classification.outcome === 'failed' ||
13531
- lifecycle.classification.outcome === 'cancelled'
13532
- ) {
13410
+ private providerOperationKeyForQueuedToolCall(
13411
+ request: ToolCallRequest,
13412
+ ): string {
13413
+ const operationKey = request.providerIdempotencyKeyBase?.trim();
13414
+ if (!operationKey) {
13533
13415
  throw new Error(
13534
- lifecycle.classification.message ??
13535
- `Async operation ${input.toolId} (${lifecycle.jobId}) ended ${lifecycle.classification.outcome}.`,
13416
+ `Queued tool call ${request.callId} is missing its semantic provider operation identity.`,
13536
13417
  );
13537
13418
  }
13538
- const materialized = lifecycle.materialized!;
13539
- const selectedResponse = lifecycle.finishResponse ?? lifecycle.pollResponse;
13540
- return {
13541
- ...selectedResponse,
13542
- status:
13543
- lifecycle.classification.outcome === 'no_result' ||
13544
- materialized.outcome === 'no_result'
13545
- ? 'no_result'
13546
- : 'completed',
13547
- result: { data: materialized.result },
13548
- toolResponse: materializedAsyncToolResponse(
13549
- selectedResponse.toolResponse,
13550
- materialized.result,
13551
- ),
13552
- };
13419
+ return operationKey;
13553
13420
  }
13554
13421
 
13555
13422
  private async callToolExecutionAPI(
@@ -13565,7 +13432,29 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13565
13432
  const requestsDurableInvocationFence =
13566
13433
  this.#options.requestDurableInvocationFence === true ||
13567
13434
  this.#options.durableInvocationFence === true;
13568
- 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
13569
13458
  ? 'execute-fenced-v1'
13570
13459
  : 'execute';
13571
13460
  // Keep receipts, runner heartbeats, terminals, sheets, and runtime control
@@ -13580,17 +13469,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13580
13469
  options?.timeoutMs,
13581
13470
  this.currentAuthoringContractEdition,
13582
13471
  );
13583
- const provider = toolId.split(/[._]/)[0]?.trim() || 'provider';
13584
- const asyncContract = options?.skipAsyncOperationResolution
13585
- ? null
13586
- : ((await this.#options.getToolAsyncOperation?.(toolId)) ?? null);
13587
- const shouldWaitForAsyncOperation = Boolean(
13588
- asyncContract && asyncOperationShouldWait(asyncContract, input),
13589
- );
13590
- const effectiveInput =
13591
- asyncContract && shouldWaitForAsyncOperation
13592
- ? asyncOperationDurableStartInput(asyncContract, input)
13593
- : input;
13594
13472
  const activityId = `provider:${toolId}`;
13595
13473
  let retryActivityEmitted = false;
13596
13474
  let toolCallSucceeded = false;
@@ -13599,16 +13477,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13599
13477
  // execution. Per-provider rate admission is intentionally deferred until
13600
13478
  // each physical fetch is ready to leave this process.
13601
13479
  const admissionStartedAt = Date.now();
13602
- const asyncCapacityRelease =
13603
- asyncContract && shouldWaitForAsyncOperation
13604
- ? await this.acquireAsyncOperationCapacity({
13605
- toolId,
13606
- provider,
13607
- contract: asyncContract,
13608
- authScopeDigest:
13609
- (await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null,
13610
- })
13611
- : null;
13612
13480
  let toolSlot: RuntimeResourceLease;
13613
13481
  try {
13614
13482
  toolSlot = await this.resourceGovernor.acquireTool({
@@ -13617,7 +13485,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13617
13485
  toolId,
13618
13486
  });
13619
13487
  } catch (error) {
13620
- asyncCapacityRelease?.();
13621
13488
  if (!(error instanceof ProviderExhaustedError)) throw error;
13622
13489
  const retryAtMs = Date.parse(error.retryAt);
13623
13490
  throw createToolHttpError(
@@ -13673,9 +13540,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13673
13540
  // Snapshot the caller-controlled payload exactly once. The replay
13674
13541
  // decision and every physical attempt must refer to the same bytes,
13675
13542
  // even if the original object has getters or is later mutated.
13676
- const toolInputSnapshot = JSON.parse(
13677
- JSON.stringify(effectiveInput),
13678
- ) as Record<string, unknown>;
13543
+ const toolInputSnapshot = JSON.parse(JSON.stringify(input)) as Record<
13544
+ string,
13545
+ unknown
13546
+ >;
13679
13547
  const retryPolicy = await this.#options
13680
13548
  .getToolRetryPolicy?.(toolId, toolInputSnapshot)
13681
13549
  .catch(() => null);
@@ -13695,19 +13563,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13695
13563
  const ambiguousTransportReplayAllowed =
13696
13564
  readOnlyQuery || retrySafeTransientHttp;
13697
13565
  const invocationOrgId = this.#options.orgId?.trim() ?? '';
13698
- const durableCallReceiptKey =
13699
- options?.durableCallReceiptKey?.trim() ||
13700
- (requestsDurableInvocationFence && invocationOrgId
13701
- ? `${buildDurableToolReceiptPrefix({
13702
- orgId: invocationOrgId,
13703
- toolId,
13704
- })}${stableDigest(
13705
- `transport:${this.currentRunId}:${toolId}:${crypto.randomUUID()}`,
13706
- )}`
13707
- : null);
13708
- const executionAuthScopeDigest = options?.executionAuthScopeDigest
13709
- ? options.executionAuthScopeDigest.trim() || null
13710
- : ((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);
13711
13577
  const executionRequestNonce = crypto.randomUUID();
13712
13578
  // This identifies one logical tool call to billing. Physical retry
13713
13579
  // count remains in request metadata, so a transport retry cannot
@@ -13727,6 +13593,29 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13727
13593
  `Tool ${toolId} requires a provider execution fence but no provider operation identity was supplied.`,
13728
13594
  );
13729
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;
13730
13619
  // Every runtime provider call carries at least a tool-scoped
13731
13620
  // attribution. Callers that know the execution location (row-scoped
13732
13621
  // map cells, batched owners) supply the fuller scope.
@@ -13738,7 +13627,10 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13738
13627
  metadata: {
13739
13628
  parent_run_id: this.#options.runId,
13740
13629
  invocation_attempt: invocationAttempt,
13741
- ...(requestsDurableInvocationFence
13630
+ ...(shouldWaitForAsyncOperation
13631
+ ? { async_operation_wait: true }
13632
+ : {}),
13633
+ ...(usesDurableInvocationFence
13742
13634
  ? { invocation_fence_version: 1 }
13743
13635
  : {}),
13744
13636
  ...(durableCallReceiptKey
@@ -13763,6 +13655,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
13763
13655
  : {}),
13764
13656
  }
13765
13657
  : {}),
13658
+ ...(canonicalOperation
13659
+ ? { canonical_operation: canonicalOperation }
13660
+ : {}),
13766
13661
  ...(options?.customerDbDataset
13767
13662
  ? {
13768
13663
  query_result_dataset: {
@@ -14391,29 +14286,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14391
14286
  ? 'array'
14392
14287
  : typeof parsed.result,
14393
14288
  });
14394
- const parsedValue =
14395
- parsed.toolResponse && 'raw' in parsed.toolResponse
14396
- ? parsed.toolResponse.raw
14397
- : (recordOrNull(parsed.result)?.data ?? parsed.result);
14398
- const asyncStartPending =
14399
- asyncContract && shouldWaitForAsyncOperation
14400
- ? classifyAsyncOperationResult(asyncContract, parsedValue)
14401
- .outcome === 'running'
14402
- : false;
14403
- const resolved =
14404
- asyncContract &&
14405
- shouldWaitForAsyncOperation &&
14406
- (parsed.status === 'running' || asyncStartPending)
14407
- ? await this.resolveAsyncOperation({
14408
- toolId,
14409
- startInput: input,
14410
- startResponse: parsed,
14411
- contract: asyncContract,
14412
- receiptKey: durableCallReceiptKey,
14413
- })
14414
- : parsed;
14415
14289
  toolCallSucceeded = true;
14416
- return resolved;
14290
+ return parsed;
14417
14291
  }
14418
14292
  },
14419
14293
  );
@@ -14440,7 +14314,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
14440
14314
  if (!toolSlotTransferred) {
14441
14315
  toolSlot.release();
14442
14316
  }
14443
- asyncCapacityRelease?.();
14444
14317
  }
14445
14318
  }
14446
14319