deepline 0.3.128 → 0.3.130
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 +34 -9
- package/dist/bundling-sources/sdk/src/http.ts +22 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
- package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
- package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
- package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
- package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
- package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +114 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +28 -8
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +146 -15
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +904 -77
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
- package/dist/cli/index.js +250 -38
- package/dist/cli/index.mjs +250 -38
- package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
- package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +231 -35
- package/dist/index.mjs +231 -35
- package/dist/install-integrity.json +31 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +65 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -114,6 +114,7 @@ import {
|
|
|
114
114
|
import {
|
|
115
115
|
normalizePlayRuntimeEnvironment,
|
|
116
116
|
normalizePlayRuntimeNamespace,
|
|
117
|
+
normalizePlayRuntimeSandboxPlacementPolicy,
|
|
117
118
|
normalizePlayRuntimeSelection,
|
|
118
119
|
type PlayRuntimeSelection,
|
|
119
120
|
} from '../../shared_libs/play-runtime/runtime-environment.js';
|
|
@@ -304,7 +305,7 @@ function resolvePlayRunRuntimeSelection(
|
|
|
304
305
|
const runtime = normalizePlayRuntimeSelection(request.runtime);
|
|
305
306
|
if (!runtime) {
|
|
306
307
|
throw new DeeplineError(
|
|
307
|
-
'runtime must be { environment: "preview", namespace, backend?: "daytona" | "modal" } with namespace matching ^[a-z][a-z0-9-]{0,30}$.',
|
|
308
|
+
'runtime must be { environment: "preview", namespace, backend?: "daytona" | "modal", sandboxPlacementPolicy?: "policy_name@1" } with namespace matching ^[a-z][a-z0-9-]{0,30}$.',
|
|
308
309
|
undefined,
|
|
309
310
|
'INVALID_RUNTIME_SELECTION',
|
|
310
311
|
);
|
|
@@ -320,8 +321,10 @@ function resolvePlayRunRuntimeSelection(
|
|
|
320
321
|
const configuredNamespace = process.env.DEEPLINE_RUNTIME_NAMESPACE?.trim();
|
|
321
322
|
const configuredToken =
|
|
322
323
|
process.env.DEEPLINE_RUNTIME_ENVIRONMENT_TOKEN?.trim();
|
|
324
|
+
const configuredPlacementPolicy =
|
|
325
|
+
process.env.DEEPLINE_RUNTIME_SANDBOX_PLACEMENT_POLICY?.trim();
|
|
323
326
|
if (!configured) {
|
|
324
|
-
if (configuredNamespace || configuredToken) {
|
|
327
|
+
if (configuredNamespace || configuredToken || configuredPlacementPolicy) {
|
|
325
328
|
// Name the variables that are actually set. This branch is almost always
|
|
326
329
|
// hit by a stale value auto-loaded from a .env/.env.local rather than by
|
|
327
330
|
// a deliberate export, so the message has to say where to look and how
|
|
@@ -329,6 +332,9 @@ function resolvePlayRunRuntimeSelection(
|
|
|
329
332
|
const present: string[] = [];
|
|
330
333
|
if (configuredNamespace) present.push('DEEPLINE_RUNTIME_NAMESPACE');
|
|
331
334
|
if (configuredToken) present.push('DEEPLINE_RUNTIME_ENVIRONMENT_TOKEN');
|
|
335
|
+
if (configuredPlacementPolicy) {
|
|
336
|
+
present.push('DEEPLINE_RUNTIME_SANDBOX_PLACEMENT_POLICY');
|
|
337
|
+
}
|
|
332
338
|
const isPlural = present.length > 1;
|
|
333
339
|
throw new DeeplineError(
|
|
334
340
|
[
|
|
@@ -359,18 +365,37 @@ function resolvePlayRunRuntimeSelection(
|
|
|
359
365
|
'INVALID_RUNTIME_NAMESPACE',
|
|
360
366
|
);
|
|
361
367
|
}
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
368
|
+
const configuredBackendValue =
|
|
369
|
+
process.env.DEEPLINE_PLAY_RUNNER_BACKEND?.trim();
|
|
370
|
+
const configuredBackend =
|
|
371
|
+
configuredBackendValue === 'daytona' || configuredBackendValue === 'modal'
|
|
372
|
+
? configuredBackendValue
|
|
373
|
+
: undefined;
|
|
374
|
+
if (configuredBackendValue && !configuredBackend) {
|
|
367
375
|
throw new DeeplineError(
|
|
368
|
-
`DEEPLINE_PLAY_RUNNER_BACKEND must be daytona or modal for preview runtime selection. Received "${
|
|
376
|
+
`DEEPLINE_PLAY_RUNNER_BACKEND must be daytona or modal for preview runtime selection. Received "${configuredBackendValue}".`,
|
|
369
377
|
undefined,
|
|
370
378
|
'INVALID_RUNTIME_BACKEND',
|
|
371
379
|
);
|
|
372
380
|
}
|
|
373
|
-
|
|
381
|
+
if (
|
|
382
|
+
configuredPlacementPolicy &&
|
|
383
|
+
!normalizePlayRuntimeSandboxPlacementPolicy(configuredPlacementPolicy)
|
|
384
|
+
) {
|
|
385
|
+
throw new DeeplineError(
|
|
386
|
+
`DEEPLINE_RUNTIME_SANDBOX_PLACEMENT_POLICY must be a versioned placement policy id such as daytona_only@1. Received "${configuredPlacementPolicy}".`,
|
|
387
|
+
undefined,
|
|
388
|
+
'INVALID_RUNTIME_SANDBOX_PLACEMENT_POLICY',
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
environment,
|
|
393
|
+
namespace,
|
|
394
|
+
...(configuredBackend ? { backend: configuredBackend } : {}),
|
|
395
|
+
...(configuredPlacementPolicy
|
|
396
|
+
? { sandboxPlacementPolicy: configuredPlacementPolicy }
|
|
397
|
+
: {}),
|
|
398
|
+
};
|
|
374
399
|
}
|
|
375
400
|
|
|
376
401
|
function runtimeSelectionHeaders(
|
|
@@ -550,6 +550,28 @@ export class HttpClient {
|
|
|
550
550
|
throw lastError;
|
|
551
551
|
}
|
|
552
552
|
|
|
553
|
+
// Vercel's deployment-protection interstitial is served as a 2xx
|
|
554
|
+
// HTML page, so it never reaches the !ok branch above. Without this
|
|
555
|
+
// the caller receives a string where it expected JSON and reports
|
|
556
|
+
// whatever field it found missing — an expired `vercel login` shows
|
|
557
|
+
// up as an unrelated validation error several layers away.
|
|
558
|
+
if (typeof parsed === 'string') {
|
|
559
|
+
const interstitial = detectHtmlErrorBody(
|
|
560
|
+
body,
|
|
561
|
+
response.headers.get('content-type'),
|
|
562
|
+
);
|
|
563
|
+
if (interstitial) {
|
|
564
|
+
throw new DeeplineError(
|
|
565
|
+
`${this.config.baseUrl} returned a deployment-protection page ` +
|
|
566
|
+
'instead of JSON. Set VERCEL_PROTECTION_BYPASS_TOKEN, or run ' +
|
|
567
|
+
'`vercel login` if the CLI session expired.',
|
|
568
|
+
response.status,
|
|
569
|
+
'DEPLOYMENT_PROTECTED',
|
|
570
|
+
{ htmlErrorPage: true },
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
553
575
|
return parsed as T;
|
|
554
576
|
} catch (error) {
|
|
555
577
|
clearTimeout(timeoutId);
|
|
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
|
|
|
200
200
|
// getters keep their established compatibility behavior.
|
|
201
201
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
202
202
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
203
|
-
version: '0.3.
|
|
203
|
+
version: '0.3.130',
|
|
204
204
|
updateSummary:
|
|
205
205
|
'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
|
|
206
206
|
packageCapabilities: {
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
PutObjectCommand,
|
|
4
4
|
GetObjectCommand,
|
|
5
5
|
HeadObjectCommand,
|
|
6
|
+
DeleteObjectCommand,
|
|
6
7
|
} from '@aws-sdk/client-s3';
|
|
7
8
|
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
|
8
9
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
@@ -203,6 +204,27 @@ export async function getBufferFromR2(
|
|
|
203
204
|
return Buffer.from(bytes);
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Delete one immutable runtime object. Callers must retain their own durable
|
|
209
|
+
* deletion intent and confirm absence with HEAD before they acknowledge this
|
|
210
|
+
* operation: object storage and Postgres do not share a transaction.
|
|
211
|
+
*/
|
|
212
|
+
export async function deleteR2Object(
|
|
213
|
+
key: string,
|
|
214
|
+
kind: R2BucketKind = 'default',
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
const startedAt = Date.now();
|
|
217
|
+
const config = getR2Config(kind);
|
|
218
|
+
const client = getR2Client(config);
|
|
219
|
+
await client.send(
|
|
220
|
+
new DeleteObjectCommand({
|
|
221
|
+
Bucket: config.bucket,
|
|
222
|
+
Key: key,
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
logR2Perf('delete', key, startedAt, { kind });
|
|
226
|
+
}
|
|
227
|
+
|
|
206
228
|
export async function createSignedR2ReadUrl(input: {
|
|
207
229
|
key: string;
|
|
208
230
|
kind?: R2BucketKind;
|
|
@@ -47,6 +47,14 @@ import {
|
|
|
47
47
|
LEGACY_RUNTIME_RECEIPT_WIRE_HEADER,
|
|
48
48
|
} from './/runtime-contract';
|
|
49
49
|
import { PLAY_RUNTIME_API_COMPAT_PATH } from './/runtime-api-paths';
|
|
50
|
+
import {
|
|
51
|
+
RUNTIME_API_ACCEPTS_HEADER,
|
|
52
|
+
gzipRuntimeApiRequestBody,
|
|
53
|
+
isLegacyRuntimeApiGzipDecoderFailure,
|
|
54
|
+
noteRuntimeApiCompressionCapability,
|
|
55
|
+
revokeRuntimeApiCompressionCapability,
|
|
56
|
+
runtimeApiOriginAcceptsGzip,
|
|
57
|
+
} from './/runtime-api-compression';
|
|
50
58
|
import {
|
|
51
59
|
PLAY_RUNTIME_TEST_FAULT_HEADER,
|
|
52
60
|
recognizedRuntimeTestFaultCount,
|
|
@@ -302,9 +310,143 @@ export type WorkerRuntimeApiContext = {
|
|
|
302
310
|
requestTimeoutMs?: number | null;
|
|
303
311
|
/** A higher-level writer owns retries and preserves the exact request body. */
|
|
304
312
|
retryPolicy?: 'default' | 'none';
|
|
313
|
+
/**
|
|
314
|
+
* Observe the gateway request identity of a SUCCESSFUL response. Failures
|
|
315
|
+
* already carry it on the thrown error; a success carried it nowhere, so a
|
|
316
|
+
* response the caller found surprising could not be traced back to the
|
|
317
|
+
* gateway record that produced it.
|
|
318
|
+
*/
|
|
319
|
+
onResponseMeta?: (meta: RuntimeApiResponseMeta) => void;
|
|
305
320
|
signal?: AbortSignal;
|
|
306
321
|
};
|
|
307
322
|
|
|
323
|
+
/**
|
|
324
|
+
* The receipt gateway's own account of a request, from its `Server-Timing`
|
|
325
|
+
* response header. Its `runtime.gateway.action.finished` durationMs starts
|
|
326
|
+
* AFTER authentication, so auth, body parse, and pool wait are invisible in
|
|
327
|
+
* gateway logs and observable only here — against `roundTripMs`, the client's
|
|
328
|
+
* measured wall for the same request, which is the only number that includes
|
|
329
|
+
* the network.
|
|
330
|
+
*/
|
|
331
|
+
export type RuntimeGatewayServerTiming = {
|
|
332
|
+
authLaunchMs: number | null;
|
|
333
|
+
authVerifyMs: number | null;
|
|
334
|
+
bodyParseMs: number | null;
|
|
335
|
+
/**
|
|
336
|
+
* Gunzip of a compressed request body, inside `bodyParseMs`. Null when the
|
|
337
|
+
* request was not compressed or the gateway predates the entry.
|
|
338
|
+
*/
|
|
339
|
+
bodyInflateMs: number | null;
|
|
340
|
+
dispatchPoolMs: number | null;
|
|
341
|
+
/**
|
|
342
|
+
* Slowest durable receipt-value persistence in this completion request.
|
|
343
|
+
* This sits inside `dispatchCallbackMs`; null means the gateway predates the
|
|
344
|
+
* measurement rather than that a persistence took no time.
|
|
345
|
+
*/
|
|
346
|
+
receiptValuePersistMs: number | null;
|
|
347
|
+
dispatchCallbackMs: number | null;
|
|
348
|
+
authSession: 'hit' | 'miss' | 'coalesced' | 'unknown' | null;
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
export type RuntimeApiResponseMeta = {
|
|
352
|
+
action: RuntimeApiRequest['action'];
|
|
353
|
+
requestId: string | null;
|
|
354
|
+
/** Client-measured wall of the responding attempt: request send to response. */
|
|
355
|
+
roundTripMs?: number | null;
|
|
356
|
+
/** Absent when the gateway sent no `Server-Timing` header. */
|
|
357
|
+
serverTiming?: RuntimeGatewayServerTiming | null;
|
|
358
|
+
/**
|
|
359
|
+
* Serialized JSON bytes of the request body, before compression. Set on
|
|
360
|
+
* every request, so `wire / raw` across a run is the compression ratio the
|
|
361
|
+
* transport actually achieved — including the uncompressed requests, which
|
|
362
|
+
* is the honest denominator.
|
|
363
|
+
*/
|
|
364
|
+
requestBodyRawBytes?: number | null;
|
|
365
|
+
/** Bytes actually put on the wire. Equals `requestBodyRawBytes` uncompressed. */
|
|
366
|
+
requestBodyWireBytes?: number | null;
|
|
367
|
+
/**
|
|
368
|
+
* Runner-local wall spent serializing the JSON body once for this retry
|
|
369
|
+
* ladder. It is deliberately outside `roundTripMs`: this synchronous work
|
|
370
|
+
* occupies the sandbox CPU before any request can leave the process.
|
|
371
|
+
*/
|
|
372
|
+
requestBodySerializeMs?: number | null;
|
|
373
|
+
/**
|
|
374
|
+
* Runner-local wall spent packing the successful request attempt for the
|
|
375
|
+
* wire (gzip when the gateway has advertised support). This is also outside
|
|
376
|
+
* `roundTripMs`, so a CPU-bound encoder cannot masquerade as gateway or
|
|
377
|
+
* network time.
|
|
378
|
+
*/
|
|
379
|
+
requestBodyPackMs?: number | null;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const RUNTIME_GATEWAY_AUTH_SESSION_VALUES = new Set([
|
|
383
|
+
'hit',
|
|
384
|
+
'miss',
|
|
385
|
+
'coalesced',
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Parse a receipt gateway `Server-Timing` header into its known entries.
|
|
390
|
+
*
|
|
391
|
+
* Deliberately total: an absent, empty, or malformed header yields nulls
|
|
392
|
+
* rather than an error. This is diagnostic instrumentation on the durable
|
|
393
|
+
* receipt path, and a gateway that changes its header must never be able to
|
|
394
|
+
* fail a run.
|
|
395
|
+
*/
|
|
396
|
+
export function parseRuntimeGatewayServerTiming(
|
|
397
|
+
header: string | null | undefined,
|
|
398
|
+
): RuntimeGatewayServerTiming | null {
|
|
399
|
+
if (typeof header !== 'string' || !header.trim()) return null;
|
|
400
|
+
const durations = new Map<string, number>();
|
|
401
|
+
let authSession: RuntimeGatewayServerTiming['authSession'] = null;
|
|
402
|
+
for (const rawEntry of header.split(',')) {
|
|
403
|
+
const parts = rawEntry.split(';');
|
|
404
|
+
const name = parts[0]?.trim().toLowerCase();
|
|
405
|
+
if (!name) continue;
|
|
406
|
+
for (const parameter of parts.slice(1)) {
|
|
407
|
+
const separator = parameter.indexOf('=');
|
|
408
|
+
if (separator < 0) continue;
|
|
409
|
+
const key = parameter.slice(0, separator).trim().toLowerCase();
|
|
410
|
+
const value = parameter
|
|
411
|
+
.slice(separator + 1)
|
|
412
|
+
.trim()
|
|
413
|
+
.replace(/^"|"$/g, '');
|
|
414
|
+
if (key === 'dur') {
|
|
415
|
+
// `Number('')` is 0, which would turn a truncated header into a
|
|
416
|
+
// confident zero — the one reading this instrument must never produce.
|
|
417
|
+
const parsed = value === '' ? Number.NaN : Number(value);
|
|
418
|
+
if (Number.isFinite(parsed)) durations.set(name, parsed);
|
|
419
|
+
} else if (key === 'desc' && name === 'auth_session' && value !== '') {
|
|
420
|
+
authSession = RUNTIME_GATEWAY_AUTH_SESSION_VALUES.has(value)
|
|
421
|
+
? (value as 'hit' | 'miss' | 'coalesced')
|
|
422
|
+
: 'unknown';
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const duration = (name: string): number | null =>
|
|
427
|
+
durations.has(name) ? durations.get(name)! : null;
|
|
428
|
+
const timing: RuntimeGatewayServerTiming = {
|
|
429
|
+
authLaunchMs: duration('auth_launch'),
|
|
430
|
+
authVerifyMs: duration('auth_verify'),
|
|
431
|
+
bodyParseMs: duration('body_parse'),
|
|
432
|
+
bodyInflateMs: duration('body_inflate'),
|
|
433
|
+
dispatchPoolMs: duration('dispatch_pool'),
|
|
434
|
+
receiptValuePersistMs: duration('receipt_value_persist'),
|
|
435
|
+
dispatchCallbackMs: duration('dispatch_callback'),
|
|
436
|
+
authSession,
|
|
437
|
+
};
|
|
438
|
+
const carriesSomething =
|
|
439
|
+
authSession !== null ||
|
|
440
|
+
timing.authLaunchMs !== null ||
|
|
441
|
+
timing.authVerifyMs !== null ||
|
|
442
|
+
timing.bodyParseMs !== null ||
|
|
443
|
+
timing.bodyInflateMs !== null ||
|
|
444
|
+
timing.dispatchPoolMs !== null ||
|
|
445
|
+
timing.receiptValuePersistMs !== null ||
|
|
446
|
+
timing.dispatchCallbackMs !== null;
|
|
447
|
+
return carriesSomething ? timing : null;
|
|
448
|
+
}
|
|
449
|
+
|
|
308
450
|
const APP_RUNTIME_API_RETRY_DELAYS_MS = [100, 250, 500, 1_000] as const;
|
|
309
451
|
const APP_RUNTIME_API_RECEIPT_RETRY_DELAYS_MS = [
|
|
310
452
|
250, 500, 1_000, 2_000, 4_000, 8_000, 12_000,
|
|
@@ -1269,8 +1411,39 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1269
1411
|
context.requestTimeoutMs ?? APP_RUNTIME_API_DEFAULT_REQUEST_TIMEOUT_MS,
|
|
1270
1412
|
),
|
|
1271
1413
|
);
|
|
1414
|
+
// Serialize ONCE for the whole retry ladder: the body is identical across
|
|
1415
|
+
// attempts (that identity is what makes a receipt retry safe), so a second
|
|
1416
|
+
// `JSON.stringify` of a megabyte payload would be pure cost.
|
|
1417
|
+
const serializationStartedAt = Date.now();
|
|
1418
|
+
const serializedBody = JSON.stringify(body);
|
|
1419
|
+
const requestBodySerializeMs = Math.max(
|
|
1420
|
+
0,
|
|
1421
|
+
Date.now() - serializationStartedAt,
|
|
1422
|
+
);
|
|
1272
1423
|
|
|
1273
|
-
|
|
1424
|
+
let forceUncompressedNextAttempt = false;
|
|
1425
|
+
let usedLegacyDecoderFallback = false;
|
|
1426
|
+
// A legacy decoder rejection is known to be pre-dispatch, so it earns one
|
|
1427
|
+
// immediate plain-JSON resend even when the caller disabled normal retries.
|
|
1428
|
+
// It does not consume an application retry attempt or sleep.
|
|
1429
|
+
let attempt = 1;
|
|
1430
|
+
while (attempt <= maxAttempts) {
|
|
1431
|
+
// Re-evaluated per attempt rather than hoisted: the capability can be
|
|
1432
|
+
// learned from THIS request's own first attempt (or from a sibling request
|
|
1433
|
+
// that landed in between), so a retry compresses as soon as the origin has
|
|
1434
|
+
// advertised, without waiting for the next call.
|
|
1435
|
+
const packingStartedAt = Date.now();
|
|
1436
|
+
const wireBody = await gzipRuntimeApiRequestBody({
|
|
1437
|
+
serialized: serializedBody,
|
|
1438
|
+
originAcceptsGzip:
|
|
1439
|
+
!forceUncompressedNextAttempt && runtimeApiOriginAcceptsGzip(baseUrl),
|
|
1440
|
+
});
|
|
1441
|
+
forceUncompressedNextAttempt = false;
|
|
1442
|
+
const requestBodyPackMs = Math.max(0, Date.now() - packingStartedAt);
|
|
1443
|
+
// Started AFTER the body is packed: `roundTripMs` is the request's wall
|
|
1444
|
+
// from send to parsed response, and it is read against the gateway's own
|
|
1445
|
+
// `Server-Timing` to isolate the network. Folding local compression into it
|
|
1446
|
+
// would show up as network cost that no round trip explains.
|
|
1274
1447
|
const attemptStartedAt = Date.now();
|
|
1275
1448
|
const transportAttemptId = randomUUID();
|
|
1276
1449
|
const requestSignal = context.signal
|
|
@@ -1285,7 +1458,12 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1285
1458
|
response = await runtimeFetch(resolveAppRuntimeApiUrl(context), {
|
|
1286
1459
|
method: 'POST',
|
|
1287
1460
|
headers: {
|
|
1461
|
+
// The payload is JSON either way; `content-encoding` describes only
|
|
1462
|
+
// how those JSON bytes were packed for the wire.
|
|
1288
1463
|
'content-type': 'application/json',
|
|
1464
|
+
...(wireBody.contentEncoding
|
|
1465
|
+
? { 'content-encoding': wireBody.contentEncoding }
|
|
1466
|
+
: {}),
|
|
1289
1467
|
authorization: `Bearer ${token}`,
|
|
1290
1468
|
[LEGACY_RUNTIME_RECEIPT_WIRE_HEADER]: String(
|
|
1291
1469
|
LEGACY_RUNTIME_RECEIPT_WIRE_FORMAT,
|
|
@@ -1304,7 +1482,14 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1304
1482
|
}
|
|
1305
1483
|
: {}),
|
|
1306
1484
|
},
|
|
1307
|
-
|
|
1485
|
+
// DOM fetch accepts ArrayBuffer, while Node 24 deliberately rejects a
|
|
1486
|
+
// Uint8Array backed by an arbitrary ArrayBufferLike. Copy only the
|
|
1487
|
+
// compressed branch into an exact ArrayBuffer; its wire bytes remain
|
|
1488
|
+
// identical and the plain JSON fast path stays allocation-free.
|
|
1489
|
+
body:
|
|
1490
|
+
typeof wireBody.body === 'string'
|
|
1491
|
+
? wireBody.body
|
|
1492
|
+
: Uint8Array.from(wireBody.body).buffer,
|
|
1308
1493
|
signal: requestSignal,
|
|
1309
1494
|
});
|
|
1310
1495
|
const preferredMachineId =
|
|
@@ -1338,6 +1523,7 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1338
1523
|
retryDelayMs,
|
|
1339
1524
|
});
|
|
1340
1525
|
await sleep(retryDelayMs);
|
|
1526
|
+
attempt += 1;
|
|
1341
1527
|
continue;
|
|
1342
1528
|
}
|
|
1343
1529
|
recordAppRuntimeRetryFailure({
|
|
@@ -1364,6 +1550,21 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1364
1550
|
boundaryLabel,
|
|
1365
1551
|
});
|
|
1366
1552
|
}
|
|
1553
|
+
// Learn the capability from EVERY response, including 4xx/5xx: an error
|
|
1554
|
+
// response still proves the gateway that produced it can decode a
|
|
1555
|
+
// compressed body, and a run whose first request fails must not be stuck
|
|
1556
|
+
// sending uncompressed bodies for the rest of its life. Deliberately
|
|
1557
|
+
// outside the fetch try/catch and defensive: this is rollout
|
|
1558
|
+
// instrumentation, and a header read must never be able to fail — or be
|
|
1559
|
+
// misreported as — a durable request.
|
|
1560
|
+
try {
|
|
1561
|
+
noteRuntimeApiCompressionCapability({
|
|
1562
|
+
baseUrl,
|
|
1563
|
+
header: response.headers?.get(RUNTIME_API_ACCEPTS_HEADER),
|
|
1564
|
+
});
|
|
1565
|
+
} catch {
|
|
1566
|
+
// A response without readable headers teaches nothing; it never fails.
|
|
1567
|
+
}
|
|
1367
1568
|
if (response.ok) {
|
|
1368
1569
|
try {
|
|
1369
1570
|
const parsed = (await response.json()) as TResponse;
|
|
@@ -1383,6 +1584,28 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1383
1584
|
maxAttempts,
|
|
1384
1585
|
finalAttemptStartedAt: attemptStartedAt,
|
|
1385
1586
|
});
|
|
1587
|
+
if (context.onResponseMeta) {
|
|
1588
|
+
try {
|
|
1589
|
+
context.onResponseMeta({
|
|
1590
|
+
action: body.action,
|
|
1591
|
+
requestId:
|
|
1592
|
+
response.headers.get('x-deepline-request-id')?.trim() || null,
|
|
1593
|
+
// Request send to parsed response, on the runner's clock. The
|
|
1594
|
+
// gateway's own timings are subtracted from this to leave the
|
|
1595
|
+
// network and TLS cost of the hop.
|
|
1596
|
+
roundTripMs: Date.now() - attemptStartedAt,
|
|
1597
|
+
serverTiming: parseRuntimeGatewayServerTiming(
|
|
1598
|
+
response.headers.get('server-timing'),
|
|
1599
|
+
),
|
|
1600
|
+
requestBodyRawBytes: wireBody.rawBytes,
|
|
1601
|
+
requestBodyWireBytes: wireBody.wireBytes,
|
|
1602
|
+
requestBodySerializeMs,
|
|
1603
|
+
requestBodyPackMs,
|
|
1604
|
+
});
|
|
1605
|
+
} catch {
|
|
1606
|
+
// Observation must never affect durable delivery.
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1386
1609
|
return parsed;
|
|
1387
1610
|
} catch (error) {
|
|
1388
1611
|
await retryAppRuntimeBodyTimeoutOrThrow({
|
|
@@ -1396,6 +1619,7 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1396
1619
|
httpStatus: response.status,
|
|
1397
1620
|
boundaryLabel,
|
|
1398
1621
|
});
|
|
1622
|
+
attempt += 1;
|
|
1399
1623
|
continue;
|
|
1400
1624
|
}
|
|
1401
1625
|
}
|
|
@@ -1441,6 +1665,23 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1441
1665
|
retryAfterMs: appRuntimeRetryAfterMs(response, ''),
|
|
1442
1666
|
boundaryLabel,
|
|
1443
1667
|
});
|
|
1668
|
+
attempt += 1;
|
|
1669
|
+
continue;
|
|
1670
|
+
}
|
|
1671
|
+
if (
|
|
1672
|
+
!usedLegacyDecoderFallback &&
|
|
1673
|
+
isLegacyRuntimeApiGzipDecoderFailure({
|
|
1674
|
+
contentEncoding: wireBody.contentEncoding,
|
|
1675
|
+
status: response.status,
|
|
1676
|
+
responseText,
|
|
1677
|
+
})
|
|
1678
|
+
) {
|
|
1679
|
+
// An older gateway reached through a rolling hostname rejected gzip
|
|
1680
|
+
// before JSON parsing, so no runtime action was dispatched. Demote the
|
|
1681
|
+
// hostname capability and resend the same serialized request plain.
|
|
1682
|
+
revokeRuntimeApiCompressionCapability(baseUrl);
|
|
1683
|
+
forceUncompressedNextAttempt = true;
|
|
1684
|
+
usedLegacyDecoderFallback = true;
|
|
1444
1685
|
continue;
|
|
1445
1686
|
}
|
|
1446
1687
|
const responseCode = appRuntimeErrorCode(response, responseText);
|
|
@@ -1481,6 +1722,7 @@ async function postAppRuntimeApi<TResponse>(
|
|
|
1481
1722
|
httpStatus: response.status,
|
|
1482
1723
|
});
|
|
1483
1724
|
await sleep(retryDelayMs);
|
|
1725
|
+
attempt += 1;
|
|
1484
1726
|
continue;
|
|
1485
1727
|
}
|
|
1486
1728
|
recordAppRuntimeRetryFailure({
|
|
@@ -2393,6 +2635,27 @@ export async function completeRuntimeStepReceiptsViaAppRuntime(
|
|
|
2393
2635
|
});
|
|
2394
2636
|
}
|
|
2395
2637
|
|
|
2638
|
+
/**
|
|
2639
|
+
* Publish receipt-owned R2 references written by the trusted execution relay.
|
|
2640
|
+
* This deliberately has its own action name so an older gateway rejects it
|
|
2641
|
+
* instead of treating a missing legacy `output` as a successful null write.
|
|
2642
|
+
*/
|
|
2643
|
+
export async function completeRuntimeStepReceiptsByReferenceViaAppRuntime(
|
|
2644
|
+
context: WorkerRuntimeApiContext,
|
|
2645
|
+
input: Omit<
|
|
2646
|
+
Extract<
|
|
2647
|
+
RuntimeApiRequest,
|
|
2648
|
+
{ action: 'complete_runtime_step_receipts_by_reference' }
|
|
2649
|
+
>,
|
|
2650
|
+
'action'
|
|
2651
|
+
>,
|
|
2652
|
+
): Promise<Array<RuntimeStepReceipt | null>> {
|
|
2653
|
+
return await postAppRuntimeApi<Array<RuntimeStepReceipt | null>>(context, {
|
|
2654
|
+
action: 'complete_runtime_step_receipts_by_reference',
|
|
2655
|
+
...input,
|
|
2656
|
+
});
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2396
2659
|
export async function failRuntimeStepReceiptViaAppRuntime(
|
|
2397
2660
|
context: WorkerRuntimeApiContext,
|
|
2398
2661
|
input: Omit<
|