@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
@@ -0,0 +1,135 @@
1
+ import type * as tags from "@distilled.cloud/e2b/tags";
2
+ import type * as templates from "@distilled.cloud/e2b/templates";
3
+ import type * as unstable from "@distilled.cloud/e2b/unstable";
4
+ import * as Binding from "alchemy/Binding";
5
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
6
+ import type * as Effect from "effect/Effect";
7
+ import type * as PlatformError from "effect/PlatformError";
8
+ import type { TemplateBuildFailed } from "./Errors.ts";
9
+ import type { TemplateBuildSpec } from "./internal/TemplateBuild.ts";
10
+ import type { E2BError } from "./SandboxClient.ts";
11
+
12
+ /**
13
+ * What a runtime build takes: E2B's own build inputs (a base `image` or
14
+ * `fromTemplate`, then `env`, `files` and `run` steps), with no Dockerfile.
15
+ * Files come from memory; there is no build-context directory where a
16
+ * Worker runs.
17
+ */
18
+ export type RuntimeTemplateSpec = TemplateBuildSpec;
19
+
20
+ /** A built template. */
21
+ export interface TemplateInfo {
22
+ readonly templateId: string;
23
+ readonly buildId: string;
24
+ /** Fully qualified names, e.g. `my-team/my-template:default`. */
25
+ readonly names: ReadonlyArray<string>;
26
+ }
27
+
28
+ /** Errors a runtime build can fail with. */
29
+ export type TemplateBuildError = E2BError | TemplateBuildFailed | PlatformError.PlatformError;
30
+
31
+ /** E2B templates: build, look up, tag and delete them at runtime. */
32
+ export interface TemplatesClient {
33
+ /** Builds a template and waits until it is ready. */
34
+ build(spec: RuntimeTemplateSpec): Effect.Effect<TemplateInfo, TemplateBuildError, RuntimeContext>;
35
+ /**
36
+ * Builds a template unless a ready build of these exact inputs exists.
37
+ * Each build is tagged with its content hash, so a repeat call is one
38
+ * lookup.
39
+ */
40
+ getOrBuild(
41
+ spec: RuntimeTemplateSpec,
42
+ ): Effect.Effect<TemplateInfo, TemplateBuildError, RuntimeContext>;
43
+ /** A template by id or name (alias), or `undefined`. */
44
+ get(
45
+ nameOrId: string,
46
+ ): Effect.Effect<templates.GetTemplateResponse | undefined, E2BError, RuntimeContext>;
47
+ /** The team's templates. */
48
+ list(
49
+ options?: Omit<templates.ListTemplatesRequest, "teamID" | "nextToken">,
50
+ ): Effect.Effect<ReadonlyArray<templates.Template>, E2BError, RuntimeContext>;
51
+ /** E2B's public base templates. */
52
+ listDefaults(): Effect.Effect<templates.ListDefaultTemplatesResponse, E2BError, RuntimeContext>;
53
+ /** Templates matching a search. */
54
+ search(
55
+ options: unstable.SearchTemplatesRequest,
56
+ ): Effect.Effect<unstable.TemplateSearchResponse, E2BError, RuntimeContext>;
57
+ /** Whether other teams can create sandboxes from the template. */
58
+ setPublic(templateId: string, isPublic: boolean): Effect.Effect<void, E2BError, RuntimeContext>;
59
+ /** A build's status, with its newest log lines. */
60
+ buildStatus(
61
+ templateId: string,
62
+ buildId: string,
63
+ options?: Omit<templates.GetTemplateBuildStatusRequest, "templateID" | "buildID">,
64
+ ): Effect.Effect<templates.TemplateBuildInfo, E2BError, RuntimeContext>;
65
+ /** A build's logs. */
66
+ buildLogs(
67
+ templateId: string,
68
+ buildId: string,
69
+ options?: Omit<templates.ListTemplateBuildLogsRequest, "templateID" | "buildID">,
70
+ ): Effect.Effect<templates.TemplateBuildLogsResponse, E2BError, RuntimeContext>;
71
+ /** Deletes a template. Deleting one that is gone succeeds. */
72
+ delete(templateId: string): Effect.Effect<void, E2BError, RuntimeContext>;
73
+ /** Tags on builds: `name:tag` names a build sandboxes are created from. */
74
+ readonly tags: {
75
+ /** Assigns tags to the build `target` (`name:tag` or `name`) points at. */
76
+ assign(
77
+ target: string,
78
+ tags: ReadonlyArray<string>,
79
+ ): Effect.Effect<tags.AssignedTemplateTags, E2BError, RuntimeContext>;
80
+ /** Removes tags from a template by name. */
81
+ remove(
82
+ name: string,
83
+ tags: ReadonlyArray<string>,
84
+ ): Effect.Effect<void, E2BError, RuntimeContext>;
85
+ list(
86
+ templateId: string,
87
+ ): Effect.Effect<tags.ListTemplateTagsResponse, E2BError, RuntimeContext>;
88
+ exists(templateId: string, tag: string): Effect.Effect<boolean, E2BError, RuntimeContext>;
89
+ count(templateId: string): Effect.Effect<number, E2BError, RuntimeContext>;
90
+ groups(
91
+ templateId: string,
92
+ options?: Omit<tags.ListTemplateTagGroupsRequest, "templateID">,
93
+ ): Effect.Effect<tags.TemplateTagGroupsResponse, E2BError, RuntimeContext>;
94
+ assignments(
95
+ templateId: string,
96
+ tag: string,
97
+ options?: Omit<tags.ListTemplateTagAssignmentsRequest, "templateID" | "tag">,
98
+ ): Effect.Effect<tags.TemplateTagAssignmentsResponse, E2BError, RuntimeContext>;
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Build E2B templates at runtime from E2B's own build inputs: a template
104
+ * per repository, per customer image, per agent configuration.
105
+ * E2B builds remotely, so this works from a Worker.
106
+ *
107
+ * Templates built at runtime carry no metadata, so they are not deleted
108
+ * when the host is destroyed. Delete them with `delete` when their owner
109
+ * goes away.
110
+ *
111
+ * ### Building at runtime
112
+ * **Example:** A template per repository, built once
113
+ * ```typescript
114
+ * const templates = yield* E2B.CreateTemplate();
115
+ * const sandboxes = yield* E2B.CreateSandbox();
116
+ * // in fetch:
117
+ * const template = yield* templates.getOrBuild({
118
+ * name: `repo-${slug}`,
119
+ * image: "node:22-bookworm",
120
+ * files: [{ path: "/home/user/app/package.json", contents: packageJson }],
121
+ * run: ["cd /home/user/app && npm install"],
122
+ * });
123
+ * const sandbox = yield* sandboxes.create({ templateID: template.templateId });
124
+ * ```
125
+ *
126
+ * @binding
127
+ * @product Template
128
+ */
129
+ export interface CreateTemplate extends Binding.Service<
130
+ CreateTemplate,
131
+ "E2B.CreateTemplate",
132
+ () => Effect.Effect<TemplatesClient>
133
+ > {}
134
+
135
+ export const CreateTemplate = Binding.Service<CreateTemplate>("E2B.CreateTemplate");
@@ -0,0 +1,152 @@
1
+ import * as tags from "@distilled.cloud/e2b/tags";
2
+ import * as templates from "@distilled.cloud/e2b/templates";
3
+ import * as unstable from "@distilled.cloud/e2b/unstable";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Layer from "effect/Layer";
6
+ import * as Stream from "effect/Stream";
7
+ import {
8
+ CreateTemplate,
9
+ type RuntimeTemplateSpec,
10
+ type TemplateInfo,
11
+ type TemplatesClient,
12
+ } from "./CreateTemplate.ts";
13
+ import { bindE2B } from "./E2BBindings.ts";
14
+ import { planTemplateBuild, runTemplateBuild } from "./internal/TemplateBuild.ts";
15
+
16
+ /** The tag a build of these inputs carries. */
17
+ const hashTag = (hash: string) => `alchemy-${hash.slice(0, 16)}`;
18
+
19
+ /**
20
+ * HTTP implementation of {@link CreateTemplate}. Works on any host.
21
+ *
22
+ * ### Provide the layer
23
+ * **Example:** On a Worker
24
+ * ```typescript
25
+ * Effect.gen(function* () {
26
+ * const templates = yield* E2B.CreateTemplate();
27
+ * // ...
28
+ * }).pipe(Effect.provide(E2B.CreateTemplateHttp))
29
+ * ```
30
+ *
31
+ * @layer
32
+ * @product Template
33
+ * @provides E2B.CreateTemplate
34
+ */
35
+ export const CreateTemplateHttp = Layer.succeed(
36
+ CreateTemplate,
37
+ Effect.fn("E2B.CreateTemplate")(function* () {
38
+ const auth = yield* bindE2B;
39
+
40
+ const get = (nameOrId: string) =>
41
+ auth
42
+ .authorize(
43
+ templates
44
+ .getTemplate({ templateID: nameOrId })
45
+ .pipe(
46
+ Effect.catchTag("NotFound", () =>
47
+ templates
48
+ .getTemplateByAlias({ alias: nameOrId })
49
+ .pipe(
50
+ Effect.flatMap((alias) =>
51
+ templates.getTemplate({ templateID: alias.templateID }),
52
+ ),
53
+ ),
54
+ ),
55
+ ),
56
+ )
57
+ .pipe(
58
+ Effect.map((template): templates.GetTemplateResponse | undefined => template),
59
+ Effect.catchTag("NotFound", () => Effect.undefined),
60
+ );
61
+
62
+ const build = Effect.fn("E2B.CreateTemplate.build")(function* (spec: RuntimeTemplateSpec) {
63
+ const plan = yield* planTemplateBuild(spec);
64
+ const built = yield* auth.authorize(
65
+ // E2B builds the `default` tag only when no tags are given, so keep it
66
+ // next to the hash tag unless the caller chose its own.
67
+ runTemplateBuild(
68
+ { ...spec, tags: [...(spec.tags ?? ["default"]), hashTag(plan.hash)] },
69
+ plan,
70
+ ),
71
+ );
72
+ return {
73
+ templateId: built.templateId,
74
+ buildId: built.buildId,
75
+ names: built.names,
76
+ } satisfies TemplateInfo;
77
+ });
78
+
79
+ const client: TemplatesClient = {
80
+ build,
81
+ getOrBuild: Effect.fn("E2B.CreateTemplate.getOrBuild")(function* (spec) {
82
+ const plan = yield* planTemplateBuild(spec);
83
+ const existing = yield* get(spec.name);
84
+ if (existing !== undefined) {
85
+ const tagged = yield* auth.authorize(
86
+ tags.getTemplateTagExists({ templateID: existing.templateID, tag: hashTag(plan.hash) }),
87
+ );
88
+ const ready =
89
+ existing.builds.find((b) => b.buildID === existing.buildID)?.status === "ready";
90
+ if (tagged.exists && ready) {
91
+ return {
92
+ templateId: existing.templateID,
93
+ buildId: existing.buildID,
94
+ names: existing.names,
95
+ };
96
+ }
97
+ }
98
+ return yield* build(spec);
99
+ }),
100
+ get,
101
+ list: (options) =>
102
+ auth
103
+ .authorize(Stream.runCollect(templates.listTemplates.items({ ...options })))
104
+ .pipe(Effect.map((all) => Array.from(all))),
105
+ listDefaults: () => auth.authorize(templates.listDefaultTemplates({})),
106
+ search: (options) => auth.authorize(unstable.searchTemplates(options)),
107
+ setPublic: (templateId, isPublic) =>
108
+ auth
109
+ .authorize(templates.updateTemplate({ templateID: templateId, public: isPublic }))
110
+ .pipe(Effect.asVoid),
111
+ buildStatus: (templateId, buildId, options) =>
112
+ auth.authorize(
113
+ templates.getTemplateBuildStatus({
114
+ ...options,
115
+ templateID: templateId,
116
+ buildID: buildId,
117
+ }),
118
+ ),
119
+ buildLogs: (templateId, buildId, options) =>
120
+ auth.authorize(
121
+ templates.listTemplateBuildLogs({ ...options, templateID: templateId, buildID: buildId }),
122
+ ),
123
+ delete: (templateId) =>
124
+ auth.authorize(templates.deleteTemplate({ templateID: templateId })).pipe(
125
+ Effect.asVoid,
126
+ Effect.catchTag("NotFound", () => Effect.void),
127
+ ),
128
+ tags: {
129
+ assign: (target, assigned) =>
130
+ auth.authorize(tags.createTemplateTag({ target, tags: [...assigned] })),
131
+ remove: (name, removed) =>
132
+ auth.authorize(tags.deleteTemplateTags({ name, tags: [...removed] })).pipe(Effect.asVoid),
133
+ list: (templateId) => auth.authorize(tags.listTemplateTags({ templateID: templateId })),
134
+ exists: (templateId, tag) =>
135
+ auth
136
+ .authorize(tags.getTemplateTagExists({ templateID: templateId, tag }))
137
+ .pipe(Effect.map((res) => res.exists)),
138
+ count: (templateId) =>
139
+ auth
140
+ .authorize(tags.getTemplateTagCount({ templateID: templateId }))
141
+ .pipe(Effect.map((res) => res.total)),
142
+ groups: (templateId, options) =>
143
+ auth.authorize(tags.listTemplateTagGroups({ ...options, templateID: templateId })),
144
+ assignments: (templateId, tag, options) =>
145
+ auth.authorize(
146
+ tags.listTemplateTagAssignments({ ...options, templateID: templateId, tag }),
147
+ ),
148
+ },
149
+ };
150
+ return client;
151
+ }),
152
+ );
@@ -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 { E2BError } from "./SandboxClient.ts";
5
+ import type { ReadWriteVolumeClient } from "./VolumeFiles.ts";
6
+
7
+ /** A volume created or found at runtime, with its files. */
8
+ export interface VolumeHandle {
9
+ /** E2B's volume id. */
10
+ readonly volumeId: string;
11
+ /** The volume's name. Mount it with `mounts: [{ volume, path }]`. */
12
+ readonly name: string;
13
+ /** The volume's files, readable and writable without a sandbox. */
14
+ readonly files: ReadWriteVolumeClient;
15
+ }
16
+
17
+ /** E2B volumes by name: create, look up, list and delete them. */
18
+ export interface VolumesClient {
19
+ /** Creates a volume. Fails with `Conflict` when the name is taken. */
20
+ create(name: string): Effect.Effect<VolumeHandle, E2BError, RuntimeContext>;
21
+ /** The volume with this name or id, or `undefined`. */
22
+ get(nameOrId: string): Effect.Effect<VolumeHandle | undefined, E2BError, RuntimeContext>;
23
+ /** The volume with this name, created if missing. Safe to race. */
24
+ getOrCreate(name: string): Effect.Effect<VolumeHandle, E2BError, RuntimeContext>;
25
+ /** Every volume in the team. */
26
+ list(): Effect.Effect<
27
+ ReadonlyArray<{ readonly volumeId: string; readonly name: string }>,
28
+ E2BError,
29
+ RuntimeContext
30
+ >;
31
+ /** Deletes a volume and its files. Deleting one that is gone succeeds. */
32
+ delete(volumeId: string): Effect.Effect<void, E2BError, RuntimeContext>;
33
+ }
34
+
35
+ /**
36
+ * Create and manage E2B volumes at runtime, by name: a home directory per
37
+ * user, a workspace per session. Mount one into a sandbox by its `name`, and
38
+ * read or write its files with `files` without a sandbox.
39
+ *
40
+ * E2B volumes carry no metadata, so the volumes a host creates at runtime
41
+ * are not deleted when the host is destroyed. Delete them with `delete` when
42
+ * their owner goes away.
43
+ *
44
+ * ### Per-user volumes
45
+ * **Example:** A home volume per user, mounted into their sandbox
46
+ * ```typescript
47
+ * const volumes = yield* E2B.CreateVolume();
48
+ * const sandboxes = yield* E2B.CreateSandbox(Agent);
49
+ * // in fetch:
50
+ * const home = yield* volumes.getOrCreate(`user-${user}`);
51
+ * const sandbox = yield* sandboxes.create({
52
+ * mounts: [{ volume: home, path: "/home/user/work" }],
53
+ * });
54
+ * ```
55
+ *
56
+ * @binding
57
+ * @product Volume
58
+ */
59
+ export interface CreateVolume extends Binding.Service<
60
+ CreateVolume,
61
+ "E2B.CreateVolume",
62
+ () => Effect.Effect<VolumesClient>
63
+ > {}
64
+
65
+ export const CreateVolume = Binding.Service<CreateVolume>("E2B.CreateVolume");
@@ -0,0 +1,80 @@
1
+ import * as volumes from "@distilled.cloud/e2b/volumes";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import { CreateVolume, type VolumeHandle, type VolumesClient } from "./CreateVolume.ts";
5
+ import { bindE2B, type E2BAuth } from "./E2BBindings.ts";
6
+ import { makeVolumeFiles } from "./internal/VolumeHttp.ts";
7
+
8
+ const handleOf = (auth: E2BAuth, volume: { volumeID: string; name: string }): VolumeHandle => ({
9
+ volumeId: volume.volumeID,
10
+ name: volume.name,
11
+ files: makeVolumeFiles(auth, Effect.succeed(volume.volumeID)),
12
+ });
13
+
14
+ /**
15
+ * HTTP implementation of {@link CreateVolume}. Works on any host.
16
+ *
17
+ * ### Provide the layer
18
+ * **Example:** On a Worker
19
+ * ```typescript
20
+ * Effect.gen(function* () {
21
+ * const volumes = yield* E2B.CreateVolume();
22
+ * // ...
23
+ * }).pipe(Effect.provide(E2B.CreateVolumeHttp))
24
+ * ```
25
+ *
26
+ * @layer
27
+ * @product Volume
28
+ * @provides E2B.CreateVolume
29
+ */
30
+ export const CreateVolumeHttp = Layer.succeed(
31
+ CreateVolume,
32
+ Effect.fn("E2B.CreateVolume")(function* () {
33
+ const auth = yield* bindE2B;
34
+ const findByName = (name: string) =>
35
+ auth
36
+ .authorize(volumes.listVolumes({}))
37
+ .pipe(Effect.map((all) => all.find((volume) => volume.name === name)));
38
+ const get = (nameOrId: string) =>
39
+ auth.authorize(volumes.getVolume({ volumeID: nameOrId })).pipe(
40
+ Effect.map((volume): { volumeID: string; name: string } | undefined => volume),
41
+ Effect.catchTag("NotFound", () => findByName(nameOrId)),
42
+ Effect.map((volume) => (volume === undefined ? undefined : handleOf(auth, volume))),
43
+ );
44
+ const client: VolumesClient = {
45
+ create: (name) =>
46
+ auth
47
+ .authorize(volumes.createVolume({ name }))
48
+ .pipe(Effect.map((volume) => handleOf(auth, volume))),
49
+ get,
50
+ getOrCreate: (name) =>
51
+ Effect.flatMap(get(name), (existing) =>
52
+ existing !== undefined
53
+ ? Effect.succeed(existing)
54
+ : auth.authorize(volumes.createVolume({ name })).pipe(
55
+ Effect.map((volume) => handleOf(auth, volume)),
56
+ // A concurrent create won the name: use theirs.
57
+ Effect.catchTag("Conflict", (error) =>
58
+ Effect.flatMap(get(name), (raced) =>
59
+ raced === undefined ? Effect.fail(error) : Effect.succeed(raced),
60
+ ),
61
+ ),
62
+ ),
63
+ ),
64
+ list: () =>
65
+ auth
66
+ .authorize(volumes.listVolumes({}))
67
+ .pipe(
68
+ Effect.map((all) =>
69
+ all.map((volume) => ({ volumeId: volume.volumeID, name: volume.name })),
70
+ ),
71
+ ),
72
+ delete: (volumeId) =>
73
+ auth.authorize(volumes.deleteVolume({ volumeID: volumeId })).pipe(
74
+ Effect.asVoid,
75
+ Effect.catchTag("NotFound", () => Effect.void),
76
+ ),
77
+ };
78
+ return client;
79
+ }),
80
+ );
@@ -0,0 +1,81 @@
1
+ import { ConfigError } from "@distilled.cloud/core/errors";
2
+ import * as auth from "@distilled.cloud/e2b/auth";
3
+ import {
4
+ apiBaseUrlFor,
5
+ Credentials,
6
+ credentials,
7
+ type Config,
8
+ } from "@distilled.cloud/e2b/Credentials";
9
+ import { lazyCredentials } from "alchemy/Auth/Resolve";
10
+ import * as Effect from "effect/Effect";
11
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
12
+ import * as Layer from "effect/Layer";
13
+ import { E2B_AUTH_PROVIDER_NAME, type E2BResolvedCredentials } from "./AuthProvider.ts";
14
+
15
+ export {
16
+ Credentials,
17
+ CredentialsFromEnv,
18
+ credentials,
19
+ DEFAULT_DOMAIN,
20
+ type Config as CredentialsConfig,
21
+ } from "@distilled.cloud/e2b/Credentials";
22
+
23
+ /**
24
+ * Fills in the team id for an access token that came without one: E2B
25
+ * lists the token's teams, and the default team is the one it acts on.
26
+ */
27
+ export const withTeamId = Effect.fn(function* (config: Config) {
28
+ if (config.accessToken === undefined || (config.teamId ?? "") !== "") return config;
29
+ const teams = yield* auth.listTeams({}).pipe(
30
+ Effect.provide(Layer.mergeAll(credentials(config), FetchHttpClient.layer)),
31
+ Effect.mapError(
32
+ (cause) =>
33
+ new ConfigError({
34
+ message: `Failed to look up the E2B team for the access token: ${cause.message}`,
35
+ }),
36
+ ),
37
+ );
38
+ const team = teams.find((t) => t.isDefault) ?? (teams.length === 1 ? teams[0] : undefined);
39
+ if (team === undefined) {
40
+ return yield* new ConfigError({
41
+ message:
42
+ teams.length === 0
43
+ ? "The E2B access token belongs to no team"
44
+ : `The E2B access token belongs to ${teams.length} teams and none is the default: set teamId (E2B_TEAM_ID)`,
45
+ });
46
+ }
47
+ return { ...config, teamId: team.teamID };
48
+ });
49
+
50
+ /**
51
+ * Build a `Credentials` layer that resolves E2B credentials via the current
52
+ * Alchemy profile, or directly from environment variables in CI. An access
53
+ * token without a team id gets its default team's id. A browser login's
54
+ * token is re-resolved (and refreshed) before it expires.
55
+ */
56
+ export const fromAuthProvider = () =>
57
+ Layer.effect(
58
+ Credentials,
59
+ Effect.gen(function* () {
60
+ const resolve = yield* lazyCredentials<E2BResolvedCredentials, ConfigError>()(
61
+ E2B_AUTH_PROVIDER_NAME,
62
+ (creds): Config => ({
63
+ ...(creds.apiKey === undefined ? {} : { apiKey: creds.apiKey }),
64
+ ...(creds.accessToken === undefined ? {} : { accessToken: creds.accessToken }),
65
+ ...(creds.teamId === undefined ? {} : { teamId: creds.teamId }),
66
+ domain: creds.domain,
67
+ apiBaseUrl: apiBaseUrlFor(creds.domain),
68
+ }),
69
+ { expiresAt: (creds) => creds.expires },
70
+ );
71
+ // The team lookup runs once; a failed one is retried on the next call.
72
+ let teamId: string | undefined;
73
+ return Effect.gen(function* () {
74
+ const config = yield* resolve;
75
+ if (teamId !== undefined && (config.teamId ?? "") === "") return { ...config, teamId };
76
+ const withTeam = yield* withTeamId(config);
77
+ teamId = withTeam.teamId;
78
+ return withTeam;
79
+ });
80
+ }),
81
+ );
@@ -0,0 +1,160 @@
1
+ /**
2
+ * How E2B bindings reach E2B from any host.
3
+ *
4
+ * E2B authenticates with a team API key and has no identity federation or
5
+ * key scopes, so every host — a Cloudflare Worker, a Lambda, a container, an
6
+ * E2B sandbox — gets its own API key, minted at deploy as an `E2B.ApiKey` in
7
+ * the host's namespace and deleted with the host. The deployer's credentials
8
+ * never reach a host. Minting takes the deployer's access token. The binding
9
+ * captures the key (and the E2B domain) as named runtime Outputs; inside the
10
+ * deployed host they are read back from the host's env, still `Redacted`.
11
+ * Every runtime call provides `Credentials` from those accessors, so nothing
12
+ * reads `process.env`.
13
+ *
14
+ * Bindings that create sandboxes or secrets at run time tag them with the
15
+ * host's `alchemy::stack` / `alchemy::stage` / `alchemy::id`, computed at
16
+ * deploy and bound into the host (`bindOwnerTags`). Destroying the stack
17
+ * does not delete them.
18
+ */
19
+ import { ConfigError } from "@distilled.cloud/core/errors";
20
+ import {
21
+ apiBaseUrlFor,
22
+ Credentials,
23
+ CredentialsFromEnv,
24
+ DEFAULT_DOMAIN,
25
+ } from "@distilled.cloud/e2b/Credentials";
26
+ import { orDieCredentialsUnavailable } from "alchemy/Auth/Resolve";
27
+ import * as Binding from "alchemy/Binding";
28
+ import * as Namespace from "alchemy/Namespace";
29
+ import * as Output from "alchemy/Output";
30
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
31
+ import { Stack } from "alchemy/Stack";
32
+ import { Stage } from "alchemy/Stage";
33
+ import * as Effect from "effect/Effect";
34
+ import * as FetchHttpClient from "effect/http/FetchHttpClient";
35
+ import type * as HttpClient from "effect/http/HttpClient";
36
+ import * as Layer from "effect/Layer";
37
+ import * as Option from "effect/Option";
38
+ import * as Redacted from "effect/Redacted";
39
+ import { ApiKey } from "./ApiKey.ts";
40
+ import { withTeamId } from "./Credentials.ts";
41
+
42
+ /** A plain value as an Output with no requirements (`asOutput` leaves them `any`). */
43
+ const output = <A>(value: A) => Output.asOutput(value) as Output.Output<A, never>;
44
+
45
+ /** The `alchemy::*` metadata on objects a host creates at run time. */
46
+ export type OwnerTags = Readonly<Record<string, string>>;
47
+
48
+ /** E2B's `metadata` filter: `key=value&…`. */
49
+ export const metadataQuery = (metadata: Readonly<Record<string, string>>) =>
50
+ new URLSearchParams(Object.entries(metadata)).toString();
51
+
52
+ export interface E2BAuth {
53
+ /** Runs a distilled call with the bound credentials and an HTTP client. */
54
+ readonly authorize: <A, E>(
55
+ effect: Effect.Effect<A, E, Credentials | HttpClient.HttpClient>,
56
+ ) => Effect.Effect<A, E, RuntimeContext>;
57
+ /** The E2B domain sandboxes are addressed under. */
58
+ readonly domain: Effect.Effect<string>;
59
+ /** The team the host's key belongs to. */
60
+ readonly teamId: Effect.Effect<string>;
61
+ }
62
+
63
+ /** The deploying stack's credentials: the ambient profile-backed ones, else the env. */
64
+ const deployCredentials = Effect.gen(function* () {
65
+ const ambient = yield* Effect.serviceOption(Credentials);
66
+ const resolve = Option.isSome(ambient)
67
+ ? ambient.value
68
+ : yield* Credentials.pipe(Effect.provide(CredentialsFromEnv));
69
+ return yield* resolve;
70
+ });
71
+
72
+ const requireAccessToken = (config: { readonly accessToken?: Redacted.Redacted<string> }) =>
73
+ config.accessToken === undefined
74
+ ? Effect.fail(
75
+ new ConfigError({
76
+ message:
77
+ "E2B bindings mint an API key for each host, which takes an E2B access token: " +
78
+ "add accessToken to the E2B profile (alchemy profile edit --add E2B) or set E2B_ACCESS_TOKEN",
79
+ }),
80
+ )
81
+ : Effect.void;
82
+
83
+ /**
84
+ * Mints the current host's API key and binds it, the domain and the
85
+ * ownership stamp onto the host, and returns runtime accessors for them.
86
+ * Call it in a binding's construction; every binding on a host shares the
87
+ * one key.
88
+ */
89
+ export const bindE2B = Effect.gen(function* () {
90
+ // Yielding the resource class erases its `Providers` requirement; the
91
+ // stack satisfies it at plan time.
92
+ const makeKey = yield* ApiKey;
93
+ let apiKey: Output.Output<Redacted.Redacted<string>, never> = output(Redacted.make(""));
94
+ let domain = DEFAULT_DOMAIN;
95
+ let teamId = "";
96
+ if (!globalThis.__ALCHEMY_RUNTIME__) {
97
+ const config = yield* deployCredentials.pipe(orDieCredentialsUnavailable("E2B"));
98
+ yield* requireAccessToken(config).pipe(orDieCredentialsUnavailable("E2B"));
99
+ domain = config.domain;
100
+ teamId = (yield* withTeamId(config).pipe(orDieCredentialsUnavailable("E2B"))).teamId ?? "";
101
+ const host = yield* Binding.Host;
102
+ if (host !== undefined) {
103
+ const hostKey = yield* Namespace.push(host.LogicalId, makeKey("E2BHostKey", {}));
104
+ apiKey = hostKey.key as Output.Output<Redacted.Redacted<string>, never>;
105
+ }
106
+ }
107
+ const key = yield* Output.named(apiKey, "E2B_API_KEY").asEffect();
108
+ const boundDomain = yield* Output.named(output(domain), "E2B_DOMAIN").asEffect();
109
+ const boundTeamId = yield* Output.named(output(teamId), "E2B_TEAM_ID").asEffect();
110
+
111
+ const domainOf = Effect.map(boundDomain, (value) => value || DEFAULT_DOMAIN);
112
+ const credentials = Effect.gen(function* () {
113
+ const resolvedDomain = yield* domainOf;
114
+ return {
115
+ apiKey: yield* key,
116
+ domain: resolvedDomain,
117
+ apiBaseUrl: apiBaseUrlFor(resolvedDomain),
118
+ };
119
+ });
120
+ const services = Layer.mergeAll(Layer.succeed(Credentials, credentials), FetchHttpClient.layer);
121
+
122
+ return {
123
+ authorize: (effect) => effect.pipe(Effect.provide(services)),
124
+ domain: domainOf,
125
+ teamId: boundTeamId,
126
+ } satisfies E2BAuth;
127
+ });
128
+
129
+ const parseOwnerTags = (json: string): OwnerTags => {
130
+ try {
131
+ return JSON.parse(json || "{}");
132
+ } catch {
133
+ return {};
134
+ }
135
+ };
136
+
137
+ /**
138
+ * Binds the host's `alchemy::stack` / `alchemy::stage` / `alchemy::id` into
139
+ * it, and returns them at run time for `create` to put on what it makes.
140
+ * Without a host (a script) there are none.
141
+ */
142
+ export const bindOwnerTags = Effect.gen(function* () {
143
+ let tags = "{}";
144
+ if (!globalThis.__ALCHEMY_RUNTIME__) {
145
+ const host = yield* Binding.Host;
146
+ // The values `createInternalTags(host.LogicalId)` gives, read without
147
+ // adding `Stack` / `Stage` to the binding's requirements.
148
+ const stack = yield* Effect.serviceOption(Stack);
149
+ const stage = yield* Effect.serviceOption(Stage);
150
+ if (host !== undefined && Option.isSome(stack) && Option.isSome(stage)) {
151
+ tags = JSON.stringify({
152
+ "alchemy::stack": stack.value.name,
153
+ "alchemy::stage": stage.value,
154
+ "alchemy::id": host.LogicalId,
155
+ } satisfies OwnerTags);
156
+ }
157
+ }
158
+ const bound = yield* Output.named(output(tags), "E2B_OWNER_TAGS").asEffect();
159
+ return Effect.map(bound, parseOwnerTags);
160
+ });