deepline 0.2.30 → 0.2.32
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 +252 -80
- 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.32',
|
|
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';
|
|
@@ -639,6 +645,24 @@ function isDeeplineDeveloperTunnelOrigin502(input: {
|
|
|
639
645
|
}
|
|
640
646
|
}
|
|
641
647
|
|
|
648
|
+
function isUnmarkedExecutionGateway502(input: {
|
|
649
|
+
url: string;
|
|
650
|
+
status: number;
|
|
651
|
+
responseHeaders: Headers;
|
|
652
|
+
}): boolean {
|
|
653
|
+
if (
|
|
654
|
+
input.status !== 502 ||
|
|
655
|
+
input.responseHeaders.has('x-deepline-request-id')
|
|
656
|
+
) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
return new URL(input.url).pathname.endsWith('/execute-fenced-v1');
|
|
661
|
+
} catch {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
642
666
|
function loadSafeFetch(): Promise<SafeFetchModule> {
|
|
643
667
|
safeFetchModule ??= import('@shared_libs/security/safe-fetch');
|
|
644
668
|
return safeFetchModule;
|
|
@@ -1622,6 +1646,22 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
1622
1646
|
};
|
|
1623
1647
|
|
|
1624
1648
|
constructor(options: ContextOptions) {
|
|
1649
|
+
const fixtureBehavior = validateFixtureBehavior(options.fixtureBehavior);
|
|
1650
|
+
if (fixtureBehavior.ok === false) {
|
|
1651
|
+
throw new Error(fixtureBehavior.error);
|
|
1652
|
+
}
|
|
1653
|
+
if (
|
|
1654
|
+
fixtureBehavior.behavior !== null &&
|
|
1655
|
+
options.integrationMode !== 'fixture'
|
|
1656
|
+
) {
|
|
1657
|
+
throw new Error(
|
|
1658
|
+
'fixtureBehavior is only valid when integrationMode is fixture.',
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
options = {
|
|
1662
|
+
...options,
|
|
1663
|
+
fixtureBehavior: fixtureBehavior.behavior,
|
|
1664
|
+
};
|
|
1625
1665
|
this.#options = options;
|
|
1626
1666
|
this.checkpoint = options.checkpoint ?? emptyCheckpoint();
|
|
1627
1667
|
this.durableMappedToolResultsBackedByReceipts = Boolean(
|
|
@@ -6720,6 +6760,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
6720
6760
|
}
|
|
6721
6761
|
}
|
|
6722
6762
|
|
|
6763
|
+
private fixtureProviderPacingDisabled(): boolean {
|
|
6764
|
+
return (
|
|
6765
|
+
this.#options.integrationMode === 'fixture' &&
|
|
6766
|
+
this.#options.enforceFixtureProviderPacing !== true
|
|
6767
|
+
);
|
|
6768
|
+
}
|
|
6769
|
+
|
|
6723
6770
|
private toolDispatchLane(request: ToolCallRequest): {
|
|
6724
6771
|
key: string;
|
|
6725
6772
|
readyCount: number;
|
|
@@ -8983,15 +9030,16 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
8983
9030
|
getPayload: (request: ToolCallRequest) => request.input,
|
|
8984
9031
|
});
|
|
8985
9032
|
|
|
9033
|
+
const batchParallelismCeiling =
|
|
9034
|
+
this.governor.policy.pacing.workerToolBatchDefaultParallelism;
|
|
8986
9035
|
const batchSize =
|
|
8987
|
-
compiledBatches.length > 0
|
|
9036
|
+
compiledBatches.length > 0 &&
|
|
9037
|
+
!this.fixtureProviderPacingDisabled()
|
|
8988
9038
|
? await this.resourceGovernor.suggestedToolParallelism(
|
|
8989
9039
|
compiledBatches[0]!.batchOperation,
|
|
8990
|
-
|
|
8991
|
-
.workerToolBatchDefaultParallelism,
|
|
9040
|
+
batchParallelismCeiling,
|
|
8992
9041
|
)
|
|
8993
|
-
:
|
|
8994
|
-
.workerToolBatchDefaultParallelism;
|
|
9042
|
+
: batchParallelismCeiling;
|
|
8995
9043
|
await executeChunkedRequests({
|
|
8996
9044
|
requests: compiledBatches,
|
|
8997
9045
|
batchSize,
|
|
@@ -9182,11 +9230,12 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9182
9230
|
// each in-flight call, so this only trims the launch burst.
|
|
9183
9231
|
const toolCallConcurrencyCeiling =
|
|
9184
9232
|
this.governor.policy.concurrency.toolCalls;
|
|
9185
|
-
const shapedToolParallelism =
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9233
|
+
const shapedToolParallelism = this.fixtureProviderPacingDisabled()
|
|
9234
|
+
? toolCallConcurrencyCeiling
|
|
9235
|
+
: await this.resourceGovernor.suggestedToolParallelism(
|
|
9236
|
+
toolId,
|
|
9237
|
+
toolCallConcurrencyCeiling,
|
|
9238
|
+
);
|
|
9190
9239
|
const dispatchWidth = Math.min(
|
|
9191
9240
|
toolCallConcurrencyCeiling,
|
|
9192
9241
|
Math.max(1, shapedToolParallelism),
|
|
@@ -9563,6 +9612,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9563
9612
|
});
|
|
9564
9613
|
let transportAttempt = 0;
|
|
9565
9614
|
let invocationAttempt = 0;
|
|
9615
|
+
let physicalAttempt = 0;
|
|
9566
9616
|
const retryToolTransportFailure = async (input: {
|
|
9567
9617
|
error: unknown;
|
|
9568
9618
|
elapsedMs: number;
|
|
@@ -9626,11 +9676,13 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9626
9676
|
};
|
|
9627
9677
|
|
|
9628
9678
|
while (true) {
|
|
9679
|
+
physicalAttempt += 1;
|
|
9629
9680
|
let response: Response | null = null;
|
|
9630
9681
|
let responseData: Record<string, unknown> | null = null;
|
|
9631
9682
|
let responseErrorText: string | null = null;
|
|
9632
9683
|
let providerCallStartedAt: number | null = null;
|
|
9633
9684
|
let providerCallElapsedMs: number | null = null;
|
|
9685
|
+
let integrationFetchStartedAt: number | null = null;
|
|
9634
9686
|
let fetchDispatched = false;
|
|
9635
9687
|
// Receipt ownership is a liveness contract, not a one-time check.
|
|
9636
9688
|
// Keep it alive for the whole provider HTTP request. The cadence
|
|
@@ -9641,15 +9693,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9641
9693
|
timeoutMs || hasReceiptHeartbeat ? new AbortController() : null;
|
|
9642
9694
|
const receiptHeartbeat = options?.heartbeatReceipt;
|
|
9643
9695
|
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;
|
|
9696
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
|
9653
9697
|
try {
|
|
9654
9698
|
const ownershipStartedAt = Date.now();
|
|
9655
9699
|
await options?.beforeProviderCall?.();
|
|
@@ -9705,7 +9749,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9705
9749
|
fixtureExecution &&
|
|
9706
9750
|
this.#options.enforceFixtureProviderPacing === true;
|
|
9707
9751
|
const fixtureOnlyExecution =
|
|
9708
|
-
|
|
9752
|
+
this.fixtureProviderPacingDisabled();
|
|
9709
9753
|
if (
|
|
9710
9754
|
fixtureOnlyExecution &&
|
|
9711
9755
|
!this.fixtureProviderPacingBypassLogged
|
|
@@ -9730,69 +9774,181 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9730
9774
|
toolId,
|
|
9731
9775
|
signal: abortController?.signal,
|
|
9732
9776
|
});
|
|
9733
|
-
const integrationFetchStartedAt = Date.now();
|
|
9734
|
-
providerCallStartedAt = integrationFetchStartedAt;
|
|
9735
9777
|
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
|
-
|
|
9778
|
+
// Provider admission is our queue, not provider execution.
|
|
9779
|
+
// Start the tool deadline only after admission so a busy
|
|
9780
|
+
// runtime cannot consume the remote-call budget while this
|
|
9781
|
+
// attempt is still waiting for permission to leave.
|
|
9782
|
+
if (
|
|
9783
|
+
timeoutMs &&
|
|
9784
|
+
abortController &&
|
|
9785
|
+
!abortController.signal.aborted
|
|
9786
|
+
) {
|
|
9787
|
+
timeoutHandle = setTimeout(() => {
|
|
9788
|
+
abortController.abort(
|
|
9789
|
+
new Error(
|
|
9790
|
+
`Tool ${toolId} runtime API call timed out after ${timeoutMs}ms.`,
|
|
9791
|
+
),
|
|
9792
|
+
);
|
|
9793
|
+
}, timeoutMs);
|
|
9794
|
+
}
|
|
9795
|
+
const fixtureBehavior = this.#options.fixtureBehavior;
|
|
9796
|
+
if (
|
|
9797
|
+
fixtureExecution &&
|
|
9798
|
+
fixtureBehavior &&
|
|
9799
|
+
shouldRouteFixtureToolId(toolId)
|
|
9800
|
+
) {
|
|
9801
|
+
const canonicalProvider =
|
|
9802
|
+
(await this.#options.getToolProvider?.(toolId))?.trim() ||
|
|
9803
|
+
provider;
|
|
9804
|
+
if (shouldRouteFixtureProvider(canonicalProvider)) {
|
|
9805
|
+
if (!durableCallReceiptKey) {
|
|
9806
|
+
throw new Error(
|
|
9807
|
+
'Configured fixture response timing requires a durable call receipt key.',
|
|
9808
|
+
);
|
|
9809
|
+
}
|
|
9810
|
+
const canonicalOperation =
|
|
9811
|
+
(
|
|
9812
|
+
await this.#options.getToolOperation?.(toolId)
|
|
9813
|
+
)?.trim() || toolId;
|
|
9814
|
+
const stableRequestKey = `${canonicalProvider}:${canonicalOperation}:${durableCallReceiptKey}`;
|
|
9815
|
+
const behaviorDigest = stableDigest(
|
|
9816
|
+
JSON.stringify(fixtureBehavior),
|
|
9817
|
+
);
|
|
9818
|
+
const requestKeyDigest = stableDigest(stableRequestKey);
|
|
9819
|
+
let selected: {
|
|
9820
|
+
delayMs: number;
|
|
9821
|
+
sampleIndex: number;
|
|
9822
|
+
} | null = null;
|
|
9823
|
+
const delayStartedAt = Date.now();
|
|
9824
|
+
providerCallStartedAt = delayStartedAt;
|
|
9825
|
+
try {
|
|
9826
|
+
selected = await waitForFixtureResponseDelay({
|
|
9827
|
+
behavior: fixtureBehavior,
|
|
9828
|
+
stableRequestKey,
|
|
9829
|
+
signal: abortController?.signal,
|
|
9830
|
+
onSelected: (value) => {
|
|
9831
|
+
selected = value;
|
|
9832
|
+
this.log(
|
|
9833
|
+
`[fixture.response_delay] ${JSON.stringify({
|
|
9834
|
+
outcome: 'scheduled',
|
|
9835
|
+
provider: canonicalProvider,
|
|
9836
|
+
operation: canonicalOperation,
|
|
9837
|
+
behavior_digest: `sha256_${behaviorDigest}`,
|
|
9838
|
+
request_key_digest: `sha256_${requestKeyDigest}`,
|
|
9839
|
+
delay_ms: value.delayMs,
|
|
9840
|
+
sample_index: value.sampleIndex,
|
|
9841
|
+
physical_attempt: physicalAttempt,
|
|
9842
|
+
})}`,
|
|
9843
|
+
);
|
|
9844
|
+
},
|
|
9845
|
+
});
|
|
9846
|
+
} catch (error) {
|
|
9847
|
+
this.log(
|
|
9848
|
+
`[fixture.response_delay] ${JSON.stringify({
|
|
9849
|
+
outcome: 'aborted',
|
|
9850
|
+
provider: canonicalProvider,
|
|
9851
|
+
operation: canonicalOperation,
|
|
9852
|
+
behavior_digest: `sha256_${behaviorDigest}`,
|
|
9853
|
+
request_key_digest: `sha256_${requestKeyDigest}`,
|
|
9854
|
+
delay_ms: selected?.delayMs ?? null,
|
|
9855
|
+
sample_index: selected?.sampleIndex ?? null,
|
|
9856
|
+
physical_attempt: physicalAttempt,
|
|
9857
|
+
elapsed_ms: Date.now() - delayStartedAt,
|
|
9858
|
+
})}`,
|
|
9859
|
+
);
|
|
9860
|
+
throw error;
|
|
9778
9861
|
}
|
|
9779
|
-
|
|
9862
|
+
this.log(
|
|
9863
|
+
`[fixture.response_delay] ${JSON.stringify({
|
|
9864
|
+
outcome: 'completed',
|
|
9865
|
+
provider: canonicalProvider,
|
|
9866
|
+
operation: canonicalOperation,
|
|
9867
|
+
behavior_digest: `sha256_${behaviorDigest}`,
|
|
9868
|
+
request_key_digest: `sha256_${requestKeyDigest}`,
|
|
9869
|
+
delay_ms: selected.delayMs,
|
|
9870
|
+
sample_index: selected.sampleIndex,
|
|
9871
|
+
physical_attempt: physicalAttempt,
|
|
9872
|
+
elapsed_ms: Date.now() - delayStartedAt,
|
|
9873
|
+
})}`,
|
|
9874
|
+
);
|
|
9780
9875
|
}
|
|
9781
|
-
}
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9876
|
+
}
|
|
9877
|
+
const integrationRequestLease =
|
|
9878
|
+
await this.governor.acquireIntegrationRequestSlot({
|
|
9879
|
+
signal: abortController?.signal,
|
|
9880
|
+
});
|
|
9881
|
+
try {
|
|
9882
|
+
integrationFetchStartedAt = Date.now();
|
|
9883
|
+
providerCallStartedAt ??= integrationFetchStartedAt;
|
|
9884
|
+
fetchDispatched = true;
|
|
9885
|
+
response = await fetch(url, {
|
|
9886
|
+
method: 'POST',
|
|
9887
|
+
signal: abortController?.signal,
|
|
9888
|
+
headers: {
|
|
9889
|
+
'Content-Type': 'application/json',
|
|
9890
|
+
Authorization: `Bearer ${this.#options.executorToken}`,
|
|
9891
|
+
[EXECUTE_RESPONSE_CONTRACT_HEADER]:
|
|
9892
|
+
V2_EXECUTE_RESPONSE_CONTRACT,
|
|
9893
|
+
[EXECUTE_RESPONSE_INTENT_HEADER]: 'dataset',
|
|
9894
|
+
[EXECUTE_TOOL_METADATA_HEADER]: 'true',
|
|
9895
|
+
[TOOL_EXECUTION_ERROR_SCHEMA_HEADER]: String(
|
|
9896
|
+
toolErrorSchemaVersion,
|
|
9897
|
+
),
|
|
9898
|
+
'x-deepline-request-id': deeplineRequestId,
|
|
9899
|
+
...(providerIdempotencyKey
|
|
9900
|
+
? {
|
|
9901
|
+
'x-deepline-idempotency-key':
|
|
9902
|
+
providerIdempotencyKey,
|
|
9903
|
+
}
|
|
9904
|
+
: {}),
|
|
9905
|
+
...protectionHeaders,
|
|
9906
|
+
...(this.#options.runtimeTestFaultHeader
|
|
9907
|
+
? {
|
|
9908
|
+
'x-deepline-test-fault':
|
|
9909
|
+
this.#options.runtimeTestFaultHeader,
|
|
9910
|
+
}
|
|
9911
|
+
: {}),
|
|
9912
|
+
},
|
|
9913
|
+
body: serializedRequestBody(invocationAttempt),
|
|
9914
|
+
});
|
|
9915
|
+
if (response.ok) {
|
|
9916
|
+
try {
|
|
9917
|
+
responseData = await readToolExecuteResponseBody({
|
|
9918
|
+
toolId,
|
|
9919
|
+
abortController,
|
|
9920
|
+
read: () =>
|
|
9921
|
+
response!.json() as Promise<
|
|
9922
|
+
Record<string, unknown>
|
|
9923
|
+
>,
|
|
9924
|
+
});
|
|
9925
|
+
} catch (error) {
|
|
9926
|
+
if (error instanceof SyntaxError) {
|
|
9927
|
+
throw new ToolExecuteInvalidJsonError(error);
|
|
9928
|
+
}
|
|
9929
|
+
throw new ToolExecuteResponseBodyTransportError(error);
|
|
9930
|
+
}
|
|
9931
|
+
} else {
|
|
9932
|
+
try {
|
|
9933
|
+
responseErrorText = await readToolExecuteResponseBody({
|
|
9934
|
+
toolId,
|
|
9935
|
+
abortController,
|
|
9936
|
+
read: () => response!.text(),
|
|
9937
|
+
});
|
|
9938
|
+
} catch (error) {
|
|
9939
|
+
throw new ToolExecuteResponseBodyTransportError(error);
|
|
9940
|
+
}
|
|
9790
9941
|
}
|
|
9942
|
+
} finally {
|
|
9943
|
+
integrationRequestLease.release();
|
|
9791
9944
|
}
|
|
9792
9945
|
} finally {
|
|
9793
9946
|
providerPermit.release();
|
|
9794
9947
|
}
|
|
9795
|
-
if (
|
|
9948
|
+
if (
|
|
9949
|
+
runtimeReceiptReadTraceEnabled &&
|
|
9950
|
+
integrationFetchStartedAt !== null
|
|
9951
|
+
) {
|
|
9796
9952
|
this.log(
|
|
9797
9953
|
`[perf] tool call id=${toolId} phase=integration_fetch_body elapsed_ms=${Date.now() - integrationFetchStartedAt} status=${response.status}`,
|
|
9798
9954
|
);
|
|
@@ -9877,8 +10033,15 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9877
10033
|
durableFenceVerificationSignal,
|
|
9878
10034
|
)
|
|
9879
10035
|
.catch(() => false)) === true);
|
|
10036
|
+
// Fixture execution has no provider-side effect. Replaying the
|
|
10037
|
+
// same stable request after our own transport fails is always
|
|
10038
|
+
// safe, even when this runner lacks a live-provider fence.
|
|
10039
|
+
const fixtureReplaySafe =
|
|
10040
|
+
this.#options.integrationMode === 'fixture';
|
|
9880
10041
|
const transportReplaySafe =
|
|
9881
|
-
!ambiguousDispatchedFailure ||
|
|
10042
|
+
!ambiguousDispatchedFailure ||
|
|
10043
|
+
durableInvocationFenceVerified ||
|
|
10044
|
+
fixtureReplaySafe;
|
|
9882
10045
|
if (!transportReplaySafe) {
|
|
9883
10046
|
const diagnostic = describeTransportError(transportError);
|
|
9884
10047
|
this.log(
|
|
@@ -9946,16 +10109,25 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
9946
10109
|
|
|
9947
10110
|
if (!response.ok) {
|
|
9948
10111
|
const text = responseErrorText ?? '';
|
|
9949
|
-
|
|
10112
|
+
const developerTunnelOrigin502 =
|
|
9950
10113
|
isDeeplineDeveloperTunnelOrigin502({
|
|
9951
10114
|
url,
|
|
9952
10115
|
status: response.status,
|
|
9953
10116
|
bodyText: text,
|
|
9954
|
-
})
|
|
9955
|
-
|
|
10117
|
+
});
|
|
10118
|
+
const unmarkedExecutionGateway502 = isUnmarkedExecutionGateway502(
|
|
10119
|
+
{
|
|
10120
|
+
url,
|
|
10121
|
+
status: response.status,
|
|
10122
|
+
responseHeaders: response.headers,
|
|
10123
|
+
},
|
|
10124
|
+
);
|
|
10125
|
+
if (developerTunnelOrigin502 || unmarkedExecutionGateway502) {
|
|
9956
10126
|
await retryToolTransportFailure({
|
|
9957
10127
|
error: new Error(
|
|
9958
|
-
|
|
10128
|
+
developerTunnelOrigin502
|
|
10129
|
+
? 'the Deepline development tunnel returned a branded 502 before reaching the app origin'
|
|
10130
|
+
: 'the execution gateway ingress returned an unmarked 502 before reaching the durable invocation handler',
|
|
9959
10131
|
),
|
|
9960
10132
|
elapsedMs: providerCallElapsedMs ?? 0,
|
|
9961
10133
|
requestId: deeplineRequestId,
|
|
@@ -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,
|