@prized/sdk 0.0.0-stage → 0.1.1

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.
Files changed (52) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +666 -2
  3. package/dist/cjs/commands.d.ts +199 -0
  4. package/dist/cjs/commands.js +597 -0
  5. package/dist/cjs/errors.d.ts +113 -0
  6. package/dist/cjs/errors.js +306 -0
  7. package/dist/cjs/files.d.ts +101 -0
  8. package/dist/cjs/files.js +387 -0
  9. package/dist/cjs/http.d.ts +226 -0
  10. package/dist/cjs/http.js +618 -0
  11. package/dist/cjs/index.d.ts +12 -0
  12. package/dist/cjs/index.js +23 -0
  13. package/dist/cjs/outcome.d.ts +76 -0
  14. package/dist/cjs/outcome.js +170 -0
  15. package/dist/cjs/output.d.ts +70 -0
  16. package/dist/cjs/output.js +383 -0
  17. package/dist/cjs/package.json +1 -0
  18. package/dist/cjs/sandbox.d.ts +218 -0
  19. package/dist/cjs/sandbox.js +836 -0
  20. package/dist/cjs/shell.d.ts +152 -0
  21. package/dist/cjs/shell.js +393 -0
  22. package/dist/cjs/toolbox.d.ts +48 -0
  23. package/dist/cjs/toolbox.js +88 -0
  24. package/dist/cjs/types.d.ts +111 -0
  25. package/dist/cjs/types.js +102 -0
  26. package/dist/cjs/version.d.ts +2 -0
  27. package/dist/cjs/version.js +5 -0
  28. package/dist/esm/commands.d.ts +199 -0
  29. package/dist/esm/commands.js +590 -0
  30. package/dist/esm/errors.d.ts +113 -0
  31. package/dist/esm/errors.js +297 -0
  32. package/dist/esm/files.d.ts +101 -0
  33. package/dist/esm/files.js +381 -0
  34. package/dist/esm/http.d.ts +226 -0
  35. package/dist/esm/http.js +601 -0
  36. package/dist/esm/index.d.ts +12 -0
  37. package/dist/esm/index.js +7 -0
  38. package/dist/esm/outcome.d.ts +76 -0
  39. package/dist/esm/outcome.js +155 -0
  40. package/dist/esm/output.d.ts +70 -0
  41. package/dist/esm/output.js +374 -0
  42. package/dist/esm/sandbox.d.ts +218 -0
  43. package/dist/esm/sandbox.js +832 -0
  44. package/dist/esm/shell.d.ts +152 -0
  45. package/dist/esm/shell.js +379 -0
  46. package/dist/esm/toolbox.d.ts +48 -0
  47. package/dist/esm/toolbox.js +83 -0
  48. package/dist/esm/types.d.ts +111 -0
  49. package/dist/esm/types.js +95 -0
  50. package/dist/esm/version.d.ts +2 -0
  51. package/dist/esm/version.js +2 -0
  52. package/package.json +53 -4
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_FILE_BYTES = exports.MAX_OUTPUT_BYTES = void 0;
4
+ exports.rawExec = rawExec;
5
+ exports.helperExec = helperExec;
6
+ // The exec/files edge for one sandbox: POST {base}/process/execute,
7
+ // POST {base}/files/upload?path=, GET {base}/files/download?path=.
8
+ const errors_js_1 = require("./errors.js");
9
+ const http_js_1 = require("./http.js");
10
+ const outcome_js_1 = require("./outcome.js");
11
+ /** Each stream of one call keeps at most this many bytes. */
12
+ exports.MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
13
+ /** One upload or download moves at most this many bytes. */
14
+ exports.MAX_FILE_BYTES = 100 * 1024 * 1024;
15
+ /** Slack on top of a command's own timeout before the SDK stops waiting for the answer. */
16
+ const EXEC_ANSWER_SLACK_MS = 30_000;
17
+ /**
18
+ * How many of the stream's bytes the sandbox kept, from the text its answer
19
+ * carried. The sandbox keeps exactly the first MAX_OUTPUT_BYTES; each byte
20
+ * of that which was not valid UTF-8 arrives as one U+FFFD, so U+FFFD counts
21
+ * as one byte here, not the three it encodes to (as the Python SDK counts):
22
+ * 2 MiB of binary output is not a full stream.
23
+ */
24
+ function keptBytes(text) {
25
+ // Fast path: fewer than MAX/3 UTF-16 units can never encode to MAX bytes.
26
+ if (text.length * 3 < exports.MAX_OUTPUT_BYTES)
27
+ return text.length;
28
+ let replaced = 0;
29
+ for (let at = text.indexOf('\uFFFD'); at !== -1; at = text.indexOf('\uFFFD', at + 1))
30
+ replaced += 1;
31
+ return new TextEncoder().encode(text).length - 2 * replaced;
32
+ }
33
+ /** One command over the edge, answered when it finishes. */
34
+ async function rawExec(toolbox, req, options) {
35
+ if (!(req.timeoutMs > 0))
36
+ throw new errors_js_1.PrizedError('invalid_argument', 'A command sent on one call needs a timeout > 0 ms');
37
+ const effect = options.effect ?? (options.retry === 'exec' ? (0, outcome_js_1.commandEffect)() : undefined);
38
+ const body = { command: req.command };
39
+ if (req.cwd !== undefined)
40
+ body.cwd = req.cwd;
41
+ if (req.env !== undefined && Object.keys(req.env).length > 0)
42
+ body.env = req.env;
43
+ body.timeout = req.timeoutMs / 1000;
44
+ const result = await (0, http_js_1.request)(toolbox.config, {
45
+ method: 'POST',
46
+ url: `${toolbox.base()}/process/execute`,
47
+ json: body,
48
+ retry: options.retry,
49
+ timeoutMs: req.timeoutMs + EXEC_ANSWER_SLACK_MS,
50
+ signal: options.signal,
51
+ deadline: options.deadline,
52
+ sandboxId: toolbox.sandboxId,
53
+ ...(effect === undefined ? {} : { effect }),
54
+ });
55
+ const doc = result.json;
56
+ if (doc === null || typeof doc !== 'object' || typeof doc.exitCode !== 'number') {
57
+ const unread = new errors_js_1.PrizedError('bad_response', 'The exec answer carried no exit code.', {
58
+ status: result.status,
59
+ sandboxId: toolbox.sandboxId,
60
+ });
61
+ // A 2xx: the command ran, whatever its answer lacks.
62
+ throw effect === undefined ? unread : (0, outcome_js_1.withOutcome)(unread, effect, 'did');
63
+ }
64
+ const stdout = typeof doc.stdout === 'string' ? doc.stdout : '';
65
+ const stderr = typeof doc.stderr === 'string' ? doc.stderr : '';
66
+ const truncated = keptBytes(stdout) >= exports.MAX_OUTPUT_BYTES || keptBytes(stderr) >= exports.MAX_OUTPUT_BYTES;
67
+ return {
68
+ stdout,
69
+ stderr,
70
+ exitCode: doc.exitCode,
71
+ durationMs: typeof doc.durationMs === 'number' ? doc.durationMs : 0,
72
+ timedOut: doc.timedOut === true,
73
+ truncated,
74
+ ...(truncated ? { truncatedReason: 'output_limit' } : {}),
75
+ };
76
+ }
77
+ /** A helper command the SDK sends itself; a non-zero exit is the caller's to judge. */
78
+ function helperExec(toolbox, command, options = {}) {
79
+ const retry = options.retry ?? 'safe';
80
+ return rawExec(toolbox, { command, timeoutMs: options.timeoutMs ?? 60_000 }, {
81
+ retry,
82
+ signal: options.signal,
83
+ deadline: options.deadline,
84
+ // Not the caller's command: an `exec` helper that names no effect (a
85
+ // kill) gets words that claim nothing about what it does.
86
+ effect: options.effect ?? (retry === 'exec' ? (0, outcome_js_1.helperEffect)() : undefined),
87
+ });
88
+ }
@@ -0,0 +1,111 @@
1
+ /** A sandbox's lifecycle state. */
2
+ export type SandboxState = 'starting' | 'running' | 'pausing' | 'paused' | 'resizing' | 'deleting' | 'deleted' | 'error';
3
+ /** A sandbox shape. Memory travels in MiB; `memoryGb` is the same number in GiB. */
4
+ export interface Resources {
5
+ vcpu: number;
6
+ memoryMb: number;
7
+ memoryGb: number;
8
+ diskGb: number;
9
+ }
10
+ /** What a create asks for. Every field is optional: the template's default fills the gaps. */
11
+ export interface ResourcesInput {
12
+ vcpu?: number;
13
+ /** Memory in GiB (`memoryGb: 4` is 4096 MiB). Give this or `memoryMb`, not both. */
14
+ memoryGb?: number;
15
+ /** Memory in MiB. */
16
+ memoryMb?: number;
17
+ diskGb?: number;
18
+ }
19
+ /** What the API says about a sandbox. */
20
+ export interface SandboxInfo {
21
+ id: string;
22
+ state: SandboxState;
23
+ template: string | null;
24
+ resources: Resources;
25
+ labels: Record<string, string>;
26
+ /** Idle minutes before a running sandbox pauses itself (null: never). */
27
+ autoPauseMin: number | null;
28
+ /** Minutes after pausing before the sandbox deletes itself (null: never). */
29
+ autoDeleteMin: number | null;
30
+ /**
31
+ * When the sandbox's run time ends and it is deleted. Null while a paused
32
+ * sandbox's clock is stopped: a resume on a paid plan starts a fresh one;
33
+ * on the free tier it carries on the running time left.
34
+ */
35
+ expiresAt: Date | null;
36
+ createdAt: Date | null;
37
+ lastActiveAt: Date | null;
38
+ /** Why the sandbox is in `error`, when it is. */
39
+ errorReason: string | null;
40
+ /** The base URL its exec and files calls go to. */
41
+ toolboxBase: string | null;
42
+ /** The document exactly as the API sent it. */
43
+ raw: Record<string, unknown>;
44
+ }
45
+ /** One entry of `Sandbox.templates()`. */
46
+ export interface Template {
47
+ id: string;
48
+ defaults: Resources;
49
+ min: Resources;
50
+ max: Resources;
51
+ durability: string;
52
+ /** The run time a sandbox gets when its create gives no `maxRuntimeMin` (for your workspace's plan). */
53
+ defaultMaxRuntimeMin: number;
54
+ }
55
+ /** What one command produced. */
56
+ export interface ExecResult {
57
+ stdout: string;
58
+ stderr: string;
59
+ /** The command's exit code; -1 when it timed out or ended without reporting one. */
60
+ exitCode: number;
61
+ /** How long the command ran, in ms. */
62
+ durationMs: number;
63
+ /**
64
+ * True when the command hit its timeout and was killed (`exitCode` is then
65
+ * -1). `exec()` and `wait()` throw CommandTimeoutError with such a result
66
+ * rather than return it.
67
+ */
68
+ timedOut: boolean;
69
+ /**
70
+ * True when stdout or stderr reached the 4 MiB per-stream cap of a single
71
+ * call: the command kept running, the rest of that stream was dropped.
72
+ * Pass `fullOutput: true` for commands that may print more (it keeps up
73
+ * to 100 MiB of each stream, and sets this past that). `truncatedReason`
74
+ * says why.
75
+ */
76
+ truncated: boolean;
77
+ /**
78
+ * Why `truncated` is true; absent when it is false. `output_limit`: a
79
+ * stream reached its cap (4 MiB in one call, 100 MiB with `fullOutput`).
80
+ * `write_failed` (`fullOutput` only): the sandbox could not save a stream
81
+ * as the command printed it, most often because its disk is full, so that
82
+ * stream stops where the write failed, however short; free some space and
83
+ * run it again.
84
+ */
85
+ truncatedReason?: TruncatedReason;
86
+ }
87
+ /** Why a command's output was cut: ExecResult.truncatedReason. */
88
+ export type TruncatedReason = 'output_limit' | 'write_failed';
89
+ /** One directory entry, as `sandbox.files.list()` returns it. */
90
+ export interface FileInfo {
91
+ name: string;
92
+ path: string;
93
+ /** Size in bytes. */
94
+ size: number;
95
+ isDir: boolean;
96
+ isSymlink: boolean;
97
+ /** Permissions as `ls -l` prints them, e.g. `-rw-r--r--`. */
98
+ mode: string;
99
+ modified: Date | null;
100
+ }
101
+ /** What a write or an upload reports. */
102
+ export interface WriteInfo {
103
+ path: string;
104
+ size: number;
105
+ }
106
+ export declare function parseTime(value: unknown): Date | null;
107
+ export declare function parseResources(value: unknown): Resources;
108
+ export declare function parseSandbox(doc: unknown): SandboxInfo;
109
+ export declare function parseTemplate(entry: unknown): Template | null;
110
+ /** The wire shape of a create's `resources`: memory in MiB, integers only. */
111
+ export declare function resourcesToWire(input: ResourcesInput): Record<string, number>;
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTime = parseTime;
4
+ exports.parseResources = parseResources;
5
+ exports.parseSandbox = parseSandbox;
6
+ exports.parseTemplate = parseTemplate;
7
+ exports.resourcesToWire = resourcesToWire;
8
+ // Public types and the parsers that turn API documents into them.
9
+ const errors_js_1 = require("./errors.js");
10
+ function int(value, fallback = 0) {
11
+ return typeof value === 'number' && Number.isInteger(value) ? value : fallback;
12
+ }
13
+ function optInt(value) {
14
+ return typeof value === 'number' && Number.isInteger(value) ? value : null;
15
+ }
16
+ function optString(value) {
17
+ return typeof value === 'string' ? value : null;
18
+ }
19
+ function parseTime(value) {
20
+ if (typeof value !== 'string' || value === '')
21
+ return null;
22
+ const at = Date.parse(value);
23
+ return Number.isNaN(at) ? null : new Date(at);
24
+ }
25
+ function record(value) {
26
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {};
27
+ }
28
+ function parseResources(value) {
29
+ const r = record(value);
30
+ const memoryMb = int(r.memoryMb);
31
+ return { vcpu: int(r.vcpu), memoryMb, memoryGb: memoryMb / 1024, diskGb: int(r.diskGb) };
32
+ }
33
+ function parseSandbox(doc) {
34
+ const d = record(doc);
35
+ if (typeof d.id !== 'string' || d.id === '') {
36
+ throw new errors_js_1.PrizedError('bad_response', 'The API answered without a sandbox document.');
37
+ }
38
+ const labels = {};
39
+ for (const [key, value] of Object.entries(record(d.labels))) {
40
+ if (typeof value === 'string')
41
+ labels[key] = value;
42
+ }
43
+ return {
44
+ id: d.id,
45
+ state: (typeof d.state === 'string' ? d.state : 'error'),
46
+ template: optString(d.template),
47
+ resources: parseResources(d.resources),
48
+ labels,
49
+ autoPauseMin: optInt(d.autoPauseMin),
50
+ autoDeleteMin: optInt(d.autoDeleteMin),
51
+ expiresAt: parseTime(d.expiresAt),
52
+ createdAt: parseTime(d.createdAt),
53
+ lastActiveAt: parseTime(d.lastActiveAt),
54
+ errorReason: optString(d.errorReason),
55
+ toolboxBase: optString(d.toolboxBase),
56
+ raw: d,
57
+ };
58
+ }
59
+ function parseTemplate(entry) {
60
+ const e = record(entry);
61
+ if (typeof e.id !== 'string' || e.id === '')
62
+ return null;
63
+ const resources = record(e.resources);
64
+ return {
65
+ id: e.id,
66
+ defaults: parseResources(resources.defaults),
67
+ min: parseResources(resources.min),
68
+ max: parseResources(resources.max),
69
+ durability: optString(e.durability) ?? 'ephemeral',
70
+ defaultMaxRuntimeMin: int(e.defaultMaxRuntimeMin),
71
+ };
72
+ }
73
+ /** The wire shape of a create's `resources`: memory in MiB, integers only. */
74
+ function resourcesToWire(input) {
75
+ const out = {};
76
+ const whole = (name, value) => {
77
+ if (!Number.isInteger(value) || value <= 0) {
78
+ throw new errors_js_1.PrizedError('invalid_argument', `resources.${name} must be a positive whole number (got ${value})`);
79
+ }
80
+ return value;
81
+ };
82
+ if (input.vcpu !== undefined)
83
+ out.vcpu = whole('vcpu', input.vcpu);
84
+ if (input.memoryGb !== undefined && input.memoryMb !== undefined) {
85
+ if (Math.round(input.memoryGb * 1024) !== input.memoryMb) {
86
+ throw new errors_js_1.PrizedError('invalid_argument', 'Give resources.memoryGb or resources.memoryMb, not both');
87
+ }
88
+ }
89
+ if (input.memoryMb !== undefined) {
90
+ out.memoryMb = whole('memoryMb', input.memoryMb);
91
+ }
92
+ else if (input.memoryGb !== undefined) {
93
+ const mb = input.memoryGb * 1024;
94
+ if (!Number.isInteger(mb) || mb <= 0) {
95
+ throw new errors_js_1.PrizedError('invalid_argument', `resources.memoryGb must be a whole number of MiB (got ${input.memoryGb} GiB)`);
96
+ }
97
+ out.memoryMb = mb;
98
+ }
99
+ if (input.diskGb !== undefined)
100
+ out.diskGb = whole('diskGb', input.diskGb);
101
+ return out;
102
+ }
@@ -0,0 +1,2 @@
1
+ /** This package's version; test/version.test.ts holds it equal to package.json. */
2
+ export declare const VERSION = "0.1.1";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VERSION = void 0;
4
+ /** This package's version; test/version.test.ts holds it equal to package.json. */
5
+ exports.VERSION = '0.1.1';
@@ -0,0 +1,199 @@
1
+ import { PrizedError } from './errors.js';
2
+ import { type Toolbox } from './toolbox.js';
3
+ import type { ExecResult } from './types.js';
4
+ /** The timeout a command gets when the call names none. */
5
+ export declare const DEFAULT_EXEC_TIMEOUT_MS = 60000;
6
+ /**
7
+ * The longest command one blocking call carries. Longer timeouts (and
8
+ * `timeoutMs: 0`) run detached and are polled: Node's fetch gives up on an
9
+ * answer after 300 s, and load balancers drop idle connections not long after.
10
+ */
11
+ export declare const MAX_BLOCKING_EXEC_MS = 240000;
12
+ /**
13
+ * Whether a failed poll is one an outage window rides out (sdk/retry-spec.json
14
+ * `pollOutage`): no answer, an answer that could not be read, a rate limit or
15
+ * any 5xx. Any other (a refused key, any other refusal, an answer that
16
+ * settles the run, an abort) ends the wait at once. The Python SDK's
17
+ * rides_out.
18
+ */
19
+ export declare function ridesOut(error: PrizedError): boolean;
20
+ /**
21
+ * The failed polls of a wait that rides out an outage of up to `windowMs`
22
+ * (pollOutageMs), from when the first poll's retries ran out, and, while the
23
+ * command runs, never past the wait's own deadline (`waitDeadline`, epoch
24
+ * ms; null for none): polled again with backoff, each poll and its retries
25
+ * ending before the last look, which goes out LAST_LOOK_MAX_MS before the
26
+ * window's end (half the window, for one shorter than twice that) with room
27
+ * for its own retries. So an outage that ends inside the window is always
28
+ * seen to end. The Python SDK's Outage.
29
+ */
30
+ export declare class Outage {
31
+ private readonly windowMs;
32
+ private readonly waitDeadline;
33
+ private end;
34
+ private last;
35
+ private tries;
36
+ private room;
37
+ private finished;
38
+ constructor(windowMs: number, waitDeadline?: number | null);
39
+ /** The command has ended: draining its output, and an outage meanwhile, are not held to the deadline. */
40
+ finish(): void;
41
+ /**
42
+ * Whether a wait's failed poll (`error`, at `now`; `cut`: its bound cut it
43
+ * short) means the wait's own time ran out: its deadline cut the poll
44
+ * short while the command ran, with no outage being ridden out, so the
45
+ * command was still running when last seen. With no window, any timeout
46
+ * while it runs says so, as it always has. Never once the command has
47
+ * ended. The Python SDK's Outage.ran_out.
48
+ */
49
+ ranOut(error: PrizedError, now: number, cut: boolean): boolean;
50
+ /** The bound on a poll sent `now`: undefined unless polls are failing; else the last look's start, or the window's end for the last look. */
51
+ deadline(now: number): number | undefined;
52
+ /** A poll was answered: the outage, if any, is over. */
53
+ answered(): void;
54
+ /**
55
+ * A poll sent at `sentAt` failed `now` with `error` (`cut`: its bound cut
56
+ * it short). Returns the pause before polling again, or null to give up,
57
+ * with the failure to give up with: the last one a poll met on its own,
58
+ * never the cut.
59
+ */
60
+ failed(error: PrizedError, sentAt: number, now: number, cut: boolean, backoff: (tries: number) => number): {
61
+ pause: number | null;
62
+ failure: PrizedError;
63
+ };
64
+ }
65
+ export interface ExecOptions {
66
+ /** Working directory. Relative paths start in /home/prized/work (the default). */
67
+ cwd?: string;
68
+ /**
69
+ * Environment variables for this command, on top of the sandbox's own.
70
+ * Background and streamed commands also get PYTHONUNBUFFERED=1 unless
71
+ * this or the sandbox sets it (`''` keeps Python's buffering).
72
+ */
73
+ env?: Record<string, string>;
74
+ /**
75
+ * Kill the command after this many ms. Default 60 000. `0` means no limit.
76
+ * For a background command the default is no limit.
77
+ */
78
+ timeoutMs?: number;
79
+ /** Called with stdout as it arrives (the command then runs detached and is polled). */
80
+ onStdout?: (chunk: string) => void;
81
+ /** Called with stderr as it arrives. */
82
+ onStderr?: (chunk: string) => void;
83
+ /** Start the command and return a handle at once instead of waiting for it. */
84
+ background?: boolean;
85
+ /**
86
+ * Keep the whole output, not just the first 4 MiB of each stream: up to
87
+ * 100 MiB each (`truncated` past that). Still one request when the output
88
+ * is small; past 4 MiB the SDK reads it back from files the command wrote
89
+ * in the sandbox. Use it for builds, test suites and anything else whose
90
+ * output you need whole. Background, streamed and long commands read
91
+ * their whole output already.
92
+ */
93
+ fullOutput?: boolean;
94
+ /**
95
+ * Abort the call. The command stops with it, as when a blocking call is
96
+ * cut off: the call rejects once the SDK has tried to stop it (a round
97
+ * trip or two; it gives up after about 20 s). With `background: true`
98
+ * this covers the start only; once exec has returned the handle, its
99
+ * kill() stops the command.
100
+ */
101
+ signal?: AbortSignal;
102
+ /**
103
+ * For a streamed or long command: how long the SDK keeps polling while
104
+ * its polls fail (no answer, a 5xx, a rate limit), in ms, before it gives
105
+ * up and stops the command. Default: the client's `pollOutageMs`
106
+ * (120 000). A refused key, or an answer that settles the run (the
107
+ * sandbox deleted or not running, the run gone), stops it at once.
108
+ */
109
+ pollOutageMs?: number;
110
+ }
111
+ export interface WaitOptions {
112
+ /**
113
+ * Stop waiting after this many ms (the command keeps running). Default: no
114
+ * limit. Once the command has ended, reading the rest of its output is not
115
+ * held to it.
116
+ */
117
+ timeoutMs?: number;
118
+ onStdout?: (chunk: string) => void;
119
+ onStderr?: (chunk: string) => void;
120
+ signal?: AbortSignal;
121
+ /**
122
+ * How long the wait keeps polling while its polls fail (no answer, a 5xx,
123
+ * a rate limit), in ms, before it gives up and throws what the polls met.
124
+ * While the command runs, never past `timeoutMs`. Default: the client's
125
+ * `pollOutageMs` (120 000).
126
+ * `0` gives up once one poll's retries run out. A refused key, or an
127
+ * answer that settles the run (the sandbox deleted or not running, the
128
+ * run gone), ends the wait at once. Giving up stops nothing: the command
129
+ * keeps running, and wait() can be called again.
130
+ */
131
+ pollOutageMs?: number;
132
+ }
133
+ export interface CommandStatus {
134
+ running: boolean;
135
+ /** The exit code once it has one; -1 if its time limit stopped it or it ended without reporting one. */
136
+ exitCode: number | null;
137
+ }
138
+ /**
139
+ * A command running in the background (`sandbox.exec(cmd, { background: true })`).
140
+ * It runs in its own session: it keeps running after this process exits, and
141
+ * while it runs, kill() reaches every process it started in its process group.
142
+ */
143
+ export declare class CommandHandle {
144
+ /** The run's id (`run_…`). */
145
+ readonly id: string;
146
+ /** The process group id of the command inside the sandbox. */
147
+ readonly pid: number;
148
+ readonly sandboxId: string;
149
+ private readonly toolbox;
150
+ private readonly startedAt;
151
+ /** @internal The time limit exec gave the command, in ms; null for none. */
152
+ timeoutMs: number | null;
153
+ /** @internal */
154
+ constructor(toolbox: Toolbox, id: string, pid: number, startedAt: number);
155
+ private poll;
156
+ /** Whether the command is still running, and its exit code once it is not. */
157
+ status(): Promise<CommandStatus>;
158
+ /** Everything the command has printed so far. */
159
+ logs(): Promise<{
160
+ stdout: string;
161
+ stderr: string;
162
+ }>;
163
+ /**
164
+ * Wait for the command to finish and return everything it printed.
165
+ * `onStdout`/`onStderr` receive the output as it arrives, from the start.
166
+ * A `timeoutMs` here bounds the wait only; the command keeps running. If
167
+ * the command ran past its own timeout (the one given to exec), it was
168
+ * killed and this throws CommandTimeoutError with what it printed. While
169
+ * polls fail (a dropped connection, a 5xx), it keeps polling for up to
170
+ * `pollOutageMs` (the client's, 120 000 by default) before it throws.
171
+ */
172
+ wait(options?: WaitOptions): Promise<ExecResult>;
173
+ /**
174
+ * @internal wait() without the timeout check: exec cleans up before it
175
+ * throws. `outageMs` (pollOutageMs): how long polls may fail before the
176
+ * wait gives up (Outage), never past the wait's own deadline.
177
+ */
178
+ waitRaw(options: WaitOptions, outageMs: number): Promise<ExecResult>;
179
+ private waitTimeout;
180
+ private result;
181
+ /**
182
+ * Send a signal (default SIGTERM) to the command and everything it
183
+ * started in its process group. Resolves true if it was sent, false if
184
+ * the run had already ended. Processes that left the group (`setsid`, a
185
+ * daemon) are not reached, nor anything after the run itself has ended.
186
+ */
187
+ kill(signal?: string): Promise<boolean>;
188
+ /**
189
+ * Remove the command's files (its output under `~/.prized/runs/<id>/`)
190
+ * from the sandbox once it has ended: call it after wait() resolves or
191
+ * throws CommandTimeoutError (after a kill(), await wait() first). On a
192
+ * command still running it does nothing, so status(), wait() and kill()
193
+ * keep working. Once its files are gone, status(), logs(), wait() and
194
+ * kill() reject with `not_found`. Best effort: it never rejects.
195
+ */
196
+ cleanup(): Promise<void>;
197
+ }
198
+ /** sandbox.exec's body. */
199
+ export declare function execCommand(toolbox: Toolbox, command: string | readonly string[], options?: ExecOptions): Promise<ExecResult | CommandHandle>;