deepline 0.3.18 → 0.3.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +31 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +121 -27
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-response-contract.ts +51 -4
- package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +29 -2
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +60 -2
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +36 -5
- package/dist/cli/index.js +55 -5
- package/dist/cli/index.mjs +55 -5
- package/dist/{compiler-manifest-Bd0O94yZ.d.mts → compiler-manifest-DwYe2C2S.d.mts} +24 -1
- package/dist/{compiler-manifest-Bd0O94yZ.d.ts → compiler-manifest-DwYe2C2S.d.ts} +24 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/install-integrity.json +2 -2
- package/dist/plays/bundle-play-file.d.mts +9 -3
- package/dist/plays/bundle-play-file.d.ts +9 -3
- package/dist/plays/bundle-play-file.mjs +85 -7
- package/package.json +1 -1
|
@@ -192,7 +192,7 @@ export const SDK_RELEASE = {
|
|
|
192
192
|
// 0.3.0 introduces raw-v2: complete scrubbed provider responses are
|
|
193
193
|
// available at toolResponse.rawV2 while toolResponse.raw and all declared
|
|
194
194
|
// getters keep their established compatibility behavior.
|
|
195
|
-
version: '0.3.
|
|
195
|
+
version: '0.3.20',
|
|
196
196
|
updateSummary:
|
|
197
197
|
'New raw-v2 tool responses preserve complete scrubbed provider envelopes at toolResponse.rawV2, including JSON:API included resources, links, cursors, and totals. Existing toolResponse.raw and declared getters keep working unchanged.',
|
|
198
198
|
contracts: {
|
|
@@ -964,10 +964,12 @@ export class AppRuntimeApiTransportError extends Error {
|
|
|
964
964
|
* whether to retry or block a delivery without parsing an error message.
|
|
965
965
|
*/
|
|
966
966
|
export class AppRuntimeApiResponseError extends Error {
|
|
967
|
+
readonly action: RuntimeApiRequest['action'];
|
|
967
968
|
readonly status: number;
|
|
968
969
|
readonly code: string | null;
|
|
969
970
|
readonly requestId: string | null;
|
|
970
971
|
readonly retryable: boolean;
|
|
972
|
+
readonly detail: string;
|
|
971
973
|
|
|
972
974
|
constructor(input: {
|
|
973
975
|
action: RuntimeApiRequest['action'];
|
|
@@ -985,10 +987,12 @@ export class AppRuntimeApiResponseError extends Error {
|
|
|
985
987
|
input.detail,
|
|
986
988
|
);
|
|
987
989
|
this.name = 'AppRuntimeApiResponseError';
|
|
990
|
+
this.action = input.action;
|
|
988
991
|
this.status = input.status;
|
|
989
992
|
this.code = input.code?.trim() || null;
|
|
990
993
|
this.requestId = input.requestId?.trim() || null;
|
|
991
994
|
this.retryable = input.retryable;
|
|
995
|
+
this.detail = input.detail;
|
|
992
996
|
}
|
|
993
997
|
}
|
|
994
998
|
|
|
@@ -1514,9 +1518,36 @@ export function fallbackRuntimeTrafficPolicyForUnavailableControlPlane(
|
|
|
1514
1518
|
) {
|
|
1515
1519
|
return DEFAULT_RUNTIME_TRAFFIC_POLICY;
|
|
1516
1520
|
}
|
|
1521
|
+
// A new worker is intentionally canaried against the currently active app
|
|
1522
|
+
// before the staged app is promoted. Older apps predate this non-critical
|
|
1523
|
+
// traffic-policy action and reject the unknown action before capability
|
|
1524
|
+
// evaluation with this exact response. Treat only that known cross-version
|
|
1525
|
+
// contract as unavailable; broad 403 fallback would mask a genuinely
|
|
1526
|
+
// missing or revoked capability.
|
|
1527
|
+
if (isLegacyRuntimeTrafficPolicyControlPlaneResponse(error)) {
|
|
1528
|
+
return DEFAULT_RUNTIME_TRAFFIC_POLICY;
|
|
1529
|
+
}
|
|
1517
1530
|
return null;
|
|
1518
1531
|
}
|
|
1519
1532
|
|
|
1533
|
+
/**
|
|
1534
|
+
* Identifies the precise response an app predating runtime traffic policy
|
|
1535
|
+
* returns during a rolling worker-before-app deployment. Keep this separate
|
|
1536
|
+
* from normal control-plane unavailability so on-call can distinguish a
|
|
1537
|
+
* rollout bridge from a service outage.
|
|
1538
|
+
*/
|
|
1539
|
+
export function isLegacyRuntimeTrafficPolicyControlPlaneResponse(
|
|
1540
|
+
error: unknown,
|
|
1541
|
+
): error is AppRuntimeApiResponseError {
|
|
1542
|
+
return (
|
|
1543
|
+
error instanceof AppRuntimeApiResponseError &&
|
|
1544
|
+
error.action === 'get_runtime_traffic_policy' &&
|
|
1545
|
+
error.status === 403 &&
|
|
1546
|
+
error.code === null &&
|
|
1547
|
+
error.detail === 'Unsupported runtime action capability scope.'
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1520
1551
|
export async function writeStagedFileFromAppRuntime(
|
|
1521
1552
|
context: WorkerRuntimeApiContext,
|
|
1522
1553
|
file: Pick<PlayStagedFileRef, 'storageKey'>,
|
|
@@ -376,8 +376,10 @@ type InlineCompositionStore = {
|
|
|
376
376
|
toolErrorSchemaVersion: ToolExecutionErrorSchemaVersion;
|
|
377
377
|
/** Immutable authoring semantics pinned by the child play artifact. */
|
|
378
378
|
authoringContractEdition: PlayAuthoringContractEdition;
|
|
379
|
-
/**
|
|
379
|
+
/** Execute response contract pinned by the child artifact. */
|
|
380
380
|
toolResponseContract: ToolResponseContract;
|
|
381
|
+
/** Explicit response-transform revision used for durable receipt identity. */
|
|
382
|
+
toolResponseReceiptRevision?: string;
|
|
381
383
|
};
|
|
382
384
|
const inlineCompositionContext =
|
|
383
385
|
new AsyncLocalStorage<InlineCompositionStore>();
|
|
@@ -1340,6 +1342,11 @@ type ToolExecutionApiOptions = {
|
|
|
1340
1342
|
timeoutMs?: number;
|
|
1341
1343
|
durableCallReceiptKey?: string | null;
|
|
1342
1344
|
executionAuthScopeDigest?: string | null;
|
|
1345
|
+
/**
|
|
1346
|
+
* Historical receipt identity that anchors external provider idempotency
|
|
1347
|
+
* while a newer response contract uses a distinct durable receipt.
|
|
1348
|
+
*/
|
|
1349
|
+
providerIdempotencyReceiptKey?: string | null;
|
|
1343
1350
|
providerIdempotencyKey?: string | null;
|
|
1344
1351
|
receiptLeaseExpiresAt?: string | null;
|
|
1345
1352
|
beforeProviderCall?: () => Promise<void> | void;
|
|
@@ -2648,6 +2655,18 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2648
2655
|
);
|
|
2649
2656
|
}
|
|
2650
2657
|
|
|
2658
|
+
/**
|
|
2659
|
+
* Only explicitly declared response transformations affect receipt reuse.
|
|
2660
|
+
* Missing preserves historical artifact/receipt identity, while the public
|
|
2661
|
+
* response header still normalizes it to the legacy V2 behavior above.
|
|
2662
|
+
*/
|
|
2663
|
+
private get currentToolResponseReceiptRevision(): string | undefined {
|
|
2664
|
+
if (this.activeInlineComposition) {
|
|
2665
|
+
return this.activeInlineComposition.toolResponseReceiptRevision;
|
|
2666
|
+
}
|
|
2667
|
+
return this.#options.toolResponseReceiptRevision;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2651
2670
|
private get currentAuthoringContractEdition(): PlayAuthoringContractEdition {
|
|
2652
2671
|
return (
|
|
2653
2672
|
this.activeInlineComposition?.authoringContractEdition ??
|
|
@@ -4026,13 +4045,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4026
4045
|
);
|
|
4027
4046
|
}
|
|
4028
4047
|
|
|
4029
|
-
private async durableToolCallCacheKeyForScope(
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4048
|
+
private async durableToolCallCacheKeyForScope(
|
|
4049
|
+
input: {
|
|
4050
|
+
toolId: string;
|
|
4051
|
+
requestInput: Record<string, unknown>;
|
|
4052
|
+
executionAuthScopeDigest?: string | null;
|
|
4053
|
+
staleAfterSeconds?: number | null;
|
|
4054
|
+
playLocalScope?: string | null;
|
|
4055
|
+
},
|
|
4056
|
+
toolResponseReceiptRevision: string | null | undefined = this
|
|
4057
|
+
.currentToolResponseReceiptRevision,
|
|
4058
|
+
): Promise<string> {
|
|
4036
4059
|
const providerActionVersion =
|
|
4037
4060
|
(await this.#options.getToolActionCacheVersion?.(input.toolId))?.trim() ??
|
|
4038
4061
|
'';
|
|
@@ -4051,21 +4074,29 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4051
4074
|
executionAuthScopeDigest,
|
|
4052
4075
|
}),
|
|
4053
4076
|
providerActionVersion,
|
|
4077
|
+
toolResponseReceiptRevision,
|
|
4054
4078
|
staleAfterSeconds: input.staleAfterSeconds,
|
|
4055
4079
|
cacheEpochMs: this.durableCallCacheEpochMs,
|
|
4056
4080
|
});
|
|
4057
4081
|
}
|
|
4058
4082
|
|
|
4059
|
-
private async durableToolCallCacheKey(
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4083
|
+
private async durableToolCallCacheKey(
|
|
4084
|
+
input: {
|
|
4085
|
+
toolId: string;
|
|
4086
|
+
requestInput: Record<string, unknown>;
|
|
4087
|
+
executionAuthScopeDigest?: string | null;
|
|
4088
|
+
staleAfterSeconds?: number | null;
|
|
4089
|
+
},
|
|
4090
|
+
toolResponseReceiptRevision: string | null | undefined = this
|
|
4091
|
+
.currentToolResponseReceiptRevision,
|
|
4092
|
+
): Promise<string> {
|
|
4093
|
+
return await this.durableToolCallCacheKeyForScope(
|
|
4094
|
+
{
|
|
4095
|
+
...input,
|
|
4096
|
+
playLocalScope: this.currentGovernance.currentPlayId,
|
|
4097
|
+
},
|
|
4098
|
+
toolResponseReceiptRevision,
|
|
4099
|
+
);
|
|
4069
4100
|
}
|
|
4070
4101
|
|
|
4071
4102
|
private async resolveToolAuthScopeDigest(
|
|
@@ -8968,6 +8999,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8968
8999
|
executionAuthScopeDigest,
|
|
8969
9000
|
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
8970
9001
|
});
|
|
9002
|
+
let providerIdempotencyKeyBase = await this.durableToolCallCacheKey(
|
|
9003
|
+
{
|
|
9004
|
+
toolId,
|
|
9005
|
+
requestInput: input,
|
|
9006
|
+
executionAuthScopeDigest,
|
|
9007
|
+
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
9008
|
+
},
|
|
9009
|
+
null,
|
|
9010
|
+
);
|
|
8971
9011
|
const checkpointCacheKeys = [durableCacheKey];
|
|
8972
9012
|
|
|
8973
9013
|
const executeTool = async (context?: {
|
|
@@ -9091,9 +9131,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9091
9131
|
callKey: normalizedKey,
|
|
9092
9132
|
}),
|
|
9093
9133
|
executionAuthScopeDigest,
|
|
9134
|
+
providerIdempotencyReceiptKey: cacheableToolResult
|
|
9135
|
+
? providerIdempotencyKeyBase
|
|
9136
|
+
: physicalDirectKey,
|
|
9094
9137
|
providerIdempotencyKey: cacheableToolResult
|
|
9095
9138
|
? this.providerIdempotencyKeyForToolCall({
|
|
9096
|
-
cacheKey:
|
|
9139
|
+
cacheKey: providerIdempotencyKeyBase,
|
|
9097
9140
|
force: toolCachePolicy.force,
|
|
9098
9141
|
leaseId: directReceiptLeaseId,
|
|
9099
9142
|
logicalCallId,
|
|
@@ -9241,6 +9284,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9241
9284
|
this.enqueueToolCall({
|
|
9242
9285
|
callId,
|
|
9243
9286
|
cacheKey: toolResultCacheKey,
|
|
9287
|
+
providerIdempotencyKeyBase,
|
|
9244
9288
|
cacheable: cacheableToolResult,
|
|
9245
9289
|
receiptKey: cacheableToolResult ? durableCacheKey : null,
|
|
9246
9290
|
executionAuthScopeDigest,
|
|
@@ -9361,6 +9405,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9361
9405
|
executionAuthScopeDigest,
|
|
9362
9406
|
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
9363
9407
|
});
|
|
9408
|
+
providerIdempotencyKeyBase = await this.durableToolCallCacheKey(
|
|
9409
|
+
{
|
|
9410
|
+
toolId,
|
|
9411
|
+
requestInput: input,
|
|
9412
|
+
executionAuthScopeDigest,
|
|
9413
|
+
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
9414
|
+
},
|
|
9415
|
+
null,
|
|
9416
|
+
);
|
|
9364
9417
|
checkpointCacheKeys[0] = durableCacheKey;
|
|
9365
9418
|
}
|
|
9366
9419
|
}
|
|
@@ -9523,14 +9576,18 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9523
9576
|
`Unable to resolve play "${resolvedName}" for ctx.runPlay(...).`,
|
|
9524
9577
|
);
|
|
9525
9578
|
}
|
|
9526
|
-
const
|
|
9579
|
+
const childCompatibilitySnapshot =
|
|
9527
9580
|
resolvedPlay.contractSnapshot?.compatibility ??
|
|
9528
|
-
|
|
9529
|
-
|
|
9581
|
+
resolvedPlay.artifact?.compatibility ??
|
|
9582
|
+
buildPlayContractCompatibility();
|
|
9583
|
+
const childCompatibility = normalizePlayContractCompatibility(
|
|
9584
|
+
childCompatibilitySnapshot,
|
|
9530
9585
|
);
|
|
9531
9586
|
const childToolErrorSchemaVersion =
|
|
9532
9587
|
childCompatibility.toolErrorSchemaVersion;
|
|
9533
9588
|
const childToolResponseContract = childCompatibility.toolResponseContract;
|
|
9589
|
+
const childToolResponseReceiptRevision =
|
|
9590
|
+
childCompatibility.toolResponseReceiptRevision;
|
|
9534
9591
|
const childExecutionDecision = resolveChildExecutionStrategy({
|
|
9535
9592
|
pipeline: resolvedPlay.staticPipeline,
|
|
9536
9593
|
timeoutMs: options?.timeoutMs,
|
|
@@ -9604,6 +9661,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9604
9661
|
staticPipeline: resolvedPlay.staticPipeline ?? null,
|
|
9605
9662
|
toolErrorSchemaVersion: childToolErrorSchemaVersion,
|
|
9606
9663
|
toolResponseContract: childToolResponseContract,
|
|
9664
|
+
toolResponseReceiptRevision: childToolResponseReceiptRevision,
|
|
9607
9665
|
authoringContractEdition:
|
|
9608
9666
|
childCompatibility.authoringContractEdition,
|
|
9609
9667
|
},
|
|
@@ -10882,12 +10940,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10882
10940
|
durableCallReceiptKey: receiptKey,
|
|
10883
10941
|
playNodeScope: playNodeScopeForToolCallRequest(owner),
|
|
10884
10942
|
executionAuthScopeDigest: owner.executionAuthScopeDigest,
|
|
10943
|
+
providerIdempotencyReceiptKey:
|
|
10944
|
+
owner.providerIdempotencyKeyBase ?? owner.cacheKey,
|
|
10885
10945
|
receiptLeaseExpiresAt: owner.receiptLeaseExpiresAt,
|
|
10886
10946
|
heartbeatReceipt: () =>
|
|
10887
10947
|
this.renewRuntimeToolReceiptOwnership([owner]),
|
|
10888
10948
|
providerIdempotencyKey:
|
|
10889
10949
|
this.providerIdempotencyKeyForToolCall({
|
|
10890
|
-
cacheKey:
|
|
10950
|
+
cacheKey:
|
|
10951
|
+
owner.providerIdempotencyKeyBase ?? owner.cacheKey,
|
|
10891
10952
|
force: owner.force === true,
|
|
10892
10953
|
leaseId: owner.receiptLeaseId,
|
|
10893
10954
|
}),
|
|
@@ -11219,6 +11280,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11219
11280
|
const receiptKeys = batch.memberRequests.map(
|
|
11220
11281
|
(request) => request.cacheKey,
|
|
11221
11282
|
);
|
|
11283
|
+
const providerIdempotencyReceiptKeys =
|
|
11284
|
+
batch.memberRequests.map(
|
|
11285
|
+
(request) =>
|
|
11286
|
+
request.providerIdempotencyKeyBase ?? request.cacheKey,
|
|
11287
|
+
);
|
|
11222
11288
|
const aggregateReceiptKey =
|
|
11223
11289
|
buildDurableToolAggregateReceiptKey({
|
|
11224
11290
|
receiptKeys,
|
|
@@ -11228,6 +11294,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11228
11294
|
toolId: batch.batchOperation,
|
|
11229
11295
|
}),
|
|
11230
11296
|
});
|
|
11297
|
+
const aggregateProviderIdempotencyReceiptKey =
|
|
11298
|
+
buildDurableToolAggregateReceiptKey({
|
|
11299
|
+
receiptKeys: providerIdempotencyReceiptKeys,
|
|
11300
|
+
prefix: 'batch',
|
|
11301
|
+
aggregateReceiptPrefix: buildDurableToolReceiptPrefix({
|
|
11302
|
+
orgId: this.#options.orgId,
|
|
11303
|
+
toolId: batch.batchOperation,
|
|
11304
|
+
}),
|
|
11305
|
+
});
|
|
11231
11306
|
let releaseToolSlot: () => void = () => undefined;
|
|
11232
11307
|
try {
|
|
11233
11308
|
const execution = await this.callToolExecutionAPI(
|
|
@@ -11247,14 +11322,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11247
11322
|
executionAuthScopeDigest:
|
|
11248
11323
|
batch.memberRequests[0]?.executionAuthScopeDigest ??
|
|
11249
11324
|
null,
|
|
11325
|
+
providerIdempotencyReceiptKey:
|
|
11326
|
+
aggregateProviderIdempotencyReceiptKey,
|
|
11250
11327
|
providerIdempotencyKey:
|
|
11251
11328
|
buildDurableToolAggregateProviderIdempotencyKey({
|
|
11252
|
-
aggregateReceiptKey
|
|
11253
|
-
|
|
11329
|
+
aggregateReceiptKey:
|
|
11330
|
+
aggregateProviderIdempotencyReceiptKey,
|
|
11331
|
+
receiptKeys: providerIdempotencyReceiptKeys,
|
|
11254
11332
|
providerIdempotencyKeys: batch.memberRequests.map(
|
|
11255
11333
|
(request) =>
|
|
11256
11334
|
this.providerIdempotencyKeyForToolCall({
|
|
11257
|
-
cacheKey:
|
|
11335
|
+
cacheKey:
|
|
11336
|
+
request.providerIdempotencyKeyBase ??
|
|
11337
|
+
request.cacheKey,
|
|
11258
11338
|
force: request.force === true,
|
|
11259
11339
|
leaseId: request.receiptLeaseId,
|
|
11260
11340
|
}),
|
|
@@ -11481,9 +11561,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11481
11561
|
durableCallReceiptKey: request.receiptKey,
|
|
11482
11562
|
executionAuthScopeDigest:
|
|
11483
11563
|
request.executionAuthScopeDigest,
|
|
11564
|
+
providerIdempotencyReceiptKey:
|
|
11565
|
+
request.providerIdempotencyKeyBase ??
|
|
11566
|
+
request.receiptKey,
|
|
11484
11567
|
providerIdempotencyKey:
|
|
11485
11568
|
this.providerIdempotencyKeyForToolCall({
|
|
11486
|
-
cacheKey:
|
|
11569
|
+
cacheKey:
|
|
11570
|
+
request.providerIdempotencyKeyBase ??
|
|
11571
|
+
request.receiptKey,
|
|
11487
11572
|
force: request.force === true,
|
|
11488
11573
|
leaseId: request.receiptLeaseId,
|
|
11489
11574
|
}),
|
|
@@ -11783,6 +11868,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11783
11868
|
: ((await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null);
|
|
11784
11869
|
const providerIdempotencyKey =
|
|
11785
11870
|
options?.providerIdempotencyKey?.trim() || durableCallReceiptKey;
|
|
11871
|
+
const providerIdempotencyReceiptKey =
|
|
11872
|
+
options?.providerIdempotencyReceiptKey?.trim() ||
|
|
11873
|
+
durableCallReceiptKey;
|
|
11786
11874
|
// Correlation identity is stable across every transport retry,
|
|
11787
11875
|
// including calls without a durable receipt.
|
|
11788
11876
|
const deeplineRequestId = providerIdempotencyKey
|
|
@@ -11836,6 +11924,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11836
11924
|
...(providerIdempotencyKey
|
|
11837
11925
|
? { provider_idempotency_key: providerIdempotencyKey }
|
|
11838
11926
|
: {}),
|
|
11927
|
+
...(providerIdempotencyReceiptKey
|
|
11928
|
+
? {
|
|
11929
|
+
provider_idempotency_receipt_key:
|
|
11930
|
+
providerIdempotencyReceiptKey,
|
|
11931
|
+
}
|
|
11932
|
+
: {}),
|
|
11839
11933
|
...(playNodeScope
|
|
11840
11934
|
? { play_node_scope: playNodeScopeToWire(playNodeScope) }
|
|
11841
11935
|
: {}),
|
|
@@ -60,6 +60,8 @@ export interface RowState {
|
|
|
60
60
|
export interface ToolCallRequest {
|
|
61
61
|
callId: string;
|
|
62
62
|
cacheKey: string;
|
|
63
|
+
/** Stable external-operation identity, independent of receipt serialization. */
|
|
64
|
+
providerIdempotencyKeyBase?: string | null;
|
|
63
65
|
cacheable?: boolean;
|
|
64
66
|
receiptKey?: string | null;
|
|
65
67
|
executionAuthScopeDigest?: string | null;
|
|
@@ -575,6 +577,8 @@ export interface ContextOptions {
|
|
|
575
577
|
toolErrorSchemaVersion?: ToolExecutionErrorSchemaVersion;
|
|
576
578
|
/** Execute-result contract pinned by the immutable Play artifact. */
|
|
577
579
|
toolResponseContract?: ToolResponseContract;
|
|
580
|
+
/** Explicit response-transform revision used for durable receipt identity. */
|
|
581
|
+
toolResponseReceiptRevision?: string;
|
|
578
582
|
/** Short-lived HMAC-signed internal token for tool callbacks. Required for cloud execution. */
|
|
579
583
|
executorToken?: string;
|
|
580
584
|
baseUrl?: string;
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
// resolve.
|
|
8
8
|
} from '../plays/row-identity';
|
|
9
9
|
import { resolveDurableCallCachePolicy } from './durable-call-policy';
|
|
10
|
+
import { normalizeToolResponseReceiptRevision } from './tool-response-contract';
|
|
10
11
|
|
|
11
12
|
export {
|
|
12
13
|
DURABLE_CALL_STALE_AFTER_SECONDS_ERROR,
|
|
@@ -45,6 +46,11 @@ export function buildDurableToolCallCacheKey(input: {
|
|
|
45
46
|
/** Run-stable clock used to choose the stale bucket. */
|
|
46
47
|
cacheEpochMs?: number;
|
|
47
48
|
playLocalScope?: string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Explicit revision for a response transformation. Bump it only when the
|
|
51
|
+
* serialized tool output changes; omit it for historical artifacts.
|
|
52
|
+
*/
|
|
53
|
+
toolResponseReceiptRevision?: string | null;
|
|
48
54
|
}): string {
|
|
49
55
|
const orgId = input.orgId?.trim() || 'org';
|
|
50
56
|
const toolId = input.toolId.trim();
|
|
@@ -58,6 +64,11 @@ export function buildDurableToolCallCacheKey(input: {
|
|
|
58
64
|
);
|
|
59
65
|
}
|
|
60
66
|
const playLocalScope = input.playLocalScope?.trim() || 'play';
|
|
67
|
+
// Keep historical digests byte-for-byte stable when no explicit revision
|
|
68
|
+
// exists on the stored artifact.
|
|
69
|
+
const toolResponseReceiptRevision = normalizeToolResponseReceiptRevision(
|
|
70
|
+
input.toolResponseReceiptRevision,
|
|
71
|
+
);
|
|
61
72
|
const digest = sha256Hex(
|
|
62
73
|
stableStringify({
|
|
63
74
|
kind: 'tool' satisfies DurableCallKind,
|
|
@@ -70,6 +81,7 @@ export function buildDurableToolCallCacheKey(input: {
|
|
|
70
81
|
providerActionVersion,
|
|
71
82
|
cachePolicyVersion:
|
|
72
83
|
input.cachePolicyVersion ?? DURABLE_CALL_CACHE_POLICY_VERSION,
|
|
84
|
+
...(toolResponseReceiptRevision ? { toolResponseReceiptRevision } : {}),
|
|
73
85
|
staleBucket: durableCacheStaleBucket({
|
|
74
86
|
staleAfterSeconds: input.staleAfterSeconds,
|
|
75
87
|
nowMs: input.cacheEpochMs,
|
|
@@ -141,6 +141,8 @@ export interface PlayRunnerContextConfig {
|
|
|
141
141
|
toolErrorSchemaVersion?: ToolExecutionErrorSchemaVersion;
|
|
142
142
|
/** Immutable execute response contract copied from the Play artifact. */
|
|
143
143
|
toolResponseContract?: ToolResponseContract;
|
|
144
|
+
/** Explicit response-transform revision participating in receipt identity. */
|
|
145
|
+
toolResponseReceiptRevision?: string;
|
|
144
146
|
orgId?: string;
|
|
145
147
|
workflowId?: string;
|
|
146
148
|
playId?: string;
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Public tool-response contracts shared by the API, SDK, Play bundler, and
|
|
3
|
-
* runtime.
|
|
3
|
+
* runtime. This is a versioned response transformation contract: a change to
|
|
4
|
+
* the persisted public tool-result shape must introduce a new value here and
|
|
5
|
+
* have newly built Play artifacts select it. Receipt-cache revisions are
|
|
6
|
+
* intentionally separate: ordinary response-contract changes do not refresh
|
|
7
|
+
* durable receipts unless the response transformation changed as well.
|
|
4
8
|
*/
|
|
5
9
|
export const V2_TOOL_RESPONSE_CONTRACT = 'v2-tool-response' as const;
|
|
6
10
|
export const RAW_V2_TOOL_RESPONSE_CONTRACT = 'raw-v2' as const;
|
|
7
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Deliberate durable-receipt boundary for the raw-v2 response transformation.
|
|
14
|
+
* Bump only when that transformation changes serialized tool output. Do not
|
|
15
|
+
* bump for ordinary protocol, authoring, or transport contract changes.
|
|
16
|
+
*/
|
|
17
|
+
export const RAW_V2_TOOL_RESPONSE_RECEIPT_REVISION = 'raw-v2-receipt-v1';
|
|
18
|
+
const TOOL_RESPONSE_RECEIPT_REVISION_PATTERN =
|
|
19
|
+
/^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,63})$/;
|
|
20
|
+
|
|
8
21
|
export type ToolResponseContract =
|
|
9
22
|
| typeof V2_TOOL_RESPONSE_CONTRACT
|
|
10
23
|
| typeof RAW_V2_TOOL_RESPONSE_CONTRACT;
|
|
@@ -20,6 +33,19 @@ export function isToolResponseContract(
|
|
|
20
33
|
);
|
|
21
34
|
}
|
|
22
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Preserves the distinction between an old artifact with no declared response
|
|
38
|
+
* contract and an artifact that explicitly selected the legacy V2 contract.
|
|
39
|
+
* Receipt reuse is controlled separately by `toolResponseReceiptRevision`.
|
|
40
|
+
*/
|
|
41
|
+
export function declaredToolResponseContract(
|
|
42
|
+
value: unknown,
|
|
43
|
+
): ToolResponseContract | undefined {
|
|
44
|
+
if (value == null) return undefined;
|
|
45
|
+
if (isToolResponseContract(value)) return value;
|
|
46
|
+
throw new UnsupportedToolResponseContractError(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
23
49
|
export class UnsupportedToolResponseContractError extends Error {
|
|
24
50
|
constructor(value: unknown) {
|
|
25
51
|
super(
|
|
@@ -29,13 +55,34 @@ export class UnsupportedToolResponseContractError extends Error {
|
|
|
29
55
|
}
|
|
30
56
|
}
|
|
31
57
|
|
|
58
|
+
export class InvalidToolResponseReceiptRevisionError extends Error {
|
|
59
|
+
constructor(value: unknown) {
|
|
60
|
+
super(
|
|
61
|
+
`Tool response receipt revision must be a non-empty static identifier (letters, numbers, '.', '_', or '-'); received ${String(value)}.`,
|
|
62
|
+
);
|
|
63
|
+
this.name = 'InvalidToolResponseReceiptRevisionError';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Missing means a historical artifact and preserves its original receipt key. */
|
|
68
|
+
export function normalizeToolResponseReceiptRevision(
|
|
69
|
+
value: unknown,
|
|
70
|
+
): string | undefined {
|
|
71
|
+
if (value == null) return undefined;
|
|
72
|
+
if (
|
|
73
|
+
typeof value === 'string' &&
|
|
74
|
+
TOOL_RESPONSE_RECEIPT_REVISION_PATTERN.test(value)
|
|
75
|
+
) {
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
throw new InvalidToolResponseReceiptRevisionError(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
32
81
|
/** Missing artifact compatibility predates canonical bodies and stays V2. */
|
|
33
82
|
export function normalizeToolResponseContract(
|
|
34
83
|
value: unknown,
|
|
35
84
|
): ToolResponseContract {
|
|
36
|
-
|
|
37
|
-
if (isToolResponseContract(value)) return value;
|
|
38
|
-
throw new UnsupportedToolResponseContractError(value);
|
|
85
|
+
return declaredToolResponseContract(value) ?? V2_TOOL_RESPONSE_CONTRACT;
|
|
39
86
|
}
|
|
40
87
|
|
|
41
88
|
export function legacyRawFromToolResponseRawV2(
|
|
@@ -31,6 +31,8 @@ export type PlayArtifactCompatibility = {
|
|
|
31
31
|
authoringContractEdition?: PlayAuthoringContractEdition;
|
|
32
32
|
/** Missing preserves the raw-only V2 execute response contract. */
|
|
33
33
|
toolResponseContract?: ToolResponseContract;
|
|
34
|
+
/** Missing preserves the receipt namespace of artifacts created before this revision existed. */
|
|
35
|
+
toolResponseReceiptRevision?: string;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
/** The only executable Play artifact contract. */
|
|
@@ -344,7 +344,12 @@ export type PlayAuthoringAstBindings = {
|
|
|
344
344
|
export type PlayAuthoringBindings = {
|
|
345
345
|
description?: string;
|
|
346
346
|
compatibility?: {
|
|
347
|
-
toolErrorSchemaVersion
|
|
347
|
+
toolErrorSchemaVersion?: ToolExecutionErrorSchemaVersion;
|
|
348
|
+
/**
|
|
349
|
+
* Bump only when a Play's response transformation changes serialized tool
|
|
350
|
+
* output and requires fresh durable tool receipts.
|
|
351
|
+
*/
|
|
352
|
+
toolResponseReceiptRevision?: string;
|
|
348
353
|
};
|
|
349
354
|
inline?: boolean;
|
|
350
355
|
billing?: {
|
|
@@ -1103,6 +1108,28 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
|
|
|
1103
1108
|
errorMessage:
|
|
1104
1109
|
'compatibility.toolErrorSchemaVersion must be the static literal 0 or 1.',
|
|
1105
1110
|
},
|
|
1111
|
+
'compatibility.toolResponseReceiptRevision': {
|
|
1112
|
+
schema: Type.String({
|
|
1113
|
+
minLength: 1,
|
|
1114
|
+
maxLength: 64,
|
|
1115
|
+
pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$',
|
|
1116
|
+
}),
|
|
1117
|
+
fixtures: {
|
|
1118
|
+
valid: 'raw-v2-receipt-v1',
|
|
1119
|
+
invalid: 'has spaces',
|
|
1120
|
+
absent: undefined,
|
|
1121
|
+
unresolved: { expression: 'revision' },
|
|
1122
|
+
edition1: undefined,
|
|
1123
|
+
},
|
|
1124
|
+
referenceType: 'string',
|
|
1125
|
+
required: false,
|
|
1126
|
+
resolution: 'static-required',
|
|
1127
|
+
issueCode: 'play_authoring_binding_invalid',
|
|
1128
|
+
description:
|
|
1129
|
+
'Explicit durable-receipt revision for a response transformation; bump only when serialized tool output changes.',
|
|
1130
|
+
errorMessage:
|
|
1131
|
+
'compatibility.toolResponseReceiptRevision must be a non-empty static identifier using letters, numbers, dots, underscores, or hyphens.',
|
|
1132
|
+
},
|
|
1106
1133
|
inline: {
|
|
1107
1134
|
schema: Type.Boolean(),
|
|
1108
1135
|
fixtures: {
|
|
@@ -2488,7 +2515,7 @@ export const PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
|
|
|
2488
2515
|
'};',
|
|
2489
2516
|
'export type PlayBindings = {',
|
|
2490
2517
|
` description?: ${cloudReferenceType('description')};`,
|
|
2491
|
-
` compatibility?: { toolErrorSchemaVersion
|
|
2518
|
+
` compatibility?: { toolErrorSchemaVersion?: ${cloudReferenceType('compatibility.toolErrorSchemaVersion')}; toolResponseReceiptRevision?: ${cloudReferenceType('compatibility.toolResponseReceiptRevision')} };`,
|
|
2492
2519
|
` inline?: ${cloudReferenceType('inline')};`,
|
|
2493
2520
|
` billing?: { maxCreditsPerRun?: ${cloudReferenceType('billing.maxCreditsPerRun')} };`,
|
|
2494
2521
|
` runtime?: { timeout?: ${cloudReferenceType('runtime.timeout')}; size?: ${cloudReferenceType('runtime.size')} };`,
|