withruntime 0.3.1 → 0.5.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.
Files changed (105) hide show
  1. package/DAYTONA.md +129 -0
  2. package/E2B.md +146 -0
  3. package/README.md +84 -12
  4. package/VERCEL.md +127 -0
  5. package/dist/ai/index.d.ts +33 -0
  6. package/dist/ai/index.js +38 -0
  7. package/dist/claude-agent-sdk/index.d.ts +7 -0
  8. package/dist/claude-agent-sdk/index.js +65 -0
  9. package/dist/cli.d.ts +6 -0
  10. package/dist/cli.js +949 -105
  11. package/dist/client.d.ts +25 -2
  12. package/dist/client.js +14 -1
  13. package/dist/credentials.d.ts +10 -0
  14. package/dist/credentials.js +56 -1
  15. package/dist/daytona/client.d.ts +44 -0
  16. package/dist/daytona/client.js +40 -0
  17. package/dist/daytona/context.d.ts +26 -0
  18. package/dist/daytona/context.js +22 -0
  19. package/dist/daytona/daytona.d.ts +185 -0
  20. package/dist/daytona/daytona.js +416 -0
  21. package/dist/daytona/errors.d.ts +106 -0
  22. package/dist/daytona/errors.js +182 -0
  23. package/dist/daytona/filesystem.d.ts +84 -0
  24. package/dist/daytona/filesystem.js +206 -0
  25. package/dist/daytona/git.d.ts +33 -0
  26. package/dist/daytona/git.js +128 -0
  27. package/dist/daytona/image.d.ts +45 -0
  28. package/dist/daytona/image.js +140 -0
  29. package/dist/daytona/index.d.ts +50 -0
  30. package/dist/daytona/index.js +50 -0
  31. package/dist/daytona/process.d.ts +86 -0
  32. package/dist/daytona/process.js +339 -0
  33. package/dist/daytona/sandbox.d.ts +196 -0
  34. package/dist/daytona/sandbox.js +448 -0
  35. package/dist/e2b/client.d.ts +58 -0
  36. package/dist/e2b/client.js +66 -0
  37. package/dist/e2b/code-interpreter.d.ts +144 -0
  38. package/dist/e2b/code-interpreter.js +244 -0
  39. package/dist/e2b/commands.d.ts +100 -0
  40. package/dist/e2b/commands.js +268 -0
  41. package/dist/e2b/errors.d.ts +72 -0
  42. package/dist/e2b/errors.js +151 -0
  43. package/dist/e2b/filesystem.d.ts +82 -0
  44. package/dist/e2b/filesystem.js +143 -0
  45. package/dist/e2b/index.d.ts +28 -0
  46. package/dist/e2b/index.js +61 -0
  47. package/dist/e2b/sandbox.d.ts +203 -0
  48. package/dist/e2b/sandbox.js +490 -0
  49. package/dist/e2b/unsupported.d.ts +19 -0
  50. package/dist/e2b/unsupported.js +27 -0
  51. package/dist/errors.d.ts +10 -4
  52. package/dist/errors.js +26 -6
  53. package/dist/image-context.d.ts +39 -0
  54. package/dist/image-context.js +176 -0
  55. package/dist/index.d.ts +19 -2
  56. package/dist/index.js +17 -2
  57. package/dist/keys.d.ts +43 -0
  58. package/dist/keys.js +196 -0
  59. package/dist/login.js +27 -6
  60. package/dist/mcp.d.ts +2 -2
  61. package/dist/mcp.js +4 -3
  62. package/dist/openai-agents/index.d.ts +132 -0
  63. package/dist/openai-agents/index.js +791 -0
  64. package/dist/products/audit.d.ts +48 -0
  65. package/dist/products/audit.js +10 -0
  66. package/dist/products/images.d.ts +113 -7
  67. package/dist/products/images.js +186 -27
  68. package/dist/products/index.d.ts +11 -0
  69. package/dist/products/index.js +17 -1
  70. package/dist/products/observability.d.ts +186 -0
  71. package/dist/products/observability.js +117 -0
  72. package/dist/products/secrets.d.ts +40 -0
  73. package/dist/products/secrets.js +24 -0
  74. package/dist/products/switching.d.ts +89 -0
  75. package/dist/products/switching.js +25 -0
  76. package/dist/proxy.d.ts +30 -0
  77. package/dist/proxy.js +178 -0
  78. package/dist/sandbox.d.ts +30 -2
  79. package/dist/sandbox.js +101 -1
  80. package/dist/ssh.d.ts +21 -0
  81. package/dist/ssh.js +312 -0
  82. package/dist/tar.d.ts +1 -0
  83. package/dist/tar.js +4 -4
  84. package/dist/tools/index.d.ts +62 -0
  85. package/dist/tools/index.js +180 -0
  86. package/dist/transport.d.ts +12 -2
  87. package/dist/transport.js +76 -16
  88. package/dist/tunnel.d.ts +67 -0
  89. package/dist/tunnel.js +311 -0
  90. package/dist/types.d.ts +57 -1
  91. package/dist/vercel/client.d.ts +40 -0
  92. package/dist/vercel/client.js +39 -0
  93. package/dist/vercel/command.d.ts +70 -0
  94. package/dist/vercel/command.js +148 -0
  95. package/dist/vercel/errors.d.ts +53 -0
  96. package/dist/vercel/errors.js +113 -0
  97. package/dist/vercel/filesystem.d.ts +150 -0
  98. package/dist/vercel/filesystem.js +248 -0
  99. package/dist/vercel/index.d.ts +30 -0
  100. package/dist/vercel/index.js +38 -0
  101. package/dist/vercel/sandbox.d.ts +317 -0
  102. package/dist/vercel/sandbox.js +726 -0
  103. package/dist/vercel/snapshot.d.ts +93 -0
  104. package/dist/vercel/snapshot.js +123 -0
  105. package/package.json +69 -5
