@spheron/sandbox 0.0.0-stage → 0.1.0
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/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +164 -2
- package/dist/commands.d.ts +112 -0
- package/dist/commands.js +223 -0
- package/dist/commands.js.map +1 -0
- package/dist/errors.d.ts +108 -0
- package/dist/errors.js +101 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +32 -0
- package/dist/files.js +144 -0
- package/dist/files.js.map +1 -0
- package/dist/http.d.ts +37 -0
- package/dist/http.js +198 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/offers.d.ts +25 -0
- package/dist/offers.js +55 -0
- package/dist/offers.js.map +1 -0
- package/dist/sandbox.d.ts +140 -0
- package/dist/sandbox.js +332 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -4
package/dist/commands.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { CommandExitError, SandboxUnreachableError, TimeoutError } from './errors.js';
|
|
2
|
+
import { sleep } from './http.js';
|
|
3
|
+
import { FINAL_STATUSES } from './types.js';
|
|
4
|
+
/** How long one output read may be held open by the server waiting for new output (it allows up to 60). */
|
|
5
|
+
const LONG_POLL_SEC = 25;
|
|
6
|
+
/** How long run()'s first request may wait for the command to finish (the server allows up to 60). */
|
|
7
|
+
const RUN_WAIT_SEC = 50;
|
|
8
|
+
/** The server's own default command timeout, used to bound run()'s wait when none is given. */
|
|
9
|
+
const DEFAULT_TIMEOUT_SEC = 600;
|
|
10
|
+
/** run() waits this long past the command's timeout before giving up on hearing back (an unreachable instance). */
|
|
11
|
+
const RUN_WAIT_GRACE_MS = 5 * 60_000;
|
|
12
|
+
const byteLength = (s) => new TextEncoder().encode(s).length;
|
|
13
|
+
/** A command on the sandbox, running or finished. Get one from `sandbox.commands.start()` or `.get()`. */
|
|
14
|
+
export class CommandHandle {
|
|
15
|
+
http;
|
|
16
|
+
base;
|
|
17
|
+
pollIntervalMs;
|
|
18
|
+
id;
|
|
19
|
+
currentStatus = 'queued';
|
|
20
|
+
currentExitCode;
|
|
21
|
+
/** Byte offset of the next output to read. */
|
|
22
|
+
offset = 0;
|
|
23
|
+
output = '';
|
|
24
|
+
last;
|
|
25
|
+
/** @internal Use sandbox.commands.start() or .get(). */
|
|
26
|
+
constructor(http, base, commandId, pollIntervalMs, view) {
|
|
27
|
+
this.http = http;
|
|
28
|
+
this.base = base;
|
|
29
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
30
|
+
this.id = commandId;
|
|
31
|
+
if (view)
|
|
32
|
+
this.absorb(view);
|
|
33
|
+
}
|
|
34
|
+
/** Last status seen: queued, running, done, failed, killed or expired. */
|
|
35
|
+
get status() {
|
|
36
|
+
return this.currentStatus;
|
|
37
|
+
}
|
|
38
|
+
/** Set once the command has exited. */
|
|
39
|
+
get exitCode() {
|
|
40
|
+
return this.currentExitCode;
|
|
41
|
+
}
|
|
42
|
+
/** The command has finished (done, failed, killed or expired). */
|
|
43
|
+
get finished() {
|
|
44
|
+
return FINAL_STATUSES.has(this.currentStatus);
|
|
45
|
+
}
|
|
46
|
+
/** Output this handle has collected so far. */
|
|
47
|
+
get outputSoFar() {
|
|
48
|
+
return this.output;
|
|
49
|
+
}
|
|
50
|
+
/** Takes in an answer and returns its new output. The server reads from our offset, so new output starts there. */
|
|
51
|
+
absorb(view) {
|
|
52
|
+
this.last = view;
|
|
53
|
+
this.currentStatus = view.status;
|
|
54
|
+
if (typeof view.exitCode === 'number')
|
|
55
|
+
this.currentExitCode = view.exitCode;
|
|
56
|
+
if (view.outputOffset !== this.offset || !view.output)
|
|
57
|
+
return '';
|
|
58
|
+
this.offset = view.nextOffset ?? view.outputOffset + byteLength(view.output);
|
|
59
|
+
this.output += view.output;
|
|
60
|
+
return view.output;
|
|
61
|
+
}
|
|
62
|
+
result() {
|
|
63
|
+
const v = this.last;
|
|
64
|
+
return {
|
|
65
|
+
commandId: this.id,
|
|
66
|
+
status: this.currentStatus,
|
|
67
|
+
exitCode: this.currentExitCode,
|
|
68
|
+
ok: this.currentStatus === 'done' && this.currentExitCode === 0,
|
|
69
|
+
output: this.output,
|
|
70
|
+
outputTruncated: !!v?.outputTruncated,
|
|
71
|
+
logPath: v?.logPath,
|
|
72
|
+
error: v?.error,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Reads the status and any new output once. With waitSec, the server holds the answer until there is some. */
|
|
76
|
+
async poll(opts = {}) {
|
|
77
|
+
const waitSec = opts.waitSec ?? 0;
|
|
78
|
+
const view = await this.http.request('GET', `${this.base}/commands/${encodeURIComponent(this.id)}`, {
|
|
79
|
+
query: { offset: this.offset, waitSec: waitSec > 0 ? waitSec : undefined },
|
|
80
|
+
timeoutMs: waitSec * 1000 + 30_000,
|
|
81
|
+
signal: opts.signal,
|
|
82
|
+
});
|
|
83
|
+
return this.absorb(view);
|
|
84
|
+
}
|
|
85
|
+
/** New output until the command finishes, reading at most once per poll interval. */
|
|
86
|
+
async *follow(signal) {
|
|
87
|
+
while (!this.finished) {
|
|
88
|
+
const started = Date.now();
|
|
89
|
+
const chunk = await this.poll({ waitSec: LONG_POLL_SEC, signal });
|
|
90
|
+
if (chunk)
|
|
91
|
+
yield chunk;
|
|
92
|
+
if (this.finished)
|
|
93
|
+
return;
|
|
94
|
+
// Also when the server answers at once (output flowing).
|
|
95
|
+
const left = this.pollIntervalMs - (Date.now() - started);
|
|
96
|
+
if (left > 0)
|
|
97
|
+
await sleep(left, signal);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Output as it arrives, until the command finishes: first what this handle already has, then each new piece.
|
|
102
|
+
* The command keeps running if you stop iterating.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* for await (const chunk of handle.stream()) process.stdout.write(chunk);
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
async *stream(opts = {}) {
|
|
109
|
+
if (this.output)
|
|
110
|
+
yield this.output;
|
|
111
|
+
yield* this.follow(opts.signal);
|
|
112
|
+
return this.result();
|
|
113
|
+
}
|
|
114
|
+
/** Wait for the command to finish and return everything it printed. */
|
|
115
|
+
async wait(opts = {}) {
|
|
116
|
+
if (opts.signal?.aborted)
|
|
117
|
+
throw opts.signal.reason;
|
|
118
|
+
const ac = new AbortController();
|
|
119
|
+
const onAbort = () => ac.abort(opts.signal.reason);
|
|
120
|
+
opts.signal?.addEventListener('abort', onAbort, { once: true });
|
|
121
|
+
const timer = opts.timeoutMs !== undefined ? setTimeout(() => ac.abort(new TimeoutError('timeout')), opts.timeoutMs) : undefined;
|
|
122
|
+
try {
|
|
123
|
+
if (this.output)
|
|
124
|
+
opts.onOutput?.(this.output);
|
|
125
|
+
for await (const chunk of this.follow(ac.signal))
|
|
126
|
+
opts.onOutput?.(chunk);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (ac.signal.reason instanceof TimeoutError && error === ac.signal.reason) {
|
|
130
|
+
throw new TimeoutError(`Command ${this.id} did not finish within ${opts.timeoutMs} ms. It is still ${this.currentStatus} on the instance.`);
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
opts.signal?.removeEventListener('abort', onAbort);
|
|
137
|
+
}
|
|
138
|
+
const result = this.result();
|
|
139
|
+
if (opts.check)
|
|
140
|
+
assertSucceeded(result);
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
/** Stop the command and everything it started. Safe on one that already finished. */
|
|
144
|
+
async kill() {
|
|
145
|
+
const out = await this.http.request('POST', `${this.base}/commands/${encodeURIComponent(this.id)}/kill`, {
|
|
146
|
+
idempotent: true,
|
|
147
|
+
});
|
|
148
|
+
this.currentStatus = out.status;
|
|
149
|
+
return out.status;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function assertSucceeded(r) {
|
|
153
|
+
if (r.ok)
|
|
154
|
+
return;
|
|
155
|
+
// A non-zero exit comes back as failed with its exit code; a timeout or a kill also carries an error.
|
|
156
|
+
const why = typeof r.exitCode === 'number' && r.exitCode !== 0
|
|
157
|
+
? `exited with code ${r.exitCode}${r.error ? ` (${r.error})` : ''}`
|
|
158
|
+
: `ended as ${r.status}${r.error ? `: ${r.error}` : ''}`;
|
|
159
|
+
throw new CommandExitError(`Command ${r.commandId} ${why}`, { commandId: r.commandId, exitCode: r.exitCode, status: r.status, output: r.output });
|
|
160
|
+
}
|
|
161
|
+
/** Shell commands on the sandbox. Use it as `sandbox.commands`. */
|
|
162
|
+
export class Commands {
|
|
163
|
+
http;
|
|
164
|
+
base;
|
|
165
|
+
pollIntervalMs;
|
|
166
|
+
/** @internal */
|
|
167
|
+
constructor(http, base, pollIntervalMs) {
|
|
168
|
+
this.http = http;
|
|
169
|
+
this.base = base;
|
|
170
|
+
this.pollIntervalMs = pollIntervalMs;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Run a command with bash, as the login user (with sudo), and wait for it to finish.
|
|
174
|
+
*
|
|
175
|
+
* ```ts
|
|
176
|
+
* const r = await sandbox.commands.run('nvidia-smi');
|
|
177
|
+
* console.log(r.exitCode, r.output);
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* It is killed after timeoutSec (default 600): pass a larger one, up to 86400, for long jobs, or use start().
|
|
181
|
+
* Throws SandboxUnreachableError (with the command id and a recovery command) when the instance cannot be reached;
|
|
182
|
+
* the command then stays queued and runs once the sandbox is back, unless you kill it.
|
|
183
|
+
*/
|
|
184
|
+
async run(command, opts = {}) {
|
|
185
|
+
// With onOutput, answer at once and stream; otherwise let the first request wait for a short command to end.
|
|
186
|
+
const waitSec = opts.onOutput ? 0 : RUN_WAIT_SEC;
|
|
187
|
+
const view = await this.http.request('POST', `${this.base}/commands`, {
|
|
188
|
+
body: { command, cwd: opts.cwd, env: opts.env, timeoutSec: opts.timeoutSec, waitSec },
|
|
189
|
+
timeoutMs: waitSec * 1000 + 30_000,
|
|
190
|
+
signal: opts.signal,
|
|
191
|
+
});
|
|
192
|
+
if (!FINAL_STATUSES.has(view.status) && view.recovery)
|
|
193
|
+
throw unreachable(view);
|
|
194
|
+
const handle = new CommandHandle(this.http, this.base, view.commandId, this.pollIntervalMs, view);
|
|
195
|
+
const waitTimeoutMs = opts.waitTimeoutMs ?? (opts.timeoutSec ?? DEFAULT_TIMEOUT_SEC) * 1000 + RUN_WAIT_GRACE_MS;
|
|
196
|
+
return handle.wait({ onOutput: opts.onOutput, timeoutMs: waitTimeoutMs, check: opts.check, signal: opts.signal });
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Start a command in the background and return right away. Use the handle to stream output, wait or kill it.
|
|
200
|
+
*
|
|
201
|
+
* ```ts
|
|
202
|
+
* const job = await sandbox.commands.start('python train.py');
|
|
203
|
+
* for await (const chunk of job.stream()) process.stdout.write(chunk);
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async start(command, opts = {}) {
|
|
207
|
+
const view = await this.http.request('POST', `${this.base}/commands`, {
|
|
208
|
+
body: { command, cwd: opts.cwd, env: opts.env, timeoutSec: opts.timeoutSec, background: true, waitSec: 0 },
|
|
209
|
+
});
|
|
210
|
+
return new CommandHandle(this.http, this.base, view.commandId, this.pollIntervalMs, view);
|
|
211
|
+
}
|
|
212
|
+
/** A handle on a command started earlier, for example from another process. */
|
|
213
|
+
async get(commandId) {
|
|
214
|
+
const handle = new CommandHandle(this.http, this.base, commandId, this.pollIntervalMs);
|
|
215
|
+
await handle.poll();
|
|
216
|
+
return handle;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function unreachable(view) {
|
|
220
|
+
const message = `${view.recovery.message} Command ${view.commandId} is queued and runs once the sandbox is back, unless you kill it.`;
|
|
221
|
+
return new SandboxUnreachableError(503, message, { recovery: view.recovery, commandId: view.commandId, body: view });
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** What to do when Spheron cannot reach the sandbox program on the instance. */
|
|
2
|
+
export interface Recovery {
|
|
3
|
+
message: string;
|
|
4
|
+
/** A one-line command to run over SSH on the instance. It brings the sandbox back. Expires within 30 minutes. */
|
|
5
|
+
command?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Base class for every error this SDK throws. */
|
|
8
|
+
export declare class SpheronError extends Error {
|
|
9
|
+
/** The instance this error is about, when one was already created (it bills until it is terminated). */
|
|
10
|
+
deploymentId?: string;
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/** The Spheron API refused a request. */
|
|
14
|
+
export declare class SpheronApiError extends SpheronError {
|
|
15
|
+
/** HTTP status. */
|
|
16
|
+
readonly status: number;
|
|
17
|
+
/** Stable error code from the API (for example `insufficient_credits`), when it sent one. */
|
|
18
|
+
readonly code?: string;
|
|
19
|
+
/** The response's X-Request-Id: quote it when asking Spheron support about a failure. */
|
|
20
|
+
readonly requestId?: string;
|
|
21
|
+
/** The parsed response body. */
|
|
22
|
+
readonly body: unknown;
|
|
23
|
+
constructor(status: number, message: string, opts?: {
|
|
24
|
+
code?: string;
|
|
25
|
+
requestId?: string;
|
|
26
|
+
body?: unknown;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** 401 or 403 on the API key: missing, wrong, expired, an Inference key, or used from an address it does not allow. */
|
|
30
|
+
export declare class AuthenticationError extends SpheronApiError {
|
|
31
|
+
}
|
|
32
|
+
/** 404: no such instance on this account, no sandbox on it, or it was deployed by someone else on the team. */
|
|
33
|
+
export declare class NotFoundError extends SpheronApiError {
|
|
34
|
+
}
|
|
35
|
+
/** 429: too many requests (or too many file transfers at once). The SDK already retried before throwing this. */
|
|
36
|
+
export declare class RateLimitError extends SpheronApiError {
|
|
37
|
+
/** Seconds the API asked to wait, when it said. */
|
|
38
|
+
readonly retryAfterSec?: number;
|
|
39
|
+
constructor(status: number, message: string, opts?: {
|
|
40
|
+
code?: string;
|
|
41
|
+
requestId?: string;
|
|
42
|
+
body?: unknown;
|
|
43
|
+
retryAfterSec?: number;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** 409: the instance is stopped, terminated or failed. Start it (or deploy a new one) to use the sandbox again. */
|
|
47
|
+
export declare class InstanceNotRunningError extends SpheronApiError {
|
|
48
|
+
}
|
|
49
|
+
/** 409: the sandbox program is still being installed. `Sandbox.waitUntilReady()` waits for this to pass. */
|
|
50
|
+
export declare class SandboxNotReadyError extends SpheronApiError {
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Spheron cannot reach the sandbox program on the instance. `recovery.command` is a one-line command to run over SSH
|
|
54
|
+
* on the instance to bring it back. From commands.run(), `commandId` names the command, which stays queued and runs
|
|
55
|
+
* once the sandbox is back unless it is killed.
|
|
56
|
+
*/
|
|
57
|
+
export declare class SandboxUnreachableError extends SpheronApiError {
|
|
58
|
+
readonly recovery?: Recovery;
|
|
59
|
+
readonly commandId?: string;
|
|
60
|
+
constructor(status: number, message: string, opts?: {
|
|
61
|
+
code?: string;
|
|
62
|
+
requestId?: string;
|
|
63
|
+
body?: unknown;
|
|
64
|
+
recovery?: Recovery;
|
|
65
|
+
commandId?: string;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** A wait in the SDK (for the sandbox to be ready, or for a command to finish) ran out of time. */
|
|
69
|
+
export declare class TimeoutError extends SpheronError {
|
|
70
|
+
}
|
|
71
|
+
/** `Sandbox.create()` found no offer it could deploy with the sandbox. */
|
|
72
|
+
export declare class NoOfferError extends SpheronError {
|
|
73
|
+
/** Why each offer that was tried was refused. */
|
|
74
|
+
readonly attempts: {
|
|
75
|
+
offerId: string;
|
|
76
|
+
provider: string;
|
|
77
|
+
reason: string;
|
|
78
|
+
}[];
|
|
79
|
+
constructor(message: string, attempts?: {
|
|
80
|
+
offerId: string;
|
|
81
|
+
provider: string;
|
|
82
|
+
reason: string;
|
|
83
|
+
}[]);
|
|
84
|
+
}
|
|
85
|
+
/** The sandbox install failed or the sandbox was removed. `recoveryCommand` brings it back when present. */
|
|
86
|
+
export declare class SandboxFailedError extends SpheronError {
|
|
87
|
+
readonly state: string;
|
|
88
|
+
readonly failureReason?: string;
|
|
89
|
+
readonly recoveryCommand?: string;
|
|
90
|
+
constructor(message: string, opts: {
|
|
91
|
+
state: string;
|
|
92
|
+
failureReason?: string;
|
|
93
|
+
recoveryCommand?: string;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** A command finished with a non-zero exit code, from `run(..., { check: true })`. */
|
|
97
|
+
export declare class CommandExitError extends SpheronError {
|
|
98
|
+
readonly commandId: string;
|
|
99
|
+
readonly exitCode?: number;
|
|
100
|
+
readonly status: string;
|
|
101
|
+
readonly output: string;
|
|
102
|
+
constructor(message: string, opts: {
|
|
103
|
+
commandId: string;
|
|
104
|
+
exitCode?: number;
|
|
105
|
+
status: string;
|
|
106
|
+
output: string;
|
|
107
|
+
});
|
|
108
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Base class for every error this SDK throws. */
|
|
2
|
+
export class SpheronError extends Error {
|
|
3
|
+
/** The instance this error is about, when one was already created (it bills until it is terminated). */
|
|
4
|
+
deploymentId;
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = new.target.name;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** The Spheron API refused a request. */
|
|
11
|
+
export class SpheronApiError extends SpheronError {
|
|
12
|
+
/** HTTP status. */
|
|
13
|
+
status;
|
|
14
|
+
/** Stable error code from the API (for example `insufficient_credits`), when it sent one. */
|
|
15
|
+
code;
|
|
16
|
+
/** The response's X-Request-Id: quote it when asking Spheron support about a failure. */
|
|
17
|
+
requestId;
|
|
18
|
+
/** The parsed response body. */
|
|
19
|
+
body;
|
|
20
|
+
constructor(status, message, opts = {}) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.code = opts.code;
|
|
24
|
+
this.requestId = opts.requestId;
|
|
25
|
+
this.body = opts.body;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** 401 or 403 on the API key: missing, wrong, expired, an Inference key, or used from an address it does not allow. */
|
|
29
|
+
export class AuthenticationError extends SpheronApiError {
|
|
30
|
+
}
|
|
31
|
+
/** 404: no such instance on this account, no sandbox on it, or it was deployed by someone else on the team. */
|
|
32
|
+
export class NotFoundError extends SpheronApiError {
|
|
33
|
+
}
|
|
34
|
+
/** 429: too many requests (or too many file transfers at once). The SDK already retried before throwing this. */
|
|
35
|
+
export class RateLimitError extends SpheronApiError {
|
|
36
|
+
/** Seconds the API asked to wait, when it said. */
|
|
37
|
+
retryAfterSec;
|
|
38
|
+
constructor(status, message, opts = {}) {
|
|
39
|
+
super(status, message, opts);
|
|
40
|
+
this.retryAfterSec = opts.retryAfterSec;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** 409: the instance is stopped, terminated or failed. Start it (or deploy a new one) to use the sandbox again. */
|
|
44
|
+
export class InstanceNotRunningError extends SpheronApiError {
|
|
45
|
+
}
|
|
46
|
+
/** 409: the sandbox program is still being installed. `Sandbox.waitUntilReady()` waits for this to pass. */
|
|
47
|
+
export class SandboxNotReadyError extends SpheronApiError {
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Spheron cannot reach the sandbox program on the instance. `recovery.command` is a one-line command to run over SSH
|
|
51
|
+
* on the instance to bring it back. From commands.run(), `commandId` names the command, which stays queued and runs
|
|
52
|
+
* once the sandbox is back unless it is killed.
|
|
53
|
+
*/
|
|
54
|
+
export class SandboxUnreachableError extends SpheronApiError {
|
|
55
|
+
recovery;
|
|
56
|
+
commandId;
|
|
57
|
+
constructor(status, message, opts = {}) {
|
|
58
|
+
super(status, message, opts);
|
|
59
|
+
this.recovery = opts.recovery;
|
|
60
|
+
this.commandId = opts.commandId;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** A wait in the SDK (for the sandbox to be ready, or for a command to finish) ran out of time. */
|
|
64
|
+
export class TimeoutError extends SpheronError {
|
|
65
|
+
}
|
|
66
|
+
/** `Sandbox.create()` found no offer it could deploy with the sandbox. */
|
|
67
|
+
export class NoOfferError extends SpheronError {
|
|
68
|
+
/** Why each offer that was tried was refused. */
|
|
69
|
+
attempts;
|
|
70
|
+
constructor(message, attempts = []) {
|
|
71
|
+
super(message);
|
|
72
|
+
this.attempts = attempts;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** The sandbox install failed or the sandbox was removed. `recoveryCommand` brings it back when present. */
|
|
76
|
+
export class SandboxFailedError extends SpheronError {
|
|
77
|
+
state;
|
|
78
|
+
failureReason;
|
|
79
|
+
recoveryCommand;
|
|
80
|
+
constructor(message, opts) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.state = opts.state;
|
|
83
|
+
this.failureReason = opts.failureReason;
|
|
84
|
+
this.recoveryCommand = opts.recoveryCommand;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** A command finished with a non-zero exit code, from `run(..., { check: true })`. */
|
|
88
|
+
export class CommandExitError extends SpheronError {
|
|
89
|
+
commandId;
|
|
90
|
+
exitCode;
|
|
91
|
+
status;
|
|
92
|
+
output;
|
|
93
|
+
constructor(message, opts) {
|
|
94
|
+
super(message);
|
|
95
|
+
this.commandId = opts.commandId;
|
|
96
|
+
this.exitCode = opts.exitCode;
|
|
97
|
+
this.status = opts.status;
|
|
98
|
+
this.output = opts.output;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HttpClient } from './http.js';
|
|
2
|
+
import type { FileEntry } from './types.js';
|
|
3
|
+
/** The most one read or write moves. Larger data: download it on the instance with commands.run (curl, git, huggingface-cli). */
|
|
4
|
+
export declare const MAX_FILE_BYTES: number;
|
|
5
|
+
export declare function toBase64(bytes: Uint8Array): string;
|
|
6
|
+
export declare function fromBase64(b64: string): Uint8Array;
|
|
7
|
+
/**
|
|
8
|
+
* Files on the sandbox. Use it as `sandbox.files`. Each read or write moves up to 5 MB. An account moves at most two
|
|
9
|
+
* files at once: this sandbox runs two at a time and queues the rest, and a 429 from transfers elsewhere is retried.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Files {
|
|
12
|
+
private readonly http;
|
|
13
|
+
private readonly base;
|
|
14
|
+
private readonly slots;
|
|
15
|
+
/** @internal */
|
|
16
|
+
constructor(http: HttpClient, base: string);
|
|
17
|
+
/** Read a text file (UTF-8). Throws for anything that is not valid UTF-8: use readBytes for binary files. */
|
|
18
|
+
read(path: string): Promise<string>;
|
|
19
|
+
/** Read a file's raw bytes, up to 5 MB. */
|
|
20
|
+
readBytes(path: string): Promise<Uint8Array>;
|
|
21
|
+
/** Write a file, replacing it if it exists. Strings are written as UTF-8. mode is octal, like '0755'. */
|
|
22
|
+
write(path: string, data: string | Uint8Array | ArrayBuffer, opts?: {
|
|
23
|
+
mode?: string;
|
|
24
|
+
}): Promise<{
|
|
25
|
+
path: string;
|
|
26
|
+
size: number;
|
|
27
|
+
}>;
|
|
28
|
+
/** List a directory: each entry's name, type (file, dir or other) and size. */
|
|
29
|
+
list(path: string): Promise<FileEntry[]>;
|
|
30
|
+
/** Whether a path exists on the instance. Other failures, such as permission denied, are thrown. */
|
|
31
|
+
exists(path: string): Promise<boolean>;
|
|
32
|
+
}
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { SpheronApiError, SpheronError } from './errors.js';
|
|
2
|
+
/** The most one read or write moves. Larger data: download it on the instance with commands.run (curl, git, huggingface-cli). */
|
|
3
|
+
export const MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
4
|
+
/** How long a file operation may wait for the instance (the server allows up to 60). */
|
|
5
|
+
const FILE_WAIT_SEC = 50;
|
|
6
|
+
/** The API moves at most two files at once per account; more from one sandbox would only queue on 429s. */
|
|
7
|
+
const TRANSFER_SLOTS = 2;
|
|
8
|
+
/** What a missing path answers with (a failed file operation): the instance's own error text. */
|
|
9
|
+
const MISSING = /no such file or directory|not a directory/i;
|
|
10
|
+
/** Lets at most `size` callers run at once; the rest wait their turn. */
|
|
11
|
+
class Slots {
|
|
12
|
+
free;
|
|
13
|
+
waiting = [];
|
|
14
|
+
constructor(size) {
|
|
15
|
+
this.free = size;
|
|
16
|
+
}
|
|
17
|
+
async run(fn) {
|
|
18
|
+
if (this.free > 0)
|
|
19
|
+
this.free--;
|
|
20
|
+
else
|
|
21
|
+
await new Promise((resolve) => this.waiting.push(resolve));
|
|
22
|
+
try {
|
|
23
|
+
return await fn();
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
const next = this.waiting.shift();
|
|
27
|
+
if (next)
|
|
28
|
+
next();
|
|
29
|
+
else
|
|
30
|
+
this.free++;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function toBase64(bytes) {
|
|
35
|
+
const B = globalThis.Buffer;
|
|
36
|
+
if (B)
|
|
37
|
+
return B.from(bytes).toString('base64');
|
|
38
|
+
let bin = '';
|
|
39
|
+
for (let i = 0; i < bytes.length; i += 0x8000)
|
|
40
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
41
|
+
return btoa(bin);
|
|
42
|
+
}
|
|
43
|
+
export function fromBase64(b64) {
|
|
44
|
+
const B = globalThis.Buffer;
|
|
45
|
+
if (B)
|
|
46
|
+
return new Uint8Array(B.from(b64, 'base64'));
|
|
47
|
+
const bin = atob(b64);
|
|
48
|
+
const out = new Uint8Array(bin.length);
|
|
49
|
+
for (let i = 0; i < bin.length; i++)
|
|
50
|
+
out[i] = bin.charCodeAt(i);
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function toBytes(data) {
|
|
54
|
+
if (typeof data === 'string')
|
|
55
|
+
return new TextEncoder().encode(data);
|
|
56
|
+
if (data instanceof Uint8Array)
|
|
57
|
+
return data;
|
|
58
|
+
if (data instanceof ArrayBuffer)
|
|
59
|
+
return new Uint8Array(data);
|
|
60
|
+
throw new SpheronError('data must be a string, a Uint8Array or an ArrayBuffer');
|
|
61
|
+
}
|
|
62
|
+
function checkPath(path) {
|
|
63
|
+
if (typeof path !== 'string' || !path.startsWith('/'))
|
|
64
|
+
throw new SpheronError(`path must be absolute, like /root/data.txt (got ${JSON.stringify(path)})`);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Files on the sandbox. Use it as `sandbox.files`. Each read or write moves up to 5 MB. An account moves at most two
|
|
68
|
+
* files at once: this sandbox runs two at a time and queues the rest, and a 429 from transfers elsewhere is retried.
|
|
69
|
+
*/
|
|
70
|
+
export class Files {
|
|
71
|
+
http;
|
|
72
|
+
base;
|
|
73
|
+
slots = new Slots(TRANSFER_SLOTS);
|
|
74
|
+
/** @internal */
|
|
75
|
+
constructor(http, base) {
|
|
76
|
+
this.http = http;
|
|
77
|
+
this.base = base;
|
|
78
|
+
}
|
|
79
|
+
/** Read a text file (UTF-8). Throws for anything that is not valid UTF-8: use readBytes for binary files. */
|
|
80
|
+
async read(path) {
|
|
81
|
+
const bytes = await this.readBytes(path);
|
|
82
|
+
try {
|
|
83
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new SpheronError(`${path} is not a UTF-8 text file. Use files.readBytes() for binary files.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Read a file's raw bytes, up to 5 MB. */
|
|
90
|
+
async readBytes(path) {
|
|
91
|
+
checkPath(path);
|
|
92
|
+
const out = await this.slots.run(() => this.http.request('POST', `${this.base}/files/read`, {
|
|
93
|
+
body: { path, waitSec: FILE_WAIT_SEC },
|
|
94
|
+
timeoutMs: FILE_WAIT_SEC * 1000 + 30_000,
|
|
95
|
+
idempotent: true,
|
|
96
|
+
}));
|
|
97
|
+
return fromBase64(out.contentB64 ?? '');
|
|
98
|
+
}
|
|
99
|
+
/** Write a file, replacing it if it exists. Strings are written as UTF-8. mode is octal, like '0755'. */
|
|
100
|
+
async write(path, data, opts = {}) {
|
|
101
|
+
checkPath(path);
|
|
102
|
+
const bytes = toBytes(data);
|
|
103
|
+
if (bytes.length > MAX_FILE_BYTES) {
|
|
104
|
+
throw new SpheronError(`${path} is ${bytes.length} bytes; one write moves at most 5 MB. Download large data on the instance instead, for example with commands.run('curl -L -o ${path} <url>').`);
|
|
105
|
+
}
|
|
106
|
+
if (opts.mode !== undefined && !/^[0-7]{3,4}$/.test(opts.mode))
|
|
107
|
+
throw new SpheronError(`mode must be octal, like '0644' (got ${JSON.stringify(opts.mode)})`);
|
|
108
|
+
return this.slots.run(() => this.http.request('POST', `${this.base}/files/write`, {
|
|
109
|
+
body: { path, contentB64: toBase64(bytes), mode: opts.mode, waitSec: FILE_WAIT_SEC },
|
|
110
|
+
timeoutMs: FILE_WAIT_SEC * 1000 + 60_000,
|
|
111
|
+
// Writing the same bytes again leaves the same file.
|
|
112
|
+
idempotent: true,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
/** List a directory: each entry's name, type (file, dir or other) and size. */
|
|
116
|
+
async list(path) {
|
|
117
|
+
checkPath(path);
|
|
118
|
+
const out = await this.http.request('POST', `${this.base}/files/list`, {
|
|
119
|
+
body: { path, waitSec: FILE_WAIT_SEC },
|
|
120
|
+
timeoutMs: FILE_WAIT_SEC * 1000 + 30_000,
|
|
121
|
+
idempotent: true,
|
|
122
|
+
});
|
|
123
|
+
return out.entries ?? [];
|
|
124
|
+
}
|
|
125
|
+
/** Whether a path exists on the instance. Other failures, such as permission denied, are thrown. */
|
|
126
|
+
async exists(path) {
|
|
127
|
+
checkPath(path);
|
|
128
|
+
const trimmed = path.replace(/\/+$/, '');
|
|
129
|
+
const slash = trimmed.lastIndexOf('/');
|
|
130
|
+
const name = trimmed.slice(slash + 1);
|
|
131
|
+
if (!name)
|
|
132
|
+
return true;
|
|
133
|
+
try {
|
|
134
|
+
return (await this.list(slash <= 0 ? '/' : trimmed.slice(0, slash))).some((e) => e.name === name);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
// A missing parent (or a parent that is a file) means the path does not exist either.
|
|
138
|
+
if (error instanceof SpheronApiError && error.status === 400 && MISSING.test(error.message))
|
|
139
|
+
return false;
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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"}
|