deepline 0.3.72 → 0.3.74
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/monitor-fleet-contract.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +5 -3
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +617 -623
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +40 -10
- package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +62 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +122 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +2 -14
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +9 -92
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +159 -621
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +19 -13
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +1 -4
- package/dist/bundling-sources/shared_libs/plays/tool-response-contract.ts +2 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/install-integrity.json +2 -0
- 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,
|
|
@@ -175,6 +173,7 @@ import {
|
|
|
175
173
|
buildDurableToolAggregateReceiptKey,
|
|
176
174
|
buildDurableToolCallAuthScopeDigest,
|
|
177
175
|
buildDurableToolCallCacheKey,
|
|
176
|
+
buildLegacyRevisionedDurableToolCallCacheKey,
|
|
178
177
|
buildDurableToolPayloadProviderIdempotencyKey,
|
|
179
178
|
buildDurableToolProviderIdempotencyKey,
|
|
180
179
|
buildDurableToolReceiptPrefix,
|
|
@@ -194,6 +193,10 @@ import {
|
|
|
194
193
|
type PlayAuthoringRunScope,
|
|
195
194
|
type PlayAuthoringRuntimeContext,
|
|
196
195
|
} from '../plays/authoring-contract';
|
|
196
|
+
import {
|
|
197
|
+
resolvePlayArtifactRuntimeContract,
|
|
198
|
+
type PlayArtifactRuntimeContract,
|
|
199
|
+
} from './play-artifact-runtime-contract';
|
|
197
200
|
import {
|
|
198
201
|
LOG_LEVELS,
|
|
199
202
|
formatCtxFetchHttpFailureDiagnostic,
|
|
@@ -406,14 +409,8 @@ type InlineCompositionStore = {
|
|
|
406
409
|
governor: PlayExecutionGovernor;
|
|
407
410
|
playName: string;
|
|
408
411
|
staticPipeline: ContextOptions['staticPipeline'];
|
|
409
|
-
/** Immutable
|
|
410
|
-
|
|
411
|
-
/** Immutable authoring semantics pinned by the child play artifact. */
|
|
412
|
-
authoringContractEdition: PlayAuthoringContractEdition;
|
|
413
|
-
/** Execute response contract pinned by the child artifact. */
|
|
414
|
-
toolResponseContract: ToolResponseContract;
|
|
415
|
-
/** Explicit response-transform revision used for durable receipt identity. */
|
|
416
|
-
toolResponseReceiptRevision?: string;
|
|
412
|
+
/** Immutable compatibility projection of the child Play artifact. */
|
|
413
|
+
runtimeContract: PlayArtifactRuntimeContract;
|
|
417
414
|
};
|
|
418
415
|
const inlineCompositionContext =
|
|
419
416
|
new AsyncLocalStorage<InlineCompositionStore>();
|
|
@@ -1088,7 +1085,10 @@ export function resolveToolRuntimeTimeoutMs(
|
|
|
1088
1085
|
authoringContractEdition: PlayAuthoringContractEdition = PLAY_AUTHORING_CONTRACT_EDITION,
|
|
1089
1086
|
): number | undefined {
|
|
1090
1087
|
if (requestedTimeoutMs !== undefined) {
|
|
1091
|
-
if (
|
|
1088
|
+
if (
|
|
1089
|
+
resolvePlayArtifactRuntimeContract({ authoringContractEdition }).semantics
|
|
1090
|
+
.validation === 'strict'
|
|
1091
|
+
) {
|
|
1092
1092
|
validatePlayAuthoringField(
|
|
1093
1093
|
'ctx.tools.execute.timeoutMs',
|
|
1094
1094
|
requestedTimeoutMs,
|
|
@@ -1395,6 +1395,8 @@ function playNodeScopeForToolCallRequest(
|
|
|
1395
1395
|
type ToolExecutionApiOptions = {
|
|
1396
1396
|
timeoutMs?: number;
|
|
1397
1397
|
durableCallReceiptKey?: string | null;
|
|
1398
|
+
/** The receipt key is a completed-result cache for this read-only call. */
|
|
1399
|
+
completedResultCache?: boolean;
|
|
1398
1400
|
executionAuthScopeDigest?: string | null;
|
|
1399
1401
|
/**
|
|
1400
1402
|
* Historical receipt identity used for the API's provider/billing
|
|
@@ -2112,6 +2114,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2112
2114
|
{ active: number; waiters: Array<() => void> }
|
|
2113
2115
|
>();
|
|
2114
2116
|
#options: ContextOptions;
|
|
2117
|
+
private readonly rootRuntimeContract: PlayArtifactRuntimeContract;
|
|
2115
2118
|
private readonly executionScope: RunExecutionScope;
|
|
2116
2119
|
private logBuffer: string[] = [];
|
|
2117
2120
|
private readonly logRateLimitState = new Map<
|
|
@@ -2250,7 +2253,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2250
2253
|
assertNoSecretTaint(request.input, 'ctx.tools.execute input');
|
|
2251
2254
|
if (
|
|
2252
2255
|
request.timeoutMs !== undefined &&
|
|
2253
|
-
this.
|
|
2256
|
+
this.currentRuntimeSemantics.validation === 'strict'
|
|
2254
2257
|
) {
|
|
2255
2258
|
validatePlayAuthoringField(
|
|
2256
2259
|
'ctx.tools.execute.timeoutMs',
|
|
@@ -2259,7 +2262,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2259
2262
|
}
|
|
2260
2263
|
if (
|
|
2261
2264
|
request.receiptWaitMs !== undefined &&
|
|
2262
|
-
this.
|
|
2265
|
+
this.currentRuntimeSemantics.validation === 'strict'
|
|
2263
2266
|
) {
|
|
2264
2267
|
validatePlayAuthoringField(
|
|
2265
2268
|
'ctx.tools.execute.receiptWaitMs',
|
|
@@ -2403,7 +2406,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2403
2406
|
}
|
|
2404
2407
|
const handle = createSecretHandle(name.trim());
|
|
2405
2408
|
const value =
|
|
2406
|
-
this.
|
|
2409
|
+
this.currentRuntimeSemantics.secrets.value === 'plaintext_promise'
|
|
2407
2410
|
? createPlaintextSecretPromise(handle.name, () =>
|
|
2408
2411
|
this.resolveSecretHandle(handle),
|
|
2409
2412
|
)
|
|
@@ -2427,6 +2430,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2427
2430
|
};
|
|
2428
2431
|
|
|
2429
2432
|
constructor(options: ContextOptions) {
|
|
2433
|
+
this.rootRuntimeContract =
|
|
2434
|
+
options.artifactRuntimeContract ??
|
|
2435
|
+
resolvePlayArtifactRuntimeContract({
|
|
2436
|
+
authoringContractEdition: options.authoringContractEdition,
|
|
2437
|
+
toolResponseContract: options.toolResponseContract,
|
|
2438
|
+
toolResponseReceiptRevision: options.toolResponseReceiptRevision,
|
|
2439
|
+
});
|
|
2430
2440
|
const fixtureBehavior = validateFixtureBehavior(options.fixtureBehavior);
|
|
2431
2441
|
if (fixtureBehavior.ok === false) {
|
|
2432
2442
|
throw new Error(fixtureBehavior.error);
|
|
@@ -2746,42 +2756,54 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2746
2756
|
|
|
2747
2757
|
/**
|
|
2748
2758
|
* Inline children share this context's queues, but not its artifact contract.
|
|
2749
|
-
* Each child
|
|
2750
|
-
*
|
|
2759
|
+
* Each child uses its own admitted semantics and response projection even
|
|
2760
|
+
* when the parent was published under another artifact contract.
|
|
2751
2761
|
*/
|
|
2752
2762
|
private get currentToolErrorSchemaVersion(): ToolExecutionErrorSchemaVersion {
|
|
2753
|
-
|
|
2754
|
-
// strings. Every runtime tool call and receipt rehydration uses v1.
|
|
2755
|
-
return TOOL_EXECUTION_ERROR_SCHEMA_VERSION;
|
|
2763
|
+
return this.currentRuntimeContract.toolErrorSchemaVersion;
|
|
2756
2764
|
}
|
|
2757
2765
|
|
|
2758
2766
|
private get currentToolResponseContract(): ToolResponseContract {
|
|
2759
|
-
return
|
|
2760
|
-
|
|
2761
|
-
|
|
2767
|
+
return this.currentRuntimeContract.toolResponseContract;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
/**
|
|
2771
|
+
* A real published artifact pins its response presentation in its launch
|
|
2772
|
+
* contract. In-process callers that omit the option are runtime consumers,
|
|
2773
|
+
* not historical Play artifacts, and retain the canonical result they pass
|
|
2774
|
+
* in. This keeps the context API backward-compatible while letting a pinned
|
|
2775
|
+
* legacy artifact request its old projection after a cache read.
|
|
2776
|
+
*/
|
|
2777
|
+
private get hasPinnedToolResponseContract(): boolean {
|
|
2778
|
+
return (
|
|
2779
|
+
this.activeInlineComposition !== null ||
|
|
2780
|
+
this.#options.artifactRuntimeContract !== undefined ||
|
|
2781
|
+
this.#options.toolResponseContract !== undefined
|
|
2762
2782
|
);
|
|
2763
2783
|
}
|
|
2764
2784
|
|
|
2765
2785
|
/**
|
|
2766
|
-
*
|
|
2767
|
-
*
|
|
2768
|
-
* response header still normalizes it to the legacy V2 behavior above.
|
|
2786
|
+
* Compatibility metadata retained for already-published artifacts. It never
|
|
2787
|
+
* changes the completed-result cache identity.
|
|
2769
2788
|
*/
|
|
2770
2789
|
private get currentToolResponseReceiptRevision(): string | undefined {
|
|
2771
|
-
|
|
2772
|
-
return this.activeInlineComposition.toolResponseReceiptRevision;
|
|
2773
|
-
}
|
|
2774
|
-
return this.#options.toolResponseReceiptRevision;
|
|
2790
|
+
return this.currentRuntimeContract.toolResponseReceiptRevision;
|
|
2775
2791
|
}
|
|
2776
2792
|
|
|
2777
2793
|
private get currentAuthoringContractEdition(): PlayAuthoringContractEdition {
|
|
2794
|
+
return this.currentRuntimeContract.semantics.authoringContractEdition;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
private get currentRuntimeContract(): PlayArtifactRuntimeContract {
|
|
2778
2798
|
return (
|
|
2779
|
-
this.activeInlineComposition?.
|
|
2780
|
-
this.#options.authoringContractEdition ??
|
|
2781
|
-
PLAY_AUTHORING_CONTRACT_EDITION
|
|
2799
|
+
this.activeInlineComposition?.runtimeContract ?? this.rootRuntimeContract
|
|
2782
2800
|
);
|
|
2783
2801
|
}
|
|
2784
2802
|
|
|
2803
|
+
private get currentRuntimeSemantics() {
|
|
2804
|
+
return this.currentRuntimeContract.semantics;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2785
2807
|
private recordPlayCallStep(input: {
|
|
2786
2808
|
playId: string;
|
|
2787
2809
|
execution?: 'inline';
|
|
@@ -4020,14 +4042,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4020
4042
|
);
|
|
4021
4043
|
if (normalized) byKey.set(normalized.key, normalized);
|
|
4022
4044
|
}
|
|
4023
|
-
await this.
|
|
4045
|
+
await this.reconcileIncompleteRuntimeStepReceiptCompletions(
|
|
4024
4046
|
normalizedInputs,
|
|
4025
4047
|
byKey,
|
|
4026
4048
|
);
|
|
4027
4049
|
return byKey;
|
|
4028
4050
|
}
|
|
4029
4051
|
|
|
4030
|
-
private async
|
|
4052
|
+
private async reconcileIncompleteRuntimeStepReceiptCompletions(
|
|
4031
4053
|
receipts: Array<{
|
|
4032
4054
|
key: string;
|
|
4033
4055
|
runId: string;
|
|
@@ -4038,32 +4060,45 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4038
4060
|
completedByKey: Map<string, RuntimeStepReceipt>,
|
|
4039
4061
|
): Promise<void> {
|
|
4040
4062
|
if (
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
!this.#options.getRuntimeStepReceipts)
|
|
4063
|
+
!this.#options.getRuntimeStepReceipt &&
|
|
4064
|
+
!this.#options.getRuntimeStepReceipts
|
|
4044
4065
|
) {
|
|
4045
4066
|
return;
|
|
4046
4067
|
}
|
|
4047
|
-
|
|
4068
|
+
// The receipt gateway is allowed to return a compact terminal
|
|
4069
|
+
// acknowledgement without `output`. That is sufficient for old callers
|
|
4070
|
+
// which keep the live result, but a completed-result cache must hydrate
|
|
4071
|
+
// the stored output before handing it to a cache cohort.
|
|
4072
|
+
const incompleteKeys = receipts
|
|
4048
4073
|
.map((receipt) => receipt.key.trim())
|
|
4049
|
-
.filter((key) =>
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4074
|
+
.filter((key) => {
|
|
4075
|
+
if (!key) return false;
|
|
4076
|
+
const completed = completedByKey.get(key);
|
|
4077
|
+
return (
|
|
4078
|
+
!completed ||
|
|
4079
|
+
((completed.status === 'completed' ||
|
|
4080
|
+
completed.status === 'skipped') &&
|
|
4081
|
+
completed.output === undefined)
|
|
4082
|
+
);
|
|
4083
|
+
});
|
|
4084
|
+
if (incompleteKeys.length === 0) return;
|
|
4085
|
+
const latest = await this.getRuntimeStepReceipts(incompleteKeys);
|
|
4086
|
+
for (const key of incompleteKeys) {
|
|
4053
4087
|
const recovered = latest.get(key);
|
|
4054
4088
|
if (
|
|
4055
|
-
recovered?.status === 'completed' ||
|
|
4056
|
-
|
|
4089
|
+
(recovered?.status === 'completed' ||
|
|
4090
|
+
recovered?.status === 'skipped') &&
|
|
4091
|
+
recovered.output !== undefined
|
|
4057
4092
|
) {
|
|
4058
4093
|
completedByKey.set(key, recovered);
|
|
4059
4094
|
}
|
|
4060
4095
|
}
|
|
4061
|
-
const recoveredCount =
|
|
4062
|
-
completedByKey.
|
|
4096
|
+
const recoveredCount = incompleteKeys.filter(
|
|
4097
|
+
(key) => completedByKey.get(key)?.output !== undefined,
|
|
4063
4098
|
).length;
|
|
4064
4099
|
if (recoveredCount > 0) {
|
|
4065
4100
|
this.runtimeLog(
|
|
4066
|
-
`Runtime tool receipt completion response
|
|
4101
|
+
`Runtime tool receipt completion response hydrated ${recoveredCount}/${incompleteKeys.length} incomplete receipt(s) from durable store read-back.`,
|
|
4067
4102
|
);
|
|
4068
4103
|
}
|
|
4069
4104
|
}
|
|
@@ -4178,17 +4213,39 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4178
4213
|
);
|
|
4179
4214
|
}
|
|
4180
4215
|
|
|
4181
|
-
private async durableToolCallCacheKeyForScope(
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4216
|
+
private async durableToolCallCacheKeyForScope(input: {
|
|
4217
|
+
toolId: string;
|
|
4218
|
+
requestInput: Record<string, unknown>;
|
|
4219
|
+
executionAuthScopeDigest?: string | null;
|
|
4220
|
+
staleAfterSeconds?: number | null;
|
|
4221
|
+
playLocalScope?: string | null;
|
|
4222
|
+
}): Promise<string> {
|
|
4223
|
+
const identity = await this.durableToolCallCacheIdentity(input);
|
|
4224
|
+
return buildDurableToolCallCacheKey(identity);
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
private async legacyDurableToolCallCacheKeyForScope(input: {
|
|
4228
|
+
toolId: string;
|
|
4229
|
+
requestInput: Record<string, unknown>;
|
|
4230
|
+
executionAuthScopeDigest?: string | null;
|
|
4231
|
+
staleAfterSeconds?: number | null;
|
|
4232
|
+
playLocalScope?: string | null;
|
|
4233
|
+
}): Promise<string | null> {
|
|
4234
|
+
const revision = this.currentToolResponseReceiptRevision;
|
|
4235
|
+
if (!revision) return null;
|
|
4236
|
+
return buildLegacyRevisionedDurableToolCallCacheKey({
|
|
4237
|
+
...(await this.durableToolCallCacheIdentity(input)),
|
|
4238
|
+
toolResponseReceiptRevision: revision,
|
|
4239
|
+
});
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
private async durableToolCallCacheIdentity(input: {
|
|
4243
|
+
toolId: string;
|
|
4244
|
+
requestInput: Record<string, unknown>;
|
|
4245
|
+
executionAuthScopeDigest?: string | null;
|
|
4246
|
+
staleAfterSeconds?: number | null;
|
|
4247
|
+
playLocalScope?: string | null;
|
|
4248
|
+
}): Promise<Parameters<typeof buildDurableToolCallCacheKey>[0]> {
|
|
4192
4249
|
const providerActionVersion =
|
|
4193
4250
|
(await this.#options.getToolActionCacheVersion?.(input.toolId))?.trim() ??
|
|
4194
4251
|
'';
|
|
@@ -4196,7 +4253,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4196
4253
|
input.executionAuthScopeDigest?.trim() ||
|
|
4197
4254
|
(await this.resolveToolAuthScopeDigest(input.toolId))?.trim() ||
|
|
4198
4255
|
null;
|
|
4199
|
-
return
|
|
4256
|
+
return {
|
|
4200
4257
|
orgId: this.#options.orgId,
|
|
4201
4258
|
playLocalScope: input.playLocalScope,
|
|
4202
4259
|
toolId: input.toolId,
|
|
@@ -4207,29 +4264,33 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
4207
4264
|
executionAuthScopeDigest,
|
|
4208
4265
|
}),
|
|
4209
4266
|
providerActionVersion,
|
|
4210
|
-
toolResponseReceiptRevision,
|
|
4211
4267
|
staleAfterSeconds: input.staleAfterSeconds,
|
|
4212
4268
|
cacheEpochMs: this.durableCallCacheEpochMs,
|
|
4269
|
+
};
|
|
4270
|
+
}
|
|
4271
|
+
|
|
4272
|
+
private async durableToolCallCacheKey(input: {
|
|
4273
|
+
toolId: string;
|
|
4274
|
+
requestInput: Record<string, unknown>;
|
|
4275
|
+
executionAuthScopeDigest?: string | null;
|
|
4276
|
+
staleAfterSeconds?: number | null;
|
|
4277
|
+
}): Promise<string> {
|
|
4278
|
+
return await this.durableToolCallCacheKeyForScope({
|
|
4279
|
+
...input,
|
|
4280
|
+
playLocalScope: this.currentGovernance.currentPlayId,
|
|
4213
4281
|
});
|
|
4214
4282
|
}
|
|
4215
4283
|
|
|
4216
|
-
private async
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
.
|
|
4225
|
-
|
|
4226
|
-
return await this.durableToolCallCacheKeyForScope(
|
|
4227
|
-
{
|
|
4228
|
-
...input,
|
|
4229
|
-
playLocalScope: this.currentGovernance.currentPlayId,
|
|
4230
|
-
},
|
|
4231
|
-
toolResponseReceiptRevision,
|
|
4232
|
-
);
|
|
4284
|
+
private async legacyDurableToolCallCacheKey(input: {
|
|
4285
|
+
toolId: string;
|
|
4286
|
+
requestInput: Record<string, unknown>;
|
|
4287
|
+
executionAuthScopeDigest?: string | null;
|
|
4288
|
+
staleAfterSeconds?: number | null;
|
|
4289
|
+
}): Promise<string | null> {
|
|
4290
|
+
return await this.legacyDurableToolCallCacheKeyForScope({
|
|
4291
|
+
...input,
|
|
4292
|
+
playLocalScope: this.currentGovernance.currentPlayId,
|
|
4293
|
+
});
|
|
4233
4294
|
}
|
|
4234
4295
|
|
|
4235
4296
|
private async resolveToolAuthScopeDigest(
|
|
@@ -5379,6 +5440,23 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
5379
5440
|
return null;
|
|
5380
5441
|
}
|
|
5381
5442
|
|
|
5443
|
+
private canReuseToolResultForCurrentResponseContract(
|
|
5444
|
+
result: unknown,
|
|
5445
|
+
): boolean {
|
|
5446
|
+
if (this.currentToolResponseContract !== RAW_V2_TOOL_RESPONSE_CONTRACT) {
|
|
5447
|
+
return true;
|
|
5448
|
+
}
|
|
5449
|
+
const toolResult = isToolExecuteResult(result)
|
|
5450
|
+
? result
|
|
5451
|
+
: isSerializedToolExecuteResult(result)
|
|
5452
|
+
? deserializeToolExecuteResult(result)
|
|
5453
|
+
: null;
|
|
5454
|
+
return (
|
|
5455
|
+
toolResult !== null &&
|
|
5456
|
+
Object.prototype.hasOwnProperty.call(toolResult.toolResponse, 'rawV2')
|
|
5457
|
+
);
|
|
5458
|
+
}
|
|
5459
|
+
|
|
5382
5460
|
private cacheToolResult(
|
|
5383
5461
|
toolId: string,
|
|
5384
5462
|
rowCacheKey: string,
|
|
@@ -5491,6 +5569,38 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
5491
5569
|
);
|
|
5492
5570
|
}
|
|
5493
5571
|
|
|
5572
|
+
/**
|
|
5573
|
+
* Receipts retain the canonical raw-v2 provider outcome. Older artifacts
|
|
5574
|
+
* receive their legacy projection only at the Play boundary, after cache
|
|
5575
|
+
* lookup and persistence have finished.
|
|
5576
|
+
*/
|
|
5577
|
+
private materializeToolExecutionResult(
|
|
5578
|
+
result: ToolExecuteResult,
|
|
5579
|
+
): ToolExecuteResult {
|
|
5580
|
+
if (
|
|
5581
|
+
this.currentToolResponseContract === RAW_V2_TOOL_RESPONSE_CONTRACT ||
|
|
5582
|
+
!this.hasPinnedToolResponseContract ||
|
|
5583
|
+
!Object.prototype.hasOwnProperty.call(result.toolResponse, 'rawV2')
|
|
5584
|
+
) {
|
|
5585
|
+
return result;
|
|
5586
|
+
}
|
|
5587
|
+
const serialized = serializeToolExecuteResult(result);
|
|
5588
|
+
if (serialized.__kind !== 'deepline.tool_execute_result.v2') {
|
|
5589
|
+
return result;
|
|
5590
|
+
}
|
|
5591
|
+
return deserializeToolExecuteResult({
|
|
5592
|
+
...serialized,
|
|
5593
|
+
__kind: 'deepline.tool_execute_result.v1',
|
|
5594
|
+
toolResponse: {
|
|
5595
|
+
raw: legacyRawFromToolResponseRawV2(
|
|
5596
|
+
serialized.toolResponse.rawV2,
|
|
5597
|
+
serialized.toolResponse.view,
|
|
5598
|
+
serialized.toolResponse.responseMeta,
|
|
5599
|
+
),
|
|
5600
|
+
},
|
|
5601
|
+
});
|
|
5602
|
+
}
|
|
5603
|
+
|
|
5494
5604
|
private attachCustomerDbDatasetResult(
|
|
5495
5605
|
toolId: string,
|
|
5496
5606
|
requestInput: Record<string, unknown> | undefined,
|
|
@@ -9222,7 +9332,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9222
9332
|
}
|
|
9223
9333
|
if (
|
|
9224
9334
|
options?.timeoutMs !== undefined &&
|
|
9225
|
-
this.
|
|
9335
|
+
this.currentRuntimeSemantics.validation === 'strict'
|
|
9226
9336
|
) {
|
|
9227
9337
|
validatePlayAuthoringField(
|
|
9228
9338
|
'ctx.tools.execute.timeoutMs',
|
|
@@ -9231,7 +9341,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9231
9341
|
}
|
|
9232
9342
|
if (
|
|
9233
9343
|
options?.receiptWaitMs !== undefined &&
|
|
9234
|
-
this.
|
|
9344
|
+
this.currentRuntimeSemantics.validation === 'strict'
|
|
9235
9345
|
) {
|
|
9236
9346
|
validatePlayAuthoringField(
|
|
9237
9347
|
'ctx.tools.execute.receiptWaitMs',
|
|
@@ -9272,16 +9382,26 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9272
9382
|
executionAuthScopeDigest,
|
|
9273
9383
|
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
9274
9384
|
});
|
|
9275
|
-
let
|
|
9276
|
-
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9385
|
+
let legacyDurableCacheKey = await this.legacyDurableToolCallCacheKey({
|
|
9386
|
+
toolId,
|
|
9387
|
+
requestInput: input,
|
|
9388
|
+
executionAuthScopeDigest,
|
|
9389
|
+
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
9390
|
+
});
|
|
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;
|
|
9394
|
+
let directResultReceiptKey = durableCacheKey;
|
|
9395
|
+
// A raw-v2 artifact must never read an older receipt that cannot render
|
|
9396
|
+
// raw-v2. If both the primary and its revisioned recovery receipt are
|
|
9397
|
+
// unrenderable, make a live call rather than returning either cache entry.
|
|
9398
|
+
// We cannot safely replace an immutable completed receipt, so that rare
|
|
9399
|
+
// incompatible state deliberately forgoes caching until it is repaired.
|
|
9400
|
+
let bypassDirectResultCache = false;
|
|
9401
|
+
const checkpointCacheKeys = [
|
|
9402
|
+
durableCacheKey,
|
|
9403
|
+
...(legacyDurableCacheKey ? [legacyDurableCacheKey] : []),
|
|
9404
|
+
];
|
|
9285
9405
|
|
|
9286
9406
|
const executeTool = async (context?: {
|
|
9287
9407
|
leaseId?: string | null;
|
|
@@ -9310,7 +9430,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9310
9430
|
if (!store) {
|
|
9311
9431
|
const directCacheKey = durableCacheKey;
|
|
9312
9432
|
let initialDirectReceipt: RuntimeStepReceipt | null | undefined;
|
|
9313
|
-
const
|
|
9433
|
+
const cachedCandidate = !cacheableToolResult
|
|
9314
9434
|
? null
|
|
9315
9435
|
: toolCachePolicy.force
|
|
9316
9436
|
? null
|
|
@@ -9319,6 +9439,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9319
9439
|
checkpointCacheKeys,
|
|
9320
9440
|
'direct',
|
|
9321
9441
|
);
|
|
9442
|
+
const cached =
|
|
9443
|
+
cachedCandidate &&
|
|
9444
|
+
this.canReuseToolResultForCurrentResponseContract(
|
|
9445
|
+
cachedCandidate.result.result,
|
|
9446
|
+
)
|
|
9447
|
+
? cachedCandidate
|
|
9448
|
+
: null;
|
|
9322
9449
|
if (cached) {
|
|
9323
9450
|
this.runtimeLog(`Calling tool: ${toolId} recovered from checkpoint`);
|
|
9324
9451
|
// Started and settled are paired in a finally: an uncovered throw
|
|
@@ -9342,7 +9469,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9342
9469
|
outcome: 'cached',
|
|
9343
9470
|
durationMs: 0,
|
|
9344
9471
|
});
|
|
9345
|
-
return recovered;
|
|
9472
|
+
return this.materializeToolExecutionResult(recovered);
|
|
9346
9473
|
} finally {
|
|
9347
9474
|
if (!settled) {
|
|
9348
9475
|
this.emitToolCallLifecycle('settled', toolId, normalizedKey, {
|
|
@@ -9361,17 +9488,36 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9361
9488
|
const historicalReceipt = await this.withRuntimeReceiptHydrationTurn(
|
|
9362
9489
|
async () => await this.getRuntimeStepReceipt(directCacheKey),
|
|
9363
9490
|
);
|
|
9364
|
-
|
|
9491
|
+
const historicalOutput = historicalReceipt
|
|
9492
|
+
? this.runtimeReceiptOutput<unknown>(historicalReceipt)
|
|
9493
|
+
: undefined;
|
|
9494
|
+
const historicalIsTerminal =
|
|
9495
|
+
historicalReceipt?.status === 'completed' ||
|
|
9496
|
+
historicalReceipt?.status === 'skipped';
|
|
9497
|
+
const historicalCanServeCurrentContract =
|
|
9498
|
+
!historicalIsTerminal ||
|
|
9499
|
+
this.canReuseToolResultForCurrentResponseContract(historicalOutput);
|
|
9500
|
+
initialDirectReceipt = historicalCanServeCurrentContract
|
|
9501
|
+
? historicalReceipt
|
|
9502
|
+
: null;
|
|
9503
|
+
if (!historicalCanServeCurrentContract) {
|
|
9504
|
+
if (!legacyDurableCacheKey) {
|
|
9505
|
+
throw new Error(
|
|
9506
|
+
`Tool ${toolId} has an unrenderable legacy result but no response-contract recovery cache key.`,
|
|
9507
|
+
);
|
|
9508
|
+
}
|
|
9509
|
+
directResultReceiptKey = legacyDurableCacheKey;
|
|
9510
|
+
}
|
|
9365
9511
|
if (
|
|
9366
9512
|
historicalReceipt &&
|
|
9367
|
-
|
|
9368
|
-
|
|
9513
|
+
historicalIsTerminal &&
|
|
9514
|
+
historicalCanServeCurrentContract &&
|
|
9369
9515
|
!isSerializedToolExecuteResult(historicalReceipt.output) &&
|
|
9370
9516
|
!isToolExecuteResult(historicalReceipt.output)
|
|
9371
9517
|
) {
|
|
9372
9518
|
const historicalOutput =
|
|
9373
9519
|
this.runtimeReceiptOutput<unknown>(historicalReceipt);
|
|
9374
|
-
|
|
9520
|
+
const recoveredHistorical = markToolExecuteResultExecutionOutcome(
|
|
9375
9521
|
historicalOutput,
|
|
9376
9522
|
toolExecutionOutcomeForDurableReceipt({
|
|
9377
9523
|
source: 'cache',
|
|
@@ -9379,6 +9525,43 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9379
9525
|
cacheKey: directCacheKey,
|
|
9380
9526
|
}),
|
|
9381
9527
|
);
|
|
9528
|
+
return isToolExecuteResult(recoveredHistorical)
|
|
9529
|
+
? this.materializeToolExecutionResult(recoveredHistorical)
|
|
9530
|
+
: recoveredHistorical;
|
|
9531
|
+
}
|
|
9532
|
+
if (
|
|
9533
|
+
(!historicalReceipt || !historicalCanServeCurrentContract) &&
|
|
9534
|
+
legacyDurableCacheKey
|
|
9535
|
+
) {
|
|
9536
|
+
const legacyReceipt = await this.withRuntimeReceiptHydrationTurn(
|
|
9537
|
+
async () =>
|
|
9538
|
+
await this.getRuntimeStepReceipt(legacyDurableCacheKey!),
|
|
9539
|
+
);
|
|
9540
|
+
if (
|
|
9541
|
+
legacyReceipt &&
|
|
9542
|
+
(legacyReceipt.status === 'completed' ||
|
|
9543
|
+
legacyReceipt.status === 'skipped') &&
|
|
9544
|
+
legacyReceipt.output !== undefined
|
|
9545
|
+
) {
|
|
9546
|
+
const recoveredLegacy = markToolExecuteResultExecutionOutcome(
|
|
9547
|
+
this.runtimeReceiptOutput(legacyReceipt),
|
|
9548
|
+
toolExecutionOutcomeForDurableReceipt({
|
|
9549
|
+
source: 'cache',
|
|
9550
|
+
receiptKey: legacyDurableCacheKey,
|
|
9551
|
+
cacheKey: directCacheKey,
|
|
9552
|
+
}),
|
|
9553
|
+
);
|
|
9554
|
+
if (
|
|
9555
|
+
this.canReuseToolResultForCurrentResponseContract(
|
|
9556
|
+
recoveredLegacy,
|
|
9557
|
+
)
|
|
9558
|
+
) {
|
|
9559
|
+
return isToolExecuteResult(recoveredLegacy)
|
|
9560
|
+
? this.materializeToolExecutionResult(recoveredLegacy)
|
|
9561
|
+
: recoveredLegacy;
|
|
9562
|
+
}
|
|
9563
|
+
bypassDirectResultCache = true;
|
|
9564
|
+
}
|
|
9382
9565
|
}
|
|
9383
9566
|
}
|
|
9384
9567
|
this.runtimeLog(
|
|
@@ -9416,23 +9599,27 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9416
9599
|
'Direct tool execution requires a stable logical call identity.',
|
|
9417
9600
|
);
|
|
9418
9601
|
}
|
|
9419
|
-
const
|
|
9602
|
+
const useDirectResultReceipts =
|
|
9603
|
+
cacheableToolResult && !bypassDirectResultCache;
|
|
9604
|
+
const physicalDirectKey = useDirectResultReceipts
|
|
9420
9605
|
? directCacheKey
|
|
9421
9606
|
: `${buildDurableToolReceiptPrefix({
|
|
9422
9607
|
orgId: this.#options.orgId ?? 'unknown-org',
|
|
9423
9608
|
toolId,
|
|
9424
9609
|
})}${stableDigest(`${this.currentRunId}:${logicalCallId}:always-fresh`)}`;
|
|
9610
|
+
const resultReceiptKey = directResultReceiptKey;
|
|
9425
9611
|
const providerOperationKey = providerIdempotencyKeyBase;
|
|
9426
|
-
const resultReceiptKey = directCacheKey;
|
|
9427
9612
|
const toolRetryPolicy = await this.#options
|
|
9428
9613
|
.getToolRetryPolicy?.(toolId, input)
|
|
9429
9614
|
.catch(() => null);
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9615
|
+
if (
|
|
9616
|
+
bypassDirectResultCache &&
|
|
9617
|
+
toolRetryPolicy?.requiresExecutionFence === true
|
|
9618
|
+
) {
|
|
9619
|
+
throw new Error(
|
|
9620
|
+
`Tool ${toolId} has an unrenderable completed result under an execution fence and cannot safely refresh it.`,
|
|
9621
|
+
);
|
|
9622
|
+
}
|
|
9436
9623
|
const dispatcher = {
|
|
9437
9624
|
dispatch: async ({
|
|
9438
9625
|
providerOperationKey: operationKey,
|
|
@@ -9440,7 +9627,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9440
9627
|
providerOperationKey: string | null;
|
|
9441
9628
|
}) => {
|
|
9442
9629
|
if (
|
|
9443
|
-
|
|
9630
|
+
useDirectResultReceipts &&
|
|
9444
9631
|
operationKey !== providerOperationKey
|
|
9445
9632
|
) {
|
|
9446
9633
|
throw new Error(
|
|
@@ -9453,18 +9640,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9453
9640
|
startedAt: directCallStartedAt,
|
|
9454
9641
|
input,
|
|
9455
9642
|
options: {
|
|
9456
|
-
...(
|
|
9643
|
+
...(useDirectResultReceipts
|
|
9457
9644
|
? {
|
|
9458
9645
|
durableCallReceiptKey: physicalDirectKey,
|
|
9646
|
+
completedResultCache: true,
|
|
9459
9647
|
executionAuthScopeDigest,
|
|
9460
|
-
providerIdempotencyReceiptKey: operationKey,
|
|
9461
|
-
providerIdempotencyKey:
|
|
9462
|
-
this.providerIdempotencyKeyForToolCall({
|
|
9463
|
-
cacheKey: providerIdempotencyKeyBase,
|
|
9464
|
-
force: toolCachePolicy.force,
|
|
9465
|
-
leaseId: directReceiptLeaseId,
|
|
9466
|
-
logicalCallId,
|
|
9467
|
-
}),
|
|
9468
9648
|
...(directReceiptLeaseId &&
|
|
9469
9649
|
(this.#options.heartbeatRuntimeStepReceipts ||
|
|
9470
9650
|
this.#options.getRuntimeStepReceipt ||
|
|
@@ -9487,6 +9667,24 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9487
9667
|
: {}),
|
|
9488
9668
|
}
|
|
9489
9669
|
: {}),
|
|
9670
|
+
...(toolRetryPolicy?.requiresExecutionFence === true
|
|
9671
|
+
? {
|
|
9672
|
+
providerIdempotencyReceiptKey:
|
|
9673
|
+
cacheableToolResult && operationKey
|
|
9674
|
+
? operationKey
|
|
9675
|
+
: physicalDirectKey,
|
|
9676
|
+
providerIdempotencyKey:
|
|
9677
|
+
this.providerIdempotencyKeyForToolCall({
|
|
9678
|
+
cacheKey:
|
|
9679
|
+
useDirectResultReceipts && operationKey
|
|
9680
|
+
? providerIdempotencyKeyBase
|
|
9681
|
+
: physicalDirectKey,
|
|
9682
|
+
force: cacheableToolResult && toolCachePolicy.force,
|
|
9683
|
+
leaseId: directReceiptLeaseId,
|
|
9684
|
+
logicalCallId,
|
|
9685
|
+
}),
|
|
9686
|
+
}
|
|
9687
|
+
: {}),
|
|
9490
9688
|
playNodeScope: buildPlayNodeScope({
|
|
9491
9689
|
toolId,
|
|
9492
9690
|
callKey: normalizedKey,
|
|
@@ -9511,14 +9709,14 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9511
9709
|
execution: toolExecutionMetadataForOutcome({
|
|
9512
9710
|
kind: 'live',
|
|
9513
9711
|
cacheKey: directCacheKey,
|
|
9514
|
-
...(
|
|
9712
|
+
...(useDirectResultReceipts
|
|
9515
9713
|
? { receiptKey: resultReceiptKey }
|
|
9516
9714
|
: {}),
|
|
9517
9715
|
}),
|
|
9518
9716
|
});
|
|
9519
9717
|
},
|
|
9520
9718
|
};
|
|
9521
|
-
const toolCall =
|
|
9719
|
+
const toolCall = useDirectResultReceipts
|
|
9522
9720
|
? createToolCallJob({
|
|
9523
9721
|
resultReceipts: createRuntimeStepToolResultReceipts({
|
|
9524
9722
|
store: this.durableReceiptExecutionStore(),
|
|
@@ -9526,7 +9724,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9526
9724
|
...(initialDirectReceipt !== undefined
|
|
9527
9725
|
? {
|
|
9528
9726
|
initialReceipt: {
|
|
9529
|
-
key:
|
|
9727
|
+
key: directResultReceiptKey,
|
|
9530
9728
|
receipt: initialDirectReceipt,
|
|
9531
9729
|
},
|
|
9532
9730
|
}
|
|
@@ -9537,6 +9735,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9537
9735
|
? {
|
|
9538
9736
|
executionLock: {
|
|
9539
9737
|
required: true,
|
|
9738
|
+
key: providerOperationKey,
|
|
9540
9739
|
ttlMs: Math.min(
|
|
9541
9740
|
600_000,
|
|
9542
9741
|
resolveToolRuntimeTimeoutMs(
|
|
@@ -9599,11 +9798,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9599
9798
|
})
|
|
9600
9799
|
: await createAlwaysFreshToolCallAdapter({ dispatcher }).call(call);
|
|
9601
9800
|
settleDirectCall(toolAttemptOutcomeForResult(wrapped));
|
|
9602
|
-
if (
|
|
9603
|
-
this.cacheToolResult(
|
|
9801
|
+
if (useDirectResultReceipts) {
|
|
9802
|
+
this.cacheToolResult(
|
|
9803
|
+
toolId,
|
|
9804
|
+
directResultReceiptKey,
|
|
9805
|
+
wrapped,
|
|
9806
|
+
'direct',
|
|
9807
|
+
);
|
|
9604
9808
|
this.#options.onBatchComplete?.(this.checkpoint);
|
|
9605
9809
|
}
|
|
9606
|
-
return wrapped;
|
|
9810
|
+
return this.materializeToolExecutionResult(wrapped);
|
|
9607
9811
|
} catch (error) {
|
|
9608
9812
|
settleDirectCall('failed', error);
|
|
9609
9813
|
throw error;
|
|
@@ -9637,17 +9841,22 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9637
9841
|
: this.getCachedToolResultCandidate(toolId, checkpointCacheKeys);
|
|
9638
9842
|
if (cached) {
|
|
9639
9843
|
this.runtimeLog(` Row ${rowId} ${toolId}: recovered from checkpoint`);
|
|
9640
|
-
return
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9844
|
+
return this.materializeToolExecutionResult(
|
|
9845
|
+
await this.wrapToolExecutionResult({
|
|
9846
|
+
toolId,
|
|
9847
|
+
status: cached.result.result == null ? 'no_result' : 'completed',
|
|
9848
|
+
result: cached.result.result,
|
|
9849
|
+
requestInput: input,
|
|
9850
|
+
execution: toolExecutionMetadataForOutcome({
|
|
9851
|
+
kind: 'checkpoint',
|
|
9852
|
+
cacheKey: cached.cacheKey,
|
|
9853
|
+
}),
|
|
9648
9854
|
}),
|
|
9649
|
-
|
|
9855
|
+
);
|
|
9650
9856
|
}
|
|
9857
|
+
const rowToolRetryPolicy = await this.#options
|
|
9858
|
+
.getToolRetryPolicy?.(toolId, input)
|
|
9859
|
+
.catch(() => null);
|
|
9651
9860
|
|
|
9652
9861
|
// ADR 0018: record this leg before it runs. `producer` still names the
|
|
9653
9862
|
// last leg (the usual cascade winner); `producers` keeps every leg, which
|
|
@@ -9702,7 +9911,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9702
9911
|
providerIdempotencyKeyBase,
|
|
9703
9912
|
cacheable: cacheableToolResult,
|
|
9704
9913
|
receiptKey: cacheableToolResult ? durableCacheKey : null,
|
|
9914
|
+
legacyReceiptKeys: legacyDurableCacheKey
|
|
9915
|
+
? [legacyDurableCacheKey]
|
|
9916
|
+
: [],
|
|
9705
9917
|
executionAuthScopeDigest,
|
|
9918
|
+
requiresExecutionFence:
|
|
9919
|
+
rowToolRetryPolicy?.requiresExecutionFence === true,
|
|
9706
9920
|
force: toolCachePolicy.force,
|
|
9707
9921
|
forceFailedRefresh: toolCachePolicy.forceFailedRefresh,
|
|
9708
9922
|
rowId,
|
|
@@ -9722,7 +9936,9 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9722
9936
|
return await pendingToolCall.then(
|
|
9723
9937
|
(value) => {
|
|
9724
9938
|
settleProducerAttempt(toolAttemptOutcomeForResult(value));
|
|
9725
|
-
return
|
|
9939
|
+
return this.materializeToolExecutionResult(
|
|
9940
|
+
value as ToolExecuteResult,
|
|
9941
|
+
);
|
|
9726
9942
|
},
|
|
9727
9943
|
(error: unknown) => {
|
|
9728
9944
|
settleProducerAttempt('failed');
|
|
@@ -9771,16 +9987,21 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9771
9987
|
executionAuthScopeDigest,
|
|
9772
9988
|
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
9773
9989
|
});
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9990
|
+
legacyDurableCacheKey = await this.legacyDurableToolCallCacheKey({
|
|
9991
|
+
toolId,
|
|
9992
|
+
requestInput: input,
|
|
9993
|
+
executionAuthScopeDigest,
|
|
9994
|
+
staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
|
|
9995
|
+
});
|
|
9996
|
+
providerIdempotencyKeyBase = durableCacheKey;
|
|
9997
|
+
directResultReceiptKey = durableCacheKey;
|
|
9998
|
+
bypassDirectResultCache = false;
|
|
9999
|
+
checkpointCacheKeys.splice(
|
|
10000
|
+
0,
|
|
10001
|
+
checkpointCacheKeys.length,
|
|
10002
|
+
durableCacheKey,
|
|
10003
|
+
...(legacyDurableCacheKey ? [legacyDurableCacheKey] : []),
|
|
9782
10004
|
);
|
|
9783
|
-
checkpointCacheKeys[0] = durableCacheKey;
|
|
9784
10005
|
}
|
|
9785
10006
|
}
|
|
9786
10007
|
|
|
@@ -9949,8 +10170,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9949
10170
|
const childCompatibility = normalizePlayContractCompatibility(
|
|
9950
10171
|
childCompatibilitySnapshot,
|
|
9951
10172
|
);
|
|
9952
|
-
const childToolErrorSchemaVersion =
|
|
9953
|
-
childCompatibility.toolErrorSchemaVersion;
|
|
9954
10173
|
const childToolResponseContract = childCompatibility.toolResponseContract;
|
|
9955
10174
|
const childToolResponseReceiptRevision =
|
|
9956
10175
|
childCompatibility.toolResponseReceiptRevision;
|
|
@@ -10025,11 +10244,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10025
10244
|
governor: inlineChildGovernor,
|
|
10026
10245
|
playName: resolvedName,
|
|
10027
10246
|
staticPipeline: resolvedPlay.staticPipeline ?? null,
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10247
|
+
runtimeContract: resolvePlayArtifactRuntimeContract({
|
|
10248
|
+
authoringContractEdition:
|
|
10249
|
+
childCompatibility.authoringContractEdition,
|
|
10250
|
+
toolResponseContract: childToolResponseContract,
|
|
10251
|
+
toolResponseReceiptRevision: childToolResponseReceiptRevision,
|
|
10252
|
+
}),
|
|
10033
10253
|
},
|
|
10034
10254
|
() => this.executeResolvedPlay(resolvedPlay, this, input),
|
|
10035
10255
|
);
|
|
@@ -10338,7 +10558,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10338
10558
|
async sleep(ms: number): Promise<void> {
|
|
10339
10559
|
this.assertInlineChildContract('suspending_child');
|
|
10340
10560
|
const delayMs =
|
|
10341
|
-
this.
|
|
10561
|
+
this.currentRuntimeSemantics.validation === 'strict'
|
|
10342
10562
|
? (validatePlayAuthoringField('ctx.sleep.ms', ms), ms)
|
|
10343
10563
|
: Math.max(0, Math.round(ms));
|
|
10344
10564
|
if (this.#options.durableBoundaries) {
|
|
@@ -10464,14 +10684,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10464
10684
|
...secretHeaderMarkers,
|
|
10465
10685
|
},
|
|
10466
10686
|
row: rowFetchScope,
|
|
10467
|
-
//
|
|
10468
|
-
// non-cacheable errors
|
|
10469
|
-
//
|
|
10470
|
-
//
|
|
10471
|
-
...(this.
|
|
10687
|
+
// A pinned fetch policy that turns non-2xx responses into typed,
|
|
10688
|
+
// non-cacheable errors needs its own receipt epoch. Preserve the
|
|
10689
|
+
// historical epoch selected by the artifact rather than deriving a
|
|
10690
|
+
// new cache key from every future authoring edition.
|
|
10691
|
+
...(this.currentRuntimeSemantics.fetch.receiptIdentityEdition !==
|
|
10692
|
+
null
|
|
10472
10693
|
? {
|
|
10473
10694
|
authoringContractEdition:
|
|
10474
|
-
this.
|
|
10695
|
+
this.currentRuntimeSemantics.fetch.receiptIdentityEdition,
|
|
10475
10696
|
}
|
|
10476
10697
|
: {}),
|
|
10477
10698
|
}),
|
|
@@ -10487,7 +10708,11 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10487
10708
|
httpStatus: output.status,
|
|
10488
10709
|
});
|
|
10489
10710
|
}
|
|
10490
|
-
if (
|
|
10711
|
+
if (
|
|
10712
|
+
!output.ok &&
|
|
10713
|
+
this.currentRuntimeSemantics.fetch.httpFailure ===
|
|
10714
|
+
'throw_typed_error'
|
|
10715
|
+
) {
|
|
10491
10716
|
throw new CtxFetchHttpError(output);
|
|
10492
10717
|
}
|
|
10493
10718
|
return output;
|
|
@@ -10495,7 +10720,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10495
10720
|
shouldPersistFailure: (error) =>
|
|
10496
10721
|
!(
|
|
10497
10722
|
error instanceof CtxFetchHttpError &&
|
|
10498
|
-
this.
|
|
10723
|
+
!this.currentRuntimeSemantics.fetch.persistHttpFailure
|
|
10499
10724
|
),
|
|
10500
10725
|
execute: async ({ retainExternalCallSlot }) => {
|
|
10501
10726
|
const secretHeaders = await this.resolveSecretAuth(secretAuth);
|
|
@@ -10699,11 +10924,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
10699
10924
|
});
|
|
10700
10925
|
}
|
|
10701
10926
|
|
|
10702
|
-
//
|
|
10703
|
-
// a failed durable operation.
|
|
10704
|
-
// completion so the
|
|
10705
|
-
//
|
|
10706
|
-
if (
|
|
10927
|
+
// The artifact-pinned fetch policy decides whether a non-2xx
|
|
10928
|
+
// response is a returned record or a failed durable operation.
|
|
10929
|
+
// Throw before checkpoint/receipt completion so the throwing
|
|
10930
|
+
// policy never caches its failure.
|
|
10931
|
+
if (
|
|
10932
|
+
!output.ok &&
|
|
10933
|
+
this.currentRuntimeSemantics.fetch.httpFailure ===
|
|
10934
|
+
'throw_typed_error'
|
|
10935
|
+
) {
|
|
10707
10936
|
throw new CtxFetchHttpError(output);
|
|
10708
10937
|
}
|
|
10709
10938
|
|
|
@@ -11009,75 +11238,59 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11009
11238
|
/**
|
|
11010
11239
|
* Concrete RuntimeStepReceipt adapter for the map receipt-cohort Module.
|
|
11011
11240
|
*
|
|
11012
|
-
*
|
|
11013
|
-
*
|
|
11014
|
-
* protocol: both need an atomic claim/reclaim, a single cohort heartbeat,
|
|
11015
|
-
* and lease-fenced terminal fanout. Keeping that at one seam prevents a
|
|
11016
|
-
* native batch from becoming a second, subtly different receipt runtime.
|
|
11241
|
+
* Scalar and provider-native map calls share completed-result cache reads
|
|
11242
|
+
* and atomic successful inserts. A miss has no durable execution owner.
|
|
11017
11243
|
*/
|
|
11018
11244
|
private createRuntimeStepToolCallReceiptCohortStore(input: {
|
|
11019
11245
|
toolId: string;
|
|
11020
11246
|
requestForReceipt: ReadonlyMap<string, ToolCallRequest>;
|
|
11021
11247
|
}): ToolCallReceiptCohortStore {
|
|
11022
11248
|
const asRecord = async (
|
|
11023
|
-
|
|
11249
|
+
resultReceiptKey: string,
|
|
11250
|
+
storedReceiptKey: string,
|
|
11024
11251
|
receipt: RuntimeStepReceipt | null | undefined,
|
|
11025
11252
|
): Promise<ToolCallReceiptRecord | null> => {
|
|
11026
11253
|
if (!receipt) return null;
|
|
11027
|
-
const normalized = this.normalizeRuntimeStepReceipt(
|
|
11254
|
+
const normalized = this.normalizeRuntimeStepReceipt(
|
|
11255
|
+
storedReceiptKey,
|
|
11256
|
+
receipt,
|
|
11257
|
+
);
|
|
11028
11258
|
if (!normalized) return null;
|
|
11029
|
-
|
|
11259
|
+
if (
|
|
11260
|
+
normalized.status !== 'completed' &&
|
|
11261
|
+
normalized.status !== 'skipped'
|
|
11262
|
+
) {
|
|
11263
|
+
return null;
|
|
11264
|
+
}
|
|
11265
|
+
// A terminal acknowledgement without output is not a cache hit. The
|
|
11266
|
+
// completion path hydrates it first; if that read cannot provide an
|
|
11267
|
+
// output, retain the live result rather than resolving `undefined`.
|
|
11268
|
+
if (normalized.output === undefined) return null;
|
|
11269
|
+
const request = input.requestForReceipt.get(resultReceiptKey);
|
|
11030
11270
|
if (!request) {
|
|
11031
11271
|
throw new Error(
|
|
11032
|
-
`Unknown Tool Call Receipt Cohort receipt ${
|
|
11272
|
+
`Unknown Tool Call Receipt Cohort receipt ${resultReceiptKey}.`,
|
|
11033
11273
|
);
|
|
11034
11274
|
}
|
|
11035
|
-
const
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
11040
|
-
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
attachedToReceiptKey: receiptKey,
|
|
11051
|
-
}),
|
|
11052
|
-
})
|
|
11053
|
-
: undefined;
|
|
11275
|
+
const result = await this.wrapToolExecutionResult({
|
|
11276
|
+
toolId: input.toolId,
|
|
11277
|
+
status:
|
|
11278
|
+
normalized.output === null || normalized.output === undefined
|
|
11279
|
+
? 'no_result'
|
|
11280
|
+
: 'completed',
|
|
11281
|
+
result: this.runtimeReceiptOutput(normalized),
|
|
11282
|
+
requestInput: request.input,
|
|
11283
|
+
execution: toolExecutionMetadataForOutcome({
|
|
11284
|
+
kind: 'cache',
|
|
11285
|
+
cacheKey: request.cacheKey,
|
|
11286
|
+
receiptKey: resultReceiptKey,
|
|
11287
|
+
attachedToReceiptKey: storedReceiptKey,
|
|
11288
|
+
}),
|
|
11289
|
+
});
|
|
11054
11290
|
return {
|
|
11055
|
-
resultReceiptKey
|
|
11291
|
+
resultReceiptKey,
|
|
11056
11292
|
status: normalized.status,
|
|
11057
|
-
|
|
11058
|
-
...(normalized.status === 'failed'
|
|
11059
|
-
? {
|
|
11060
|
-
error: runtimeReceiptFailureError(
|
|
11061
|
-
normalized,
|
|
11062
|
-
'Durable tool call failed',
|
|
11063
|
-
this.currentToolErrorSchemaVersion,
|
|
11064
|
-
),
|
|
11065
|
-
}
|
|
11066
|
-
: {}),
|
|
11067
|
-
...(normalized.leaseId &&
|
|
11068
|
-
(normalized.leaseOwnerRunId ?? normalized.runId)?.trim()
|
|
11069
|
-
? {
|
|
11070
|
-
lease: {
|
|
11071
|
-
leaseId: normalized.leaseId,
|
|
11072
|
-
ownerRunId: (normalized.leaseOwnerRunId ??
|
|
11073
|
-
normalized.runId)!.trim(),
|
|
11074
|
-
},
|
|
11075
|
-
}
|
|
11076
|
-
: {}),
|
|
11077
|
-
...(normalized.leaseExpiresAt
|
|
11078
|
-
? { leaseExpiresAt: normalized.leaseExpiresAt }
|
|
11079
|
-
: {}),
|
|
11080
|
-
...(normalized.claimState ? { claimState: normalized.claimState } : {}),
|
|
11293
|
+
result,
|
|
11081
11294
|
};
|
|
11082
11295
|
};
|
|
11083
11296
|
const recordsFor = async (
|
|
@@ -11086,10 +11299,22 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11086
11299
|
): Promise<ToolCallReceiptRecord[]> =>
|
|
11087
11300
|
(
|
|
11088
11301
|
await Promise.all(
|
|
11089
|
-
receiptKeys.map(
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11302
|
+
receiptKeys.map(async (receiptKey) => {
|
|
11303
|
+
const request = input.requestForReceipt.get(receiptKey);
|
|
11304
|
+
const candidates = [
|
|
11305
|
+
receiptKey,
|
|
11306
|
+
...(request?.legacyReceiptKeys ?? []),
|
|
11307
|
+
];
|
|
11308
|
+
for (const candidate of candidates) {
|
|
11309
|
+
const record = await asRecord(
|
|
11310
|
+
receiptKey,
|
|
11311
|
+
candidate,
|
|
11312
|
+
receipts.get(candidate),
|
|
11313
|
+
);
|
|
11314
|
+
if (record) return record;
|
|
11315
|
+
}
|
|
11316
|
+
return null;
|
|
11317
|
+
}),
|
|
11093
11318
|
)
|
|
11094
11319
|
).filter((record): record is ToolCallReceiptRecord => record !== null);
|
|
11095
11320
|
|
|
@@ -11099,83 +11324,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11099
11324
|
async () =>
|
|
11100
11325
|
await recordsFor(
|
|
11101
11326
|
resultReceiptKeys,
|
|
11102
|
-
await this.getRuntimeStepReceipts(
|
|
11327
|
+
await this.getRuntimeStepReceipts([
|
|
11328
|
+
...new Set(
|
|
11329
|
+
resultReceiptKeys.flatMap((receiptKey) => [
|
|
11330
|
+
receiptKey,
|
|
11331
|
+
...(input.requestForReceipt.get(receiptKey)
|
|
11332
|
+
?.legacyReceiptKeys ?? []),
|
|
11333
|
+
]),
|
|
11334
|
+
),
|
|
11335
|
+
]),
|
|
11103
11336
|
),
|
|
11104
11337
|
),
|
|
11105
|
-
claim: async ({ receipts }) => {
|
|
11106
|
-
const claims = new Map<string, RuntimeStepReceipt>();
|
|
11107
|
-
const groups = new Map<string, Array<(typeof receipts)[number]>>();
|
|
11108
|
-
for (const receipt of receipts) {
|
|
11109
|
-
const groupKey = [
|
|
11110
|
-
receipt.reclaimRunning ? 'reclaim' : 'ordinary',
|
|
11111
|
-
receipt.force ? 'force' : 'cached',
|
|
11112
|
-
receipt.forceFailedRefresh ? 'failed-force' : 'failed-cached',
|
|
11113
|
-
].join(':');
|
|
11114
|
-
const group = groups.get(groupKey) ?? [];
|
|
11115
|
-
group.push(receipt);
|
|
11116
|
-
groups.set(groupKey, group);
|
|
11117
|
-
}
|
|
11118
|
-
for (const group of groups.values()) {
|
|
11119
|
-
const first = group[0]!;
|
|
11120
|
-
const claimed = await this.claimRuntimeStepReceipts(
|
|
11121
|
-
group.map((receipt) => receipt.resultReceiptKey),
|
|
11122
|
-
this.currentReceiptOwnerRunId,
|
|
11123
|
-
first.reclaimRunning,
|
|
11124
|
-
first.force,
|
|
11125
|
-
first.forceFailedRefresh,
|
|
11126
|
-
);
|
|
11127
|
-
for (const [key, receipt] of claimed) claims.set(key, receipt);
|
|
11128
|
-
}
|
|
11129
|
-
const safeFailedKeys = receipts.flatMap((receipt) => {
|
|
11130
|
-
const claimed = claims.get(receipt.resultReceiptKey);
|
|
11131
|
-
return claimed && this.canRetryWithoutProviderDispatch(claimed)
|
|
11132
|
-
? [receipt.resultReceiptKey]
|
|
11133
|
-
: [];
|
|
11134
|
-
});
|
|
11135
|
-
if (safeFailedKeys.length > 0) {
|
|
11136
|
-
const refreshed = await this.claimRuntimeStepReceipts(
|
|
11137
|
-
safeFailedKeys,
|
|
11138
|
-
this.currentReceiptOwnerRunId,
|
|
11139
|
-
false,
|
|
11140
|
-
false,
|
|
11141
|
-
true,
|
|
11142
|
-
);
|
|
11143
|
-
for (const [key, receipt] of refreshed) claims.set(key, receipt);
|
|
11144
|
-
}
|
|
11145
|
-
return await recordsFor(
|
|
11146
|
-
receipts.map((receipt) => receipt.resultReceiptKey),
|
|
11147
|
-
claims,
|
|
11148
|
-
);
|
|
11149
|
-
},
|
|
11150
|
-
heartbeat: async ({ receipts }) => {
|
|
11151
|
-
if (!this.#options.heartbeatRuntimeStepReceipts) {
|
|
11152
|
-
return await recordsFor(
|
|
11153
|
-
receipts.map((receipt) => receipt.resultReceiptKey),
|
|
11154
|
-
await this.getRuntimeStepReceipts(
|
|
11155
|
-
receipts.map((receipt) => receipt.resultReceiptKey),
|
|
11156
|
-
),
|
|
11157
|
-
);
|
|
11158
|
-
}
|
|
11159
|
-
const response = await this.#options.heartbeatRuntimeStepReceipts({
|
|
11160
|
-
runId: this.currentReceiptOwnerRunId,
|
|
11161
|
-
runAttempt: this.currentRunAttempt,
|
|
11162
|
-
keys: receipts.map((receipt) => receipt.resultReceiptKey),
|
|
11163
|
-
leaseIds: receipts.map((receipt) => receipt.lease.leaseId),
|
|
11164
|
-
});
|
|
11165
|
-
const byKey = new Map<string, RuntimeStepReceipt>();
|
|
11166
|
-
for (let index = 0; index < receipts.length; index += 1) {
|
|
11167
|
-
const request = receipts[index]!;
|
|
11168
|
-
const normalized = this.normalizeRuntimeStepReceipt(
|
|
11169
|
-
request.resultReceiptKey,
|
|
11170
|
-
response[index],
|
|
11171
|
-
);
|
|
11172
|
-
if (normalized) byKey.set(request.resultReceiptKey, normalized);
|
|
11173
|
-
}
|
|
11174
|
-
return await recordsFor(
|
|
11175
|
-
receipts.map((receipt) => receipt.resultReceiptKey),
|
|
11176
|
-
byKey,
|
|
11177
|
-
);
|
|
11178
|
-
},
|
|
11179
11338
|
complete: async ({ receipts }) => {
|
|
11180
11339
|
const completed = await this.completeRuntimeStepReceipts(
|
|
11181
11340
|
receipts.map((receipt) => ({
|
|
@@ -11190,33 +11349,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11190
11349
|
completed,
|
|
11191
11350
|
);
|
|
11192
11351
|
},
|
|
11193
|
-
fail: async ({ receipts }) => {
|
|
11194
|
-
const failed = await this.failRuntimeStepReceipts(
|
|
11195
|
-
receipts.map((receipt) => ({
|
|
11196
|
-
key: receipt.resultReceiptKey,
|
|
11197
|
-
runId: this.currentReceiptOwnerRunId,
|
|
11198
|
-
leaseId: receipt.lease.leaseId,
|
|
11199
|
-
error: this.formatRuntimeError(receipt.error),
|
|
11200
|
-
errorPayload: serializeToolExecutionFailure(receipt.error),
|
|
11201
|
-
failureKind: runtimeReceiptFailureKindForError(receipt.error),
|
|
11202
|
-
})),
|
|
11203
|
-
);
|
|
11204
|
-
return await recordsFor(
|
|
11205
|
-
receipts.map((receipt) => receipt.resultReceiptKey),
|
|
11206
|
-
failed,
|
|
11207
|
-
);
|
|
11208
|
-
},
|
|
11209
11352
|
};
|
|
11210
11353
|
}
|
|
11211
11354
|
|
|
11212
11355
|
/**
|
|
11213
|
-
*
|
|
11214
|
-
*
|
|
11215
|
-
*
|
|
11216
|
-
* Tool Call boundary to be only cosmetic. This is the narrow replacement
|
|
11217
|
-
* slice: cacheable members of a *non-native* map hand their whole receipt
|
|
11218
|
-
* lifecycle to the cohort Module, while this Context remains only the
|
|
11219
|
-
* transport/presentation Adapter.
|
|
11356
|
+
* Cacheable scalar map members share completed-result reads and successful
|
|
11357
|
+
* writes through the cohort Module. This Context only transports and
|
|
11358
|
+
* presents results.
|
|
11220
11359
|
*
|
|
11221
11360
|
* Do not widen this to physical native batches or always-fresh calls. A
|
|
11222
11361
|
* native batch owns a different aggregate operation receipt, and
|
|
@@ -11318,7 +11457,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11318
11457
|
const resolve = async (
|
|
11319
11458
|
request: ToolCallRequest,
|
|
11320
11459
|
result: ToolExecuteResult,
|
|
11321
|
-
source: 'cache' | '
|
|
11460
|
+
source: 'cache' | 'owner',
|
|
11322
11461
|
): Promise<void> => {
|
|
11323
11462
|
const receiptKey = request.receiptKey!;
|
|
11324
11463
|
const finalResult = cloneToolExecuteResultWithExecution(
|
|
@@ -11353,180 +11492,111 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11353
11492
|
};
|
|
11354
11493
|
const reject = async (request: ToolCallRequest, error: unknown) => {
|
|
11355
11494
|
await this.rejectToolCall(toolId, request, error, {
|
|
11356
|
-
//
|
|
11357
|
-
// legacy writer here would race its member terminal transition.
|
|
11495
|
+
// Failures are never cache entries, including failed local misses.
|
|
11358
11496
|
persistReceiptFailure: false,
|
|
11359
11497
|
});
|
|
11360
11498
|
};
|
|
11361
11499
|
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11500
|
+
const handled = new Set<string>();
|
|
11501
|
+
const followerResults: Promise<void>[] = [];
|
|
11502
|
+
const collectReadyMembers = (): Array<{
|
|
11503
|
+
request: ToolCallRequest;
|
|
11504
|
+
receipts: ToolResultReceipts;
|
|
11505
|
+
}> => {
|
|
11506
|
+
const owned: Array<{
|
|
11366
11507
|
request: ToolCallRequest;
|
|
11367
11508
|
receipts: ToolResultReceipts;
|
|
11368
|
-
}>
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11378
|
-
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
} else if (member.kind === 'following') {
|
|
11385
|
-
followOrRecover.push(
|
|
11386
|
-
member.result.then(
|
|
11387
|
-
async (result) => await resolve(request, result, 'in_flight'),
|
|
11388
|
-
async (error: unknown) => await reject(request, error),
|
|
11389
|
-
),
|
|
11390
|
-
);
|
|
11391
|
-
} else {
|
|
11392
|
-
request.receiptLeaseId = member.lease.leaseId;
|
|
11393
|
-
request.receiptLeaseExpiresAt = member.leaseExpiresAt ?? null;
|
|
11394
|
-
owned.push({ request, receipts: member.receipts });
|
|
11395
|
-
}
|
|
11509
|
+
}> = [];
|
|
11510
|
+
for (const request of requests) {
|
|
11511
|
+
if (handled.has(request.callId)) continue;
|
|
11512
|
+
const member = cohort.member(request.callId);
|
|
11513
|
+
handled.add(request.callId);
|
|
11514
|
+
if (member.kind === 'completed') {
|
|
11515
|
+
followerResults.push(resolve(request, member.result, member.source));
|
|
11516
|
+
} else if (member.kind === 'following') {
|
|
11517
|
+
followerResults.push(
|
|
11518
|
+
member.result.then(
|
|
11519
|
+
async (result) => await resolve(request, result, 'owner'),
|
|
11520
|
+
async (error: unknown) => await reject(request, error),
|
|
11521
|
+
),
|
|
11522
|
+
);
|
|
11523
|
+
} else {
|
|
11524
|
+
owned.push({ request, receipts: member.receipts });
|
|
11396
11525
|
}
|
|
11397
|
-
|
|
11398
|
-
|
|
11526
|
+
}
|
|
11527
|
+
return owned;
|
|
11528
|
+
};
|
|
11399
11529
|
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
),
|
|
11530
|
+
const width = Math.min(
|
|
11531
|
+
this.governor.policy.concurrency.toolCalls,
|
|
11532
|
+
Math.max(
|
|
11533
|
+
1,
|
|
11534
|
+
await this.resourceGovernor.suggestedToolParallelism(
|
|
11535
|
+
toolId,
|
|
11536
|
+
this.governor.policy.concurrency.toolCalls,
|
|
11408
11537
|
),
|
|
11409
|
-
)
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
11428
|
-
|
|
11429
|
-
|
|
11430
|
-
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
|
|
11434
|
-
|
|
11435
|
-
|
|
11436
|
-
|
|
11437
|
-
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
);
|
|
11449
|
-
}
|
|
11450
|
-
} catch (error) {
|
|
11451
|
-
if (error instanceof ToolCallReceiptLeaseLostError) {
|
|
11452
|
-
throw new RuntimeReceiptLeaseLostError({
|
|
11453
|
-
receiptKey: request.receiptKey!,
|
|
11454
|
-
runId: this.currentReceiptOwnerRunId,
|
|
11455
|
-
leaseId:
|
|
11456
|
-
request.receiptLeaseId ?? 'missing-cohort-lease',
|
|
11457
|
-
});
|
|
11458
|
-
}
|
|
11459
|
-
throw error;
|
|
11460
|
-
}
|
|
11461
|
-
},
|
|
11462
|
-
playNodeScope: playNodeScopeForToolCallRequest(request),
|
|
11463
|
-
durableCallReceiptKey: request.receiptKey,
|
|
11464
|
-
executionAuthScopeDigest: request.executionAuthScopeDigest,
|
|
11465
|
-
providerIdempotencyReceiptKey:
|
|
11466
|
-
request.providerIdempotencyKeyBase ?? request.receiptKey,
|
|
11467
|
-
providerIdempotencyKey:
|
|
11468
|
-
this.providerIdempotencyKeyForToolCall({
|
|
11469
|
-
cacheKey:
|
|
11470
|
-
request.providerIdempotencyKeyBase ??
|
|
11471
|
-
request.receiptKey!,
|
|
11472
|
-
force: request.force === true,
|
|
11473
|
-
leaseId: request.receiptLeaseId,
|
|
11474
|
-
logicalCallId: request.logicalCallId,
|
|
11475
|
-
}),
|
|
11476
|
-
receiptLeaseExpiresAt: request.receiptLeaseExpiresAt,
|
|
11477
|
-
timeoutMs:
|
|
11478
|
-
typeof request.timeoutMs === 'number' &&
|
|
11479
|
-
request.timeoutMs > 0
|
|
11480
|
-
? request.timeoutMs
|
|
11481
|
-
: undefined,
|
|
11482
|
-
retainToolSlot: (release) => {
|
|
11483
|
-
releaseToolSlot = release;
|
|
11484
|
-
},
|
|
11538
|
+
),
|
|
11539
|
+
);
|
|
11540
|
+
const dispatchOwned = async (
|
|
11541
|
+
owned: Array<{
|
|
11542
|
+
request: ToolCallRequest;
|
|
11543
|
+
receipts: ToolResultReceipts;
|
|
11544
|
+
}>,
|
|
11545
|
+
) =>
|
|
11546
|
+
await dispatchBoundedSettled(
|
|
11547
|
+
owned,
|
|
11548
|
+
width,
|
|
11549
|
+
async ({ request, receipts }) => {
|
|
11550
|
+
if (this.persistenceLatch.tripped) {
|
|
11551
|
+
const error = new RuntimePersistenceCircuitOpenError(
|
|
11552
|
+
this.persistenceLatch,
|
|
11553
|
+
);
|
|
11554
|
+
throw error;
|
|
11555
|
+
}
|
|
11556
|
+
let releaseToolSlot: () => void = () => undefined;
|
|
11557
|
+
try {
|
|
11558
|
+
const result = await this.executeMappedToolCallThroughModule({
|
|
11559
|
+
request,
|
|
11560
|
+
dispatchToolId: toolId,
|
|
11561
|
+
dispatchPayload: request.input,
|
|
11562
|
+
resultReceiptKey: request.receiptKey!,
|
|
11563
|
+
providerOperationKey:
|
|
11564
|
+
request.providerIdempotencyKeyBase ?? request.receiptKey!,
|
|
11565
|
+
resultReceipts: receipts,
|
|
11566
|
+
apiOptions: {
|
|
11567
|
+
playNodeScope: playNodeScopeForToolCallRequest(request),
|
|
11568
|
+
durableCallReceiptKey: request.receiptKey,
|
|
11569
|
+
completedResultCache: true,
|
|
11570
|
+
executionAuthScopeDigest: request.executionAuthScopeDigest,
|
|
11571
|
+
timeoutMs:
|
|
11572
|
+
typeof request.timeoutMs === 'number' && request.timeoutMs > 0
|
|
11573
|
+
? request.timeoutMs
|
|
11574
|
+
: undefined,
|
|
11575
|
+
retainToolSlot: (release) => {
|
|
11576
|
+
releaseToolSlot = release;
|
|
11485
11577
|
},
|
|
11486
|
-
}
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
11491
|
-
|
|
11492
|
-
|
|
11493
|
-
}
|
|
11494
|
-
|
|
11578
|
+
},
|
|
11579
|
+
});
|
|
11580
|
+
await resolve(request, result, 'owner');
|
|
11581
|
+
} catch (error) {
|
|
11582
|
+
await reject(request, error);
|
|
11583
|
+
} finally {
|
|
11584
|
+
releaseToolSlot();
|
|
11585
|
+
}
|
|
11586
|
+
},
|
|
11587
|
+
);
|
|
11495
11588
|
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
// overlap: a broken foreign read must not strand an already-owned
|
|
11499
|
-
// provider operation behind it.
|
|
11500
|
-
const initialDispatch = dispatchOwned(collectReadyMembers());
|
|
11501
|
-
const settlement = cohort.settle();
|
|
11502
|
-
const [initialResult, settlementResult] = await Promise.allSettled([
|
|
11503
|
-
initialDispatch,
|
|
11504
|
-
settlement,
|
|
11505
|
-
]);
|
|
11506
|
-
// A recovery can reclaim an expired receipt and turn it into another
|
|
11507
|
-
// owned member. Collect its final state after the initial work settles.
|
|
11508
|
-
const recoveredDispatch = await dispatchOwned(collectReadyMembers());
|
|
11509
|
-
await Promise.allSettled(followOrRecover);
|
|
11510
|
-
// dispatchBoundedSettled intentionally captures provider failures; all
|
|
11511
|
-
// affected row resolvers have already been rejected above.
|
|
11512
|
-
void initialResult;
|
|
11513
|
-
void recoveredDispatch;
|
|
11514
|
-
if (settlementResult.status === 'rejected') {
|
|
11515
|
-
throw settlementResult.reason;
|
|
11516
|
-
}
|
|
11517
|
-
} finally {
|
|
11518
|
-
heartbeat.stop();
|
|
11519
|
-
}
|
|
11589
|
+
await dispatchOwned(collectReadyMembers());
|
|
11590
|
+
await Promise.allSettled(followerResults);
|
|
11520
11591
|
return successful;
|
|
11521
11592
|
}
|
|
11522
11593
|
|
|
11523
11594
|
/**
|
|
11524
11595
|
* Executes provider-native batches through the Native Batch Tool Call
|
|
11525
|
-
* Module.
|
|
11526
|
-
*
|
|
11527
|
-
*
|
|
11528
|
-
*
|
|
11529
|
-
* to map rows.
|
|
11596
|
+
* Module. The Module reads exact completed-result cache entries, batches
|
|
11597
|
+
* misses, then publishes each successful split result. This Context only
|
|
11598
|
+
* compiles provider payloads, invokes transport, and presents terminal
|
|
11599
|
+
* member values to map rows.
|
|
11530
11600
|
*/
|
|
11531
11601
|
private async executeCacheableNativeMapToolCallCohort(input: {
|
|
11532
11602
|
toolId: string;
|
|
@@ -11581,9 +11651,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11581
11651
|
batchParallelismCeiling,
|
|
11582
11652
|
)
|
|
11583
11653
|
: batchParallelismCeiling;
|
|
11584
|
-
const batchProviderActionVersion =
|
|
11585
|
-
(await this.#options.getToolActionCacheVersion?.(toolId))?.trim() ??
|
|
11586
|
-
'unversioned';
|
|
11587
11654
|
const successful: ToolCallRequest[] = [];
|
|
11588
11655
|
|
|
11589
11656
|
const deliver = (request: ToolCallRequest, result: ToolExecuteResult) => {
|
|
@@ -11613,7 +11680,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11613
11680
|
};
|
|
11614
11681
|
const reject = async (request: ToolCallRequest, error: unknown) => {
|
|
11615
11682
|
await this.rejectToolCall(toolId, request, error, {
|
|
11616
|
-
//
|
|
11683
|
+
// Failed provider calls are never cache entries.
|
|
11617
11684
|
persistReceiptFailure: false,
|
|
11618
11685
|
});
|
|
11619
11686
|
};
|
|
@@ -11675,15 +11742,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11675
11742
|
call.payload,
|
|
11676
11743
|
{
|
|
11677
11744
|
durableCallReceiptKey: compatibility.resultReceiptKey,
|
|
11745
|
+
completedResultCache: true,
|
|
11678
11746
|
playNodeScope: playNodeScopeForToolCallRequest(
|
|
11679
11747
|
detail.members[0]!,
|
|
11680
11748
|
call.tool.id,
|
|
11681
11749
|
),
|
|
11682
11750
|
executionAuthScopeDigest:
|
|
11683
11751
|
detail.members[0]?.executionAuthScopeDigest ?? null,
|
|
11684
|
-
providerIdempotencyReceiptKey:
|
|
11685
|
-
detail.providerOperationKey,
|
|
11686
|
-
providerIdempotencyKey: detail.providerOperationKey,
|
|
11687
11752
|
receiptLeaseExpiresAt: detail.receiptLeaseExpiresAt,
|
|
11688
11753
|
timeoutMs: detail.timeoutMs,
|
|
11689
11754
|
},
|
|
@@ -11751,8 +11816,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11751
11816
|
},
|
|
11752
11817
|
compatibility: {
|
|
11753
11818
|
resultReceiptKey: request.receiptKey!,
|
|
11754
|
-
providerOperationKey:
|
|
11755
|
-
request.providerIdempotencyKeyBase ?? request.receiptKey!,
|
|
11819
|
+
providerOperationKey: request.receiptKey!,
|
|
11756
11820
|
},
|
|
11757
11821
|
},
|
|
11758
11822
|
})),
|
|
@@ -11775,27 +11839,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11775
11839
|
'Native batch owned members changed provider operation during compilation.',
|
|
11776
11840
|
);
|
|
11777
11841
|
}
|
|
11778
|
-
const providerOperationKey =
|
|
11779
|
-
buildDurableToolPayloadProviderIdempotencyKey({
|
|
11780
|
-
orgId: this.#options.orgId,
|
|
11781
|
-
toolId: batch.batchOperation,
|
|
11782
|
-
executionAuthScopeDigest:
|
|
11783
|
-
owned[0]?.executionAuthScopeDigest ?? null,
|
|
11784
|
-
providerActionVersion: batchProviderActionVersion,
|
|
11785
|
-
payload: compiled.batchPayload,
|
|
11786
|
-
forceRefreshIdentities: owned
|
|
11787
|
-
.filter((request) => request.force === true)
|
|
11788
|
-
.map((request) =>
|
|
11789
|
-
this.providerIdempotencyKeyForToolCall({
|
|
11790
|
-
cacheKey:
|
|
11791
|
-
request.providerIdempotencyKeyBase ??
|
|
11792
|
-
request.receiptKey!,
|
|
11793
|
-
force: true,
|
|
11794
|
-
leaseId: request.receiptLeaseId,
|
|
11795
|
-
logicalCallId: request.logicalCallId,
|
|
11796
|
-
}),
|
|
11797
|
-
),
|
|
11798
|
-
});
|
|
11842
|
+
const providerOperationKey = aggregateReceiptKey;
|
|
11799
11843
|
dispatchDetails.set(aggregateReceiptKey, {
|
|
11800
11844
|
members: owned,
|
|
11801
11845
|
compiled,
|
|
@@ -11885,8 +11929,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11885
11929
|
toolResponse: publicToolResponseForBatchedItem(
|
|
11886
11930
|
aggregate,
|
|
11887
11931
|
item,
|
|
11888
|
-
|
|
11889
|
-
RAW_V2_TOOL_RESPONSE_CONTRACT,
|
|
11932
|
+
true,
|
|
11890
11933
|
),
|
|
11891
11934
|
requestInput: request.input,
|
|
11892
11935
|
execution: toolExecutionMetadataForOutcome({
|
|
@@ -11899,60 +11942,22 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11899
11942
|
}),
|
|
11900
11943
|
);
|
|
11901
11944
|
},
|
|
11902
|
-
heartbeat: {
|
|
11903
|
-
// Claim replies carry the real expiry. A fixed fallback interval
|
|
11904
|
-
// can be too slow for a short-lived gateway lease, so derive the
|
|
11905
|
-
// one cohort supervisor from the earliest owned member instead.
|
|
11906
|
-
intervalMs: ({ members }) =>
|
|
11907
|
-
Math.max(
|
|
11908
|
-
1,
|
|
11909
|
-
Math.floor(
|
|
11910
|
-
runtimeLeaseHeartbeatIntervalFromExpiry({
|
|
11911
|
-
leaseExpiresAt: members.reduce<string | null>(
|
|
11912
|
-
(earliest, member) => {
|
|
11913
|
-
const expiresAt = member.leaseExpiresAt ?? null;
|
|
11914
|
-
if (!expiresAt) return earliest;
|
|
11915
|
-
if (!earliest) return expiresAt;
|
|
11916
|
-
return Date.parse(expiresAt) < Date.parse(earliest)
|
|
11917
|
-
? expiresAt
|
|
11918
|
-
: earliest;
|
|
11919
|
-
},
|
|
11920
|
-
null,
|
|
11921
|
-
),
|
|
11922
|
-
fallbackTtlMs: PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
11923
|
-
}) / 2,
|
|
11924
|
-
),
|
|
11925
|
-
),
|
|
11926
|
-
onTransientFailure: (error) => {
|
|
11927
|
-
this.runtimeLog(
|
|
11928
|
-
`Native Tool Call Receipt Cohort heartbeat transport failed; retrying: ${
|
|
11929
|
-
error instanceof Error ? error.message : String(error)
|
|
11930
|
-
}`,
|
|
11931
|
-
);
|
|
11932
|
-
},
|
|
11933
|
-
},
|
|
11934
11945
|
});
|
|
11935
11946
|
return { batch, result };
|
|
11936
11947
|
},
|
|
11937
11948
|
onChunkComplete: async (entries) => {
|
|
11938
11949
|
for (const entry of entries) {
|
|
11939
11950
|
if (entry.error !== undefined) {
|
|
11940
|
-
//
|
|
11941
|
-
//
|
|
11942
|
-
// duplicate provider work. It must not convert a failed bulk
|
|
11943
|
-
// persistence boundary into a successful map result: callers need
|
|
11944
|
-
// the loud failure so this run can be retried from the durable
|
|
11945
|
-
// receipts instead of continuing after an ambiguous bulk write.
|
|
11951
|
+
// A failed batch is not a cache entry. Surface it to every row so
|
|
11952
|
+
// a later run can make a fresh provider attempt.
|
|
11946
11953
|
for (const request of entry.request.memberRequests) {
|
|
11947
11954
|
await reject(request, entry.error);
|
|
11948
11955
|
}
|
|
11949
11956
|
throw entry.error;
|
|
11950
11957
|
}
|
|
11951
11958
|
if (!entry.result) {
|
|
11952
|
-
//
|
|
11953
|
-
//
|
|
11954
|
-
// the same cohort still reach their rows and a stale failure never
|
|
11955
|
-
// causes this Context to write a second terminal transition.
|
|
11959
|
+
// Successful split results must be present in the completed cache
|
|
11960
|
+
// before they are delivered to rows.
|
|
11956
11961
|
const durable = await store.read({
|
|
11957
11962
|
resultReceiptKeys: entry.request.memberRequests.map(
|
|
11958
11963
|
(request) => request.receiptKey!,
|
|
@@ -11973,8 +11978,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
11973
11978
|
} else {
|
|
11974
11979
|
await reject(
|
|
11975
11980
|
request,
|
|
11976
|
-
|
|
11977
|
-
entry.error ??
|
|
11981
|
+
entry.error ??
|
|
11978
11982
|
new Error(
|
|
11979
11983
|
'Native Tool Call did not reach a terminal receipt.',
|
|
11980
11984
|
),
|
|
@@ -12198,6 +12202,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12198
12202
|
const cohortRequests = pendingRequests.filter(
|
|
12199
12203
|
(request) =>
|
|
12200
12204
|
request.cacheable !== false &&
|
|
12205
|
+
request.requiresExecutionFence !== true &&
|
|
12201
12206
|
Boolean(request.receiptKey?.trim()),
|
|
12202
12207
|
);
|
|
12203
12208
|
if (cohortRequests.length > 0) {
|
|
@@ -12230,6 +12235,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
12230
12235
|
const cohortRequests = pendingRequests.filter(
|
|
12231
12236
|
(request) =>
|
|
12232
12237
|
request.cacheable !== false &&
|
|
12238
|
+
request.requiresExecutionFence !== true &&
|
|
12233
12239
|
Boolean(request.receiptKey?.trim()),
|
|
12234
12240
|
);
|
|
12235
12241
|
if (cohortRequests.length > 0) {
|
|
@@ -13056,8 +13062,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13056
13062
|
: publicToolResponseForBatchedItem(
|
|
13057
13063
|
batchExecution.execution,
|
|
13058
13064
|
splitResults[index] ?? null,
|
|
13059
|
-
|
|
13060
|
-
RAW_V2_TOOL_RESPONSE_CONTRACT,
|
|
13065
|
+
true,
|
|
13061
13066
|
),
|
|
13062
13067
|
}),
|
|
13063
13068
|
),
|
|
@@ -13213,6 +13218,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13213
13218
|
...(request.receiptKey
|
|
13214
13219
|
? {
|
|
13215
13220
|
durableCallReceiptKey: request.receiptKey,
|
|
13221
|
+
completedResultCache: true,
|
|
13216
13222
|
executionAuthScopeDigest:
|
|
13217
13223
|
request.executionAuthScopeDigest,
|
|
13218
13224
|
providerIdempotencyReceiptKey:
|
|
@@ -13675,6 +13681,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13675
13681
|
.catch(() => null);
|
|
13676
13682
|
const retrySafeTransientHttp =
|
|
13677
13683
|
retryPolicy?.retrySafeTransientHttp === true;
|
|
13684
|
+
const requiresExecutionFence =
|
|
13685
|
+
retryPolicy?.requiresExecutionFence === true;
|
|
13686
|
+
const fixtureReplaySafe = this.#options.integrationMode === 'fixture';
|
|
13687
|
+
const readOnlyQuery = isQueryResultDatasetReadRequest(
|
|
13688
|
+
toolId,
|
|
13689
|
+
toolInputSnapshot,
|
|
13690
|
+
);
|
|
13691
|
+
// A completed result cache eliminates repeated successful work; it
|
|
13692
|
+
// does not prove an ambiguous provider write can be replayed. Only
|
|
13693
|
+
// a read-only query or an action which explicitly declares transient
|
|
13694
|
+
// retries safe may issue another physical request after dispatch.
|
|
13695
|
+
const ambiguousTransportReplayAllowed =
|
|
13696
|
+
readOnlyQuery || retrySafeTransientHttp;
|
|
13678
13697
|
const invocationOrgId = this.#options.orgId?.trim() ?? '';
|
|
13679
13698
|
const durableCallReceiptKey =
|
|
13680
13699
|
options?.durableCallReceiptKey?.trim() ||
|
|
@@ -13689,26 +13708,25 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13689
13708
|
const executionAuthScopeDigest = options?.executionAuthScopeDigest
|
|
13690
13709
|
? options.executionAuthScopeDigest.trim() || null
|
|
13691
13710
|
: ((await this.resolveToolAuthScopeDigest(toolId))?.trim() ?? null);
|
|
13692
|
-
const
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
};
|
|
13702
|
-
const providerIdempotencyKey =
|
|
13703
|
-
options?.providerIdempotencyKey?.trim() || durableCallReceiptKey;
|
|
13711
|
+
const executionRequestNonce = crypto.randomUUID();
|
|
13712
|
+
// This identifies one logical tool call to billing. Physical retry
|
|
13713
|
+
// count remains in request metadata, so a transport retry cannot
|
|
13714
|
+
// reserve or charge the same call twice.
|
|
13715
|
+
const deeplineRequestId = `ctx-tool-${stableDigest(executionRequestNonce).slice(0, 32)}`;
|
|
13716
|
+
// The provider operation has a semantic identity independent of
|
|
13717
|
+
// the response receipt. Its key is supplied by the caller that owns
|
|
13718
|
+
// that compatibility contract; never substitute request/billing
|
|
13719
|
+
// attribution for it.
|
|
13704
13720
|
const providerIdempotencyReceiptKey =
|
|
13705
|
-
options?.providerIdempotencyReceiptKey?.trim() ||
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13721
|
+
options?.providerIdempotencyReceiptKey?.trim() || null;
|
|
13722
|
+
const providerExecutionFenceKey = requiresExecutionFence
|
|
13723
|
+
? options?.providerIdempotencyKey?.trim() || null
|
|
13724
|
+
: null;
|
|
13725
|
+
if (requiresExecutionFence && !providerExecutionFenceKey) {
|
|
13726
|
+
throw new Error(
|
|
13727
|
+
`Tool ${toolId} requires a provider execution fence but no provider operation identity was supplied.`,
|
|
13728
|
+
);
|
|
13729
|
+
}
|
|
13712
13730
|
// Every runtime provider call carries at least a tool-scoped
|
|
13713
13731
|
// attribution. Callers that know the execution location (row-scoped
|
|
13714
13732
|
// map cells, batched owners) supply the fuller scope.
|
|
@@ -13723,16 +13741,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13723
13741
|
...(requestsDurableInvocationFence
|
|
13724
13742
|
? { invocation_fence_version: 1 }
|
|
13725
13743
|
: {}),
|
|
13726
|
-
// Canonical provider-operation receipts validate this semantic
|
|
13727
|
-
// plan before replay. It is intentionally removed from the
|
|
13728
|
-
// legacy byte-receipt fingerprint so mixed runner artifacts
|
|
13729
|
-
// retain their existing provider-once behavior.
|
|
13730
|
-
...(requestsDurableInvocationFence
|
|
13731
|
-
? { canonical_operation: canonicalOperation }
|
|
13732
|
-
: {}),
|
|
13733
|
-
// The worker-owned receipt coordinates this physical durable
|
|
13734
|
-
// call. Provider idempotency is carried separately below so
|
|
13735
|
-
// exact-payload reuse never replaces batch receipt ownership.
|
|
13736
13744
|
...(durableCallReceiptKey
|
|
13737
13745
|
? {
|
|
13738
13746
|
durable_call_receipt_key: durableCallReceiptKey,
|
|
@@ -13744,10 +13752,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13744
13752
|
: {}),
|
|
13745
13753
|
}
|
|
13746
13754
|
: {}),
|
|
13747
|
-
...(
|
|
13755
|
+
...(providerExecutionFenceKey
|
|
13748
13756
|
? {
|
|
13749
|
-
|
|
13750
|
-
|
|
13757
|
+
provider_idempotency_key: providerExecutionFenceKey,
|
|
13758
|
+
...(providerIdempotencyReceiptKey
|
|
13759
|
+
? {
|
|
13760
|
+
provider_idempotency_receipt_key:
|
|
13761
|
+
providerIdempotencyReceiptKey,
|
|
13762
|
+
}
|
|
13763
|
+
: {}),
|
|
13751
13764
|
}
|
|
13752
13765
|
: {}),
|
|
13753
13766
|
...(options?.customerDbDataset
|
|
@@ -13770,9 +13783,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13770
13783
|
: {}),
|
|
13771
13784
|
}
|
|
13772
13785
|
: {}),
|
|
13773
|
-
...(providerIdempotencyKey
|
|
13774
|
-
? { provider_idempotency_key: providerIdempotencyKey }
|
|
13775
|
-
: {}),
|
|
13776
13786
|
...(playNodeScope
|
|
13777
13787
|
? { play_node_scope: playNodeScopeToWire(playNodeScope) }
|
|
13778
13788
|
: {}),
|
|
@@ -13789,6 +13799,8 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13789
13799
|
elapsedMs: number;
|
|
13790
13800
|
requestId: string | null;
|
|
13791
13801
|
aborted: boolean;
|
|
13802
|
+
/** The ingress rejected the request before any provider dispatch. */
|
|
13803
|
+
knownNotDispatched?: boolean;
|
|
13792
13804
|
}): Promise<void> => {
|
|
13793
13805
|
transportAttempt += 1;
|
|
13794
13806
|
const diagnostic = describeTransportError(input.error);
|
|
@@ -13804,7 +13816,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
13804
13816
|
error: diagnostic,
|
|
13805
13817
|
})}`,
|
|
13806
13818
|
);
|
|
13807
|
-
if (
|
|
13819
|
+
if (
|
|
13820
|
+
transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS &&
|
|
13821
|
+
(input.knownNotDispatched === true ||
|
|
13822
|
+
ambiguousTransportReplayAllowed ||
|
|
13823
|
+
fixtureReplaySafe)
|
|
13824
|
+
) {
|
|
13825
|
+
invocationAttempt += 1;
|
|
13808
13826
|
await this.governor.chargeBudget('retry');
|
|
13809
13827
|
const retryAfterMs =
|
|
13810
13828
|
TOOL_RETRY_AFTER_FALLBACK_MS * transportAttempt;
|
|
@@ -14033,17 +14051,17 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14033
14051
|
'Content-Type': 'application/json',
|
|
14034
14052
|
Authorization: `Bearer ${this.#options.executorToken}`,
|
|
14035
14053
|
[EXECUTE_RESPONSE_CONTRACT_HEADER]:
|
|
14036
|
-
|
|
14054
|
+
RAW_V2_TOOL_RESPONSE_CONTRACT,
|
|
14037
14055
|
[EXECUTE_RESPONSE_INTENT_HEADER]: 'dataset',
|
|
14038
14056
|
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
14039
14057
|
[TOOL_EXECUTION_ERROR_SCHEMA_HEADER]: String(
|
|
14040
14058
|
toolErrorSchemaVersion,
|
|
14041
14059
|
),
|
|
14042
14060
|
'x-deepline-request-id': deeplineRequestId,
|
|
14043
|
-
...(
|
|
14061
|
+
...(providerExecutionFenceKey
|
|
14044
14062
|
? {
|
|
14045
14063
|
'x-deepline-idempotency-key':
|
|
14046
|
-
|
|
14064
|
+
providerExecutionFenceKey,
|
|
14047
14065
|
}
|
|
14048
14066
|
: {}),
|
|
14049
14067
|
...protectionHeaders,
|
|
@@ -14145,40 +14163,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14145
14163
|
(response === null ||
|
|
14146
14164
|
error instanceof ToolExecuteResponseBodyTransportError ||
|
|
14147
14165
|
error instanceof ToolExecuteInvalidJsonError);
|
|
14148
|
-
const hasDurableInvocationIdentity = Boolean(
|
|
14149
|
-
durableCallReceiptKey &&
|
|
14150
|
-
providerIdempotencyKey &&
|
|
14151
|
-
executionAuthScopeDigest,
|
|
14152
|
-
);
|
|
14153
14166
|
// A post-header body deadline aborts the fetch controller solely
|
|
14154
14167
|
// to stop consuming that response. It must not also cancel the
|
|
14155
14168
|
// independent gateway health check that establishes whether the
|
|
14156
14169
|
// committed response can be replayed safely. Preserve genuine
|
|
14157
14170
|
// caller/runtime cancellation by dropping the signal only when
|
|
14158
14171
|
// this exact body deadline caused the abort.
|
|
14159
|
-
const durableFenceVerificationSignal =
|
|
14160
|
-
abortController?.signal.aborted === true &&
|
|
14161
|
-
abortController.signal.reason instanceof
|
|
14162
|
-
ToolExecuteResponseBodyTimeoutError
|
|
14163
|
-
? undefined
|
|
14164
|
-
: abortController?.signal;
|
|
14165
|
-
const durableInvocationFenceVerified =
|
|
14166
|
-
hasDurableInvocationIdentity &&
|
|
14167
|
-
requestsDurableInvocationFence &&
|
|
14168
|
-
(this.#options.durableInvocationFence === true ||
|
|
14169
|
-
(await this.#options
|
|
14170
|
-
.verifyDurableInvocationFence?.(
|
|
14171
|
-
durableFenceVerificationSignal,
|
|
14172
|
-
)
|
|
14173
|
-
.catch(() => false)) === true);
|
|
14174
|
-
// Fixture execution has no provider-side effect. Replaying the
|
|
14175
|
-
// same stable request after our own transport fails is always
|
|
14176
|
-
// safe, even when this runner lacks a live-provider fence.
|
|
14177
|
-
const fixtureReplaySafe =
|
|
14178
|
-
this.#options.integrationMode === 'fixture';
|
|
14179
14172
|
const transportReplaySafe =
|
|
14180
14173
|
!ambiguousDispatchedFailure ||
|
|
14181
|
-
|
|
14174
|
+
ambiguousTransportReplayAllowed ||
|
|
14182
14175
|
fixtureReplaySafe;
|
|
14183
14176
|
if (!transportReplaySafe) {
|
|
14184
14177
|
const diagnostic = describeTransportError(transportError);
|
|
@@ -14271,6 +14264,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14271
14264
|
elapsedMs: providerCallElapsedMs ?? 0,
|
|
14272
14265
|
requestId: deeplineRequestId,
|
|
14273
14266
|
aborted: false,
|
|
14267
|
+
knownNotDispatched: true,
|
|
14274
14268
|
});
|
|
14275
14269
|
continue;
|
|
14276
14270
|
}
|
|
@@ -14308,13 +14302,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14308
14302
|
failure.backpressureDelayMs,
|
|
14309
14303
|
);
|
|
14310
14304
|
}
|
|
14311
|
-
if (
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14305
|
+
if (
|
|
14306
|
+
failure.shouldRetry &&
|
|
14307
|
+
(ambiguousTransportReplayAllowed ||
|
|
14308
|
+
retrySafeTransientHttp ||
|
|
14309
|
+
fixtureReplaySafe)
|
|
14310
|
+
) {
|
|
14311
|
+
invocationAttempt += 1;
|
|
14318
14312
|
if (failure.chargeRetryBudget) {
|
|
14319
14313
|
await this.governor.chargeBudget('retry');
|
|
14320
14314
|
}
|