@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.
Files changed (284) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +56 -4
  3. package/lib/ApiKey.d.ts +53 -0
  4. package/lib/ApiKey.d.ts.map +1 -0
  5. package/lib/ApiKey.js +107 -0
  6. package/lib/ApiKey.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +60 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +293 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/Bootstrap.d.ts +7 -0
  12. package/lib/Bootstrap.d.ts.map +1 -0
  13. package/lib/Bootstrap.js +25 -0
  14. package/lib/Bootstrap.js.map +1 -0
  15. package/lib/ConnectSandbox.d.ts +67 -0
  16. package/lib/ConnectSandbox.d.ts.map +1 -0
  17. package/lib/ConnectSandbox.js +3 -0
  18. package/lib/ConnectSandbox.js.map +1 -0
  19. package/lib/ConnectSandboxHttp.d.ts +22 -0
  20. package/lib/ConnectSandboxHttp.d.ts.map +1 -0
  21. package/lib/ConnectSandboxHttp.js +30 -0
  22. package/lib/ConnectSandboxHttp.js.map +1 -0
  23. package/lib/Contributions.d.ts +7 -0
  24. package/lib/Contributions.d.ts.map +1 -0
  25. package/lib/Contributions.js +6 -0
  26. package/lib/Contributions.js.map +1 -0
  27. package/lib/CreateSandbox.d.ts +139 -0
  28. package/lib/CreateSandbox.d.ts.map +1 -0
  29. package/lib/CreateSandbox.js +3 -0
  30. package/lib/CreateSandbox.js.map +1 -0
  31. package/lib/CreateSandboxHttp.d.ts +22 -0
  32. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  33. package/lib/CreateSandboxHttp.js +99 -0
  34. package/lib/CreateSandboxHttp.js.map +1 -0
  35. package/lib/CreateSecret.d.ts +57 -0
  36. package/lib/CreateSecret.d.ts.map +1 -0
  37. package/lib/CreateSecret.js +3 -0
  38. package/lib/CreateSecret.js.map +1 -0
  39. package/lib/CreateSecretHttp.d.ts +20 -0
  40. package/lib/CreateSecretHttp.d.ts.map +1 -0
  41. package/lib/CreateSecretHttp.js +64 -0
  42. package/lib/CreateSecretHttp.js.map +1 -0
  43. package/lib/CreateSnapshot.d.ts +53 -0
  44. package/lib/CreateSnapshot.d.ts.map +1 -0
  45. package/lib/CreateSnapshot.js +3 -0
  46. package/lib/CreateSnapshot.js.map +1 -0
  47. package/lib/CreateSnapshotHttp.d.ts +20 -0
  48. package/lib/CreateSnapshotHttp.d.ts.map +1 -0
  49. package/lib/CreateSnapshotHttp.js +41 -0
  50. package/lib/CreateSnapshotHttp.js.map +1 -0
  51. package/lib/CreateTemplate.d.ts +96 -0
  52. package/lib/CreateTemplate.d.ts.map +1 -0
  53. package/lib/CreateTemplate.js +3 -0
  54. package/lib/CreateTemplate.js.map +1 -0
  55. package/lib/CreateTemplateHttp.d.ts +20 -0
  56. package/lib/CreateTemplateHttp.d.ts.map +1 -0
  57. package/lib/CreateTemplateHttp.js +99 -0
  58. package/lib/CreateTemplateHttp.js.map +1 -0
  59. package/lib/CreateVolume.d.ts +58 -0
  60. package/lib/CreateVolume.d.ts.map +1 -0
  61. package/lib/CreateVolume.js +3 -0
  62. package/lib/CreateVolume.js.map +1 -0
  63. package/lib/CreateVolumeHttp.d.ts +20 -0
  64. package/lib/CreateVolumeHttp.d.ts.map +1 -0
  65. package/lib/CreateVolumeHttp.js +51 -0
  66. package/lib/CreateVolumeHttp.js.map +1 -0
  67. package/lib/Credentials.d.ts +18 -0
  68. package/lib/Credentials.d.ts.map +1 -0
  69. package/lib/Credentials.js +55 -0
  70. package/lib/Credentials.js.map +1 -0
  71. package/lib/E2BBindings.d.ts +35 -0
  72. package/lib/E2BBindings.d.ts.map +1 -0
  73. package/lib/E2BBindings.js +127 -0
  74. package/lib/E2BBindings.js.map +1 -0
  75. package/lib/Errors.d.ts +61 -0
  76. package/lib/Errors.d.ts.map +1 -0
  77. package/lib/Errors.js +23 -0
  78. package/lib/Errors.js.map +1 -0
  79. package/lib/InferEnv.d.ts +26 -0
  80. package/lib/InferEnv.d.ts.map +1 -0
  81. package/lib/InferEnv.js +1 -0
  82. package/lib/InferEnv.js.map +1 -0
  83. package/lib/MountVolume.d.ts +86 -0
  84. package/lib/MountVolume.d.ts.map +1 -0
  85. package/lib/MountVolume.js +3 -0
  86. package/lib/MountVolume.js.map +1 -0
  87. package/lib/MountVolumeLive.d.ts +23 -0
  88. package/lib/MountVolumeLive.d.ts.map +1 -0
  89. package/lib/MountVolumeLive.js +41 -0
  90. package/lib/MountVolumeLive.js.map +1 -0
  91. package/lib/Providers.d.ts +14 -0
  92. package/lib/Providers.d.ts.map +1 -0
  93. package/lib/Providers.js +23 -0
  94. package/lib/Providers.js.map +1 -0
  95. package/lib/ReadTeam.d.ts +41 -0
  96. package/lib/ReadTeam.d.ts.map +1 -0
  97. package/lib/ReadTeam.js +3 -0
  98. package/lib/ReadTeam.js.map +1 -0
  99. package/lib/ReadTeamHttp.d.ts +20 -0
  100. package/lib/ReadTeamHttp.d.ts.map +1 -0
  101. package/lib/ReadTeamHttp.js +40 -0
  102. package/lib/ReadTeamHttp.js.map +1 -0
  103. package/lib/ReadVolume.d.ts +25 -0
  104. package/lib/ReadVolume.d.ts.map +1 -0
  105. package/lib/ReadVolume.js +3 -0
  106. package/lib/ReadVolume.js.map +1 -0
  107. package/lib/ReadVolumeHttp.d.ts +20 -0
  108. package/lib/ReadVolumeHttp.d.ts.map +1 -0
  109. package/lib/ReadVolumeHttp.js +27 -0
  110. package/lib/ReadVolumeHttp.js.map +1 -0
  111. package/lib/ReadWebhook.d.ts +32 -0
  112. package/lib/ReadWebhook.d.ts.map +1 -0
  113. package/lib/ReadWebhook.js +3 -0
  114. package/lib/ReadWebhook.js.map +1 -0
  115. package/lib/ReadWebhookHttp.d.ts +20 -0
  116. package/lib/ReadWebhookHttp.d.ts.map +1 -0
  117. package/lib/ReadWebhookHttp.js +31 -0
  118. package/lib/ReadWebhookHttp.js.map +1 -0
  119. package/lib/ReadWriteVolume.d.ts +26 -0
  120. package/lib/ReadWriteVolume.d.ts.map +1 -0
  121. package/lib/ReadWriteVolume.js +3 -0
  122. package/lib/ReadWriteVolume.js.map +1 -0
  123. package/lib/ReadWriteVolumeHttp.d.ts +20 -0
  124. package/lib/ReadWriteVolumeHttp.d.ts.map +1 -0
  125. package/lib/ReadWriteVolumeHttp.js +27 -0
  126. package/lib/ReadWriteVolumeHttp.js.map +1 -0
  127. package/lib/Sandbox.d.ts +295 -0
  128. package/lib/Sandbox.d.ts.map +1 -0
  129. package/lib/Sandbox.js +351 -0
  130. package/lib/Sandbox.js.map +1 -0
  131. package/lib/SandboxClient.d.ts +226 -0
  132. package/lib/SandboxClient.d.ts.map +1 -0
  133. package/lib/SandboxClient.js +1 -0
  134. package/lib/SandboxClient.js.map +1 -0
  135. package/lib/SandboxEvents.d.ts +87 -0
  136. package/lib/SandboxEvents.d.ts.map +1 -0
  137. package/lib/SandboxEvents.js +39 -0
  138. package/lib/SandboxEvents.js.map +1 -0
  139. package/lib/SandboxEventsLive.d.ts +13 -0
  140. package/lib/SandboxEventsLive.d.ts.map +1 -0
  141. package/lib/SandboxEventsLive.js +80 -0
  142. package/lib/SandboxEventsLive.js.map +1 -0
  143. package/lib/Secret.d.ts +105 -0
  144. package/lib/Secret.d.ts.map +1 -0
  145. package/lib/Secret.js +161 -0
  146. package/lib/Secret.js.map +1 -0
  147. package/lib/Snapshot.d.ts +118 -0
  148. package/lib/Snapshot.d.ts.map +1 -0
  149. package/lib/Snapshot.js +158 -0
  150. package/lib/Snapshot.js.map +1 -0
  151. package/lib/Template.d.ts +313 -0
  152. package/lib/Template.d.ts.map +1 -0
  153. package/lib/Template.js +421 -0
  154. package/lib/Template.js.map +1 -0
  155. package/lib/Volume.d.ts +88 -0
  156. package/lib/Volume.d.ts.map +1 -0
  157. package/lib/Volume.js +136 -0
  158. package/lib/Volume.js.map +1 -0
  159. package/lib/VolumeFiles.d.ts +39 -0
  160. package/lib/VolumeFiles.d.ts.map +1 -0
  161. package/lib/VolumeFiles.js +1 -0
  162. package/lib/VolumeFiles.js.map +1 -0
  163. package/lib/Webhook.d.ts +83 -0
  164. package/lib/Webhook.d.ts.map +1 -0
  165. package/lib/Webhook.js +138 -0
  166. package/lib/Webhook.js.map +1 -0
  167. package/lib/WriteSecret.d.ts +34 -0
  168. package/lib/WriteSecret.d.ts.map +1 -0
  169. package/lib/WriteSecret.js +3 -0
  170. package/lib/WriteSecret.js.map +1 -0
  171. package/lib/WriteSecretHttp.d.ts +20 -0
  172. package/lib/WriteSecretHttp.d.ts.map +1 -0
  173. package/lib/WriteSecretHttp.js +37 -0
  174. package/lib/WriteSecretHttp.js.map +1 -0
  175. package/lib/WriteVolume.d.ts +25 -0
  176. package/lib/WriteVolume.d.ts.map +1 -0
  177. package/lib/WriteVolume.js +3 -0
  178. package/lib/WriteVolume.js.map +1 -0
  179. package/lib/WriteVolumeHttp.d.ts +20 -0
  180. package/lib/WriteVolumeHttp.d.ts.map +1 -0
  181. package/lib/WriteVolumeHttp.js +27 -0
  182. package/lib/WriteVolumeHttp.js.map +1 -0
  183. package/lib/index.d.ts +43 -0
  184. package/lib/index.d.ts.map +1 -0
  185. package/lib/index.js +43 -0
  186. package/lib/index.js.map +1 -0
  187. package/lib/internal/Declared.d.ts +29 -0
  188. package/lib/internal/Declared.d.ts.map +1 -0
  189. package/lib/internal/Declared.js +31 -0
  190. package/lib/internal/Declared.js.map +1 -0
  191. package/lib/internal/Login.d.ts +45 -0
  192. package/lib/internal/Login.d.ts.map +1 -0
  193. package/lib/internal/Login.js +157 -0
  194. package/lib/internal/Login.js.map +1 -0
  195. package/lib/internal/Ownership.d.ts +29 -0
  196. package/lib/internal/Ownership.d.ts.map +1 -0
  197. package/lib/internal/Ownership.js +49 -0
  198. package/lib/internal/Ownership.js.map +1 -0
  199. package/lib/internal/Program.d.ts +31 -0
  200. package/lib/internal/Program.d.ts.map +1 -0
  201. package/lib/internal/Program.js +60 -0
  202. package/lib/internal/Program.js.map +1 -0
  203. package/lib/internal/SandboxHandle.d.ts +25 -0
  204. package/lib/internal/SandboxHandle.d.ts.map +1 -0
  205. package/lib/internal/SandboxHandle.js +316 -0
  206. package/lib/internal/SandboxHandle.js.map +1 -0
  207. package/lib/internal/SnapshotBuild.d.ts +19 -0
  208. package/lib/internal/SnapshotBuild.d.ts.map +1 -0
  209. package/lib/internal/SnapshotBuild.js +49 -0
  210. package/lib/internal/SnapshotBuild.js.map +1 -0
  211. package/lib/internal/Tar.d.ts +10 -0
  212. package/lib/internal/Tar.d.ts.map +1 -0
  213. package/lib/internal/Tar.js +61 -0
  214. package/lib/internal/Tar.js.map +1 -0
  215. package/lib/internal/TemplateBuild.d.ts +142 -0
  216. package/lib/internal/TemplateBuild.d.ts.map +1 -0
  217. package/lib/internal/TemplateBuild.js +220 -0
  218. package/lib/internal/TemplateBuild.js.map +1 -0
  219. package/lib/internal/TemplatePlan.d.ts +57 -0
  220. package/lib/internal/TemplatePlan.d.ts.map +1 -0
  221. package/lib/internal/TemplatePlan.js +185 -0
  222. package/lib/internal/TemplatePlan.js.map +1 -0
  223. package/lib/internal/VolumeHttp.d.ts +5 -0
  224. package/lib/internal/VolumeHttp.d.ts.map +1 -0
  225. package/lib/internal/VolumeHttp.js +46 -0
  226. package/lib/internal/VolumeHttp.js.map +1 -0
  227. package/package.json +62 -3
  228. package/src/ApiKey.ts +148 -0
  229. package/src/AuthProvider.ts +437 -0
  230. package/src/Bootstrap.ts +51 -0
  231. package/src/ConnectSandbox.ts +71 -0
  232. package/src/ConnectSandboxHttp.ts +38 -0
  233. package/src/Contributions.ts +9 -0
  234. package/src/CreateSandbox.ts +157 -0
  235. package/src/CreateSandboxHttp.ts +131 -0
  236. package/src/CreateSecret.ts +65 -0
  237. package/src/CreateSecretHttp.ts +94 -0
  238. package/src/CreateSnapshot.ts +64 -0
  239. package/src/CreateSnapshotHttp.ts +55 -0
  240. package/src/CreateTemplate.ts +135 -0
  241. package/src/CreateTemplateHttp.ts +152 -0
  242. package/src/CreateVolume.ts +65 -0
  243. package/src/CreateVolumeHttp.ts +80 -0
  244. package/src/Credentials.ts +81 -0
  245. package/src/E2BBindings.ts +160 -0
  246. package/src/Errors.ts +47 -0
  247. package/src/InferEnv.ts +27 -0
  248. package/src/MountVolume.ts +92 -0
  249. package/src/MountVolumeLive.ts +49 -0
  250. package/src/Providers.ts +48 -0
  251. package/src/ReadTeam.ts +55 -0
  252. package/src/ReadTeamHttp.ts +44 -0
  253. package/src/ReadVolume.ts +29 -0
  254. package/src/ReadVolumeHttp.ts +32 -0
  255. package/src/ReadWebhook.ts +41 -0
  256. package/src/ReadWebhookHttp.ts +42 -0
  257. package/src/ReadWriteVolume.ts +30 -0
  258. package/src/ReadWriteVolumeHttp.ts +32 -0
  259. package/src/Sandbox.ts +581 -0
  260. package/src/SandboxClient.ts +268 -0
  261. package/src/SandboxEvents.ts +101 -0
  262. package/src/SandboxEventsLive.ts +118 -0
  263. package/src/Secret.ts +229 -0
  264. package/src/Snapshot.ts +234 -0
  265. package/src/Template.ts +636 -0
  266. package/src/Volume.ts +173 -0
  267. package/src/VolumeFiles.ts +53 -0
  268. package/src/Webhook.ts +215 -0
  269. package/src/WriteSecret.ts +39 -0
  270. package/src/WriteSecretHttp.ts +43 -0
  271. package/src/WriteVolume.ts +29 -0
  272. package/src/WriteVolumeHttp.ts +32 -0
  273. package/src/globals.d.ts +2 -0
  274. package/src/index.ts +42 -0
  275. package/src/internal/Declared.ts +80 -0
  276. package/src/internal/Login.ts +210 -0
  277. package/src/internal/Ownership.ts +67 -0
  278. package/src/internal/Program.ts +75 -0
  279. package/src/internal/SandboxHandle.ts +534 -0
  280. package/src/internal/SnapshotBuild.ts +74 -0
  281. package/src/internal/Tar.ts +74 -0
  282. package/src/internal/TemplateBuild.ts +383 -0
  283. package/src/internal/TemplatePlan.ts +230 -0
  284. 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
+ );