deepline 0.1.249 → 0.1.251
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 +82 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/types.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +93 -9
- package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +10 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +44 -0
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +18 -0
- package/dist/cli/index.js +112 -14
- package/dist/cli/index.mjs +112 -14
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +102 -2
- package/dist/index.mjs +102 -2
- package/package.json +1 -1
|
@@ -80,6 +80,13 @@ import type {
|
|
|
80
80
|
import type { PlayStagedFileRef } from './plays/local-file-discovery.js';
|
|
81
81
|
import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest.js';
|
|
82
82
|
import type { EnrichCompiledConfig } from './cli/enrich-play-compiler.js';
|
|
83
|
+
import { RUNTIME_ENVIRONMENT_TOKEN_HEADER } from '../../shared_libs/play-runtime/coordinator-headers.js';
|
|
84
|
+
import {
|
|
85
|
+
normalizePlayRuntimeEnvironment,
|
|
86
|
+
normalizePlayRuntimeNamespace,
|
|
87
|
+
normalizePlayRuntimeSelection,
|
|
88
|
+
type PlayRuntimeSelection,
|
|
89
|
+
} from '../../shared_libs/play-runtime/runtime-environment.js';
|
|
83
90
|
|
|
84
91
|
const TERMINAL_PLAY_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
85
92
|
const INCLUDE_TOOL_METADATA_HEADER = 'x-deepline-include-tool-metadata';
|
|
@@ -109,6 +116,75 @@ function resolvePlayRunIntegrationMode(
|
|
|
109
116
|
);
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
function resolvePlayRunRuntimeSelection(
|
|
120
|
+
request: StartPlayRunRequest,
|
|
121
|
+
): PlayRuntimeSelection | undefined {
|
|
122
|
+
if (request.runtime !== undefined) {
|
|
123
|
+
const runtime = normalizePlayRuntimeSelection(request.runtime);
|
|
124
|
+
if (!runtime) {
|
|
125
|
+
throw new DeeplineError(
|
|
126
|
+
'runtime must be exactly { environment: "preview", namespace } with namespace matching ^[a-z][a-z0-9-]{0,30}$.',
|
|
127
|
+
undefined,
|
|
128
|
+
'INVALID_RUNTIME_SELECTION',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return runtime;
|
|
132
|
+
}
|
|
133
|
+
// These variables select where the app executes a run, not which app the
|
|
134
|
+
// SDK calls. DEEPLINE_API_BASE_URL still chooses the app host. Environment
|
|
135
|
+
// opts into remote preview routing, namespace names the isolated lane, and
|
|
136
|
+
// the token only authorizes that selection. Omitting all three leaves the
|
|
137
|
+
// app free to use its native runtime (local for a local app, prod for prod).
|
|
138
|
+
const configured = process.env.DEEPLINE_RUNTIME_ENVIRONMENT?.trim();
|
|
139
|
+
const configuredNamespace = process.env.DEEPLINE_RUNTIME_NAMESPACE?.trim();
|
|
140
|
+
const configuredToken =
|
|
141
|
+
process.env.DEEPLINE_RUNTIME_ENVIRONMENT_TOKEN?.trim();
|
|
142
|
+
if (!configured) {
|
|
143
|
+
if (configuredNamespace || configuredToken) {
|
|
144
|
+
throw new DeeplineError(
|
|
145
|
+
'DEEPLINE_RUNTIME_ENVIRONMENT=preview and DEEPLINE_RUNTIME_NAMESPACE are required when runtime selection configuration is present.',
|
|
146
|
+
undefined,
|
|
147
|
+
'INVALID_RUNTIME_ENVIRONMENT',
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const environment = normalizePlayRuntimeEnvironment(configured);
|
|
153
|
+
if (!environment) {
|
|
154
|
+
throw new DeeplineError(
|
|
155
|
+
`DEEPLINE_RUNTIME_ENVIRONMENT supports only explicit preview selection. Received "${configured}". Omit it to use the app-native runtime.`,
|
|
156
|
+
undefined,
|
|
157
|
+
'INVALID_RUNTIME_ENVIRONMENT',
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const namespace = normalizePlayRuntimeNamespace(configuredNamespace);
|
|
161
|
+
if (!namespace) {
|
|
162
|
+
throw new DeeplineError(
|
|
163
|
+
'DEEPLINE_RUNTIME_NAMESPACE is required for preview and must match ^[a-z][a-z0-9-]{0,30}$.',
|
|
164
|
+
undefined,
|
|
165
|
+
'INVALID_RUNTIME_NAMESPACE',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return { environment, namespace };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function runtimeSelectionHeaders(
|
|
172
|
+
runtime: PlayRuntimeSelection | undefined,
|
|
173
|
+
): Record<string, string> | undefined {
|
|
174
|
+
if (!runtime) return undefined;
|
|
175
|
+
// Keep the credential out of the typed runtime payload. The app consumes it
|
|
176
|
+
// at the HTTP boundary and never forwards it to Fly, Absurd, or Daytona.
|
|
177
|
+
const token = process.env.DEEPLINE_RUNTIME_ENVIRONMENT_TOKEN?.trim();
|
|
178
|
+
if (!token) {
|
|
179
|
+
throw new DeeplineError(
|
|
180
|
+
'DEEPLINE_RUNTIME_ENVIRONMENT_TOKEN is required for explicit preview runtime selection.',
|
|
181
|
+
undefined,
|
|
182
|
+
'RUNTIME_ENVIRONMENT_TOKEN_REQUIRED',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return { [RUNTIME_ENVIRONMENT_TOKEN_HEADER]: token };
|
|
186
|
+
}
|
|
187
|
+
|
|
112
188
|
function normalizeTestPolicyOverrides(
|
|
113
189
|
value: unknown,
|
|
114
190
|
source: string,
|
|
@@ -1577,6 +1653,7 @@ export class DeeplineClient {
|
|
|
1577
1653
|
const testPolicyOverrides =
|
|
1578
1654
|
request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
|
|
1579
1655
|
const forceToolRefresh = request.forceToolRefresh === true;
|
|
1656
|
+
const runtime = resolvePlayRunRuntimeSelection(request);
|
|
1580
1657
|
const response = await this.http.post<Record<string, unknown>>(
|
|
1581
1658
|
'/api/v2/plays/run',
|
|
1582
1659
|
{
|
|
@@ -1619,9 +1696,10 @@ export class DeeplineClient {
|
|
|
1619
1696
|
// defaults to absurd; callers normally omit this field.
|
|
1620
1697
|
...(request.profile ? { profile: request.profile } : {}),
|
|
1621
1698
|
...(integrationMode ? { integrationMode } : {}),
|
|
1699
|
+
...(runtime ? { runtime } : {}),
|
|
1622
1700
|
...(testPolicyOverrides ? { testPolicyOverrides } : {}),
|
|
1623
1701
|
},
|
|
1624
|
-
|
|
1702
|
+
runtimeSelectionHeaders(runtime),
|
|
1625
1703
|
// A start can reach the server even when its response is lost or times
|
|
1626
1704
|
// out. Retrying this mutation without an idempotency key can create a
|
|
1627
1705
|
// second root run, so callers must resolve ambiguous failures explicitly.
|
|
@@ -1649,6 +1727,7 @@ export class DeeplineClient {
|
|
|
1649
1727
|
const testPolicyOverrides =
|
|
1650
1728
|
request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
|
|
1651
1729
|
const forceToolRefresh = request.forceToolRefresh === true;
|
|
1730
|
+
const runtime = resolvePlayRunRuntimeSelection(request);
|
|
1652
1731
|
const body = {
|
|
1653
1732
|
...(request.name ? { name: request.name } : {}),
|
|
1654
1733
|
...(request.revisionId ? { revisionId: request.revisionId } : {}),
|
|
@@ -1687,6 +1766,7 @@ export class DeeplineClient {
|
|
|
1687
1766
|
: {}),
|
|
1688
1767
|
...(request.profile ? { profile: request.profile } : {}),
|
|
1689
1768
|
...(integrationMode ? { integrationMode } : {}),
|
|
1769
|
+
...(runtime ? { runtime } : {}),
|
|
1690
1770
|
...(testPolicyOverrides ? { testPolicyOverrides } : {}),
|
|
1691
1771
|
};
|
|
1692
1772
|
for await (const event of this.http.streamSse<PlayLiveEvent>(
|
|
@@ -1694,6 +1774,7 @@ export class DeeplineClient {
|
|
|
1694
1774
|
{
|
|
1695
1775
|
method: 'POST',
|
|
1696
1776
|
body,
|
|
1777
|
+
headers: runtimeSelectionHeaders(runtime),
|
|
1697
1778
|
signal: options?.signal,
|
|
1698
1779
|
},
|
|
1699
1780
|
)) {
|
|
@@ -113,7 +113,7 @@ export const SDK_RELEASE = {
|
|
|
113
113
|
// runtime intentionally no longer compiles at publish or launch time.
|
|
114
114
|
// 0.1.242 removes the retired Workers/ESM compiler, generated bundles, and
|
|
115
115
|
// deploy-time artifact migration. Play authoring now has one CJS contract.
|
|
116
|
-
version: '0.1.
|
|
116
|
+
version: '0.1.251',
|
|
117
117
|
apiContract: '2026-07-cjs-absurd-only-play-runtime-hard-cutover',
|
|
118
118
|
supportPolicy: {
|
|
119
119
|
minimumSupported: '0.1.53',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PlayCompilerManifest } from '../../shared_libs/plays/compiler-manifest';
|
|
2
|
+
import type { PlayRuntimeSelection } from '../../shared_libs/play-runtime/runtime-environment';
|
|
2
3
|
import type {
|
|
3
4
|
DeeplineToolCategory,
|
|
4
5
|
PlayBootstrapFinderKind,
|
|
@@ -1226,6 +1227,8 @@ export interface StartPlayRunRequest {
|
|
|
1226
1227
|
profile?: string;
|
|
1227
1228
|
/** Optional per-run provider execution mode for eval/smoke runs. */
|
|
1228
1229
|
integrationMode?: 'live' | 'eval_stub' | 'fixture';
|
|
1230
|
+
/** Internal runtime estate selection. The app host remains unchanged. */
|
|
1231
|
+
runtime?: PlayRuntimeSelection;
|
|
1229
1232
|
/** Internal/dev-only runtime policy overrides for black-box durability tests. */
|
|
1230
1233
|
testPolicyOverrides?: Record<string, unknown>;
|
|
1231
1234
|
}
|
|
@@ -1542,13 +1542,17 @@ export class PlayContextImpl {
|
|
|
1542
1542
|
this.#options.workflowId &&
|
|
1543
1543
|
this.#options.runId
|
|
1544
1544
|
) {
|
|
1545
|
-
const
|
|
1546
|
-
|
|
1547
|
-
|
|
1545
|
+
const url = `${this.#options.baseUrl.replace(/\/$/, '')}${PLAY_RUNTIME_API_COMPAT_PATH}`;
|
|
1546
|
+
const requestId = `ctx-secret-${crypto.randomUUID()}`;
|
|
1547
|
+
const startedAt = Date.now();
|
|
1548
|
+
let response: Response;
|
|
1549
|
+
try {
|
|
1550
|
+
response = await fetch(url, {
|
|
1548
1551
|
method: 'POST',
|
|
1549
1552
|
headers: {
|
|
1550
1553
|
Authorization: `Bearer ${this.#options.executorToken}`,
|
|
1551
1554
|
'Content-Type': 'application/json',
|
|
1555
|
+
'x-deepline-request-id': requestId,
|
|
1552
1556
|
...(await this.vercelProtectionHeaders()),
|
|
1553
1557
|
},
|
|
1554
1558
|
body: JSON.stringify({
|
|
@@ -1556,11 +1560,89 @@ export class PlayContextImpl {
|
|
|
1556
1560
|
name: auth.secret.name,
|
|
1557
1561
|
playName: this.#options.playName,
|
|
1558
1562
|
}),
|
|
1559
|
-
}
|
|
1560
|
-
)
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1563
|
+
});
|
|
1564
|
+
} catch (error) {
|
|
1565
|
+
const diagnostic = describeTransportError(error);
|
|
1566
|
+
this.log(
|
|
1567
|
+
`[runtime.secret_resolution_failure] ${JSON.stringify({
|
|
1568
|
+
stage: 'transport',
|
|
1569
|
+
secret_name: auth.secret.name,
|
|
1570
|
+
gateway_origin: transportGatewayOriginForDiagnostic(url),
|
|
1571
|
+
request_id: requestId,
|
|
1572
|
+
elapsed_ms: Date.now() - startedAt,
|
|
1573
|
+
error: diagnostic,
|
|
1574
|
+
})}`,
|
|
1575
|
+
);
|
|
1576
|
+
throw new Error(
|
|
1577
|
+
`Secret ${auth.secret.name} resolution transport failed (request_id=${requestId}): ${diagnostic.message ?? 'unknown transport error'}`,
|
|
1578
|
+
);
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
const responseDiagnostic = {
|
|
1582
|
+
stage: response.ok ? 'invalid_response' : 'http_response',
|
|
1583
|
+
secret_name: auth.secret.name,
|
|
1584
|
+
gateway_origin: transportGatewayOriginForDiagnostic(url),
|
|
1585
|
+
request_id: requestId,
|
|
1586
|
+
elapsed_ms: Date.now() - startedAt,
|
|
1587
|
+
http_status: response.status,
|
|
1588
|
+
status_text: response.statusText || null,
|
|
1589
|
+
content_type: response.headers.get('content-type'),
|
|
1590
|
+
server: response.headers.get('server'),
|
|
1591
|
+
vercel_request_id:
|
|
1592
|
+
response.headers.get('x-vercel-id') ??
|
|
1593
|
+
response.headers.get('x-vercel-request-id'),
|
|
1594
|
+
response_request_id: response.headers.get('x-deepline-request-id'),
|
|
1595
|
+
error_code: response.headers.get('x-deepline-error-code'),
|
|
1596
|
+
retry_after: response.headers.get('retry-after'),
|
|
1597
|
+
};
|
|
1598
|
+
if (!response.ok) {
|
|
1599
|
+
this.log(
|
|
1600
|
+
`[runtime.secret_resolution_failure] ${JSON.stringify(responseDiagnostic)}`,
|
|
1601
|
+
);
|
|
1602
|
+
throw new Error(
|
|
1603
|
+
`Secret ${auth.secret.name} is not available to this run (resolution status=${response.status}, request_id=${requestId}).`,
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
let payload: unknown;
|
|
1608
|
+
try {
|
|
1609
|
+
payload = await response.json();
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
this.log(
|
|
1612
|
+
`[runtime.secret_resolution_failure] ${JSON.stringify({
|
|
1613
|
+
...responseDiagnostic,
|
|
1614
|
+
response_kind: 'invalid_json',
|
|
1615
|
+
parse_error_name:
|
|
1616
|
+
error instanceof Error ? error.name : typeof error,
|
|
1617
|
+
})}`,
|
|
1618
|
+
);
|
|
1619
|
+
throw new Error(
|
|
1620
|
+
`Secret ${auth.secret.name} is not available to this run (invalid resolution response, request_id=${requestId}).`,
|
|
1621
|
+
);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
value =
|
|
1625
|
+
payload &&
|
|
1626
|
+
typeof payload === 'object' &&
|
|
1627
|
+
!Array.isArray(payload) &&
|
|
1628
|
+
typeof (payload as { value?: unknown }).value === 'string'
|
|
1629
|
+
? (payload as { value: string }).value
|
|
1630
|
+
: null;
|
|
1631
|
+
if (!value) {
|
|
1632
|
+
this.log(
|
|
1633
|
+
`[runtime.secret_resolution_failure] ${JSON.stringify({
|
|
1634
|
+
...responseDiagnostic,
|
|
1635
|
+
response_kind:
|
|
1636
|
+
payload === null
|
|
1637
|
+
? 'null'
|
|
1638
|
+
: Array.isArray(payload)
|
|
1639
|
+
? 'array'
|
|
1640
|
+
: typeof payload,
|
|
1641
|
+
})}`,
|
|
1642
|
+
);
|
|
1643
|
+
throw new Error(
|
|
1644
|
+
`Secret ${auth.secret.name} is not available to this run (missing resolution value, request_id=${requestId}).`,
|
|
1645
|
+
);
|
|
1564
1646
|
}
|
|
1565
1647
|
} else {
|
|
1566
1648
|
throw new Error(
|
|
@@ -6997,7 +7079,9 @@ export class PlayContextImpl {
|
|
|
6997
7079
|
Object.fromEntries(response.headers.entries()),
|
|
6998
7080
|
) as Record<string, string>,
|
|
6999
7081
|
bodyText: redactedBodyText,
|
|
7000
|
-
json: this.secretRedactor.
|
|
7082
|
+
json: this.secretRedactor.redactKnownSecrets(
|
|
7083
|
+
parseJsonOrNull(bodyText),
|
|
7084
|
+
),
|
|
7001
7085
|
};
|
|
7002
7086
|
|
|
7003
7087
|
this.checkpoint.resolvedBoundaries = {
|
|
@@ -28,6 +28,13 @@ export const WORKER_CALLBACK_URL_OVERRIDE_HEADER =
|
|
|
28
28
|
'x-deepline-worker-callback-url';
|
|
29
29
|
export const RUNTIME_SCHEDULER_SCHEMA_OVERRIDE_HEADER =
|
|
30
30
|
'x-deepline-runtime-scheduler-schema';
|
|
31
|
+
/**
|
|
32
|
+
* CLI-to-app credential used only to authorize a typed runtime-environment
|
|
33
|
+
* selection. It is deliberately separate from the coordinator credential:
|
|
34
|
+
* selecting an approved environment must not grant access to runtime callbacks.
|
|
35
|
+
*/
|
|
36
|
+
export const RUNTIME_ENVIRONMENT_TOKEN_HEADER =
|
|
37
|
+
'x-deepline-runtime-environment-token';
|
|
31
38
|
/**
|
|
32
39
|
* Internal top-level `profile=absurd` launches, including deploy canaries, use
|
|
33
40
|
* this header to pin an exact candidate release before activation. Public API
|
|
@@ -27,10 +27,17 @@ export function daytonaClientOptions(apiKey: string): DaytonaClientOptions {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export function loadDaytonaRequiredConfig(
|
|
31
|
-
|
|
30
|
+
export function loadDaytonaRequiredConfig(
|
|
31
|
+
options: {
|
|
32
|
+
environment?: 'preview' | 'production' | null;
|
|
33
|
+
} = {},
|
|
34
|
+
): DaytonaRequiredConfig {
|
|
35
|
+
const environment = options.environment ?? null;
|
|
36
|
+
const keyName =
|
|
37
|
+
environment === 'preview' ? 'DAYTONA_API_KEY_PREVIEW' : 'DAYTONA_API_KEY';
|
|
38
|
+
const apiKey = process.env[keyName]?.trim();
|
|
32
39
|
if (!apiKey) {
|
|
33
|
-
throw new Error(
|
|
40
|
+
throw new Error(`Missing required Daytona configuration: ${keyName}.`);
|
|
34
41
|
}
|
|
35
42
|
return {
|
|
36
43
|
apiKey,
|
|
@@ -53,6 +53,13 @@ const RUNTIME_RECEIPT_GATEWAY_TRANSPORT_RETRY_PATTERN =
|
|
|
53
53
|
/AppRuntimeApiTransportError: App runtime API transport exhausted action=(?:get|claim|mark|complete|fail|heartbeat|release|skip)_runtime_step_receipts?/i;
|
|
54
54
|
const RUNTIME_API_TRANSPORT_RETRY_PATTERN =
|
|
55
55
|
/\bRuntime API request to .+ failed before receiving a response:\s*(?:fetch failed|connection (?:terminated|timed out|closed|reset)|ECONNRESET|ETIMEDOUT|ECONNREFUSED)\b/i;
|
|
56
|
+
// The ready fence precedes customer play execution. A failed detached start is
|
|
57
|
+
// safe to retry only when the runner never wrote its ready marker or attempted
|
|
58
|
+
// the start acknowledgement, and Daytona has no exit evidence. This covers a
|
|
59
|
+
// transient Daytona control-plane blind spot where the accepted command cannot
|
|
60
|
+
// be inspected, while keeping post-start and crash failures terminal.
|
|
61
|
+
const RUNTIME_SANDBOX_UNSTARTED_RETRY_PATTERN =
|
|
62
|
+
/DaytonaRunnerInitializationError:\s*RUNTIME_SANDBOX_START_FAILED[\s\S]*Marker diagnosis:\s*ready_marker_unavailable[\s\S]*"readyMarkerObserved"\s*:\s*false[\s\S]*"startAcknowledgementAttempted"\s*:\s*false[\s\S]*"exitCode"\s*:\s*null[\s\S]*"exitCodeFile"\s*:\s*null/i;
|
|
56
63
|
const DAYTONA_INFRASTRUCTURE_RETRY_PATTERN =
|
|
57
64
|
/\b(?:Request failed with status code (?:408|429|500|502|503|504)|ECONNRESET|ECONNREFUSED|ETIMEDOUT|UND_ERR_CONNECT_TIMEOUT|fetch failed|socket hang up|Daytona sandbox create failed across|Daytona sandbox create did not complete)\b/i;
|
|
58
65
|
|
|
@@ -446,6 +453,7 @@ export function retryableRuntimeInitializationFailureReason(
|
|
|
446
453
|
| 'runtime_postgres_connect'
|
|
447
454
|
| 'runtime_api_transport'
|
|
448
455
|
| 'runtime_receipt_gateway_transport'
|
|
456
|
+
| 'runtime_sandbox_unstarted'
|
|
449
457
|
| null {
|
|
450
458
|
if (result.status !== 'failed') return null;
|
|
451
459
|
if (RUNTIME_POSTGRES_CONNECT_RETRY_PATTERN.test(result.error)) {
|
|
@@ -455,6 +463,13 @@ export function retryableRuntimeInitializationFailureReason(
|
|
|
455
463
|
return 'runtime_receipt_gateway_transport';
|
|
456
464
|
}
|
|
457
465
|
const rowsProcessed = Number(result.stats?.rowsProcessed ?? 0);
|
|
466
|
+
if (
|
|
467
|
+
rowsProcessed === 0 &&
|
|
468
|
+
result.steps.length === 0 &&
|
|
469
|
+
RUNTIME_SANDBOX_UNSTARTED_RETRY_PATTERN.test(result.error)
|
|
470
|
+
) {
|
|
471
|
+
return 'runtime_sandbox_unstarted';
|
|
472
|
+
}
|
|
458
473
|
if (
|
|
459
474
|
rowsProcessed === 0 &&
|
|
460
475
|
result.steps.length === 0 &&
|
|
@@ -624,6 +639,10 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
624
639
|
await callbacks?.onRuntimeResourceAcquired?.({
|
|
625
640
|
kind: 'daytona_sandbox',
|
|
626
641
|
sandboxId: sandbox.id,
|
|
642
|
+
daytonaEnvironment:
|
|
643
|
+
process.env.DEEPLINE_RUNTIME_ENVIRONMENT === 'preview'
|
|
644
|
+
? 'preview'
|
|
645
|
+
: 'production',
|
|
627
646
|
billingStartedAt: acquired.billingStartedAt,
|
|
628
647
|
billingEndedAt,
|
|
629
648
|
cpu: typeof sandbox.cpu === 'number' ? sandbox.cpu : null,
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
export type PlayRunnerRuntimeResource = {
|
|
13
13
|
kind: 'daytona_sandbox';
|
|
14
14
|
sandboxId: string;
|
|
15
|
+
daytonaEnvironment?: 'preview' | 'production';
|
|
15
16
|
billingStartedAt: number;
|
|
16
17
|
billingEndedAt?: number | null;
|
|
17
18
|
terminalReason?: RuntimeResourceTerminalReason | null;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const PLAY_RUNTIME_ENVIRONMENTS = ['preview'] as const;
|
|
2
|
+
|
|
3
|
+
export type PlayRuntimeEnvironment = (typeof PLAY_RUNTIME_ENVIRONMENTS)[number];
|
|
4
|
+
|
|
5
|
+
export type PlayRuntimeSelection = {
|
|
6
|
+
environment: 'preview';
|
|
7
|
+
/** Caller-named isolation scope inside a remote runtime environment. */
|
|
8
|
+
namespace: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const PLAY_RUNTIME_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]{0,30}$/;
|
|
12
|
+
|
|
13
|
+
export function normalizePlayRuntimeNamespace(value: unknown): string | null {
|
|
14
|
+
if (typeof value !== 'string') return null;
|
|
15
|
+
const normalized = value.trim();
|
|
16
|
+
return PLAY_RUNTIME_NAMESPACE_PATTERN.test(normalized) ? normalized : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Validate the selector as one value so the SDK and server cannot drift. */
|
|
20
|
+
export function normalizePlayRuntimeSelection(
|
|
21
|
+
value: unknown,
|
|
22
|
+
): PlayRuntimeSelection | null {
|
|
23
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
24
|
+
const record = value as Record<string, unknown>;
|
|
25
|
+
if (
|
|
26
|
+
Object.keys(record).some(
|
|
27
|
+
(key) => key !== 'environment' && key !== 'namespace',
|
|
28
|
+
) ||
|
|
29
|
+
record.environment !== 'preview'
|
|
30
|
+
) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const namespace = normalizePlayRuntimeNamespace(record.namespace);
|
|
34
|
+
return namespace ? { environment: 'preview', namespace } : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizePlayRuntimeEnvironment(
|
|
38
|
+
value: unknown,
|
|
39
|
+
): PlayRuntimeEnvironment | null {
|
|
40
|
+
return typeof value === 'string' &&
|
|
41
|
+
PLAY_RUNTIME_ENVIRONMENTS.includes(value as PlayRuntimeEnvironment)
|
|
42
|
+
? (value as PlayRuntimeEnvironment)
|
|
43
|
+
: null;
|
|
44
|
+
}
|
|
@@ -76,6 +76,7 @@ export function redactSecretLikeString(value: string): string {
|
|
|
76
76
|
export type SecretRedactionContext = {
|
|
77
77
|
register(value: string): void;
|
|
78
78
|
redactString(value: string): string;
|
|
79
|
+
redactKnownSecrets<T>(value: T): T;
|
|
79
80
|
redact<T>(value: T): T;
|
|
80
81
|
};
|
|
81
82
|
|
|
@@ -129,9 +130,26 @@ export function createSecretRedactionContext(
|
|
|
129
130
|
return value;
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
function redactKnownSecrets<T>(value: T): T {
|
|
134
|
+
if (typeof value === 'string') return redactString(value) as T;
|
|
135
|
+
if (Array.isArray(value)) {
|
|
136
|
+
return value.map((entry) => redactKnownSecrets(entry)) as T;
|
|
137
|
+
}
|
|
138
|
+
if (value && typeof value === 'object') {
|
|
139
|
+
return Object.fromEntries(
|
|
140
|
+
Object.entries(value).map(([key, entry]) => [
|
|
141
|
+
key,
|
|
142
|
+
redactKnownSecrets(entry),
|
|
143
|
+
]),
|
|
144
|
+
) as T;
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
132
149
|
return {
|
|
133
150
|
register,
|
|
134
151
|
redactString,
|
|
152
|
+
redactKnownSecrets,
|
|
135
153
|
redact,
|
|
136
154
|
};
|
|
137
155
|
}
|