@evolvingmachines/modal 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,3 +1,4 @@
1
- 'use strict';var modal=require('modal'),tarStream=require('tar-stream');var b={"evolve-all":"evolvingmachines/evolve-all:latest"},h=new Set([".xlsx",".xls",".docx",".doc",".pptx",".ppt",".pdf",".zip",".tar",".gz",".7z",".rar",".png",".jpg",".jpeg",".gif",".webp",".ico",".bmp",".mp3",".wav",".ogg",".flac",".aac",".mp4",".avi",".mov",".mkv",".webm",".woff",".woff2",".ttf",".otf",".eot",".exe",".dll",".so",".dylib",".sqlite",".db",".pickle",".pkl",".parquet"]);function y(c){let t=c.substring(c.lastIndexOf(".")).toLowerCase();return h.has(t)}var l=class{constructor(t){this.sandbox=t;}wrapAsUser(t,e,n){let r="";n&&Object.keys(n).length>0&&(r=Object.entries(n).filter(([,o])=>o!=null).map(([o,a])=>`export ${o}='${String(a).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let s=e?`cd '${e.replace(/'/g,"'\\''")}' && ${r}${t}`:`${r}${t}`;return ["su","user","-c",`echo ${Buffer.from(s).toString("base64")} | base64 -d | bash`]}async run(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(r,e?.onStdout,e?.onStderr);return {exitCode:await r.wait(),stdout:s,stderr:i}}async spawn(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),s="",i="",o=this.accumulateStreams(r,e?.onStdout?d=>{e.onStdout(d);}:void 0,e?.onStderr?d=>{e.onStderr(d);}:void 0).then(({stdout:d,stderr:m})=>{s=d,i=m;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await o,{exitCode:await r.wait(),stdout:s,stderr:i}),kill:async()=>false}}async list(){let t=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await t.wait(),(await t.stdout.readText()).trim().split(`
2
- `).slice(1).map(r=>{let s=r.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(t,e){throw new Error("Modal does not support connecting to existing processes")}async sendStdin(t,e){throw new Error("Modal does not support sendStdin by process ID")}async kill(t){return await(await this.sandbox.exec(["kill","-9",t],{timeoutMs:1e4})).wait()===0}async accumulateStreams(t,e,n){let r="",s="",i=[];return i.push((async()=>{try{for await(let o of t.stdout){let a=typeof o=="string"?o:new TextDecoder().decode(o);r+=a,e?.(a);}}catch{}})()),i.push((async()=>{try{for await(let o of t.stderr){let a=typeof o=="string"?o:new TextDecoder().decode(o);s+=a,n?.(a);}}catch{}})()),await Promise.all(i),{stdout:r,stderr:s}}},f=class{constructor(t){this.sandbox=t;}async read(t){if(y(t)){let r=await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"}),s=await r.wait();if(s!==0){let i=await r.stderr.readText();throw new Error(`Failed to read file ${t}: ${i||`exit code ${s}`}`)}return await r.stdout.readBytes()}let e=await this.sandbox.exec(["cat",t],{timeoutMs:3e5}),n=await e.wait();if(n!==0){let r=await e.stderr.readText();throw new Error(`Failed to read file ${t}: ${r||`exit code ${n}`}`)}return await e.stdout.readText()}async write(t,e){let n=this.toBuffer(e),r=t.substring(0,t.lastIndexOf("/"));r&&await this.makeDir(r);let s=t.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await i.stdin.writeBytes(new Uint8Array(n)),await i.stdin.getWriter().close(),await i.wait(),await(await this.sandbox.exec(["chown","user:user",t],{timeoutMs:1e4})).wait();}async writeBatch(t){let e=tarStream.pack(),n=[],r=new Set;for(let a of t){let d=this.toBuffer(a.data),m=a.path.startsWith("/")?a.path.slice(1):a.path;e.entry({name:m},d);let p=a.path.substring(0,a.path.lastIndexOf("/"));p&&r.add(p);}e.finalize();for await(let a of e)n.push(Buffer.from(a));let s=Buffer.concat(n),i=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await i.stdin.writeBytes(new Uint8Array(s)),await i.stdin.getWriter().close(),await i.wait(),r.size>0){let a=Array.from(r),d=new Set(a.map(m=>m.split("/").slice(0,4).join("/")));for(let m of d)await(await this.sandbox.exec(["chown","-R","user:user",m],{timeoutMs:3e4})).wait();}}async makeDir(t){await(await this.sandbox.exec(["mkdir","-p",t],{timeoutMs:1e4})).wait(),await(await this.sandbox.exec(["chown","-R","user:user",t],{timeoutMs:1e4})).wait();}async exists(t){return await(await this.sandbox.exec(["test","-e",t],{timeoutMs:1e4})).wait()===0}async list(t){let e=t.replace(/'/g,"'\\''"),n=await this.sandbox.exec(["bash","-c",`ls -la '${e}' | tail -n +2`],{timeoutMs:3e4});await n.wait();let r=await n.stdout.readText(),s=[];for(let i of r.trim().split(`
3
- `)){if(!i)continue;let o=i.split(/\s+/);if(o.length<9)continue;let a=o[0],d=o.slice(8).join(" ");d==="."||d===".."||s.push({name:d,path:t.endsWith("/")?`${t}${d}`:`${t}/${d}`,type:a.startsWith("d")?"dir":"file"});}return s}async remove(t){await(await this.sandbox.exec(["rm","-rf",t],{timeoutMs:3e4})).wait();}async rename(t,e){await(await this.sandbox.exec(["mv",t,e],{timeoutMs:3e4})).wait();}async readStream(t){return (await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"})).stdout}async writeStream(t,e){let n=t.substring(0,t.lastIndexOf("/"));n&&await this.makeDir(n);let r=t.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${r}'`],{mode:"binary"}),i=e.getReader();try{for(;;){let{done:a,value:d}=await i.read();if(a)break;await s.stdin.writeBytes(d);}}finally{i.releaseLock();}await s.stdin.getWriter().close(),await s.wait();}async uploadUrl(t,e){throw new Error("Modal does not support pre-signed upload URLs")}async downloadUrl(t,e){throw new Error("Modal does not support pre-signed download URLs")}async watchDir(t,e,n){throw new Error("Modal does not support watchDir")}toBuffer(t){if(typeof t=="string")return Buffer.from(t,"utf-8");if(t instanceof Buffer)return t;if(t instanceof ArrayBuffer||t instanceof Uint8Array)return Buffer.from(t);throw new Error(`Unsupported data type: ${typeof t}`)}},u=class{constructor(t,e){this.sandbox=t;this.commands=new l(t),this.files=new f(t),this.image=e,this.startTime=new Date;}commands;files;image;startTime;get sandboxId(){return this.sandbox.sandboxId}async getHost(t){let n=(await this.sandbox.tunnels())[t];if(!n)throw new Error(`No tunnel found for port ${t}`);return n.url}async isRunning(){try{return await(await this.sandbox.exec(["echo","ping"],{timeoutMs:5e3})).wait(),!0}catch{return false}}async getInfo(){return {sandboxId:this.sandbox.sandboxId,image:this.image,metadata:{},startedAt:this.startTime.toISOString()}}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(t=>setTimeout(t,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause. Use kill() instead.")}},w=class{providerType="modal";client;appName;defaultTimeoutMs;_app;constructor(t={}){this.client=new modal.ModalClient,this.appName=t.appName??"evolve-sandbox",this.defaultTimeoutMs=t.defaultTimeoutMs??36e5;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async create(t){let e=await this.getApp(),n=t.timeoutMs??this.defaultTimeoutMs,r=b[t.image]??t.image,s=this.client.images.fromRegistry(r),i=t.envs?Object.fromEntries(Object.entries(t.envs).filter(([,d])=>d!=null)):void 0,o=i&&Object.keys(i).length>0?i:void 0,a=await this.client.sandboxes.create(e,s,{cpu:4,memoryMiB:4096,timeoutMs:n,workdir:t.workingDirectory,env:o});return t.workingDirectory&&await(await a.exec(["chown","-R","user:user",t.workingDirectory],{timeoutMs:3e4})).wait(),new u(a,r)}async connect(t,e){let n=await this.client.sandboxes.fromId(t);return new u(n,"unknown")}async list(t){let e=[],n=t?.limit??100;try{for await(let r of this.client.sandboxes.list())if(e.push({sandboxId:r.sandboxId,image:"unknown",metadata:{},startedAt:new Date().toISOString()}),e.length>=n)break}catch{}return e}};function M(c={}){return new w(c)}exports.ModalProvider=w;exports.createModalProvider=M;
1
+ 'use strict';var modal=require('modal'),tarStream=require('tar-stream');var b={"evolve-all":"evolvingmachines/evolve-all:latest"},h=new Set([".xlsx",".xls",".docx",".doc",".pptx",".ppt",".pdf",".zip",".tar",".gz",".7z",".rar",".png",".jpg",".jpeg",".gif",".webp",".ico",".bmp",".mp3",".wav",".ogg",".flac",".aac",".mp4",".avi",".mov",".mkv",".webm",".woff",".woff2",".ttf",".otf",".eot",".exe",".dll",".so",".dylib",".sqlite",".db",".pickle",".pkl",".parquet"]);function y(c){let t=c.substring(c.lastIndexOf(".")).toLowerCase();return h.has(t)}var l=class{constructor(t){this.sandbox=t;}wrapAsUser(t,e,n){let r="";n&&Object.keys(n).length>0&&(r=Object.entries(n).filter(([,a])=>a!=null).map(([a,o])=>`export ${a}='${String(o).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let s=e?`cd '${e.replace(/'/g,"'\\''")}' && ${r}${t}`:`${r}${t}`;return ["su","user","-c",`echo ${Buffer.from(s).toString("base64")} | base64 -d | bash`]}async run(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(r,e?.onStdout,e?.onStderr);return {exitCode:await r.wait(),stdout:s,stderr:i}}async spawn(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),s="",i="",a=this.accumulateStreams(r,e?.onStdout?d=>{e.onStdout(d);}:void 0,e?.onStderr?d=>{e.onStderr(d);}:void 0).then(({stdout:d,stderr:m})=>{s=d,i=m;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await a,{exitCode:await r.wait(),stdout:s,stderr:i}),kill:async()=>false}}async list(){let t=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await t.wait(),(await t.stdout.readText()).trim().split(`
2
+ `).slice(1).map(r=>{let s=r.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(t,e){throw new Error("Modal does not support connecting to existing processes")}async sendStdin(t,e){throw new Error("Modal does not support sendStdin by process ID")}async kill(t){return await(await this.sandbox.exec(["kill","-9",t],{timeoutMs:1e4})).wait()===0}async accumulateStreams(t,e,n){let r="",s="",i=[];return i.push((async()=>{try{for await(let a of t.stdout){let o=typeof a=="string"?a:new TextDecoder().decode(a);r+=o,e?.(o);}}catch{}})()),i.push((async()=>{try{for await(let a of t.stderr){let o=typeof a=="string"?a:new TextDecoder().decode(a);s+=o,n?.(o);}}catch{}})()),await Promise.all(i),{stdout:r,stderr:s}}},f=class{constructor(t){this.sandbox=t;}async read(t){if(y(t)){let r=await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"}),s=await r.wait();if(s!==0){let i=await r.stderr.readText();throw new Error(`Failed to read file ${t}: ${i||`exit code ${s}`}`)}return await r.stdout.readBytes()}let e=await this.sandbox.exec(["cat",t],{timeoutMs:3e5}),n=await e.wait();if(n!==0){let r=await e.stderr.readText();throw new Error(`Failed to read file ${t}: ${r||`exit code ${n}`}`)}return await e.stdout.readText()}async write(t,e){let n=this.toBuffer(e),r=t.substring(0,t.lastIndexOf("/"));r&&await this.makeDir(r);let s=t.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await i.stdin.writeBytes(new Uint8Array(n)),await i.stdin.getWriter().close(),await i.wait(),await(await this.sandbox.exec(["chown","user:user",t],{timeoutMs:1e4})).wait();}async writeBatch(t){let e=tarStream.pack(),n=[],r=new Set;for(let o of t){let d=this.toBuffer(o.data),m=o.path.startsWith("/")?o.path.slice(1):o.path;e.entry({name:m},d);let u=o.path.substring(0,o.path.lastIndexOf("/"));u&&r.add(u);}e.finalize();for await(let o of e)n.push(Buffer.from(o));let s=Buffer.concat(n),i=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await i.stdin.writeBytes(new Uint8Array(s)),await i.stdin.getWriter().close(),await i.wait(),r.size>0){let o=Array.from(r),d=new Set(o.map(m=>m.split("/").slice(0,4).join("/")));for(let m of d)await(await this.sandbox.exec(["chown","-R","user:user",m],{timeoutMs:3e4})).wait();}}async makeDir(t){await(await this.sandbox.exec(["mkdir","-p",t],{timeoutMs:1e4})).wait(),await(await this.sandbox.exec(["chown","-R","user:user",t],{timeoutMs:1e4})).wait();}async exists(t){return await(await this.sandbox.exec(["test","-e",t],{timeoutMs:1e4})).wait()===0}async list(t){let e=t.replace(/'/g,"'\\''"),n=await this.sandbox.exec(["bash","-c",`ls -la '${e}' | tail -n +2`],{timeoutMs:3e4});await n.wait();let r=await n.stdout.readText(),s=[];for(let i of r.trim().split(`
3
+ `)){if(!i)continue;let a=i.split(/\s+/);if(a.length<9)continue;let o=a[0],d=a.slice(8).join(" ");d==="."||d===".."||s.push({name:d,path:t.endsWith("/")?`${t}${d}`:`${t}/${d}`,type:o.startsWith("d")?"dir":"file"});}return s}async remove(t){await(await this.sandbox.exec(["rm","-rf",t],{timeoutMs:3e4})).wait();}async rename(t,e){await(await this.sandbox.exec(["mv",t,e],{timeoutMs:3e4})).wait();}async readStream(t){return (await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"})).stdout}async writeStream(t,e){let n=t.substring(0,t.lastIndexOf("/"));n&&await this.makeDir(n);let r=t.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${r}'`],{mode:"binary"}),i=e.getReader();try{for(;;){let{done:o,value:d}=await i.read();if(o)break;await s.stdin.writeBytes(d);}}finally{i.releaseLock();}await s.stdin.getWriter().close(),await s.wait();}async uploadUrl(t,e){throw new Error("Modal does not support pre-signed upload URLs")}async downloadUrl(t,e){throw new Error("Modal does not support pre-signed download URLs")}async watchDir(t,e,n){throw new Error("Modal does not support watchDir")}toBuffer(t){if(typeof t=="string")return Buffer.from(t,"utf-8");if(t instanceof Buffer)return t;if(t instanceof ArrayBuffer||t instanceof Uint8Array)return Buffer.from(t);throw new Error(`Unsupported data type: ${typeof t}`)}},p=class{constructor(t,e){this.sandbox=t;this.commands=new l(t),this.files=new f(t),this.image=e,this.startTime=new Date;}commands;files;image;startTime;get sandboxId(){return this.sandbox.sandboxId}async getHost(t){let n=(await this.sandbox.tunnels())[t];if(!n)throw new Error(`No tunnel found for port ${t}`);return n.url}async isRunning(){try{return await(await this.sandbox.exec(["echo","ping"],{timeoutMs:5e3})).wait(),!0}catch{return false}}async getInfo(){return {sandboxId:this.sandbox.sandboxId,image:this.image,metadata:{},startedAt:this.startTime.toISOString()}}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(t=>setTimeout(t,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause. Use kill() instead.")}},w=class{providerType="modal";client;appName;defaultTimeoutMs;_app;constructor(t){!t.endpoint&&process.env.MODAL_SERVER_URL?.startsWith("unix:")?process.env.MODAL_SERVER_URL="https://api.modal.com:443":t.endpoint&&(process.env.MODAL_SERVER_URL=t.endpoint),this.client=new modal.ModalClient({tokenId:t.tokenId,tokenSecret:t.tokenSecret}),this.appName=t.appName??"evolve-sandbox",this.defaultTimeoutMs=t.defaultTimeoutMs??36e5;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async create(t){let e=await this.getApp(),n=t.timeoutMs??this.defaultTimeoutMs,r=b[t.image]??t.image,s=this.client.images.fromRegistry(r),i=t.envs?Object.fromEntries(Object.entries(t.envs).filter(([,d])=>d!=null)):void 0,a=i&&Object.keys(i).length>0?i:void 0,o=await this.client.sandboxes.create(e,s,{cpu:4,memoryMiB:4096,timeoutMs:n,workdir:t.workingDirectory,env:a});return t.workingDirectory&&await(await o.exec(["chown","-R","user:user",t.workingDirectory],{timeoutMs:3e4})).wait(),new p(o,r)}async connect(t,e){let n=await this.client.sandboxes.fromId(t);return new p(n,"unknown")}async list(t){let e=[],n=t?.limit??100;try{for await(let r of this.client.sandboxes.list())if(e.push({sandboxId:r.sandboxId,image:"unknown",metadata:{},startedAt:new Date().toISOString()}),e.length>=n)break}catch{}return e}};function k(c={}){let t=c.tokenId??process.env.MODAL_TOKEN_ID,e=c.tokenSecret??process.env.MODAL_TOKEN_SECRET;if(!t||!e)throw new Error("Modal credentials required. Set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables, or pass tokenId/tokenSecret in config. Get your token at https://modal.com/settings/tokens");return new w({...c,tokenId:t,tokenSecret:e})}
4
+ exports.ModalProvider=w;exports.createModalProvider=k;
package/dist/index.d.cts CHANGED
@@ -181,6 +181,20 @@ interface ModalConfig {
181
181
  appName?: string;
182
182
  /** Default timeout in ms. Default: 3600000 (1 hour) */
183
183
  defaultTimeoutMs?: number;
184
+ /** Modal token ID. Falls back to MODAL_TOKEN_ID env var */
185
+ tokenId?: string;
186
+ /** Modal token secret. Falls back to MODAL_TOKEN_SECRET env var */
187
+ tokenSecret?: string;
188
+ /** Modal API endpoint. Default: https://api.modal.com:443 */
189
+ endpoint?: string;
190
+ }
191
+ /** Internal resolved config with required credentials */
192
+ interface ResolvedModalConfig {
193
+ tokenId: string;
194
+ tokenSecret: string;
195
+ appName?: string;
196
+ defaultTimeoutMs?: number;
197
+ endpoint?: string;
184
198
  }
185
199
  declare class ModalProvider implements SandboxProvider {
186
200
  readonly providerType: "modal";
@@ -188,7 +202,7 @@ declare class ModalProvider implements SandboxProvider {
188
202
  private readonly appName;
189
203
  private readonly defaultTimeoutMs;
190
204
  private _app;
191
- constructor(config?: ModalConfig);
205
+ constructor(config: ResolvedModalConfig);
192
206
  private getApp;
193
207
  create(options: SandboxCreateOptions): Promise<SandboxInstance>;
194
208
  connect(sandboxId: string, _timeoutMs?: number): Promise<SandboxInstance>;
@@ -197,10 +211,10 @@ declare class ModalProvider implements SandboxProvider {
197
211
  /**
198
212
  * Create Modal sandbox provider.
199
213
  *
200
- * Requires MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables,
201
- * or a .modal.toml configuration file.
214
+ * @param config - Optional configuration. If credentials not provided, reads from env vars.
215
+ * @throws Error if credentials cannot be resolved
202
216
  *
203
- * @param config - Optional configuration
217
+ * @see https://github.com/evolving-machines-lab/evolve/issues/8
204
218
  */
205
219
  declare function createModalProvider(config?: ModalConfig): SandboxProvider;
206
220
 
package/dist/index.d.ts CHANGED
@@ -181,6 +181,20 @@ interface ModalConfig {
181
181
  appName?: string;
182
182
  /** Default timeout in ms. Default: 3600000 (1 hour) */
183
183
  defaultTimeoutMs?: number;
184
+ /** Modal token ID. Falls back to MODAL_TOKEN_ID env var */
185
+ tokenId?: string;
186
+ /** Modal token secret. Falls back to MODAL_TOKEN_SECRET env var */
187
+ tokenSecret?: string;
188
+ /** Modal API endpoint. Default: https://api.modal.com:443 */
189
+ endpoint?: string;
190
+ }
191
+ /** Internal resolved config with required credentials */
192
+ interface ResolvedModalConfig {
193
+ tokenId: string;
194
+ tokenSecret: string;
195
+ appName?: string;
196
+ defaultTimeoutMs?: number;
197
+ endpoint?: string;
184
198
  }
185
199
  declare class ModalProvider implements SandboxProvider {
186
200
  readonly providerType: "modal";
@@ -188,7 +202,7 @@ declare class ModalProvider implements SandboxProvider {
188
202
  private readonly appName;
189
203
  private readonly defaultTimeoutMs;
190
204
  private _app;
191
- constructor(config?: ModalConfig);
205
+ constructor(config: ResolvedModalConfig);
192
206
  private getApp;
193
207
  create(options: SandboxCreateOptions): Promise<SandboxInstance>;
194
208
  connect(sandboxId: string, _timeoutMs?: number): Promise<SandboxInstance>;
@@ -197,10 +211,10 @@ declare class ModalProvider implements SandboxProvider {
197
211
  /**
198
212
  * Create Modal sandbox provider.
199
213
  *
200
- * Requires MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables,
201
- * or a .modal.toml configuration file.
214
+ * @param config - Optional configuration. If credentials not provided, reads from env vars.
215
+ * @throws Error if credentials cannot be resolved
202
216
  *
203
- * @param config - Optional configuration
217
+ * @see https://github.com/evolving-machines-lab/evolve/issues/8
204
218
  */
205
219
  declare function createModalProvider(config?: ModalConfig): SandboxProvider;
206
220
 
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- import {ModalClient}from'modal';import {pack}from'tar-stream';var b={"evolve-all":"evolvingmachines/evolve-all:latest"},h=new Set([".xlsx",".xls",".docx",".doc",".pptx",".ppt",".pdf",".zip",".tar",".gz",".7z",".rar",".png",".jpg",".jpeg",".gif",".webp",".ico",".bmp",".mp3",".wav",".ogg",".flac",".aac",".mp4",".avi",".mov",".mkv",".webm",".woff",".woff2",".ttf",".otf",".eot",".exe",".dll",".so",".dylib",".sqlite",".db",".pickle",".pkl",".parquet"]);function y(c){let t=c.substring(c.lastIndexOf(".")).toLowerCase();return h.has(t)}var l=class{constructor(t){this.sandbox=t;}wrapAsUser(t,e,n){let r="";n&&Object.keys(n).length>0&&(r=Object.entries(n).filter(([,o])=>o!=null).map(([o,a])=>`export ${o}='${String(a).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let s=e?`cd '${e.replace(/'/g,"'\\''")}' && ${r}${t}`:`${r}${t}`;return ["su","user","-c",`echo ${Buffer.from(s).toString("base64")} | base64 -d | bash`]}async run(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(r,e?.onStdout,e?.onStderr);return {exitCode:await r.wait(),stdout:s,stderr:i}}async spawn(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),s="",i="",o=this.accumulateStreams(r,e?.onStdout?d=>{e.onStdout(d);}:void 0,e?.onStderr?d=>{e.onStderr(d);}:void 0).then(({stdout:d,stderr:m})=>{s=d,i=m;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await o,{exitCode:await r.wait(),stdout:s,stderr:i}),kill:async()=>false}}async list(){let t=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await t.wait(),(await t.stdout.readText()).trim().split(`
2
- `).slice(1).map(r=>{let s=r.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(t,e){throw new Error("Modal does not support connecting to existing processes")}async sendStdin(t,e){throw new Error("Modal does not support sendStdin by process ID")}async kill(t){return await(await this.sandbox.exec(["kill","-9",t],{timeoutMs:1e4})).wait()===0}async accumulateStreams(t,e,n){let r="",s="",i=[];return i.push((async()=>{try{for await(let o of t.stdout){let a=typeof o=="string"?o:new TextDecoder().decode(o);r+=a,e?.(a);}}catch{}})()),i.push((async()=>{try{for await(let o of t.stderr){let a=typeof o=="string"?o:new TextDecoder().decode(o);s+=a,n?.(a);}}catch{}})()),await Promise.all(i),{stdout:r,stderr:s}}},f=class{constructor(t){this.sandbox=t;}async read(t){if(y(t)){let r=await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"}),s=await r.wait();if(s!==0){let i=await r.stderr.readText();throw new Error(`Failed to read file ${t}: ${i||`exit code ${s}`}`)}return await r.stdout.readBytes()}let e=await this.sandbox.exec(["cat",t],{timeoutMs:3e5}),n=await e.wait();if(n!==0){let r=await e.stderr.readText();throw new Error(`Failed to read file ${t}: ${r||`exit code ${n}`}`)}return await e.stdout.readText()}async write(t,e){let n=this.toBuffer(e),r=t.substring(0,t.lastIndexOf("/"));r&&await this.makeDir(r);let s=t.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await i.stdin.writeBytes(new Uint8Array(n)),await i.stdin.getWriter().close(),await i.wait(),await(await this.sandbox.exec(["chown","user:user",t],{timeoutMs:1e4})).wait();}async writeBatch(t){let e=pack(),n=[],r=new Set;for(let a of t){let d=this.toBuffer(a.data),m=a.path.startsWith("/")?a.path.slice(1):a.path;e.entry({name:m},d);let p=a.path.substring(0,a.path.lastIndexOf("/"));p&&r.add(p);}e.finalize();for await(let a of e)n.push(Buffer.from(a));let s=Buffer.concat(n),i=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await i.stdin.writeBytes(new Uint8Array(s)),await i.stdin.getWriter().close(),await i.wait(),r.size>0){let a=Array.from(r),d=new Set(a.map(m=>m.split("/").slice(0,4).join("/")));for(let m of d)await(await this.sandbox.exec(["chown","-R","user:user",m],{timeoutMs:3e4})).wait();}}async makeDir(t){await(await this.sandbox.exec(["mkdir","-p",t],{timeoutMs:1e4})).wait(),await(await this.sandbox.exec(["chown","-R","user:user",t],{timeoutMs:1e4})).wait();}async exists(t){return await(await this.sandbox.exec(["test","-e",t],{timeoutMs:1e4})).wait()===0}async list(t){let e=t.replace(/'/g,"'\\''"),n=await this.sandbox.exec(["bash","-c",`ls -la '${e}' | tail -n +2`],{timeoutMs:3e4});await n.wait();let r=await n.stdout.readText(),s=[];for(let i of r.trim().split(`
3
- `)){if(!i)continue;let o=i.split(/\s+/);if(o.length<9)continue;let a=o[0],d=o.slice(8).join(" ");d==="."||d===".."||s.push({name:d,path:t.endsWith("/")?`${t}${d}`:`${t}/${d}`,type:a.startsWith("d")?"dir":"file"});}return s}async remove(t){await(await this.sandbox.exec(["rm","-rf",t],{timeoutMs:3e4})).wait();}async rename(t,e){await(await this.sandbox.exec(["mv",t,e],{timeoutMs:3e4})).wait();}async readStream(t){return (await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"})).stdout}async writeStream(t,e){let n=t.substring(0,t.lastIndexOf("/"));n&&await this.makeDir(n);let r=t.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${r}'`],{mode:"binary"}),i=e.getReader();try{for(;;){let{done:a,value:d}=await i.read();if(a)break;await s.stdin.writeBytes(d);}}finally{i.releaseLock();}await s.stdin.getWriter().close(),await s.wait();}async uploadUrl(t,e){throw new Error("Modal does not support pre-signed upload URLs")}async downloadUrl(t,e){throw new Error("Modal does not support pre-signed download URLs")}async watchDir(t,e,n){throw new Error("Modal does not support watchDir")}toBuffer(t){if(typeof t=="string")return Buffer.from(t,"utf-8");if(t instanceof Buffer)return t;if(t instanceof ArrayBuffer||t instanceof Uint8Array)return Buffer.from(t);throw new Error(`Unsupported data type: ${typeof t}`)}},u=class{constructor(t,e){this.sandbox=t;this.commands=new l(t),this.files=new f(t),this.image=e,this.startTime=new Date;}commands;files;image;startTime;get sandboxId(){return this.sandbox.sandboxId}async getHost(t){let n=(await this.sandbox.tunnels())[t];if(!n)throw new Error(`No tunnel found for port ${t}`);return n.url}async isRunning(){try{return await(await this.sandbox.exec(["echo","ping"],{timeoutMs:5e3})).wait(),!0}catch{return false}}async getInfo(){return {sandboxId:this.sandbox.sandboxId,image:this.image,metadata:{},startedAt:this.startTime.toISOString()}}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(t=>setTimeout(t,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause. Use kill() instead.")}},w=class{providerType="modal";client;appName;defaultTimeoutMs;_app;constructor(t={}){this.client=new ModalClient,this.appName=t.appName??"evolve-sandbox",this.defaultTimeoutMs=t.defaultTimeoutMs??36e5;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async create(t){let e=await this.getApp(),n=t.timeoutMs??this.defaultTimeoutMs,r=b[t.image]??t.image,s=this.client.images.fromRegistry(r),i=t.envs?Object.fromEntries(Object.entries(t.envs).filter(([,d])=>d!=null)):void 0,o=i&&Object.keys(i).length>0?i:void 0,a=await this.client.sandboxes.create(e,s,{cpu:4,memoryMiB:4096,timeoutMs:n,workdir:t.workingDirectory,env:o});return t.workingDirectory&&await(await a.exec(["chown","-R","user:user",t.workingDirectory],{timeoutMs:3e4})).wait(),new u(a,r)}async connect(t,e){let n=await this.client.sandboxes.fromId(t);return new u(n,"unknown")}async list(t){let e=[],n=t?.limit??100;try{for await(let r of this.client.sandboxes.list())if(e.push({sandboxId:r.sandboxId,image:"unknown",metadata:{},startedAt:new Date().toISOString()}),e.length>=n)break}catch{}return e}};function M(c={}){return new w(c)}export{w as ModalProvider,M as createModalProvider};
1
+ import {ModalClient}from'modal';import {pack}from'tar-stream';var b={"evolve-all":"evolvingmachines/evolve-all:latest"},h=new Set([".xlsx",".xls",".docx",".doc",".pptx",".ppt",".pdf",".zip",".tar",".gz",".7z",".rar",".png",".jpg",".jpeg",".gif",".webp",".ico",".bmp",".mp3",".wav",".ogg",".flac",".aac",".mp4",".avi",".mov",".mkv",".webm",".woff",".woff2",".ttf",".otf",".eot",".exe",".dll",".so",".dylib",".sqlite",".db",".pickle",".pkl",".parquet"]);function y(c){let t=c.substring(c.lastIndexOf(".")).toLowerCase();return h.has(t)}var l=class{constructor(t){this.sandbox=t;}wrapAsUser(t,e,n){let r="";n&&Object.keys(n).length>0&&(r=Object.entries(n).filter(([,a])=>a!=null).map(([a,o])=>`export ${a}='${String(o).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let s=e?`cd '${e.replace(/'/g,"'\\''")}' && ${r}${t}`:`${r}${t}`;return ["su","user","-c",`echo ${Buffer.from(s).toString("base64")} | base64 -d | bash`]}async run(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(r,e?.onStdout,e?.onStderr);return {exitCode:await r.wait(),stdout:s,stderr:i}}async spawn(t,e){let n=this.wrapAsUser(t,e?.cwd,e?.envs),r=await this.sandbox.exec(n,{timeoutMs:e?.timeoutMs}),s="",i="",a=this.accumulateStreams(r,e?.onStdout?d=>{e.onStdout(d);}:void 0,e?.onStderr?d=>{e.onStderr(d);}:void 0).then(({stdout:d,stderr:m})=>{s=d,i=m;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await a,{exitCode:await r.wait(),stdout:s,stderr:i}),kill:async()=>false}}async list(){let t=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await t.wait(),(await t.stdout.readText()).trim().split(`
2
+ `).slice(1).map(r=>{let s=r.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(t,e){throw new Error("Modal does not support connecting to existing processes")}async sendStdin(t,e){throw new Error("Modal does not support sendStdin by process ID")}async kill(t){return await(await this.sandbox.exec(["kill","-9",t],{timeoutMs:1e4})).wait()===0}async accumulateStreams(t,e,n){let r="",s="",i=[];return i.push((async()=>{try{for await(let a of t.stdout){let o=typeof a=="string"?a:new TextDecoder().decode(a);r+=o,e?.(o);}}catch{}})()),i.push((async()=>{try{for await(let a of t.stderr){let o=typeof a=="string"?a:new TextDecoder().decode(a);s+=o,n?.(o);}}catch{}})()),await Promise.all(i),{stdout:r,stderr:s}}},f=class{constructor(t){this.sandbox=t;}async read(t){if(y(t)){let r=await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"}),s=await r.wait();if(s!==0){let i=await r.stderr.readText();throw new Error(`Failed to read file ${t}: ${i||`exit code ${s}`}`)}return await r.stdout.readBytes()}let e=await this.sandbox.exec(["cat",t],{timeoutMs:3e5}),n=await e.wait();if(n!==0){let r=await e.stderr.readText();throw new Error(`Failed to read file ${t}: ${r||`exit code ${n}`}`)}return await e.stdout.readText()}async write(t,e){let n=this.toBuffer(e),r=t.substring(0,t.lastIndexOf("/"));r&&await this.makeDir(r);let s=t.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await i.stdin.writeBytes(new Uint8Array(n)),await i.stdin.getWriter().close(),await i.wait(),await(await this.sandbox.exec(["chown","user:user",t],{timeoutMs:1e4})).wait();}async writeBatch(t){let e=pack(),n=[],r=new Set;for(let o of t){let d=this.toBuffer(o.data),m=o.path.startsWith("/")?o.path.slice(1):o.path;e.entry({name:m},d);let u=o.path.substring(0,o.path.lastIndexOf("/"));u&&r.add(u);}e.finalize();for await(let o of e)n.push(Buffer.from(o));let s=Buffer.concat(n),i=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await i.stdin.writeBytes(new Uint8Array(s)),await i.stdin.getWriter().close(),await i.wait(),r.size>0){let o=Array.from(r),d=new Set(o.map(m=>m.split("/").slice(0,4).join("/")));for(let m of d)await(await this.sandbox.exec(["chown","-R","user:user",m],{timeoutMs:3e4})).wait();}}async makeDir(t){await(await this.sandbox.exec(["mkdir","-p",t],{timeoutMs:1e4})).wait(),await(await this.sandbox.exec(["chown","-R","user:user",t],{timeoutMs:1e4})).wait();}async exists(t){return await(await this.sandbox.exec(["test","-e",t],{timeoutMs:1e4})).wait()===0}async list(t){let e=t.replace(/'/g,"'\\''"),n=await this.sandbox.exec(["bash","-c",`ls -la '${e}' | tail -n +2`],{timeoutMs:3e4});await n.wait();let r=await n.stdout.readText(),s=[];for(let i of r.trim().split(`
3
+ `)){if(!i)continue;let a=i.split(/\s+/);if(a.length<9)continue;let o=a[0],d=a.slice(8).join(" ");d==="."||d===".."||s.push({name:d,path:t.endsWith("/")?`${t}${d}`:`${t}/${d}`,type:o.startsWith("d")?"dir":"file"});}return s}async remove(t){await(await this.sandbox.exec(["rm","-rf",t],{timeoutMs:3e4})).wait();}async rename(t,e){await(await this.sandbox.exec(["mv",t,e],{timeoutMs:3e4})).wait();}async readStream(t){return (await this.sandbox.exec(["cat",t],{timeoutMs:3e5,mode:"binary"})).stdout}async writeStream(t,e){let n=t.substring(0,t.lastIndexOf("/"));n&&await this.makeDir(n);let r=t.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${r}'`],{mode:"binary"}),i=e.getReader();try{for(;;){let{done:o,value:d}=await i.read();if(o)break;await s.stdin.writeBytes(d);}}finally{i.releaseLock();}await s.stdin.getWriter().close(),await s.wait();}async uploadUrl(t,e){throw new Error("Modal does not support pre-signed upload URLs")}async downloadUrl(t,e){throw new Error("Modal does not support pre-signed download URLs")}async watchDir(t,e,n){throw new Error("Modal does not support watchDir")}toBuffer(t){if(typeof t=="string")return Buffer.from(t,"utf-8");if(t instanceof Buffer)return t;if(t instanceof ArrayBuffer||t instanceof Uint8Array)return Buffer.from(t);throw new Error(`Unsupported data type: ${typeof t}`)}},p=class{constructor(t,e){this.sandbox=t;this.commands=new l(t),this.files=new f(t),this.image=e,this.startTime=new Date;}commands;files;image;startTime;get sandboxId(){return this.sandbox.sandboxId}async getHost(t){let n=(await this.sandbox.tunnels())[t];if(!n)throw new Error(`No tunnel found for port ${t}`);return n.url}async isRunning(){try{return await(await this.sandbox.exec(["echo","ping"],{timeoutMs:5e3})).wait(),!0}catch{return false}}async getInfo(){return {sandboxId:this.sandbox.sandboxId,image:this.image,metadata:{},startedAt:this.startTime.toISOString()}}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(t=>setTimeout(t,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause. Use kill() instead.")}},w=class{providerType="modal";client;appName;defaultTimeoutMs;_app;constructor(t){!t.endpoint&&process.env.MODAL_SERVER_URL?.startsWith("unix:")?process.env.MODAL_SERVER_URL="https://api.modal.com:443":t.endpoint&&(process.env.MODAL_SERVER_URL=t.endpoint),this.client=new ModalClient({tokenId:t.tokenId,tokenSecret:t.tokenSecret}),this.appName=t.appName??"evolve-sandbox",this.defaultTimeoutMs=t.defaultTimeoutMs??36e5;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async create(t){let e=await this.getApp(),n=t.timeoutMs??this.defaultTimeoutMs,r=b[t.image]??t.image,s=this.client.images.fromRegistry(r),i=t.envs?Object.fromEntries(Object.entries(t.envs).filter(([,d])=>d!=null)):void 0,a=i&&Object.keys(i).length>0?i:void 0,o=await this.client.sandboxes.create(e,s,{cpu:4,memoryMiB:4096,timeoutMs:n,workdir:t.workingDirectory,env:a});return t.workingDirectory&&await(await o.exec(["chown","-R","user:user",t.workingDirectory],{timeoutMs:3e4})).wait(),new p(o,r)}async connect(t,e){let n=await this.client.sandboxes.fromId(t);return new p(n,"unknown")}async list(t){let e=[],n=t?.limit??100;try{for await(let r of this.client.sandboxes.list())if(e.push({sandboxId:r.sandboxId,image:"unknown",metadata:{},startedAt:new Date().toISOString()}),e.length>=n)break}catch{}return e}};function k(c={}){let t=c.tokenId??process.env.MODAL_TOKEN_ID,e=c.tokenSecret??process.env.MODAL_TOKEN_SECRET;if(!t||!e)throw new Error("Modal credentials required. Set MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables, or pass tokenId/tokenSecret in config. Get your token at https://modal.com/settings/tokens");return new w({...c,tokenId:t,tokenSecret:e})}
4
+ export{w as ModalProvider,k as createModalProvider};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolvingmachines/modal",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "keywords": [
5
5
  "ai",
6
6
  "agents",