deepline 0.1.307 → 0.1.309
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/play.ts +26 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +7 -0
- 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 +19 -10
- 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 +174 -11
- package/dist/cli/index.mjs +174 -11
- package/dist/index.d.mts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +11 -1
- package/dist/index.mjs +11 -1
- 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/dist/viewer/viewer.css +64 -61
- package/package.json +1 -1
|
@@ -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.309',
|
|
161
161
|
contracts: {
|
|
162
162
|
api: {
|
|
163
163
|
name: 'sdk-http-api',
|
|
@@ -8810,7 +8810,12 @@ export class PlayContextImpl {
|
|
|
8810
8810
|
const executeSuffix = requestsDurableInvocationFence
|
|
8811
8811
|
? 'execute-fenced-v1'
|
|
8812
8812
|
: 'execute';
|
|
8813
|
-
|
|
8813
|
+
// Keep receipts, runner heartbeats, terminals, sheets, and runtime control
|
|
8814
|
+
// on baseUrl (the receipt gateway). Only the potentially long-lived
|
|
8815
|
+
// provider request may use the separately managed execution relay.
|
|
8816
|
+
const executionBaseUrl =
|
|
8817
|
+
this.#options.executionGatewayBaseUrl?.trim() || this.#options.baseUrl;
|
|
8818
|
+
const url = `${executionBaseUrl.replace(/\/$/, '')}/api/v2/integrations/${encodeURIComponent(toolId)}/${executeSuffix}`;
|
|
8814
8819
|
const toolErrorSchemaVersion = this.currentToolErrorSchemaVersion;
|
|
8815
8820
|
const timeoutMs = resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs);
|
|
8816
8821
|
const provider = toolId.split(/[._]/)[0]?.trim() || 'provider';
|
|
@@ -522,6 +522,13 @@ export interface ContextOptions {
|
|
|
522
522
|
/** Short-lived HMAC-signed internal token for tool callbacks. Required for cloud execution. */
|
|
523
523
|
executorToken?: string;
|
|
524
524
|
baseUrl?: string;
|
|
525
|
+
/**
|
|
526
|
+
* Optional long-lived integration transport. Runtime control, receipts, and
|
|
527
|
+
* runner terminals continue to use baseUrl; only provider execute requests
|
|
528
|
+
* use this origin. This lets production rotate the receipt gateway without
|
|
529
|
+
* terminating an in-flight provider request.
|
|
530
|
+
*/
|
|
531
|
+
executionGatewayBaseUrl?: string | null;
|
|
525
532
|
/**
|
|
526
533
|
* The integration base URL persists each keyed execute response before
|
|
527
534
|
* exposing it. Only runtime adapters that route through that gateway may
|
|
@@ -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
|
}]
|
|
@@ -366,11 +370,12 @@ function remoteRuntimeContextForDaytona(
|
|
|
366
370
|
context.executionGatewayBaseUrl?.trim() || gatewayBaseUrl;
|
|
367
371
|
return {
|
|
368
372
|
...context,
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
|
|
373
|
+
// The generic runtime transport owns receipts, runner terminal publication,
|
|
374
|
+
// heartbeats, sheets, and runtime control, so it must stay on the receipt
|
|
375
|
+
// gateway. Only ctx.tools.execute selects executionGatewayBaseUrl below.
|
|
376
|
+
// Otherwise a receipt gateway rollout silently moves all durable control
|
|
377
|
+
// traffic to the relay and can leave a run parked after its sandbox starts.
|
|
378
|
+
baseUrl: gatewayBaseUrl,
|
|
374
379
|
executionGatewayBaseUrl,
|
|
375
380
|
receiptGatewayBaseUrl: gatewayBaseUrl,
|
|
376
381
|
// The execution relay owns the Postgres-backed invocation fence even though
|
|
@@ -498,6 +503,9 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
498
503
|
materializedFiles: remoteMaterializedFiles,
|
|
499
504
|
context: remoteRuntimeContextForDaytona(input.config.context),
|
|
500
505
|
};
|
|
506
|
+
const runtimeLimitSeconds =
|
|
507
|
+
input.config.context.sandboxRuntimeLimits?.timeoutSeconds ??
|
|
508
|
+
STANDARD_PLAY_RUNTIME_LIMIT_SECONDS;
|
|
501
509
|
|
|
502
510
|
const envelopeUpload = input.bundlePromise.then((bundle) =>
|
|
503
511
|
timedDaytonaUpload({
|
|
@@ -511,7 +519,8 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
511
519
|
artifactBundledCode: compactedArtifact.bundledCode,
|
|
512
520
|
artifactSourceMap: input.config.artifact.sourceMap,
|
|
513
521
|
config: remoteConfig,
|
|
514
|
-
crashPusherCode:
|
|
522
|
+
crashPusherCode:
|
|
523
|
+
buildDaytonaCrashTerminalPusherSource(runtimeLimitSeconds),
|
|
515
524
|
}),
|
|
516
525
|
}),
|
|
517
526
|
);
|
|
@@ -542,7 +551,7 @@ export async function stageDaytonaRunnerPayload(input: {
|
|
|
542
551
|
shellQuote(configPath),
|
|
543
552
|
shellQuote(runtimeStartedPath),
|
|
544
553
|
shellQuote(runtimeCompletedPath),
|
|
545
|
-
String(
|
|
554
|
+
String(runtimeLimitSeconds * 1_000),
|
|
546
555
|
String(PLAY_RUNNER_STARTUP_GRACE_SECONDS * 1_000),
|
|
547
556
|
String(PLAY_RUNNER_TERMINAL_GRACE_SECONDS * 1_000),
|
|
548
557
|
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;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { MAX_PLAY_SANDBOX_RUNTIME_LIMITS } from './sandbox-runtime-limits';
|
|
2
|
+
|
|
1
3
|
/** Maximum active user-code runtime for a standard play, in seconds. */
|
|
2
4
|
export const STANDARD_PLAY_RUNTIME_LIMIT_SECONDS = 30 * 60;
|
|
3
5
|
export const STANDARD_PLAY_RUNTIME_LIMIT_LABEL = '30 minutes';
|
|
@@ -22,7 +24,9 @@ export const PLAY_RUNNER_STARTUP_GRACE_SECONDS = 4 * 60;
|
|
|
22
24
|
export const PLAY_RUNNER_TIMEOUT_SECONDS = 40 * 60;
|
|
23
25
|
|
|
24
26
|
/** TTL for workflow executor tokens, in seconds. */
|
|
25
|
-
export const WORKFLOW_EXECUTOR_TOKEN_TTL_SECONDS =
|
|
27
|
+
export const WORKFLOW_EXECUTOR_TOKEN_TTL_SECONDS =
|
|
28
|
+
MAX_PLAY_SANDBOX_RUNTIME_LIMITS.timeoutSeconds +
|
|
29
|
+
(PLAY_RUNNER_TIMEOUT_SECONDS - STANDARD_PLAY_RUNTIME_LIMIT_SECONDS);
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Absurd run-claim lease window, in seconds. The scheduler expires a claim whose
|
|
@@ -22,6 +22,7 @@ export function resolveDaytonaRuntimeComputeItems(input: {
|
|
|
22
22
|
cpu: number | null;
|
|
23
23
|
memoryGiB: number | null;
|
|
24
24
|
diskGiB: number | null;
|
|
25
|
+
maxBillingDurationSeconds: number | null;
|
|
25
26
|
}
|
|
26
27
|
>();
|
|
27
28
|
const items: ComputeBillingItem[] = [];
|
|
@@ -38,6 +39,9 @@ export function resolveDaytonaRuntimeComputeItems(input: {
|
|
|
38
39
|
const billingStartedAt = finiteNonNegative(value.billingStartedAt);
|
|
39
40
|
if (billingStartedAt === null) continue;
|
|
40
41
|
const billingEndedAt = finiteNonNegative(value.billingEndedAt);
|
|
42
|
+
const maxBillingDurationSeconds = finiteNonNegative(
|
|
43
|
+
value.maxBillingDurationSeconds,
|
|
44
|
+
);
|
|
41
45
|
const existing = resourcesBySandboxId.get(sandboxId);
|
|
42
46
|
resourcesBySandboxId.set(sandboxId, {
|
|
43
47
|
startedAt: Math.min(
|
|
@@ -46,16 +50,27 @@ export function resolveDaytonaRuntimeComputeItems(input: {
|
|
|
46
50
|
),
|
|
47
51
|
endedAt:
|
|
48
52
|
billingEndedAt === null
|
|
49
|
-
? existing?.endedAt ?? null
|
|
53
|
+
? (existing?.endedAt ?? null)
|
|
50
54
|
: Math.max(existing?.endedAt ?? billingEndedAt, billingEndedAt),
|
|
51
55
|
cpu: finiteNonNegative(value.cpu) ?? existing?.cpu ?? null,
|
|
52
56
|
memoryGiB:
|
|
53
57
|
finiteNonNegative(value.memoryGiB) ?? existing?.memoryGiB ?? null,
|
|
54
58
|
diskGiB: finiteNonNegative(value.diskGiB) ?? existing?.diskGiB ?? null,
|
|
59
|
+
maxBillingDurationSeconds:
|
|
60
|
+
maxBillingDurationSeconds ??
|
|
61
|
+
existing?.maxBillingDurationSeconds ??
|
|
62
|
+
null,
|
|
55
63
|
});
|
|
56
64
|
}
|
|
57
65
|
for (const [sandboxId, resource] of resourcesBySandboxId) {
|
|
58
|
-
const
|
|
66
|
+
const observedEndedAt = resource.endedAt ?? input.endedAt;
|
|
67
|
+
const endedAt =
|
|
68
|
+
resource.maxBillingDurationSeconds === null
|
|
69
|
+
? observedEndedAt
|
|
70
|
+
: Math.min(
|
|
71
|
+
observedEndedAt,
|
|
72
|
+
resource.startedAt + resource.maxBillingDurationSeconds * 1_000,
|
|
73
|
+
);
|
|
59
74
|
items.push(
|
|
60
75
|
resolveDaytonaSandboxComputeItem({
|
|
61
76
|
itemId: `daytona:${sandboxId}`,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export type PlaySandboxRuntimeLimits = {
|
|
2
|
+
timeoutSeconds: number;
|
|
3
|
+
memoryGiB: number;
|
|
4
|
+
cpu: number;
|
|
5
|
+
diskGiB: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS: PlaySandboxRuntimeLimits = {
|
|
9
|
+
timeoutSeconds: 30 * 60,
|
|
10
|
+
memoryGiB: 1,
|
|
11
|
+
cpu: 1,
|
|
12
|
+
diskGiB: 3,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Product-wide ceilings. Organisation entitlements may lower these, but a
|
|
16
|
+
// customer-authored Play can never ask Daytona for an unbounded resource.
|
|
17
|
+
export const MAX_PLAY_SANDBOX_RUNTIME_LIMITS: PlaySandboxRuntimeLimits = {
|
|
18
|
+
timeoutSeconds: 4 * 60 * 60,
|
|
19
|
+
memoryGiB: 16,
|
|
20
|
+
cpu: 4,
|
|
21
|
+
diskGiB: 50,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type PlaySandboxRuntimeDeclaration = {
|
|
25
|
+
timeout?: string;
|
|
26
|
+
memory?: string;
|
|
27
|
+
cpu?: number;
|
|
28
|
+
disk?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function parsePositiveInteger(value: string, unit: string): number | null {
|
|
32
|
+
const match = new RegExp(`^(\\d+)\\s*${unit}$`, 'i').exec(value.trim());
|
|
33
|
+
if (!match) return null;
|
|
34
|
+
const parsed = Number(match[1]);
|
|
35
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseTimeout(value: string): number | null {
|
|
39
|
+
const match = /^(\d+)\s*([mh])$/i.exec(value.trim());
|
|
40
|
+
if (!match) return null;
|
|
41
|
+
const amount = Number(match[1]);
|
|
42
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) return null;
|
|
43
|
+
return amount * (match[2].toLowerCase() === 'h' ? 3600 : 60);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolvePlaySandboxRuntimeLimits(
|
|
47
|
+
declaration: PlaySandboxRuntimeDeclaration | null | undefined,
|
|
48
|
+
): PlaySandboxRuntimeLimits {
|
|
49
|
+
const base = STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS;
|
|
50
|
+
if (!declaration) return { ...base };
|
|
51
|
+
const timeoutSeconds = declaration.timeout
|
|
52
|
+
? parseTimeout(declaration.timeout)
|
|
53
|
+
: base.timeoutSeconds;
|
|
54
|
+
const memoryGiB = declaration.memory
|
|
55
|
+
? parsePositiveInteger(declaration.memory, 'GiB')
|
|
56
|
+
: base.memoryGiB;
|
|
57
|
+
const diskGiB = declaration.disk
|
|
58
|
+
? parsePositiveInteger(declaration.disk, 'GiB')
|
|
59
|
+
: base.diskGiB;
|
|
60
|
+
const cpu = declaration.cpu ?? base.cpu;
|
|
61
|
+
if (
|
|
62
|
+
timeoutSeconds === null ||
|
|
63
|
+
memoryGiB === null ||
|
|
64
|
+
diskGiB === null ||
|
|
65
|
+
!Number.isSafeInteger(cpu) ||
|
|
66
|
+
cpu <= 0
|
|
67
|
+
) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'Invalid runtime sandbox declaration. Use timeout like "90m" or "2h", memory/disk like "4GiB", and a positive integer cpu.',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const resolved = { timeoutSeconds, memoryGiB, cpu, diskGiB };
|
|
73
|
+
for (const key of ['memoryGiB', 'cpu', 'diskGiB'] as const) {
|
|
74
|
+
if (resolved[key] < base[key]) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Requested runtime ${key}=${resolved[key]} is below the supported minimum ${base[key]}.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const key of Object.keys(
|
|
81
|
+
resolved,
|
|
82
|
+
) as (keyof PlaySandboxRuntimeLimits)[]) {
|
|
83
|
+
if (resolved[key] > MAX_PLAY_SANDBOX_RUNTIME_LIMITS[key]) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Requested runtime ${key}=${resolved[key]} exceeds this organisation's maximum ${MAX_PLAY_SANDBOX_RUNTIME_LIMITS[key]}.`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function hasNonStandardPlaySandboxRuntimeLimits(
|
|
93
|
+
value: PlaySandboxRuntimeLimits,
|
|
94
|
+
): boolean {
|
|
95
|
+
return Object.keys(value).some(
|
|
96
|
+
(key) =>
|
|
97
|
+
value[key as keyof PlaySandboxRuntimeLimits] !==
|
|
98
|
+
STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS[
|
|
99
|
+
key as keyof PlaySandboxRuntimeLimits
|
|
100
|
+
],
|
|
101
|
+
);
|
|
102
|
+
}
|