withruntime 0.8.3 → 0.9.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/COMPATIBILITY.md +145 -0
- package/E2B.md +23 -13
- package/README.md +8 -5
- package/README.src.md +8 -5
- package/compatibility-lock.json +281 -0
- package/dist/ai-harness/index.js +2 -2
- package/dist/api-defaults.d.ts +17 -0
- package/dist/api-defaults.js +22 -0
- package/dist/blaxel/preview.d.ts +1 -1
- package/dist/blaxel/preview.js +1 -1
- package/dist/blaxel/process.d.ts +1 -1
- package/dist/blaxel/process.js +11 -2
- package/dist/cli-words.d.ts +27 -0
- package/dist/cli-words.js +135 -0
- package/dist/cli.js +162 -31
- package/dist/client.d.ts +4 -1
- package/dist/client.js +8 -2
- package/dist/cloudflare/index.d.ts +294 -0
- package/dist/cloudflare/index.js +554 -0
- package/dist/cloudflare/worker.d.ts +415 -0
- package/dist/cloudflare/worker.js +297 -0
- package/dist/codesandbox/guest-service.d.ts +31 -0
- package/dist/codesandbox/guest-service.js +453 -0
- package/dist/codesandbox/index.d.ts +386 -0
- package/dist/codesandbox/index.js +1313 -0
- package/dist/compat/core.d.ts +39 -0
- package/dist/compat/core.js +199 -0
- package/dist/compat/files.d.ts +26 -0
- package/dist/compat/files.js +115 -0
- package/dist/compat/shell.d.ts +4 -0
- package/dist/compat/shell.js +165 -0
- package/dist/daytona/context.js +2 -1
- package/dist/daytona/process.d.ts +1 -1
- package/dist/daytona/process.js +27 -4
- package/dist/e2b/code-interpreter.d.ts +4 -5
- package/dist/e2b/code-interpreter.js +54 -56
- package/dist/e2b/commands.d.ts +5 -4
- package/dist/e2b/commands.js +37 -29
- package/dist/e2b/errors.js +1 -1
- package/dist/e2b/filesystem.d.ts +8 -6
- package/dist/e2b/filesystem.js +127 -28
- package/dist/e2b/sandbox.d.ts +4 -3
- package/dist/e2b/sandbox.js +10 -10
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +15 -2
- package/dist/freestyle/index.d.ts +301 -0
- package/dist/freestyle/index.js +310 -0
- package/dist/index.d.ts +1 -0
- package/dist/mcp.js +2 -2
- package/dist/modal/index.d.ts +205 -0
- package/dist/modal/index.js +485 -0
- package/dist/network-cli.js +1 -1
- package/dist/openai-agents/index.js +9 -26
- package/dist/products/audit.d.ts +3 -0
- package/dist/products/index.d.ts +2 -0
- package/dist/products/index.js +2 -0
- package/dist/products/interpreter.d.ts +11 -4
- package/dist/products/interpreter.js +33 -9
- package/dist/products/jobs.d.ts +1 -1
- package/dist/products/jobs.js +5 -4
- package/dist/products/limits.d.ts +10 -0
- package/dist/products/previews.d.ts +10 -2
- package/dist/products/previews.js +3 -2
- package/dist/products/tailscale.d.ts +57 -0
- package/dist/products/tailscale.js +17 -0
- package/dist/products/watch.d.ts +25 -4
- package/dist/products/watch.js +75 -14
- package/dist/runloop/index.d.ts +340 -0
- package/dist/runloop/index.js +438 -0
- package/dist/runloop/pagination.d.ts +46 -0
- package/dist/runloop/pagination.js +82 -0
- package/dist/runloop/snapshots.d.ts +52 -0
- package/dist/runloop/snapshots.js +244 -0
- package/dist/sandbox.d.ts +40 -9
- package/dist/sandbox.js +273 -76
- package/dist/snapshots.d.ts +11 -1
- package/dist/snapshots.js +11 -1
- package/dist/sprites/index.d.ts +251 -0
- package/dist/sprites/index.js +552 -0
- package/dist/tar.d.ts +8 -1
- package/dist/tar.js +185 -35
- package/dist/tools/index.js +2 -1
- package/dist/transport.d.ts +20 -2
- package/dist/transport.js +48 -11
- package/dist/types.d.ts +52 -3
- package/dist/vercel/command.js +12 -1
- package/dist/vercel/errors.d.ts +2 -2
- package/dist/vercel/errors.js +12 -1
- package/dist/vercel/filesystem.d.ts +21 -18
- package/dist/vercel/filesystem.js +95 -48
- package/dist/vercel/sandbox.js +5 -5
- package/package.json +45 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RuntimeError } from "../errors.js";
|
|
1
2
|
/** A stateful interpreter in the sandbox, in Python, JavaScript, TypeScript,
|
|
2
3
|
* R, Java, Bash or Go:
|
|
3
4
|
* `await sbx.interpreter.run("import pandas as pd; pd.DataFrame({'a': [1]})")`.
|
|
@@ -7,33 +8,56 @@ export function sandboxInterpreter(t, sandbox) {
|
|
|
7
8
|
return {
|
|
8
9
|
/** Run a cell. With any of the on* callbacks, output streams as it happens. */
|
|
9
10
|
async run(code, options = {}) {
|
|
10
|
-
const { language, context, timeoutMs, onStdout, onStderr, onResult, onError, ...request } = options;
|
|
11
|
+
const { language, context, timeoutMs, requestTimeoutMs, interruptOnDisconnect, onStdout, onStderr, onResult, onError, ...request } = options;
|
|
12
|
+
const transportOptions = {
|
|
13
|
+
...request,
|
|
14
|
+
...(requestTimeoutMs !== undefined
|
|
15
|
+
? { timeoutMs: requestTimeoutMs }
|
|
16
|
+
: timeoutMs !== undefined
|
|
17
|
+
? { timeoutMs: timeoutMs === 0 ? 0 : timeoutMs + 60_000 }
|
|
18
|
+
: {}),
|
|
19
|
+
};
|
|
11
20
|
const body = {
|
|
12
21
|
code,
|
|
13
22
|
...(language ? { language } : {}),
|
|
14
23
|
...(context ? { context } : {}),
|
|
15
|
-
...(timeoutMs ? { timeoutMs } : {}),
|
|
24
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
25
|
+
...(interruptOnDisconnect !== undefined ? { interruptOnDisconnect } : {}),
|
|
16
26
|
};
|
|
17
27
|
if (!onStdout && !onStderr && !onResult && !onError)
|
|
18
|
-
return t.json({
|
|
28
|
+
return t.json({
|
|
29
|
+
method: "POST",
|
|
30
|
+
path: `${base()}:run`,
|
|
31
|
+
body,
|
|
32
|
+
...transportOptions,
|
|
33
|
+
});
|
|
19
34
|
for await (const event of t.events({
|
|
20
35
|
method: "POST",
|
|
21
36
|
path: `${base()}:run`,
|
|
22
37
|
body: { ...body, stream: true },
|
|
23
|
-
...
|
|
38
|
+
...transportOptions,
|
|
24
39
|
})) {
|
|
25
40
|
if (event.k === "stdout")
|
|
26
|
-
onStdout?.(event.text);
|
|
41
|
+
await onStdout?.(event.text);
|
|
27
42
|
else if (event.k === "stderr")
|
|
28
|
-
onStderr?.(event.text);
|
|
43
|
+
await onStderr?.(event.text);
|
|
29
44
|
else if (event.k === "result")
|
|
30
|
-
onResult?.({ main: event.main, data: event.data, refs: event.refs });
|
|
45
|
+
await onResult?.({ main: event.main, data: event.data, refs: event.refs });
|
|
31
46
|
else if (event.k === "error")
|
|
32
|
-
onError?.(event);
|
|
47
|
+
await onError?.(event);
|
|
33
48
|
else if (event.k === "execution")
|
|
34
49
|
return event.execution;
|
|
35
50
|
else if (event.k === "failure")
|
|
36
|
-
|
|
51
|
+
// As every other refusal: its code, words, hint and request id,
|
|
52
|
+
// printed by the CLI as it prints the rest (28 September 2026:
|
|
53
|
+
// "Error: forbidden: cloud forbidden", with no hint and no id).
|
|
54
|
+
throw new RuntimeError({
|
|
55
|
+
code: event.code,
|
|
56
|
+
status: event.status ?? 0,
|
|
57
|
+
message: event.message,
|
|
58
|
+
...(event.hint ? { hint: event.hint } : {}),
|
|
59
|
+
...(event.requestId ? { requestId: event.requestId } : {}),
|
|
60
|
+
});
|
|
37
61
|
}
|
|
38
62
|
throw new Error("The interpreter stream ended without a result");
|
|
39
63
|
},
|
package/dist/products/jobs.d.ts
CHANGED
|
@@ -134,7 +134,7 @@ export type CreateJob = {
|
|
|
134
134
|
maxTotalCostMicros?: number;
|
|
135
135
|
};
|
|
136
136
|
/** The size a run gets when `compute` leaves a field out: the sandbox
|
|
137
|
-
* defaults, in the region `us-east
|
|
137
|
+
* defaults, in the region `us-east`. */
|
|
138
138
|
export declare const JOB_DEFAULTS: {
|
|
139
139
|
region: string;
|
|
140
140
|
vcpu: number;
|
package/dist/products/jobs.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
+
import { JOB_START_SECONDS, JOB_TIMEOUT_SECONDS } from "../api-defaults.js";
|
|
1
2
|
import { Page } from "../page.js";
|
|
2
3
|
import { RuntimeError, ServiceUnavailableError } from "../errors.js";
|
|
3
4
|
/** The size a run gets when `compute` leaves a field out: the sandbox
|
|
4
|
-
* defaults, in the region `us-east
|
|
5
|
+
* defaults, in the region `us-east`. */
|
|
5
6
|
export const JOB_DEFAULTS = {
|
|
6
|
-
region: "us-east
|
|
7
|
+
region: "us-east",
|
|
7
8
|
vcpu: 2,
|
|
8
9
|
cpuMode: "shared",
|
|
9
10
|
cpuFloorMillis: 50,
|
|
10
11
|
memoryMiB: 4096,
|
|
11
12
|
diskMiB: 4096,
|
|
12
|
-
timeoutSeconds:
|
|
13
|
+
timeoutSeconds: JOB_TIMEOUT_SECONDS,
|
|
13
14
|
/** Added to the timeout for the sandbox to start, within the hour. */
|
|
14
|
-
startSeconds:
|
|
15
|
+
startSeconds: JOB_START_SECONDS,
|
|
15
16
|
};
|
|
16
17
|
/** A run is one sandbox: at most 16 vCPU and 64 GiB, a disk no smaller than
|
|
17
18
|
* the system image, and paid for at most an hour. */
|
|
@@ -14,6 +14,16 @@ export type KeyLimits = {
|
|
|
14
14
|
remainingMicros: string | null;
|
|
15
15
|
window: "24h";
|
|
16
16
|
};
|
|
17
|
+
/** The account's free trial time in milliseconds, the same figures as
|
|
18
|
+
* `usage.get()`, or null when the account has no trial. A trial sandbox
|
|
19
|
+
* that has not ended holds its whole lease in `reservedMs`;
|
|
20
|
+
* `availableMs = totalMs - usedMs - reservedMs`. */
|
|
21
|
+
trial: {
|
|
22
|
+
totalMs: number;
|
|
23
|
+
usedMs: number;
|
|
24
|
+
reservedMs: number;
|
|
25
|
+
availableMs: number;
|
|
26
|
+
} | null;
|
|
17
27
|
};
|
|
18
28
|
/** `runtime.limits.get()`: whether this key is read-only, and what its agent
|
|
19
29
|
* may still spend today. An owner sets the limit at
|
|
@@ -17,6 +17,8 @@ export type Preview = {
|
|
|
17
17
|
urlWithToken: string | null;
|
|
18
18
|
/** Turned off by Runtime after a report. */
|
|
19
19
|
disabled: boolean;
|
|
20
|
+
/** The sites that may show it in an iframe; null for any site. */
|
|
21
|
+
embedOrigins: string[] | null;
|
|
20
22
|
createdAt: string;
|
|
21
23
|
/** On create only: how to share the address, in one line. */
|
|
22
24
|
hint?: string;
|
|
@@ -27,6 +29,11 @@ export type CreatePreview = {
|
|
|
27
29
|
visibility?: "private" | "public";
|
|
28
30
|
/** How long the returned token lasts, 60 s to 7 days (default 1 day). */
|
|
29
31
|
ttlSeconds?: number;
|
|
32
|
+
/** The sites that may show it in an iframe, as origins
|
|
33
|
+
* (`https://app.example.com`, `https://*.example.com`, `http://localhost:3000`),
|
|
34
|
+
* up to 16. Any other site's iframe is refused. Omitted: a shared port keeps
|
|
35
|
+
* its list; `[]` or null: any site, the default. */
|
|
36
|
+
embedOrigins?: string[] | null;
|
|
30
37
|
};
|
|
31
38
|
/** `sbx.previews`: share ports of this sandbox at public HTTPS addresses.
|
|
32
39
|
*
|
|
@@ -35,7 +42,8 @@ export type CreatePreview = {
|
|
|
35
42
|
*
|
|
36
43
|
* WebSockets work. The server must listen on 0.0.0.0 or localhost. */
|
|
37
44
|
export declare function sandboxPreviews(t: Transport, sandbox: Sandbox): {
|
|
38
|
-
/** Shares `port`, or changes its visibility if it is
|
|
45
|
+
/** Shares `port`, or changes its visibility and embedOrigins if it is
|
|
46
|
+
* shared already. */
|
|
39
47
|
create: (port: number, input?: CreatePreview, options?: RequestOptions) => Promise<Preview>;
|
|
40
48
|
/** Every shared port, each private one with a fresh token. */
|
|
41
49
|
list: (options?: RequestOptions) => Promise<Preview[]>;
|
|
@@ -43,7 +51,7 @@ export declare function sandboxPreviews(t: Transport, sandbox: Sandbox): {
|
|
|
43
51
|
get: (port: number, ttlSeconds?: number, options?: RequestOptions) => Promise<Preview>;
|
|
44
52
|
/** Refuses every token issued for this port so far and returns a new one. */
|
|
45
53
|
rotate: (port: number, options?: RequestOptions) => Promise<Preview>;
|
|
46
|
-
/** Stops sharing `port`.
|
|
54
|
+
/** Stops sharing `port`. Its open connections are closed before this returns. */
|
|
47
55
|
delete: (port: number, options?: RequestOptions) => Promise<{
|
|
48
56
|
id: string;
|
|
49
57
|
sandboxId: string;
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
export function sandboxPreviews(t, sandbox) {
|
|
8
8
|
const base = () => `/v1/sandboxes/${encodeURIComponent(sandbox.id)}/previews`;
|
|
9
9
|
return {
|
|
10
|
-
/** Shares `port`, or changes its visibility if it is
|
|
10
|
+
/** Shares `port`, or changes its visibility and embedOrigins if it is
|
|
11
|
+
* shared already. */
|
|
11
12
|
create: (port, input = {}, options) => t.json({ method: "POST", path: base(), body: { port, ...input }, ...options }),
|
|
12
13
|
/** Every shared port, each private one with a fresh token. */
|
|
13
14
|
list: async (options) => (await t.json({ method: "GET", path: base(), ...options })).data,
|
|
@@ -20,7 +21,7 @@ export function sandboxPreviews(t, sandbox) {
|
|
|
20
21
|
}),
|
|
21
22
|
/** Refuses every token issued for this port so far and returns a new one. */
|
|
22
23
|
rotate: (port, options) => t.json({ method: "POST", path: `${base()}/${port}:rotate`, ...options }),
|
|
23
|
-
/** Stops sharing `port`.
|
|
24
|
+
/** Stops sharing `port`. Its open connections are closed before this returns. */
|
|
24
25
|
delete: (port, options) => t.json({
|
|
25
26
|
method: "DELETE",
|
|
26
27
|
path: `${base()}/${port}`,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Sandbox } from "../sandbox.js";
|
|
2
|
+
import type { RequestOptions, Transport } from "../transport.js";
|
|
3
|
+
/** Joining a sandbox to your tailnet. */
|
|
4
|
+
export type TailscaleJoin = {
|
|
5
|
+
/** The name of a secret stored for jobs (`runtime secrets set NAME --jobs`)
|
|
6
|
+
* that holds a Tailscale auth key. Use an ephemeral, tagged key. */
|
|
7
|
+
authKeySecret: string;
|
|
8
|
+
/** The machine's name on the tailnet, a DNS label. Default: the sandbox's
|
|
9
|
+
* name, or runtime- and the first part of its id. */
|
|
10
|
+
hostname?: string;
|
|
11
|
+
/** Tags to advertise, such as `tag:agents`; the key must be allowed them. */
|
|
12
|
+
tags?: string[];
|
|
13
|
+
};
|
|
14
|
+
/** A sandbox's machine on your tailnet, as the sandbox reports it. */
|
|
15
|
+
export type TailscaleNode = {
|
|
16
|
+
/** Logged in and running now. */
|
|
17
|
+
running: boolean;
|
|
18
|
+
/** Tailscale's own word: Running, NeedsLogin, Starting, Stopped. */
|
|
19
|
+
state: string | null;
|
|
20
|
+
/** kernel: a tailscale0 interface reaches 100.x directly. userspace: the
|
|
21
|
+
* tailnet reaches the sandbox's ports, and programs in it reach the tailnet
|
|
22
|
+
* through `proxy`. */
|
|
23
|
+
mode: "kernel" | "userspace" | null;
|
|
24
|
+
/** Its MagicDNS name, such as agent-1.tail1234.ts.net. */
|
|
25
|
+
dnsName: string | null;
|
|
26
|
+
/** Its tailnet addresses, 100.x and fd7a:… */
|
|
27
|
+
addresses: string[];
|
|
28
|
+
/** SOCKS5 and HTTP proxy to the tailnet in userspace mode. */
|
|
29
|
+
proxy: string | null;
|
|
30
|
+
};
|
|
31
|
+
/** What a join answers: the node, and what Runtime recorded. */
|
|
32
|
+
export type TailscaleJoined = TailscaleNode & {
|
|
33
|
+
hostname: string;
|
|
34
|
+
tags: string[];
|
|
35
|
+
/** The secret the key was read from; never the key. */
|
|
36
|
+
authKeySecret: string | null;
|
|
37
|
+
joinedAt: number;
|
|
38
|
+
};
|
|
39
|
+
/** What `status()` answers: the node, and the join, or null on none. */
|
|
40
|
+
export type TailscaleStatus = TailscaleNode & {
|
|
41
|
+
tailnet: Pick<TailscaleJoined, "hostname" | "tags" | "authKeySecret" | "joinedAt"> | null;
|
|
42
|
+
};
|
|
43
|
+
/** `sbx.tailscale`: this sandbox on your own Tailscale network. Paid
|
|
44
|
+
* sandboxes only.
|
|
45
|
+
*
|
|
46
|
+
* const node = await sbx.tailscale.up({ authKeySecret: "TS_AUTHKEY", tags: ["tag:agents"] });
|
|
47
|
+
* console.log(node.addresses);
|
|
48
|
+
*
|
|
49
|
+
* The auth key stays in the sandbox's memory. `down()` takes the machine off
|
|
50
|
+
* the tailnet at once; after a stop or a delete, Tailscale removes it. */
|
|
51
|
+
export declare function sandboxTailscale(t: Transport, sandbox: Sandbox): {
|
|
52
|
+
up: (input: TailscaleJoin, options?: RequestOptions) => Promise<TailscaleJoined>;
|
|
53
|
+
status: (options?: RequestOptions) => Promise<TailscaleStatus>;
|
|
54
|
+
down: (options?: RequestOptions) => Promise<{
|
|
55
|
+
left: boolean;
|
|
56
|
+
}>;
|
|
57
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const enc = encodeURIComponent;
|
|
2
|
+
/** `sbx.tailscale`: this sandbox on your own Tailscale network. Paid
|
|
3
|
+
* sandboxes only.
|
|
4
|
+
*
|
|
5
|
+
* const node = await sbx.tailscale.up({ authKeySecret: "TS_AUTHKEY", tags: ["tag:agents"] });
|
|
6
|
+
* console.log(node.addresses);
|
|
7
|
+
*
|
|
8
|
+
* The auth key stays in the sandbox's memory. `down()` takes the machine off
|
|
9
|
+
* the tailnet at once; after a stop or a delete, Tailscale removes it. */
|
|
10
|
+
export function sandboxTailscale(t, sandbox) {
|
|
11
|
+
const path = () => `/v1/sandboxes/${enc(sandbox.id)}/tailscale`;
|
|
12
|
+
return {
|
|
13
|
+
up: (input, options) => t.json({ method: "POST", path: path(), body: input, ...options }),
|
|
14
|
+
status: (options) => t.json({ method: "GET", path: path(), ...options }),
|
|
15
|
+
down: (options) => t.json({ method: "DELETE", path: path(), ...options }),
|
|
16
|
+
};
|
|
17
|
+
}
|
package/dist/products/watch.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RequestOptions, Transport } from "../transport.js";
|
|
2
|
-
export type FileEventType = "create" | "write" | "remove" | "rename" | "chmod";
|
|
2
|
+
export type FileEventType = "create" | "write" | "remove" | "rename" | "chmod" | "access";
|
|
3
3
|
export type FileEvent = {
|
|
4
4
|
type: FileEventType;
|
|
5
5
|
/** Absolute path in the sandbox. */
|
|
@@ -23,7 +23,7 @@ export type WatchNotice = {
|
|
|
23
23
|
};
|
|
24
24
|
export type WatchOptions = Omit<RequestOptions, "timeoutMs"> & {
|
|
25
25
|
recursive?: boolean;
|
|
26
|
-
/** Only these event types;
|
|
26
|
+
/** Only these event types; the five mutation types by default; access is opt-in. */
|
|
27
27
|
events?: FileEventType[];
|
|
28
28
|
/** Globs relative to the path: only matching paths are reported. */
|
|
29
29
|
include?: string[];
|
|
@@ -31,7 +31,7 @@ export type WatchOptions = Omit<RequestOptions, "timeoutMs"> & {
|
|
|
31
31
|
exclude?: string[];
|
|
32
32
|
/** Events are gathered this long and delivered together. Default 100. */
|
|
33
33
|
batchMs?: number;
|
|
34
|
-
/** The watch ends by itself after running this long. Default 1 hour. */
|
|
34
|
+
/** The watch ends by itself after running this long. Default 1 hour; 0 runs until stopped or the sandbox ends. */
|
|
35
35
|
timeoutMs?: number;
|
|
36
36
|
maxWatches?: number;
|
|
37
37
|
/** A batch at a time, rather than one call per event. */
|
|
@@ -49,6 +49,16 @@ export type WatchInfo = {
|
|
|
49
49
|
processId: string;
|
|
50
50
|
state: string;
|
|
51
51
|
startedAt: number;
|
|
52
|
+
/** Its changes go to the account's webhooks as `sandbox.files.changed`. */
|
|
53
|
+
webhook?: boolean;
|
|
54
|
+
};
|
|
55
|
+
/** A watch whose changes Runtime sends to the account's webhooks. */
|
|
56
|
+
export type WebhookWatchOptions = Omit<WatchOptions, "onBatch" | "onNotice" | "onExit" | "timeoutMs"> & {
|
|
57
|
+
/** The watch ends by itself after running this long; 0 (the default here)
|
|
58
|
+
* runs until stopped or the sandbox ends. */
|
|
59
|
+
timeoutMs?: number;
|
|
60
|
+
/** Its own id, 1 to 40 lowercase letters, digits or dashes. */
|
|
61
|
+
id?: string;
|
|
52
62
|
};
|
|
53
63
|
/** A running watch. Events arrive through the callbacks until `stop()`. */
|
|
54
64
|
export declare class WatchHandle {
|
|
@@ -65,7 +75,9 @@ export declare class WatchHandle {
|
|
|
65
75
|
get done(): Promise<void>;
|
|
66
76
|
/** Start delivering again from where it left off, after a pause. */
|
|
67
77
|
resume(): void;
|
|
68
|
-
/** Stop the watch in the sandbox
|
|
78
|
+
/** Stop the watch in the sandbox. What changed before the stop is still
|
|
79
|
+
* delivered: the watch reports it and ends, and this returns once that has
|
|
80
|
+
* arrived (at most 5 s). Called from inside a callback, it stops at once. */
|
|
69
81
|
stop(options?: RequestOptions): Promise<void>;
|
|
70
82
|
/** @internal */
|
|
71
83
|
begin(): this;
|
|
@@ -85,6 +97,15 @@ export declare function sandboxWatches(t: Transport, sandboxId: string): {
|
|
|
85
97
|
lostBytes: number;
|
|
86
98
|
ended: boolean;
|
|
87
99
|
}>;
|
|
100
|
+
/** Starts a watch whose changes Runtime sends to the account's webhooks
|
|
101
|
+
* as `sandbox.files.changed` events, with nothing reading it here:
|
|
102
|
+
*
|
|
103
|
+
* await sbx.files.watches.webhook("/workspace/app", { recursive: true });
|
|
104
|
+
*
|
|
105
|
+
* Runs until stopped (timeoutMs 0) unless given a timeout. */
|
|
106
|
+
webhook: (path: string, input?: WebhookWatchOptions) => Promise<WatchInfo & {
|
|
107
|
+
cursor: number;
|
|
108
|
+
}>;
|
|
88
109
|
stop: (id: string, options?: RequestOptions) => Promise<{
|
|
89
110
|
stopped: boolean;
|
|
90
111
|
}>;
|
package/dist/products/watch.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { WEBHOOK_WATCH_TIMEOUT_MS } from "../api-defaults.js";
|
|
2
|
+
import { RuntimeError } from "../errors.js";
|
|
1
3
|
/** A running watch. Events arrive through the callbacks until `stop()`. */
|
|
2
4
|
export class WatchHandle {
|
|
3
5
|
t;
|
|
@@ -8,6 +10,11 @@ export class WatchHandle {
|
|
|
8
10
|
onEvent;
|
|
9
11
|
cursor;
|
|
10
12
|
#stopped = false;
|
|
13
|
+
/** stop() was called: deliver what the watch reports until its end. */
|
|
14
|
+
#stopping = false;
|
|
15
|
+
/** Callbacks under way. */
|
|
16
|
+
#delivering = 0;
|
|
17
|
+
#exitNotified = false;
|
|
11
18
|
#following;
|
|
12
19
|
#abort = new AbortController();
|
|
13
20
|
constructor(t, base, id, path, cursor, options, onEvent) {
|
|
@@ -25,22 +32,42 @@ export class WatchHandle {
|
|
|
25
32
|
}
|
|
26
33
|
/** Start delivering again from where it left off, after a pause. */
|
|
27
34
|
resume() {
|
|
28
|
-
if (this.#stopped)
|
|
35
|
+
if (this.#stopped || this.#stopping)
|
|
29
36
|
throw new Error("This watch was stopped");
|
|
30
37
|
this.#abort = new AbortController();
|
|
31
38
|
this.begin();
|
|
32
39
|
}
|
|
33
|
-
/** Stop the watch in the sandbox
|
|
40
|
+
/** Stop the watch in the sandbox. What changed before the stop is still
|
|
41
|
+
* delivered: the watch reports it and ends, and this returns once that has
|
|
42
|
+
* arrived (at most 5 s). Called from inside a callback, it stops at once. */
|
|
34
43
|
async stop(options) {
|
|
35
|
-
if (this.#stopped)
|
|
44
|
+
if (this.#stopped || this.#stopping)
|
|
36
45
|
return;
|
|
37
|
-
this.#
|
|
38
|
-
this.#abort.abort();
|
|
46
|
+
this.#stopping = true;
|
|
39
47
|
await this.t
|
|
40
48
|
.json({ method: "DELETE", path: `${this.base}/${encodeURIComponent(this.id)}`, ...options })
|
|
41
49
|
.catch(() => undefined);
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
// Inside a callback, #following is awaiting the caller: waiting for it
|
|
51
|
+
// here would make that callback await itself.
|
|
52
|
+
if (this.#following && this.#delivering === 0) {
|
|
53
|
+
let timer;
|
|
54
|
+
await Promise.race([
|
|
55
|
+
this.#following.catch(() => undefined),
|
|
56
|
+
new Promise((resolve) => {
|
|
57
|
+
timer = setTimeout(resolve, 5_000);
|
|
58
|
+
}),
|
|
59
|
+
]);
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
}
|
|
62
|
+
this.#stopped = true;
|
|
63
|
+
this.#abort.abort();
|
|
64
|
+
this.#notifyExit("stopped");
|
|
65
|
+
}
|
|
66
|
+
#notifyExit(reason) {
|
|
67
|
+
if (this.#exitNotified)
|
|
68
|
+
return;
|
|
69
|
+
this.#exitNotified = true;
|
|
70
|
+
this.options.onExit?.(reason);
|
|
44
71
|
}
|
|
45
72
|
async #follow() {
|
|
46
73
|
for (;;) {
|
|
@@ -55,21 +82,38 @@ export class WatchHandle {
|
|
|
55
82
|
})) {
|
|
56
83
|
this.cursor = event.cursor ?? this.cursor;
|
|
57
84
|
if (event.k === "events") {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
85
|
+
this.#delivering++;
|
|
86
|
+
try {
|
|
87
|
+
if (this.options.onBatch)
|
|
88
|
+
await this.options.onBatch(event.events);
|
|
89
|
+
if (this.#stopped)
|
|
90
|
+
return;
|
|
91
|
+
if (this.onEvent)
|
|
92
|
+
for (const item of event.events) {
|
|
93
|
+
await this.onEvent(item);
|
|
94
|
+
if (this.#stopped)
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
this.#delivering--;
|
|
100
|
+
}
|
|
63
101
|
}
|
|
64
102
|
else if (event.k === "continue")
|
|
65
103
|
next = "continue";
|
|
66
104
|
else if (event.k === "end" || event.k === "paused") {
|
|
67
105
|
if (!this.#stopped)
|
|
68
|
-
this
|
|
106
|
+
this.#notifyExit(event.k === "paused" ? "paused" : event.reason);
|
|
69
107
|
return;
|
|
70
108
|
}
|
|
71
109
|
else if (event.k === "failure")
|
|
72
|
-
throw new
|
|
110
|
+
throw new RuntimeError({
|
|
111
|
+
code: event.code,
|
|
112
|
+
status: event.status ?? 0,
|
|
113
|
+
message: event.message,
|
|
114
|
+
...(event.hint ? { hint: event.hint } : {}),
|
|
115
|
+
...(event.requestId ? { requestId: event.requestId } : {}),
|
|
116
|
+
});
|
|
73
117
|
else
|
|
74
118
|
this.options.onNotice?.(event);
|
|
75
119
|
}
|
|
@@ -85,6 +129,7 @@ export class WatchHandle {
|
|
|
85
129
|
}
|
|
86
130
|
/** @internal */
|
|
87
131
|
begin() {
|
|
132
|
+
this.#exitNotified = false;
|
|
88
133
|
this.#following = this.#follow();
|
|
89
134
|
// An error surfaces through `done`; nothing is left unhandled meanwhile.
|
|
90
135
|
this.#following.catch(() => undefined);
|
|
@@ -126,6 +171,22 @@ export function sandboxWatches(t, sandboxId) {
|
|
|
126
171
|
query: { cursor, ...(input.waitMs !== undefined ? { waitMs: input.waitMs } : {}) },
|
|
127
172
|
...options,
|
|
128
173
|
}),
|
|
174
|
+
/** Starts a watch whose changes Runtime sends to the account's webhooks
|
|
175
|
+
* as `sandbox.files.changed` events, with nothing reading it here:
|
|
176
|
+
*
|
|
177
|
+
* await sbx.files.watches.webhook("/workspace/app", { recursive: true });
|
|
178
|
+
*
|
|
179
|
+
* Runs until stopped (timeoutMs 0) unless given a timeout. */
|
|
180
|
+
webhook: (path, input = {}) => {
|
|
181
|
+
const { idempotencyKey, signal, timeoutMs, ...rest } = input;
|
|
182
|
+
return t.json({
|
|
183
|
+
method: "POST",
|
|
184
|
+
path: base,
|
|
185
|
+
body: { path, ...rest, timeoutMs: timeoutMs ?? WEBHOOK_WATCH_TIMEOUT_MS, webhook: true },
|
|
186
|
+
...(idempotencyKey ? { idempotencyKey } : {}),
|
|
187
|
+
...(signal ? { signal } : {}),
|
|
188
|
+
});
|
|
189
|
+
},
|
|
129
190
|
stop: (id, options) => t.json({
|
|
130
191
|
method: "DELETE",
|
|
131
192
|
path: `${base}/${encodeURIComponent(id)}`,
|