@alchemy.run/modal 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 (499) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +96 -4
  3. package/lib/App.d.ts +143 -0
  4. package/lib/App.d.ts.map +1 -0
  5. package/lib/App.js +198 -0
  6. package/lib/App.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +53 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +209 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/BindingHttp.d.ts +56 -0
  12. package/lib/BindingHttp.d.ts.map +1 -0
  13. package/lib/BindingHttp.js +50 -0
  14. package/lib/BindingHttp.js.map +1 -0
  15. package/lib/Blob.d.ts +34 -0
  16. package/lib/Blob.d.ts.map +1 -0
  17. package/lib/Blob.js +59 -0
  18. package/lib/Blob.js.map +1 -0
  19. package/lib/Bootstrap.d.ts +8 -0
  20. package/lib/Bootstrap.d.ts.map +1 -0
  21. package/lib/Bootstrap.js +26 -0
  22. package/lib/Bootstrap.js.map +1 -0
  23. package/lib/Cbor.d.ts +10 -0
  24. package/lib/Cbor.d.ts.map +1 -0
  25. package/lib/Cbor.js +177 -0
  26. package/lib/Cbor.js.map +1 -0
  27. package/lib/ConnectSandbox.d.ts +64 -0
  28. package/lib/ConnectSandbox.d.ts.map +1 -0
  29. package/lib/ConnectSandbox.js +33 -0
  30. package/lib/ConnectSandbox.js.map +1 -0
  31. package/lib/Container.d.ts +124 -0
  32. package/lib/Container.d.ts.map +1 -0
  33. package/lib/Container.js +1 -0
  34. package/lib/Container.js.map +1 -0
  35. package/lib/Contributions.d.ts +7 -0
  36. package/lib/Contributions.d.ts.map +1 -0
  37. package/lib/Contributions.js +6 -0
  38. package/lib/Contributions.js.map +1 -0
  39. package/lib/CreateDict.d.ts +83 -0
  40. package/lib/CreateDict.d.ts.map +1 -0
  41. package/lib/CreateDict.js +3 -0
  42. package/lib/CreateDict.js.map +1 -0
  43. package/lib/CreateDictHttp.d.ts +33 -0
  44. package/lib/CreateDictHttp.d.ts.map +1 -0
  45. package/lib/CreateDictHttp.js +67 -0
  46. package/lib/CreateDictHttp.js.map +1 -0
  47. package/lib/CreateImage.d.ts +98 -0
  48. package/lib/CreateImage.d.ts.map +1 -0
  49. package/lib/CreateImage.js +3 -0
  50. package/lib/CreateImage.js.map +1 -0
  51. package/lib/CreateImageHttp.d.ts +51 -0
  52. package/lib/CreateImageHttp.d.ts.map +1 -0
  53. package/lib/CreateImageHttp.js +87 -0
  54. package/lib/CreateImageHttp.js.map +1 -0
  55. package/lib/CreateQueue.d.ts +83 -0
  56. package/lib/CreateQueue.d.ts.map +1 -0
  57. package/lib/CreateQueue.js +3 -0
  58. package/lib/CreateQueue.js.map +1 -0
  59. package/lib/CreateQueueHttp.d.ts +31 -0
  60. package/lib/CreateQueueHttp.d.ts.map +1 -0
  61. package/lib/CreateQueueHttp.js +67 -0
  62. package/lib/CreateQueueHttp.js.map +1 -0
  63. package/lib/CreateSandbox.d.ts +186 -0
  64. package/lib/CreateSandbox.d.ts.map +1 -0
  65. package/lib/CreateSandbox.js +3 -0
  66. package/lib/CreateSandbox.js.map +1 -0
  67. package/lib/CreateSandboxHttp.d.ts +17 -0
  68. package/lib/CreateSandboxHttp.d.ts.map +1 -0
  69. package/lib/CreateSandboxHttp.js +117 -0
  70. package/lib/CreateSandboxHttp.js.map +1 -0
  71. package/lib/CreateSecret.d.ts +95 -0
  72. package/lib/CreateSecret.d.ts.map +1 -0
  73. package/lib/CreateSecret.js +3 -0
  74. package/lib/CreateSecret.js.map +1 -0
  75. package/lib/CreateSecretHttp.d.ts +24 -0
  76. package/lib/CreateSecretHttp.d.ts.map +1 -0
  77. package/lib/CreateSecretHttp.js +60 -0
  78. package/lib/CreateSecretHttp.js.map +1 -0
  79. package/lib/CreateVolume.d.ts +84 -0
  80. package/lib/CreateVolume.d.ts.map +1 -0
  81. package/lib/CreateVolume.js +3 -0
  82. package/lib/CreateVolume.js.map +1 -0
  83. package/lib/CreateVolumeHttp.d.ts +32 -0
  84. package/lib/CreateVolumeHttp.d.ts.map +1 -0
  85. package/lib/CreateVolumeHttp.js +69 -0
  86. package/lib/CreateVolumeHttp.js.map +1 -0
  87. package/lib/Credentials.d.ts +32 -0
  88. package/lib/Credentials.d.ts.map +1 -0
  89. package/lib/Credentials.js +44 -0
  90. package/lib/Credentials.js.map +1 -0
  91. package/lib/Dict.d.ts +147 -0
  92. package/lib/Dict.d.ts.map +1 -0
  93. package/lib/Dict.js +175 -0
  94. package/lib/Dict.js.map +1 -0
  95. package/lib/DictClient.d.ts +55 -0
  96. package/lib/DictClient.d.ts.map +1 -0
  97. package/lib/DictClient.js +89 -0
  98. package/lib/DictClient.js.map +1 -0
  99. package/lib/Domain.d.ts +86 -0
  100. package/lib/Domain.d.ts.map +1 -0
  101. package/lib/Domain.js +113 -0
  102. package/lib/Domain.js.map +1 -0
  103. package/lib/Endpoint.d.ts +181 -0
  104. package/lib/Endpoint.d.ts.map +1 -0
  105. package/lib/Endpoint.js +283 -0
  106. package/lib/Endpoint.js.map +1 -0
  107. package/lib/Environment.d.ts +174 -0
  108. package/lib/Environment.d.ts.map +1 -0
  109. package/lib/Environment.js +244 -0
  110. package/lib/Environment.js.map +1 -0
  111. package/lib/FetchServer.d.ts +105 -0
  112. package/lib/FetchServer.d.ts.map +1 -0
  113. package/lib/FetchServer.js +3 -0
  114. package/lib/FetchServer.js.map +1 -0
  115. package/lib/FetchServerHttp.d.ts +21 -0
  116. package/lib/FetchServerHttp.d.ts.map +1 -0
  117. package/lib/FetchServerHttp.js +84 -0
  118. package/lib/FetchServerHttp.js.map +1 -0
  119. package/lib/Function.d.ts +236 -0
  120. package/lib/Function.d.ts.map +1 -0
  121. package/lib/Function.js +273 -0
  122. package/lib/Function.js.map +1 -0
  123. package/lib/FunctionAutoscaler.d.ts +99 -0
  124. package/lib/FunctionAutoscaler.d.ts.map +1 -0
  125. package/lib/FunctionAutoscaler.js +136 -0
  126. package/lib/FunctionAutoscaler.js.map +1 -0
  127. package/lib/FunctionTarget.d.ts +61 -0
  128. package/lib/FunctionTarget.d.ts.map +1 -0
  129. package/lib/FunctionTarget.js +136 -0
  130. package/lib/FunctionTarget.js.map +1 -0
  131. package/lib/Image.d.ts +159 -0
  132. package/lib/Image.d.ts.map +1 -0
  133. package/lib/Image.js +203 -0
  134. package/lib/Image.js.map +1 -0
  135. package/lib/ImageBuild.d.ts +108 -0
  136. package/lib/ImageBuild.d.ts.map +1 -0
  137. package/lib/ImageBuild.js +157 -0
  138. package/lib/ImageBuild.js.map +1 -0
  139. package/lib/ImagePlan.d.ts +31 -0
  140. package/lib/ImagePlan.d.ts.map +1 -0
  141. package/lib/ImagePlan.js +70 -0
  142. package/lib/ImagePlan.js.map +1 -0
  143. package/lib/InferEnv.d.ts +26 -0
  144. package/lib/InferEnv.d.ts.map +1 -0
  145. package/lib/InferEnv.js +1 -0
  146. package/lib/InferEnv.js.map +1 -0
  147. package/lib/InvokeFunction.d.ts +186 -0
  148. package/lib/InvokeFunction.d.ts.map +1 -0
  149. package/lib/InvokeFunction.js +10 -0
  150. package/lib/InvokeFunction.js.map +1 -0
  151. package/lib/InvokeFunctionHttp.d.ts +39 -0
  152. package/lib/InvokeFunctionHttp.d.ts.map +1 -0
  153. package/lib/InvokeFunctionHttp.js +210 -0
  154. package/lib/InvokeFunctionHttp.js.map +1 -0
  155. package/lib/ModalBindings.d.ts +138 -0
  156. package/lib/ModalBindings.d.ts.map +1 -0
  157. package/lib/ModalBindings.js +223 -0
  158. package/lib/ModalBindings.js.map +1 -0
  159. package/lib/MountBucket.d.ts +80 -0
  160. package/lib/MountBucket.d.ts.map +1 -0
  161. package/lib/MountBucket.js +44 -0
  162. package/lib/MountBucket.js.map +1 -0
  163. package/lib/MountVolume.d.ts +77 -0
  164. package/lib/MountVolume.d.ts.map +1 -0
  165. package/lib/MountVolume.js +41 -0
  166. package/lib/MountVolume.js.map +1 -0
  167. package/lib/Mounts.d.ts +134 -0
  168. package/lib/Mounts.d.ts.map +1 -0
  169. package/lib/Mounts.js +87 -0
  170. package/lib/Mounts.js.map +1 -0
  171. package/lib/PersistentSandbox.d.ts +75 -0
  172. package/lib/PersistentSandbox.d.ts.map +1 -0
  173. package/lib/PersistentSandbox.js +93 -0
  174. package/lib/PersistentSandbox.js.map +1 -0
  175. package/lib/Providers.d.ts +23 -0
  176. package/lib/Providers.d.ts.map +1 -0
  177. package/lib/Providers.js +64 -0
  178. package/lib/Providers.js.map +1 -0
  179. package/lib/Proxy.d.ts +94 -0
  180. package/lib/Proxy.d.ts.map +1 -0
  181. package/lib/Proxy.js +132 -0
  182. package/lib/Proxy.js.map +1 -0
  183. package/lib/Queue.d.ts +147 -0
  184. package/lib/Queue.d.ts.map +1 -0
  185. package/lib/Queue.js +177 -0
  186. package/lib/Queue.js.map +1 -0
  187. package/lib/QueueClient.d.ts +64 -0
  188. package/lib/QueueClient.d.ts.map +1 -0
  189. package/lib/QueueClient.js +73 -0
  190. package/lib/QueueClient.js.map +1 -0
  191. package/lib/ReadApp.d.ts +85 -0
  192. package/lib/ReadApp.d.ts.map +1 -0
  193. package/lib/ReadApp.js +3 -0
  194. package/lib/ReadApp.js.map +1 -0
  195. package/lib/ReadAppHttp.d.ts +26 -0
  196. package/lib/ReadAppHttp.d.ts.map +1 -0
  197. package/lib/ReadAppHttp.js +106 -0
  198. package/lib/ReadAppHttp.js.map +1 -0
  199. package/lib/ReadDict.d.ts +39 -0
  200. package/lib/ReadDict.d.ts.map +1 -0
  201. package/lib/ReadDict.js +3 -0
  202. package/lib/ReadDict.js.map +1 -0
  203. package/lib/ReadDictHttp.d.ts +12 -0
  204. package/lib/ReadDictHttp.d.ts.map +1 -0
  205. package/lib/ReadDictHttp.js +23 -0
  206. package/lib/ReadDictHttp.js.map +1 -0
  207. package/lib/ReadFunction.d.ts +73 -0
  208. package/lib/ReadFunction.d.ts.map +1 -0
  209. package/lib/ReadFunction.js +3 -0
  210. package/lib/ReadFunction.js.map +1 -0
  211. package/lib/ReadFunctionHttp.d.ts +25 -0
  212. package/lib/ReadFunctionHttp.d.ts.map +1 -0
  213. package/lib/ReadFunctionHttp.js +63 -0
  214. package/lib/ReadFunctionHttp.js.map +1 -0
  215. package/lib/ReadQueue.d.ts +39 -0
  216. package/lib/ReadQueue.d.ts.map +1 -0
  217. package/lib/ReadQueue.js +3 -0
  218. package/lib/ReadQueue.js.map +1 -0
  219. package/lib/ReadQueueHttp.d.ts +12 -0
  220. package/lib/ReadQueueHttp.d.ts.map +1 -0
  221. package/lib/ReadQueueHttp.js +23 -0
  222. package/lib/ReadQueueHttp.js.map +1 -0
  223. package/lib/ReadServer.d.ts +72 -0
  224. package/lib/ReadServer.d.ts.map +1 -0
  225. package/lib/ReadServer.js +3 -0
  226. package/lib/ReadServer.js.map +1 -0
  227. package/lib/ReadServerHttp.d.ts +25 -0
  228. package/lib/ReadServerHttp.d.ts.map +1 -0
  229. package/lib/ReadServerHttp.js +70 -0
  230. package/lib/ReadServerHttp.js.map +1 -0
  231. package/lib/ReadVolume.d.ts +40 -0
  232. package/lib/ReadVolume.d.ts.map +1 -0
  233. package/lib/ReadVolume.js +3 -0
  234. package/lib/ReadVolume.js.map +1 -0
  235. package/lib/ReadVolumeHttp.d.ts +12 -0
  236. package/lib/ReadVolumeHttp.d.ts.map +1 -0
  237. package/lib/ReadVolumeHttp.js +24 -0
  238. package/lib/ReadVolumeHttp.js.map +1 -0
  239. package/lib/ReadWriteDict.d.ts +39 -0
  240. package/lib/ReadWriteDict.d.ts.map +1 -0
  241. package/lib/ReadWriteDict.js +3 -0
  242. package/lib/ReadWriteDict.js.map +1 -0
  243. package/lib/ReadWriteDictHttp.d.ts +12 -0
  244. package/lib/ReadWriteDictHttp.d.ts.map +1 -0
  245. package/lib/ReadWriteDictHttp.js +23 -0
  246. package/lib/ReadWriteDictHttp.js.map +1 -0
  247. package/lib/ReadWriteQueue.d.ts +40 -0
  248. package/lib/ReadWriteQueue.d.ts.map +1 -0
  249. package/lib/ReadWriteQueue.js +3 -0
  250. package/lib/ReadWriteQueue.js.map +1 -0
  251. package/lib/ReadWriteQueueHttp.d.ts +12 -0
  252. package/lib/ReadWriteQueueHttp.d.ts.map +1 -0
  253. package/lib/ReadWriteQueueHttp.js +23 -0
  254. package/lib/ReadWriteQueueHttp.js.map +1 -0
  255. package/lib/ReadWriteVolume.d.ts +40 -0
  256. package/lib/ReadWriteVolume.d.ts.map +1 -0
  257. package/lib/ReadWriteVolume.js +3 -0
  258. package/lib/ReadWriteVolume.js.map +1 -0
  259. package/lib/ReadWriteVolumeHttp.d.ts +12 -0
  260. package/lib/ReadWriteVolumeHttp.d.ts.map +1 -0
  261. package/lib/ReadWriteVolumeHttp.js +24 -0
  262. package/lib/ReadWriteVolumeHttp.js.map +1 -0
  263. package/lib/RuntimeOwner.d.ts +39 -0
  264. package/lib/RuntimeOwner.d.ts.map +1 -0
  265. package/lib/RuntimeOwner.js +85 -0
  266. package/lib/RuntimeOwner.js.map +1 -0
  267. package/lib/Sandbox.d.ts +215 -0
  268. package/lib/Sandbox.d.ts.map +1 -0
  269. package/lib/Sandbox.js +278 -0
  270. package/lib/Sandbox.js.map +1 -0
  271. package/lib/SandboxClient.d.ts +748 -0
  272. package/lib/SandboxClient.d.ts.map +1 -0
  273. package/lib/SandboxClient.js +710 -0
  274. package/lib/SandboxClient.js.map +1 -0
  275. package/lib/ScaleFunction.d.ts +54 -0
  276. package/lib/ScaleFunction.d.ts.map +1 -0
  277. package/lib/ScaleFunction.js +3 -0
  278. package/lib/ScaleFunction.js.map +1 -0
  279. package/lib/ScaleFunctionHttp.d.ts +21 -0
  280. package/lib/ScaleFunctionHttp.d.ts.map +1 -0
  281. package/lib/ScaleFunctionHttp.js +39 -0
  282. package/lib/ScaleFunctionHttp.js.map +1 -0
  283. package/lib/Secret.d.ts +170 -0
  284. package/lib/Secret.d.ts.map +1 -0
  285. package/lib/Secret.js +196 -0
  286. package/lib/Secret.js.map +1 -0
  287. package/lib/Server.d.ts +155 -0
  288. package/lib/Server.d.ts.map +1 -0
  289. package/lib/Server.js +184 -0
  290. package/lib/Server.js.map +1 -0
  291. package/lib/ServiceUser.d.ts +139 -0
  292. package/lib/ServiceUser.d.ts.map +1 -0
  293. package/lib/ServiceUser.js +210 -0
  294. package/lib/ServiceUser.js.map +1 -0
  295. package/lib/Volume.d.ts +170 -0
  296. package/lib/Volume.d.ts.map +1 -0
  297. package/lib/Volume.js +220 -0
  298. package/lib/Volume.js.map +1 -0
  299. package/lib/VolumeClient.d.ts +53 -0
  300. package/lib/VolumeClient.d.ts.map +1 -0
  301. package/lib/VolumeClient.js +22 -0
  302. package/lib/VolumeClient.js.map +1 -0
  303. package/lib/VolumeFiles.d.ts +94 -0
  304. package/lib/VolumeFiles.d.ts.map +1 -0
  305. package/lib/VolumeFiles.js +139 -0
  306. package/lib/VolumeFiles.js.map +1 -0
  307. package/lib/WebhookToken.d.ts +102 -0
  308. package/lib/WebhookToken.d.ts.map +1 -0
  309. package/lib/WebhookToken.js +176 -0
  310. package/lib/WebhookToken.js.map +1 -0
  311. package/lib/WorkspaceSettings.d.ts +70 -0
  312. package/lib/WorkspaceSettings.d.ts.map +1 -0
  313. package/lib/WorkspaceSettings.js +105 -0
  314. package/lib/WorkspaceSettings.js.map +1 -0
  315. package/lib/WriteApp.d.ts +38 -0
  316. package/lib/WriteApp.d.ts.map +1 -0
  317. package/lib/WriteApp.js +3 -0
  318. package/lib/WriteApp.js.map +1 -0
  319. package/lib/WriteAppHttp.d.ts +23 -0
  320. package/lib/WriteAppHttp.d.ts.map +1 -0
  321. package/lib/WriteAppHttp.js +36 -0
  322. package/lib/WriteAppHttp.js.map +1 -0
  323. package/lib/WriteDict.d.ts +39 -0
  324. package/lib/WriteDict.d.ts.map +1 -0
  325. package/lib/WriteDict.js +3 -0
  326. package/lib/WriteDict.js.map +1 -0
  327. package/lib/WriteDictHttp.d.ts +12 -0
  328. package/lib/WriteDictHttp.d.ts.map +1 -0
  329. package/lib/WriteDictHttp.js +23 -0
  330. package/lib/WriteDictHttp.js.map +1 -0
  331. package/lib/WriteQueue.d.ts +39 -0
  332. package/lib/WriteQueue.d.ts.map +1 -0
  333. package/lib/WriteQueue.js +3 -0
  334. package/lib/WriteQueue.js.map +1 -0
  335. package/lib/WriteQueueHttp.d.ts +12 -0
  336. package/lib/WriteQueueHttp.d.ts.map +1 -0
  337. package/lib/WriteQueueHttp.js +23 -0
  338. package/lib/WriteQueueHttp.js.map +1 -0
  339. package/lib/WriteSecret.d.ts +53 -0
  340. package/lib/WriteSecret.d.ts.map +1 -0
  341. package/lib/WriteSecret.js +3 -0
  342. package/lib/WriteSecret.js.map +1 -0
  343. package/lib/WriteSecretHttp.d.ts +23 -0
  344. package/lib/WriteSecretHttp.d.ts.map +1 -0
  345. package/lib/WriteSecretHttp.js +44 -0
  346. package/lib/WriteSecretHttp.js.map +1 -0
  347. package/lib/WriteVolume.d.ts +39 -0
  348. package/lib/WriteVolume.d.ts.map +1 -0
  349. package/lib/WriteVolume.js +3 -0
  350. package/lib/WriteVolume.js.map +1 -0
  351. package/lib/WriteVolumeHttp.d.ts +12 -0
  352. package/lib/WriteVolumeHttp.d.ts.map +1 -0
  353. package/lib/WriteVolumeHttp.js +24 -0
  354. package/lib/WriteVolumeHttp.js.map +1 -0
  355. package/lib/index.d.ts +80 -0
  356. package/lib/index.d.ts.map +1 -0
  357. package/lib/index.js +78 -0
  358. package/lib/index.js.map +1 -0
  359. package/lib/internal/Declared.d.ts +29 -0
  360. package/lib/internal/Declared.d.ts.map +1 -0
  361. package/lib/internal/Declared.js +31 -0
  362. package/lib/internal/Declared.js.map +1 -0
  363. package/lib/internal/Deploy.d.ts +110 -0
  364. package/lib/internal/Deploy.d.ts.map +1 -0
  365. package/lib/internal/Deploy.js +230 -0
  366. package/lib/internal/Deploy.js.map +1 -0
  367. package/lib/internal/Entrypoint.d.ts +52 -0
  368. package/lib/internal/Entrypoint.d.ts.map +1 -0
  369. package/lib/internal/Entrypoint.js +193 -0
  370. package/lib/internal/Entrypoint.js.map +1 -0
  371. package/lib/internal/Rpc.d.ts +17 -0
  372. package/lib/internal/Rpc.d.ts.map +1 -0
  373. package/lib/internal/Rpc.js +49 -0
  374. package/lib/internal/Rpc.js.map +1 -0
  375. package/lib/internal/RpcAuth.d.ts +41 -0
  376. package/lib/internal/RpcAuth.d.ts.map +1 -0
  377. package/lib/internal/RpcAuth.js +100 -0
  378. package/lib/internal/RpcAuth.js.map +1 -0
  379. package/lib/internal/Runtime.d.ts +127 -0
  380. package/lib/internal/Runtime.d.ts.map +1 -0
  381. package/lib/internal/Runtime.js +246 -0
  382. package/lib/internal/Runtime.js.map +1 -0
  383. package/lib/internal/Tags.d.ts +19 -0
  384. package/lib/internal/Tags.d.ts.map +1 -0
  385. package/lib/internal/Tags.js +33 -0
  386. package/lib/internal/Tags.js.map +1 -0
  387. package/lib/internal/TcpConnect.d.ts +12 -0
  388. package/lib/internal/TcpConnect.d.ts.map +1 -0
  389. package/lib/internal/TcpConnect.js +46 -0
  390. package/lib/internal/TcpConnect.js.map +1 -0
  391. package/lib/internal/Transport.d.ts +13 -0
  392. package/lib/internal/Transport.d.ts.map +1 -0
  393. package/lib/internal/Transport.js +16 -0
  394. package/lib/internal/Transport.js.map +1 -0
  395. package/lib/internal.d.ts +71 -0
  396. package/lib/internal.d.ts.map +1 -0
  397. package/lib/internal.js +102 -0
  398. package/lib/internal.js.map +1 -0
  399. package/package.json +59 -3
  400. package/src/App.ts +262 -0
  401. package/src/AuthProvider.ts +295 -0
  402. package/src/BindingHttp.ts +112 -0
  403. package/src/Blob.ts +71 -0
  404. package/src/Bootstrap.ts +52 -0
  405. package/src/Cbor.ts +158 -0
  406. package/src/ConnectSandbox.ts +99 -0
  407. package/src/Container.ts +121 -0
  408. package/src/Contributions.ts +9 -0
  409. package/src/CreateDict.ts +95 -0
  410. package/src/CreateDictHttp.ts +91 -0
  411. package/src/CreateImage.ts +105 -0
  412. package/src/CreateImageHttp.ts +117 -0
  413. package/src/CreateQueue.ts +95 -0
  414. package/src/CreateQueueHttp.ts +91 -0
  415. package/src/CreateSandbox.ts +228 -0
  416. package/src/CreateSandboxHttp.ts +160 -0
  417. package/src/CreateSecret.ts +109 -0
  418. package/src/CreateSecretHttp.ts +83 -0
  419. package/src/CreateVolume.ts +96 -0
  420. package/src/CreateVolumeHttp.ts +93 -0
  421. package/src/Credentials.ts +74 -0
  422. package/src/Dict.ts +245 -0
  423. package/src/DictClient.ts +166 -0
  424. package/src/Domain.ts +147 -0
  425. package/src/Endpoint.ts +406 -0
  426. package/src/Environment.ts +373 -0
  427. package/src/FetchServer.ts +126 -0
  428. package/src/FetchServerHttp.ts +110 -0
  429. package/src/Function.ts +397 -0
  430. package/src/FunctionAutoscaler.ts +210 -0
  431. package/src/FunctionTarget.ts +181 -0
  432. package/src/Image.ts +281 -0
  433. package/src/ImageBuild.ts +228 -0
  434. package/src/ImagePlan.ts +78 -0
  435. package/src/InferEnv.ts +27 -0
  436. package/src/InvokeFunction.ts +211 -0
  437. package/src/InvokeFunctionHttp.ts +316 -0
  438. package/src/ModalBindings.ts +310 -0
  439. package/src/MountBucket.ts +137 -0
  440. package/src/MountVolume.ts +113 -0
  441. package/src/Mounts.ts +230 -0
  442. package/src/PersistentSandbox.ts +142 -0
  443. package/src/Providers.ts +100 -0
  444. package/src/Proxy.ts +191 -0
  445. package/src/Queue.ts +258 -0
  446. package/src/QueueClient.ts +149 -0
  447. package/src/ReadApp.ts +101 -0
  448. package/src/ReadAppHttp.ts +142 -0
  449. package/src/ReadDict.ts +44 -0
  450. package/src/ReadDictHttp.ts +27 -0
  451. package/src/ReadFunction.ts +107 -0
  452. package/src/ReadFunctionHttp.ts +91 -0
  453. package/src/ReadQueue.ts +44 -0
  454. package/src/ReadQueueHttp.ts +27 -0
  455. package/src/ReadServer.ts +98 -0
  456. package/src/ReadServerHttp.ts +101 -0
  457. package/src/ReadVolume.ts +45 -0
  458. package/src/ReadVolumeHttp.ts +28 -0
  459. package/src/ReadWriteDict.ts +44 -0
  460. package/src/ReadWriteDictHttp.ts +27 -0
  461. package/src/ReadWriteQueue.ts +45 -0
  462. package/src/ReadWriteQueueHttp.ts +27 -0
  463. package/src/ReadWriteVolume.ts +45 -0
  464. package/src/ReadWriteVolumeHttp.ts +28 -0
  465. package/src/RuntimeOwner.ts +111 -0
  466. package/src/Sandbox.ts +402 -0
  467. package/src/SandboxClient.ts +1404 -0
  468. package/src/ScaleFunction.ts +70 -0
  469. package/src/ScaleFunctionHttp.ts +54 -0
  470. package/src/Secret.ts +296 -0
  471. package/src/Server.ts +285 -0
  472. package/src/ServiceUser.ts +295 -0
  473. package/src/Volume.ts +320 -0
  474. package/src/VolumeClient.ts +108 -0
  475. package/src/VolumeFiles.ts +194 -0
  476. package/src/WebhookToken.ts +230 -0
  477. package/src/WorkspaceSettings.ts +160 -0
  478. package/src/WriteApp.ts +43 -0
  479. package/src/WriteAppHttp.ts +51 -0
  480. package/src/WriteDict.ts +44 -0
  481. package/src/WriteDictHttp.ts +27 -0
  482. package/src/WriteQueue.ts +44 -0
  483. package/src/WriteQueueHttp.ts +27 -0
  484. package/src/WriteSecret.ts +60 -0
  485. package/src/WriteSecretHttp.ts +56 -0
  486. package/src/WriteVolume.ts +44 -0
  487. package/src/WriteVolumeHttp.ts +28 -0
  488. package/src/globals.d.ts +2 -0
  489. package/src/index.ts +85 -0
  490. package/src/internal/Declared.ts +80 -0
  491. package/src/internal/Deploy.ts +309 -0
  492. package/src/internal/Entrypoint.ts +198 -0
  493. package/src/internal/Rpc.ts +59 -0
  494. package/src/internal/RpcAuth.ts +132 -0
  495. package/src/internal/Runtime.ts +386 -0
  496. package/src/internal/Tags.ts +44 -0
  497. package/src/internal/TcpConnect.ts +78 -0
  498. package/src/internal/Transport.ts +25 -0
  499. package/src/internal.ts +147 -0
