deepline 0.1.306 → 0.1.308
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 +11 -24
- package/dist/bundling-sources/sdk/src/errors.ts +3 -1
- package/dist/bundling-sources/sdk/src/http.ts +16 -24
- package/dist/bundling-sources/sdk/src/play.ts +26 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- 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 +66 -11
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +13 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +12 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +21 -9
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +5 -1
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +17 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runtime-limits.ts +102 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +56 -9
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +56 -0
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +6 -0
- package/dist/cli/index.js +197 -42
- package/dist/cli/index.mjs +197 -42
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +33 -30
- package/dist/index.mjs +33 -30
- package/dist/plays/bundle-play-file.d.mts +8 -0
- package/dist/plays/bundle-play-file.d.ts +8 -0
- package/dist/plays/bundle-play-file.mjs +42 -0
- package/package.json +1 -1
|
@@ -1785,7 +1785,6 @@ export class DeeplineClient {
|
|
|
1785
1785
|
},
|
|
1786
1786
|
headers,
|
|
1787
1787
|
{
|
|
1788
|
-
forbiddenAsApiError: true,
|
|
1789
1788
|
timeout: options?.timeout ?? resolveToolExecuteTimeoutMs(toolId, input),
|
|
1790
1789
|
maxRetries: options?.maxRetries ?? 0,
|
|
1791
1790
|
exactUrlOnly: true,
|
|
@@ -1848,17 +1847,11 @@ export class DeeplineClient {
|
|
|
1848
1847
|
async repairIngestionStorage(input?: {
|
|
1849
1848
|
provider?: string;
|
|
1850
1849
|
}): Promise<IngestionStorageRepairResult> {
|
|
1851
|
-
// forbiddenAsApiError: the repair route returns a meaningful 403 for
|
|
1852
|
-
// non-org-admin callers. Without it, http.ts collapses 403 into a generic
|
|
1853
|
-
// AuthError (statusCode 401) and the CLI's "requires org admin" branch
|
|
1854
|
-
// could never fire.
|
|
1855
1850
|
return this.http.post<IngestionStorageRepairResult>(
|
|
1856
1851
|
'/api/v2/ingestion/repair',
|
|
1857
1852
|
{
|
|
1858
1853
|
...(input?.provider ? { provider: input.provider } : {}),
|
|
1859
1854
|
},
|
|
1860
|
-
undefined,
|
|
1861
|
-
{ forbiddenAsApiError: true },
|
|
1862
1855
|
);
|
|
1863
1856
|
}
|
|
1864
1857
|
|
|
@@ -2080,7 +2073,7 @@ export class DeeplineClient {
|
|
|
2080
2073
|
compilerManifest,
|
|
2081
2074
|
},
|
|
2082
2075
|
undefined,
|
|
2083
|
-
{
|
|
2076
|
+
{ retryApiErrors: true },
|
|
2084
2077
|
);
|
|
2085
2078
|
}
|
|
2086
2079
|
|
|
@@ -2124,7 +2117,7 @@ export class DeeplineClient {
|
|
|
2124
2117
|
'/api/v2/plays/artifacts',
|
|
2125
2118
|
{ artifacts },
|
|
2126
2119
|
undefined,
|
|
2127
|
-
{
|
|
2120
|
+
{ retryApiErrors: true },
|
|
2128
2121
|
);
|
|
2129
2122
|
}
|
|
2130
2123
|
const compiledArtifacts = await mapWithConcurrency(
|
|
@@ -2166,7 +2159,7 @@ export class DeeplineClient {
|
|
|
2166
2159
|
artifacts: chunk,
|
|
2167
2160
|
},
|
|
2168
2161
|
undefined,
|
|
2169
|
-
{
|
|
2162
|
+
{ retryApiErrors: true },
|
|
2170
2163
|
),
|
|
2171
2164
|
);
|
|
2172
2165
|
}
|
|
@@ -3612,8 +3605,6 @@ export class DeeplineClient {
|
|
|
3612
3605
|
return this.http.post<PublishPlayVersionResult>(
|
|
3613
3606
|
`/api/v2/plays/${encodedName}/live`,
|
|
3614
3607
|
request,
|
|
3615
|
-
undefined,
|
|
3616
|
-
{ forbiddenAsApiError: true },
|
|
3617
3608
|
);
|
|
3618
3609
|
}
|
|
3619
3610
|
|
|
@@ -3935,8 +3926,7 @@ export class DeeplineClient {
|
|
|
3935
3926
|
* `client.monitors.status()`.
|
|
3936
3927
|
*/
|
|
3937
3928
|
async getMonitorsAccess(): Promise<MonitorsAccessStatus> {
|
|
3938
|
-
//
|
|
3939
|
-
// denied cases, so a 403 would be a real auth failure, not a denial body.
|
|
3929
|
+
// The endpoint answers 200 in both the granted and denied cases.
|
|
3940
3930
|
const payload = await this.http.request<MonitorsAccessStatus>(
|
|
3941
3931
|
'/api/v2/monitors/access',
|
|
3942
3932
|
{ method: 'GET' },
|
|
@@ -3989,7 +3979,7 @@ export class DeeplineClient {
|
|
|
3989
3979
|
const suffix = query ? `?${query}` : '';
|
|
3990
3980
|
return this.http.request<MonitorsAvailableResult>(
|
|
3991
3981
|
`/api/v2/monitors/tools${suffix}`,
|
|
3992
|
-
{ method: 'GET'
|
|
3982
|
+
{ method: 'GET' },
|
|
3993
3983
|
);
|
|
3994
3984
|
}
|
|
3995
3985
|
|
|
@@ -4000,7 +3990,6 @@ export class DeeplineClient {
|
|
|
4000
3990
|
return this.http.request<MonitorCheckResult>('/api/v2/monitors/check', {
|
|
4001
3991
|
method: 'POST',
|
|
4002
3992
|
body: definition,
|
|
4003
|
-
forbiddenAsApiError: true,
|
|
4004
3993
|
});
|
|
4005
3994
|
}
|
|
4006
3995
|
|
|
@@ -4019,13 +4008,11 @@ export class DeeplineClient {
|
|
|
4019
4008
|
return this.http.request<MonitorDeployResult>('/api/v2/monitors/check', {
|
|
4020
4009
|
method: 'POST',
|
|
4021
4010
|
body: definition,
|
|
4022
|
-
forbiddenAsApiError: true,
|
|
4023
4011
|
});
|
|
4024
4012
|
}
|
|
4025
4013
|
return this.http.request<MonitorDeployResult>('/api/v2/monitors/deploy', {
|
|
4026
4014
|
method: 'POST',
|
|
4027
4015
|
body: definition,
|
|
4028
|
-
forbiddenAsApiError: true,
|
|
4029
4016
|
});
|
|
4030
4017
|
}
|
|
4031
4018
|
|
|
@@ -4045,7 +4032,7 @@ export class DeeplineClient {
|
|
|
4045
4032
|
const suffix = query ? `?${query}` : '';
|
|
4046
4033
|
return this.http.request<MonitorsListResult>(
|
|
4047
4034
|
`/api/v2/monitors/deployed${suffix}`,
|
|
4048
|
-
{ method: 'GET'
|
|
4035
|
+
{ method: 'GET' },
|
|
4049
4036
|
);
|
|
4050
4037
|
}
|
|
4051
4038
|
|
|
@@ -4053,7 +4040,7 @@ export class DeeplineClient {
|
|
|
4053
4040
|
async getMonitor(key: string): Promise<MonitorDetail> {
|
|
4054
4041
|
return this.http.request<MonitorDetail>(
|
|
4055
4042
|
`/api/v2/monitors/deployed/${encodeURIComponent(key)}`,
|
|
4056
|
-
{ method: 'GET'
|
|
4043
|
+
{ method: 'GET' },
|
|
4057
4044
|
);
|
|
4058
4045
|
}
|
|
4059
4046
|
|
|
@@ -4061,7 +4048,7 @@ export class DeeplineClient {
|
|
|
4061
4048
|
async getMonitorDependents(key: string): Promise<MonitorDependents> {
|
|
4062
4049
|
return this.http.request<MonitorDependents>(
|
|
4063
4050
|
`/api/v2/monitors/deployed/${encodeURIComponent(key)}/dependents`,
|
|
4064
|
-
{ method: 'GET'
|
|
4051
|
+
{ method: 'GET' },
|
|
4065
4052
|
);
|
|
4066
4053
|
}
|
|
4067
4054
|
|
|
@@ -4072,7 +4059,7 @@ export class DeeplineClient {
|
|
|
4072
4059
|
): Promise<MonitorUpdateResult> {
|
|
4073
4060
|
return this.http.request<MonitorUpdateResult>(
|
|
4074
4061
|
`/api/v2/monitors/deployed/${encodeURIComponent(key)}`,
|
|
4075
|
-
{ method: 'PATCH', body: patch
|
|
4062
|
+
{ method: 'PATCH', body: patch },
|
|
4076
4063
|
);
|
|
4077
4064
|
}
|
|
4078
4065
|
|
|
@@ -4091,7 +4078,7 @@ export class DeeplineClient {
|
|
|
4091
4078
|
const query = params.toString();
|
|
4092
4079
|
return this.http.request<MonitorDeleteResult>(
|
|
4093
4080
|
`/api/v2/monitors/deployed/${encodeURIComponent(key)}${query ? `?${query}` : ''}`,
|
|
4094
|
-
{ method: 'DELETE'
|
|
4081
|
+
{ method: 'DELETE' },
|
|
4095
4082
|
);
|
|
4096
4083
|
}
|
|
4097
4084
|
|
|
@@ -4106,7 +4093,7 @@ export class DeeplineClient {
|
|
|
4106
4093
|
const query = options?.dryRun ? '?dry_run=true' : '';
|
|
4107
4094
|
return this.http.request<MonitorReactivateResult>(
|
|
4108
4095
|
`/api/v2/monitors/deployed/${encodeURIComponent(key)}/reactivate${query}`,
|
|
4109
|
-
{ method: 'POST', body: {}
|
|
4096
|
+
{ method: 'POST', body: {} },
|
|
4110
4097
|
);
|
|
4111
4098
|
}
|
|
4112
4099
|
|
|
@@ -30,7 +30,9 @@ export {
|
|
|
30
30
|
/**
|
|
31
31
|
* Thrown when the API rejects the request due to an invalid or missing API key.
|
|
32
32
|
*
|
|
33
|
-
* This maps to HTTP 401
|
|
33
|
+
* This maps to HTTP 401 responses. HTTP 403 means the caller was authenticated
|
|
34
|
+
* but lacks permission, so the SDK preserves the server's API error instead.
|
|
35
|
+
* The SDK never retries auth errors —
|
|
34
36
|
* they fail immediately.
|
|
35
37
|
*
|
|
36
38
|
* Fix: run `deepline auth register` to obtain a valid key, or pass one via
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - **Max attempts**: `1 + maxRetries` (default: 4 total attempts)
|
|
10
10
|
* - **Backoff**: exponential — 1s, 2s, 4s, 8s, ... capped at 30s
|
|
11
11
|
* - **Retryable**: network errors, timeouts, HTTP 429 (rate limit)
|
|
12
|
-
* - **Not retryable**: HTTP 401
|
|
12
|
+
* - **Not retryable**: HTTP 401 (auth error), HTTP 403 and other 4xx API errors
|
|
13
13
|
*
|
|
14
14
|
* ## Localhost failover
|
|
15
15
|
*
|
|
@@ -61,14 +61,6 @@ interface RequestOptions {
|
|
|
61
61
|
headers?: Record<string, string>;
|
|
62
62
|
/** Per-request timeout override in milliseconds. */
|
|
63
63
|
timeout?: number;
|
|
64
|
-
/**
|
|
65
|
-
* Treat HTTP 403 as a regular API error instead of a generic {@link AuthError}.
|
|
66
|
-
* Use for endpoints that return 403 with a meaningful server error message
|
|
67
|
-
* (e.g. feature-flagged-off billing subscription checkout) so the server
|
|
68
|
-
* message is preserved and surfaced loudly. A provider-originated HTTP 401
|
|
69
|
-
* remains a regular API error when its response envelope says so.
|
|
70
|
-
*/
|
|
71
|
-
forbiddenAsApiError?: boolean;
|
|
72
64
|
/**
|
|
73
65
|
* Retry HTTP 408/425/5xx JSON API responses. Use only for idempotent requests;
|
|
74
66
|
* most mutating API calls must fail loudly after the first server response.
|
|
@@ -437,11 +429,10 @@ export class HttpClient {
|
|
|
437
429
|
}
|
|
438
430
|
|
|
439
431
|
if (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
(response.status === 403 && !options?.forbiddenAsApiError)
|
|
432
|
+
response.status === 401 &&
|
|
433
|
+
!isProviderOriginatedHttpError(parsed)
|
|
443
434
|
) {
|
|
444
|
-
throw new AuthError();
|
|
435
|
+
throw new AuthError(apiErrorMessage(parsed, response.status));
|
|
445
436
|
}
|
|
446
437
|
if (!response.ok) {
|
|
447
438
|
const retryableApiError =
|
|
@@ -576,10 +567,17 @@ export class HttpClient {
|
|
|
576
567
|
* @typeParam T - Expected response body type
|
|
577
568
|
* @param path - API path (e.g. `"/api/v2/tools"`)
|
|
578
569
|
*/
|
|
579
|
-
async get<T = unknown>(
|
|
570
|
+
async get<T = unknown>(
|
|
571
|
+
path: string,
|
|
572
|
+
options?: Pick<
|
|
573
|
+
RequestOptions,
|
|
574
|
+
'retryApiErrors' | 'timeout' | 'maxRetries' | 'exactUrlOnly' | 'toolId'
|
|
575
|
+
>,
|
|
576
|
+
): Promise<T> {
|
|
580
577
|
return this.request<T>(path, {
|
|
581
578
|
method: 'GET',
|
|
582
579
|
retryApiErrors: true,
|
|
580
|
+
...options,
|
|
583
581
|
});
|
|
584
582
|
}
|
|
585
583
|
|
|
@@ -614,11 +612,10 @@ export class HttpClient {
|
|
|
614
612
|
const body = await response.text();
|
|
615
613
|
const parsed = parseResponseBody(body);
|
|
616
614
|
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
response.status === 403
|
|
615
|
+
response.status === 401 &&
|
|
616
|
+
!isProviderOriginatedHttpError(parsed)
|
|
620
617
|
) {
|
|
621
|
-
throw new AuthError();
|
|
618
|
+
throw new AuthError(apiErrorMessage(parsed, response.status));
|
|
622
619
|
}
|
|
623
620
|
const htmlError = detectHtmlErrorBody(
|
|
624
621
|
body,
|
|
@@ -681,12 +678,7 @@ export class HttpClient {
|
|
|
681
678
|
headers?: Record<string, string>,
|
|
682
679
|
options?: Pick<
|
|
683
680
|
RequestOptions,
|
|
684
|
-
| '
|
|
685
|
-
| 'retryApiErrors'
|
|
686
|
-
| 'timeout'
|
|
687
|
-
| 'maxRetries'
|
|
688
|
-
| 'exactUrlOnly'
|
|
689
|
-
| 'toolId'
|
|
681
|
+
'retryApiErrors' | 'timeout' | 'maxRetries' | 'exactUrlOnly' | 'toolId'
|
|
690
682
|
>,
|
|
691
683
|
): Promise<T> {
|
|
692
684
|
return this.request<T>(path, {
|
|
@@ -198,6 +198,17 @@ export type PlayBindings = {
|
|
|
198
198
|
/** Stop the run before a billed action would push total run credits above this cap. */
|
|
199
199
|
maxCreditsPerRun?: number;
|
|
200
200
|
};
|
|
201
|
+
/** Requested sandbox envelope; the server enforces the final allowed limits. */
|
|
202
|
+
runtime?: {
|
|
203
|
+
/** Duration such as `"90m"` or `"2h"`. */
|
|
204
|
+
timeout?: string;
|
|
205
|
+
/** Memory such as `"4GiB"`. */
|
|
206
|
+
memory?: string;
|
|
207
|
+
/** Whole vCPU count. */
|
|
208
|
+
cpu?: number;
|
|
209
|
+
/** Disk such as `"10GiB"`. */
|
|
210
|
+
disk?: string;
|
|
211
|
+
};
|
|
201
212
|
/** Webhook trigger with optional HMAC signature verification. */
|
|
202
213
|
webhook?: {
|
|
203
214
|
hmac?: {
|
|
@@ -1193,6 +1204,8 @@ export type DefinePlayConfig<TInput, TOutput extends PlayReturnObject> = {
|
|
|
1193
1204
|
bindings?: PlayBindings;
|
|
1194
1205
|
/** Billing options. */
|
|
1195
1206
|
billing?: PlayBindings['billing'];
|
|
1207
|
+
/** Requested sandbox envelope; the server enforces the final allowed limits. */
|
|
1208
|
+
runtime?: PlayBindings['runtime'];
|
|
1196
1209
|
/** Runtime compatibility override. Omit for the current typed contract. */
|
|
1197
1210
|
compatibility?: PlayBindings['compatibility'];
|
|
1198
1211
|
};
|
|
@@ -1354,6 +1367,8 @@ export type DefinedPlay<TInput, TOutput extends PlayReturnObject> = ((
|
|
|
1354
1367
|
DeeplineNamedPlay<TInput, TOutput> & {
|
|
1355
1368
|
/** Optional trigger bindings (cron, webhook). */
|
|
1356
1369
|
readonly bindings?: PlayBindings;
|
|
1370
|
+
/** Requested sandbox envelope. */
|
|
1371
|
+
readonly runtime?: PlayBindings['runtime'];
|
|
1357
1372
|
/** Runtime compatibility explicitly selected by the author. */
|
|
1358
1373
|
readonly compatibility?: PlayBindings['compatibility'];
|
|
1359
1374
|
/** The play's name (same as `.name`). */
|
|
@@ -1366,6 +1381,7 @@ type PlayMetadata = {
|
|
|
1366
1381
|
bindings?: PlayBindings;
|
|
1367
1382
|
inputSchema?: Record<string, unknown>;
|
|
1368
1383
|
billing?: PlayBindings['billing'];
|
|
1384
|
+
runtime?: PlayBindings['runtime'];
|
|
1369
1385
|
compatibility?: PlayBindings['compatibility'];
|
|
1370
1386
|
};
|
|
1371
1387
|
|
|
@@ -2124,6 +2140,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
2124
2140
|
description: maybeBindings?.description,
|
|
2125
2141
|
inputSchema: undefined,
|
|
2126
2142
|
billing: maybeBindings?.billing,
|
|
2143
|
+
runtime: maybeBindings?.runtime,
|
|
2127
2144
|
compatibility: maybeBindings?.compatibility,
|
|
2128
2145
|
}
|
|
2129
2146
|
: {
|
|
@@ -2133,6 +2150,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
2133
2150
|
description: nameOrConfig.description,
|
|
2134
2151
|
inputSchema: nameOrConfig.input.schema,
|
|
2135
2152
|
billing: nameOrConfig.billing,
|
|
2153
|
+
runtime: nameOrConfig.runtime ?? nameOrConfig.bindings?.runtime,
|
|
2136
2154
|
compatibility:
|
|
2137
2155
|
nameOrConfig.compatibility ?? nameOrConfig.bindings?.compatibility,
|
|
2138
2156
|
};
|
|
@@ -2142,6 +2160,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
2142
2160
|
const description = config.description?.trim();
|
|
2143
2161
|
const billing = config.billing;
|
|
2144
2162
|
const inputSchema = config.inputSchema;
|
|
2163
|
+
const runtime = config.runtime;
|
|
2145
2164
|
const compatibility = config.compatibility;
|
|
2146
2165
|
if (typeof fn !== 'function') {
|
|
2147
2166
|
throw new Error('definePlay(...) requires an async run function.');
|
|
@@ -2176,6 +2195,7 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
2176
2195
|
...(bindings ? { bindings } : {}),
|
|
2177
2196
|
...(inputSchema ? { inputSchema } : {}),
|
|
2178
2197
|
...(billing ? { billing } : {}),
|
|
2198
|
+
...(runtime ? { runtime } : {}),
|
|
2179
2199
|
...(compatibility ? { compatibility } : {}),
|
|
2180
2200
|
};
|
|
2181
2201
|
const play = fn as DefinedPlay<TInput, TOutput>;
|
|
@@ -2200,6 +2220,12 @@ export function definePlay<TInput, TOutput extends PlayReturnObject>(
|
|
|
2200
2220
|
configurable: false,
|
|
2201
2221
|
writable: false,
|
|
2202
2222
|
});
|
|
2223
|
+
Object.defineProperty(play, 'runtime', {
|
|
2224
|
+
value: runtime,
|
|
2225
|
+
enumerable: true,
|
|
2226
|
+
configurable: false,
|
|
2227
|
+
writable: false,
|
|
2228
|
+
});
|
|
2203
2229
|
|
|
2204
2230
|
Object.defineProperty(play, 'compatibility', {
|
|
2205
2231
|
value: compatibility,
|
|
@@ -157,7 +157,7 @@ export const SDK_RELEASE = {
|
|
|
157
157
|
// 0.1.253 makes play-page browser opening opt-in and retires --no-open.
|
|
158
158
|
// 0.1.254 removes the internal operations tree from the published SDK CLI.
|
|
159
159
|
// Operators use the checkout-local deepline-admin binary instead.
|
|
160
|
-
version: '0.1.
|
|
160
|
+
version: '0.1.308',
|
|
161
161
|
contracts: {
|
|
162
162
|
api: {
|
|
163
163
|
name: 'sdk-http-api',
|
|
@@ -11,6 +11,7 @@ import type { PlayLiveEventSource } from './live-events';
|
|
|
11
11
|
import type { PreloadedRuntimeDbSession } from './db-session';
|
|
12
12
|
import type { PacingRule } from './governor/rate-state-backend';
|
|
13
13
|
import type { GovernanceSnapshot } from './governor/governor';
|
|
14
|
+
import type { PlaySandboxRuntimeLimits } from './sandbox-runtime-limits';
|
|
14
15
|
import type { PlayRunInputPayload } from './play-input';
|
|
15
16
|
import type { PlayRunFailureDetails } from './run-failure';
|
|
16
17
|
import type { ToolExecutionErrorSchemaVersion } from '../tool-execution-error';
|
|
@@ -169,6 +170,8 @@ export interface PlayRunnerContextConfig {
|
|
|
169
170
|
*/
|
|
170
171
|
runnerPushExecution?: RunnerPushExecutionConfig | null;
|
|
171
172
|
governance?: GovernanceSnapshot | null;
|
|
173
|
+
/** Immutable resource envelope resolved at launch. */
|
|
174
|
+
sandboxRuntimeLimits?: PlaySandboxRuntimeLimits | null;
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
/**
|
package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts
CHANGED
|
@@ -5,20 +5,17 @@ import type {
|
|
|
5
5
|
} from '@shared_libs/play-runtime/protocol';
|
|
6
6
|
import { isIsolatedRuntimeSchedulerSchema } from '@shared_libs/play-runtime/runtime-scheduler-topology';
|
|
7
7
|
import { PLAY_RUNNER_TIMEOUT_SECONDS } from '@shared_libs/play-runtime/runtime-constants';
|
|
8
|
+
import { resolvePlaySandboxRuntimeLimits } from '@shared_libs/play-runtime/sandbox-runtime-limits';
|
|
8
9
|
|
|
9
10
|
const DAYTONA_CREATE_TIMEOUT_SECONDS = 10;
|
|
10
11
|
const DAYTONA_CREATE_RETRY_DELAYS_MS = [0, 500, 1_500] as const;
|
|
11
12
|
// Explicit runner deadline + scheduler GC own the normal lifecycle. Daytona's
|
|
12
13
|
// inactivity stop is a wider crash backstop measured from sandbox creation, so
|
|
13
14
|
// setup time cannot consume the terminal-flush grace.
|
|
14
|
-
const DAYTONA_AUTO_STOP_INTERVAL_MINUTES = Math.ceil(
|
|
15
|
-
PLAY_RUNNER_TIMEOUT_SECONDS / 60,
|
|
16
|
-
);
|
|
17
15
|
const DAYTONA_AUTO_ARCHIVE_INTERVAL_MINUTES = 15;
|
|
18
16
|
const DAYTONA_SANDBOX_LABEL_SOURCE = 'deepline-play-runner';
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// control-plane round trip raises cold-start latency.
|
|
17
|
+
// Daytona's default image is the fast path. Non-standard resources are applied
|
|
18
|
+
// during acquisition, before customer code and before the billing clock starts.
|
|
22
19
|
export const DAYTONA_SANDBOX_CPU = 1;
|
|
23
20
|
export const DAYTONA_SANDBOX_MEMORY_GIB = 1;
|
|
24
21
|
export const DAYTONA_SANDBOX_DISK_GIB = 3;
|
|
@@ -176,6 +173,16 @@ async function createOneShotDaytonaSandbox(input: {
|
|
|
176
173
|
orgId: string;
|
|
177
174
|
context: DaytonaExecutionContext;
|
|
178
175
|
}): Promise<DaytonaSandbox> {
|
|
176
|
+
const limits = resolvePlaySandboxRuntimeLimits(
|
|
177
|
+
input.context.sandboxRuntimeLimits
|
|
178
|
+
? {
|
|
179
|
+
timeout: `${input.context.sandboxRuntimeLimits.timeoutSeconds / 60}m`,
|
|
180
|
+
memory: `${input.context.sandboxRuntimeLimits.memoryGiB}GiB`,
|
|
181
|
+
cpu: input.context.sandboxRuntimeLimits.cpu,
|
|
182
|
+
disk: `${input.context.sandboxRuntimeLimits.diskGiB}GiB`,
|
|
183
|
+
}
|
|
184
|
+
: null,
|
|
185
|
+
);
|
|
179
186
|
const orgId = normalizeLabelValue(input.orgId);
|
|
180
187
|
const workflowId = normalizeLabelValue(input.context.workflowId);
|
|
181
188
|
const playId = normalizeLabelValue(input.context.playId);
|
|
@@ -203,7 +210,9 @@ async function createOneShotDaytonaSandbox(input: {
|
|
|
203
210
|
{
|
|
204
211
|
labels,
|
|
205
212
|
ephemeral: true,
|
|
206
|
-
autoStopInterval:
|
|
213
|
+
autoStopInterval: Math.ceil(
|
|
214
|
+
(limits.timeoutSeconds + PLAY_RUNNER_TIMEOUT_SECONDS - 30 * 60) / 60,
|
|
215
|
+
),
|
|
207
216
|
autoArchiveInterval: DAYTONA_AUTO_ARCHIVE_INTERVAL_MINUTES,
|
|
208
217
|
// A non-empty `networkAllowList` IS the "block all egress except these
|
|
209
218
|
// CIDRs" control; Daytona rejects create when `networkBlockAll: true` is
|
|
@@ -277,6 +286,16 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
277
286
|
emitStage: DaytonaStageEmitter;
|
|
278
287
|
startedAt: number;
|
|
279
288
|
}): Promise<AcquiredDaytonaSandbox> {
|
|
289
|
+
const limits = resolvePlaySandboxRuntimeLimits(
|
|
290
|
+
input.context.sandboxRuntimeLimits
|
|
291
|
+
? {
|
|
292
|
+
timeout: `${input.context.sandboxRuntimeLimits.timeoutSeconds / 60}m`,
|
|
293
|
+
memory: `${input.context.sandboxRuntimeLimits.memoryGiB}GiB`,
|
|
294
|
+
cpu: input.context.sandboxRuntimeLimits.cpu,
|
|
295
|
+
disk: `${input.context.sandboxRuntimeLimits.diskGiB}GiB`,
|
|
296
|
+
}
|
|
297
|
+
: null,
|
|
298
|
+
);
|
|
280
299
|
input.emitStage('create:start');
|
|
281
300
|
const result = await createRetriedOneShotDaytonaSandbox(input);
|
|
282
301
|
const granted = {
|
|
@@ -285,15 +304,51 @@ async function acquireOneShotDaytonaSandbox(input: {
|
|
|
285
304
|
diskGiB: result.sandbox.disk,
|
|
286
305
|
gpu: result.sandbox.gpu ?? 0,
|
|
287
306
|
};
|
|
307
|
+
// Daytona's SDK only accepts resources when a custom image is supplied.
|
|
308
|
+
// We deliberately retain the provider's fast default image, then resize
|
|
309
|
+
// before any customer code or billing window begins.
|
|
310
|
+
if (
|
|
311
|
+
typeof result.sandbox.resize === 'function' &&
|
|
312
|
+
(granted.cpu !== limits.cpu ||
|
|
313
|
+
granted.memoryGiB !== limits.memoryGiB ||
|
|
314
|
+
granted.diskGiB !== limits.diskGiB)
|
|
315
|
+
) {
|
|
316
|
+
try {
|
|
317
|
+
if (granted.diskGiB !== limits.diskGiB) {
|
|
318
|
+
await result.sandbox.stop(60);
|
|
319
|
+
await result.sandbox.resize({
|
|
320
|
+
cpu: limits.cpu,
|
|
321
|
+
memory: limits.memoryGiB,
|
|
322
|
+
disk: limits.diskGiB,
|
|
323
|
+
});
|
|
324
|
+
await result.sandbox.start(60);
|
|
325
|
+
} else {
|
|
326
|
+
await result.sandbox.resize({
|
|
327
|
+
cpu: limits.cpu,
|
|
328
|
+
memory: limits.memoryGiB,
|
|
329
|
+
disk: limits.diskGiB,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
} catch (error) {
|
|
333
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
334
|
+
return await rejectAcquiredSandbox(
|
|
335
|
+
result.sandbox,
|
|
336
|
+
`Daytona sandbox resize failed before execution: ${message}.`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
granted.cpu = result.sandbox.cpu;
|
|
340
|
+
granted.memoryGiB = result.sandbox.memory;
|
|
341
|
+
granted.diskGiB = result.sandbox.disk;
|
|
342
|
+
}
|
|
288
343
|
if (
|
|
289
|
-
granted.cpu !==
|
|
290
|
-
granted.memoryGiB !==
|
|
291
|
-
granted.diskGiB !==
|
|
344
|
+
granted.cpu !== limits.cpu ||
|
|
345
|
+
granted.memoryGiB !== limits.memoryGiB ||
|
|
346
|
+
granted.diskGiB !== limits.diskGiB ||
|
|
292
347
|
granted.gpu !== DAYTONA_SANDBOX_GPU
|
|
293
348
|
) {
|
|
294
349
|
await rejectAcquiredSandbox(
|
|
295
350
|
result.sandbox,
|
|
296
|
-
`Daytona sandbox resource boundary mismatch: expected cpu=${
|
|
351
|
+
`Daytona sandbox resource boundary mismatch: expected cpu=${limits.cpu} memoryGiB=${limits.memoryGiB} diskGiB=${limits.diskGiB} gpu=${DAYTONA_SANDBOX_GPU}, granted cpu=${granted.cpu} memoryGiB=${granted.memoryGiB} diskGiB=${granted.diskGiB} gpu=${granted.gpu}`,
|
|
297
352
|
);
|
|
298
353
|
}
|
|
299
354
|
const configuredOrganizationId =
|
|
@@ -120,7 +120,11 @@ function nodeMaterializePayloadCommand(input: {
|
|
|
120
120
|
* Exported (with the builder) for the unit test that executes the script
|
|
121
121
|
* against a real local HTTP server.
|
|
122
122
|
*/
|
|
123
|
-
export function buildDaytonaCrashTerminalPusherSource(
|
|
123
|
+
export function buildDaytonaCrashTerminalPusherSource(
|
|
124
|
+
runtimeLimitSeconds = STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
|
|
125
|
+
): string {
|
|
126
|
+
const runtimeLimitMessage = `The play reached its configured ${runtimeLimitSeconds} second runtime limit and was stopped.`;
|
|
127
|
+
const runtimeLimitDetail = `${runtimeLimitMessage} Completed row state was preserved; run a smaller batch or continue from the persisted rows.`;
|
|
124
128
|
return `
|
|
125
129
|
const fs = require('node:fs');
|
|
126
130
|
const configPath = process.argv[2];
|
|
@@ -264,7 +268,7 @@ async function main() {
|
|
|
264
268
|
const sandboxOom =
|
|
265
269
|
sandboxKilled && (outputHasExplicitOomSignature() || cgroupOomKillObserved());
|
|
266
270
|
const synthesizedError = runtimeLimitExceeded
|
|
267
|
-
? 'RUNTIME_LIMIT_EXCEEDED:
|
|
271
|
+
? 'RUNTIME_LIMIT_EXCEEDED: ' + ${JSON.stringify(runtimeLimitMessage)}
|
|
268
272
|
: sandboxOom
|
|
269
273
|
? 'RUNTIME_SANDBOX_OOM: ' + ${JSON.stringify(RUNTIME_SANDBOX_OOM_MESSAGE)}
|
|
270
274
|
: sandboxKilled
|
|
@@ -280,7 +284,7 @@ async function main() {
|
|
|
280
284
|
? [{
|
|
281
285
|
code: 'RUNTIME_LIMIT_EXCEEDED',
|
|
282
286
|
phase: 'runtime',
|
|
283
|
-
message:
|
|
287
|
+
message: ${JSON.stringify(runtimeLimitDetail)},
|
|
284
288
|
retryable: false,
|
|
285
289
|
cause: synthesizedError,
|
|
286
290
|
}]
|
|
@@ -498,6 +502,9 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
498
502
|
materializedFiles: remoteMaterializedFiles,
|
|
499
503
|
context: remoteRuntimeContextForDaytona(input.config.context),
|
|
500
504
|
};
|
|
505
|
+
const runtimeLimitSeconds =
|
|
506
|
+
input.config.context.sandboxRuntimeLimits?.timeoutSeconds ??
|
|
507
|
+
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS;
|
|
501
508
|
|
|
502
509
|
const envelopeUpload = input.bundlePromise.then((bundle) =>
|
|
503
510
|
timedDaytonaUpload({
|
|
@@ -511,7 +518,8 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
511
518
|
artifactBundledCode: compactedArtifact.bundledCode,
|
|
512
519
|
artifactSourceMap: input.config.artifact.sourceMap,
|
|
513
520
|
config: remoteConfig,
|
|
514
|
-
crashPusherCode:
|
|
521
|
+
crashPusherCode:
|
|
522
|
+
buildDaytonaCrashTerminalPusherSource(runtimeLimitSeconds),
|
|
515
523
|
}),
|
|
516
524
|
}),
|
|
517
525
|
);
|
|
@@ -542,7 +550,7 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
542
550
|
shellQuote(configPath),
|
|
543
551
|
shellQuote(runtimeStartedPath),
|
|
544
552
|
shellQuote(runtimeCompletedPath),
|
|
545
|
-
String(
|
|
553
|
+
String(runtimeLimitSeconds * 1_000),
|
|
546
554
|
String(PLAY_RUNNER_STARTUP_GRACE_SECONDS * 1_000),
|
|
547
555
|
String(PLAY_RUNNER_TERMINAL_GRACE_SECONDS * 1_000),
|
|
548
556
|
shellQuote(runtimeLimitMarkerPath),
|
|
@@ -744,6 +744,9 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
744
744
|
const bundlePromise = preparedExecution.bundlePromise;
|
|
745
745
|
const sandboxLifecycle = preparedExecution.sandboxLifecycle;
|
|
746
746
|
const startedAt = sandboxLifecycle.startedAt;
|
|
747
|
+
const executionTimeoutSeconds =
|
|
748
|
+
config.context.sandboxRuntimeLimits?.timeoutSeconds ??
|
|
749
|
+
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS;
|
|
747
750
|
const runtimeTiming: PlayRunnerRuntimeTiming = { backend: 'daytona' };
|
|
748
751
|
const sandboxCleanup = createDaytonaSandboxCleanupManager();
|
|
749
752
|
const withCleanup = (result: PlayRunnerResult): PlayRunnerResult => {
|
|
@@ -794,6 +797,10 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
794
797
|
daytonaOrganizationId: acquired.daytonaOrganizationId,
|
|
795
798
|
billingStartedAt: acquired.billingStartedAt,
|
|
796
799
|
billingEndedAt,
|
|
800
|
+
maxBillingDurationSeconds:
|
|
801
|
+
(config.context.sandboxRuntimeLimits?.timeoutSeconds ??
|
|
802
|
+
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS) +
|
|
803
|
+
PLAY_RUNNER_TERMINAL_GRACE_SECONDS,
|
|
797
804
|
cpu: typeof sandbox.cpu === 'number' ? sandbox.cpu : null,
|
|
798
805
|
memoryGiB: typeof sandbox.memory === 'number' ? sandbox.memory : null,
|
|
799
806
|
diskGiB: typeof sandbox.disk === 'number' ? sandbox.disk : null,
|
|
@@ -976,7 +983,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
976
983
|
|
|
977
984
|
emitDaytonaStage(callbacks, config.context, 'execute:start', {
|
|
978
985
|
sandboxId: sandbox.id,
|
|
979
|
-
timeoutSeconds:
|
|
986
|
+
timeoutSeconds: executionTimeoutSeconds,
|
|
980
987
|
mode: 'session_detached',
|
|
981
988
|
});
|
|
982
989
|
// Push execution (B2-final, park-and-wake): start the runner command
|
|
@@ -1012,7 +1019,7 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
1012
1019
|
sessionId: start.sessionId,
|
|
1013
1020
|
cmdId: start.cmdId,
|
|
1014
1021
|
runnerAttempt,
|
|
1015
|
-
ceilingSeconds:
|
|
1022
|
+
ceilingSeconds: executionTimeoutSeconds,
|
|
1016
1023
|
terminalGraceSeconds: PLAY_RUNNER_TERMINAL_GRACE_SECONDS,
|
|
1017
1024
|
outputPath: stagedPayload.outputPath,
|
|
1018
1025
|
exitCodePath: stagedPayload.exitCodePath,
|
|
@@ -1031,7 +1038,9 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
1031
1038
|
exitCodePath: stagedPayload.exitCodePath,
|
|
1032
1039
|
runtimeCompletedPath: stagedPayload.runtimeCompletedPath,
|
|
1033
1040
|
startedAtMs: Date.now(),
|
|
1034
|
-
ceilingMs:
|
|
1041
|
+
ceilingMs:
|
|
1042
|
+
(executionTimeoutSeconds + PLAY_RUNNER_TERMINAL_GRACE_SECONDS) *
|
|
1043
|
+
1_000,
|
|
1035
1044
|
},
|
|
1036
1045
|
logs: [],
|
|
1037
1046
|
stats: {},
|
package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts
CHANGED
|
@@ -20,10 +20,7 @@ import {
|
|
|
20
20
|
recordComputeBillingItemViaAppRuntime,
|
|
21
21
|
type WorkerRuntimeApiContext,
|
|
22
22
|
} from '@shared_libs/play-runtime/app-runtime-api';
|
|
23
|
-
import {
|
|
24
|
-
STANDARD_PLAY_RUNTIME_LIMIT_LABEL,
|
|
25
|
-
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS,
|
|
26
|
-
} from '@shared_libs/play-runtime/runtime-constants';
|
|
23
|
+
import { STANDARD_PLAY_RUNTIME_LIMIT_SECONDS } from '@shared_libs/play-runtime/runtime-constants';
|
|
27
24
|
|
|
28
25
|
interface CommandExecutionResult {
|
|
29
26
|
exitCode: number;
|
|
@@ -44,7 +41,9 @@ const LOCAL_PROCESS_COMPUTE_MEMORY_GIB = 1;
|
|
|
44
41
|
const LOCAL_PROCESS_COMPUTE_DISK_GIB = 3;
|
|
45
42
|
export const LOCAL_PROCESS_CANCEL_GRACE_MS = 2_000;
|
|
46
43
|
const LOCAL_PROCESS_CANCELLED_ERROR = 'Local play runner cancelled';
|
|
47
|
-
|
|
44
|
+
function localProcessRuntimeLimitError(runtimeLimitSeconds: number): string {
|
|
45
|
+
return `Configured max runtime is ${runtimeLimitSeconds} seconds. Use smaller batches or increase runtime.timeout.`;
|
|
46
|
+
}
|
|
48
47
|
|
|
49
48
|
export const LOCAL_PROCESS_COMPUTE_PROFILE = {
|
|
50
49
|
source: LOCAL_PROCESS_COMPUTE_SOURCE,
|
|
@@ -192,16 +191,28 @@ async function runCommand(
|
|
|
192
191
|
});
|
|
193
192
|
}
|
|
194
193
|
|
|
195
|
-
|
|
194
|
+
export function resolveLocalProcessRuntimeLimitSeconds(
|
|
195
|
+
context: PlayRunnerExecutionConfig['context'],
|
|
196
|
+
): number {
|
|
197
|
+
return (
|
|
198
|
+
context.sandboxRuntimeLimits?.timeoutSeconds ??
|
|
199
|
+
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function runCommandWithRuntimeLimit(
|
|
196
204
|
command: string,
|
|
197
205
|
args: string[],
|
|
206
|
+
runtimeLimitSeconds: number,
|
|
198
207
|
options?: SpawnOptionsWithoutStdio,
|
|
199
208
|
callbacks?: StreamedCommandCallbacks,
|
|
200
209
|
): Promise<CommandExecutionResult> {
|
|
201
210
|
const runtimeLimitController = new AbortController();
|
|
202
211
|
const runtimeDeadlineTimer = setTimeout(() => {
|
|
203
|
-
runtimeLimitController.abort(
|
|
204
|
-
|
|
212
|
+
runtimeLimitController.abort(
|
|
213
|
+
localProcessRuntimeLimitError(runtimeLimitSeconds),
|
|
214
|
+
);
|
|
215
|
+
}, runtimeLimitSeconds * 1000);
|
|
205
216
|
runtimeDeadlineTimer.unref?.();
|
|
206
217
|
|
|
207
218
|
const onExternalCancel = () => {
|
|
@@ -413,9 +424,10 @@ export const localProcessPlayRunnerBackend: PlayRunnerBackend = {
|
|
|
413
424
|
},
|
|
414
425
|
},
|
|
415
426
|
async (span) => {
|
|
416
|
-
const result = await
|
|
427
|
+
const result = await runCommandWithRuntimeLimit(
|
|
417
428
|
'node',
|
|
418
429
|
[runnerPath, configPath],
|
|
430
|
+
resolveLocalProcessRuntimeLimitSeconds(config.context),
|
|
419
431
|
{
|
|
420
432
|
cwd: workspaceRoot,
|
|
421
433
|
env: {
|
|
@@ -17,6 +17,8 @@ export type PlayRunnerRuntimeResource = {
|
|
|
17
17
|
daytonaOrganizationId?: string;
|
|
18
18
|
billingStartedAt: number;
|
|
19
19
|
billingEndedAt?: number | null;
|
|
20
|
+
/** Customer liability ceiling for this physical sandbox. */
|
|
21
|
+
maxBillingDurationSeconds?: number | null;
|
|
20
22
|
terminalReason?: RuntimeResourceTerminalReason | null;
|
|
21
23
|
terminalAt?: number | null;
|
|
22
24
|
cpu?: number | null;
|