deepline 0.2.30 → 0.2.31
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/client.ts +6 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +221 -76
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +421 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +18 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +12 -2
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +12 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-modal-fallback.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +100 -3
- package/dist/cli/index.js +253 -7
- package/dist/cli/index.mjs +253 -7
- package/dist/index.d.mts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +3 -1
- package/dist/index.mjs +3 -1
- package/dist/install-integrity.json +1 -0
- package/package.json +1 -1
|
@@ -2155,6 +2155,9 @@ export class DeeplineClient {
|
|
|
2155
2155
|
// defaults to absurd; callers normally omit this field.
|
|
2156
2156
|
...(request.profile ? { profile: request.profile } : {}),
|
|
2157
2157
|
...(integrationMode ? { integrationMode } : {}),
|
|
2158
|
+
...(request.fixtureBehavior
|
|
2159
|
+
? { fixtureBehavior: request.fixtureBehavior }
|
|
2160
|
+
: {}),
|
|
2158
2161
|
...(runtime ? { runtime } : {}),
|
|
2159
2162
|
...(testPolicyOverrides ? { testPolicyOverrides } : {}),
|
|
2160
2163
|
},
|
|
@@ -2225,6 +2228,9 @@ export class DeeplineClient {
|
|
|
2225
2228
|
: {}),
|
|
2226
2229
|
...(request.profile ? { profile: request.profile } : {}),
|
|
2227
2230
|
...(integrationMode ? { integrationMode } : {}),
|
|
2231
|
+
...(request.fixtureBehavior
|
|
2232
|
+
? { fixtureBehavior: request.fixtureBehavior }
|
|
2233
|
+
: {}),
|
|
2228
2234
|
...(runtime ? { runtime } : {}),
|
|
2229
2235
|
...(testPolicyOverrides ? { testPolicyOverrides } : {}),
|
|
2230
2236
|
};
|
|
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
|
|
|
160
160
|
// 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
|
|
161
161
|
// exposed storage-dependent synchronous access. This deliberate minor
|
|
162
162
|
// release keeps lazy paging semantics independent of row residency.
|
|
163
|
-
version: '0.2.
|
|
163
|
+
version: '0.2.31',
|
|
164
164
|
contracts: {
|
|
165
165
|
api: {
|
|
166
166
|
name: 'sdk-http-api',
|
|
@@ -1394,6 +1394,8 @@ export interface StartPlayRunRequest {
|
|
|
1394
1394
|
profile?: string;
|
|
1395
1395
|
/** Optional per-run provider execution mode for eval/smoke runs. */
|
|
1396
1396
|
integrationMode?: 'live' | 'eval_stub' | 'fixture';
|
|
1397
|
+
/** Fixture-only provider response timing and outcome simulation. */
|
|
1398
|
+
fixtureBehavior?: import('../../shared_libs/play-runtime/fixture-behavior').FixtureBehavior;
|
|
1397
1399
|
/** Internal runtime estate selection. The app host remains unchanged. */
|
|
1398
1400
|
runtime?: PlayRuntimeSelection;
|
|
1399
1401
|
/** Internal/dev-only runtime policy overrides for black-box durability tests. */
|
|
@@ -992,6 +992,9 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
992
992
|
: AbortSignal.timeout(requestTimeoutMs),
|
|
993
993
|
});
|
|
994
994
|
} catch (error) {
|
|
995
|
+
if (context.signal?.aborted) {
|
|
996
|
+
throw context.signal.reason ?? error;
|
|
997
|
+
}
|
|
995
998
|
if (
|
|
996
999
|
attempt < maxAttempts &&
|
|
997
1000
|
isRetryableAppRuntimeFetchError({ action: body.action, error })
|
|
@@ -35,6 +35,12 @@ import {
|
|
|
35
35
|
runtimeLeaseHeartbeatIntervalFromExpiry,
|
|
36
36
|
} from './lease-policy';
|
|
37
37
|
import { createRuntimeReceiptHeartbeatSupervisor } from './receipt-heartbeat-supervisor';
|
|
38
|
+
import {
|
|
39
|
+
shouldRouteFixtureProvider,
|
|
40
|
+
shouldRouteFixtureToolId,
|
|
41
|
+
validateFixtureBehavior,
|
|
42
|
+
waitForFixtureResponseDelay,
|
|
43
|
+
} from './fixture-behavior';
|
|
38
44
|
import { dispatchBoundedSettled } from './bounded-dispatch';
|
|
39
45
|
import type { PlayQueueHint } from './governor/rate-state-backend';
|
|
40
46
|
import type { MapRowOutcome } from './durability-store';
|
|
@@ -1622,6 +1628,22 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
1622
1628
|
};
|
|
1623
1629
|
|
|
1624
1630
|
constructor(options: ContextOptions) {
|
|
1631
|
+
const fixtureBehavior = validateFixtureBehavior(options.fixtureBehavior);
|
|
1632
|
+
if (fixtureBehavior.ok === false) {
|
|
1633
|
+
throw new Error(fixtureBehavior.error);
|
|
1634
|
+
}
|
|
1635
|
+
if (
|
|
1636
|
+
fixtureBehavior.behavior !== null &&
|
|
1637
|
+
options.integrationMode !== 'fixture'
|
|
1638
|
+
) {
|
|
1639
|
+
throw new Error(
|
|
1640
|
+
'fixtureBehavior is only valid when integrationMode is fixture.',
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
options = {
|
|
1644
|
+
...options,
|
|
1645
|
+
fixtureBehavior: fixtureBehavior.behavior,
|
|
1646
|
+
};
|
|
1625
1647
|
this.#options = options;
|
|
1626
1648
|
this.checkpoint = options.checkpoint ?? emptyCheckpoint();
|
|
1627
1649
|
this.durableMappedToolResultsBackedByReceipts = Boolean(
|
|
@@ -6720,6 +6742,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
6720
6742
|
}
|
|
6721
6743
|
}
|
|
6722
6744
|
|
|
6745
|
+
private fixtureProviderPacingDisabled(): boolean {
|
|
6746
|
+
return (
|
|
6747
|
+
this.#options.integrationMode === 'fixture' &&
|
|
6748
|
+
this.#options.enforceFixtureProviderPacing !== true
|
|
6749
|
+
);
|
|
6750
|
+
}
|
|
6751
|
+
|
|
6723
6752
|
private toolDispatchLane(request: ToolCallRequest): {
|
|
6724
6753
|
key: string;
|
|
6725
6754
|
readyCount: number;
|
|
@@ -8983,15 +9012,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8983
9012
|
getPayload: (request: ToolCallRequest) => request.input,
|
|
8984
9013
|
});
|
|
8985
9014
|
|
|
9015
|
+
const batchParallelismCeiling =
|
|
9016
|
+
this.governor.policy.pacing.workerToolBatchDefaultParallelism;
|
|
8986
9017
|
const batchSize =
|
|
8987
|
-
compiledBatches.length > 0
|
|
9018
|
+
compiledBatches.length > 0 &&
|
|
9019
|
+
!this.fixtureProviderPacingDisabled()
|
|
8988
9020
|
? await this.resourceGovernor.suggestedToolParallelism(
|
|
8989
9021
|
compiledBatches[0]!.batchOperation,
|
|
8990
|
-
|
|
8991
|
-
.workerToolBatchDefaultParallelism,
|
|
9022
|
+
batchParallelismCeiling,
|
|
8992
9023
|
)
|
|
8993
|
-
:
|
|
8994
|
-
.workerToolBatchDefaultParallelism;
|
|
9024
|
+
: batchParallelismCeiling;
|
|
8995
9025
|
await executeChunkedRequests({
|
|
8996
9026
|
requests: compiledBatches,
|
|
8997
9027
|
batchSize,
|
|
@@ -9182,11 +9212,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9182
9212
|
// each in-flight call, so this only trims the launch burst.
|
|
9183
9213
|
const toolCallConcurrencyCeiling =
|
|
9184
9214
|
this.governor.policy.concurrency.toolCalls;
|
|
9185
|
-
const shapedToolParallelism =
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9215
|
+
const shapedToolParallelism = this.fixtureProviderPacingDisabled()
|
|
9216
|
+
? toolCallConcurrencyCeiling
|
|
9217
|
+
: await this.resourceGovernor.suggestedToolParallelism(
|
|
9218
|
+
toolId,
|
|
9219
|
+
toolCallConcurrencyCeiling,
|
|
9220
|
+
);
|
|
9190
9221
|
const dispatchWidth = Math.min(
|
|
9191
9222
|
toolCallConcurrencyCeiling,
|
|
9192
9223
|
Math.max(1, shapedToolParallelism),
|
|
@@ -9563,6 +9594,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9563
9594
|
});
|
|
9564
9595
|
let transportAttempt = 0;
|
|
9565
9596
|
let invocationAttempt = 0;
|
|
9597
|
+
let physicalAttempt = 0;
|
|
9566
9598
|
const retryToolTransportFailure = async (input: {
|
|
9567
9599
|
error: unknown;
|
|
9568
9600
|
elapsedMs: number;
|
|
@@ -9626,11 +9658,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9626
9658
|
};
|
|
9627
9659
|
|
|
9628
9660
|
while (true) {
|
|
9661
|
+
physicalAttempt += 1;
|
|
9629
9662
|
let response: Response | null = null;
|
|
9630
9663
|
let responseData: Record<string, unknown> | null = null;
|
|
9631
9664
|
let responseErrorText: string | null = null;
|
|
9632
9665
|
let providerCallStartedAt: number | null = null;
|
|
9633
9666
|
let providerCallElapsedMs: number | null = null;
|
|
9667
|
+
let integrationFetchStartedAt: number | null = null;
|
|
9634
9668
|
let fetchDispatched = false;
|
|
9635
9669
|
// Receipt ownership is a liveness contract, not a one-time check.
|
|
9636
9670
|
// Keep it alive for the whole provider HTTP request. The cadence
|
|
@@ -9641,15 +9675,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9641
9675
|
timeoutMs || hasReceiptHeartbeat ? new AbortController() : null;
|
|
9642
9676
|
const receiptHeartbeat = options?.heartbeatReceipt;
|
|
9643
9677
|
let heartbeatFailure: unknown = null;
|
|
9644
|
-
|
|
9645
|
-
? setTimeout(() => {
|
|
9646
|
-
abortController?.abort(
|
|
9647
|
-
new Error(
|
|
9648
|
-
`Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
|
|
9649
|
-
),
|
|
9650
|
-
);
|
|
9651
|
-
}, timeoutMs)
|
|
9652
|
-
: null;
|
|
9678
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
|
9653
9679
|
try {
|
|
9654
9680
|
const ownershipStartedAt = Date.now();
|
|
9655
9681
|
await options?.beforeProviderCall?.();
|
|
@@ -9705,7 +9731,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9705
9731
|
fixtureExecution &&
|
|
9706
9732
|
this.#options.enforceFixtureProviderPacing === true;
|
|
9707
9733
|
const fixtureOnlyExecution =
|
|
9708
|
-
|
|
9734
|
+
this.fixtureProviderPacingDisabled();
|
|
9709
9735
|
if (
|
|
9710
9736
|
fixtureOnlyExecution &&
|
|
9711
9737
|
!this.fixtureProviderPacingBypassLogged
|
|
@@ -9730,69 +9756,181 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9730
9756
|
toolId,
|
|
9731
9757
|
signal: abortController?.signal,
|
|
9732
9758
|
});
|
|
9733
|
-
const integrationFetchStartedAt = Date.now();
|
|
9734
|
-
providerCallStartedAt = integrationFetchStartedAt;
|
|
9735
9759
|
try {
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9760
|
+
// Provider admission is our queue, not provider execution.
|
|
9761
|
+
// Start the tool deadline only after admission so a busy
|
|
9762
|
+
// runtime cannot consume the remote-call budget while this
|
|
9763
|
+
// attempt is still waiting for permission to leave.
|
|
9764
|
+
if (
|
|
9765
|
+
timeoutMs &&
|
|
9766
|
+
abortController &&
|
|
9767
|
+
!abortController.signal.aborted
|
|
9768
|
+
) {
|
|
9769
|
+
timeoutHandle = setTimeout(() => {
|
|
9770
|
+
abortController.abort(
|
|
9771
|
+
new Error(
|
|
9772
|
+
`Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
|
|
9773
|
+
),
|
|
9774
|
+
);
|
|
9775
|
+
}, timeoutMs);
|
|
9776
|
+
}
|
|
9777
|
+
const fixtureBehavior = this.#options.fixtureBehavior;
|
|
9778
|
+
if (
|
|
9779
|
+
fixtureExecution &&
|
|
9780
|
+
fixtureBehavior &&
|
|
9781
|
+
shouldRouteFixtureToolId(toolId)
|
|
9782
|
+
) {
|
|
9783
|
+
const canonicalProvider =
|
|
9784
|
+
(await this.#options.getToolProvider?.(toolId))?.trim() ||
|
|
9785
|
+
provider;
|
|
9786
|
+
if (shouldRouteFixtureProvider(canonicalProvider)) {
|
|
9787
|
+
if (!durableCallReceiptKey) {
|
|
9788
|
+
throw new Error(
|
|
9789
|
+
'Configured fixture response timing requires a durable call receipt key.',
|
|
9790
|
+
);
|
|
9791
|
+
}
|
|
9792
|
+
const canonicalOperation =
|
|
9793
|
+
(
|
|
9794
|
+
await this.#options.getToolOperation?.(toolId)
|
|
9795
|
+
)?.trim() || toolId;
|
|
9796
|
+
const stableRequestKey = `${canonicalProvider}:${canonicalOperation}:${durableCallReceiptKey}`;
|
|
9797
|
+
const behaviorDigest = stableDigest(
|
|
9798
|
+
JSON.stringify(fixtureBehavior),
|
|
9799
|
+
);
|
|
9800
|
+
const requestKeyDigest = stableDigest(stableRequestKey);
|
|
9801
|
+
let selected: {
|
|
9802
|
+
delayMs: number;
|
|
9803
|
+
sampleIndex: number;
|
|
9804
|
+
} | null = null;
|
|
9805
|
+
const delayStartedAt = Date.now();
|
|
9806
|
+
providerCallStartedAt = delayStartedAt;
|
|
9807
|
+
try {
|
|
9808
|
+
selected = await waitForFixtureResponseDelay({
|
|
9809
|
+
behavior: fixtureBehavior,
|
|
9810
|
+
stableRequestKey,
|
|
9811
|
+
signal: abortController?.signal,
|
|
9812
|
+
onSelected: (value) => {
|
|
9813
|
+
selected = value;
|
|
9814
|
+
this.log(
|
|
9815
|
+
`[fixture.response_delay] ${JSON.stringify({
|
|
9816
|
+
outcome: 'scheduled',
|
|
9817
|
+
provider: canonicalProvider,
|
|
9818
|
+
operation: canonicalOperation,
|
|
9819
|
+
behavior_digest: `sha256_${behaviorDigest}`,
|
|
9820
|
+
request_key_digest: `sha256_${requestKeyDigest}`,
|
|
9821
|
+
delay_ms: value.delayMs,
|
|
9822
|
+
sample_index: value.sampleIndex,
|
|
9823
|
+
physical_attempt: physicalAttempt,
|
|
9824
|
+
})}`,
|
|
9825
|
+
);
|
|
9826
|
+
},
|
|
9827
|
+
});
|
|
9828
|
+
} catch (error) {
|
|
9829
|
+
this.log(
|
|
9830
|
+
`[fixture.response_delay] ${JSON.stringify({
|
|
9831
|
+
outcome: 'aborted',
|
|
9832
|
+
provider: canonicalProvider,
|
|
9833
|
+
operation: canonicalOperation,
|
|
9834
|
+
behavior_digest: `sha256_${behaviorDigest}`,
|
|
9835
|
+
request_key_digest: `sha256_${requestKeyDigest}`,
|
|
9836
|
+
delay_ms: selected?.delayMs ?? null,
|
|
9837
|
+
sample_index: selected?.sampleIndex ?? null,
|
|
9838
|
+
physical_attempt: physicalAttempt,
|
|
9839
|
+
elapsed_ms: Date.now() - delayStartedAt,
|
|
9840
|
+
})}`,
|
|
9841
|
+
);
|
|
9842
|
+
throw error;
|
|
9778
9843
|
}
|
|
9779
|
-
|
|
9844
|
+
this.log(
|
|
9845
|
+
`[fixture.response_delay] ${JSON.stringify({
|
|
9846
|
+
outcome: 'completed',
|
|
9847
|
+
provider: canonicalProvider,
|
|
9848
|
+
operation: canonicalOperation,
|
|
9849
|
+
behavior_digest: `sha256_${behaviorDigest}`,
|
|
9850
|
+
request_key_digest: `sha256_${requestKeyDigest}`,
|
|
9851
|
+
delay_ms: selected.delayMs,
|
|
9852
|
+
sample_index: selected.sampleIndex,
|
|
9853
|
+
physical_attempt: physicalAttempt,
|
|
9854
|
+
elapsed_ms: Date.now() - delayStartedAt,
|
|
9855
|
+
})}`,
|
|
9856
|
+
);
|
|
9780
9857
|
}
|
|
9781
|
-
}
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9858
|
+
}
|
|
9859
|
+
const integrationRequestLease =
|
|
9860
|
+
await this.governor.acquireIntegrationRequestSlot({
|
|
9861
|
+
signal: abortController?.signal,
|
|
9862
|
+
});
|
|
9863
|
+
try {
|
|
9864
|
+
integrationFetchStartedAt = Date.now();
|
|
9865
|
+
providerCallStartedAt ??= integrationFetchStartedAt;
|
|
9866
|
+
fetchDispatched = true;
|
|
9867
|
+
response = await fetch(url, {
|
|
9868
|
+
method: 'POST',
|
|
9869
|
+
signal: abortController?.signal,
|
|
9870
|
+
headers: {
|
|
9871
|
+
'Content-Type': 'application/json',
|
|
9872
|
+
Authorization: `Bearer ${this.#options.executorToken}`,
|
|
9873
|
+
[EXECUTE_RESPONSE_CONTRACT_HEADER]:
|
|
9874
|
+
V2_EXECUTE_RESPONSE_CONTRACT,
|
|
9875
|
+
[EXECUTE_RESPONSE_INTENT_HEADER]: 'dataset',
|
|
9876
|
+
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
9877
|
+
[TOOL_EXECUTION_ERROR_SCHEMA_HEADER]: String(
|
|
9878
|
+
toolErrorSchemaVersion,
|
|
9879
|
+
),
|
|
9880
|
+
'x-deepline-request-id': deeplineRequestId,
|
|
9881
|
+
...(providerIdempotencyKey
|
|
9882
|
+
? {
|
|
9883
|
+
'x-deepline-idempotency-key':
|
|
9884
|
+
providerIdempotencyKey,
|
|
9885
|
+
}
|
|
9886
|
+
: {}),
|
|
9887
|
+
...protectionHeaders,
|
|
9888
|
+
...(this.#options.runtimeTestFaultHeader
|
|
9889
|
+
? {
|
|
9890
|
+
'x-deepline-test-fault':
|
|
9891
|
+
this.#options.runtimeTestFaultHeader,
|
|
9892
|
+
}
|
|
9893
|
+
: {}),
|
|
9894
|
+
},
|
|
9895
|
+
body: serializedRequestBody(invocationAttempt),
|
|
9896
|
+
});
|
|
9897
|
+
if (response.ok) {
|
|
9898
|
+
try {
|
|
9899
|
+
responseData = await readToolExecuteResponseBody({
|
|
9900
|
+
toolId,
|
|
9901
|
+
abortController,
|
|
9902
|
+
read: () =>
|
|
9903
|
+
response!.json() as Promise<
|
|
9904
|
+
Record<string, unknown>
|
|
9905
|
+
>,
|
|
9906
|
+
});
|
|
9907
|
+
} catch (error) {
|
|
9908
|
+
if (error instanceof SyntaxError) {
|
|
9909
|
+
throw new ToolExecuteInvalidJsonError(error);
|
|
9910
|
+
}
|
|
9911
|
+
throw new ToolExecuteResponseBodyTransportError(error);
|
|
9912
|
+
}
|
|
9913
|
+
} else {
|
|
9914
|
+
try {
|
|
9915
|
+
responseErrorText = await readToolExecuteResponseBody({
|
|
9916
|
+
toolId,
|
|
9917
|
+
abortController,
|
|
9918
|
+
read: () => response!.text(),
|
|
9919
|
+
});
|
|
9920
|
+
} catch (error) {
|
|
9921
|
+
throw new ToolExecuteResponseBodyTransportError(error);
|
|
9922
|
+
}
|
|
9790
9923
|
}
|
|
9924
|
+
} finally {
|
|
9925
|
+
integrationRequestLease.release();
|
|
9791
9926
|
}
|
|
9792
9927
|
} finally {
|
|
9793
9928
|
providerPermit.release();
|
|
9794
9929
|
}
|
|
9795
|
-
if (
|
|
9930
|
+
if (
|
|
9931
|
+
runtimeReceiptReadTraceEnabled &&
|
|
9932
|
+
integrationFetchStartedAt !== null
|
|
9933
|
+
) {
|
|
9796
9934
|
this.log(
|
|
9797
9935
|
`[perf] tool call id=${toolId} phase=integration_fetch_body elapsed_ms=${Date.now() - integrationFetchStartedAt} status=${response.status}`,
|
|
9798
9936
|
);
|
|
@@ -9877,8 +10015,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9877
10015
|
durableFenceVerificationSignal,
|
|
9878
10016
|
)
|
|
9879
10017
|
.catch(() => false)) === true);
|
|
10018
|
+
// Fixture execution has no provider-side effect. Replaying the
|
|
10019
|
+
// same stable request after our own transport fails is always
|
|
10020
|
+
// safe, even when this runner lacks a live-provider fence.
|
|
10021
|
+
const fixtureReplaySafe =
|
|
10022
|
+
this.#options.integrationMode === 'fixture';
|
|
9880
10023
|
const transportReplaySafe =
|
|
9881
|
-
!ambiguousDispatchedFailure ||
|
|
10024
|
+
!ambiguousDispatchedFailure ||
|
|
10025
|
+
durableInvocationFenceVerified ||
|
|
10026
|
+
fixtureReplaySafe;
|
|
9882
10027
|
if (!transportReplaySafe) {
|
|
9883
10028
|
const diagnostic = describeTransportError(transportError);
|
|
9884
10029
|
this.log(
|
|
@@ -42,6 +42,7 @@ import type {
|
|
|
42
42
|
ToolExecutionErrorSchemaVersion,
|
|
43
43
|
ToolExecutionFailureV1,
|
|
44
44
|
} from '../tool-execution-error';
|
|
45
|
+
import type { FixtureBehavior } from './fixture-behavior';
|
|
45
46
|
|
|
46
47
|
export interface RowState {
|
|
47
48
|
results: Map<string, unknown>;
|
|
@@ -512,6 +513,8 @@ export interface ContextOptions {
|
|
|
512
513
|
vercelProtectionBypassToken?: string | null;
|
|
513
514
|
/** Optional per-run integration execution mode for provider calls. */
|
|
514
515
|
integrationMode?: 'live' | 'eval_stub' | 'fixture';
|
|
516
|
+
/** Internal fixture-only simulation of provider response residence. */
|
|
517
|
+
fixtureBehavior?: FixtureBehavior | null;
|
|
515
518
|
/** Preview/dev test seam that applies provider pacing to fixture responses. */
|
|
516
519
|
enforceFixtureProviderPacing?: boolean;
|
|
517
520
|
orgId?: string;
|
|
@@ -641,6 +644,7 @@ export interface ContextOptions {
|
|
|
641
644
|
resolvePlay?: (playRef: string) => Promise<ResolvedPlayExecution | null>;
|
|
642
645
|
getToolQueueHints?: (toolId: string) => Promise<readonly PlayQueueHint[]>;
|
|
643
646
|
getToolProvider?: (toolId: string) => Promise<string | null>;
|
|
647
|
+
getToolOperation?: (toolId: string) => Promise<string | null>;
|
|
644
648
|
getToolRetryPolicy?: (
|
|
645
649
|
toolId: string,
|
|
646
650
|
input: Record<string, unknown>,
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateFixtureBehavior,
|
|
3
|
+
type FixtureBehavior,
|
|
4
|
+
} from './fixture-behavior';
|
|
5
|
+
|
|
1
6
|
export const RUNTIME_EXECUTION_CAPABILITIES = [
|
|
2
7
|
'artifact.read',
|
|
3
8
|
'file.read',
|
|
@@ -37,6 +42,7 @@ export type RuntimeAuthorityDescriptor = {
|
|
|
37
42
|
allowedSecrets: string[];
|
|
38
43
|
maxCreditsPerRun?: number | null;
|
|
39
44
|
integrationMode?: 'live' | 'eval_stub' | 'fixture' | null;
|
|
45
|
+
fixtureBehavior?: FixtureBehavior | null;
|
|
40
46
|
synthetic?: boolean | null;
|
|
41
47
|
actorUserId?: string | null;
|
|
42
48
|
actorEmail?: string | null;
|
|
@@ -91,11 +97,15 @@ export function requireRuntimeAuthorityDescriptor(
|
|
|
91
97
|
const capabilities = normalizeRuntimeExecutionCapabilities(
|
|
92
98
|
descriptor.capabilities,
|
|
93
99
|
);
|
|
100
|
+
const fixtureBehavior = validateFixtureBehavior(descriptor.fixtureBehavior);
|
|
94
101
|
if (
|
|
95
102
|
requiredStrings.some(
|
|
96
103
|
(entry) => typeof entry !== 'string' || entry.trim().length === 0,
|
|
97
104
|
) ||
|
|
98
|
-
capabilities.length === 0
|
|
105
|
+
capabilities.length === 0 ||
|
|
106
|
+
!fixtureBehavior.ok ||
|
|
107
|
+
(fixtureBehavior.behavior !== null &&
|
|
108
|
+
descriptor.integrationMode !== 'fixture')
|
|
99
109
|
) {
|
|
100
110
|
throw new Error(
|
|
101
111
|
'Durable play launch contains an invalid runtimeAuthority descriptor.',
|
|
@@ -114,6 +124,7 @@ export function requireRuntimeAuthorityDescriptor(
|
|
|
114
124
|
: [],
|
|
115
125
|
maxCreditsPerRun: descriptor.maxCreditsPerRun ?? null,
|
|
116
126
|
integrationMode: descriptor.integrationMode ?? null,
|
|
127
|
+
fixtureBehavior: fixtureBehavior.ok ? fixtureBehavior.behavior : null,
|
|
117
128
|
synthetic: descriptor.synthetic === true,
|
|
118
129
|
actorUserId: descriptor.actorUserId ?? null,
|
|
119
130
|
actorEmail: descriptor.actorEmail ?? null,
|