@@ -0,0 +1,1404 @@
1
+ import * as persistent from "@distilled.cloud/modal/persistent";
2
+ import * as sandbox from "@distilled.cloud/modal/sandbox";
3
+ import * as router from "@distilled.cloud/modal/task_command_router";
4
+ import type { RuntimeContext } from "alchemy/RuntimeContext";
5
+ import * as Sandbox from "alchemy/Sandbox";
6
+ import * as SecureRandom from "effect/crypto/SecureRandom";
7
+ import * as Data from "effect/Data";
8
+ import * as DateTime from "effect/DateTime";
9
+ import * as Effect from "effect/Effect";
10
+ import * as HttpClient from "effect/http/HttpClient";
11
+ import type * as HttpClientError from "effect/http/HttpClientError";
12
+ import * as HttpClientRequest from "effect/http/HttpClientRequest";
13
+ import type * as HttpClientResponse from "effect/http/HttpClientResponse";
14
+ import type * as HttpServerRequest from "effect/http/HttpServerRequest";
15
+ import type * as HttpServerResponse from "effect/http/HttpServerResponse";
16
+ import * as Schedule from "effect/Schedule";
17
+ import type * as Scope from "effect/Scope";
18
+ import type * as Socket from "effect/socket/Socket";
19
+ import * as Stream from "effect/Stream";
20
+ import { buildImage, gpuConfig, ImageBuildOps, type ImageBuildSpec } from "./ImageBuild.ts";
21
+ import { environmentOf, type ErrorOf, operations, seconds } from "./internal.ts";
22
+ import { connectTcp } from "./internal/TcpConnect.ts";
23
+ import { streamRouter, withRouter } from "./ModalBindings.ts";
24
+ import { type ModalMounts, modalMountsOf, type SandboxMount } from "./Mounts.ts";
25
+
26
+ /** A sandbox ended (or never became ready) before the call could finish. */
27
+ export class SandboxNotRunning extends Data.TaggedError("Modal.SandboxNotRunning")<{
28
+ readonly sandboxId: string;
29
+ readonly message: string;
30
+ }> {}
31
+
32
+ /** A Modal call returned without the field the client needs. */
33
+ export class SandboxUnexpectedResponse extends Data.TaggedError("Modal.SandboxUnexpectedResponse")<{
34
+ readonly message: string;
35
+ }> {}
36
+
37
+ /** A filesystem operation inside the sandbox failed (missing path, permissions). */
38
+ export class SandboxFilesystemError extends Data.TaggedError("Modal.SandboxFilesystemError")<{
39
+ readonly path: string;
40
+ readonly message: string;
41
+ }> {}
42
+
43
+ /** A Modal duration: seconds, or a string like `"10 minutes"`. */
44
+ export type DurationInput = number | string;
45
+
46
+ /** An image a sandbox boots from: a registry ref, or a `Modal.Image` (resolved). */
47
+ export type SandboxImage = string | { readonly imageId: string };
48
+
49
+ /** Where a route sends matching traffic: blocked, through a `Modal.Proxy`, or to a sidecar. */
50
+ export type OutboundRedirect =
51
+ | "block"
52
+ | { readonly proxy: { readonly proxyId: string } }
53
+ | { readonly sidecar: string };
54
+
55
+ /** An inclusive port range, or one port. */
56
+ export type OutboundPorts = ReadonlyArray<
57
+ number | { readonly start: number; readonly end: number }
58
+ >;
59
+
60
+ /** Set headers on decrypted HTTPS requests, with values from a secret. */
61
+ export interface HeaderReplacement {
62
+ /** The secret whose keys the header values reference. Omit for static values. */
63
+ readonly secret?: { readonly secretId: string };
64
+ /** Header name to value; `$KEY` is replaced with the secret's `KEY`, `$$` is a literal `$`. */
65
+ readonly headers: Record<string, string>;
66
+ }
67
+
68
+ /**
69
+ * One route of an {@link OutboundPolicy}. Routes match in order; traffic a
70
+ * route matches without a `redirect` goes to its destination.
71
+ */
72
+ export type OutboundRoute =
73
+ | {
74
+ /** Traffic to these CIDR ranges. */
75
+ readonly ip: {
76
+ readonly cidrs: ReadonlyArray<string>;
77
+ /** @default every port */
78
+ readonly ports?: OutboundPorts;
79
+ readonly transport?: "tcp" | "udp" | "any";
80
+ readonly redirect?: OutboundRedirect;
81
+ };
82
+ }
83
+ | {
84
+ /** TLS connections to these domains (`*.` wildcards allowed), never decrypted. */
85
+ readonly tls: {
86
+ readonly domains: ReadonlyArray<string>;
87
+ /** @default 443 */
88
+ readonly ports?: OutboundPorts;
89
+ readonly redirect?: OutboundRedirect;
90
+ };
91
+ }
92
+ | {
93
+ /** HTTPS requests to these domains, decrypted so headers can be replaced. */
94
+ readonly https: {
95
+ readonly domains: ReadonlyArray<string>;
96
+ /** @default 443 */
97
+ readonly ports?: OutboundPorts;
98
+ readonly headerReplacements?: ReadonlyArray<HeaderReplacement>;
99
+ readonly redirect?: OutboundRedirect;
100
+ };
101
+ }
102
+ | {
103
+ /** Everything no earlier route matched. Must be the last route. */
104
+ readonly any: { readonly redirect?: OutboundRedirect };
105
+ };
106
+
107
+ /**
108
+ * A sandbox's outbound traffic rules, enforced by Modal outside the
109
+ * sandbox. HTTPS routes decrypt traffic with a certificate authority the
110
+ * sandbox trusts, so Modal can add credentials the sandbox never sees.
111
+ */
112
+ export interface OutboundPolicy {
113
+ readonly routes: ReadonlyArray<OutboundRoute>;
114
+ /**
115
+ * Let HTTPS routes decrypt traffic. Fixed at creation.
116
+ * @default true when a route is `https`
117
+ */
118
+ readonly tlsDecryption?: boolean;
119
+ }
120
+
121
+ /** Options for creating a sandbox: Modal's own sandbox parameters. */
122
+ export interface SandboxCreateOptions {
123
+ /** Image to boot. Defaults to the binding's image, else `debian:bookworm-slim`. */
124
+ readonly image?: SandboxImage;
125
+ /**
126
+ * The sandbox's main process. Without one the sandbox idles (`sleep`) and
127
+ * you drive it with {@link SandboxHandle.exec}.
128
+ */
129
+ readonly command?: ReadonlyArray<string>;
130
+ /** Working directory of the main process and of exec'd commands. */
131
+ readonly workdir?: string;
132
+ /** Environment variables for the main process. */
133
+ readonly env?: Record<string, string>;
134
+ /** Secrets whose variables the sandbox sees as environment variables. */
135
+ readonly secrets?: ReadonlyArray<{ readonly secretId: string }>;
136
+ /**
137
+ * Volumes and cloud buckets to mount: volume handles, bucket names, or
138
+ * the references `Modal.MountVolume` / `Modal.MountBucket` return.
139
+ */
140
+ readonly mounts?: ReadonlyArray<SandboxMount>;
141
+ /** CPU cores (fractional allowed). */
142
+ readonly cpu?: number;
143
+ /** Memory in MiB. */
144
+ readonly memory?: number;
145
+ /** GPU: `"T4"`, `"A10G"`, `"A100-80GB"`, `"H100"`, with an optional `":<count>"`. */
146
+ readonly gpu?: string;
147
+ /** Ephemeral disk in MiB. */
148
+ readonly ephemeralDisk?: number;
149
+ /** Most time the sandbox lives. @default 5 minutes (Modal's default); at most 24 hours */
150
+ readonly timeout?: DurationInput;
151
+ /** Terminate after this long without exec'd commands or connections. */
152
+ readonly idleTimeout?: DurationInput;
153
+ /** Name, unique among the app's running sandboxes; find it again with `fromName`. */
154
+ readonly name?: string;
155
+ /** Tags, for `list({ tags })`. */
156
+ readonly tags?: Record<string, string>;
157
+ /** Regions to run in (`"us-east"`, `"eu"`, ...). */
158
+ readonly regions?: ReadonlyArray<string>;
159
+ /** Block all network access. */
160
+ readonly blockNetwork?: boolean;
161
+ /** Allow outbound traffic only to these CIDR ranges (and {@link allowedDomains}). */
162
+ readonly cidrAllowlist?: ReadonlyArray<string>;
163
+ /** Allow outbound traffic only to these domains (and {@link cidrAllowlist}). */
164
+ readonly allowedDomains?: ReadonlyArray<string>;
165
+ /** Allow inbound connections (tunnels) only from these CIDR ranges. */
166
+ readonly inboundCidrAllowlist?: ReadonlyArray<string>;
167
+ /** Outbound header injection: secrets the sandbox never sees. */
168
+ readonly outboundPolicy?: OutboundPolicy;
169
+ /** Route outbound traffic through a `Modal.Proxy`'s static IPs. */
170
+ readonly proxy?: { readonly proxyId: string };
171
+ /**
172
+ * Give the sandbox a `MODAL_IDENTITY_TOKEN` environment variable: an OIDC
173
+ * token Modal signs, naming the workspace, environment, app and sandbox.
174
+ * A cloud that trusts Modal's issuer (`https://oidc.modal.com`) exchanges
175
+ * it for short-lived credentials, so no long-lived key enters the sandbox.
176
+ */
177
+ readonly includeOidcIdentityToken?: boolean;
178
+ /**
179
+ * Serve the sandbox an X.509 certificate that names it (an X.509-SVID),
180
+ * over the SPIFFE Workload API socket at `SPIFFE_ENDPOINT_SOCKET`. A
181
+ * cloud that trusts Modal's certificate authority (AWS IAM Roles
182
+ * Anywhere) exchanges it for short-lived credentials.
183
+ */
184
+ readonly includeX509Svid?: boolean;
185
+ /** Ports reachable through TLS tunnels (`sandbox.getTcpPort(port)`). */
186
+ readonly encryptedPorts?: ReadonlyArray<number>;
187
+ /** Ports reachable through plain TCP tunnels. */
188
+ readonly unencryptedPorts?: ReadonlyArray<number>;
189
+ /** Ports reachable through HTTP/2 tunnels. */
190
+ readonly h2Ports?: ReadonlyArray<number>;
191
+ /** Allow memory snapshots of this sandbox (`snapshotMemory`). */
192
+ readonly enableSnapshot?: boolean;
193
+ /** Serve tunnels on a custom domain registered with Modal. */
194
+ readonly customDomain?: string;
195
+ }
196
+
197
+ /** What a finished command printed, and its exit code. */
198
+ export interface ExecResult {
199
+ readonly exitCode: number;
200
+ readonly stdout: string;
201
+ readonly stderr: string;
202
+ }
203
+
204
+ /** A command: a shell string (run with `sh -c`), or an argument vector (no shell). */
205
+ export type Command = Sandbox.Command;
206
+
207
+ /** Options for {@link SandboxHandleOf.exec}. */
208
+ export interface ExecOptions {
209
+ /** Working directory. @default the sandbox's `workdir` */
210
+ readonly cwd?: string;
211
+ /** Extra environment variables for this command. */
212
+ readonly env?: Record<string, string>;
213
+ /** Secrets whose variables this command sees. */
214
+ readonly secrets?: ReadonlyArray<{ readonly secretId: string }>;
215
+ /** Kill the command after this long. */
216
+ readonly timeout?: DurationInput;
217
+ /** Bytes (or text) written to the command's stdin, then closed. */
218
+ readonly stdin?: string | Uint8Array;
219
+ }
220
+
221
+ /** A filesystem entry inside a sandbox. */
222
+ export interface SandboxFileEntry {
223
+ readonly path: string;
224
+ readonly type: "file" | "directory" | "symlink" | "other";
225
+ }
226
+
227
+ /** What `files.stat` reports about a path. */
228
+ export interface SandboxFileStat extends SandboxFileEntry {
229
+ /** Size in bytes. */
230
+ readonly size: number;
231
+ /** Last modification time. */
232
+ readonly modifiedAt: DateTime.Utc;
233
+ }
234
+
235
+ /** A tunnel to a port inside the sandbox. */
236
+ export interface SandboxTunnel {
237
+ /** `https://…` (or `tcp://…` for unencrypted tunnels). */
238
+ readonly url: string;
239
+ readonly host: string;
240
+ readonly port: number;
241
+ }
242
+
243
+ /** The error of one of {@link SandboxOps}. */
244
+ export type SandboxOpError<K extends keyof SandboxOps> = ErrorOf<SandboxOps[K]>;
245
+ type RouterOpError<K extends keyof typeof routerOps> =
246
+ Effect.Success<ReturnType<(typeof routerOps)[K]>> extends (...args: any[]) => infer Out
247
+ ? Out extends Effect.Effect<any, infer E, any>
248
+ ? E
249
+ : Out extends Stream.Stream<any, infer E, any>
250
+ ? E
251
+ : never
252
+ : never;
253
+
254
+ /** Reaching a sandbox's task: it ended, or Modal's task lookup failed. */
255
+ export type SandboxTaskError =
256
+ | SandboxNotRunning
257
+ | SandboxUnexpectedResponse
258
+ | SandboxOpError<"getTaskId">
259
+ | SandboxOpError<"getCommandRouterAccess">;
260
+
261
+ /** Running a command in a sandbox. */
262
+ export type SandboxExecError =
263
+ | SandboxTaskError
264
+ | RouterOpError<"execStart" | "execStdinWrite" | "execStdioRead" | "execWait">;
265
+
266
+ /** A started process: running it, following its output, writing its stdin, signalling it. */
267
+ export type SandboxProcessError = SandboxExecError | RouterOpError<"execStdioFollow">;
268
+
269
+ /** Modal's task command router has no call to resize a running PTY. */
270
+ export class SandboxPtyResizeUnsupported extends Data.TaggedError(
271
+ "Modal.SandboxPtyResizeUnsupported",
272
+ )<{
273
+ readonly message: string;
274
+ }> {}
275
+
276
+ /** A terminal session's errors. */
277
+ export type SandboxPtyError = SandboxProcessError | SandboxPtyResizeUnsupported;
278
+
279
+ /** Options for `pty.open`: the terminal size, and what runs in it. */
280
+ export interface SandboxPtyOptions extends Sandbox.PtyOptions {
281
+ /** Secrets whose variables the session sees. */
282
+ readonly secrets?: ReadonlyArray<{ readonly secretId: string }>;
283
+ }
284
+
285
+ /** A file operation inside a sandbox (it runs as a command). */
286
+ export type SandboxFilesError = SandboxExecError | SandboxFilesystemError;
287
+
288
+ /** Taking a snapshot or changing a running sandbox through its command router. */
289
+ export type SandboxRouterError<K extends keyof typeof routerOps> =
290
+ | SandboxTaskError
291
+ | RouterOpError<K>;
292
+
293
+ /** The files inside a sandbox, read and written with commands its image has (`sh`, `base64`, `stat`). */
294
+ export interface SandboxFilesOf<R> {
295
+ /** Read a file. */
296
+ read(path: string): Effect.Effect<Uint8Array, SandboxFilesError, R>;
297
+ /** Read a UTF-8 file. */
298
+ readText(path: string): Effect.Effect<string, SandboxFilesError, R>;
299
+ /** Write (create or truncate) a file, creating its directory. */
300
+ write(path: string, data: string | Uint8Array): Effect.Effect<void, SandboxFilesError, R>;
301
+ /** List a directory. */
302
+ list(path: string): Effect.Effect<ReadonlyArray<SandboxFileEntry>, SandboxFilesError, R>;
303
+ /** A path's type, size and modification time. */
304
+ stat(path: string): Effect.Effect<SandboxFileStat, SandboxFilesError, R>;
305
+ /** Remove a file, or a directory with `recursive`. */
306
+ remove(
307
+ path: string,
308
+ options?: { readonly recursive?: boolean },
309
+ ): Effect.Effect<void, SandboxFilesError, R>;
310
+ /** Create a directory and its parents. */
311
+ makeDir(path: string): Effect.Effect<void, SandboxFilesError, R>;
312
+ }
313
+
314
+ /** A running (or finished) Modal sandbox. Every method calls Modal's API. */
315
+ export interface SandboxHandleOf<R> {
316
+ readonly sandboxId: string;
317
+ /**
318
+ * Run a command and wait for it. A string runs with `sh -c`; an argument
319
+ * vector runs without a shell. A non-zero exit code is returned, not failed.
320
+ */
321
+ exec(command: Command, options?: ExecOptions): Effect.Effect<ExecResult, SandboxExecError, R>;
322
+ /**
323
+ * Start a command and follow it: its output streams as it runs, `write`
324
+ * feeds its stdin, `kill` signals it. Closing the scope detaches; the
325
+ * command keeps running.
326
+ */
327
+ start(
328
+ command: Command,
329
+ options?: ExecOptions,
330
+ ): Effect.Effect<Sandbox.Process<SandboxProcessError>, SandboxProcessError, R | Scope.Scope>;
331
+ /**
332
+ * Terminals: `open` starts a login shell (or `command`) on a PTY of the
333
+ * given size. Modal can't resize a running PTY, so `resize` fails with
334
+ * {@link SandboxPtyResizeUnsupported}.
335
+ */
336
+ readonly pty: Sandbox.Pty<SandboxPtyError, SandboxPtyOptions>;
337
+ /** The sandbox's files. */
338
+ readonly files: SandboxFilesOf<R>;
339
+ /** Tunnels to the sandbox's open ports, by container port. */
340
+ tunnels(
341
+ timeout?: DurationInput,
342
+ ): Effect.Effect<Record<number, SandboxTunnel>, SandboxOpError<"getTunnels">, R>;
343
+ /**
344
+ * A token-authenticated URL for one port, for a browser or client that
345
+ * shouldn't hold Modal credentials.
346
+ */
347
+ connectToken(options?: {
348
+ readonly port?: number;
349
+ readonly userMetadata?: string;
350
+ }): Effect.Effect<
351
+ { readonly url: string; readonly token: string },
352
+ SandboxOpError<"createConnectToken"> | SandboxUnexpectedResponse,
353
+ R
354
+ >;
355
+ /** Capture the filesystem as a new image (usable as `image` later). */
356
+ snapshotFilesystem(
357
+ timeout?: DurationInput,
358
+ ): Effect.Effect<{ readonly imageId: string }, SandboxRouterError<"snapshotFilesystem">, R>;
359
+ /** Capture memory and filesystem (needs `enableSnapshot`); restore with `restore`. */
360
+ snapshotMemory(): Effect.Effect<
361
+ { readonly snapshotId: string },
362
+ SandboxRouterError<"snapshotMemory">,
363
+ R
364
+ >;
365
+ /** Capture one directory as an image (mount it into another sandbox with `mountDirectory`). */
366
+ snapshotDirectory(
367
+ path: string,
368
+ ): Effect.Effect<{ readonly imageId: string }, SandboxRouterError<"snapshotDirectory">, R>;
369
+ /** Mount a directory snapshot at `path`. */
370
+ mountDirectory(
371
+ path: string,
372
+ snapshot: { readonly imageId: string },
373
+ ): Effect.Effect<void, SandboxRouterError<"mountDirectory">, R>;
374
+ /**
375
+ * Replace the sandbox's outbound network policy while it runs. Connections
376
+ * the new policy no longer allows are closed. `{}` opens the network.
377
+ */
378
+ setNetworkAccess(access: {
379
+ readonly blockNetwork?: boolean;
380
+ readonly cidrAllowlist?: ReadonlyArray<string>;
381
+ readonly allowedDomains?: ReadonlyArray<string>;
382
+ }): Effect.Effect<void, SandboxRouterError<"setNetworkAccess">, R>;
383
+ /** Replace the outbound header injection rules while it runs. */
384
+ setOutboundPolicy(
385
+ policy: OutboundPolicy,
386
+ ): Effect.Effect<void, SandboxOpError<"setOutboundPolicy">, R>;
387
+ /** Replace the sandbox's tags. */
388
+ setTags(tags: Record<string, string>): Effect.Effect<void, SandboxOpError<"setTags">, R>;
389
+ /** The sandbox's tags. */
390
+ getTags(): Effect.Effect<Record<string, string>, SandboxOpError<"getTags">, R>;
391
+ /** Name the sandbox (unique among its app's running sandboxes) so `fromName` finds it. */
392
+ setName(name: string): Effect.Effect<void, SandboxOpError<"setName">, R>;
393
+ /** CPU, memory and GPU used so far (core-seconds, GiB-seconds, GPU-seconds). */
394
+ resourceUsage(): Effect.Effect<
395
+ { readonly cpuSeconds: number; readonly memoryGibSeconds: number; readonly gpuSeconds: number },
396
+ SandboxOpError<"getResourceUsage">,
397
+ R
398
+ >;
399
+ /**
400
+ * The main process's stdout so far: what the sandbox's command router sends
401
+ * within `timeout` (default 5 seconds), or all of it once the program has
402
+ * exited.
403
+ */
404
+ logs(options?: {
405
+ readonly timeout?: DurationInput;
406
+ }): Effect.Effect<string, SandboxRouterError<"mainStdio">, R>;
407
+ /** Wait for the sandbox to finish; `undefined` exit code while still running. */
408
+ wait(
409
+ timeout?: DurationInput,
410
+ ): Effect.Effect<
411
+ { readonly exitCode?: number; readonly status: string },
412
+ SandboxOpError<"wait">,
413
+ R
414
+ >;
415
+ /** Terminate the sandbox (no-op when it already finished). */
416
+ terminate(): Effect.Effect<
417
+ void,
418
+ Exclude<SandboxOpError<"terminate">, { readonly _tag: "SandboxNotFound" }>,
419
+ R
420
+ >;
421
+ /**
422
+ * A port inside the sandbox, reached through its tunnel: `fetch` sends a
423
+ * client request there, or proxies a server request a handler received;
424
+ * `connect` opens a TCP socket to it. The port needs a tunnel
425
+ * (`encryptedPorts`, `unencryptedPorts` or `h2Ports`); without a port
426
+ * number, the `Modal.Sandbox` program's.
427
+ */
428
+ getTcpPort(port?: number): Effect.Effect<SandboxPortOf<R>, SandboxPortError, R>;
429
+ }
430
+
431
+ /** Looking up a sandbox port's tunnel. */
432
+ export type SandboxPortError = SandboxOpError<"getTunnels"> | SandboxUnexpectedResponse;
433
+
434
+ /**
435
+ * A port inside a sandbox, with the tunnel Modal opened to it. `fetch` keeps
436
+ * the request's method, path, query, headers and body, and sends it to the
437
+ * tunnel; given the server request a handler received, it proxies it and
438
+ * returns the server response.
439
+ */
440
+ export interface SandboxPortOf<R> extends SandboxTunnel {
441
+ fetch(
442
+ request: HttpClientRequest.HttpClientRequest,
443
+ ): Effect.Effect<
444
+ HttpClientResponse.HttpClientResponse,
445
+ SandboxPortError | HttpClientError.HttpClientError,
446
+ R
447
+ >;
448
+ fetch(
449
+ request: HttpServerRequest.HttpServerRequest,
450
+ ): Effect.Effect<
451
+ HttpServerResponse.HttpServerResponse,
452
+ SandboxPortError | HttpClientError.HttpClientError,
453
+ R
454
+ >;
455
+ /**
456
+ * A TCP socket to the port through its tunnel: TLS for an encrypted tunnel
457
+ * (`encryptedPorts`), plain TCP for an unencrypted one (`unencryptedPorts`).
458
+ * It connects when run.
459
+ */
460
+ connect(): Effect.Effect<Socket.Socket>;
461
+ }
462
+
463
+ /** A sandbox port as bindings return it. */
464
+ export type SandboxPort = SandboxPortOf<RuntimeContext>;
465
+
466
+ /**
467
+ * A sandbox handle as bindings return it: calls run inside a deployed
468
+ * runtime. It has the shared `alchemy/Sandbox` shape plus Modal's own calls.
469
+ */
470
+ export interface SandboxHandle
471
+ extends
472
+ SandboxHandleOf<RuntimeContext>,
473
+ Sandbox.SandboxExec<SandboxExecError, ExecResult, ExecOptions>,
474
+ Sandbox.SandboxProcesses<SandboxProcessError, ExecOptions>,
475
+ Sandbox.SandboxFiles<SandboxFilesError, SandboxFileEntry>,
476
+ Sandbox.SandboxPty<SandboxPtyError, SandboxPtyOptions>,
477
+ Sandbox.SandboxPorts<SandboxPortError, SandboxPort> {
478
+ readonly files: SandboxFilesOf<RuntimeContext>;
479
+ getTcpPort(port?: number): Effect.Effect<SandboxPort, SandboxPortError, RuntimeContext>;
480
+ }
481
+
482
+ /** A sandbox handle whose calls need Modal credentials and an HTTP client. */
483
+ export type RawSandboxHandle = SandboxHandleOf<never>;
484
+
485
+ /** Where a sandbox client creates sandboxes. */
486
+ export interface SandboxTarget {
487
+ readonly appId: string;
488
+ /** Environment of the app (for name lookups and tag updates). */
489
+ readonly environment?: string;
490
+ /** Name of the app (for name lookups). */
491
+ readonly appName?: string;
492
+ /** Image used when `create` names none. */
493
+ readonly imageId?: string;
494
+ /** A `Modal.Sandbox` program: the secret holding its env, its command, its port and mounts. */
495
+ readonly program?: {
496
+ readonly secretId: string;
497
+ readonly command: ReadonlyArray<string>;
498
+ readonly port: number;
499
+ readonly mounts?: ModalMounts;
500
+ };
501
+ }
502
+
503
+ /** The text of a command's output stream, from its router read chunks. */
504
+ const streamText = (chunks: ReadonlyArray<{ readonly data?: string }>) =>
505
+ Buffer.concat(chunks.map((chunk) => Buffer.from(chunk.data ?? "", "base64"))).toString("utf8");
506
+
507
+ const decodeBase64 = (data: string) =>
508
+ Effect.sync(() => new Uint8Array(Buffer.from(data, "base64")));
509
+ const encodeBase64 = (data: string | Uint8Array) =>
510
+ Effect.sync(() =>
511
+ typeof data === "string"
512
+ ? Buffer.from(data, "utf8").toString("base64")
513
+ : Buffer.from(data).toString("base64"),
514
+ );
515
+
516
+ const tunnelTypeOf = (h2: boolean): sandbox.TunnelType | undefined =>
517
+ h2 ? "TUNNEL_TYPE_H2" : undefined;
518
+
519
+ /** Modal's `Sandbox` definition for {@link SandboxCreateOptions}. */
520
+ const definitionOf = (
521
+ imageId: string,
522
+ options: SandboxCreateOptions,
523
+ mounts: ModalMounts,
524
+ ): sandbox.Sandbox => ({
525
+ imageId,
526
+ entrypointArgs: options.command ? [...options.command] : ["sleep", "infinity"],
527
+ workdir: options.workdir,
528
+ environmentVariables: options.env,
529
+ secretIds: options.secrets?.map((s) => s.secretId),
530
+ volumeMounts: mounts.volumeMounts,
531
+ cloudBucketMounts: mounts.cloudBucketMounts,
532
+ resources: {
533
+ milliCpu: options.cpu !== undefined ? Math.round(options.cpu * 1000) : undefined,
534
+ memoryMb: options.memory,
535
+ gpuConfig: gpuConfig(options.gpu),
536
+ ephemeralDiskMb: options.ephemeralDisk,
537
+ },
538
+ timeoutSecs: seconds(options.timeout),
539
+ idleTimeoutSecs: seconds(options.idleTimeout),
540
+ name: options.name,
541
+ schedulerPlacement: options.regions ? { regions: [...options.regions] } : undefined,
542
+ blockNetwork: options.blockNetwork,
543
+ networkAccess: networkAccessOf(options),
544
+ inboundCidrAllowlist: options.inboundCidrAllowlist
545
+ ? [...options.inboundCidrAllowlist]
546
+ : undefined,
547
+ outboundPolicy: options.outboundPolicy ? outboundPolicyOf(options.outboundPolicy) : undefined,
548
+ proxyId: options.proxy?.proxyId,
549
+ includeOidcIdentityToken: options.includeOidcIdentityToken,
550
+ includeX509Svid: options.includeX509Svid,
551
+ openPorts: {
552
+ ports: [
553
+ ...(options.encryptedPorts ?? []).map((port) => ({ port })),
554
+ ...(options.unencryptedPorts ?? []).map((port) => ({ port, unencrypted: true })),
555
+ ...(options.h2Ports ?? []).map((port) => ({ port, tunnelType: tunnelTypeOf(true) })),
556
+ ],
557
+ },
558
+ enableSnapshot: options.enableSnapshot,
559
+ customDomain: options.customDomain,
560
+ });
561
+
562
+ const networkAccessOf = (options: {
563
+ readonly blockNetwork?: boolean;
564
+ readonly cidrAllowlist?: ReadonlyArray<string>;
565
+ readonly allowedDomains?: ReadonlyArray<string>;
566
+ }): sandbox.NetworkAccess | undefined =>
567
+ options.blockNetwork
568
+ ? { networkAccessType: "BLOCKED" }
569
+ : options.cidrAllowlist !== undefined || options.allowedDomains !== undefined
570
+ ? {
571
+ networkAccessType: "ALLOWLIST",
572
+ allowedCidrs: options.cidrAllowlist ? [...options.cidrAllowlist] : [],
573
+ allowedDomains: options.allowedDomains ? [...options.allowedDomains] : [],
574
+ }
575
+ : undefined;
576
+
577
+ /**
578
+ * The policy for a sandbox that is already running. Modal's task router
579
+ * accepts only an allow-list here: an empty one blocks everything, and
580
+ * `0.0.0.0/0` opens the network.
581
+ */
582
+ const runningNetworkAccessOf = (access: {
583
+ readonly blockNetwork?: boolean;
584
+ readonly cidrAllowlist?: ReadonlyArray<string>;
585
+ readonly allowedDomains?: ReadonlyArray<string>;
586
+ }): sandbox.NetworkAccess => {
587
+ const open = access.cidrAllowlist === undefined && access.allowedDomains === undefined;
588
+ return {
589
+ networkAccessType: "ALLOWLIST",
590
+ allowedCidrs: access.blockNetwork
591
+ ? []
592
+ : open
593
+ ? ["0.0.0.0/0"]
594
+ : [...(access.cidrAllowlist ?? [])],
595
+ allowedDomains: access.blockNetwork ? [] : [...(access.allowedDomains ?? [])],
596
+ };
597
+ };
598
+
599
+ const portsOf = (ports: OutboundPorts | undefined) =>
600
+ ports?.map((port) => (typeof port === "number" ? { start: port, end: port } : port));
601
+
602
+ const transportOf = (transport: "tcp" | "udp" | "any" | undefined) =>
603
+ transport === undefined ? undefined : (`TRANSPORT_${transport.toUpperCase()}` as const);
604
+
605
+ const redirectOf = (
606
+ redirect: OutboundRedirect | undefined,
607
+ ): sandbox.OutboundPolicyRedirect | undefined =>
608
+ redirect === undefined
609
+ ? undefined
610
+ : redirect === "block"
611
+ ? { block: {} }
612
+ : "proxy" in redirect
613
+ ? { viaProxy: { proxyId: redirect.proxy.proxyId } }
614
+ : { viaSidecar: { sidecarName: redirect.sidecar } };
615
+
616
+ const outboundPolicyOf = (policy: OutboundPolicy): sandbox.OutboundPolicy => ({
617
+ tlsDecryption: {
618
+ allowed: policy.tlsDecryption ?? policy.routes.some((route) => "https" in route),
619
+ },
620
+ routes: policy.routes.map((route): sandbox.OutboundPolicyRoute => {
621
+ if ("ip" in route) {
622
+ return {
623
+ ip: {
624
+ cidrs: [...route.ip.cidrs],
625
+ ports: portsOf(route.ip.ports),
626
+ transport: transportOf(route.ip.transport),
627
+ redirect: redirectOf(route.ip.redirect),
628
+ },
629
+ };
630
+ }
631
+ if ("tls" in route) {
632
+ return {
633
+ tls: {
634
+ domains: [...route.tls.domains],
635
+ ports: portsOf(route.tls.ports),
636
+ redirect: redirectOf(route.tls.redirect),
637
+ },
638
+ };
639
+ }
640
+ if ("https" in route) {
641
+ return {
642
+ https: {
643
+ domains: [...route.https.domains],
644
+ ports: portsOf(route.https.ports),
645
+ headerReplacements: route.https.headerReplacements?.map((r) => ({
646
+ secretId: r.secret?.secretId,
647
+ headers: r.headers,
648
+ })),
649
+ redirect: redirectOf(route.https.redirect),
650
+ },
651
+ };
652
+ }
653
+ return { any: { redirect: redirectOf(route.any.redirect) } };
654
+ }),
655
+ });
656
+
657
+ /**
658
+ * The operations sandbox clients call, resolved once by
659
+ * `ModalBindings.withCredentials` (or at deploy, with the deployer's).
660
+ */
661
+ export const SandboxOps = operations({
662
+ create: sandbox.sandboxCreateV2,
663
+ getFromName: sandbox.sandboxGetFromNameV2,
664
+ list: sandbox.sandboxListV2,
665
+ restore: sandbox.sandboxRestoreV2,
666
+ getTaskId: sandbox.sandboxGetTaskIdV2,
667
+ getCommandRouterAccess: sandbox.sandboxGetCommandRouterAccess,
668
+ getTunnels: sandbox.sandboxGetTunnelsV2,
669
+ createConnectToken: sandbox.sandboxCreateConnectTokenV2,
670
+ setOutboundPolicy: sandbox.sandboxSetOutboundPolicyV2,
671
+ setTags: sandbox.sandboxTagsSetV2,
672
+ getTags: sandbox.sandboxTagsGetV2,
673
+ setName: sandbox.sandboxSetName,
674
+ getResourceUsage: sandbox.sandboxGetResourceUsage,
675
+ wait: sandbox.sandboxWaitV2,
676
+ terminate: sandbox.sandboxTerminateV2,
677
+ usage: sandbox.sandboxGetTimeRangeStats,
678
+ createPersistent: persistent.createPersistentSandbox,
679
+ getPersistent: persistent.getPersistentSandbox,
680
+ resumePersistent: persistent.resumePersistentSandbox,
681
+ terminatePersistent: persistent.terminatePersistentSandbox,
682
+ images: ImageBuildOps,
683
+ http: HttpClient.HttpClient,
684
+ });
685
+
686
+ export type SandboxOps = Effect.Success<typeof SandboxOps>;
687
+
688
+ /** Task command router operations, resolved per sandbox with its router access. */
689
+ const routerOps = {
690
+ execStart: withRouter(router.taskExecStart),
691
+ execStdinWrite: withRouter(router.taskExecStdinWrite),
692
+ execStdioRead: withRouter(router.taskExecStdioRead),
693
+ execWait: withRouter(router.taskExecWait),
694
+ // A running command's output, each chunk as the router sends it; the
695
+ // stream ends when the command closes the descriptor.
696
+ execStdioFollow: streamRouter(router.taskExecStdioRead),
697
+ snapshotFilesystem: withRouter(router.taskSnapshotFilesystem),
698
+ snapshotMemory: withRouter(router.taskSnapshotMemory),
699
+ snapshotDirectory: withRouter(router.taskSnapshotDirectory),
700
+ mountDirectory: withRouter(router.taskMountDirectory),
701
+ setNetworkAccess: withRouter(router.taskSetNetworkAccess),
702
+ // The sandbox's own program's output (V2 sandboxes serve it here, not
703
+ // through the control plane's sandboxGetLogs).
704
+ mainStdio: streamRouter(router.sandboxStdioReadV2),
705
+ };
706
+
707
+ /**
708
+ * The image to boot: a `Modal.Image`'s id, or a registry ref built (and
709
+ * cached by Modal) into an image in the target app.
710
+ */
711
+ export const resolveSandboxImage = Effect.fn(function* (
712
+ ops: SandboxOps,
713
+ target: SandboxTarget,
714
+ image: SandboxImage | undefined,
715
+ ) {
716
+ if (image !== undefined && typeof image !== "string") return image.imageId;
717
+ if (image === undefined && target.imageId !== undefined) return target.imageId;
718
+ const spec: ImageBuildSpec = { dockerTag: image ?? "debian:bookworm-slim", commands: [] };
719
+ return yield* buildImage(ops.images, target.appId, spec);
720
+ });
721
+
722
+ /** The handle for an existing sandbox id. */
723
+ export const sandboxHandle = (
724
+ ops: SandboxOps,
725
+ sandboxId: string,
726
+ target: Pick<SandboxTarget, "environment" | "program">,
727
+ ): RawSandboxHandle => {
728
+ let cachedTaskId: string | undefined;
729
+ const taskId = Effect.gen(function* () {
730
+ if (cachedTaskId !== undefined) return cachedTaskId;
731
+ for (let attempt = 0; attempt < 10; attempt++) {
732
+ const response = yield* ops.getTaskId({
733
+ sandboxId,
734
+ timeout: 55,
735
+ waitUntilReady: true,
736
+ });
737
+ if (response.taskId) {
738
+ cachedTaskId = response.taskId;
739
+ return response.taskId;
740
+ }
741
+ if (
742
+ response.taskResult?.status &&
743
+ response.taskResult.status !== "GENERIC_STATUS_UNSPECIFIED"
744
+ ) {
745
+ return yield* new SandboxNotRunning({
746
+ sandboxId,
747
+ message: `Sandbox ${sandboxId} finished with ${response.taskResult.status}`,
748
+ });
749
+ }
750
+ }
751
+ return yield* new SandboxNotRunning({
752
+ sandboxId,
753
+ message: `Sandbox ${sandboxId} wasn't scheduled within 9 minutes`,
754
+ });
755
+ });
756
+
757
+ const viaRouter = <Op, A, E>(
758
+ resolve: (access: { readonly url: string; readonly jwt: string }) => Effect.Effect<Op>,
759
+ f: (op: Op, taskId: string) => Effect.Effect<A, E>,
760
+ ) =>
761
+ Effect.gen(function* () {
762
+ const id = yield* taskId;
763
+ const access = yield* ops.getCommandRouterAccess({ taskId: id });
764
+ if (!access.url || !access.jwt) {
765
+ return yield* new SandboxUnexpectedResponse({
766
+ message: `Modal returned no command router for sandbox ${sandboxId}`,
767
+ });
768
+ }
769
+ const op = yield* resolve({ url: access.url, jwt: access.jwt });
770
+ return yield* f(op, id);
771
+ });
772
+
773
+ /**
774
+ * Run a filesystem command with `sh` (every image Modal boots has one) and
775
+ * fail with {@link SandboxFilesystemError} on a non-zero exit.
776
+ */
777
+ const fsExec = (path: string, script: string, stdin?: string | Uint8Array) =>
778
+ Effect.flatMap(
779
+ exec(["sh", "-c", script, "sh", path], stdin === undefined ? {} : { stdin }),
780
+ (result) =>
781
+ result.exitCode === 0
782
+ ? Effect.succeed(result.stdout)
783
+ : Effect.fail(
784
+ new SandboxFilesystemError({
785
+ path,
786
+ message: result.stderr.trim() || `exit code ${result.exitCode}`,
787
+ }),
788
+ ),
789
+ );
790
+
791
+ const exec = Effect.fn(function* (command: Command, options: ExecOptions = {}) {
792
+ // V2 sandboxes run commands through the task command router: start the
793
+ // command under an id we choose, then read its output streams while
794
+ // waiting for it to exit.
795
+ const id = yield* taskId;
796
+ const access = yield* ops.getCommandRouterAccess({ taskId: id });
797
+ if (!access.url || !access.jwt) {
798
+ return yield* new SandboxUnexpectedResponse({
799
+ message: `Modal returned no command router for sandbox ${sandboxId}`,
800
+ });
801
+ }
802
+ const at = { url: access.url, jwt: access.jwt };
803
+ const start = yield* routerOps.execStart(at);
804
+ const stdinWrite = yield* routerOps.execStdinWrite(at);
805
+ const stdioRead = yield* routerOps.execStdioRead(at);
806
+ const wait = yield* routerOps.execWait(at);
807
+ const execId = yield* Effect.sync(() => SecureRandom.uuidV4Unsafe());
808
+ yield* start({
809
+ taskId: id,
810
+ execId,
811
+ commandArgs: typeof command === "string" ? ["sh", "-c", command] : [...command],
812
+ workdir: options.cwd,
813
+ timeoutSecs: seconds(options.timeout),
814
+ secretIds: options.secrets?.map((s) => s.secretId),
815
+ env: options.env,
816
+ stdoutConfig: "TASK_EXEC_STDOUT_CONFIG_PIPE",
817
+ stderrConfig: "TASK_EXEC_STDERR_CONFIG_PIPE",
818
+ });
819
+ if (options.stdin !== undefined) {
820
+ yield* stdinWrite({
821
+ taskId: id,
822
+ execId,
823
+ offset: "0",
824
+ data: yield* encodeBase64(options.stdin),
825
+ eof: true,
826
+ });
827
+ }
828
+ const read = (fileDescriptor: router.TaskExecStdioFileDescriptor) =>
829
+ stdioRead({ taskId: id, execId, offset: "0", fileDescriptor }).pipe(
830
+ Effect.map(({ items }) => streamText(items)),
831
+ );
832
+ const [stdout, stderr, exit] = yield* Effect.all(
833
+ [
834
+ read("TASK_EXEC_STDIO_FILE_DESCRIPTOR_STDOUT"),
835
+ read("TASK_EXEC_STDIO_FILE_DESCRIPTOR_STDERR"),
836
+ wait({ taskId: id, execId }),
837
+ ],
838
+ { concurrency: "unbounded" },
839
+ );
840
+ return {
841
+ exitCode: exit.code ?? (exit.signal !== undefined ? 128 + exit.signal : -1),
842
+ stdout,
843
+ stderr,
844
+ };
845
+ });
846
+
847
+ /** The task and its router access, for the router calls of one command. */
848
+ const routerAccess = Effect.gen(function* () {
849
+ const id = yield* taskId;
850
+ const access = yield* ops.getCommandRouterAccess({ taskId: id });
851
+ if (!access.url || !access.jwt) {
852
+ return yield* new SandboxUnexpectedResponse({
853
+ message: `Modal returned no command router for sandbox ${sandboxId}`,
854
+ });
855
+ }
856
+ return { taskId: id, at: { url: access.url, jwt: access.jwt } };
857
+ });
858
+
859
+ /**
860
+ * Start a command (on a PTY with `pty`) and return how to follow it: a
861
+ * stream per output, stdin writes from a running offset, and its exit.
862
+ */
863
+ const startExec = Effect.fn(function* (
864
+ commandArgs: ReadonlyArray<string>,
865
+ options: ExecOptions,
866
+ pty?: router.PTYInfo,
867
+ ) {
868
+ const { taskId: id, at } = yield* routerAccess;
869
+ const begin = yield* routerOps.execStart(at);
870
+ const stdinWrite = yield* routerOps.execStdinWrite(at);
871
+ const follow = yield* routerOps.execStdioFollow(at);
872
+ const wait = yield* routerOps.execWait(at);
873
+ const execId = yield* Effect.sync(() => SecureRandom.uuidV4Unsafe());
874
+ yield* begin({
875
+ taskId: id,
876
+ execId,
877
+ commandArgs: [...commandArgs],
878
+ workdir: options.cwd,
879
+ timeoutSecs: seconds(options.timeout),
880
+ secretIds: options.secrets?.map((s) => s.secretId),
881
+ env: options.env,
882
+ stdoutConfig: "TASK_EXEC_STDOUT_CONFIG_PIPE",
883
+ stderrConfig: pty ? undefined : "TASK_EXEC_STDERR_CONFIG_PIPE",
884
+ ptyInfo: pty,
885
+ });
886
+ let stdinOffset = 0;
887
+ const write = (data: string | Uint8Array, eof = false) =>
888
+ Effect.gen(function* () {
889
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
890
+ const offset = stdinOffset;
891
+ stdinOffset += bytes.length;
892
+ yield* stdinWrite({
893
+ taskId: id,
894
+ execId,
895
+ offset: String(offset),
896
+ data: yield* encodeBase64(bytes),
897
+ eof,
898
+ });
899
+ });
900
+ /** One output of the command, each chunk as it arrives. */
901
+ const output = (fileDescriptor: router.TaskExecStdioFileDescriptor) =>
902
+ follow({ taskId: id, execId, offset: "0", fileDescriptor }).pipe(
903
+ Stream.map((item) => new Uint8Array(Buffer.from(item.data, "base64"))),
904
+ Stream.filter((bytes) => bytes.length > 0),
905
+ );
906
+ if (options.stdin !== undefined) yield* write(options.stdin, true);
907
+ return {
908
+ execId,
909
+ write,
910
+ output,
911
+ wait: Effect.map(wait({ taskId: id, execId }), (exit) => ({
912
+ exitCode: exit.code ?? (exit.signal !== undefined ? 128 + exit.signal : -1),
913
+ })),
914
+ };
915
+ });
916
+
917
+ const argvOf = (command: Command): ReadonlyArray<string> =>
918
+ typeof command === "string" ? ["sh", "-c", command] : command;
919
+
920
+ const start = (command: Command, options: ExecOptions = {}) =>
921
+ Effect.gen(function* () {
922
+ // The router has no signal call: the command records its pid, and
923
+ // `kill` signals it with a second command.
924
+ const pidFile = `/tmp/.alchemy-exec-${yield* Effect.sync(() => SecureRandom.uuidV4Unsafe())}.pid`;
925
+ const running = yield* startExec(
926
+ ["sh", "-c", 'echo $$ > "$0"; exec "$@"', pidFile, ...argvOf(command)],
927
+ options,
928
+ );
929
+ return {
930
+ id: running.execId,
931
+ output: Stream.merge(
932
+ Stream.map(running.output("TASK_EXEC_STDIO_FILE_DESCRIPTOR_STDOUT"), (data) => ({
933
+ fd: "stdout" as const,
934
+ data,
935
+ })),
936
+ Stream.map(running.output("TASK_EXEC_STDIO_FILE_DESCRIPTOR_STDERR"), (data) => ({
937
+ fd: "stderr" as const,
938
+ data,
939
+ })),
940
+ ),
941
+ write: (data: string | Uint8Array) => running.write(data),
942
+ kill: (signal: "SIGTERM" | "SIGKILL" = "SIGTERM") =>
943
+ Effect.asVoid(exec(["sh", "-c", `kill -s ${signal.slice(3)} "$(cat "$0")"`, pidFile])),
944
+ wait: running.wait,
945
+ } satisfies Sandbox.Process<SandboxProcessError>;
946
+ });
947
+
948
+ const pty: Sandbox.Pty<SandboxPtyError, SandboxPtyOptions> = {
949
+ open: (options) =>
950
+ Effect.gen(function* () {
951
+ const running = yield* startExec(
952
+ options.command
953
+ ? ["sh", "-c", options.command]
954
+ : ["sh", "-c", "exec bash -l 2>/dev/null || exec sh -l"],
955
+ { cwd: options.cwd, env: options.env, secrets: options.secrets },
956
+ {
957
+ enabled: true,
958
+ winszRows: options.rows,
959
+ winszCols: options.cols,
960
+ envTerm: "xterm-256color",
961
+ ptyType: "PTY_TYPE_SHELL",
962
+ noTerminateOnIdleStdin: true,
963
+ },
964
+ );
965
+ return {
966
+ id: running.execId,
967
+ output: running.output("TASK_EXEC_STDIO_FILE_DESCRIPTOR_STDOUT"),
968
+ write: (data: string | Uint8Array) => running.write(data),
969
+ resize: () =>
970
+ Effect.fail(
971
+ new SandboxPtyResizeUnsupported({
972
+ message: "Modal's task command router can't resize a running PTY",
973
+ }),
974
+ ),
975
+ } satisfies Sandbox.PtyConnection<SandboxPtyError>;
976
+ }),
977
+ };
978
+
979
+ const read = Effect.fn(function* (path: string) {
980
+ // base64 keeps binary content intact through the text output stream.
981
+ const encoded = yield* fsExec(path, 'base64 < "$1" | tr -d "\\n"');
982
+ return yield* decodeBase64(encoded);
983
+ });
984
+
985
+ const typeOf = (letter: string): SandboxFileEntry["type"] =>
986
+ letter === "d" ? "directory" : letter === "l" ? "symlink" : letter === "f" ? "file" : "other";
987
+
988
+ const files: SandboxFilesOf<never> = {
989
+ read,
990
+ readText: (path) => read(path).pipe(Effect.map((bytes) => new TextDecoder().decode(bytes))),
991
+ write: Effect.fn(function* (path, data) {
992
+ const encoded = yield* encodeBase64(data);
993
+ yield* fsExec(path, 'mkdir -p "$(dirname "$1")" && base64 -d > "$1"', encoded);
994
+ }),
995
+ list: Effect.fn(function* (path) {
996
+ const output = yield* fsExec(
997
+ path,
998
+ 'cd "$1" && for f in * .[!.]* ..?*; do [ -e "$f" ] || [ -L "$f" ] || continue; if [ -L "$f" ]; then echo "l $f"; elif [ -d "$f" ]; then echo "d $f"; elif [ -f "$f" ]; then echo "f $f"; else echo "o $f"; fi; done',
999
+ );
1000
+ return output
1001
+ .split("\n")
1002
+ .filter((line) => line.length > 2)
1003
+ .map((line) => ({
1004
+ path: `${path.replace(/\/$/, "")}/${line.slice(2)}`,
1005
+ type: typeOf(line[0]!),
1006
+ }));
1007
+ }),
1008
+ stat: Effect.fn(function* (path) {
1009
+ // `%F` is GNU stat's type name; busybox prints the same names.
1010
+ const output = yield* fsExec(path, 'stat -c "%F|%s|%Y" "$1"');
1011
+ const [kind = "", size = "0", mtime = "0"] = output.trim().split("|");
1012
+ return {
1013
+ path,
1014
+ type: kind.includes("directory")
1015
+ ? "directory"
1016
+ : kind.includes("symbolic link")
1017
+ ? "symlink"
1018
+ : kind.includes("regular")
1019
+ ? "file"
1020
+ : "other",
1021
+ size: Number(size),
1022
+ modifiedAt: DateTime.makeUnsafe(Number(mtime) * 1000),
1023
+ } satisfies SandboxFileStat;
1024
+ }),
1025
+ remove: Effect.fn(function* (path, options = {}) {
1026
+ yield* fsExec(path, options.recursive ? 'rm -r "$1"' : 'rm "$1"');
1027
+ }),
1028
+ makeDir: Effect.fn(function* (path) {
1029
+ yield* fsExec(path, 'mkdir -p "$1"');
1030
+ }),
1031
+ };
1032
+
1033
+ const tunnelsOf = Effect.fn(function* (timeout?: DurationInput) {
1034
+ const { tunnels } = yield* ops.getTunnels({
1035
+ sandboxId,
1036
+ timeout: seconds(timeout) ?? 50,
1037
+ });
1038
+ const out: Record<number, SandboxTunnel> = {};
1039
+ for (const tunnel of tunnels ?? []) {
1040
+ if (tunnel.containerPort === undefined) continue;
1041
+ const encrypted = tunnel.host !== undefined && tunnel.host !== "";
1042
+ const host = (encrypted ? tunnel.host : tunnel.unencryptedHost) ?? "";
1043
+ const port = (encrypted ? tunnel.port : tunnel.unencryptedPort) ?? 443;
1044
+ out[tunnel.containerPort] = {
1045
+ host,
1046
+ port,
1047
+ url: encrypted
1048
+ ? `https://${host}${port === 443 ? "" : `:${port}`}`
1049
+ : `tcp://${host}:${port}`,
1050
+ };
1051
+ }
1052
+ return out;
1053
+ });
1054
+
1055
+ const getTcpPort = Effect.fn(function* (port: number | undefined = target.program?.port) {
1056
+ if (port === undefined) {
1057
+ return yield* new SandboxUnexpectedResponse({
1058
+ message: "getTcpPort needs a port: create the sandbox from a Modal.Sandbox, or pass one",
1059
+ });
1060
+ }
1061
+ const tunnel = (yield* tunnelsOf(50))[port];
1062
+ if (!tunnel) {
1063
+ return yield* new SandboxUnexpectedResponse({
1064
+ message: `Port ${port} of sandbox ${sandboxId} has no tunnel; open it with encryptedPorts`,
1065
+ });
1066
+ }
1067
+ const tls = tunnel.url.startsWith("https:");
1068
+ // An unencrypted tunnel carries plain TCP, so HTTP to it is cleartext.
1069
+ const origin = tls ? tunnel.url : `http://${tunnel.host}:${tunnel.port}`;
1070
+ const sandboxPort: SandboxPortOf<never> = {
1071
+ ...tunnel,
1072
+ // The user's own program, reached through its tunnel (allowed raw HTTP).
1073
+ fetch: Sandbox.portFetch((request) => {
1074
+ const url = new URL(request.url, "http://sandbox");
1075
+ // The tunnel routes by host, so a proxied request's own `host` goes.
1076
+ return ops.http.execute(
1077
+ HttpClientRequest.setUrl(
1078
+ HttpClientRequest.removeHeader(request, "host"),
1079
+ `${origin}${url.pathname}${url.search}`,
1080
+ ),
1081
+ );
1082
+ }) as SandboxPortOf<never>["fetch"],
1083
+ connect: () => connectTcp({ host: tunnel.host, port: tunnel.port, tls }),
1084
+ };
1085
+ return sandboxPort;
1086
+ });
1087
+
1088
+ const handle: RawSandboxHandle = {
1089
+ sandboxId,
1090
+ exec,
1091
+ start,
1092
+ pty,
1093
+ files,
1094
+ tunnels: tunnelsOf,
1095
+ connectToken: Effect.fn(function* (options = {}) {
1096
+ const response = yield* ops.createConnectToken({
1097
+ sandboxId,
1098
+ port: options.port,
1099
+ userMetadata: options.userMetadata,
1100
+ });
1101
+ if (!response.url || !response.token) {
1102
+ return yield* new SandboxUnexpectedResponse({ message: "Modal returned no connect token" });
1103
+ }
1104
+ return { url: response.url, token: response.token };
1105
+ }),
1106
+ snapshotFilesystem: (_timeout) =>
1107
+ viaRouter(routerOps.snapshotFilesystem, (op, task) =>
1108
+ Effect.flatMap(
1109
+ Effect.sync(() => SecureRandom.uuidV4Unsafe()),
1110
+ // -1: the snapshot image never expires.
1111
+ (snapshotId) => op({ taskId: task, snapshotId, ttlSeconds: "-1" }),
1112
+ ),
1113
+ ).pipe(
1114
+ Effect.flatMap((response) =>
1115
+ response.imageId
1116
+ ? Effect.succeed({ imageId: response.imageId })
1117
+ : Effect.fail(
1118
+ new SandboxUnexpectedResponse({
1119
+ message: `Filesystem snapshot of ${sandboxId} returned no image`,
1120
+ }),
1121
+ ),
1122
+ ),
1123
+ ),
1124
+ snapshotMemory: () =>
1125
+ viaRouter(routerOps.snapshotMemory, (op, task) =>
1126
+ Effect.flatMap(
1127
+ Effect.sync(() => SecureRandom.uuidV4Unsafe()),
1128
+ (idempotencyKey) => op({ taskId: task, idempotencyKey }),
1129
+ ),
1130
+ ).pipe(
1131
+ Effect.flatMap((response) =>
1132
+ response.snapshotId
1133
+ ? Effect.succeed({ snapshotId: response.snapshotId })
1134
+ : Effect.fail(new SandboxUnexpectedResponse({ message: "No snapshot id" })),
1135
+ ),
1136
+ ),
1137
+ snapshotDirectory: (path) =>
1138
+ viaRouter(routerOps.snapshotDirectory, (op, task) => op({ taskId: task, path })).pipe(
1139
+ Effect.flatMap((response) =>
1140
+ response.imageId
1141
+ ? Effect.succeed({ imageId: response.imageId })
1142
+ : Effect.fail(new SandboxUnexpectedResponse({ message: `No image for ${path}` })),
1143
+ ),
1144
+ ),
1145
+ mountDirectory: (path, snapshot) =>
1146
+ viaRouter(routerOps.mountDirectory, (op, task) =>
1147
+ op({ taskId: task, path, imageId: snapshot.imageId }),
1148
+ ).pipe(Effect.asVoid),
1149
+ setNetworkAccess: (access) =>
1150
+ viaRouter(routerOps.setNetworkAccess, (op, task) =>
1151
+ op({ taskId: task, networkAccess: runningNetworkAccessOf(access) }),
1152
+ ).pipe(Effect.asVoid),
1153
+ setOutboundPolicy: Effect.fn(function* (policy) {
1154
+ yield* ops.setOutboundPolicy({
1155
+ sandboxId,
1156
+ outboundPolicy: outboundPolicyOf(policy),
1157
+ });
1158
+ }),
1159
+ setTags: Effect.fn(function* (tags) {
1160
+ yield* ops.setTags({
1161
+ sandboxId,
1162
+ environmentName: environmentOf(target.environment),
1163
+ tags: Object.entries(tags).map(([tagName, tagValue]) => ({ tagName, tagValue })),
1164
+ });
1165
+ }),
1166
+ getTags: Effect.fn(function* () {
1167
+ const { tags } = yield* ops.getTags({ sandboxId });
1168
+ return Object.fromEntries((tags ?? []).map((t) => [t.tagName ?? "", t.tagValue ?? ""]));
1169
+ }),
1170
+ setName: Effect.fn(function* (name) {
1171
+ yield* ops.setName({ sandboxId, name });
1172
+ }),
1173
+ resourceUsage: Effect.fn(function* () {
1174
+ const usage = yield* ops.getResourceUsage({ sandboxId });
1175
+ return {
1176
+ cpuSeconds: Number(usage.cpuCoreNanosecs ?? 0) / 1e9,
1177
+ memoryGibSeconds: Number(usage.memGibNanosecs ?? 0) / 1e9,
1178
+ gpuSeconds: Number(usage.gpuNanosecs ?? 0) / 1e9,
1179
+ };
1180
+ }),
1181
+ logs: Effect.fn(function* (options = {}) {
1182
+ const { taskId: id, at } = yield* routerAccess;
1183
+ const read = yield* routerOps.mainStdio(at);
1184
+ const chunks = yield* read({
1185
+ taskId: id,
1186
+ offset: "0",
1187
+ fileDescriptor: "SANDBOX_STDIO_FILE_DESCRIPTOR_STDOUT",
1188
+ }).pipe(
1189
+ Stream.map((item) => Buffer.from(item.data, "base64")),
1190
+ Stream.interruptWhen(Effect.sleep(`${seconds(options.timeout) ?? 5} seconds`)),
1191
+ Stream.runCollect,
1192
+ );
1193
+ return Buffer.concat(chunks).toString("utf8");
1194
+ }),
1195
+ wait: Effect.fn(function* (timeout) {
1196
+ const { result } = yield* ops.wait({
1197
+ sandboxId,
1198
+ timeout: seconds(timeout) ?? 0,
1199
+ });
1200
+ return {
1201
+ exitCode: result?.exitcode,
1202
+ status: result?.status ?? "GENERIC_STATUS_UNSPECIFIED",
1203
+ };
1204
+ }),
1205
+ getTcpPort,
1206
+ terminate: Effect.fn(function* () {
1207
+ yield* ops
1208
+ .terminate({ sandboxId })
1209
+ .pipe(Effect.catchTag("SandboxNotFound", () => Effect.void));
1210
+ }),
1211
+ };
1212
+ return handle;
1213
+ };
1214
+
1215
+ /**
1216
+ * Create a sandbox in `target`'s app and return its handle. The one
1217
+ * implementation behind `CreateSandbox` and warm-base image builds.
1218
+ */
1219
+ export const createSandbox = Effect.fn(function* (
1220
+ ops: SandboxOps,
1221
+ target: SandboxTarget,
1222
+ options: SandboxCreateOptions = {},
1223
+ ) {
1224
+ const imageId = yield* resolveSandboxImage(ops, target, options.image);
1225
+ const program = target.program;
1226
+ const withProgram: SandboxCreateOptions = program
1227
+ ? {
1228
+ ...options,
1229
+ command: options.command ?? program.command,
1230
+ secrets: [{ secretId: program.secretId }, ...(options.secrets ?? [])],
1231
+ encryptedPorts: [...new Set([program.port, ...(options.encryptedPorts ?? [])])],
1232
+ }
1233
+ : options;
1234
+ const own = yield* modalMountsOf(options.mounts);
1235
+ const mounts: ModalMounts = {
1236
+ volumeMounts: [...(program?.mounts?.volumeMounts ?? []), ...own.volumeMounts],
1237
+ cloudBucketMounts: [...(program?.mounts?.cloudBucketMounts ?? []), ...own.cloudBucketMounts],
1238
+ };
1239
+ const created = yield* ops.create({
1240
+ appId: target.appId,
1241
+ definition: definitionOf(imageId, withProgram, mounts),
1242
+ tags: Object.entries(options.tags ?? {}).map(([tagName, tagValue]) => ({ tagName, tagValue })),
1243
+ });
1244
+ if (!created.sandboxId) {
1245
+ return yield* new SandboxUnexpectedResponse({ message: "Modal returned no sandbox id" });
1246
+ }
1247
+ return sandboxHandle(ops, created.sandboxId, target);
1248
+ });
1249
+
1250
+ /** A running sandbox of `target`'s app by name, or `undefined`. */
1251
+ export const sandboxFromName = Effect.fn(function* (
1252
+ ops: SandboxOps,
1253
+ target: SandboxTarget,
1254
+ name: string,
1255
+ ) {
1256
+ const found = yield* ops
1257
+ .getFromName({
1258
+ sandboxName: name,
1259
+ appName: target.appName,
1260
+ environmentName: environmentOf(target.environment),
1261
+ })
1262
+ .pipe(Effect.catchTag("SandboxNotFound", () => Effect.undefined));
1263
+ return found?.sandboxId ? sandboxHandle(ops, found.sandboxId, target) : undefined;
1264
+ });
1265
+
1266
+ /** Running sandboxes of `target`'s app, optionally filtered by tags. */
1267
+ export const listSandboxes = Effect.fn(function* (
1268
+ ops: SandboxOps,
1269
+ target: SandboxTarget,
1270
+ options: { readonly tags?: Record<string, string>; readonly includeFinished?: boolean } = {},
1271
+ ) {
1272
+ const { sandboxes } = yield* ops.list({
1273
+ appId: target.appId,
1274
+ environmentName: environmentOf(target.environment),
1275
+ includeFinished: options.includeFinished ?? false,
1276
+ tags: Object.entries(options.tags ?? {}).map(([tagName, tagValue]) => ({ tagName, tagValue })),
1277
+ });
1278
+ return (sandboxes ?? []).flatMap((info) =>
1279
+ info.id
1280
+ ? [
1281
+ {
1282
+ sandboxId: info.id,
1283
+ name: info.name || undefined,
1284
+ createdAt: info.createdAt,
1285
+ tags: Object.fromEntries(
1286
+ (info.tags ?? []).map((t) => [t.tagName ?? "", t.tagValue ?? ""]),
1287
+ ),
1288
+ },
1289
+ ]
1290
+ : [],
1291
+ );
1292
+ });
1293
+
1294
+ /** Restore a memory snapshot into a new sandbox. */
1295
+ export const restoreSandbox = Effect.fn(function* (
1296
+ ops: SandboxOps,
1297
+ target: SandboxTarget,
1298
+ snapshot: { readonly snapshotId: string },
1299
+ options: { readonly name?: string } = {},
1300
+ ) {
1301
+ const restored = yield* ops.restore({
1302
+ snapshotId: snapshot.snapshotId,
1303
+ sandboxNameOverride: options.name,
1304
+ sandboxNameOverrideType: options.name ? "SANDBOX_NAME_OVERRIDE_TYPE_STRING" : undefined,
1305
+ });
1306
+ if (!restored.sandboxId) {
1307
+ return yield* new SandboxUnexpectedResponse({ message: "Modal returned no restored sandbox" });
1308
+ }
1309
+ return sandboxHandle(ops, restored.sandboxId, target);
1310
+ });
1311
+
1312
+ /**
1313
+ * Options for a persistent sandbox. Modal fixes each session's lifetime (an
1314
+ * hour, paused after five idle minutes) and doesn't support names, GPUs,
1315
+ * `env`, tags or memory snapshots on persistent sandboxes yet.
1316
+ */
1317
+ export type PersistentSandboxCreateOptions = Omit<
1318
+ SandboxCreateOptions,
1319
+ "timeout" | "idleTimeout" | "name" | "gpu" | "env" | "tags" | "enableSnapshot"
1320
+ >;
1321
+
1322
+ /** Modal's session limits for persistent sandboxes. */
1323
+ const PERSISTENT_SESSION = { timeout: 60 * 60, idleTimeout: 5 * 60 } as const;
1324
+
1325
+ /** A persistent sandbox: one box whose filesystem outlives its sessions. */
1326
+ export interface PersistentSandboxHandleOf<R> {
1327
+ readonly persistentSandboxId: string;
1328
+ /**
1329
+ * The running session, resuming the box from its last session's
1330
+ * filesystem when it's paused.
1331
+ */
1332
+ session(): Effect.Effect<
1333
+ SandboxHandleOf<R>,
1334
+ | SandboxOpError<"getPersistent">
1335
+ | SandboxOpError<"resumePersistent">
1336
+ | SandboxUnexpectedResponse,
1337
+ R
1338
+ >;
1339
+ /** Terminate the box for good. */
1340
+ terminate(): Effect.Effect<
1341
+ void,
1342
+ Exclude<SandboxOpError<"terminatePersistent">, { readonly _tag: "PersistentSandboxNotFound" }>,
1343
+ R
1344
+ >;
1345
+ }
1346
+
1347
+ export type RawPersistentSandboxHandle = PersistentSandboxHandleOf<never>;
1348
+ export type PersistentSandboxHandle = PersistentSandboxHandleOf<RuntimeContext>;
1349
+
1350
+ /** The handle for an existing persistent sandbox id. */
1351
+ export const persistentSandboxHandle = (
1352
+ ops: SandboxOps,
1353
+ persistentSandboxId: string,
1354
+ target: Pick<SandboxTarget, "environment" | "program">,
1355
+ ): RawPersistentSandboxHandle => ({
1356
+ persistentSandboxId,
1357
+ session: Effect.fn(function* () {
1358
+ const running = yield* ops.getPersistent({ persistentSandboxId });
1359
+ if (running.sandboxId) return sandboxHandle(ops, running.sandboxId, target);
1360
+ // A paused box resumes from its last session's filesystem. Modal answers
1361
+ // busy while the previous session is still saving it.
1362
+ const resumed = yield* ops.resumePersistent({ persistentSandboxId }).pipe(
1363
+ Effect.retry({
1364
+ while: (error) => error._tag === "PersistentSandboxBusy",
1365
+ schedule: Schedule.spaced("1 second"),
1366
+ times: 60,
1367
+ }),
1368
+ );
1369
+ if (!resumed.sandboxId) {
1370
+ return yield* new SandboxUnexpectedResponse({
1371
+ message: `Modal returned no session for persistent sandbox ${persistentSandboxId}`,
1372
+ });
1373
+ }
1374
+ return sandboxHandle(ops, resumed.sandboxId, target);
1375
+ }),
1376
+ terminate: Effect.fn(function* () {
1377
+ yield* ops
1378
+ .terminatePersistent({ persistentSandboxId })
1379
+ .pipe(Effect.catchTag("PersistentSandboxNotFound", () => Effect.void));
1380
+ }),
1381
+ });
1382
+
1383
+ /** Create a persistent sandbox in `target`'s app. */
1384
+ export const createPersistentSandbox = Effect.fn(function* (
1385
+ ops: SandboxOps,
1386
+ target: SandboxTarget,
1387
+ options: PersistentSandboxCreateOptions = {},
1388
+ ) {
1389
+ const imageId = yield* resolveSandboxImage(ops, target, options.image);
1390
+ const created = yield* ops.createPersistent({
1391
+ appId: target.appId,
1392
+ definition: definitionOf(
1393
+ imageId,
1394
+ { ...options, ...PERSISTENT_SESSION },
1395
+ yield* modalMountsOf(options.mounts),
1396
+ ),
1397
+ });
1398
+ if (!created.persistentSandboxId) {
1399
+ return yield* new SandboxUnexpectedResponse({
1400
+ message: "Modal returned no persistent sandbox id",
1401
+ });
1402
+ }
1403
+ return persistentSandboxHandle(ops, created.persistentSandboxId, target);
1404
+ });