@@ -0,0 +1,140 @@
1
+ import { createHash } from "node:crypto";
2
+ import { NotSupportedError } from "./errors.js";
3
+ const PYTHON = ["3.9", "3.10", "3.11", "3.12", "3.13"];
4
+ /** Daytona's declarative image, kept as the Dockerfile it stands for and
5
+ * built as a Runtime image (`runtime.images.build`) when a sandbox or a
6
+ * snapshot is created from it. Local files go with the build as its context. */
7
+ export class Image {
8
+ #lines;
9
+ #files = [];
10
+ #dockerfilePath;
11
+ constructor(lines) {
12
+ this.#lines = lines;
13
+ }
14
+ /** The Dockerfile so far. */
15
+ get dockerfile() {
16
+ return `${this.#lines.join("\n")}\n`;
17
+ }
18
+ static base(image) {
19
+ return new Image([`FROM ${image}`]);
20
+ }
21
+ static debianSlim(pythonVersion = "3.12") {
22
+ if (!PYTHON.includes(pythonVersion))
23
+ throw new NotSupportedError(`Python ${pythonVersion}`, `Use one of ${PYTHON.join(", ")}.`);
24
+ return new Image([
25
+ `FROM python:${pythonVersion}-slim-bookworm`,
26
+ "RUN apt-get update && apt-get install -y --no-install-recommends gcc gfortran build-essential && rm -rf /var/lib/apt/lists/*",
27
+ "RUN pip install --upgrade pip",
28
+ ]);
29
+ }
30
+ /** A local Dockerfile; files it copies come from its directory. */
31
+ static fromDockerfile(path) {
32
+ const image = new Image([]);
33
+ image.#dockerfilePath = path;
34
+ return image;
35
+ }
36
+ pipInstall(packages, options = {}) {
37
+ const list = [packages].flat();
38
+ if (!list.length)
39
+ return this;
40
+ const flags = [
41
+ ...(options.indexUrl ? [`--index-url ${options.indexUrl}`] : []),
42
+ ...(options.extraIndexUrls ?? []).map((url) => `--extra-index-url ${url}`),
43
+ ...(options.findLinks ?? []).map((url) => `--find-links ${url}`),
44
+ ...(options.pre ? ["--pre"] : []),
45
+ ...(options.extraOptions ? [options.extraOptions] : []),
46
+ ];
47
+ this.#lines.push(`RUN python -m pip install ${list.map((one) => JSON.stringify(one)).join(" ")} ${flags.join(" ")}`.trim());
48
+ return this;
49
+ }
50
+ pipInstallFromRequirements(requirementsTxt) {
51
+ const remote = `/.requirements/${createHash("sha256").update(requirementsTxt).digest("hex").slice(0, 12)}.txt`;
52
+ this.#files.push({ local: requirementsTxt, remote, dir: false });
53
+ this.#lines.push(`COPY ${remote.slice(1)} ${remote}`, `RUN python -m pip install -r ${remote}`);
54
+ return this;
55
+ }
56
+ pipInstallFromPyproject() {
57
+ throw new NotSupportedError("Installing from pyproject.toml (pipInstallFromPyproject)", "Export requirements (`uv export > requirements.txt`) and use pipInstallFromRequirements.");
58
+ }
59
+ addLocalFile(localPath, remotePath) {
60
+ const source = `ctx/${this.#files.length}`;
61
+ this.#files.push({ local: localPath, remote: source, dir: false });
62
+ this.#lines.push(`COPY ${source} ${remotePath}`);
63
+ return this;
64
+ }
65
+ addLocalDir(localPath, remotePath) {
66
+ const source = `ctx/${this.#files.length}`;
67
+ this.#files.push({ local: localPath, remote: source, dir: true });
68
+ this.#lines.push(`COPY ${source} ${remotePath}`);
69
+ return this;
70
+ }
71
+ runCommands(...commands) {
72
+ for (const command of commands)
73
+ this.#lines.push(`RUN ${Array.isArray(command) ? JSON.stringify(command) : command}`);
74
+ return this;
75
+ }
76
+ env(envVars) {
77
+ for (const [key, value] of Object.entries(envVars))
78
+ this.#lines.push(`ENV ${key}=${JSON.stringify(value)}`);
79
+ return this;
80
+ }
81
+ workdir(dirPath) {
82
+ this.#lines.push(`WORKDIR ${dirPath}`);
83
+ return this;
84
+ }
85
+ entrypoint(entrypointCommands) {
86
+ this.#lines.push(`ENTRYPOINT ${JSON.stringify(entrypointCommands)}`);
87
+ return this;
88
+ }
89
+ cmd(cmd) {
90
+ this.#lines.push(`CMD ${JSON.stringify(cmd)}`);
91
+ return this;
92
+ }
93
+ dockerfileCommands(dockerfileCommands) {
94
+ this.#lines.push(...dockerfileCommands);
95
+ return this;
96
+ }
97
+ /** The Dockerfile and its context files, read from the local disk. */
98
+ async build() {
99
+ const { readFile, readdir, stat } = await import("node:fs/promises");
100
+ const paths = await import("node:path");
101
+ const files = [];
102
+ const add = async (local, remote) => {
103
+ const info = await stat(local);
104
+ if (info.isDirectory()) {
105
+ for (const entry of await readdir(local, { recursive: true, withFileTypes: true })) {
106
+ if (!entry.isFile())
107
+ continue;
108
+ const full = paths.join(entry.parentPath, entry.name);
109
+ files.push({
110
+ path: `${remote}/${paths.relative(local, full)}`,
111
+ content: (await readFile(full)).toString("base64"),
112
+ encoding: "base64",
113
+ });
114
+ }
115
+ }
116
+ else
117
+ files.push({
118
+ path: remote,
119
+ content: (await readFile(local)).toString("base64"),
120
+ encoding: "base64",
121
+ mode: info.mode & 0o777,
122
+ });
123
+ };
124
+ let dockerfile = this.dockerfile;
125
+ if (this.#dockerfilePath) {
126
+ dockerfile = await readFile(this.#dockerfilePath, "utf8");
127
+ const context = paths.dirname(this.#dockerfilePath);
128
+ for (const match of dockerfile.matchAll(/^\s*(?:COPY|ADD)\s+(?:--\S+\s+)*(.+?)\s+\S+\s*$/gim))
129
+ for (const source of match[1].split(/\s+/))
130
+ if (!/^[a-z]+:\/\//.test(source))
131
+ await add(paths.join(context, source), source.replace(/^\.\//, ""));
132
+ }
133
+ for (const file of this.#files)
134
+ await add(file.local, file.remote);
135
+ const digest = createHash("sha256").update(dockerfile);
136
+ for (const file of files)
137
+ digest.update(file.path).update(file.content);
138
+ return { dockerfile, files, name: `daytona-image-${digest.digest("hex").slice(0, 16)}` };
139
+ }
140
+ }
@@ -0,0 +1,50 @@
1
+ export { Daytona, CodeLanguage, SnapshotService, VolumeService, DEFAULT_RESOURCES, DEFAULT_AUTO_STOP_MINUTES, lifecycleOf, type CreateSandboxBaseParams, type CreateSandboxFromImageParams, type CreateSandboxFromSnapshotParams, type CreateSnapshotParams, type ForkSandboxParams, type ListSandboxesQuery, type PaginatedSnapshots, type Resources, type Snapshot, type Volume, type VolumeMount, } from "./daytona.js";
2
+ export { Sandbox, CodeInterpreter, networkRules, windowSeconds, type ExecutionError, type ExecutionResult, type InterpreterContext, type OutputMessage, type PortPreviewUrl, } from "./sandbox.js";
3
+ export { Process, CodeRunParams, type Command, type ExecuteResponse, type Session, type SessionCommandLogsResponse, type SessionExecuteRequest, type SessionExecuteResponse, } from "./process.js";
4
+ export { FileSystem, type FileDownloadRequest, type FileDownloadResponse, type FileInfo, type FilePermissionsParams, type FileUpload, type Match, type ReplaceResult, type SearchFilesResponse, type UploadSource, } from "./filesystem.js";
5
+ export { Git, type GitStatus, type ListBranchResponse } from "./git.js";
6
+ export { Image } from "./image.js";
7
+ export { resolvePath } from "./context.js";
8
+ export type { DaytonaConfig, WithRuntime } from "./client.js";
9
+ export * from "./errors.js";
10
+ /** Daytona's sandbox states, as strings. */
11
+ export declare const SandboxState: {
12
+ readonly STARTED: "started";
13
+ readonly STARTING: "starting";
14
+ readonly STOPPING: "stopping";
15
+ readonly STOPPED: "stopped";
16
+ readonly PAUSED: "paused";
17
+ readonly DESTROYING: "destroying";
18
+ readonly DESTROYED: "destroyed";
19
+ };
20
+ export type SandboxState = (typeof SandboxState)[keyof typeof SandboxState];
21
+ export declare const ComputerUse: {
22
+ (...args: unknown[]): never;
23
+ new (...args: unknown[]): never;
24
+ readonly [key: string]: (...args: unknown[]) => never;
25
+ };
26
+ export declare const Mouse: {
27
+ (...args: unknown[]): never;
28
+ new (...args: unknown[]): never;
29
+ readonly [key: string]: (...args: unknown[]) => never;
30
+ };
31
+ export declare const Keyboard: {
32
+ (...args: unknown[]): never;
33
+ new (...args: unknown[]): never;
34
+ readonly [key: string]: (...args: unknown[]) => never;
35
+ };
36
+ export declare const Screenshot: {
37
+ (...args: unknown[]): never;
38
+ new (...args: unknown[]): never;
39
+ readonly [key: string]: (...args: unknown[]) => never;
40
+ };
41
+ export declare const Display: {
42
+ (...args: unknown[]): never;
43
+ new (...args: unknown[]): never;
44
+ readonly [key: string]: (...args: unknown[]) => never;
45
+ };
46
+ export declare const LspLanguageId: {
47
+ (...args: unknown[]): never;
48
+ new (...args: unknown[]): never;
49
+ readonly [key: string]: (...args: unknown[]) => never;
50
+ };
@@ -0,0 +1,50 @@
1
+ /** Code written for Daytona's SDK, on Runtime Cloud. Change the import:
2
+ *
3
+ * import { Daytona } from "withruntime/daytona"; // was: from "@daytona/sdk" or "@daytonaio/sdk"
4
+ *
5
+ * and set RUNTIME_API_KEY (or put a Runtime key in DAYTONA_API_KEY), or run
6
+ * `npx withruntime login` once. A Daytona key is never sent anywhere. What
7
+ * Runtime cannot do the way Daytona does throws NotSupportedError, naming what
8
+ * to use. */
9
+ import { NotSupportedError } from "./errors.js";
10
+ export { Daytona, CodeLanguage, SnapshotService, VolumeService, DEFAULT_RESOURCES, DEFAULT_AUTO_STOP_MINUTES, lifecycleOf, } from "./daytona.js";
11
+ export { Sandbox, CodeInterpreter, networkRules, windowSeconds, } from "./sandbox.js";
12
+ export { Process, CodeRunParams, } from "./process.js";
13
+ export { FileSystem, } from "./filesystem.js";
14
+ export { Git } from "./git.js";
15
+ export { Image } from "./image.js";
16
+ export { resolvePath } from "./context.js";
17
+ export * from "./errors.js";
18
+ /** Daytona's sandbox states, as strings. */
19
+ export const SandboxState = {
20
+ STARTED: "started",
21
+ STARTING: "starting",
22
+ STOPPING: "stopping",
23
+ STOPPED: "stopped",
24
+ PAUSED: "paused",
25
+ DESTROYING: "destroying",
26
+ DESTROYED: "destroyed",
27
+ };
28
+ /** A stand-in for a Daytona export Runtime has no counterpart for. Importing
29
+ * it works; using it throws NotSupportedError naming the alternative. */
30
+ function unsupportedExport(name, alternative) {
31
+ const fail = () => {
32
+ throw new NotSupportedError(`Daytona's ${name}`, alternative);
33
+ };
34
+ return new Proxy(function unsupported() { }, {
35
+ apply: fail,
36
+ construct: fail,
37
+ get: (_target, property) => {
38
+ if (typeof property === "symbol" || property === "then" || property === "prototype")
39
+ return undefined;
40
+ return fail();
41
+ },
42
+ });
43
+ }
44
+ const DESKTOP = "Use Runtime's desktop: `await sandbox.withruntime.desktop.start()`.";
45
+ export const ComputerUse = unsupportedExport("computer use", DESKTOP);
46
+ export const Mouse = unsupportedExport("computer use (Mouse)", DESKTOP);
47
+ export const Keyboard = unsupportedExport("computer use (Keyboard)", DESKTOP);
48
+ export const Screenshot = unsupportedExport("computer use (Screenshot)", DESKTOP);
49
+ export const Display = unsupportedExport("computer use (Display)", DESKTOP);
50
+ export const LspLanguageId = unsupportedExport("language servers", "Start one in a session with process.executeSessionCommand(..., { runAsync: true }).");
@@ -0,0 +1,86 @@
1
+ import { quote, type SandboxContext } from "./context.js";
2
+ /** Daytona's ExecuteResponse. `result` is the command's stdout and stderr
3
+ * together, in the order they were written. */
4
+ export interface ExecuteResponse {
5
+ exitCode: number;
6
+ result: string;
7
+ artifacts?: {
8
+ stdout: string;
9
+ charts?: unknown[];
10
+ };
11
+ }
12
+ export declare class CodeRunParams {
13
+ argv?: string[];
14
+ env?: Record<string, string>;
15
+ }
16
+ export interface SessionExecuteRequest {
17
+ command: string;
18
+ runAsync?: boolean;
19
+ /** @deprecated Daytona's older name for runAsync. */
20
+ async?: boolean;
21
+ suppressInputEcho?: boolean;
22
+ }
23
+ export interface SessionExecuteResponse {
24
+ cmdId?: string;
25
+ output?: string;
26
+ stdout?: string;
27
+ stderr?: string;
28
+ exitCode?: number;
29
+ }
30
+ export interface SessionCommandLogsResponse {
31
+ output?: string;
32
+ stdout?: string;
33
+ stderr?: string;
34
+ }
35
+ export interface Command {
36
+ id: string;
37
+ command: string;
38
+ exitCode?: number;
39
+ }
40
+ export interface Session {
41
+ sessionId: string;
42
+ commands: Command[];
43
+ }
44
+ /** `sandbox.process`: Daytona's commands, code runs and sessions over
45
+ * Runtime's exec and processes. */
46
+ export declare class Process {
47
+ #private;
48
+ constructor(ctx: SandboxContext);
49
+ /** Runs a shell command and resolves with its exit code and output. A
50
+ * non-zero exit is a result, not an error. `timeout` is in seconds; past
51
+ * it the command is killed and DaytonaProcessExecutionTimeoutError is
52
+ * thrown. */
53
+ executeCommand(command: string, cwd?: string, env?: Record<string, string>, timeout?: number): Promise<ExecuteResponse>;
54
+ /** Runs code in the sandbox's language (python by default, or javascript or
55
+ * typescript as given at create) as a fresh process. `artifacts.charts` is
56
+ * always empty on Runtime. */
57
+ codeRun(code: string, params?: CodeRunParams, timeout?: number): Promise<ExecuteResponse>;
58
+ /** Starts a session: a bash that keeps its directory and variables from
59
+ * one command to the next. Runs one command at a time; a command sent while
60
+ * another runs waits for it, as it would typed into a shell. */
61
+ createSession(sessionId: string): Promise<void>;
62
+ getSession(sessionId: string): Promise<Session>;
63
+ getSessionCommand(sessionId: string, commandId: string): Promise<Command>;
64
+ /** Runs a command in the session. It waits and returns the output and exit
65
+ * code, or with `runAsync: true` returns the cmdId at once. */
66
+ executeSessionCommand(sessionId: string, req: SessionExecuteRequest, _timeout?: number): Promise<SessionExecuteResponse>;
67
+ /** A command's output so far, or with callbacks, its output from the start
68
+ * as it happens until it ends. */
69
+ getSessionCommandLogs(sessionId: string, commandId: string): Promise<SessionCommandLogsResponse>;
70
+ getSessionCommandLogs(sessionId: string, commandId: string, onStdout: (chunk: string) => void, onStderr: (chunk: string) => void): Promise<void>;
71
+ /** Types `data` into the session, where the running command reads it. */
72
+ sendSessionCommandInput(sessionId: string, commandId: string, data: string): Promise<void>;
73
+ /** Sessions this object made or found. */
74
+ listSessions(): Promise<Session[]>;
75
+ /** Ends the session's shell and everything it runs. */
76
+ deleteSession(sessionId: string): Promise<void>;
77
+ getEntrypointSession(): Promise<never>;
78
+ getEntrypointLogs(): Promise<never>;
79
+ createPty(): Promise<never>;
80
+ connectPty(): Promise<never>;
81
+ listPtySessions(): Promise<never>;
82
+ getPtySessionInfo(): Promise<never>;
83
+ killPtySession(): Promise<never>;
84
+ resizePtySession(): Promise<never>;
85
+ }
86
+ export { quote };
@@ -0,0 +1,339 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { HOME, LONGEST_MS, quote, resolvePath } from "./context.js";
3
+ import { DaytonaCommandAlreadyCompletedError, DaytonaConflictError, DaytonaNotFoundError, DaytonaProcessExecutionTimeoutError, DaytonaSessionEndedError, guard, NotSupportedError, } from "./errors.js";
4
+ export class CodeRunParams {
5
+ argv;
6
+ env;
7
+ }
8
+ const MARK = "\x1e";
9
+ /* Each session command is one line to the session's bash, which bash reads
10
+ whole before running it, so input sent later reaches the command, not bash.
11
+ The command travels base64-encoded and runs through eval, keeping the
12
+ shell's directory, variables and functions from one command to the next;
13
+ the markers say where its output ends and what it exited with. */
14
+ const PRELUDE = '__rt_run() { eval "$(printf %s "$1" | base64 -d)"; __rt_c=$?; ' +
15
+ 'printf \'\\036RT%s:%s\\036\' "$2" "$__rt_c"; printf \'\\036RT%s\\036\' "$2" >&2; return $__rt_c; }\n';
16
+ const TAG = "daytona-session:";
17
+ class SessionCommand {
18
+ id;
19
+ command;
20
+ exitCode;
21
+ stdout = "";
22
+ stderr = "";
23
+ output = "";
24
+ done = Promise.resolve();
25
+ /** Settles once the command's line has reached the session's shell: input
26
+ * sent before then would be read by the shell, not the command. */
27
+ written = Promise.resolve();
28
+ listeners = new Set();
29
+ constructor(id, command) {
30
+ this.id = id;
31
+ this.command = command;
32
+ }
33
+ emit(stream, text) {
34
+ if (!text)
35
+ return;
36
+ this[stream] += text;
37
+ this.output += text;
38
+ for (const listener of this.listeners)
39
+ (stream === "stdout" ? listener.onStdout : listener.onStderr)(text);
40
+ }
41
+ }
42
+ class ShellSession {
43
+ id;
44
+ process;
45
+ cursor;
46
+ commands = [];
47
+ tail = Promise.resolve();
48
+ ended = false;
49
+ constructor(id, process, cursor) {
50
+ this.id = id;
51
+ this.process = process;
52
+ this.cursor = cursor;
53
+ }
54
+ }
55
+ /** `sandbox.process`: Daytona's commands, code runs and sessions over
56
+ * Runtime's exec and processes. */
57
+ export class Process {
58
+ #ctx;
59
+ #sessions = new Map();
60
+ constructor(ctx) {
61
+ this.#ctx = ctx;
62
+ }
63
+ #env(extra) {
64
+ const merged = { ...this.#ctx.env, ...extra };
65
+ return Object.keys(merged).length ? { env: merged } : {};
66
+ }
67
+ /** Runs a shell command and resolves with its exit code and output. A
68
+ * non-zero exit is a result, not an error. `timeout` is in seconds; past
69
+ * it the command is killed and DaytonaProcessExecutionTimeoutError is
70
+ * thrown. */
71
+ async executeCommand(command, cwd, env, timeout) {
72
+ await this.#ctx.ensureHome(`${command}\n${cwd ?? ""}`);
73
+ const runtime = await this.#ctx.live();
74
+ const timeoutMs = timeout ? timeout * 1000 : LONGEST_MS;
75
+ const result = await guard("sandbox", () => runtime.exec(`{ ${command}\n} 2>&1`, {
76
+ cwd: cwd === undefined ? HOME : resolvePath(cwd),
77
+ ...this.#env(env),
78
+ timeoutMs,
79
+ }));
80
+ if (result.timedOut)
81
+ throw new DaytonaProcessExecutionTimeoutError(`Command timed out after ${timeout} s. Pass a larger timeout, or 0 for none.`, 408);
82
+ const output = result.stdout + result.stderr;
83
+ return { exitCode: result.exitCode ?? -1, result: output, artifacts: { stdout: output } };
84
+ }
85
+ /** Runs code in the sandbox's language (python by default, or javascript or
86
+ * typescript as given at create) as a fresh process. `artifacts.charts` is
87
+ * always empty on Runtime. */
88
+ async codeRun(code, params = {}, timeout) {
89
+ const runtime = await this.#ctx.live();
90
+ const interpreter = this.#ctx.language === "python"
91
+ ? ["python3", "-c", code]
92
+ : this.#ctx.language === "typescript"
93
+ ? ["bun", "-e", code]
94
+ : ["node", "-e", code];
95
+ const timeoutMs = timeout ? timeout * 1000 : LONGEST_MS;
96
+ const result = await guard("sandbox", () => runtime.exec(["sh", "-c", 'exec "$@" 2>&1', "sh", ...interpreter, ...(params.argv ?? [])], {
97
+ cwd: HOME,
98
+ ...this.#env(params.env),
99
+ timeoutMs,
100
+ }));
101
+ if (result.timedOut)
102
+ throw new DaytonaProcessExecutionTimeoutError(`Code run timed out after ${timeout} s.`, 408);
103
+ return {
104
+ exitCode: result.exitCode ?? -1,
105
+ result: result.stdout,
106
+ artifacts: { stdout: result.stdout, charts: [] },
107
+ };
108
+ }
109
+ // ---- sessions ----------------------------------------------------------
110
+ /** Starts a session: a bash that keeps its directory and variables from
111
+ * one command to the next. Runs one command at a time; a command sent while
112
+ * another runs waits for it, as it would typed into a shell. */
113
+ async createSession(sessionId) {
114
+ if (this.#sessions.has(sessionId) || (await this.#discover(sessionId)))
115
+ throw new DaytonaConflictError(`Session ${sessionId} already exists.`, 409);
116
+ const runtime = await this.#ctx.live();
117
+ const process = await guard("sandbox", () => runtime.spawn(["bash", "--noprofile", "--norc", "-s", `${TAG}${sessionId}`], {
118
+ cwd: HOME,
119
+ ...this.#env(),
120
+ stdin: "pipe",
121
+ timeoutMs: LONGEST_MS,
122
+ }));
123
+ await guard("process", () => process.write(PRELUDE));
124
+ this.#sessions.set(sessionId, new ShellSession(sessionId, process, 0));
125
+ }
126
+ /** A session made by another client (another Daytona object on this
127
+ * sandbox): found by its tag among the running processes. Its output from
128
+ * now on is followed; earlier commands are not known here. */
129
+ async #discover(sessionId) {
130
+ const runtime = await this.#ctx.live();
131
+ const list = await guard("sandbox", () => runtime.processes.list());
132
+ const info = list.find((one) => one.state === "running" && one.command.split(" ").includes(`${TAG}${sessionId}`));
133
+ if (!info)
134
+ return undefined;
135
+ const process = await guard("process", () => runtime.processes.get(info.id));
136
+ const session = new ShellSession(sessionId, process, info.outputBytes);
137
+ this.#sessions.set(sessionId, session);
138
+ return session;
139
+ }
140
+ async #session(sessionId) {
141
+ const session = this.#sessions.get(sessionId) ?? (await this.#discover(sessionId));
142
+ if (!session)
143
+ throw new DaytonaNotFoundError(`Session ${sessionId} was not found.`, 404);
144
+ if (session.ended)
145
+ throw new DaytonaSessionEndedError(`Session ${sessionId} has ended.`, 410);
146
+ return session;
147
+ }
148
+ #command(session, commandId) {
149
+ const found = session.commands.find((one) => one.id === commandId);
150
+ if (!found)
151
+ throw new DaytonaNotFoundError(`Command ${commandId} was not found in session ${session.id} (commands run from another client are not known here).`, 404);
152
+ return found;
153
+ }
154
+ async getSession(sessionId) {
155
+ const session = await this.#session(sessionId);
156
+ return { sessionId, commands: session.commands.map(describe) };
157
+ }
158
+ async getSessionCommand(sessionId, commandId) {
159
+ return describe(this.#command(await this.#session(sessionId), commandId));
160
+ }
161
+ /** Runs a command in the session. It waits and returns the output and exit
162
+ * code, or with `runAsync: true` returns the cmdId at once. */
163
+ async executeSessionCommand(sessionId, req, _timeout) {
164
+ const session = await this.#session(sessionId);
165
+ await this.#ctx.ensureHome(req.command);
166
+ await this.#ctx.live();
167
+ const command = new SessionCommand(randomUUID(), req.command);
168
+ session.commands.push(command);
169
+ const previous = session.tail;
170
+ let release;
171
+ session.tail = new Promise((resolve) => (release = resolve));
172
+ let sent;
173
+ command.written = new Promise((resolve) => (sent = resolve));
174
+ command.done = (async () => {
175
+ try {
176
+ await previous;
177
+ if (session.ended)
178
+ throw new DaytonaSessionEndedError(`Session ${sessionId} has ended.`, 410);
179
+ const encoded = Buffer.from(req.command).toString("base64");
180
+ await guard("process", () => session.process.write(`__rt_run ${encoded} ${command.id}\n`));
181
+ sent();
182
+ await this.#follow(session, command);
183
+ }
184
+ finally {
185
+ sent();
186
+ release();
187
+ }
188
+ })();
189
+ if (req.runAsync || req.async) {
190
+ command.done.catch(() => undefined);
191
+ return { cmdId: command.id };
192
+ }
193
+ await command.done;
194
+ return {
195
+ cmdId: command.id,
196
+ output: command.output,
197
+ stdout: command.stdout,
198
+ stderr: command.stderr,
199
+ ...(command.exitCode === undefined ? {} : { exitCode: command.exitCode }),
200
+ };
201
+ }
202
+ async #follow(session, command) {
203
+ const outMark = `${MARK}RT${command.id}:`;
204
+ const errMark = `${MARK}RT${command.id}${MARK}`;
205
+ const buffer = { stdout: "", stderr: "" };
206
+ const sent = { stdout: 0, stderr: 0 };
207
+ let outDone = false;
208
+ let errDone = false;
209
+ // Sends what is certainly the command's: everything before a marker, or
210
+ // before a marker's first byte that may still be arriving.
211
+ const flush = (stream, end) => {
212
+ command.emit(stream, buffer[stream].slice(sent[stream], end));
213
+ sent[stream] = Math.max(sent[stream], end);
214
+ };
215
+ await guard("process", async () => {
216
+ for await (const event of session.process.output({ cursor: session.cursor })) {
217
+ if (event.type === "exit") {
218
+ session.ended = true;
219
+ flush("stdout", buffer.stdout.length);
220
+ flush("stderr", buffer.stderr.length);
221
+ command.exitCode = event.exitCode ?? -1;
222
+ return;
223
+ }
224
+ if (event.type !== "stdout" && event.type !== "stderr")
225
+ continue;
226
+ session.cursor = event.offset + Buffer.byteLength(event.data);
227
+ buffer[event.type] += event.data;
228
+ if (event.type === "stdout" && !outDone) {
229
+ const at = buffer.stdout.indexOf(outMark);
230
+ const end = at < 0 ? -1 : buffer.stdout.indexOf(MARK, at + outMark.length);
231
+ if (end >= 0) {
232
+ flush("stdout", at);
233
+ command.exitCode = Number(buffer.stdout.slice(at + outMark.length, end));
234
+ outDone = true;
235
+ }
236
+ else {
237
+ const pending = buffer.stdout.lastIndexOf(MARK);
238
+ flush("stdout", pending < 0 ? buffer.stdout.length : pending);
239
+ }
240
+ }
241
+ if (event.type === "stderr" && !errDone) {
242
+ const at = buffer.stderr.indexOf(errMark);
243
+ if (at >= 0) {
244
+ flush("stderr", at);
245
+ errDone = true;
246
+ }
247
+ else {
248
+ const pending = buffer.stderr.lastIndexOf(MARK);
249
+ flush("stderr", pending < 0 ? buffer.stderr.length : pending);
250
+ }
251
+ }
252
+ if (outDone && errDone)
253
+ return;
254
+ }
255
+ session.ended = true;
256
+ });
257
+ }
258
+ async getSessionCommandLogs(sessionId, commandId, onStdout, onStderr) {
259
+ const command = this.#command(await this.#session(sessionId), commandId);
260
+ if (!onStdout && !onStderr)
261
+ return { output: command.output, stdout: command.stdout, stderr: command.stderr };
262
+ const listener = {
263
+ onStdout: onStdout ?? (() => undefined),
264
+ onStderr: onStderr ?? (() => undefined),
265
+ };
266
+ if (command.stdout)
267
+ listener.onStdout(command.stdout);
268
+ if (command.stderr)
269
+ listener.onStderr(command.stderr);
270
+ command.listeners.add(listener);
271
+ try {
272
+ await command.done;
273
+ }
274
+ finally {
275
+ command.listeners.delete(listener);
276
+ }
277
+ }
278
+ /** Types `data` into the session, where the running command reads it. */
279
+ async sendSessionCommandInput(sessionId, commandId, data) {
280
+ const session = await this.#session(sessionId);
281
+ const command = this.#command(session, commandId);
282
+ await command.written;
283
+ if (command.exitCode !== undefined)
284
+ throw new DaytonaCommandAlreadyCompletedError(`Command ${commandId} has already completed.`, 410);
285
+ await guard("process", () => session.process.write(data));
286
+ }
287
+ /** Sessions this object made or found. */
288
+ async listSessions() {
289
+ return [...this.#sessions.values()]
290
+ .filter((one) => !one.ended)
291
+ .map((one) => ({ sessionId: one.id, commands: one.commands.map(describe) }));
292
+ }
293
+ /** Ends the session's shell and everything it runs. */
294
+ async deleteSession(sessionId) {
295
+ const session = await this.#session(sessionId);
296
+ session.ended = true;
297
+ this.#sessions.delete(sessionId);
298
+ await guard("process", () => session.process.kill("SIGKILL"));
299
+ }
300
+ getEntrypointSession() {
301
+ return entrypoint();
302
+ }
303
+ getEntrypointLogs() {
304
+ return entrypoint();
305
+ }
306
+ // ---- terminals ---------------------------------------------------------
307
+ createPty() {
308
+ return pty();
309
+ }
310
+ connectPty() {
311
+ return pty();
312
+ }
313
+ listPtySessions() {
314
+ return pty();
315
+ }
316
+ getPtySessionInfo() {
317
+ return pty();
318
+ }
319
+ killPtySession() {
320
+ return pty();
321
+ }
322
+ resizePtySession() {
323
+ return pty();
324
+ }
325
+ }
326
+ function describe(command) {
327
+ return {
328
+ id: command.id,
329
+ command: command.command,
330
+ ...(command.exitCode === undefined ? {} : { exitCode: command.exitCode }),
331
+ };
332
+ }
333
+ function pty() {
334
+ return Promise.reject(new NotSupportedError("Daytona's PTY sessions", "Use `await sandbox.withruntime.terminal({ cols, rows, onData })` for an interactive terminal, or a session for commands."));
335
+ }
336
+ function entrypoint() {
337
+ return Promise.reject(new NotSupportedError("The image entrypoint session", "A Runtime image does not run an entrypoint; start it with process.createSession and executeSessionCommand."));
338
+ }
339
+ export { quote };