@alchemy.run/e2b 0.0.0 → 2.0.0-beta.86
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 +201 -0
- package/README.md +56 -4
- package/lib/ApiKey.d.ts +53 -0
- package/lib/ApiKey.d.ts.map +1 -0
- package/lib/ApiKey.js +107 -0
- package/lib/ApiKey.js.map +1 -0
- package/lib/AuthProvider.d.ts +60 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +293 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Bootstrap.d.ts +7 -0
- package/lib/Bootstrap.d.ts.map +1 -0
- package/lib/Bootstrap.js +25 -0
- package/lib/Bootstrap.js.map +1 -0
- package/lib/ConnectSandbox.d.ts +67 -0
- package/lib/ConnectSandbox.d.ts.map +1 -0
- package/lib/ConnectSandbox.js +3 -0
- package/lib/ConnectSandbox.js.map +1 -0
- package/lib/ConnectSandboxHttp.d.ts +22 -0
- package/lib/ConnectSandboxHttp.d.ts.map +1 -0
- package/lib/ConnectSandboxHttp.js +30 -0
- package/lib/ConnectSandboxHttp.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/CreateSandbox.d.ts +139 -0
- package/lib/CreateSandbox.d.ts.map +1 -0
- package/lib/CreateSandbox.js +3 -0
- package/lib/CreateSandbox.js.map +1 -0
- package/lib/CreateSandboxHttp.d.ts +22 -0
- package/lib/CreateSandboxHttp.d.ts.map +1 -0
- package/lib/CreateSandboxHttp.js +99 -0
- package/lib/CreateSandboxHttp.js.map +1 -0
- package/lib/CreateSecret.d.ts +57 -0
- package/lib/CreateSecret.d.ts.map +1 -0
- package/lib/CreateSecret.js +3 -0
- package/lib/CreateSecret.js.map +1 -0
- package/lib/CreateSecretHttp.d.ts +20 -0
- package/lib/CreateSecretHttp.d.ts.map +1 -0
- package/lib/CreateSecretHttp.js +64 -0
- package/lib/CreateSecretHttp.js.map +1 -0
- package/lib/CreateSnapshot.d.ts +53 -0
- package/lib/CreateSnapshot.d.ts.map +1 -0
- package/lib/CreateSnapshot.js +3 -0
- package/lib/CreateSnapshot.js.map +1 -0
- package/lib/CreateSnapshotHttp.d.ts +20 -0
- package/lib/CreateSnapshotHttp.d.ts.map +1 -0
- package/lib/CreateSnapshotHttp.js +41 -0
- package/lib/CreateSnapshotHttp.js.map +1 -0
- package/lib/CreateTemplate.d.ts +96 -0
- package/lib/CreateTemplate.d.ts.map +1 -0
- package/lib/CreateTemplate.js +3 -0
- package/lib/CreateTemplate.js.map +1 -0
- package/lib/CreateTemplateHttp.d.ts +20 -0
- package/lib/CreateTemplateHttp.d.ts.map +1 -0
- package/lib/CreateTemplateHttp.js +99 -0
- package/lib/CreateTemplateHttp.js.map +1 -0
- package/lib/CreateVolume.d.ts +58 -0
- package/lib/CreateVolume.d.ts.map +1 -0
- package/lib/CreateVolume.js +3 -0
- package/lib/CreateVolume.js.map +1 -0
- package/lib/CreateVolumeHttp.d.ts +20 -0
- package/lib/CreateVolumeHttp.d.ts.map +1 -0
- package/lib/CreateVolumeHttp.js +51 -0
- package/lib/CreateVolumeHttp.js.map +1 -0
- package/lib/Credentials.d.ts +18 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +55 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/E2BBindings.d.ts +35 -0
- package/lib/E2BBindings.d.ts.map +1 -0
- package/lib/E2BBindings.js +127 -0
- package/lib/E2BBindings.js.map +1 -0
- package/lib/Errors.d.ts +61 -0
- package/lib/Errors.d.ts.map +1 -0
- package/lib/Errors.js +23 -0
- package/lib/Errors.js.map +1 -0
- package/lib/InferEnv.d.ts +26 -0
- package/lib/InferEnv.d.ts.map +1 -0
- package/lib/InferEnv.js +1 -0
- package/lib/InferEnv.js.map +1 -0
- package/lib/MountVolume.d.ts +86 -0
- package/lib/MountVolume.d.ts.map +1 -0
- package/lib/MountVolume.js +3 -0
- package/lib/MountVolume.js.map +1 -0
- package/lib/MountVolumeLive.d.ts +23 -0
- package/lib/MountVolumeLive.d.ts.map +1 -0
- package/lib/MountVolumeLive.js +41 -0
- package/lib/MountVolumeLive.js.map +1 -0
- package/lib/Providers.d.ts +14 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +23 -0
- package/lib/Providers.js.map +1 -0
- package/lib/ReadTeam.d.ts +41 -0
- package/lib/ReadTeam.d.ts.map +1 -0
- package/lib/ReadTeam.js +3 -0
- package/lib/ReadTeam.js.map +1 -0
- package/lib/ReadTeamHttp.d.ts +20 -0
- package/lib/ReadTeamHttp.d.ts.map +1 -0
- package/lib/ReadTeamHttp.js +40 -0
- package/lib/ReadTeamHttp.js.map +1 -0
- package/lib/ReadVolume.d.ts +25 -0
- package/lib/ReadVolume.d.ts.map +1 -0
- package/lib/ReadVolume.js +3 -0
- package/lib/ReadVolume.js.map +1 -0
- package/lib/ReadVolumeHttp.d.ts +20 -0
- package/lib/ReadVolumeHttp.d.ts.map +1 -0
- package/lib/ReadVolumeHttp.js +27 -0
- package/lib/ReadVolumeHttp.js.map +1 -0
- package/lib/ReadWebhook.d.ts +32 -0
- package/lib/ReadWebhook.d.ts.map +1 -0
- package/lib/ReadWebhook.js +3 -0
- package/lib/ReadWebhook.js.map +1 -0
- package/lib/ReadWebhookHttp.d.ts +20 -0
- package/lib/ReadWebhookHttp.d.ts.map +1 -0
- package/lib/ReadWebhookHttp.js +31 -0
- package/lib/ReadWebhookHttp.js.map +1 -0
- package/lib/ReadWriteVolume.d.ts +26 -0
- package/lib/ReadWriteVolume.d.ts.map +1 -0
- package/lib/ReadWriteVolume.js +3 -0
- package/lib/ReadWriteVolume.js.map +1 -0
- package/lib/ReadWriteVolumeHttp.d.ts +20 -0
- package/lib/ReadWriteVolumeHttp.d.ts.map +1 -0
- package/lib/ReadWriteVolumeHttp.js +27 -0
- package/lib/ReadWriteVolumeHttp.js.map +1 -0
- package/lib/Sandbox.d.ts +295 -0
- package/lib/Sandbox.d.ts.map +1 -0
- package/lib/Sandbox.js +351 -0
- package/lib/Sandbox.js.map +1 -0
- package/lib/SandboxClient.d.ts +226 -0
- package/lib/SandboxClient.d.ts.map +1 -0
- package/lib/SandboxClient.js +1 -0
- package/lib/SandboxClient.js.map +1 -0
- package/lib/SandboxEvents.d.ts +87 -0
- package/lib/SandboxEvents.d.ts.map +1 -0
- package/lib/SandboxEvents.js +39 -0
- package/lib/SandboxEvents.js.map +1 -0
- package/lib/SandboxEventsLive.d.ts +13 -0
- package/lib/SandboxEventsLive.d.ts.map +1 -0
- package/lib/SandboxEventsLive.js +80 -0
- package/lib/SandboxEventsLive.js.map +1 -0
- package/lib/Secret.d.ts +105 -0
- package/lib/Secret.d.ts.map +1 -0
- package/lib/Secret.js +161 -0
- package/lib/Secret.js.map +1 -0
- package/lib/Snapshot.d.ts +118 -0
- package/lib/Snapshot.d.ts.map +1 -0
- package/lib/Snapshot.js +158 -0
- package/lib/Snapshot.js.map +1 -0
- package/lib/Template.d.ts +313 -0
- package/lib/Template.d.ts.map +1 -0
- package/lib/Template.js +421 -0
- package/lib/Template.js.map +1 -0
- package/lib/Volume.d.ts +88 -0
- package/lib/Volume.d.ts.map +1 -0
- package/lib/Volume.js +136 -0
- package/lib/Volume.js.map +1 -0
- package/lib/VolumeFiles.d.ts +39 -0
- package/lib/VolumeFiles.d.ts.map +1 -0
- package/lib/VolumeFiles.js +1 -0
- package/lib/VolumeFiles.js.map +1 -0
- package/lib/Webhook.d.ts +83 -0
- package/lib/Webhook.d.ts.map +1 -0
- package/lib/Webhook.js +138 -0
- package/lib/Webhook.js.map +1 -0
- package/lib/WriteSecret.d.ts +34 -0
- package/lib/WriteSecret.d.ts.map +1 -0
- package/lib/WriteSecret.js +3 -0
- package/lib/WriteSecret.js.map +1 -0
- package/lib/WriteSecretHttp.d.ts +20 -0
- package/lib/WriteSecretHttp.d.ts.map +1 -0
- package/lib/WriteSecretHttp.js +37 -0
- package/lib/WriteSecretHttp.js.map +1 -0
- package/lib/WriteVolume.d.ts +25 -0
- package/lib/WriteVolume.d.ts.map +1 -0
- package/lib/WriteVolume.js +3 -0
- package/lib/WriteVolume.js.map +1 -0
- package/lib/WriteVolumeHttp.d.ts +20 -0
- package/lib/WriteVolumeHttp.d.ts.map +1 -0
- package/lib/WriteVolumeHttp.js +27 -0
- package/lib/WriteVolumeHttp.js.map +1 -0
- package/lib/index.d.ts +43 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +43 -0
- package/lib/index.js.map +1 -0
- package/lib/internal/Declared.d.ts +29 -0
- package/lib/internal/Declared.d.ts.map +1 -0
- package/lib/internal/Declared.js +31 -0
- package/lib/internal/Declared.js.map +1 -0
- package/lib/internal/Login.d.ts +45 -0
- package/lib/internal/Login.d.ts.map +1 -0
- package/lib/internal/Login.js +157 -0
- package/lib/internal/Login.js.map +1 -0
- package/lib/internal/Ownership.d.ts +29 -0
- package/lib/internal/Ownership.d.ts.map +1 -0
- package/lib/internal/Ownership.js +49 -0
- package/lib/internal/Ownership.js.map +1 -0
- package/lib/internal/Program.d.ts +31 -0
- package/lib/internal/Program.d.ts.map +1 -0
- package/lib/internal/Program.js +60 -0
- package/lib/internal/Program.js.map +1 -0
- package/lib/internal/SandboxHandle.d.ts +25 -0
- package/lib/internal/SandboxHandle.d.ts.map +1 -0
- package/lib/internal/SandboxHandle.js +316 -0
- package/lib/internal/SandboxHandle.js.map +1 -0
- package/lib/internal/SnapshotBuild.d.ts +19 -0
- package/lib/internal/SnapshotBuild.d.ts.map +1 -0
- package/lib/internal/SnapshotBuild.js +49 -0
- package/lib/internal/SnapshotBuild.js.map +1 -0
- package/lib/internal/Tar.d.ts +10 -0
- package/lib/internal/Tar.d.ts.map +1 -0
- package/lib/internal/Tar.js +61 -0
- package/lib/internal/Tar.js.map +1 -0
- package/lib/internal/TemplateBuild.d.ts +142 -0
- package/lib/internal/TemplateBuild.d.ts.map +1 -0
- package/lib/internal/TemplateBuild.js +220 -0
- package/lib/internal/TemplateBuild.js.map +1 -0
- package/lib/internal/TemplatePlan.d.ts +57 -0
- package/lib/internal/TemplatePlan.d.ts.map +1 -0
- package/lib/internal/TemplatePlan.js +185 -0
- package/lib/internal/TemplatePlan.js.map +1 -0
- package/lib/internal/VolumeHttp.d.ts +5 -0
- package/lib/internal/VolumeHttp.d.ts.map +1 -0
- package/lib/internal/VolumeHttp.js +46 -0
- package/lib/internal/VolumeHttp.js.map +1 -0
- package/package.json +62 -3
- package/src/ApiKey.ts +148 -0
- package/src/AuthProvider.ts +437 -0
- package/src/Bootstrap.ts +51 -0
- package/src/ConnectSandbox.ts +71 -0
- package/src/ConnectSandboxHttp.ts +38 -0
- package/src/Contributions.ts +9 -0
- package/src/CreateSandbox.ts +157 -0
- package/src/CreateSandboxHttp.ts +131 -0
- package/src/CreateSecret.ts +65 -0
- package/src/CreateSecretHttp.ts +94 -0
- package/src/CreateSnapshot.ts +64 -0
- package/src/CreateSnapshotHttp.ts +55 -0
- package/src/CreateTemplate.ts +135 -0
- package/src/CreateTemplateHttp.ts +152 -0
- package/src/CreateVolume.ts +65 -0
- package/src/CreateVolumeHttp.ts +80 -0
- package/src/Credentials.ts +81 -0
- package/src/E2BBindings.ts +160 -0
- package/src/Errors.ts +47 -0
- package/src/InferEnv.ts +27 -0
- package/src/MountVolume.ts +92 -0
- package/src/MountVolumeLive.ts +49 -0
- package/src/Providers.ts +48 -0
- package/src/ReadTeam.ts +55 -0
- package/src/ReadTeamHttp.ts +44 -0
- package/src/ReadVolume.ts +29 -0
- package/src/ReadVolumeHttp.ts +32 -0
- package/src/ReadWebhook.ts +41 -0
- package/src/ReadWebhookHttp.ts +42 -0
- package/src/ReadWriteVolume.ts +30 -0
- package/src/ReadWriteVolumeHttp.ts +32 -0
- package/src/Sandbox.ts +581 -0
- package/src/SandboxClient.ts +268 -0
- package/src/SandboxEvents.ts +101 -0
- package/src/SandboxEventsLive.ts +118 -0
- package/src/Secret.ts +229 -0
- package/src/Snapshot.ts +234 -0
- package/src/Template.ts +636 -0
- package/src/Volume.ts +173 -0
- package/src/VolumeFiles.ts +53 -0
- package/src/Webhook.ts +215 -0
- package/src/WriteSecret.ts +39 -0
- package/src/WriteSecretHttp.ts +43 -0
- package/src/WriteVolume.ts +29 -0
- package/src/WriteVolumeHttp.ts +32 -0
- package/src/globals.d.ts +2 -0
- package/src/index.ts +42 -0
- package/src/internal/Declared.ts +80 -0
- package/src/internal/Login.ts +210 -0
- package/src/internal/Ownership.ts +67 -0
- package/src/internal/Program.ts +75 -0
- package/src/internal/SandboxHandle.ts +534 -0
- package/src/internal/SnapshotBuild.ts +74 -0
- package/src/internal/Tar.ts +74 -0
- package/src/internal/TemplateBuild.ts +383 -0
- package/src/internal/TemplatePlan.ts +230 -0
- package/src/internal/VolumeHttp.ts +88 -0
package/src/Sandbox.ts
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
import { Credentials } from "@distilled.cloud/e2b/Credentials";
|
|
2
|
+
import * as sandboxes from "@distilled.cloud/e2b/sandboxes";
|
|
3
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
4
|
+
import { isResolved } from "alchemy/Diff";
|
|
5
|
+
import * as Provider from "alchemy/Provider";
|
|
6
|
+
import { Resource } from "alchemy/Resource";
|
|
7
|
+
import * as Duration from "effect/Duration";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import * as Redacted from "effect/Redacted";
|
|
10
|
+
import * as Stream from "effect/Stream";
|
|
11
|
+
import { E2BUnexpectedResponse } from "./Errors.ts";
|
|
12
|
+
import { declarable } from "./internal/Declared.ts";
|
|
13
|
+
import { hasOwnershipMetadata, ownershipMetadata, userMetadata } from "./internal/Ownership.ts";
|
|
14
|
+
import { denyUnlisted } from "./internal/SandboxHandle.ts";
|
|
15
|
+
import type { VolumeMount } from "./MountVolume.ts";
|
|
16
|
+
import type { Providers } from "./Providers.ts";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* A SOCKS5 proxy that carries a sandbox's outbound TCP. E2B applies the
|
|
20
|
+
* allow and deny rules first, then tunnels what's left through the proxy;
|
|
21
|
+
* code in the sandbox can't tell, and can't go around it. The proxy must
|
|
22
|
+
* accept TCP connections, so a Cloudflare Worker can't be one.
|
|
23
|
+
*/
|
|
24
|
+
export interface SandboxEgressProxy {
|
|
25
|
+
/** The proxy's address, as `host:port`. */
|
|
26
|
+
address: string;
|
|
27
|
+
/** SOCKS5 username. */
|
|
28
|
+
username?: string;
|
|
29
|
+
/** SOCKS5 password. E2B never returns it. */
|
|
30
|
+
password?: string | Redacted.Redacted<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Connect to the proxy over TLS, so the username and password aren't
|
|
33
|
+
* sent in the clear.
|
|
34
|
+
*/
|
|
35
|
+
tls?: {
|
|
36
|
+
/** Whether to use TLS. */
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
/** Name to check the proxy's certificate against. @default the host in `address` */
|
|
39
|
+
serverName?: string;
|
|
40
|
+
/** PEM certificates to trust instead of the system's, for a private CA. */
|
|
41
|
+
caCert?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Egress rules for a declared sandbox, in E2B's shape. */
|
|
46
|
+
export interface SandboxNetwork {
|
|
47
|
+
/**
|
|
48
|
+
* Whether the sandbox's URLs accept requests without its traffic token.
|
|
49
|
+
* Changing it replaces the sandbox.
|
|
50
|
+
* @default true
|
|
51
|
+
*/
|
|
52
|
+
allowPublicTraffic?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* The only destinations the sandbox may reach: CIDR blocks, IPs or
|
|
55
|
+
* domains (`*.example.com`). Everything else is denied unless `denyOut`
|
|
56
|
+
* says otherwise.
|
|
57
|
+
*/
|
|
58
|
+
allowOut?: string[];
|
|
59
|
+
/** Denied CIDR blocks or IPs. Domains are not supported. */
|
|
60
|
+
denyOut?: string[];
|
|
61
|
+
/** DNS servers the sandbox may reach when `allowOut` lists a domain. @default ["8.8.8.8"] */
|
|
62
|
+
dnsServers?: string[];
|
|
63
|
+
/**
|
|
64
|
+
* Header rules for outbound HTTPS requests, by domain. Reference an
|
|
65
|
+
* `E2B.Secret`'s `ref` here to inject it at egress.
|
|
66
|
+
*/
|
|
67
|
+
rules?: sandboxes.SandboxNetworkConfigRulesMap;
|
|
68
|
+
/**
|
|
69
|
+
* Send the sandbox's outbound TCP through a SOCKS5 proxy you run: a
|
|
70
|
+
* gateway that adds credentials, a static IP, or an audit log.
|
|
71
|
+
*/
|
|
72
|
+
egressProxy?: SandboxEgressProxy;
|
|
73
|
+
/**
|
|
74
|
+
* The `Host` header requests to the sandbox's URLs arrive with, such as
|
|
75
|
+
* `localhost:${PORT}`. Changing it replaces the sandbox.
|
|
76
|
+
*/
|
|
77
|
+
maskRequestHost?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Ports where the sandbox serves HTTPS itself. E2B connects to them over
|
|
80
|
+
* TLS without checking the certificate. Changing them replaces the
|
|
81
|
+
* sandbox.
|
|
82
|
+
*/
|
|
83
|
+
httpsPorts?: number[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* What a sandbox boots from: an `E2B.Template` or an `E2B.Snapshot`, declared
|
|
88
|
+
* or yielded.
|
|
89
|
+
*/
|
|
90
|
+
export interface SandboxTemplate {
|
|
91
|
+
templateId: string;
|
|
92
|
+
/** A template's current build. A new build replaces the sandbox. */
|
|
93
|
+
buildId?: string;
|
|
94
|
+
/** A snapshot's setup hash. A new snapshot replaces the sandbox. */
|
|
95
|
+
setupHash?: string;
|
|
96
|
+
/** The volumes a template program mounts (`E2B.MountVolume`). */
|
|
97
|
+
mounts?: VolumeMount[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface SandboxProps {
|
|
101
|
+
/**
|
|
102
|
+
* The `E2B.Template` or `E2B.Snapshot` the sandbox boots from. Changing
|
|
103
|
+
* it, or rebuilding it, replaces the sandbox so it boots the new build.
|
|
104
|
+
*/
|
|
105
|
+
template: SandboxTemplate;
|
|
106
|
+
/**
|
|
107
|
+
* How long the sandbox runs after it starts or resumes before E2B pauses
|
|
108
|
+
* it. Changing it applies to the current run (and resumes a paused
|
|
109
|
+
* sandbox once to apply it).
|
|
110
|
+
* @default "5 minutes"
|
|
111
|
+
*/
|
|
112
|
+
timeout?: Duration.Input;
|
|
113
|
+
/**
|
|
114
|
+
* Resume the paused sandbox on the next request: to one of its URLs, or
|
|
115
|
+
* a call into it from `E2B.ConnectSandbox`. Without it, calls into the
|
|
116
|
+
* paused sandbox fail with `SandboxNotRunning` until `resume()`. Needs
|
|
117
|
+
* `memory`. Changing it replaces the sandbox.
|
|
118
|
+
* @default true
|
|
119
|
+
*/
|
|
120
|
+
autoResume?: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Keep memory as well as the filesystem when the sandbox pauses, so it
|
|
123
|
+
* resumes where it left off. `false` keeps only the filesystem and the
|
|
124
|
+
* sandbox reboots on resume. Changing it replaces the sandbox.
|
|
125
|
+
* @default true
|
|
126
|
+
*/
|
|
127
|
+
memory?: boolean;
|
|
128
|
+
/** Environment variables for the sandbox. Changing them replaces the sandbox. */
|
|
129
|
+
env?: Record<string, string>;
|
|
130
|
+
/** Your own metadata. Changing it replaces the sandbox. */
|
|
131
|
+
metadata?: Record<string, string>;
|
|
132
|
+
/**
|
|
133
|
+
* Egress rules. `allowOut`, `denyOut`, `dnsServers`, `rules` and
|
|
134
|
+
* `egressProxy` update in place; `allowPublicTraffic`, `maskRequestHost`
|
|
135
|
+
* and `httpsPorts` replace the sandbox.
|
|
136
|
+
*/
|
|
137
|
+
network?: SandboxNetwork;
|
|
138
|
+
/**
|
|
139
|
+
* Volumes to mount: `{ volume, path }`, where `volume` is an `E2B.Volume`.
|
|
140
|
+
* A template program's own mounts are added. E2B attaches volumes only
|
|
141
|
+
* when a sandbox is created, so changing them replaces the sandbox.
|
|
142
|
+
*/
|
|
143
|
+
mounts?: VolumeMount[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface SandboxAttributes {
|
|
147
|
+
/** E2B's sandbox id. */
|
|
148
|
+
sandboxId: string;
|
|
149
|
+
/** The template id the sandbox booted from. */
|
|
150
|
+
templateId: string;
|
|
151
|
+
/** The template build (or snapshot) the sandbox booted. */
|
|
152
|
+
templateBuild: string | undefined;
|
|
153
|
+
/** The volumes mounted into the sandbox: its own and its template program's. */
|
|
154
|
+
mounts: VolumeMount[];
|
|
155
|
+
/**
|
|
156
|
+
* The domain its ports are served under: port `p` answers at
|
|
157
|
+
* `https://<p>-<sandboxId>.<domain>`.
|
|
158
|
+
*/
|
|
159
|
+
domain: string;
|
|
160
|
+
/**
|
|
161
|
+
* The token requests to the sandbox's URLs send in the
|
|
162
|
+
* `e2b-traffic-access-token` header when `allowPublicTraffic` is `false`.
|
|
163
|
+
*/
|
|
164
|
+
trafficAccessToken: Redacted.Redacted<string> | undefined;
|
|
165
|
+
/** Your metadata, without Alchemy's ownership stamp. */
|
|
166
|
+
metadata: Record<string, string>;
|
|
167
|
+
/** CPU cores. */
|
|
168
|
+
cpuCount: number;
|
|
169
|
+
/** Memory in MiB. */
|
|
170
|
+
memoryMB: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface Sandbox extends Resource<
|
|
174
|
+
"E2B.Sandbox",
|
|
175
|
+
SandboxProps,
|
|
176
|
+
SandboxAttributes,
|
|
177
|
+
never,
|
|
178
|
+
Providers,
|
|
179
|
+
{ sandboxId: string }
|
|
180
|
+
> {}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* An E2B sandbox the stack owns: one long-lived sandbox that pauses when it
|
|
184
|
+
* is idle and resumes when it is used, instead of one per request.
|
|
185
|
+
*
|
|
186
|
+
* Use it for a workspace that outlives requests (an agent's home, a dev
|
|
187
|
+
* box) or to serve a template program at a stable URL. The sandbox runs for
|
|
188
|
+
* `timeout` after it starts or resumes, then E2B pauses it with its memory
|
|
189
|
+
* and files. A request to one of its URLs resumes it (`autoResume`), and so
|
|
190
|
+
* does `resume()` on `E2B.ConnectSandbox`.
|
|
191
|
+
*
|
|
192
|
+
* Alchemy stamps its stack, stage and logical ID into the sandbox's
|
|
193
|
+
* metadata to recognise the sandboxes it owns.
|
|
194
|
+
*
|
|
195
|
+
* :::caution
|
|
196
|
+
* Replacing the sandbox (a new or rebuilt `template`, or new `env`,
|
|
197
|
+
* `metadata`, `mounts`, `memory`, `autoResume`, `allowPublicTraffic`,
|
|
198
|
+
* `maskRequestHost` or `httpsPorts`) starts a
|
|
199
|
+
* fresh sandbox and kills the old one with its files. If E2B no longer has
|
|
200
|
+
* the sandbox, the next deploy that updates it (or `alchemy drift`) creates
|
|
201
|
+
* a new one.
|
|
202
|
+
* :::
|
|
203
|
+
*
|
|
204
|
+
* ### Creating a Sandbox
|
|
205
|
+
* **Example:** A dev box from a template
|
|
206
|
+
* ```typescript
|
|
207
|
+
* import * as E2B from "@alchemy.run/e2b";
|
|
208
|
+
* import * as Effect from "effect/Effect";
|
|
209
|
+
*
|
|
210
|
+
* export const Node = E2B.Template("Node", { image: "node:22" });
|
|
211
|
+
*
|
|
212
|
+
* export const DevBox = E2B.Sandbox("DevBox", {
|
|
213
|
+
* template: Node,
|
|
214
|
+
* timeout: "10 minutes",
|
|
215
|
+
* });
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* ### Serving a template program
|
|
219
|
+
* A template program starts when its sandbox boots. Declare one sandbox for
|
|
220
|
+
* it: each new build of the program replaces the sandbox, so it always runs
|
|
221
|
+
* the current one.
|
|
222
|
+
*
|
|
223
|
+
* **Example:** The program at a stable URL
|
|
224
|
+
* ```typescript
|
|
225
|
+
* import * as E2B from "@alchemy.run/e2b";
|
|
226
|
+
* import * as Output from "alchemy/Output";
|
|
227
|
+
* import * as Effect from "effect/Effect";
|
|
228
|
+
* import Api from "./src/api.ts";
|
|
229
|
+
*
|
|
230
|
+
* export const ApiSandbox = Effect.gen(function* () {
|
|
231
|
+
* const sandbox = yield* E2B.Sandbox("Api", { template: Api });
|
|
232
|
+
* return { url: Output.interpolate`https://3000-${sandbox.sandboxId}.${sandbox.domain}` };
|
|
233
|
+
* });
|
|
234
|
+
* ```
|
|
235
|
+
*
|
|
236
|
+
* ### Private URLs
|
|
237
|
+
* **Example:** Require the traffic token
|
|
238
|
+
* ```typescript
|
|
239
|
+
* const sandbox = yield* E2B.Sandbox("Private", {
|
|
240
|
+
* template: Node,
|
|
241
|
+
* network: { allowPublicTraffic: false },
|
|
242
|
+
* });
|
|
243
|
+
* // send sandbox.trafficAccessToken in the `e2b-traffic-access-token` header
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* ### Egress through your own proxy
|
|
247
|
+
* E2B sends the sandbox's outbound TCP through a SOCKS5 proxy you run,
|
|
248
|
+
* after its allow and deny rules. Code in the sandbox can't tell, and
|
|
249
|
+
* can't go around it.
|
|
250
|
+
*
|
|
251
|
+
* **Example:** A proxy over TLS, with a password
|
|
252
|
+
* ```typescript
|
|
253
|
+
* import * as E2B from "@alchemy.run/e2b";
|
|
254
|
+
* import * as Config from "effect/Config";
|
|
255
|
+
* import { Node } from "./Node.ts";
|
|
256
|
+
*
|
|
257
|
+
* export const Proxied = E2B.Sandbox("Proxied", {
|
|
258
|
+
* template: Node,
|
|
259
|
+
* network: {
|
|
260
|
+
* egressProxy: {
|
|
261
|
+
* address: "proxy.example.com:1080",
|
|
262
|
+
* username: "sandbox",
|
|
263
|
+
* password: Config.Redacted("PROXY_PASSWORD"),
|
|
264
|
+
* tls: { enabled: true },
|
|
265
|
+
* },
|
|
266
|
+
* },
|
|
267
|
+
* });
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* ### Using it at runtime
|
|
271
|
+
* **Example:** Run a command in it from a Worker
|
|
272
|
+
* ```typescript
|
|
273
|
+
* const devBox = yield* E2B.ConnectSandbox(DevBox);
|
|
274
|
+
* // in fetch:
|
|
275
|
+
* yield* devBox.resume();
|
|
276
|
+
* const result = yield* devBox.exec("git pull && npm test", { cwd: "/home/user/repo" });
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* ### Mounting volumes
|
|
280
|
+
* **Example:** A shared cache volume
|
|
281
|
+
* ```typescript
|
|
282
|
+
* export const Cache = E2B.Volume("Cache", {});
|
|
283
|
+
*
|
|
284
|
+
* export const Builder = E2B.Sandbox("Builder", {
|
|
285
|
+
* template: Node,
|
|
286
|
+
* mounts: [{ volume: Cache, path: "/cache" }],
|
|
287
|
+
* });
|
|
288
|
+
* ```
|
|
289
|
+
*
|
|
290
|
+
* ### Referencing a sandbox the stack doesn't manage
|
|
291
|
+
* **Example:** Look one up by id
|
|
292
|
+
* ```typescript
|
|
293
|
+
* const shared = E2B.Sandbox.lookup({ sandboxId: "i1a2b3c4d5e6f7g8h9" });
|
|
294
|
+
* const box = yield* E2B.ConnectSandbox(shared);
|
|
295
|
+
* ```
|
|
296
|
+
*
|
|
297
|
+
* @resource
|
|
298
|
+
* @product Sandbox
|
|
299
|
+
*/
|
|
300
|
+
export const Sandbox = declarable<Sandbox>()(Resource<Sandbox>("E2B.Sandbox"));
|
|
301
|
+
|
|
302
|
+
const DEFAULT_TIMEOUT_SECONDS = 300;
|
|
303
|
+
|
|
304
|
+
const secondsOf = (timeout: Duration.Input | undefined) =>
|
|
305
|
+
timeout === undefined
|
|
306
|
+
? DEFAULT_TIMEOUT_SECONDS
|
|
307
|
+
: Math.max(1, Math.round(Duration.toMillis(Duration.fromInputUnsafe(timeout)) / 1000));
|
|
308
|
+
|
|
309
|
+
const redact = (token: string | Redacted.Redacted<string> | null | undefined) =>
|
|
310
|
+
token === undefined || token === null
|
|
311
|
+
? undefined
|
|
312
|
+
: Redacted.isRedacted(token)
|
|
313
|
+
? token
|
|
314
|
+
: Redacted.make(token);
|
|
315
|
+
|
|
316
|
+
/** The E2B domain the credentials address; sandboxes on it report no domain of their own. */
|
|
317
|
+
const configuredDomain = Effect.gen(function* () {
|
|
318
|
+
const credentials = yield* Credentials;
|
|
319
|
+
return (yield* credentials).domain;
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
/** A sandbox's details, or `undefined` when E2B no longer has it. */
|
|
323
|
+
const observeSandbox = (sandboxId: string) =>
|
|
324
|
+
sandboxes.getSandbox({ sandboxID: sandboxId }).pipe(
|
|
325
|
+
Effect.map((detail): sandboxes.SandboxDetail | undefined => detail),
|
|
326
|
+
Effect.catchTag("NotFound", () => Effect.undefined),
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
interface Stored {
|
|
330
|
+
readonly templateBuild: string | undefined;
|
|
331
|
+
readonly trafficAccessToken: Redacted.Redacted<string> | undefined;
|
|
332
|
+
readonly mounts: VolumeMount[];
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const toAttributes = (
|
|
336
|
+
detail: sandboxes.SandboxDetail,
|
|
337
|
+
stored: Stored,
|
|
338
|
+
fallbackDomain: string,
|
|
339
|
+
): SandboxAttributes => ({
|
|
340
|
+
sandboxId: detail.sandboxID,
|
|
341
|
+
templateId: detail.templateID,
|
|
342
|
+
templateBuild: stored.templateBuild,
|
|
343
|
+
mounts: stored.mounts,
|
|
344
|
+
domain: detail.domain ?? fallbackDomain,
|
|
345
|
+
trafficAccessToken: stored.trafficAccessToken,
|
|
346
|
+
metadata: userMetadata(detail.metadata as Record<string, string>),
|
|
347
|
+
cpuCount: detail.cpuCount,
|
|
348
|
+
memoryMB: detail.memoryMB,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
/** The network fields `updateSandboxNetwork` can change, as E2B stores them. */
|
|
352
|
+
const mutableNetwork = (network: SandboxNetwork | sandboxes.SandboxNetworkConfig | undefined) => {
|
|
353
|
+
const desired = network === undefined ? {} : denyUnlisted(network);
|
|
354
|
+
return {
|
|
355
|
+
allowOut: [...(desired.allowOut ?? [])].sort(),
|
|
356
|
+
denyOut: [...(desired.denyOut ?? [])].sort(),
|
|
357
|
+
dnsServers: desired.dnsServers === undefined ? undefined : [...desired.dnsServers].sort(),
|
|
358
|
+
rules: desired.rules ?? {},
|
|
359
|
+
egressProxy: proxyIdentity(desired.egressProxy),
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const plain = (value: string | Redacted.Redacted<string> | undefined) =>
|
|
364
|
+
Redacted.isRedacted(value) ? Redacted.value(value) : value;
|
|
365
|
+
|
|
366
|
+
/** An egress proxy without its password, which E2B never returns. */
|
|
367
|
+
const proxyIdentity = (
|
|
368
|
+
proxy: SandboxEgressProxy | sandboxes.SandboxEgressProxyConfig | null | undefined,
|
|
369
|
+
) =>
|
|
370
|
+
proxy === undefined || proxy === null
|
|
371
|
+
? undefined
|
|
372
|
+
: {
|
|
373
|
+
address: proxy.address,
|
|
374
|
+
username: proxy.username ?? undefined,
|
|
375
|
+
tls: proxy.tls?.enabled
|
|
376
|
+
? {
|
|
377
|
+
serverName: proxy.tls.serverName ?? undefined,
|
|
378
|
+
caCert: proxy.tls.caCert ?? undefined,
|
|
379
|
+
}
|
|
380
|
+
: undefined,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const sameJson = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b);
|
|
384
|
+
|
|
385
|
+
/** The template's mounts and the sandbox's own, by path (the sandbox's win). */
|
|
386
|
+
const mountsOf = (props: SandboxProps): VolumeMount[] =>
|
|
387
|
+
[
|
|
388
|
+
...new Map(
|
|
389
|
+
[...(props.template.mounts ?? []), ...(props.mounts ?? [])].map(
|
|
390
|
+
(mount) => [mount.path, { volume: { name: mount.volume.name }, path: mount.path }] as const,
|
|
391
|
+
),
|
|
392
|
+
).values(),
|
|
393
|
+
].sort((a, b) => a.path.localeCompare(b.path));
|
|
394
|
+
|
|
395
|
+
const templateBuildOf = (props: SandboxProps) => props.template.buildId ?? props.template.setupHash;
|
|
396
|
+
|
|
397
|
+
/** Props whose change means a new sandbox (E2B can't change them on a live one). */
|
|
398
|
+
const replacingProps = (props: SandboxProps) => ({
|
|
399
|
+
template: props.template.templateId,
|
|
400
|
+
build: templateBuildOf(props),
|
|
401
|
+
autoResume: props.autoResume ?? true,
|
|
402
|
+
memory: props.memory ?? true,
|
|
403
|
+
env: props.env ?? {},
|
|
404
|
+
metadata: props.metadata ?? {},
|
|
405
|
+
mounts: mountsOf(props),
|
|
406
|
+
allowPublicTraffic: props.network?.allowPublicTraffic ?? true,
|
|
407
|
+
maskRequestHost: props.network?.maskRequestHost,
|
|
408
|
+
httpsPorts: [...(props.network?.httpsPorts ?? [])].sort((a, b) => a - b),
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
export const SandboxProvider = () =>
|
|
412
|
+
Provider.effect(
|
|
413
|
+
Sandbox,
|
|
414
|
+
Effect.gen(function* () {
|
|
415
|
+
return {
|
|
416
|
+
stables: ["sandboxId", "templateId", "domain"],
|
|
417
|
+
diff: Effect.fn(function* ({ news, olds }) {
|
|
418
|
+
if (!isResolved(news) || olds === undefined) return undefined;
|
|
419
|
+
if (!sameJson(replacingProps(olds), replacingProps(news))) {
|
|
420
|
+
return { action: "replace" } as const;
|
|
421
|
+
}
|
|
422
|
+
return undefined;
|
|
423
|
+
}),
|
|
424
|
+
read: Effect.fn(function* ({ id, output }) {
|
|
425
|
+
if (output === undefined) return undefined;
|
|
426
|
+
const detail = yield* observeSandbox(output.sandboxId);
|
|
427
|
+
if (detail === undefined) return undefined;
|
|
428
|
+
const attrs = toAttributes(detail, output, yield* configuredDomain);
|
|
429
|
+
const ours = yield* hasOwnershipMetadata(id, detail.metadata as Record<string, string>);
|
|
430
|
+
return ours ? attrs : Unowned(attrs);
|
|
431
|
+
}),
|
|
432
|
+
lookup: Effect.fn(function* ({ sandboxId }) {
|
|
433
|
+
const detail = yield* observeSandbox(sandboxId);
|
|
434
|
+
if (detail === undefined) return undefined;
|
|
435
|
+
return toAttributes(
|
|
436
|
+
detail,
|
|
437
|
+
{ templateBuild: undefined, trafficAccessToken: undefined, mounts: [] },
|
|
438
|
+
yield* configuredDomain,
|
|
439
|
+
);
|
|
440
|
+
}),
|
|
441
|
+
reconcile: Effect.fn(function* ({ id, news, olds, output, session }) {
|
|
442
|
+
const timeout = secondsOf(news.timeout);
|
|
443
|
+
const memory = news.memory ?? true;
|
|
444
|
+
const autoResume = news.autoResume ?? true;
|
|
445
|
+
const mounts = mountsOf(news);
|
|
446
|
+
const templateBuild = templateBuildOf(news);
|
|
447
|
+
|
|
448
|
+
// Observe — `output` caches the id; the sandbox may be gone.
|
|
449
|
+
let detail = output === undefined ? undefined : yield* observeSandbox(output.sandboxId);
|
|
450
|
+
let trafficAccessToken = output?.trafficAccessToken;
|
|
451
|
+
|
|
452
|
+
// Ensure
|
|
453
|
+
if (detail === undefined) {
|
|
454
|
+
yield* session.note(`Creating sandbox from ${news.template.templateId}...`);
|
|
455
|
+
const created = yield* sandboxes.createSandbox({
|
|
456
|
+
templateID: news.template.templateId,
|
|
457
|
+
timeout,
|
|
458
|
+
autoPause: true,
|
|
459
|
+
autoPauseMemory: memory,
|
|
460
|
+
autoResume: { enabled: autoResume },
|
|
461
|
+
metadata: { ...news.metadata, ...(yield* ownershipMetadata(id)) },
|
|
462
|
+
...(news.env === undefined ? {} : { envVars: news.env }),
|
|
463
|
+
...(news.network === undefined ? {} : { network: denyUnlisted(news.network) }),
|
|
464
|
+
...(mounts.length === 0
|
|
465
|
+
? {}
|
|
466
|
+
: {
|
|
467
|
+
volumeMounts: mounts.map((mount) => ({
|
|
468
|
+
name: mount.volume.name,
|
|
469
|
+
path: mount.path,
|
|
470
|
+
})),
|
|
471
|
+
}),
|
|
472
|
+
});
|
|
473
|
+
trafficAccessToken = redact(created.trafficAccessToken);
|
|
474
|
+
detail = yield* observeSandbox(created.sandboxID);
|
|
475
|
+
if (detail === undefined) {
|
|
476
|
+
return yield* new E2BUnexpectedResponse({
|
|
477
|
+
message: `E2B sandbox ${created.sandboxID} vanished after create`,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
return toAttributes(
|
|
481
|
+
detail,
|
|
482
|
+
{ templateBuild, trafficAccessToken, mounts },
|
|
483
|
+
created.domain ?? (yield* configuredDomain),
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Sync — egress rules, observed against what E2B stores.
|
|
488
|
+
const desiredNetwork = mutableNetwork(news.network);
|
|
489
|
+
const observedNetwork = mutableNetwork(detail.network);
|
|
490
|
+
const dnsMatches =
|
|
491
|
+
desiredNetwork.dnsServers === undefined ||
|
|
492
|
+
sameJson(desiredNetwork.dnsServers, observedNetwork.dnsServers);
|
|
493
|
+
if (
|
|
494
|
+
!sameJson(desiredNetwork.allowOut, observedNetwork.allowOut) ||
|
|
495
|
+
!sameJson(desiredNetwork.denyOut, observedNetwork.denyOut) ||
|
|
496
|
+
!sameJson(desiredNetwork.rules, observedNetwork.rules) ||
|
|
497
|
+
!sameJson(desiredNetwork.egressProxy, observedNetwork.egressProxy) ||
|
|
498
|
+
// The proxy's password can't be observed, so `olds` is the only
|
|
499
|
+
// signal it changed.
|
|
500
|
+
(olds !== undefined &&
|
|
501
|
+
plain(olds.network?.egressProxy?.password) !==
|
|
502
|
+
plain(news.network?.egressProxy?.password)) ||
|
|
503
|
+
!dnsMatches
|
|
504
|
+
) {
|
|
505
|
+
yield* session.note(`Updating sandbox ${detail.sandboxID} network...`);
|
|
506
|
+
yield* sandboxes.updateSandboxNetwork({
|
|
507
|
+
sandboxID: detail.sandboxID,
|
|
508
|
+
allowOut: desiredNetwork.allowOut,
|
|
509
|
+
denyOut: desiredNetwork.denyOut,
|
|
510
|
+
rules: desiredNetwork.rules,
|
|
511
|
+
// An update replaces the whole configuration: a field left
|
|
512
|
+
// out is cleared.
|
|
513
|
+
...(news.network?.egressProxy === undefined
|
|
514
|
+
? {}
|
|
515
|
+
: { egressProxy: news.network.egressProxy }),
|
|
516
|
+
...(desiredNetwork.dnsServers === undefined
|
|
517
|
+
? {}
|
|
518
|
+
: { dnsServers: desiredNetwork.dnsServers }),
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Sync — the run's timeout. E2B doesn't report the configured
|
|
523
|
+
// timeout, so `olds` is the only signal it changed.
|
|
524
|
+
if (olds !== undefined && secondsOf(olds.timeout) !== timeout) {
|
|
525
|
+
if (detail.state === "paused") {
|
|
526
|
+
// Resuming takes the new timeout; the sandbox pauses again after it.
|
|
527
|
+
const resumed = yield* sandboxes.connectSandbox({
|
|
528
|
+
sandboxID: detail.sandboxID,
|
|
529
|
+
timeout,
|
|
530
|
+
});
|
|
531
|
+
trafficAccessToken = redact(resumed.trafficAccessToken) ?? trafficAccessToken;
|
|
532
|
+
} else {
|
|
533
|
+
yield* sandboxes.setSandboxTimeout({ sandboxID: detail.sandboxID, timeout });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return toAttributes(
|
|
538
|
+
detail,
|
|
539
|
+
{ templateBuild, trafficAccessToken, mounts },
|
|
540
|
+
yield* configuredDomain,
|
|
541
|
+
);
|
|
542
|
+
}),
|
|
543
|
+
delete: Effect.fn(function* ({ output }) {
|
|
544
|
+
yield* sandboxes
|
|
545
|
+
.deleteSandbox({ sandboxID: output.sandboxId })
|
|
546
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
547
|
+
}),
|
|
548
|
+
list: Effect.fn(function* () {
|
|
549
|
+
const domain = yield* configuredDomain;
|
|
550
|
+
const all = Array.from(
|
|
551
|
+
yield* Stream.runCollect(
|
|
552
|
+
sandboxes.listSandboxes.items({
|
|
553
|
+
state: ["running", "paused"],
|
|
554
|
+
limit: 100,
|
|
555
|
+
}),
|
|
556
|
+
),
|
|
557
|
+
);
|
|
558
|
+
return (
|
|
559
|
+
all
|
|
560
|
+
// Declared sandboxes and the ones bindings create at run time
|
|
561
|
+
// both carry `alchemy::stack`, so `nuke` finds both.
|
|
562
|
+
.filter((sandbox) => sandbox.metadata?.["alchemy::stack"] !== undefined)
|
|
563
|
+
.map((sandbox): SandboxAttributes => ({
|
|
564
|
+
sandboxId: sandbox.sandboxID,
|
|
565
|
+
templateId: sandbox.templateID,
|
|
566
|
+
templateBuild: undefined,
|
|
567
|
+
mounts: (sandbox.volumeMounts ?? []).map((mount) => ({
|
|
568
|
+
volume: { name: mount.name },
|
|
569
|
+
path: mount.path,
|
|
570
|
+
})),
|
|
571
|
+
domain,
|
|
572
|
+
trafficAccessToken: undefined,
|
|
573
|
+
metadata: userMetadata(sandbox.metadata as Record<string, string>),
|
|
574
|
+
cpuCount: sandbox.cpuCount,
|
|
575
|
+
memoryMB: sandbox.memoryMB,
|
|
576
|
+
}))
|
|
577
|
+
);
|
|
578
|
+
}),
|
|
579
|
+
};
|
|
580
|
+
}),
|
|
581
|
+
);
|