@alchemy.run/e2b 0.0.0 → 2.0.0-beta.87

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
@@ -0,0 +1,157 @@
1
+ import type * as sandboxes from "@distilled.cloud/e2b/sandboxes";
2
+ import * as Binding from "alchemy/Binding";
3
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
4
+ import type * as Effect from "effect/Effect";
5
+ import type { BoundVolumeMount, VolumeMount } from "./MountVolume.ts";
6
+ import type { E2BError, E2BSandbox } from "./SandboxClient.ts";
7
+ import type { Snapshot } from "./Snapshot.ts";
8
+ import type { Template } from "./Template.ts";
9
+
10
+ /** A template or snapshot sandboxes are created from. */
11
+ export type SandboxBase = Template | Snapshot;
12
+
13
+ /**
14
+ * E2B's `createSandbox` parameters. `templateID` defaults to the bound base;
15
+ * `metadata` is merged with the host's ownership stamp; `mounts` replaces
16
+ * E2B's `volumeMounts`.
17
+ */
18
+ export type CreateSandboxOptions = Omit<
19
+ sandboxes.CreateSandboxRequest,
20
+ "templateID" | "volumeMounts"
21
+ > & {
22
+ readonly templateID?: string;
23
+ /**
24
+ * Volumes to mount: a volume `CreateVolume` returned with a `path`, or a
25
+ * reference from `E2B.MountVolume`. A bound template's own mounts are
26
+ * added.
27
+ */
28
+ readonly mounts?: ReadonlyArray<VolumeMount | BoundVolumeMount>;
29
+ };
30
+
31
+ /** E2B's `listSandboxes` filters, with `metadata` as a record instead of a query string. */
32
+ export type ListSandboxesOptions = Omit<
33
+ sandboxes.ListSandboxesRequest,
34
+ "metadata" | "nextToken"
35
+ > & {
36
+ readonly metadata?: Readonly<Record<string, string>>;
37
+ };
38
+
39
+ /** E2B sandboxes: create, look up, list and kill them. */
40
+ export interface SandboxesClient {
41
+ /** Creates a sandbox from the bound base (or `templateID`). */
42
+ create(options?: CreateSandboxOptions): Effect.Effect<E2BSandbox, E2BError, RuntimeContext>;
43
+ /**
44
+ * A handle to an existing sandbox. A paused sandbox is resumed; `timeout`
45
+ * (seconds) resets its lifetime.
46
+ */
47
+ connect(
48
+ sandboxId: string,
49
+ options?: { readonly timeout?: number; readonly memory?: boolean },
50
+ ): Effect.Effect<E2BSandbox, E2BError, RuntimeContext>;
51
+ /** A sandbox's details, without resuming it. */
52
+ get(sandboxId: string): Effect.Effect<sandboxes.SandboxDetail, E2BError, RuntimeContext>;
53
+ /** Sandboxes, filtered by metadata, state or template (the bound base by default). */
54
+ list(
55
+ options?: ListSandboxesOptions,
56
+ ): Effect.Effect<ReadonlyArray<sandboxes.ListedSandbox>, E2BError, RuntimeContext>;
57
+ /** Kills a sandbox. Killing one that is gone succeeds. */
58
+ kill(sandboxId: string): Effect.Effect<void, E2BError, RuntimeContext>;
59
+ /** The latest CPU, memory and disk metrics of several sandboxes at once, by id. */
60
+ metrics(
61
+ sandboxIds: ReadonlyArray<string>,
62
+ ): Effect.Effect<sandboxes.SandboxesWithMetrics, E2BError, RuntimeContext>;
63
+ }
64
+
65
+ /**
66
+ * Create and manage E2B sandboxes from a Worker, Function or any other host.
67
+ *
68
+ * `E2B.CreateSandbox(base)` creates sandboxes from a template or snapshot
69
+ * declared in the stack; `E2B.CreateSandbox()` takes the template per call.
70
+ * Both return the same client, whose calls take E2B's own parameters.
71
+ *
72
+ * Sandboxes created at run time are tagged with the stack, stage and
73
+ * binding (`alchemy::stack`, `alchemy::stage`, `alchemy::id` in their E2B
74
+ * metadata) and are not deleted when the stack is destroyed. List them by
75
+ * those tags to clean them up:
76
+ * `sandboxes.list({ metadata: { "alchemy::stack": "my-app", "alchemy::stage": "prod" } })`.
77
+ * Metadata you pass to `create` merges underneath; it never replaces the
78
+ * `alchemy::*` keys.
79
+ *
80
+ * The host gets its own E2B API key, minted at deploy and revoked when the
81
+ * host is destroyed. E2B keys are not scoped, so the key reaches the whole
82
+ * team; it is never the deployer's own.
83
+ *
84
+ * ### Creating sandboxes
85
+ * **Example:** One sandbox per request, killed when the request ends
86
+ * ```typescript
87
+ * const sandboxes = yield* E2B.CreateSandbox(Agent);
88
+ * // in fetch:
89
+ * const sandbox = yield* Effect.acquireRelease(
90
+ * sandboxes.create({ timeout: 120 }),
91
+ * (sandbox) => Effect.ignore(sandbox.kill()),
92
+ * );
93
+ * const result = yield* sandbox.exec("node --version");
94
+ * ```
95
+ *
96
+ * **Example:** A long-lived sandbox per task, found by metadata
97
+ * ```typescript
98
+ * const [existing] = yield* sandboxes.list({ metadata: { task }, state: ["running", "paused"] });
99
+ * const sandbox = existing
100
+ * ? yield* sandboxes.connect(existing.sandboxID)
101
+ * : yield* sandboxes.create({ metadata: { task }, autoPause: true, timeout: 1800 });
102
+ * ```
103
+ *
104
+ * ### Running commands
105
+ * `exec` runs to completion and returns `{ exitCode, stdout, stderr }`; a
106
+ * non-zero exit is returned, not failed. A string runs in a login shell, an
107
+ * array runs directly.
108
+ *
109
+ * **Example:** A shell line, an argument vector, stdin and a timeout
110
+ * ```typescript
111
+ * const test = yield* sandbox.exec("npm test", { cwd: "/home/user/app" });
112
+ * const version = yield* sandbox.exec(["node", "--version"]);
113
+ * const sorted = yield* sandbox.exec("sort", { stdin: "b\na\n" });
114
+ * const slow = yield* sandbox.exec("make", { timeout: "2 minutes" }); // E2B.ExecTimedOut
115
+ * ```
116
+ *
117
+ * **Example:** Stream a process, then wait for it
118
+ * ```typescript
119
+ * const build = yield* sandbox.start("npm run build", { cwd: "/home/user/app" });
120
+ * yield* build.output.pipe(
121
+ * Stream.runForEach((chunk) => Effect.log(new TextDecoder().decode(chunk.data))),
122
+ * );
123
+ * const { exitCode } = yield* build.wait;
124
+ * ```
125
+ *
126
+ * Closing the scope detaches: the process keeps running, and
127
+ * `sandbox.processes.attach({ pid })` picks it up again.
128
+ *
129
+ * **Example:** A terminal
130
+ * ```typescript
131
+ * const pty = yield* sandbox.pty.open({ cols: 120, rows: 40 });
132
+ * yield* pty.write("ls\n");
133
+ * yield* pty.resize({ cols: 160, rows: 48 });
134
+ * ```
135
+ *
136
+ * ### Network and secrets
137
+ * **Example:** Allow only GitHub and inject a token at egress
138
+ * ```typescript
139
+ * const github = yield* GitHubToken;
140
+ * const sandbox = yield* sandboxes.create({
141
+ * network: {
142
+ * allowOut: ["github.com", "*.github.com"],
143
+ * rules: { "api.github.com": [{ transform: { headers: { Authorization: `Bearer ${github.ref}` } } }] },
144
+ * },
145
+ * });
146
+ * ```
147
+ *
148
+ * @binding
149
+ * @product Sandbox
150
+ */
151
+ export interface CreateSandbox extends Binding.Service<
152
+ CreateSandbox,
153
+ "E2B.CreateSandbox",
154
+ (base?: SandboxBase) => Effect.Effect<SandboxesClient>
155
+ > {}
156
+
157
+ export const CreateSandbox = Binding.Service<CreateSandbox>("E2B.CreateSandbox");
@@ -0,0 +1,131 @@
1
+ import * as sandboxes from "@distilled.cloud/e2b/sandboxes";
2
+ import type * as Output from "alchemy/Output";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Stream from "effect/Stream";
6
+ import { CreateSandbox, type SandboxBase, type SandboxesClient } from "./CreateSandbox.ts";
7
+ import { bindE2B, bindOwnerTags, metadataQuery } from "./E2BBindings.ts";
8
+ import { SandboxBaseMissing } from "./Errors.ts";
9
+ import { denyUnlisted, makeSandboxHandle } from "./internal/SandboxHandle.ts";
10
+ import type { BoundVolumeMount, VolumeMount } from "./MountVolume.ts";
11
+ import type { Template } from "./Template.ts";
12
+
13
+ /**
14
+ * HTTP implementation of {@link CreateSandbox}: E2B's control plane with the
15
+ * team key, and envd inside each sandbox with that sandbox's own token.
16
+ * Works on any host.
17
+ *
18
+ * ### Provide the layer
19
+ * **Example:** On a Cloudflare Worker
20
+ * ```typescript
21
+ * Effect.gen(function* () {
22
+ * const sandboxes = yield* E2B.CreateSandbox(Agent);
23
+ * // ...
24
+ * }).pipe(Effect.provide(E2B.CreateSandboxHttp))
25
+ * ```
26
+ *
27
+ * @layer
28
+ * @product Sandbox
29
+ * @provides E2B.CreateSandbox
30
+ */
31
+ export const CreateSandboxHttp = Layer.effect(
32
+ CreateSandbox,
33
+ Effect.gen(function* () {
34
+ return Effect.fn("E2B.CreateSandbox")(function* (base?: SandboxBase) {
35
+ const auth = yield* bindE2B;
36
+ const owner = yield* bindOwnerTags;
37
+ // Both a Template and a Snapshot expose the template id sandboxes boot from.
38
+ const baseId: Effect.Effect<string> | undefined =
39
+ base === undefined
40
+ ? undefined
41
+ : yield* (base.templateId as Output.Output<string, never>).asEffect();
42
+ // A template program's own mounts (`E2B.MountVolume`) go on every sandbox.
43
+ const baseMounts: Effect.Effect<ReadonlyArray<VolumeMount>> =
44
+ base !== undefined && base.Type === "E2B.Template"
45
+ ? yield* ((base as Template).mounts as Output.Output<VolumeMount[], never>).asEffect()
46
+ : Effect.succeed([]);
47
+
48
+ /** The template's mounts and the call's own, by path (the call's win). */
49
+ const mountsOf = Effect.fn(function* (
50
+ mounts: ReadonlyArray<VolumeMount | BoundVolumeMount> | undefined,
51
+ ) {
52
+ const byPath = new Map<string, { name: string; path: string }>();
53
+ for (const mount of yield* baseMounts) {
54
+ byPath.set(mount.path, { name: mount.volume.name, path: mount.path });
55
+ }
56
+ for (const mount of mounts ?? []) {
57
+ const name =
58
+ typeof mount.volume.name === "string" ? mount.volume.name : yield* mount.volume.name;
59
+ byPath.set(mount.path, { name, path: mount.path });
60
+ }
61
+ return [...byPath.values()];
62
+ });
63
+
64
+ const withStamp = Effect.fn(function* (
65
+ metadata: Readonly<Record<string, string | undefined>> | undefined,
66
+ ) {
67
+ return { ...metadata, ...(yield* owner) };
68
+ });
69
+
70
+ const client: SandboxesClient = {
71
+ create: Effect.fn("E2B.CreateSandbox.create")(function* (createOptions) {
72
+ const templateID =
73
+ createOptions?.templateID ?? (baseId === undefined ? undefined : yield* baseId);
74
+ if (templateID === undefined) {
75
+ return yield* new SandboxBaseMissing({
76
+ message: "E2B.CreateSandbox() has no bound base: pass `templateID` to create()",
77
+ });
78
+ }
79
+ const domain = yield* auth.domain;
80
+ const { mounts, ...request } = createOptions ?? {};
81
+ const volumeMounts = yield* mountsOf(mounts);
82
+ const created = yield* auth.authorize(
83
+ sandboxes.createSandbox({
84
+ ...request,
85
+ ...(volumeMounts.length === 0 ? {} : { volumeMounts }),
86
+ ...(createOptions?.network === undefined
87
+ ? {}
88
+ : { network: denyUnlisted(createOptions.network) }),
89
+ templateID,
90
+ metadata: yield* withStamp(createOptions?.metadata),
91
+ }),
92
+ );
93
+ return makeSandboxHandle(auth, created, domain);
94
+ }),
95
+ connect: Effect.fn("E2B.CreateSandbox.connect")(function* (sandboxId, connectOptions) {
96
+ const domain = yield* auth.domain;
97
+ const connected = yield* auth.authorize(
98
+ sandboxes.connectSandbox({ sandboxID: sandboxId, ...connectOptions }),
99
+ );
100
+ return makeSandboxHandle(auth, connected, domain);
101
+ }),
102
+ get: (sandboxId) => auth.authorize(sandboxes.getSandbox({ sandboxID: sandboxId })),
103
+ list: Effect.fn("E2B.CreateSandbox.list")(function* (listOptions) {
104
+ const template =
105
+ listOptions?.template ?? (baseId === undefined ? undefined : yield* baseId);
106
+ const all = yield* auth.authorize(
107
+ Stream.runCollect(
108
+ sandboxes.listSandboxes.items({
109
+ ...listOptions,
110
+ ...(template === undefined ? {} : { template }),
111
+ metadata:
112
+ listOptions?.metadata === undefined
113
+ ? undefined
114
+ : metadataQuery(listOptions.metadata),
115
+ }),
116
+ ),
117
+ );
118
+ return Array.from(all);
119
+ }),
120
+ kill: (sandboxId) =>
121
+ auth.authorize(sandboxes.deleteSandbox({ sandboxID: sandboxId })).pipe(
122
+ Effect.asVoid,
123
+ Effect.catchTag("NotFound", () => Effect.void),
124
+ ),
125
+ metrics: (sandboxIds) =>
126
+ auth.authorize(sandboxes.listSandboxesMetrics({ sandbox_ids: [...sandboxIds] })),
127
+ };
128
+ return client;
129
+ });
130
+ }),
131
+ );
@@ -0,0 +1,65 @@
1
+ import * as Binding from "alchemy/Binding";
2
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
3
+ import type * as Effect from "effect/Effect";
4
+ import type * as Redacted from "effect/Redacted";
5
+ import type { E2BError } from "./SandboxClient.ts";
6
+ import type { SecretAttributes } from "./Secret.ts";
7
+
8
+ /** A secret as E2B reports it: never its value. `ref` is the egress placeholder. */
9
+ export type SecretInfo = SecretAttributes;
10
+
11
+ /** E2B secrets by name: store, look up, list and delete them. */
12
+ export interface SecretsClient {
13
+ /**
14
+ * Stores a value under a name: creates the secret, or adds a new version
15
+ * of an existing one. Returns its `ref` to use in sandbox network rules.
16
+ */
17
+ put(
18
+ name: string,
19
+ value: Redacted.Redacted<string>,
20
+ options?: { readonly metadata?: Readonly<Record<string, string>> },
21
+ ): Effect.Effect<SecretInfo, E2BError, RuntimeContext>;
22
+ /** The secret with this name or id, or `undefined`. */
23
+ get(nameOrId: string): Effect.Effect<SecretInfo | undefined, E2BError, RuntimeContext>;
24
+ /** The secrets this host created. */
25
+ list(): Effect.Effect<ReadonlyArray<SecretInfo>, E2BError, RuntimeContext>;
26
+ /** Deletes a secret. Deleting one that is gone succeeds. */
27
+ delete(nameOrId: string): Effect.Effect<void, E2BError, RuntimeContext>;
28
+ }
29
+
30
+ /**
31
+ * Store per-user credentials as E2B secrets at runtime, and reference them
32
+ * from a sandbox's network rules so the sandbox never sees the value.
33
+ *
34
+ * Secrets created at run time are tagged with the stack, stage and binding
35
+ * (`alchemy::stack`, `alchemy::stage`, `alchemy::id` in their E2B metadata)
36
+ * and are not deleted when the stack is destroyed. List them by those tags
37
+ * to clean them up: `secrets.list()` returns the ones this binding stored.
38
+ * Metadata you pass to `put` merges underneath; it never replaces the
39
+ * `alchemy::*` keys.
40
+ *
41
+ * ### Per-user credentials at egress
42
+ * **Example:** A user's GitHub token, injected into their sandbox's GitHub calls
43
+ * ```typescript
44
+ * const secrets = yield* E2B.CreateSecret();
45
+ * const sandboxes = yield* E2B.CreateSandbox(Agent);
46
+ * // in fetch:
47
+ * const github = yield* secrets.put(`github-${user}`, Redacted.make(token));
48
+ * const sandbox = yield* sandboxes.create({
49
+ * network: {
50
+ * allowOut: ["api.github.com"],
51
+ * rules: { "api.github.com": [{ transform: { headers: { Authorization: `Bearer ${github.ref}` } } }] },
52
+ * },
53
+ * });
54
+ * ```
55
+ *
56
+ * @binding
57
+ * @product Secret
58
+ */
59
+ export interface CreateSecret extends Binding.Service<
60
+ CreateSecret,
61
+ "E2B.CreateSecret",
62
+ () => Effect.Effect<SecretsClient>
63
+ > {}
64
+
65
+ export const CreateSecret = Binding.Service<CreateSecret>("E2B.CreateSecret");
@@ -0,0 +1,94 @@
1
+ import * as secrets from "@distilled.cloud/e2b/secrets";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Stream from "effect/Stream";
5
+ import { CreateSecret, type SecretsClient } from "./CreateSecret.ts";
6
+ import { bindE2B, bindOwnerTags } from "./E2BBindings.ts";
7
+ import { E2BUnexpectedResponse } from "./Errors.ts";
8
+ import { toSecretAttributes } from "./Secret.ts";
9
+
10
+ /**
11
+ * HTTP implementation of {@link CreateSecret}. Works on any host.
12
+ *
13
+ * ### Provide the layer
14
+ * **Example:** On a Worker
15
+ * ```typescript
16
+ * Effect.gen(function* () {
17
+ * const secrets = yield* E2B.CreateSecret();
18
+ * // ...
19
+ * }).pipe(Effect.provide(E2B.CreateSecretHttp))
20
+ * ```
21
+ *
22
+ * @layer
23
+ * @product Secret
24
+ * @provides E2B.CreateSecret
25
+ */
26
+ export const CreateSecretHttp = Layer.effect(
27
+ CreateSecret,
28
+ Effect.gen(function* () {
29
+ return Effect.fn("E2B.CreateSecret")(function* () {
30
+ const auth = yield* bindE2B;
31
+ const owner = yield* bindOwnerTags;
32
+
33
+ const get = (nameOrId: string) =>
34
+ auth.authorize(secrets.getSecret({ secretID: nameOrId.toLowerCase() })).pipe(
35
+ Effect.map((secret): secrets.Secret | undefined => secret),
36
+ Effect.catchTag("NotFound", () => Effect.undefined),
37
+ );
38
+
39
+ const client: SecretsClient = {
40
+ put: Effect.fn("E2B.CreateSecret.put")(function* (name, value, putOptions) {
41
+ const metadata = { ...putOptions?.metadata, ...(yield* owner) };
42
+ const existing = yield* get(name);
43
+ const stored =
44
+ existing !== undefined
45
+ ? yield* auth.authorize(
46
+ secrets.updateSecret({ secretID: existing.secretID, value, metadata }),
47
+ )
48
+ : yield* auth.authorize(secrets.createSecret({ name, value, metadata })).pipe(
49
+ // A concurrent put created it first: add our version.
50
+ Effect.catchTag("Conflict", () =>
51
+ Effect.gen(function* () {
52
+ const raced = yield* get(name);
53
+ if (raced === undefined) {
54
+ return yield* new E2BUnexpectedResponse({
55
+ message: `E2B secret ${name} vanished during put`,
56
+ });
57
+ }
58
+ return yield* auth.authorize(
59
+ secrets.updateSecret({ secretID: raced.secretID, value, metadata }),
60
+ );
61
+ }),
62
+ ),
63
+ );
64
+ return toSecretAttributes(stored);
65
+ }),
66
+ get: (nameOrId) =>
67
+ get(nameOrId).pipe(
68
+ Effect.map((secret) => (secret === undefined ? undefined : toSecretAttributes(secret))),
69
+ ),
70
+ list: Effect.fn("E2B.CreateSecret.list")(function* () {
71
+ const owned = yield* owner;
72
+ const all = yield* auth.authorize(
73
+ Stream.runCollect(secrets.listSecrets.items({ limit: 100 })),
74
+ );
75
+ return Array.from(all)
76
+ .filter((secret) =>
77
+ Object.entries(owned).every(([key, value]) => secret.metadata[key] === value),
78
+ )
79
+ .map(toSecretAttributes);
80
+ }),
81
+ delete: (nameOrId) =>
82
+ Effect.flatMap(get(nameOrId), (secret) =>
83
+ secret === undefined
84
+ ? Effect.void
85
+ : auth.authorize(secrets.deleteSecret({ secretID: secret.secretID })).pipe(
86
+ Effect.asVoid,
87
+ Effect.catchTag("NotFound", () => Effect.void),
88
+ ),
89
+ ),
90
+ };
91
+ return client;
92
+ });
93
+ }),
94
+ );
@@ -0,0 +1,64 @@
1
+ import type * as sandboxes from "@distilled.cloud/e2b/sandboxes";
2
+ import type * as snapshots from "@distilled.cloud/e2b/snapshots";
3
+ import * as Binding from "alchemy/Binding";
4
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
5
+ import type * as Effect from "effect/Effect";
6
+ import type { SnapshotSetupFailed } from "./Errors.ts";
7
+ import type { SnapshotSetup } from "./internal/SnapshotBuild.ts";
8
+ import type { E2BError } from "./SandboxClient.ts";
9
+
10
+ export type { SnapshotSetup } from "./internal/SnapshotBuild.ts";
11
+
12
+ /** E2B snapshots: take, list and delete them at runtime. */
13
+ export interface SnapshotsClient {
14
+ /**
15
+ * Boots `template`, runs the setup commands, snapshots it under `name` and
16
+ * kills the setup sandbox. Snapshotting under an existing name adds a new
17
+ * version. Use `sandbox.snapshot()` to snapshot a sandbox you already have.
18
+ */
19
+ create(
20
+ name: string,
21
+ setup: SnapshotSetup,
22
+ ): Effect.Effect<sandboxes.SnapshotInfo, E2BError | SnapshotSetupFailed, RuntimeContext>;
23
+ /** Snapshots, filtered by source sandbox or name. */
24
+ list(
25
+ options?: Omit<snapshots.ListSnapshotsRequest, "nextToken">,
26
+ ): Effect.Effect<ReadonlyArray<snapshots.SnapshotInfo>, E2BError, RuntimeContext>;
27
+ /** Deletes a snapshot (and every version of it). Deleting one that is gone succeeds. */
28
+ delete(name: string): Effect.Effect<void, E2BError, RuntimeContext>;
29
+ }
30
+
31
+ /**
32
+ * Take and manage E2B snapshots at runtime: a warm base per repository or
33
+ * per customer, created on demand and forked into sandboxes.
34
+ *
35
+ * Snapshots taken at runtime carry no metadata, so they are not deleted when
36
+ * the host is destroyed. Delete them with `delete` when their owner goes
37
+ * away.
38
+ *
39
+ * ### Warm bases at runtime
40
+ * **Example:** A warm base per repository
41
+ * ```typescript
42
+ * const snapshots = yield* E2B.CreateSnapshot();
43
+ * const sandboxes = yield* E2B.CreateSandbox();
44
+ * // in fetch:
45
+ * const [existing] = yield* snapshots.list({ name: `warm-${slug}` });
46
+ * const base =
47
+ * existing ??
48
+ * (yield* snapshots.create(`warm-${slug}`, {
49
+ * template: "node-22",
50
+ * run: [`git clone https://github.com/${repo} repo`, "cd repo && npm ci"],
51
+ * }));
52
+ * const sandbox = yield* sandboxes.create({ templateID: base.snapshotID });
53
+ * ```
54
+ *
55
+ * @binding
56
+ * @product Snapshot
57
+ */
58
+ export interface CreateSnapshot extends Binding.Service<
59
+ CreateSnapshot,
60
+ "E2B.CreateSnapshot",
61
+ () => Effect.Effect<SnapshotsClient>
62
+ > {}
63
+
64
+ export const CreateSnapshot = Binding.Service<CreateSnapshot>("E2B.CreateSnapshot");
@@ -0,0 +1,55 @@
1
+ import * as snapshots from "@distilled.cloud/e2b/snapshots";
2
+ import * as templates from "@distilled.cloud/e2b/templates";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Stream from "effect/Stream";
6
+ import { CreateSnapshot, type SnapshotsClient } from "./CreateSnapshot.ts";
7
+ import { bindE2B } from "./E2BBindings.ts";
8
+ import { takeSnapshot } from "./internal/SnapshotBuild.ts";
9
+
10
+ /**
11
+ * HTTP implementation of {@link CreateSnapshot}. Works on any host.
12
+ *
13
+ * ### Provide the layer
14
+ * **Example:** On a Worker
15
+ * ```typescript
16
+ * Effect.gen(function* () {
17
+ * const snapshots = yield* E2B.CreateSnapshot();
18
+ * // ...
19
+ * }).pipe(Effect.provide(E2B.CreateSnapshotHttp))
20
+ * ```
21
+ *
22
+ * @layer
23
+ * @product Snapshot
24
+ * @provides E2B.CreateSnapshot
25
+ */
26
+ export const CreateSnapshotHttp = Layer.succeed(
27
+ CreateSnapshot,
28
+ Effect.fn("E2B.CreateSnapshot")(function* () {
29
+ const auth = yield* bindE2B;
30
+ const client: SnapshotsClient = {
31
+ create: (name, setup) => auth.authorize(takeSnapshot(name, setup)),
32
+ list: (options) =>
33
+ auth
34
+ .authorize(Stream.runCollect(snapshots.listSnapshots.items({ ...options })))
35
+ .pipe(Effect.map((all) => Array.from(all))),
36
+ // A snapshot is stored as a template under its name.
37
+ delete: (name) =>
38
+ auth
39
+ .authorize(
40
+ templates
41
+ .getTemplateByAlias({ alias: name })
42
+ .pipe(
43
+ Effect.flatMap((alias) =>
44
+ templates.deleteTemplate({ templateID: alias.templateID }),
45
+ ),
46
+ ),
47
+ )
48
+ .pipe(
49
+ Effect.asVoid,
50
+ Effect.catchTag("NotFound", () => Effect.void),
51
+ ),
52
+ };
53
+ return client;
54
+ }),
55
+ );