withruntime 0.8.2 → 0.8.4
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/BLAXEL.md +44 -42
- package/DAYTONA.md +16 -16
- package/E2B.md +1 -1
- package/README.md +26 -5
- package/README.src.md +26 -5
- package/VERCEL.md +1 -1
- package/dist/blaxel/index.d.ts +1 -1
- package/dist/blaxel/index.js +2 -2
- package/dist/blaxel/sandbox.d.ts +8 -2
- package/dist/blaxel/sandbox.js +16 -4
- package/dist/blaxel/session.d.ts +31 -0
- package/dist/blaxel/session.js +134 -0
- package/dist/blaxel/unsupported.d.ts +2 -4
- package/dist/blaxel/unsupported.js +2 -4
- package/dist/browser-stub.d.ts +1 -0
- package/dist/browser-stub.js +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +403 -21
- package/dist/daytona/daytona.js +7 -3
- package/dist/daytona/sandbox.js +3 -3
- package/dist/e2b/unsupported.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/jobs.d.ts +213 -0
- package/dist/products/jobs.js +186 -0
- package/dist/products/secrets.d.ts +81 -9
- package/dist/products/secrets.js +214 -19
- package/dist/sandbox.d.ts +63 -1
- package/dist/sandbox.js +114 -9
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/vercel/sandbox.js +1 -1
- package/package.json +7 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Runtime } from "../client.js";
|
|
2
|
+
import { guard, NotSupportedError, responseError } from "./errors.js";
|
|
3
|
+
/* Blaxel's sandbox sessions over Runtime's (ARCHITECTURE.md section 3.12).
|
|
4
|
+
*
|
|
5
|
+
* In Blaxel a session is a private preview of the sandbox's own API with a
|
|
6
|
+
* token: a frontend holding it drives the sandbox without the workspace key.
|
|
7
|
+
* On Runtime it is a sandbox session: a token that runs commands, reads and
|
|
8
|
+
* writes files and reaches previews of that one sandbox, and nothing else.
|
|
9
|
+
*
|
|
10
|
+
* What maps, and how:
|
|
11
|
+
* - `expiresAt`: Blaxel's default is a day from now, which is Runtime's most.
|
|
12
|
+
* Later than a day is refused rather than quietly shortened.
|
|
13
|
+
* - `responseHeaders["Access-Control-Allow-Origin"]`: the page that may use it,
|
|
14
|
+
* which is how Runtime limits a session to origins. One origin, or several
|
|
15
|
+
* separated by commas; `*` is refused, because a session names its pages.
|
|
16
|
+
* Runtime answers the other CORS headers itself, so Access-Control-Allow-*
|
|
17
|
+
* headers are accepted and need nothing; any other response header is not
|
|
18
|
+
* supported.
|
|
19
|
+
* - `requestHeaders`: headers Blaxel adds to each request into the sandbox.
|
|
20
|
+
* Runtime's session reaches the sandbox's commands and files, not a server
|
|
21
|
+
* in it, so there is nothing to add them to.
|
|
22
|
+
* - `url`: the sandbox's address in Runtime's API, which fromSession reads the
|
|
23
|
+
* sandbox from. `name`: `session-<id>`. */
|
|
24
|
+
const DAY_MS = 86_400_000;
|
|
25
|
+
const PREFIX = "session-";
|
|
26
|
+
const SANDBOX_URL = /^(https?:\/\/[^/]+)\/v1\/sandboxes\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
27
|
+
function origins(headers = {}) {
|
|
28
|
+
const found = [];
|
|
29
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
30
|
+
const lower = name.toLowerCase();
|
|
31
|
+
if (lower === "access-control-allow-origin") {
|
|
32
|
+
for (const origin of value
|
|
33
|
+
.split(",")
|
|
34
|
+
.map((one) => one.trim())
|
|
35
|
+
.filter(Boolean)) {
|
|
36
|
+
if (origin === "*")
|
|
37
|
+
throw new NotSupportedError('A session for every origin (Access-Control-Allow-Origin: "*")', 'Name the page that uses it: responseHeaders: { "Access-Control-Allow-Origin": "https://app.example.com" }.');
|
|
38
|
+
found.push(origin.replace(/\/$/, ""));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
else if (!lower.startsWith("access-control-"))
|
|
42
|
+
throw new NotSupportedError(`Session response header ${name}`, "Runtime answers a session's CORS headers itself; set other headers in the server your page talks to.");
|
|
43
|
+
}
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
function seconds(expiresAt) {
|
|
47
|
+
const at = expiresAt ?? new Date(Date.now() + DAY_MS);
|
|
48
|
+
const left = Math.floor((at.getTime() - Date.now()) / 1000);
|
|
49
|
+
if (!Number.isFinite(left) || left < 60)
|
|
50
|
+
throw responseError(400, "A session must last at least a minute.");
|
|
51
|
+
if (left > DAY_MS / 1000 + 5)
|
|
52
|
+
throw new NotSupportedError("A session lasting more than a day", "Make one for a day at most, and createIfExpired() again before it ends.");
|
|
53
|
+
return Math.min(left, DAY_MS / 1000);
|
|
54
|
+
}
|
|
55
|
+
function withToken(session, apiUrl, token = "") {
|
|
56
|
+
return {
|
|
57
|
+
name: `${PREFIX}${session.id}`,
|
|
58
|
+
url: `${apiUrl}/v1/sandboxes/${session.sandboxId}`,
|
|
59
|
+
token: session.token ?? token,
|
|
60
|
+
expiresAt: new Date(session.expiresAt),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** The session createIfExpired made last, per sandbox, in this process. */
|
|
64
|
+
const made = new Map();
|
|
65
|
+
/** Blaxel's `sandbox.sessions`. */
|
|
66
|
+
export class SandboxSessions {
|
|
67
|
+
#run;
|
|
68
|
+
#apiUrl;
|
|
69
|
+
#id;
|
|
70
|
+
constructor(sandbox) {
|
|
71
|
+
this.#id = sandbox.id;
|
|
72
|
+
this.#run = sandbox.run;
|
|
73
|
+
this.#apiUrl = sandbox.apiUrl;
|
|
74
|
+
}
|
|
75
|
+
/** A new session, for a day unless `expiresAt` says sooner. */
|
|
76
|
+
async create(options = {}) {
|
|
77
|
+
if (options.requestHeaders && Object.keys(options.requestHeaders).length)
|
|
78
|
+
throw new NotSupportedError("Session request headers (requestHeaders)", "A Runtime session drives the sandbox's commands and files directly; pass what the headers carried as the command's env or a file.");
|
|
79
|
+
const made = await this.#run((runtime) => guard(() => runtime.sessions.create({
|
|
80
|
+
ttlSeconds: seconds(options.expiresAt),
|
|
81
|
+
origins: origins(options.responseHeaders),
|
|
82
|
+
})));
|
|
83
|
+
return withToken(made, made.apiUrl || this.#apiUrl());
|
|
84
|
+
}
|
|
85
|
+
/** Blaxel's: the session made last, if it lasts at least `delta` ms more
|
|
86
|
+
* (an hour unless given), else a new one, and the old one ends. A token is
|
|
87
|
+
* shown only when its session is made, so "the session made last" is the
|
|
88
|
+
* one this process made for this sandbox; a new process makes a new one. */
|
|
89
|
+
async createIfExpired(options = {}, delta = 60 * 60 * 1000) {
|
|
90
|
+
const kept = made.get(this.#id);
|
|
91
|
+
if (kept) {
|
|
92
|
+
const live = (await this.list()).some((session) => session.name === kept.name);
|
|
93
|
+
if (live && kept.expiresAt.getTime() >= Date.now() + delta)
|
|
94
|
+
return kept;
|
|
95
|
+
if (live)
|
|
96
|
+
await this.delete(kept.name).catch(() => undefined);
|
|
97
|
+
}
|
|
98
|
+
const fresh = await this.create(options);
|
|
99
|
+
made.set(this.#id, fresh);
|
|
100
|
+
return fresh;
|
|
101
|
+
}
|
|
102
|
+
/** Active sessions, newest first. A token is shown only when its session is
|
|
103
|
+
* made, so these carry an empty `token`: keep the one `create` gave. */
|
|
104
|
+
async list() {
|
|
105
|
+
const sessions = await this.#run((runtime) => guard(() => runtime.sessions.list()));
|
|
106
|
+
return sessions
|
|
107
|
+
.filter((session) => session.state === "active")
|
|
108
|
+
.map((session) => withToken(session, this.#apiUrl()));
|
|
109
|
+
}
|
|
110
|
+
async get(name) {
|
|
111
|
+
const id = name.startsWith(PREFIX) ? name.slice(PREFIX.length) : name;
|
|
112
|
+
const found = (await this.#run((runtime) => guard(() => runtime.sessions.list()))).find((session) => session.id === id);
|
|
113
|
+
if (!found)
|
|
114
|
+
throw responseError(404, `Session ${name} not found.`);
|
|
115
|
+
const { name: _name, ...rest } = withToken(found, this.#apiUrl());
|
|
116
|
+
return rest;
|
|
117
|
+
}
|
|
118
|
+
/** Blaxel's delete: the session ends at once. */
|
|
119
|
+
async delete(name) {
|
|
120
|
+
const id = name.startsWith(PREFIX) ? name.slice(PREFIX.length) : name;
|
|
121
|
+
const revoked = await this.#run((runtime) => guard(() => runtime.sessions.revoke(id)));
|
|
122
|
+
return withToken(revoked, this.#apiUrl());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** The client and sandbox a session reaches, for `SandboxInstance.fromSession`.
|
|
126
|
+
* The sandbox is read once with the session, which may read it. */
|
|
127
|
+
export async function sessionSandbox(session) {
|
|
128
|
+
const found = SANDBOX_URL.exec(session.url ?? "");
|
|
129
|
+
if (!found || !session.token?.startsWith("rtsess_"))
|
|
130
|
+
throw new NotSupportedError("A session Runtime did not make", "Make the session with sandbox.sessions.create() on your backend, where a Runtime key is set, and pass what it returns.");
|
|
131
|
+
const client = new Runtime({ apiKey: session.token, baseUrl: found[1] });
|
|
132
|
+
const runtime = await guard(() => client.sandboxes.get(found[2]));
|
|
133
|
+
return { client, runtime };
|
|
134
|
+
}
|
|
@@ -8,15 +8,13 @@ export type Unsupported = {
|
|
|
8
8
|
readonly [key: string]: (...args: unknown[]) => never;
|
|
9
9
|
};
|
|
10
10
|
export declare function unsupportedExport(name: string, alternative: string): Unsupported;
|
|
11
|
-
/** A sandbox part Runtime has no counterpart for (`sandbox.
|
|
12
|
-
* `sandbox.codegen`, ...): every method answers a rejected promise with
|
|
11
|
+
/** A sandbox part Runtime has no counterpart for (`sandbox.codegen`, ...): every method answers a rejected promise with
|
|
13
12
|
* NotSupportedError naming the alternative. */
|
|
14
13
|
export declare function unsupportedPart(name: string, alternative: string): UnsupportedPart;
|
|
15
14
|
export type UnsupportedPart = {
|
|
16
15
|
readonly [method: string]: (...args: unknown[]) => Promise<never>;
|
|
17
16
|
};
|
|
18
|
-
export declare const SESSIONS = "Share a port with sandbox.previews.create({ metadata: { name }, spec: { port, public: false } }) and a token from preview.tokens.create(expiresAt); drive the sandbox itself through this SDK.";
|
|
19
17
|
export declare const CODEGEN = "Runtime has no hosted edit or rerank models: apply edits with sandbox.fs.write, and rank files with your own model.";
|
|
20
|
-
export declare const SCHEDULES = "
|
|
18
|
+
export declare const SCHEDULES = "A Blaxel schedule runs inside one sandbox; a Runtime job runs its command on a cron schedule or at a time in a fresh sandbox each run: runtime.jobs.create({ name, schedule: { cron }, command }) from withruntime. To run inside this sandbox, call sandbox.process.exec from your own scheduler.";
|
|
21
19
|
export declare const SYSTEM = "Runtime keeps each sandbox's agent current itself; there is nothing to upgrade.";
|
|
22
20
|
export declare const DRIVES = "Use a Runtime volume: create one with runtime.volumes.create({ name }) and pass volumes: [{ name, mountPath }] at create.";
|
|
@@ -15,8 +15,7 @@ export function unsupportedExport(name, alternative) {
|
|
|
15
15
|
},
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
/** A sandbox part Runtime has no counterpart for (`sandbox.
|
|
19
|
-
* `sandbox.codegen`, ...): every method answers a rejected promise with
|
|
18
|
+
/** A sandbox part Runtime has no counterpart for (`sandbox.codegen`, ...): every method answers a rejected promise with
|
|
20
19
|
* NotSupportedError naming the alternative. */
|
|
21
20
|
export function unsupportedPart(name, alternative) {
|
|
22
21
|
return new Proxy({}, {
|
|
@@ -27,8 +26,7 @@ export function unsupportedPart(name, alternative) {
|
|
|
27
26
|
},
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
|
-
export const SESSIONS = "Share a port with sandbox.previews.create({ metadata: { name }, spec: { port, public: false } }) and a token from preview.tokens.create(expiresAt); drive the sandbox itself through this SDK.";
|
|
31
29
|
export const CODEGEN = "Runtime has no hosted edit or rerank models: apply edits with sandbox.fs.write, and rank files with your own model.";
|
|
32
|
-
export const SCHEDULES = "
|
|
30
|
+
export const SCHEDULES = "A Blaxel schedule runs inside one sandbox; a Runtime job runs its command on a cron schedule or at a time in a fresh sandbox each run: runtime.jobs.create({ name, schedule: { cron }, command }) from withruntime. To run inside this sandbox, call sandbox.process.exec from your own scheduler.";
|
|
33
31
|
export const SYSTEM = "Runtime keeps each sandbox's agent current itself; there is nothing to upgrade.";
|
|
34
32
|
export const DRIVES = "Use a Runtime volume: create one with runtime.volumes.create({ name }) and pass volumes: [{ name, mountPath }] at create.";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { RuntimeError } from "./errors.js";
|
|
3
3
|
import type { Usage } from "./types.js";
|
|
4
|
+
import type { SwitchProvider } from "./products/switching.js";
|
|
4
5
|
import type { SecretRule } from "./products/secrets.js";
|
|
5
6
|
type Out = {
|
|
6
7
|
json: boolean;
|
|
@@ -35,6 +36,8 @@ export declare function run(argv: string[], env?: NodeJS.ProcessEnv, out?: Out):
|
|
|
35
36
|
* microdollar (the API's integer microdollars moved six places), so a column
|
|
36
37
|
* sums to the ledger to the last digit. */
|
|
37
38
|
export declare function usageCsv(u: Usage): string;
|
|
39
|
+
/** How to move a rival's code across, when one import does it. */
|
|
40
|
+
export declare function dropInLines(provider: SwitchProvider | undefined): string[];
|
|
38
41
|
/** `--allow` values as secret rules: each is `[METHOD[,METHOD]...] PATH`, and
|
|
39
42
|
* rules naming the same methods share one rule's paths. Only the shape is read
|
|
40
43
|
* here; the API holds every rule to the same check the host makes
|