@evolvingmachines/modal 0.0.60 → 0.0.61

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,4 +1,3 @@
1
- 'use strict';var modal=require('modal'),tarStream=require('tar-stream');var p="c-4bfce70a407f";var D={[`evolve-all-${p}`]:`evolvingmachines/evolve-all:${p}`,"evolve-all":"evolvingmachines/evolve-all"},$=1440*60*1e3,g=8*1024*1024;async function*Q(r){let e=r.getReader();try{for(;;){let{done:t,value:n}=await e.read();if(t)break;n&&(yield n);}}finally{e.releaseLock();}}var N="user",T="evolve.image",B="evolve.startedAt",v=class extends Error{requestedTimeoutMs;constructor(e){let t=(e/36e5).toFixed(1);super(`Modal sandboxes have a hard 24h lifetime cap; requested timeout was ${t}h. For sessions longer than 24h, persist progress with Evolve checkpoints and resume in a fresh sandbox instead of extending the timeout.`),this.name="ModalSandboxLifetimeError",this.requestedTimeoutMs=e;}};function L(r){if(r>$)throw new v(r)}var f=class extends Error{requestedIdleTimeoutMs;constructor(e,t){super(`Modal idleTimeoutMs of ${e}ms is invalid: ${t}`),this.name="ModalIdleTimeoutError",this.requestedIdleTimeoutMs=e;}};function U(r){if(r===void 0)return {};if(!Number.isFinite(r)||r<=0)throw new f(r,"it must be a positive number of milliseconds");if(r>$)throw new f(r,"it exceeds Modal's 24h lifetime cap, so the sandbox would always die of the lifetime first");return {idleTimeoutMs:r}}var P=class extends Error{constructor(e){super(`Modal bootCommand is invalid: ${e}`),this.name="ModalBootCommandError";}};function V(r){if(r===void 0)return {};if(r.length===0)throw new P(`an empty argv is Modal's own "no args" wire shape and would boot the image's ENTRYPOINT/CMD \u2014 omit bootCommand to boot the image's command`);return {command:[...r]}}var m=class extends Error{reason;mountPath;volumeName;constructor(e,t,n,o,s){super(o,s===void 0?void 0:{cause:s}),this.name="ModalVolumeError",this.reason=e,this.mountPath=t,this.volumeName=n;}};function F(r){if(!r)return [];let e=[];for(let[t,n]of Object.entries(r)){if(!t.startsWith("/")||t==="/")throw new m("invalid-mount-path",t,n.name,`Modal Volume mount path ${JSON.stringify(t)} is invalid: it must be an absolute in-box path other than "/"`);let o=n.name?.trim()??"";if(o.length===0)throw new m("invalid-name",t,n.name??"",`Modal Volume mounted at ${t} has a blank name`);e.push({mountPath:t,name:o,readOnly:n.readOnly===true,createIfMissing:n.createIfMissing===true});}return e}function I(r,e,t,n){let o="";n&&Object.keys(n).length>0&&(o=Object.entries(n).filter(([,a])=>a!=null).map(([a,d])=>`export ${a}='${String(d).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let s=t?`cd '${t.replace(/'/g,"'\\''")}' && ${o}${r}`:`${o}${r}`;if(e==="root")return ["bash","-c",s];let i=Buffer.from(s).toString("base64");return ["su",e,"-c",`echo ${i} | base64 -d | bash`]}var h=class extends Error{constructor(e){super(e),this.name="ModalResourcesError";}},Z=4,ee=4096;function W(r){if(r?.disk!==void 0)throw new h(`Modal's JS SDK has no create-time disk-size parameter, so a ${r.disk} GiB disk request cannot be enforced. Drop \`resources.disk\` (containers get Modal's default disk quota) or run on a provider that sizes disk.`);if((r?.gpu===void 0||r.gpu<=0)&&r?.gpuTypes?.length)throw new h("resources.gpuTypes was set without a positive resources.gpu count \u2014 declare how many GPUs to reserve, or drop gpuTypes.");let e=r?.gpu!==void 0&&r.gpu>0?`${r.gpuTypes?.length?r.gpuTypes[0]:"any"}:${r.gpu}`:void 0;return {cpu:r?.cpu??Z,memoryMiB:r?.memory!==void 0?Math.ceil(r.memory*1024):ee,...e!==void 0?{gpu:e}:{}}}var w=class extends Error{reason;destination;constructor(e,t,n){super(t),this.name="ModalNetworkPolicyError",this.reason=e,this.destination=n;}};function q(r){let e=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,3}))?$/.exec(r);return !(!e||[e[1],e[2],e[3],e[4]].some(t=>Number(t)>255)||e[5]!==void 0&&Number(e[5])>32)}function te(r){return /^\d{1,3}(\.\d{1,3}){3}(\/\d+)?$/.test(r)&&!q(r)}function ne(r){return r.startsWith("[")?true:(r.match(/:/g)?.length??0)>=2}function re(r){return /^[^:]+:\d+$/.test(r)}function E(r){if(!r||r.outbound==="open"){if(r?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {}}let e=r.allowedDestinations??[];if(e.length===0)return {blockNetwork:true};let t=[],n=[];for(let o of e)if(q(o))t.push(o.includes("/")?o:`${o}/32`);else {if(te(o))throw new w("invalid-ipv4",`"${o}" is not a valid IPv4 address or CIDR (octets must be 0-255, prefix 0-32). Fix the address or list a hostname instead.`,o);if(ne(o))t.push(o.includes("/")?o:`${o}/128`);else {if(re(o))throw new w("port-unsupported",`Modal's network allowlist filters hosts and IPs only and cannot match a port; drop the ":<port>" from "${o}" and list just the host or IP.`,o);n.push(o);}}return {outboundCidrAllowlist:t,outboundDomainAllowlist:n}}function b(r){if(!r||r.outbound==="open"){if(r?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {outboundCidrAllowlist:["0.0.0.0/0"],outboundDomainAllowlist:["*"]}}let e=E(r);return {outboundCidrAllowlist:e.outboundCidrAllowlist??[],outboundDomainAllowlist:se(e.outboundDomainAllowlist??[])}}var oe="sealed.invalid";function se(r){return r.length>0?r:[oe]}function G(r,e){if(!e||e.length===0)return false;let t=o=>o===void 0?"open|":`${o.outbound}|${[...new Set(o.allowedDestinations??[])].sort().join(",")}`,n=t(r);return e.some(o=>t(o)!==n)}function H(r){let e=r.split("/")[0];return /^\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com$/.test(e)?"aws-ecr":e==="gcr.io"||e.endsWith(".gcr.io")||e.endsWith("-docker.pkg.dev")?"gcp-artifact-registry":"registry"}function R(r,e,t){let{[T]:n,[B]:o,...s}=e;return {sandboxId:r,image:n??t??"",metadata:s,startedAt:o??""}}var O=1e4,M=class{constructor(e,t){this.sandbox=e;this.user=t;}async run(e,t){let n=I(e,this.user,t?.cwd,t?.envs),o=await this.sandbox.exec(n,{timeoutMs:t?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(o,t?.onStdout,t?.onStderr);return {exitCode:await o.wait(),stdout:s,stderr:i}}async spawn(e,t){let n=I(e,this.user,t?.cwd,t?.envs),o=await this.sandbox.exec(n,{timeoutMs:t?.timeoutMs}),s="",i="",a=this.accumulateStreams(o,t?.onStdout?c=>{t.onStdout(c);}:void 0,t?.onStderr?c=>{t.onStderr(c);}:void 0).then(({stdout:c,stderr:u})=>{s=c,i=u;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await a,{exitCode:await o.wait(),stdout:s,stderr:i}),kill:async()=>false}}async list(){let e=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await e.wait(),(await e.stdout.readText()).trim().split(`
2
- `).slice(1).map(o=>{let s=o.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(e,t){throw new Error("Modal does not support connecting to existing processes")}async sendStdin(e,t){throw new Error("Modal does not support sendStdin by process ID")}async kill(e){return await(await this.sandbox.exec(["kill","-9",e],{timeoutMs:1e4})).wait()===0}async accumulateStreams(e,t,n){let o="",s="",i=[];return i.push((async()=>{try{for await(let a of e.stdout){let d=typeof a=="string"?a:new TextDecoder().decode(a);o+=d,t?.(d);}}catch{}})()),i.push((async()=>{try{for await(let a of e.stderr){let d=typeof a=="string"?a:new TextDecoder().decode(a);s+=d,n?.(d);}}catch{}})()),await Promise.all(i),{stdout:o,stderr:s}}},A=class{constructor(e,t){this.sandbox=e;this.user=t;}async chownToUser(e,t=false){if(this.user==="root")return;let n=t?["chown","-R",`${this.user}:${this.user}`,e]:["chown",`${this.user}:${this.user}`,e];await(await this.sandbox.exec(n,{timeoutMs:t?3e4:1e4})).wait();}async writeStdinChunked(e,t){for(let n=0;n<t.length;n+=g)await e.writeBytes(t.subarray(n,n+g));}async writeStdinCoalesced(e,t){let n=[],o=0,s=async()=>{if(o===0)return;let i=n.length===1?n[0]:Buffer.concat(n,o);n=[],o=0,await this.writeStdinChunked(e,i);};for await(let i of t)n.push(i),o+=i.length,o>=g&&await s();await s();}async read(e){let t=await this.sandbox.exec(["cat",e],{timeoutMs:3e5,mode:"binary"}),n=await t.wait();if(n!==0){let s=await t.stderr.readText();throw new Error(`Failed to read file ${e}: ${s||`exit code ${n}`}`)}let o=await t.stdout.readBytes();if(!o.includes(0))try{return new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}).decode(o)}catch{}return o}async write(e,t){let n=this.toBuffer(t),o=e.substring(0,e.lastIndexOf("/"));o&&await this.makeDir(o);let s=e.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await this.writeStdinChunked(i.stdin,new Uint8Array(n)),await i.stdin.getWriter().close(),await i.wait(),await this.chownToUser(e);}async writeBatch(e){let t=tarStream.pack(),n=new Set;for(let i of e){let a=this.toBuffer(i.data),d=i.path.startsWith("/")?i.path.slice(1):i.path;t.entry({name:d},a);let c=i.path.substring(0,i.path.lastIndexOf("/"));c&&n.add(c);}t.finalize();let o=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await this.writeStdinCoalesced(o.stdin,t),await o.stdin.getWriter().close(),await o.wait(),n.size>0){let i=Array.from(n),a=new Set(i.map(d=>d.split("/").slice(0,4).join("/")));for(let d of a)await this.chownToUser(d,true);}}async makeDir(e){await(await this.sandbox.exec(["mkdir","-p",e],{timeoutMs:1e4})).wait(),await this.chownToUser(e,true);}async exists(e){return await(await this.sandbox.exec(["test","-e",e],{timeoutMs:1e4})).wait()===0}async list(e){let t=e.replace(/'/g,"'\\''"),n=await this.sandbox.exec(["bash","-c",`ls -la '${t}' | tail -n +2`],{timeoutMs:3e4});await n.wait();let o=await n.stdout.readText(),s=[];for(let i of o.trim().split(`
3
- `)){if(!i)continue;let a=i.split(/\s+/);if(a.length<9)continue;let d=a[0],c=a.slice(8).join(" ");c==="."||c===".."||s.push({name:c,path:e.endsWith("/")?`${e}${c}`:`${e}/${c}`,type:d.startsWith("d")?"dir":"file"});}return s}async remove(e){await(await this.sandbox.exec(["rm","-rf",e],{timeoutMs:3e4})).wait();}async rename(e,t){await(await this.sandbox.exec(["mv",e,t],{timeoutMs:3e4})).wait();}async readStream(e){return (await this.sandbox.exec(["cat",e],{timeoutMs:3e5,mode:"binary"})).stdout}async writeStream(e,t){let n=e.substring(0,e.lastIndexOf("/"));n&&await this.makeDir(n);let o=e.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${o}'`],{mode:"binary"});await this.writeStdinCoalesced(s.stdin,Q(t)),await s.stdin.getWriter().close(),await s.wait(),await this.chownToUser(e);}async writeFromPath(e,t){let{createReadStream:n}=await import('fs'),{Readable:o}=await import('stream'),s=o.toWeb(n(t,{highWaterMark:g}));await this.writeStream(e,s);}async uploadUrl(e,t){throw new Error("Modal does not support pre-signed upload URLs")}async downloadUrl(e,t){throw new Error("Modal does not support pre-signed download URLs")}async watchDir(e,t,n){throw new Error("Modal does not support watchDir")}toBuffer(e){if(typeof e=="string")return Buffer.from(e,"utf-8");if(e instanceof Buffer)return e;if(e instanceof ArrayBuffer||e instanceof Uint8Array)return Buffer.from(e);throw new Error(`Unsupported data type: ${typeof e}`)}},x=class{constructor(e,t,n){this.sandbox=e;this.commands=new M(e,n),this.files=new A(e,n),this.image=t;}commands;files;image;get sandboxId(){return this.sandbox.sandboxId}async getHost(e){let n=(await this.sandbox.tunnels())[e];if(!n)throw new Error(`No tunnel found for port ${e}`);return n.url}async isRunning(){try{return await this.sandbox.poll()===null}catch{return false}}async getInfo(){let e=await this.sandbox.getTags();return R(this.sandbox.sandboxId,e,this.image)}async updateNetwork(e){await this.sandbox.updateNetworkPolicy(b(e));}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(e=>setTimeout(e,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause/resume. Persist progress with Evolve checkpoints and resume in a fresh sandbox, or use kill() to terminate.")}},k=class{providerType="modal";name="Modal";supportsBootCommand=true;supportsVolumes=true;client;appName;defaultTimeoutMs;imageName;imageSecretName;_app;sandboxUsers=new Map;constructor(e){!e.endpoint&&process.env.MODAL_SERVER_URL?.startsWith("unix:")?process.env.MODAL_SERVER_URL="https://api.modal.com:443":e.endpoint&&(process.env.MODAL_SERVER_URL=e.endpoint),this.client=new modal.ModalClient({tokenId:e.tokenId,tokenSecret:e.tokenSecret}),this.appName=e.appName??"evolve-sandbox",this.defaultTimeoutMs=e.defaultTimeoutMs??36e5,this.imageName=e.imageName??`evolve-all-${p}`,this.imageSecretName=e.imageSecretName;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async resolveImage(e){let t=H(e);if(t==="registry"){let o=this.imageSecretName?await this.client.secrets.fromName(this.imageSecretName):void 0;return this.client.images.fromRegistry(e,o)}if(!this.imageSecretName)throw new Error(`Private registry image "${e}" requires config.imageSecretName \u2014 the name of a Modal Secret holding registry credentials (AWS ECR: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION with read-only ECR IAM). Create one at https://modal.com/secrets`);let n=t==="aws-ecr"?await this.client.secrets.fromName(this.imageSecretName,{requiredKeys:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"]}):await this.client.secrets.fromName(this.imageSecretName);return t==="aws-ecr"?this.client.images.fromAwsEcr(e,n):this.client.images.fromGcpArtifactRegistry(e,n)}async resolveAndBuildImage(e){let t=D[e]??e;if(!/[/:@]/.test(t))try{return {tag:t,image:await this.client.images.fromName(t)}}catch(s){if(!(s instanceof modal.NotFoundError))throw s}let n=await this.getApp(),o=await this.resolveImage(t);return {tag:t,image:await o.build(n)}}async publishImageAs(e,t){if(!e.trim())throw new Error("publishImageAs requires a non-empty alias to publish under");let{image:n}=await this.resolveAndBuildImage(t||this.imageName);return await n.publish(e),e}async prepareImage(e){await this.resolveAndBuildImage(e||this.imageName);}async create(e){let t=e.timeoutMs??this.defaultTimeoutMs;L(t);let n=U(e.idleTimeoutMs),o=V(e.bootCommand),s=F(e.volumes);for(let l of e.phaseNetworkPolicies??[])b(l);let i=G(e.network,e.phaseNetworkPolicies)?b(e.network):E(e.network),a=W(e.resources),d=e.user??N,c=await this.getApp(),{tag:u,image:j}=await this.resolveAndBuildImage(e.image||this.imageName),C=await this.resolveVolumes(s),y=e.envs?Object.fromEntries(Object.entries(e.envs).filter(([,l])=>l!=null)):void 0,z=y&&Object.keys(y).length>0?y:void 0,Y={...e.metadata,[T]:u,[B]:new Date().toISOString()},S=await this.client.sandboxes.create(c,j,{cpu:a.cpu,memoryMiB:a.memoryMiB,...a.gpu!==void 0?{gpu:a.gpu}:{},timeoutMs:t,...n,...o,workdir:e.workingDirectory,env:z,tags:Y,...i,...C!==void 0?{volumes:C}:{}});return e.workingDirectory&&d!=="root"&&await(await S.exec(["chown","-R",`${d}:${d}`,e.workingDirectory],{timeoutMs:3e4})).wait(),this.sandboxUsers.set(S.sandboxId,d),new x(S,u,d)}async resolveVolumes(e){if(e.length===0)return;let t={};for(let n of e){let o;try{o=await this.client.volumes.fromName(n.name,{createIfMissing:n.createIfMissing});}catch(s){let i=s instanceof Error?s.message:String(s),a=s instanceof modal.NotFoundError||s instanceof Error&&s.name==="NotFoundError";throw new m(a?"not-found":"resolve-failed",n.mountPath,n.name,a?`Modal Volume "${n.name}" (to mount at ${n.mountPath}) does not exist and createIfMissing was not set: ${i}`:`Modal Volume "${n.name}" (to mount at ${n.mountPath}) could not be resolved: ${i}`,s)}t[n.mountPath]=o.withMountOptions({readOnly:n.readOnly});}return t}async connect(e,t){let n=await this.client.sandboxes.fromId(e),o=this.sandboxUsers.get(e)??N;return new x(n,void 0,o)}async list(e){let t=await this.walk(e);if(t.error&&!t.stoppedAtLimit)throw new Error(t.error);return t.sandboxes}async listAll(e){let{stoppedAtLimit:t,...n}=await this.walk(e);return n}async walk(e){if(e?.state&&!e.state.includes("running"))return {sandboxes:[],complete:true,pagesFetched:0,stoppedAtLimit:false};let t;try{t=await this.getApp();}catch(n){return {sandboxes:[],complete:false,pagesFetched:0,stoppedAtLimit:false,error:`sandbox list failed: ${n instanceof Error?n.message:String(n)}`}}return K(()=>this.client.sandboxes.list({appId:t.appId,tags:e?.metadata}),e?.limit)}async listSandboxIds(){let e=new Set;try{let t=await this.getApp();for await(let n of this.client.sandboxes.list({appId:t.appId}))e.add(n.sandboxId);return {ids:e,complete:!0}}catch{return {ids:new Set,complete:false}}}};async function K(r,e){let t=[];try{for await(let n of r()){if(e!==void 0&&t.length>=e)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!0,error:`stopped at the requested limit of ${e} with more sandboxes available`};if(t.length>=O)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!1,error:`sandbox list exceeded ${O} sandboxes`};let o=await n.getTags();t.push(R(n.sandboxId,o));}}catch(n){return {sandboxes:t,complete:false,pagesFetched:t.length,stoppedAtLimit:false,error:`sandbox list failed: ${n instanceof Error?n.message:String(n)}`}}return {sandboxes:t,complete:true,pagesFetched:t.length,stoppedAtLimit:false}}function ge(r={}){let e=r.tokenId??process.env.MODAL_TOKEN_ID,t=r.tokenSecret??process.env.MODAL_TOKEN_SECRET;if(!e||!t)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 k({...r,tokenId:e,tokenSecret:t})}var fe=I,he=D,we=E,be=b,xe=G,ye=W,Se=H,ve=R,Pe=K,Ie=L,Me=U,Ae=V,ke=F;
4
- exports.EVOLVE_IMAGE_VERSION=p;exports.MODAL_MAX_LIFETIME_MS=$;exports.MODAL_MAX_LIST_SANDBOXES=O;exports.MODAL_STDIN_CHUNK_BYTES=g;exports.ModalBootCommandError=P;exports.ModalCommands=M;exports.ModalFiles=A;exports.ModalIdleTimeoutError=f;exports.ModalNetworkPolicyError=w;exports.ModalProvider=k;exports.ModalResourcesError=h;exports.ModalSandboxLifetimeError=v;exports.ModalVolumeError=m;exports._testBuildSandboxInfo=ve;exports._testCollectSandboxes=Pe;exports._testDynamicNetworkPolicyParams=be;exports._testImageMap=he;exports._testMapBootCommand=Ae;exports._testMapIdleTimeout=Me;exports._testMapNetworkPolicy=we;exports._testMapResources=ye;exports._testMapVolumeMounts=ke;exports._testRequiresDynamicNetwork=xe;exports._testResolveImageRegistry=Se;exports._testValidateTimeout=Ie;exports._testWrapCommand=fe;exports.createModalProvider=ge;
1
+ 'use strict';var modal=require('modal'),tarStream=require('tar-stream'),fs=require('fs');var l=class extends Error{feature;provider;reason;constructor(e,t,r){super(`${t} does not support ${e}${r?`: ${r}`:""}`),this.name="SandboxFeatureUnsupportedError",this.feature=e,this.provider=t,this.reason=r;}},u=class extends Error{path;provider;constructor(e,t){super(`${t}: no such file or directory: ${e}`),this.name="SandboxPathNotFoundError",this.path=e,this.provider=t;}},w=class extends Error{sandboxId;provider;state;constructor(e,t,r){super(`${t} sandbox ${e} is not running (${r}); inspect never starts or resumes a sandbox`),this.name="SandboxNotRunningError",this.sandboxId=e,this.provider=t,this.state=r;}};function R(n,e){return !!n&&typeof n=="object"&&n.name===e}function ue(n){return R(n,"SandboxFeatureUnsupportedError")}function pe(n){return R(n,"SandboxPathNotFoundError")}function ge(n){return R(n,"SandboxNotRunningError")}var k=2;var _=21;function V(n){return `'${n.replace(/'/g,"'\\''")}'`}function W(n){if(!Number.isInteger(n.offset)||n.offset<0)throw new RangeError(`offset must be a non-negative integer, got ${n.offset}`);if(!Number.isInteger(n.length)||n.length<0)throw new RangeError(`length must be a non-negative integer, got ${n.length}`)}function G(n){if(typeof n=="number")return L((n&4095).toString(8));if(/^[0-7]{1,4}$/.test(n))return L(n);throw new RangeError(`not an octal mode: ${n}`)}function L(n){return n.padStart(4,"0")}function j(n){let e=n&fs.constants.S_IFMT;return e===fs.constants.S_IFDIR?"dir":e===fs.constants.S_IFLNK?"symlink":e===fs.constants.S_IFREG?"file":"other"}function K(n){let e=n instanceof Date?n:typeof n=="number"?new Date(n*1e3):new Date(n);if(Number.isNaN(e.getTime()))throw new RangeError(`not a timestamp: ${String(n)}`);return e.toISOString()}var x="c-4bfce70a407f";var J={[`evolve-all-${x}`]:`evolvingmachines/evolve-all:${x}`,"evolve-all":"evolvingmachines/evolve-all"},Q=1440*60*1e3,y=8*1024*1024;async function*we(n){let e=n.getReader();try{for(;;){let{done:t,value:r}=await e.read();if(t)break;r&&(yield r);}}finally{e.releaseLock();}}var $="user",Z="evolve.image",ee="evolve.startedAt",T=class extends Error{requestedTimeoutMs;constructor(e){let t=(e/36e5).toFixed(1);super(`Modal sandboxes have a hard 24h lifetime cap; requested timeout was ${t}h. For sessions longer than 24h, persist progress with Evolve checkpoints and resume in a fresh sandbox instead of extending the timeout.`),this.name="ModalSandboxLifetimeError",this.requestedTimeoutMs=e;}};function te(n){if(n>Q)throw new T(n)}var S=class extends Error{requestedIdleTimeoutMs;constructor(e,t){super(`Modal idleTimeoutMs of ${e}ms is invalid: ${t}`),this.name="ModalIdleTimeoutError",this.requestedIdleTimeoutMs=e;}};function ne(n){if(n===void 0)return {};if(!Number.isFinite(n)||n<=0)throw new S(n,"it must be a positive number of milliseconds");if(n>Q)throw new S(n,"it exceeds Modal's 24h lifetime cap, so the sandbox would always die of the lifetime first");return {idleTimeoutMs:n}}var N=class extends Error{constructor(e){super(`Modal bootCommand is invalid: ${e}`),this.name="ModalBootCommandError";}};function re(n){if(n===void 0)return {};if(n.length===0)throw new N(`an empty argv is Modal's own "no args" wire shape and would boot the image's ENTRYPOINT/CMD \u2014 omit bootCommand to boot the image's command`);return {command:[...n]}}var f=class extends Error{reason;mountPath;volumeName;constructor(e,t,r,o,s){super(o,s===void 0?void 0:{cause:s}),this.name="ModalVolumeError",this.reason=e,this.mountPath=t,this.volumeName=r;}};function oe(n){if(!n)return [];let e=[];for(let[t,r]of Object.entries(n)){if(!t.startsWith("/")||t==="/")throw new f("invalid-mount-path",t,r.name,`Modal Volume mount path ${JSON.stringify(t)} is invalid: it must be an absolute in-box path other than "/"`);let o=r.name?.trim()??"";if(o.length===0)throw new f("invalid-name",t,r.name??"",`Modal Volume mounted at ${t} has a blank name`);e.push({mountPath:t,name:o,readOnly:r.readOnly===true,createIfMissing:r.createIfMissing===true});}return e}function C(n,e,t,r,o){let s="";r&&Object.keys(r).length>0&&(s=Object.entries(r).filter(([,d])=>d!=null).map(([d,m])=>`export ${d}='${String(m).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let i=t?`cd '${t.replace(/'/g,"'\\''")}' && ${s}${n}`:`${s}${n}`;if(e==="root")return ["bash","-c",i];let a=Buffer.from(i).toString("base64");return ["su",e,o?.sameSession?"--session-command":"-c",`echo ${a} | base64 -d | bash`]}var v=class extends Error{constructor(e){super(e),this.name="ModalResourcesError";}},be=4,xe=4096;function se(n){if(n?.disk!==void 0)throw new v(`Modal's JS SDK has no create-time disk-size parameter, so a ${n.disk} GiB disk request cannot be enforced. Drop \`resources.disk\` (containers get Modal's default disk quota) or run on a provider that sizes disk.`);if((n?.gpu===void 0||n.gpu<=0)&&n?.gpuTypes?.length)throw new v("resources.gpuTypes was set without a positive resources.gpu count \u2014 declare how many GPUs to reserve, or drop gpuTypes.");let e=n?.gpu!==void 0&&n.gpu>0?`${n.gpuTypes?.length?n.gpuTypes[0]:"any"}:${n.gpu}`:void 0;return {cpu:n?.cpu??be,memoryMiB:n?.memory!==void 0?Math.ceil(n.memory*1024):xe,...e!==void 0?{gpu:e}:{}}}var P=class extends Error{reason;destination;constructor(e,t,r){super(t),this.name="ModalNetworkPolicyError",this.reason=e,this.destination=r;}};function ie(n){let e=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,3}))?$/.exec(n);return !(!e||[e[1],e[2],e[3],e[4]].some(t=>Number(t)>255)||e[5]!==void 0&&Number(e[5])>32)}function ye(n){return /^\d{1,3}(\.\d{1,3}){3}(\/\d+)?$/.test(n)&&!ie(n)}function Se(n){return n.startsWith("[")?true:(n.match(/:/g)?.length??0)>=2}function ve(n){return /^[^:]+:\d+$/.test(n)}function F(n){if(!n||n.outbound==="open"){if(n?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {}}let e=n.allowedDestinations??[];if(e.length===0)return {blockNetwork:true};let t=[],r=[];for(let o of e)if(ie(o))t.push(o.includes("/")?o:`${o}/32`);else {if(ye(o))throw new P("invalid-ipv4",`"${o}" is not a valid IPv4 address or CIDR (octets must be 0-255, prefix 0-32). Fix the address or list a hostname instead.`,o);if(Se(o))t.push(o.includes("/")?o:`${o}/128`);else {if(ve(o))throw new P("port-unsupported",`Modal's network allowlist filters hosts and IPs only and cannot match a port; drop the ":<port>" from "${o}" and list just the host or IP.`,o);r.push(o);}}return {outboundCidrAllowlist:t,outboundDomainAllowlist:r}}function I(n){if(!n||n.outbound==="open"){if(n?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {outboundCidrAllowlist:["0.0.0.0/0"],outboundDomainAllowlist:["*"]}}let e=F(n);return {outboundCidrAllowlist:e.outboundCidrAllowlist??[],outboundDomainAllowlist:Ie(e.outboundDomainAllowlist??[])}}var Pe="sealed.invalid";function Ie(n){return n.length>0?n:[Pe]}function ae(n,e){if(!e||e.length===0)return false;let t=o=>o===void 0?"open|":`${o.outbound}|${[...new Set(o.allowedDestinations??[])].sort().join(",")}`,r=t(n);return e.some(o=>t(o)!==r)}function de(n){let e=n.split("/")[0];return /^\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com$/.test(e)?"aws-ecr":e==="gcr.io"||e.endsWith(".gcr.io")||e.endsWith("-docker.pkg.dev")?"gcp-artifact-registry":"registry"}function B(n,e,t){let{[Z]:r,[ee]:o,...s}=e;return {sandboxId:n,image:r??t??"",metadata:s,startedAt:o??""}}var H=1e4,D=class{constructor(e,t){this.sandbox=e;this.user=t;}async run(e,t){let r=C(e,this.user,t?.cwd,t?.envs),o=await this.sandbox.exec(r,{timeoutMs:t?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(o,t?.onStdout,t?.onStderr);return {exitCode:await o.wait(),stdout:s,stderr:i}}async spawn(e,t){let r=t?.stdin===false,o=C(e,this.user,t?.cwd,t?.envs,{sameSession:r}),s=r?["bash","-c",Re,"bash",...o]:o,i=await this.sandbox.exec(s,{timeoutMs:t?.timeoutMs}),a="",d="",m=this.accumulateStreams(i,t?.onStdout?c=>{t.onStdout(c);}:void 0,t?.onStderr?c=>{t.onStderr(c);}:void 0).then(({stdout:c,stderr:h})=>{a=c,d=h;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await m,{exitCode:await i.wait(),stdout:a,stderr:d}),kill:async()=>{if(!r)throw new l("commands.kill","modal","the Modal SDK cannot signal a running process; spawn it with `stdin: false` and kill() ends it by closing its stdin");let c=i.stdin.getWriter();try{return await c.close(),!0}catch{return false}}}}async list(){let e=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await e.wait(),(await e.stdout.readText()).trim().split(`
2
+ `).slice(1).map(o=>{let s=o.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(e,t){throw new l("commands.connect","modal","the Modal SDK cannot attach to a process by id")}async sendStdin(e,t){throw new l("commands.sendStdin","modal","the Modal SDK cannot address a process's stdin by id")}async kill(e){return await(await this.sandbox.exec(["kill","-9",e],{timeoutMs:1e4})).wait()===0}async accumulateStreams(e,t,r){let o="",s="",i=[];return i.push((async()=>{try{for await(let a of e.stdout){let d=typeof a=="string"?a:new TextDecoder().decode(a);o+=d,t?.(d);}}catch{}})()),i.push((async()=>{try{for await(let a of e.stderr){let d=typeof a=="string"?a:new TextDecoder().decode(a);s+=d,r?.(d);}}catch{}})()),await Promise.all(i),{stdout:o,stderr:s}}},O=class{constructor(e,t){this.sandbox=e;this.user=t;}async chownToUser(e,t=false){if(this.user==="root")return;let r=t?["chown","-R",`${this.user}:${this.user}`,e]:["chown",`${this.user}:${this.user}`,e];await(await this.sandbox.exec(r,{timeoutMs:t?3e4:1e4})).wait();}async writeStdinChunked(e,t){for(let r=0;r<t.length;r+=y)await e.writeBytes(t.subarray(r,r+y));}async writeStdinCoalesced(e,t){let r=[],o=0,s=async()=>{if(o===0)return;let i=r.length===1?r[0]:Buffer.concat(r,o);r=[],o=0,await this.writeStdinChunked(e,i);};for await(let i of t)r.push(i),o+=i.length,o>=y&&await s();await s();}async read(e){let t=await this.sandbox.exec(["cat",e],{timeoutMs:3e5,mode:"binary"}),r=await t.wait();if(r!==0){let s=await t.stderr.readText();throw new Error(`Failed to read file ${e}: ${s||`exit code ${r}`}`)}let o=await t.stdout.readBytes();if(!o.includes(0))try{return new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}).decode(o)}catch{}return o}async write(e,t){let r=this.toBuffer(t),o=e.substring(0,e.lastIndexOf("/"));o&&await this.makeDir(o);let s=e.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await this.writeStdinChunked(i.stdin,new Uint8Array(r)),await i.stdin.getWriter().close(),await i.wait(),await this.chownToUser(e);}async writeBatch(e){let t=tarStream.pack(),r=new Set;for(let i of e){let a=this.toBuffer(i.data),d=i.path.startsWith("/")?i.path.slice(1):i.path;t.entry({name:d},a);let m=i.path.substring(0,i.path.lastIndexOf("/"));m&&r.add(m);}t.finalize();let o=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await this.writeStdinCoalesced(o.stdin,t),await o.stdin.getWriter().close(),await o.wait(),r.size>0){let i=Array.from(r),a=new Set(i.map(d=>d.split("/").slice(0,4).join("/")));for(let d of a)await this.chownToUser(d,true);}}async makeDir(e){await(await this.sandbox.exec(["mkdir","-p",e],{timeoutMs:1e4})).wait(),await this.chownToUser(e,true);}async exists(e){return await(await this.sandbox.exec(["test","-e",e],{timeoutMs:1e4})).wait()===0}async list(e){try{return (await this.sandbox.filesystem.listFiles(e)).map(r=>z(r))}catch(t){throw X(t)?new u(e,"modal"):t}}async stat(e){try{return z(await this.sandbox.filesystem.stat(e))}catch(t){throw X(t)?new u(e,"modal"):t}}async readRange(e,t){if(W(t),t.length===0)return new Uint8Array(0);let r=V(e),o=`[ -e ${r} ] || exit ${k}; [ -d ${r} ] && exit ${_}; tail -c +${t.offset+1} -- ${r} | head -c ${t.length}; s=("\${PIPESTATUS[@]}"); [ "\${s[1]}" -eq 0 ] || exit "\${s[1]}"; [ "\${s[0]}" -eq 0 ] || [ "\${s[0]}" -eq ${Ae} ] || exit "\${s[0]}"`,s=await this.sandbox.exec(["bash","-c",o],{mode:"binary",timeoutMs:Y}),i=await s.stdout.readBytes(),a=await s.wait();if(a===k)throw new u(e,"modal");if(a===_)throw new Error(`modal: ${e} is a directory`);if(a!==0){let d=new TextDecoder().decode(await s.stderr.readBytes()).trim();throw new Error(`modal: range read of ${e} failed (exit ${a}): ${d}`)}return i}async remove(e){await(await this.sandbox.exec(["rm","-rf",e],{timeoutMs:3e4})).wait();}async rename(e,t){await(await this.sandbox.exec(["mv",e,t],{timeoutMs:3e4})).wait();}async readStream(e){return (await this.sandbox.exec(["cat",e],{timeoutMs:Y,mode:"binary"})).stdout}async writeStream(e,t){let r=e.substring(0,e.lastIndexOf("/"));r&&await this.makeDir(r);let o=e.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${o}'`],{mode:"binary"});await this.writeStdinCoalesced(s.stdin,we(t)),await s.stdin.getWriter().close(),await s.wait(),await this.chownToUser(e);}async writeFromPath(e,t){let{createReadStream:r}=await import('fs'),{Readable:o}=await import('stream'),s=o.toWeb(r(t,{highWaterMark:y}));await this.writeStream(e,s);}async uploadUrl(e,t){throw new l("files.uploadUrl","modal","Modal issues no signed upload URLs")}async downloadUrl(e,t){throw new l("files.downloadUrl","modal","Modal issues no signed download URLs")}async watchDir(e,t,r){await this.stat(e);let o=this.sandbox.filesystem.watch(e,{recursive:r?.recursive??false,filter:[...Me]})[Symbol.asyncIterator](),s=false;return (async()=>{for(;;){let{done:i,value:a}=await o.next();if(i||s)break;for(let d of Ee(a))await t(d);}})().catch(()=>{}),{stop:async()=>{s=true,o.return?.().catch(()=>{});}}}toBuffer(e){if(typeof e=="string")return Buffer.from(e,"utf-8");if(e instanceof Buffer)return e;if(e instanceof ArrayBuffer||e instanceof Uint8Array)return Buffer.from(e);throw new Error(`Unsupported data type: ${typeof e}`)}};function z(n){let e=j(n.mode),t={name:n.name,path:n.path,type:e,size:n.size,mtime:K(n.modifiedTime),mode:G(n.permissions),owner:n.owner,group:n.group};return e==="symlink"&&n.symlinkTarget!==null&&(t.target=n.symlinkTarget),t}function X(n){return !!n&&typeof n=="object"&&n.name==="SandboxFilesystemNotFoundError"}var Me=["Create","Modify","Remove"];function Ee(n){if(n.eventType==="Create")return n.paths.map(e=>({path:e,type:"create"}));if(n.eventType==="Remove")return n.paths.map(e=>({path:e,type:"remove"}));if(n.eventType==="Modify"){let e=n.paths.length>=2?"rename":"write";return n.paths.map(t=>({path:t,type:e}))}return []}var Ae=141,Y=3e5,Re=["set -m","exec 3<&0",'"$@" </dev/null 3<&- & job=$!',"( cat <&3 >/dev/null; kill -TERM -- -$job 2>/dev/null ) & eof=$!","exec 2>/dev/null","trap 'kill -TERM -- -$job' TERM","wait $job; rc=$?","kill -TERM -- -$eof","exit $rc"].join("; "),p=class{constructor(e,t,r){this.sandbox=e;this.commands=new D(e,r),this.files=new O(e,r),this.image=t;}commands;files;image;get sandboxId(){return this.sandbox.sandboxId}async metrics(){throw new l("metrics","modal","Modal has no sandbox metrics API, and /proc and the cgroup files inside its sandbox report host-wide figures (measured 2026-09-16: MemTotal 1 TiB on a 1 GiB sandbox)")}async getHost(e){let r=(await this.sandbox.tunnels())[e];if(!r)throw new Error(`No tunnel found for port ${e}`);return r.url}async isRunning(){try{return await this.sandbox.poll()===null}catch{return false}}async getInfo(){let e=await this.sandbox.getTags();return B(this.sandbox.sandboxId,e,this.image)}async updateNetwork(e){await this.sandbox.updateNetworkPolicy(I(e));}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(e=>setTimeout(e,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause/resume. Persist progress with Evolve checkpoints and resume in a fresh sandbox, or use kill() to terminate.")}},U=class{providerType="modal";name="Modal";supportsBootCommand=true;supportsVolumes=true;client;appName;defaultTimeoutMs;imageName;imageSecretName;_app;sandboxUsers=new Map;constructor(e){!e.endpoint&&process.env.MODAL_SERVER_URL?.startsWith("unix:")?process.env.MODAL_SERVER_URL="https://api.modal.com:443":e.endpoint&&(process.env.MODAL_SERVER_URL=e.endpoint),this.client=new modal.ModalClient({tokenId:e.tokenId,tokenSecret:e.tokenSecret}),this.appName=e.appName??"evolve-sandbox",this.defaultTimeoutMs=e.defaultTimeoutMs??36e5,this.imageName=e.imageName??`evolve-all-${x}`,this.imageSecretName=e.imageSecretName;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async resolveImage(e){let t=de(e);if(t==="registry"){let o=this.imageSecretName?await this.client.secrets.fromName(this.imageSecretName):void 0;return this.client.images.fromRegistry(e,o)}if(!this.imageSecretName)throw new Error(`Private registry image "${e}" requires config.imageSecretName \u2014 the name of a Modal Secret holding registry credentials (AWS ECR: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION with read-only ECR IAM). Create one at https://modal.com/secrets`);let r=t==="aws-ecr"?await this.client.secrets.fromName(this.imageSecretName,{requiredKeys:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"]}):await this.client.secrets.fromName(this.imageSecretName);return t==="aws-ecr"?this.client.images.fromAwsEcr(e,r):this.client.images.fromGcpArtifactRegistry(e,r)}async resolveAndBuildImage(e){let t=J[e]??e;if(!/[/:@]/.test(t))try{return {tag:t,image:await this.client.images.fromName(t)}}catch(s){if(!(s instanceof modal.NotFoundError))throw s}let r=await this.getApp(),o=await this.resolveImage(t);return {tag:t,image:await o.build(r)}}async publishImageAs(e,t){if(!e.trim())throw new Error("publishImageAs requires a non-empty alias to publish under");let{image:r}=await this.resolveAndBuildImage(t||this.imageName);return await r.publish(e),e}async prepareImage(e){await this.resolveAndBuildImage(e||this.imageName);}async create(e){let t=e.timeoutMs??this.defaultTimeoutMs;te(t);let r=ne(e.idleTimeoutMs),o=re(e.bootCommand),s=oe(e.volumes);for(let g of e.phaseNetworkPolicies??[])I(g);let i=ae(e.network,e.phaseNetworkPolicies)?I(e.network):F(e.network),a=se(e.resources),d=e.user??$,m=await this.getApp(),{tag:M,image:c}=await this.resolveAndBuildImage(e.image||this.imageName),h=await this.resolveVolumes(s),E=e.envs?Object.fromEntries(Object.entries(e.envs).filter(([,g])=>g!=null)):void 0,le=E&&Object.keys(E).length>0?E:void 0,me={...e.metadata,[Z]:M,[ee]:new Date().toISOString()},A=await this.client.sandboxes.create(m,c,{cpu:a.cpu,memoryMiB:a.memoryMiB,...a.gpu!==void 0?{gpu:a.gpu}:{},timeoutMs:t,...r,...o,workdir:e.workingDirectory,env:le,tags:me,...i,...h!==void 0?{volumes:h}:{}});return e.workingDirectory&&d!=="root"&&await(await A.exec(["chown","-R",`${d}:${d}`,e.workingDirectory],{timeoutMs:3e4})).wait(),this.sandboxUsers.set(A.sandboxId,d),new p(A,M,d)}async resolveVolumes(e){if(e.length===0)return;let t={};for(let r of e){let o;try{o=await this.client.volumes.fromName(r.name,{createIfMissing:r.createIfMissing});}catch(s){let i=s instanceof Error?s.message:String(s),a=s instanceof modal.NotFoundError||s instanceof Error&&s.name==="NotFoundError";throw new f(a?"not-found":"resolve-failed",r.mountPath,r.name,a?`Modal Volume "${r.name}" (to mount at ${r.mountPath}) does not exist and createIfMissing was not set: ${i}`:`Modal Volume "${r.name}" (to mount at ${r.mountPath}) could not be resolved: ${i}`,s)}t[r.mountPath]=o.withMountOptions({readOnly:r.readOnly});}return t}async connect(e,t){let r=await this.client.sandboxes.fromId(e),o=this.sandboxUsers.get(e)??$;return new p(r,void 0,o)}async inspect(e,t){let r=await this.attachSandbox(e),o=await r.poll();if(o!==null)throw new w(e,"modal",`exited with code ${o}`);let s=t?.user??this.sandboxUsers.get(e)??$;return new p(r,void 0,s)}async attachSandbox(e){return this.client.sandboxes.fromId(e)}async list(e){let t=await this.walk(e);if(t.error&&!t.stoppedAtLimit)throw new Error(t.error);return t.sandboxes}async listAll(e){let{stoppedAtLimit:t,...r}=await this.walk(e);return r}async walk(e){if(e?.state&&!e.state.includes("running"))return {sandboxes:[],complete:true,pagesFetched:0,stoppedAtLimit:false};let t;try{t=await this.getApp();}catch(r){return {sandboxes:[],complete:false,pagesFetched:0,stoppedAtLimit:false,error:`sandbox list failed: ${r instanceof Error?r.message:String(r)}`}}return ce(()=>this.client.sandboxes.list({appId:t.appId,tags:e?.metadata}),e?.limit)}async listSandboxIds(){let e=new Set;try{let t=await this.getApp();for await(let r of this.client.sandboxes.list({appId:t.appId}))e.add(r.sandboxId);return {ids:e,complete:!0}}catch{return {ids:new Set,complete:false}}}};async function ce(n,e){let t=[];try{for await(let r of n()){if(e!==void 0&&t.length>=e)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!0,error:`stopped at the requested limit of ${e} with more sandboxes available`};if(t.length>=H)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!1,error:`sandbox list exceeded ${H} sandboxes`};let o=await r.getTags();t.push(B(r.sandboxId,o));}}catch(r){return {sandboxes:t,complete:false,pagesFetched:t.length,stoppedAtLimit:false,error:`sandbox list failed: ${r instanceof Error?r.message:String(r)}`}}return {sandboxes:t,complete:true,pagesFetched:t.length,stoppedAtLimit:false}}function Ge(n={}){let e=n.tokenId??process.env.MODAL_TOKEN_ID,t=n.tokenSecret??process.env.MODAL_TOKEN_SECRET;if(!e||!t)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 U({...n,tokenId:e,tokenSecret:t})}var je=C,Ke=J,qe=F,He=I,ze=ae,Xe=se,Ye=de,Je=B,Qe=ce,Ze=te,et=ne,tt=re,nt=oe,rt=p;
3
+ exports.EVOLVE_IMAGE_VERSION=x;exports.MODAL_MAX_LIFETIME_MS=Q;exports.MODAL_MAX_LIST_SANDBOXES=H;exports.MODAL_STDIN_CHUNK_BYTES=y;exports.ModalBootCommandError=N;exports.ModalCommands=D;exports.ModalFiles=O;exports.ModalIdleTimeoutError=S;exports.ModalNetworkPolicyError=P;exports.ModalProvider=U;exports.ModalResourcesError=v;exports.ModalSandboxLifetimeError=T;exports.ModalVolumeError=f;exports.SandboxFeatureUnsupportedError=l;exports.SandboxNotRunningError=w;exports.SandboxPathNotFoundError=u;exports._testBuildSandboxInfo=Je;exports._testCollectSandboxes=Qe;exports._testDynamicNetworkPolicyParams=He;exports._testImageMap=Ke;exports._testMapBootCommand=tt;exports._testMapIdleTimeout=et;exports._testMapNetworkPolicy=qe;exports._testMapResources=Xe;exports._testMapVolumeMounts=nt;exports._testModalSandboxImplCtor=rt;exports._testRequiresDynamicNetwork=ze;exports._testResolveImageRegistry=Ye;exports._testValidateTimeout=Ze;exports._testWrapCommand=je;exports.createModalProvider=Ge;exports.isSandboxFeatureUnsupportedError=ue;exports.isSandboxNotRunningError=ge;exports.isSandboxPathNotFoundError=pe;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,32 @@
1
1
  import { Sandbox } from 'modal';
2
2
 
3
+ /** A capability this provider lacks: `feature` in dotted form (`files.watchDir`), `provider` type, optional `reason`. */
4
+ declare class SandboxFeatureUnsupportedError extends Error {
5
+ readonly feature: string;
6
+ readonly provider: string;
7
+ readonly reason?: string;
8
+ constructor(feature: string, provider: string, reason?: string);
9
+ }
10
+ /** A path the sandbox does not have — never an empty success. */
11
+ declare class SandboxPathNotFoundError extends Error {
12
+ readonly path: string;
13
+ readonly provider: string;
14
+ constructor(path: string, provider: string);
15
+ }
16
+ /** An inspect refused because the sandbox is not running; `state` is the provider's own word (`paused`, `exited with code 137`). */
17
+ declare class SandboxNotRunningError extends Error {
18
+ readonly sandboxId: string;
19
+ readonly provider: string;
20
+ readonly state: string;
21
+ constructor(sandboxId: string, provider: string, state: string);
22
+ }
23
+ /** True for a SandboxFeatureUnsupportedError from ANY package's copy. */
24
+ declare function isSandboxFeatureUnsupportedError(err: unknown): err is SandboxFeatureUnsupportedError;
25
+ /** True for a SandboxPathNotFoundError from ANY package's copy. */
26
+ declare function isSandboxPathNotFoundError(err: unknown): err is SandboxPathNotFoundError;
27
+ /** True for a SandboxNotRunningError from ANY package's copy. */
28
+ declare function isSandboxNotRunningError(err: unknown): err is SandboxNotRunningError;
29
+
3
30
  /**
4
31
  * GENERATED — DO NOT EDIT. `npm run generate:image-version` (repo root)
5
32
  * rewrites this file from the evolve-all image build inputs under
@@ -25,7 +52,7 @@ declare const EVOLVE_IMAGE_VERSION = "c-4bfce70a407f";
25
52
  * - Clear naming (run = blocking, spawn = background)
26
53
  *
27
54
  * Modal-specific notes:
28
- * - No native file APIs - uses exec() with stdin/stdout
55
+ * - File content moves over exec() stdin/stdout; list, stat and watch use the SDK's filesystem API
29
56
  * - Named Volumes mount at create (`volumes`, keyed by in-box path, read-only
30
57
  * or read-write) — Modal's "upload once, read from many sandboxes" store.
31
58
  * modal@0.9.0 has no upload-to-Volume call, so a Volume is filled from
@@ -206,7 +233,7 @@ interface ResolvedVolumeMount {
206
233
  declare function mapVolumeMounts(volumes?: Record<string, ModalVolumeMount>): ResolvedVolumeMount[];
207
234
  /**
208
235
  * Wrap a command with cwd + env handling and (when not root) an
209
- * `su <user> -c` wrapper.
236
+ * `su <user> -c` wrapper (`--session-command` when the caller must be able to signal it).
210
237
  *
211
238
  * Modal sandboxes run as root by default (ignoring the Dockerfile USER
212
239
  * directive), but Claude CLI and other tools refuse certain operations when
@@ -219,7 +246,9 @@ declare function mapVolumeMounts(volumes?: Record<string, ModalVolumeMount>): Re
219
246
  * that contain quotes, special characters, etc. Env vars are inlined because
220
247
  * su does not preserve the environment the way `sudo -E` does.
221
248
  */
222
- declare function wrapCommand(command: string, user: string, cwd?: string, envs?: Record<string, string>): string[];
249
+ declare function wrapCommand(command: string, user: string, cwd?: string, envs?: Record<string, string>, options?: {
250
+ sameSession?: boolean;
251
+ }): string[];
223
252
  /**
224
253
  * Typed error for sizing requests Modal's create() cannot enforce.
225
254
  * The installed Modal JS SDK sizes cpu (cores) and memoryMiB at create time
@@ -373,18 +402,27 @@ interface SandboxInfo {
373
402
  /** End time (undefined for running sandboxes) */
374
403
  endAt?: string;
375
404
  }
376
- /** File or directory entry info */
405
+ /** The SDK contract's FileInfo (sdk-ts types.ts), the same entry on every provider. */
377
406
  interface FileInfo {
378
407
  name: string;
379
408
  path: string;
380
- type: "file" | "dir";
409
+ type: "file" | "dir" | "symlink" | "other";
410
+ size: number;
411
+ mtime: string;
412
+ mode: string;
413
+ owner: string;
414
+ group: string;
415
+ target?: string;
416
+ }
417
+ /** A byte range of a file (the SDK contract's FileRange). */
418
+ interface FileRange {
419
+ offset: number;
420
+ length: number;
381
421
  }
382
- /** Filesystem event from watchDir */
422
+ /** One filesystem change from watchDir: an absolute path, four kinds. */
383
423
  interface FilesystemEvent {
384
- /** Relative path to the changed file/directory */
385
- name: string;
386
- /** Type of filesystem operation */
387
- type: "create" | "remove" | "rename" | "chmod" | "write";
424
+ path: string;
425
+ type: "create" | "write" | "remove" | "rename";
388
426
  }
389
427
  /** Handle to stop watching a directory */
390
428
  interface WatchHandle {
@@ -392,9 +430,21 @@ interface WatchHandle {
392
430
  }
393
431
  /** Options for watching a directory */
394
432
  interface WatchOptions {
433
+ /** Also report changes in every subdirectory. Default false. */
395
434
  recursive?: boolean;
396
- timeoutMs?: number;
397
- onExit?: (err?: Error) => void | Promise<void>;
435
+ }
436
+ /** One resource-usage sample (the SDK contract's SandboxMetrics). */
437
+ interface SandboxMetrics {
438
+ cpuPct: number;
439
+ memUsedMb: number;
440
+ memTotalMb: number;
441
+ diskUsedMb?: number;
442
+ sampledAt: string;
443
+ source: string;
444
+ }
445
+ /** Options for an inspect-only attach (the SDK contract's SandboxInspectOptions). */
446
+ interface SandboxInspectOptions {
447
+ user?: string;
398
448
  }
399
449
  /** Options for blocking sandbox command execution */
400
450
  interface SandboxRunOptions {
@@ -618,6 +668,10 @@ interface SandboxFiles {
618
668
  remove(path: string): Promise<void>;
619
669
  /** Rename or move file/directory */
620
670
  rename(oldPath: string, newPath: string): Promise<void>;
671
+ /** The entry at `path` itself; a symlink is never followed */
672
+ stat(path: string): Promise<FileInfo>;
673
+ /** Exactly the requested bytes, without moving the whole file when the range is small */
674
+ readRange(path: string, range: FileRange): Promise<Uint8Array>;
621
675
  /** Watch directory for changes */
622
676
  watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, options?: WatchOptions): Promise<WatchHandle>;
623
677
  }
@@ -632,6 +686,8 @@ interface SandboxInstance {
632
686
  isRunning(): Promise<boolean>;
633
687
  /** Get sandbox metadata and timing */
634
688
  getInfo(): Promise<SandboxInfo>;
689
+ /** Always a typed refusal here: Modal gives a sandbox no honest figures (see ModalSandboxImpl.metrics) */
690
+ metrics(): Promise<SandboxMetrics | null>;
635
691
  /** Terminate sandbox */
636
692
  kill(): Promise<void>;
637
693
  /** Pause sandbox (preserves state) */
@@ -648,6 +704,8 @@ interface SandboxProvider {
648
704
  readonly providerType: string;
649
705
  /** Human-readable provider name for logging */
650
706
  readonly name?: string;
707
+ /** Attach to a RUNNING sandbox for reads; refuses one that has exited */
708
+ inspect(sandboxId: string, options?: SandboxInspectOptions): Promise<SandboxInstance>;
651
709
  /**
652
710
  * TRUE on every build whose create() maps SandboxCreateOptions.bootCommand
653
711
  * to Modal's create args. A consumer whose safety depends on an inert boot
@@ -781,6 +839,9 @@ declare class ModalFiles implements SandboxFiles {
781
839
  makeDir(path: string): Promise<void>;
782
840
  exists(path: string): Promise<boolean>;
783
841
  list(path: string): Promise<FileInfo[]>;
842
+ /** stat describes a symlink itself, never its target (modal index.d.ts). */
843
+ stat(path: string): Promise<FileInfo>;
844
+ readRange(path: string, range: FileRange): Promise<Uint8Array>;
784
845
  remove(path: string): Promise<void>;
785
846
  rename(oldPath: string, newPath: string): Promise<void>;
786
847
  readStream(path: string): Promise<ReadableStream<Uint8Array>>;
@@ -800,7 +861,7 @@ declare class ModalFiles implements SandboxFiles {
800
861
  writeFromPath(sandboxPath: string, localPath: string): Promise<void>;
801
862
  uploadUrl(_path: string, _expiresInSeconds?: number): Promise<string>;
802
863
  downloadUrl(_path: string, _expiresInSeconds?: number): Promise<string>;
803
- watchDir(_path: string, _onEvent: (event: FilesystemEvent) => void | Promise<void>, _options?: WatchOptions): Promise<WatchHandle>;
864
+ watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, options?: WatchOptions): Promise<WatchHandle>;
804
865
  private toBuffer;
805
866
  }
806
867
  declare class ModalSandboxImpl implements SandboxInstance {
@@ -810,6 +871,7 @@ declare class ModalSandboxImpl implements SandboxInstance {
810
871
  private readonly image?;
811
872
  constructor(sandbox: Sandbox, image: string | undefined, user: string);
812
873
  get sandboxId(): string;
874
+ metrics(): Promise<SandboxMetrics | null>;
813
875
  getHost(port: number): Promise<string>;
814
876
  isRunning(): Promise<boolean>;
815
877
  getInfo(): Promise<SandboxInfo>;
@@ -970,6 +1032,9 @@ declare class ModalProvider implements SandboxProvider {
970
1032
  */
971
1033
  private resolveVolumes;
972
1034
  connect(sandboxId: string, _timeoutMs?: number): Promise<SandboxInstance>;
1035
+ inspect(sandboxId: string, options?: SandboxInspectOptions): Promise<SandboxInstance>;
1036
+ /** A read; seam for the unit test. */
1037
+ protected attachSandbox(sandboxId: string): Promise<Sandbox>;
973
1038
  /**
974
1039
  * List sandboxes, walking the whole app.
975
1040
  *
@@ -1078,5 +1143,7 @@ declare const _testMapVolumeMounts: typeof mapVolumeMounts;
1078
1143
  * the type (never the constructor) gives the seam the real methods to pin.
1079
1144
  */
1080
1145
  type _testModalSandboxImpl = ModalSandboxImpl;
1146
+ /** The constructor, for the metrics-refusal unit test. */
1147
+ declare const _testModalSandboxImplCtor: typeof ModalSandboxImpl;
1081
1148
 
1082
- export { EVOLVE_IMAGE_VERSION, type FileInfo, type FilesystemEvent, MODAL_MAX_LIFETIME_MS, MODAL_MAX_LIST_SANDBOXES, MODAL_STDIN_CHUNK_BYTES, ModalBootCommandError, ModalCommands, type ModalConfig, ModalFiles, ModalIdleTimeoutError, ModalNetworkPolicyError, type ModalNetworkPolicyReason, ModalProvider, ModalResourcesError, ModalSandboxLifetimeError, type ModalSandboxStream, ModalVolumeError, type ModalVolumeErrorReason, type ModalVolumeMount, type ProcessInfo, type SandboxCommandHandle, type SandboxCommandResult, type SandboxCommands, type SandboxConnectOptions, type SandboxCreateOptions, type SandboxFiles, type SandboxInfo, type SandboxInstance, type SandboxListOptions, type SandboxListPage, type SandboxProvider, type SandboxRunOptions, type SandboxSpawnOptions, type WatchHandle, type WatchOptions, _testBuildSandboxInfo, _testCollectSandboxes, _testDynamicNetworkPolicyParams, _testImageMap, _testMapBootCommand, _testMapIdleTimeout, _testMapNetworkPolicy, _testMapResources, _testMapVolumeMounts, type _testModalSandboxImpl, _testRequiresDynamicNetwork, _testResolveImageRegistry, _testValidateTimeout, _testWrapCommand, createModalProvider };
1149
+ export { EVOLVE_IMAGE_VERSION, type FileInfo, type FileRange, type FilesystemEvent, MODAL_MAX_LIFETIME_MS, MODAL_MAX_LIST_SANDBOXES, MODAL_STDIN_CHUNK_BYTES, ModalBootCommandError, ModalCommands, type ModalConfig, ModalFiles, ModalIdleTimeoutError, ModalNetworkPolicyError, type ModalNetworkPolicyReason, ModalProvider, ModalResourcesError, ModalSandboxLifetimeError, type ModalSandboxStream, ModalVolumeError, type ModalVolumeErrorReason, type ModalVolumeMount, type ProcessInfo, type SandboxCommandHandle, type SandboxCommandResult, type SandboxCommands, type SandboxConnectOptions, type SandboxCreateOptions, SandboxFeatureUnsupportedError, type SandboxFiles, type SandboxInfo, type SandboxInspectOptions, type SandboxInstance, type SandboxListOptions, type SandboxListPage, type SandboxMetrics, SandboxNotRunningError, SandboxPathNotFoundError, type SandboxProvider, type SandboxRunOptions, type SandboxSpawnOptions, type WatchHandle, type WatchOptions, _testBuildSandboxInfo, _testCollectSandboxes, _testDynamicNetworkPolicyParams, _testImageMap, _testMapBootCommand, _testMapIdleTimeout, _testMapNetworkPolicy, _testMapResources, _testMapVolumeMounts, type _testModalSandboxImpl, _testModalSandboxImplCtor, _testRequiresDynamicNetwork, _testResolveImageRegistry, _testValidateTimeout, _testWrapCommand, createModalProvider, isSandboxFeatureUnsupportedError, isSandboxNotRunningError, isSandboxPathNotFoundError };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,32 @@
1
1
  import { Sandbox } from 'modal';
2
2
 
3
+ /** A capability this provider lacks: `feature` in dotted form (`files.watchDir`), `provider` type, optional `reason`. */
4
+ declare class SandboxFeatureUnsupportedError extends Error {
5
+ readonly feature: string;
6
+ readonly provider: string;
7
+ readonly reason?: string;
8
+ constructor(feature: string, provider: string, reason?: string);
9
+ }
10
+ /** A path the sandbox does not have — never an empty success. */
11
+ declare class SandboxPathNotFoundError extends Error {
12
+ readonly path: string;
13
+ readonly provider: string;
14
+ constructor(path: string, provider: string);
15
+ }
16
+ /** An inspect refused because the sandbox is not running; `state` is the provider's own word (`paused`, `exited with code 137`). */
17
+ declare class SandboxNotRunningError extends Error {
18
+ readonly sandboxId: string;
19
+ readonly provider: string;
20
+ readonly state: string;
21
+ constructor(sandboxId: string, provider: string, state: string);
22
+ }
23
+ /** True for a SandboxFeatureUnsupportedError from ANY package's copy. */
24
+ declare function isSandboxFeatureUnsupportedError(err: unknown): err is SandboxFeatureUnsupportedError;
25
+ /** True for a SandboxPathNotFoundError from ANY package's copy. */
26
+ declare function isSandboxPathNotFoundError(err: unknown): err is SandboxPathNotFoundError;
27
+ /** True for a SandboxNotRunningError from ANY package's copy. */
28
+ declare function isSandboxNotRunningError(err: unknown): err is SandboxNotRunningError;
29
+
3
30
  /**
4
31
  * GENERATED — DO NOT EDIT. `npm run generate:image-version` (repo root)
5
32
  * rewrites this file from the evolve-all image build inputs under
@@ -25,7 +52,7 @@ declare const EVOLVE_IMAGE_VERSION = "c-4bfce70a407f";
25
52
  * - Clear naming (run = blocking, spawn = background)
26
53
  *
27
54
  * Modal-specific notes:
28
- * - No native file APIs - uses exec() with stdin/stdout
55
+ * - File content moves over exec() stdin/stdout; list, stat and watch use the SDK's filesystem API
29
56
  * - Named Volumes mount at create (`volumes`, keyed by in-box path, read-only
30
57
  * or read-write) — Modal's "upload once, read from many sandboxes" store.
31
58
  * modal@0.9.0 has no upload-to-Volume call, so a Volume is filled from
@@ -206,7 +233,7 @@ interface ResolvedVolumeMount {
206
233
  declare function mapVolumeMounts(volumes?: Record<string, ModalVolumeMount>): ResolvedVolumeMount[];
207
234
  /**
208
235
  * Wrap a command with cwd + env handling and (when not root) an
209
- * `su <user> -c` wrapper.
236
+ * `su <user> -c` wrapper (`--session-command` when the caller must be able to signal it).
210
237
  *
211
238
  * Modal sandboxes run as root by default (ignoring the Dockerfile USER
212
239
  * directive), but Claude CLI and other tools refuse certain operations when
@@ -219,7 +246,9 @@ declare function mapVolumeMounts(volumes?: Record<string, ModalVolumeMount>): Re
219
246
  * that contain quotes, special characters, etc. Env vars are inlined because
220
247
  * su does not preserve the environment the way `sudo -E` does.
221
248
  */
222
- declare function wrapCommand(command: string, user: string, cwd?: string, envs?: Record<string, string>): string[];
249
+ declare function wrapCommand(command: string, user: string, cwd?: string, envs?: Record<string, string>, options?: {
250
+ sameSession?: boolean;
251
+ }): string[];
223
252
  /**
224
253
  * Typed error for sizing requests Modal's create() cannot enforce.
225
254
  * The installed Modal JS SDK sizes cpu (cores) and memoryMiB at create time
@@ -373,18 +402,27 @@ interface SandboxInfo {
373
402
  /** End time (undefined for running sandboxes) */
374
403
  endAt?: string;
375
404
  }
376
- /** File or directory entry info */
405
+ /** The SDK contract's FileInfo (sdk-ts types.ts), the same entry on every provider. */
377
406
  interface FileInfo {
378
407
  name: string;
379
408
  path: string;
380
- type: "file" | "dir";
409
+ type: "file" | "dir" | "symlink" | "other";
410
+ size: number;
411
+ mtime: string;
412
+ mode: string;
413
+ owner: string;
414
+ group: string;
415
+ target?: string;
416
+ }
417
+ /** A byte range of a file (the SDK contract's FileRange). */
418
+ interface FileRange {
419
+ offset: number;
420
+ length: number;
381
421
  }
382
- /** Filesystem event from watchDir */
422
+ /** One filesystem change from watchDir: an absolute path, four kinds. */
383
423
  interface FilesystemEvent {
384
- /** Relative path to the changed file/directory */
385
- name: string;
386
- /** Type of filesystem operation */
387
- type: "create" | "remove" | "rename" | "chmod" | "write";
424
+ path: string;
425
+ type: "create" | "write" | "remove" | "rename";
388
426
  }
389
427
  /** Handle to stop watching a directory */
390
428
  interface WatchHandle {
@@ -392,9 +430,21 @@ interface WatchHandle {
392
430
  }
393
431
  /** Options for watching a directory */
394
432
  interface WatchOptions {
433
+ /** Also report changes in every subdirectory. Default false. */
395
434
  recursive?: boolean;
396
- timeoutMs?: number;
397
- onExit?: (err?: Error) => void | Promise<void>;
435
+ }
436
+ /** One resource-usage sample (the SDK contract's SandboxMetrics). */
437
+ interface SandboxMetrics {
438
+ cpuPct: number;
439
+ memUsedMb: number;
440
+ memTotalMb: number;
441
+ diskUsedMb?: number;
442
+ sampledAt: string;
443
+ source: string;
444
+ }
445
+ /** Options for an inspect-only attach (the SDK contract's SandboxInspectOptions). */
446
+ interface SandboxInspectOptions {
447
+ user?: string;
398
448
  }
399
449
  /** Options for blocking sandbox command execution */
400
450
  interface SandboxRunOptions {
@@ -618,6 +668,10 @@ interface SandboxFiles {
618
668
  remove(path: string): Promise<void>;
619
669
  /** Rename or move file/directory */
620
670
  rename(oldPath: string, newPath: string): Promise<void>;
671
+ /** The entry at `path` itself; a symlink is never followed */
672
+ stat(path: string): Promise<FileInfo>;
673
+ /** Exactly the requested bytes, without moving the whole file when the range is small */
674
+ readRange(path: string, range: FileRange): Promise<Uint8Array>;
621
675
  /** Watch directory for changes */
622
676
  watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, options?: WatchOptions): Promise<WatchHandle>;
623
677
  }
@@ -632,6 +686,8 @@ interface SandboxInstance {
632
686
  isRunning(): Promise<boolean>;
633
687
  /** Get sandbox metadata and timing */
634
688
  getInfo(): Promise<SandboxInfo>;
689
+ /** Always a typed refusal here: Modal gives a sandbox no honest figures (see ModalSandboxImpl.metrics) */
690
+ metrics(): Promise<SandboxMetrics | null>;
635
691
  /** Terminate sandbox */
636
692
  kill(): Promise<void>;
637
693
  /** Pause sandbox (preserves state) */
@@ -648,6 +704,8 @@ interface SandboxProvider {
648
704
  readonly providerType: string;
649
705
  /** Human-readable provider name for logging */
650
706
  readonly name?: string;
707
+ /** Attach to a RUNNING sandbox for reads; refuses one that has exited */
708
+ inspect(sandboxId: string, options?: SandboxInspectOptions): Promise<SandboxInstance>;
651
709
  /**
652
710
  * TRUE on every build whose create() maps SandboxCreateOptions.bootCommand
653
711
  * to Modal's create args. A consumer whose safety depends on an inert boot
@@ -781,6 +839,9 @@ declare class ModalFiles implements SandboxFiles {
781
839
  makeDir(path: string): Promise<void>;
782
840
  exists(path: string): Promise<boolean>;
783
841
  list(path: string): Promise<FileInfo[]>;
842
+ /** stat describes a symlink itself, never its target (modal index.d.ts). */
843
+ stat(path: string): Promise<FileInfo>;
844
+ readRange(path: string, range: FileRange): Promise<Uint8Array>;
784
845
  remove(path: string): Promise<void>;
785
846
  rename(oldPath: string, newPath: string): Promise<void>;
786
847
  readStream(path: string): Promise<ReadableStream<Uint8Array>>;
@@ -800,7 +861,7 @@ declare class ModalFiles implements SandboxFiles {
800
861
  writeFromPath(sandboxPath: string, localPath: string): Promise<void>;
801
862
  uploadUrl(_path: string, _expiresInSeconds?: number): Promise<string>;
802
863
  downloadUrl(_path: string, _expiresInSeconds?: number): Promise<string>;
803
- watchDir(_path: string, _onEvent: (event: FilesystemEvent) => void | Promise<void>, _options?: WatchOptions): Promise<WatchHandle>;
864
+ watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, options?: WatchOptions): Promise<WatchHandle>;
804
865
  private toBuffer;
805
866
  }
806
867
  declare class ModalSandboxImpl implements SandboxInstance {
@@ -810,6 +871,7 @@ declare class ModalSandboxImpl implements SandboxInstance {
810
871
  private readonly image?;
811
872
  constructor(sandbox: Sandbox, image: string | undefined, user: string);
812
873
  get sandboxId(): string;
874
+ metrics(): Promise<SandboxMetrics | null>;
813
875
  getHost(port: number): Promise<string>;
814
876
  isRunning(): Promise<boolean>;
815
877
  getInfo(): Promise<SandboxInfo>;
@@ -970,6 +1032,9 @@ declare class ModalProvider implements SandboxProvider {
970
1032
  */
971
1033
  private resolveVolumes;
972
1034
  connect(sandboxId: string, _timeoutMs?: number): Promise<SandboxInstance>;
1035
+ inspect(sandboxId: string, options?: SandboxInspectOptions): Promise<SandboxInstance>;
1036
+ /** A read; seam for the unit test. */
1037
+ protected attachSandbox(sandboxId: string): Promise<Sandbox>;
973
1038
  /**
974
1039
  * List sandboxes, walking the whole app.
975
1040
  *
@@ -1078,5 +1143,7 @@ declare const _testMapVolumeMounts: typeof mapVolumeMounts;
1078
1143
  * the type (never the constructor) gives the seam the real methods to pin.
1079
1144
  */
1080
1145
  type _testModalSandboxImpl = ModalSandboxImpl;
1146
+ /** The constructor, for the metrics-refusal unit test. */
1147
+ declare const _testModalSandboxImplCtor: typeof ModalSandboxImpl;
1081
1148
 
1082
- export { EVOLVE_IMAGE_VERSION, type FileInfo, type FilesystemEvent, MODAL_MAX_LIFETIME_MS, MODAL_MAX_LIST_SANDBOXES, MODAL_STDIN_CHUNK_BYTES, ModalBootCommandError, ModalCommands, type ModalConfig, ModalFiles, ModalIdleTimeoutError, ModalNetworkPolicyError, type ModalNetworkPolicyReason, ModalProvider, ModalResourcesError, ModalSandboxLifetimeError, type ModalSandboxStream, ModalVolumeError, type ModalVolumeErrorReason, type ModalVolumeMount, type ProcessInfo, type SandboxCommandHandle, type SandboxCommandResult, type SandboxCommands, type SandboxConnectOptions, type SandboxCreateOptions, type SandboxFiles, type SandboxInfo, type SandboxInstance, type SandboxListOptions, type SandboxListPage, type SandboxProvider, type SandboxRunOptions, type SandboxSpawnOptions, type WatchHandle, type WatchOptions, _testBuildSandboxInfo, _testCollectSandboxes, _testDynamicNetworkPolicyParams, _testImageMap, _testMapBootCommand, _testMapIdleTimeout, _testMapNetworkPolicy, _testMapResources, _testMapVolumeMounts, type _testModalSandboxImpl, _testRequiresDynamicNetwork, _testResolveImageRegistry, _testValidateTimeout, _testWrapCommand, createModalProvider };
1149
+ export { EVOLVE_IMAGE_VERSION, type FileInfo, type FileRange, type FilesystemEvent, MODAL_MAX_LIFETIME_MS, MODAL_MAX_LIST_SANDBOXES, MODAL_STDIN_CHUNK_BYTES, ModalBootCommandError, ModalCommands, type ModalConfig, ModalFiles, ModalIdleTimeoutError, ModalNetworkPolicyError, type ModalNetworkPolicyReason, ModalProvider, ModalResourcesError, ModalSandboxLifetimeError, type ModalSandboxStream, ModalVolumeError, type ModalVolumeErrorReason, type ModalVolumeMount, type ProcessInfo, type SandboxCommandHandle, type SandboxCommandResult, type SandboxCommands, type SandboxConnectOptions, type SandboxCreateOptions, SandboxFeatureUnsupportedError, type SandboxFiles, type SandboxInfo, type SandboxInspectOptions, type SandboxInstance, type SandboxListOptions, type SandboxListPage, type SandboxMetrics, SandboxNotRunningError, SandboxPathNotFoundError, type SandboxProvider, type SandboxRunOptions, type SandboxSpawnOptions, type WatchHandle, type WatchOptions, _testBuildSandboxInfo, _testCollectSandboxes, _testDynamicNetworkPolicyParams, _testImageMap, _testMapBootCommand, _testMapIdleTimeout, _testMapNetworkPolicy, _testMapResources, _testMapVolumeMounts, type _testModalSandboxImpl, _testModalSandboxImplCtor, _testRequiresDynamicNetwork, _testResolveImageRegistry, _testValidateTimeout, _testWrapCommand, createModalProvider, isSandboxFeatureUnsupportedError, isSandboxNotRunningError, isSandboxPathNotFoundError };
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import {ModalClient,NotFoundError}from'modal';import {pack}from'tar-stream';var p="c-4bfce70a407f";var D={[`evolve-all-${p}`]:`evolvingmachines/evolve-all:${p}`,"evolve-all":"evolvingmachines/evolve-all"},$=1440*60*1e3,g=8*1024*1024;async function*Q(r){let e=r.getReader();try{for(;;){let{done:t,value:n}=await e.read();if(t)break;n&&(yield n);}}finally{e.releaseLock();}}var N="user",T="evolve.image",B="evolve.startedAt",v=class extends Error{requestedTimeoutMs;constructor(e){let t=(e/36e5).toFixed(1);super(`Modal sandboxes have a hard 24h lifetime cap; requested timeout was ${t}h. For sessions longer than 24h, persist progress with Evolve checkpoints and resume in a fresh sandbox instead of extending the timeout.`),this.name="ModalSandboxLifetimeError",this.requestedTimeoutMs=e;}};function L(r){if(r>$)throw new v(r)}var f=class extends Error{requestedIdleTimeoutMs;constructor(e,t){super(`Modal idleTimeoutMs of ${e}ms is invalid: ${t}`),this.name="ModalIdleTimeoutError",this.requestedIdleTimeoutMs=e;}};function U(r){if(r===void 0)return {};if(!Number.isFinite(r)||r<=0)throw new f(r,"it must be a positive number of milliseconds");if(r>$)throw new f(r,"it exceeds Modal's 24h lifetime cap, so the sandbox would always die of the lifetime first");return {idleTimeoutMs:r}}var P=class extends Error{constructor(e){super(`Modal bootCommand is invalid: ${e}`),this.name="ModalBootCommandError";}};function V(r){if(r===void 0)return {};if(r.length===0)throw new P(`an empty argv is Modal's own "no args" wire shape and would boot the image's ENTRYPOINT/CMD \u2014 omit bootCommand to boot the image's command`);return {command:[...r]}}var m=class extends Error{reason;mountPath;volumeName;constructor(e,t,n,o,s){super(o,s===void 0?void 0:{cause:s}),this.name="ModalVolumeError",this.reason=e,this.mountPath=t,this.volumeName=n;}};function F(r){if(!r)return [];let e=[];for(let[t,n]of Object.entries(r)){if(!t.startsWith("/")||t==="/")throw new m("invalid-mount-path",t,n.name,`Modal Volume mount path ${JSON.stringify(t)} is invalid: it must be an absolute in-box path other than "/"`);let o=n.name?.trim()??"";if(o.length===0)throw new m("invalid-name",t,n.name??"",`Modal Volume mounted at ${t} has a blank name`);e.push({mountPath:t,name:o,readOnly:n.readOnly===true,createIfMissing:n.createIfMissing===true});}return e}function I(r,e,t,n){let o="";n&&Object.keys(n).length>0&&(o=Object.entries(n).filter(([,a])=>a!=null).map(([a,d])=>`export ${a}='${String(d).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let s=t?`cd '${t.replace(/'/g,"'\\''")}' && ${o}${r}`:`${o}${r}`;if(e==="root")return ["bash","-c",s];let i=Buffer.from(s).toString("base64");return ["su",e,"-c",`echo ${i} | base64 -d | bash`]}var h=class extends Error{constructor(e){super(e),this.name="ModalResourcesError";}},Z=4,ee=4096;function W(r){if(r?.disk!==void 0)throw new h(`Modal's JS SDK has no create-time disk-size parameter, so a ${r.disk} GiB disk request cannot be enforced. Drop \`resources.disk\` (containers get Modal's default disk quota) or run on a provider that sizes disk.`);if((r?.gpu===void 0||r.gpu<=0)&&r?.gpuTypes?.length)throw new h("resources.gpuTypes was set without a positive resources.gpu count \u2014 declare how many GPUs to reserve, or drop gpuTypes.");let e=r?.gpu!==void 0&&r.gpu>0?`${r.gpuTypes?.length?r.gpuTypes[0]:"any"}:${r.gpu}`:void 0;return {cpu:r?.cpu??Z,memoryMiB:r?.memory!==void 0?Math.ceil(r.memory*1024):ee,...e!==void 0?{gpu:e}:{}}}var w=class extends Error{reason;destination;constructor(e,t,n){super(t),this.name="ModalNetworkPolicyError",this.reason=e,this.destination=n;}};function q(r){let e=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,3}))?$/.exec(r);return !(!e||[e[1],e[2],e[3],e[4]].some(t=>Number(t)>255)||e[5]!==void 0&&Number(e[5])>32)}function te(r){return /^\d{1,3}(\.\d{1,3}){3}(\/\d+)?$/.test(r)&&!q(r)}function ne(r){return r.startsWith("[")?true:(r.match(/:/g)?.length??0)>=2}function re(r){return /^[^:]+:\d+$/.test(r)}function E(r){if(!r||r.outbound==="open"){if(r?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {}}let e=r.allowedDestinations??[];if(e.length===0)return {blockNetwork:true};let t=[],n=[];for(let o of e)if(q(o))t.push(o.includes("/")?o:`${o}/32`);else {if(te(o))throw new w("invalid-ipv4",`"${o}" is not a valid IPv4 address or CIDR (octets must be 0-255, prefix 0-32). Fix the address or list a hostname instead.`,o);if(ne(o))t.push(o.includes("/")?o:`${o}/128`);else {if(re(o))throw new w("port-unsupported",`Modal's network allowlist filters hosts and IPs only and cannot match a port; drop the ":<port>" from "${o}" and list just the host or IP.`,o);n.push(o);}}return {outboundCidrAllowlist:t,outboundDomainAllowlist:n}}function b(r){if(!r||r.outbound==="open"){if(r?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {outboundCidrAllowlist:["0.0.0.0/0"],outboundDomainAllowlist:["*"]}}let e=E(r);return {outboundCidrAllowlist:e.outboundCidrAllowlist??[],outboundDomainAllowlist:se(e.outboundDomainAllowlist??[])}}var oe="sealed.invalid";function se(r){return r.length>0?r:[oe]}function G(r,e){if(!e||e.length===0)return false;let t=o=>o===void 0?"open|":`${o.outbound}|${[...new Set(o.allowedDestinations??[])].sort().join(",")}`,n=t(r);return e.some(o=>t(o)!==n)}function H(r){let e=r.split("/")[0];return /^\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com$/.test(e)?"aws-ecr":e==="gcr.io"||e.endsWith(".gcr.io")||e.endsWith("-docker.pkg.dev")?"gcp-artifact-registry":"registry"}function R(r,e,t){let{[T]:n,[B]:o,...s}=e;return {sandboxId:r,image:n??t??"",metadata:s,startedAt:o??""}}var O=1e4,M=class{constructor(e,t){this.sandbox=e;this.user=t;}async run(e,t){let n=I(e,this.user,t?.cwd,t?.envs),o=await this.sandbox.exec(n,{timeoutMs:t?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(o,t?.onStdout,t?.onStderr);return {exitCode:await o.wait(),stdout:s,stderr:i}}async spawn(e,t){let n=I(e,this.user,t?.cwd,t?.envs),o=await this.sandbox.exec(n,{timeoutMs:t?.timeoutMs}),s="",i="",a=this.accumulateStreams(o,t?.onStdout?c=>{t.onStdout(c);}:void 0,t?.onStderr?c=>{t.onStderr(c);}:void 0).then(({stdout:c,stderr:u})=>{s=c,i=u;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await a,{exitCode:await o.wait(),stdout:s,stderr:i}),kill:async()=>false}}async list(){let e=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await e.wait(),(await e.stdout.readText()).trim().split(`
2
- `).slice(1).map(o=>{let s=o.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(e,t){throw new Error("Modal does not support connecting to existing processes")}async sendStdin(e,t){throw new Error("Modal does not support sendStdin by process ID")}async kill(e){return await(await this.sandbox.exec(["kill","-9",e],{timeoutMs:1e4})).wait()===0}async accumulateStreams(e,t,n){let o="",s="",i=[];return i.push((async()=>{try{for await(let a of e.stdout){let d=typeof a=="string"?a:new TextDecoder().decode(a);o+=d,t?.(d);}}catch{}})()),i.push((async()=>{try{for await(let a of e.stderr){let d=typeof a=="string"?a:new TextDecoder().decode(a);s+=d,n?.(d);}}catch{}})()),await Promise.all(i),{stdout:o,stderr:s}}},A=class{constructor(e,t){this.sandbox=e;this.user=t;}async chownToUser(e,t=false){if(this.user==="root")return;let n=t?["chown","-R",`${this.user}:${this.user}`,e]:["chown",`${this.user}:${this.user}`,e];await(await this.sandbox.exec(n,{timeoutMs:t?3e4:1e4})).wait();}async writeStdinChunked(e,t){for(let n=0;n<t.length;n+=g)await e.writeBytes(t.subarray(n,n+g));}async writeStdinCoalesced(e,t){let n=[],o=0,s=async()=>{if(o===0)return;let i=n.length===1?n[0]:Buffer.concat(n,o);n=[],o=0,await this.writeStdinChunked(e,i);};for await(let i of t)n.push(i),o+=i.length,o>=g&&await s();await s();}async read(e){let t=await this.sandbox.exec(["cat",e],{timeoutMs:3e5,mode:"binary"}),n=await t.wait();if(n!==0){let s=await t.stderr.readText();throw new Error(`Failed to read file ${e}: ${s||`exit code ${n}`}`)}let o=await t.stdout.readBytes();if(!o.includes(0))try{return new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}).decode(o)}catch{}return o}async write(e,t){let n=this.toBuffer(t),o=e.substring(0,e.lastIndexOf("/"));o&&await this.makeDir(o);let s=e.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await this.writeStdinChunked(i.stdin,new Uint8Array(n)),await i.stdin.getWriter().close(),await i.wait(),await this.chownToUser(e);}async writeBatch(e){let t=pack(),n=new Set;for(let i of e){let a=this.toBuffer(i.data),d=i.path.startsWith("/")?i.path.slice(1):i.path;t.entry({name:d},a);let c=i.path.substring(0,i.path.lastIndexOf("/"));c&&n.add(c);}t.finalize();let o=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await this.writeStdinCoalesced(o.stdin,t),await o.stdin.getWriter().close(),await o.wait(),n.size>0){let i=Array.from(n),a=new Set(i.map(d=>d.split("/").slice(0,4).join("/")));for(let d of a)await this.chownToUser(d,true);}}async makeDir(e){await(await this.sandbox.exec(["mkdir","-p",e],{timeoutMs:1e4})).wait(),await this.chownToUser(e,true);}async exists(e){return await(await this.sandbox.exec(["test","-e",e],{timeoutMs:1e4})).wait()===0}async list(e){let t=e.replace(/'/g,"'\\''"),n=await this.sandbox.exec(["bash","-c",`ls -la '${t}' | tail -n +2`],{timeoutMs:3e4});await n.wait();let o=await n.stdout.readText(),s=[];for(let i of o.trim().split(`
3
- `)){if(!i)continue;let a=i.split(/\s+/);if(a.length<9)continue;let d=a[0],c=a.slice(8).join(" ");c==="."||c===".."||s.push({name:c,path:e.endsWith("/")?`${e}${c}`:`${e}/${c}`,type:d.startsWith("d")?"dir":"file"});}return s}async remove(e){await(await this.sandbox.exec(["rm","-rf",e],{timeoutMs:3e4})).wait();}async rename(e,t){await(await this.sandbox.exec(["mv",e,t],{timeoutMs:3e4})).wait();}async readStream(e){return (await this.sandbox.exec(["cat",e],{timeoutMs:3e5,mode:"binary"})).stdout}async writeStream(e,t){let n=e.substring(0,e.lastIndexOf("/"));n&&await this.makeDir(n);let o=e.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${o}'`],{mode:"binary"});await this.writeStdinCoalesced(s.stdin,Q(t)),await s.stdin.getWriter().close(),await s.wait(),await this.chownToUser(e);}async writeFromPath(e,t){let{createReadStream:n}=await import('fs'),{Readable:o}=await import('stream'),s=o.toWeb(n(t,{highWaterMark:g}));await this.writeStream(e,s);}async uploadUrl(e,t){throw new Error("Modal does not support pre-signed upload URLs")}async downloadUrl(e,t){throw new Error("Modal does not support pre-signed download URLs")}async watchDir(e,t,n){throw new Error("Modal does not support watchDir")}toBuffer(e){if(typeof e=="string")return Buffer.from(e,"utf-8");if(e instanceof Buffer)return e;if(e instanceof ArrayBuffer||e instanceof Uint8Array)return Buffer.from(e);throw new Error(`Unsupported data type: ${typeof e}`)}},x=class{constructor(e,t,n){this.sandbox=e;this.commands=new M(e,n),this.files=new A(e,n),this.image=t;}commands;files;image;get sandboxId(){return this.sandbox.sandboxId}async getHost(e){let n=(await this.sandbox.tunnels())[e];if(!n)throw new Error(`No tunnel found for port ${e}`);return n.url}async isRunning(){try{return await this.sandbox.poll()===null}catch{return false}}async getInfo(){let e=await this.sandbox.getTags();return R(this.sandbox.sandboxId,e,this.image)}async updateNetwork(e){await this.sandbox.updateNetworkPolicy(b(e));}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(e=>setTimeout(e,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause/resume. Persist progress with Evolve checkpoints and resume in a fresh sandbox, or use kill() to terminate.")}},k=class{providerType="modal";name="Modal";supportsBootCommand=true;supportsVolumes=true;client;appName;defaultTimeoutMs;imageName;imageSecretName;_app;sandboxUsers=new Map;constructor(e){!e.endpoint&&process.env.MODAL_SERVER_URL?.startsWith("unix:")?process.env.MODAL_SERVER_URL="https://api.modal.com:443":e.endpoint&&(process.env.MODAL_SERVER_URL=e.endpoint),this.client=new ModalClient({tokenId:e.tokenId,tokenSecret:e.tokenSecret}),this.appName=e.appName??"evolve-sandbox",this.defaultTimeoutMs=e.defaultTimeoutMs??36e5,this.imageName=e.imageName??`evolve-all-${p}`,this.imageSecretName=e.imageSecretName;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async resolveImage(e){let t=H(e);if(t==="registry"){let o=this.imageSecretName?await this.client.secrets.fromName(this.imageSecretName):void 0;return this.client.images.fromRegistry(e,o)}if(!this.imageSecretName)throw new Error(`Private registry image "${e}" requires config.imageSecretName \u2014 the name of a Modal Secret holding registry credentials (AWS ECR: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION with read-only ECR IAM). Create one at https://modal.com/secrets`);let n=t==="aws-ecr"?await this.client.secrets.fromName(this.imageSecretName,{requiredKeys:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"]}):await this.client.secrets.fromName(this.imageSecretName);return t==="aws-ecr"?this.client.images.fromAwsEcr(e,n):this.client.images.fromGcpArtifactRegistry(e,n)}async resolveAndBuildImage(e){let t=D[e]??e;if(!/[/:@]/.test(t))try{return {tag:t,image:await this.client.images.fromName(t)}}catch(s){if(!(s instanceof NotFoundError))throw s}let n=await this.getApp(),o=await this.resolveImage(t);return {tag:t,image:await o.build(n)}}async publishImageAs(e,t){if(!e.trim())throw new Error("publishImageAs requires a non-empty alias to publish under");let{image:n}=await this.resolveAndBuildImage(t||this.imageName);return await n.publish(e),e}async prepareImage(e){await this.resolveAndBuildImage(e||this.imageName);}async create(e){let t=e.timeoutMs??this.defaultTimeoutMs;L(t);let n=U(e.idleTimeoutMs),o=V(e.bootCommand),s=F(e.volumes);for(let l of e.phaseNetworkPolicies??[])b(l);let i=G(e.network,e.phaseNetworkPolicies)?b(e.network):E(e.network),a=W(e.resources),d=e.user??N,c=await this.getApp(),{tag:u,image:j}=await this.resolveAndBuildImage(e.image||this.imageName),C=await this.resolveVolumes(s),y=e.envs?Object.fromEntries(Object.entries(e.envs).filter(([,l])=>l!=null)):void 0,z=y&&Object.keys(y).length>0?y:void 0,Y={...e.metadata,[T]:u,[B]:new Date().toISOString()},S=await this.client.sandboxes.create(c,j,{cpu:a.cpu,memoryMiB:a.memoryMiB,...a.gpu!==void 0?{gpu:a.gpu}:{},timeoutMs:t,...n,...o,workdir:e.workingDirectory,env:z,tags:Y,...i,...C!==void 0?{volumes:C}:{}});return e.workingDirectory&&d!=="root"&&await(await S.exec(["chown","-R",`${d}:${d}`,e.workingDirectory],{timeoutMs:3e4})).wait(),this.sandboxUsers.set(S.sandboxId,d),new x(S,u,d)}async resolveVolumes(e){if(e.length===0)return;let t={};for(let n of e){let o;try{o=await this.client.volumes.fromName(n.name,{createIfMissing:n.createIfMissing});}catch(s){let i=s instanceof Error?s.message:String(s),a=s instanceof NotFoundError||s instanceof Error&&s.name==="NotFoundError";throw new m(a?"not-found":"resolve-failed",n.mountPath,n.name,a?`Modal Volume "${n.name}" (to mount at ${n.mountPath}) does not exist and createIfMissing was not set: ${i}`:`Modal Volume "${n.name}" (to mount at ${n.mountPath}) could not be resolved: ${i}`,s)}t[n.mountPath]=o.withMountOptions({readOnly:n.readOnly});}return t}async connect(e,t){let n=await this.client.sandboxes.fromId(e),o=this.sandboxUsers.get(e)??N;return new x(n,void 0,o)}async list(e){let t=await this.walk(e);if(t.error&&!t.stoppedAtLimit)throw new Error(t.error);return t.sandboxes}async listAll(e){let{stoppedAtLimit:t,...n}=await this.walk(e);return n}async walk(e){if(e?.state&&!e.state.includes("running"))return {sandboxes:[],complete:true,pagesFetched:0,stoppedAtLimit:false};let t;try{t=await this.getApp();}catch(n){return {sandboxes:[],complete:false,pagesFetched:0,stoppedAtLimit:false,error:`sandbox list failed: ${n instanceof Error?n.message:String(n)}`}}return K(()=>this.client.sandboxes.list({appId:t.appId,tags:e?.metadata}),e?.limit)}async listSandboxIds(){let e=new Set;try{let t=await this.getApp();for await(let n of this.client.sandboxes.list({appId:t.appId}))e.add(n.sandboxId);return {ids:e,complete:!0}}catch{return {ids:new Set,complete:false}}}};async function K(r,e){let t=[];try{for await(let n of r()){if(e!==void 0&&t.length>=e)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!0,error:`stopped at the requested limit of ${e} with more sandboxes available`};if(t.length>=O)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!1,error:`sandbox list exceeded ${O} sandboxes`};let o=await n.getTags();t.push(R(n.sandboxId,o));}}catch(n){return {sandboxes:t,complete:false,pagesFetched:t.length,stoppedAtLimit:false,error:`sandbox list failed: ${n instanceof Error?n.message:String(n)}`}}return {sandboxes:t,complete:true,pagesFetched:t.length,stoppedAtLimit:false}}function ge(r={}){let e=r.tokenId??process.env.MODAL_TOKEN_ID,t=r.tokenSecret??process.env.MODAL_TOKEN_SECRET;if(!e||!t)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 k({...r,tokenId:e,tokenSecret:t})}var fe=I,he=D,we=E,be=b,xe=G,ye=W,Se=H,ve=R,Pe=K,Ie=L,Me=U,Ae=V,ke=F;
4
- export{p as EVOLVE_IMAGE_VERSION,$ as MODAL_MAX_LIFETIME_MS,O as MODAL_MAX_LIST_SANDBOXES,g as MODAL_STDIN_CHUNK_BYTES,P as ModalBootCommandError,M as ModalCommands,A as ModalFiles,f as ModalIdleTimeoutError,w as ModalNetworkPolicyError,k as ModalProvider,h as ModalResourcesError,v as ModalSandboxLifetimeError,m as ModalVolumeError,ve as _testBuildSandboxInfo,Pe as _testCollectSandboxes,be as _testDynamicNetworkPolicyParams,he as _testImageMap,Ae as _testMapBootCommand,Me as _testMapIdleTimeout,we as _testMapNetworkPolicy,ye as _testMapResources,ke as _testMapVolumeMounts,xe as _testRequiresDynamicNetwork,Se as _testResolveImageRegistry,Ie as _testValidateTimeout,fe as _testWrapCommand,ge as createModalProvider};
1
+ import {ModalClient,NotFoundError}from'modal';import {pack}from'tar-stream';import {constants}from'fs';var l=class extends Error{feature;provider;reason;constructor(e,t,r){super(`${t} does not support ${e}${r?`: ${r}`:""}`),this.name="SandboxFeatureUnsupportedError",this.feature=e,this.provider=t,this.reason=r;}},u=class extends Error{path;provider;constructor(e,t){super(`${t}: no such file or directory: ${e}`),this.name="SandboxPathNotFoundError",this.path=e,this.provider=t;}},w=class extends Error{sandboxId;provider;state;constructor(e,t,r){super(`${t} sandbox ${e} is not running (${r}); inspect never starts or resumes a sandbox`),this.name="SandboxNotRunningError",this.sandboxId=e,this.provider=t,this.state=r;}};function R(n,e){return !!n&&typeof n=="object"&&n.name===e}function ue(n){return R(n,"SandboxFeatureUnsupportedError")}function pe(n){return R(n,"SandboxPathNotFoundError")}function ge(n){return R(n,"SandboxNotRunningError")}var k=2;var _=21;function V(n){return `'${n.replace(/'/g,"'\\''")}'`}function W(n){if(!Number.isInteger(n.offset)||n.offset<0)throw new RangeError(`offset must be a non-negative integer, got ${n.offset}`);if(!Number.isInteger(n.length)||n.length<0)throw new RangeError(`length must be a non-negative integer, got ${n.length}`)}function G(n){if(typeof n=="number")return L((n&4095).toString(8));if(/^[0-7]{1,4}$/.test(n))return L(n);throw new RangeError(`not an octal mode: ${n}`)}function L(n){return n.padStart(4,"0")}function j(n){let e=n&constants.S_IFMT;return e===constants.S_IFDIR?"dir":e===constants.S_IFLNK?"symlink":e===constants.S_IFREG?"file":"other"}function K(n){let e=n instanceof Date?n:typeof n=="number"?new Date(n*1e3):new Date(n);if(Number.isNaN(e.getTime()))throw new RangeError(`not a timestamp: ${String(n)}`);return e.toISOString()}var x="c-4bfce70a407f";var J={[`evolve-all-${x}`]:`evolvingmachines/evolve-all:${x}`,"evolve-all":"evolvingmachines/evolve-all"},Q=1440*60*1e3,y=8*1024*1024;async function*we(n){let e=n.getReader();try{for(;;){let{done:t,value:r}=await e.read();if(t)break;r&&(yield r);}}finally{e.releaseLock();}}var $="user",Z="evolve.image",ee="evolve.startedAt",T=class extends Error{requestedTimeoutMs;constructor(e){let t=(e/36e5).toFixed(1);super(`Modal sandboxes have a hard 24h lifetime cap; requested timeout was ${t}h. For sessions longer than 24h, persist progress with Evolve checkpoints and resume in a fresh sandbox instead of extending the timeout.`),this.name="ModalSandboxLifetimeError",this.requestedTimeoutMs=e;}};function te(n){if(n>Q)throw new T(n)}var S=class extends Error{requestedIdleTimeoutMs;constructor(e,t){super(`Modal idleTimeoutMs of ${e}ms is invalid: ${t}`),this.name="ModalIdleTimeoutError",this.requestedIdleTimeoutMs=e;}};function ne(n){if(n===void 0)return {};if(!Number.isFinite(n)||n<=0)throw new S(n,"it must be a positive number of milliseconds");if(n>Q)throw new S(n,"it exceeds Modal's 24h lifetime cap, so the sandbox would always die of the lifetime first");return {idleTimeoutMs:n}}var N=class extends Error{constructor(e){super(`Modal bootCommand is invalid: ${e}`),this.name="ModalBootCommandError";}};function re(n){if(n===void 0)return {};if(n.length===0)throw new N(`an empty argv is Modal's own "no args" wire shape and would boot the image's ENTRYPOINT/CMD \u2014 omit bootCommand to boot the image's command`);return {command:[...n]}}var f=class extends Error{reason;mountPath;volumeName;constructor(e,t,r,o,s){super(o,s===void 0?void 0:{cause:s}),this.name="ModalVolumeError",this.reason=e,this.mountPath=t,this.volumeName=r;}};function oe(n){if(!n)return [];let e=[];for(let[t,r]of Object.entries(n)){if(!t.startsWith("/")||t==="/")throw new f("invalid-mount-path",t,r.name,`Modal Volume mount path ${JSON.stringify(t)} is invalid: it must be an absolute in-box path other than "/"`);let o=r.name?.trim()??"";if(o.length===0)throw new f("invalid-name",t,r.name??"",`Modal Volume mounted at ${t} has a blank name`);e.push({mountPath:t,name:o,readOnly:r.readOnly===true,createIfMissing:r.createIfMissing===true});}return e}function C(n,e,t,r,o){let s="";r&&Object.keys(r).length>0&&(s=Object.entries(r).filter(([,d])=>d!=null).map(([d,m])=>`export ${d}='${String(m).replace(/'/g,"'\\''")}'`).join("; ")+"; ");let i=t?`cd '${t.replace(/'/g,"'\\''")}' && ${s}${n}`:`${s}${n}`;if(e==="root")return ["bash","-c",i];let a=Buffer.from(i).toString("base64");return ["su",e,o?.sameSession?"--session-command":"-c",`echo ${a} | base64 -d | bash`]}var v=class extends Error{constructor(e){super(e),this.name="ModalResourcesError";}},be=4,xe=4096;function se(n){if(n?.disk!==void 0)throw new v(`Modal's JS SDK has no create-time disk-size parameter, so a ${n.disk} GiB disk request cannot be enforced. Drop \`resources.disk\` (containers get Modal's default disk quota) or run on a provider that sizes disk.`);if((n?.gpu===void 0||n.gpu<=0)&&n?.gpuTypes?.length)throw new v("resources.gpuTypes was set without a positive resources.gpu count \u2014 declare how many GPUs to reserve, or drop gpuTypes.");let e=n?.gpu!==void 0&&n.gpu>0?`${n.gpuTypes?.length?n.gpuTypes[0]:"any"}:${n.gpu}`:void 0;return {cpu:n?.cpu??be,memoryMiB:n?.memory!==void 0?Math.ceil(n.memory*1024):xe,...e!==void 0?{gpu:e}:{}}}var P=class extends Error{reason;destination;constructor(e,t,r){super(t),this.name="ModalNetworkPolicyError",this.reason=e,this.destination=r;}};function ie(n){let e=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})(?:\/(\d{1,3}))?$/.exec(n);return !(!e||[e[1],e[2],e[3],e[4]].some(t=>Number(t)>255)||e[5]!==void 0&&Number(e[5])>32)}function ye(n){return /^\d{1,3}(\.\d{1,3}){3}(\/\d+)?$/.test(n)&&!ie(n)}function Se(n){return n.startsWith("[")?true:(n.match(/:/g)?.length??0)>=2}function ve(n){return /^[^:]+:\d+$/.test(n)}function F(n){if(!n||n.outbound==="open"){if(n?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {}}let e=n.allowedDestinations??[];if(e.length===0)return {blockNetwork:true};let t=[],r=[];for(let o of e)if(ie(o))t.push(o.includes("/")?o:`${o}/32`);else {if(ye(o))throw new P("invalid-ipv4",`"${o}" is not a valid IPv4 address or CIDR (octets must be 0-255, prefix 0-32). Fix the address or list a hostname instead.`,o);if(Se(o))t.push(o.includes("/")?o:`${o}/128`);else {if(ve(o))throw new P("port-unsupported",`Modal's network allowlist filters hosts and IPs only and cannot match a port; drop the ":<port>" from "${o}" and list just the host or IP.`,o);r.push(o);}}return {outboundCidrAllowlist:t,outboundDomainAllowlist:r}}function I(n){if(!n||n.outbound==="open"){if(n?.allowedDestinations?.length)throw new Error("network.allowedDestinations is only valid when outbound is blocked");return {outboundCidrAllowlist:["0.0.0.0/0"],outboundDomainAllowlist:["*"]}}let e=F(n);return {outboundCidrAllowlist:e.outboundCidrAllowlist??[],outboundDomainAllowlist:Ie(e.outboundDomainAllowlist??[])}}var Pe="sealed.invalid";function Ie(n){return n.length>0?n:[Pe]}function ae(n,e){if(!e||e.length===0)return false;let t=o=>o===void 0?"open|":`${o.outbound}|${[...new Set(o.allowedDestinations??[])].sort().join(",")}`,r=t(n);return e.some(o=>t(o)!==r)}function de(n){let e=n.split("/")[0];return /^\d{12}\.dkr\.ecr\.[a-z0-9-]+\.amazonaws\.com$/.test(e)?"aws-ecr":e==="gcr.io"||e.endsWith(".gcr.io")||e.endsWith("-docker.pkg.dev")?"gcp-artifact-registry":"registry"}function B(n,e,t){let{[Z]:r,[ee]:o,...s}=e;return {sandboxId:n,image:r??t??"",metadata:s,startedAt:o??""}}var H=1e4,D=class{constructor(e,t){this.sandbox=e;this.user=t;}async run(e,t){let r=C(e,this.user,t?.cwd,t?.envs),o=await this.sandbox.exec(r,{timeoutMs:t?.timeoutMs}),{stdout:s,stderr:i}=await this.accumulateStreams(o,t?.onStdout,t?.onStderr);return {exitCode:await o.wait(),stdout:s,stderr:i}}async spawn(e,t){let r=t?.stdin===false,o=C(e,this.user,t?.cwd,t?.envs,{sameSession:r}),s=r?["bash","-c",Re,"bash",...o]:o,i=await this.sandbox.exec(s,{timeoutMs:t?.timeoutMs}),a="",d="",m=this.accumulateStreams(i,t?.onStdout?c=>{t.onStdout(c);}:void 0,t?.onStderr?c=>{t.onStderr(c);}:void 0).then(({stdout:c,stderr:h})=>{a=c,d=h;}).catch(()=>{});return {processId:`modal-${Date.now()}-${Math.random().toString(36).slice(2)}`,wait:async()=>(await m,{exitCode:await i.wait(),stdout:a,stderr:d}),kill:async()=>{if(!r)throw new l("commands.kill","modal","the Modal SDK cannot signal a running process; spawn it with `stdin: false` and kill() ends it by closing its stdin");let c=i.stdin.getWriter();try{return await c.close(),!0}catch{return false}}}}async list(){let e=await this.sandbox.exec(["ps","-eo","pid,comm,args"],{timeoutMs:1e4});return await e.wait(),(await e.stdout.readText()).trim().split(`
2
+ `).slice(1).map(o=>{let s=o.trim().split(/\s+/);return {processId:s[0],cmd:s[1]||"",args:s.slice(2),envs:{}}})}async connect(e,t){throw new l("commands.connect","modal","the Modal SDK cannot attach to a process by id")}async sendStdin(e,t){throw new l("commands.sendStdin","modal","the Modal SDK cannot address a process's stdin by id")}async kill(e){return await(await this.sandbox.exec(["kill","-9",e],{timeoutMs:1e4})).wait()===0}async accumulateStreams(e,t,r){let o="",s="",i=[];return i.push((async()=>{try{for await(let a of e.stdout){let d=typeof a=="string"?a:new TextDecoder().decode(a);o+=d,t?.(d);}}catch{}})()),i.push((async()=>{try{for await(let a of e.stderr){let d=typeof a=="string"?a:new TextDecoder().decode(a);s+=d,r?.(d);}}catch{}})()),await Promise.all(i),{stdout:o,stderr:s}}},O=class{constructor(e,t){this.sandbox=e;this.user=t;}async chownToUser(e,t=false){if(this.user==="root")return;let r=t?["chown","-R",`${this.user}:${this.user}`,e]:["chown",`${this.user}:${this.user}`,e];await(await this.sandbox.exec(r,{timeoutMs:t?3e4:1e4})).wait();}async writeStdinChunked(e,t){for(let r=0;r<t.length;r+=y)await e.writeBytes(t.subarray(r,r+y));}async writeStdinCoalesced(e,t){let r=[],o=0,s=async()=>{if(o===0)return;let i=r.length===1?r[0]:Buffer.concat(r,o);r=[],o=0,await this.writeStdinChunked(e,i);};for await(let i of t)r.push(i),o+=i.length,o>=y&&await s();await s();}async read(e){let t=await this.sandbox.exec(["cat",e],{timeoutMs:3e5,mode:"binary"}),r=await t.wait();if(r!==0){let s=await t.stderr.readText();throw new Error(`Failed to read file ${e}: ${s||`exit code ${r}`}`)}let o=await t.stdout.readBytes();if(!o.includes(0))try{return new TextDecoder("utf-8",{fatal:!0,ignoreBOM:!0}).decode(o)}catch{}return o}async write(e,t){let r=this.toBuffer(t),o=e.substring(0,e.lastIndexOf("/"));o&&await this.makeDir(o);let s=e.replace(/'/g,"'\\''"),i=await this.sandbox.exec(["bash","-c",`cat > '${s}'`],{mode:"binary"});await this.writeStdinChunked(i.stdin,new Uint8Array(r)),await i.stdin.getWriter().close(),await i.wait(),await this.chownToUser(e);}async writeBatch(e){let t=pack(),r=new Set;for(let i of e){let a=this.toBuffer(i.data),d=i.path.startsWith("/")?i.path.slice(1):i.path;t.entry({name:d},a);let m=i.path.substring(0,i.path.lastIndexOf("/"));m&&r.add(m);}t.finalize();let o=await this.sandbox.exec(["tar","-xf","-","-C","/"],{mode:"binary"});if(await this.writeStdinCoalesced(o.stdin,t),await o.stdin.getWriter().close(),await o.wait(),r.size>0){let i=Array.from(r),a=new Set(i.map(d=>d.split("/").slice(0,4).join("/")));for(let d of a)await this.chownToUser(d,true);}}async makeDir(e){await(await this.sandbox.exec(["mkdir","-p",e],{timeoutMs:1e4})).wait(),await this.chownToUser(e,true);}async exists(e){return await(await this.sandbox.exec(["test","-e",e],{timeoutMs:1e4})).wait()===0}async list(e){try{return (await this.sandbox.filesystem.listFiles(e)).map(r=>z(r))}catch(t){throw X(t)?new u(e,"modal"):t}}async stat(e){try{return z(await this.sandbox.filesystem.stat(e))}catch(t){throw X(t)?new u(e,"modal"):t}}async readRange(e,t){if(W(t),t.length===0)return new Uint8Array(0);let r=V(e),o=`[ -e ${r} ] || exit ${k}; [ -d ${r} ] && exit ${_}; tail -c +${t.offset+1} -- ${r} | head -c ${t.length}; s=("\${PIPESTATUS[@]}"); [ "\${s[1]}" -eq 0 ] || exit "\${s[1]}"; [ "\${s[0]}" -eq 0 ] || [ "\${s[0]}" -eq ${Ae} ] || exit "\${s[0]}"`,s=await this.sandbox.exec(["bash","-c",o],{mode:"binary",timeoutMs:Y}),i=await s.stdout.readBytes(),a=await s.wait();if(a===k)throw new u(e,"modal");if(a===_)throw new Error(`modal: ${e} is a directory`);if(a!==0){let d=new TextDecoder().decode(await s.stderr.readBytes()).trim();throw new Error(`modal: range read of ${e} failed (exit ${a}): ${d}`)}return i}async remove(e){await(await this.sandbox.exec(["rm","-rf",e],{timeoutMs:3e4})).wait();}async rename(e,t){await(await this.sandbox.exec(["mv",e,t],{timeoutMs:3e4})).wait();}async readStream(e){return (await this.sandbox.exec(["cat",e],{timeoutMs:Y,mode:"binary"})).stdout}async writeStream(e,t){let r=e.substring(0,e.lastIndexOf("/"));r&&await this.makeDir(r);let o=e.replace(/'/g,"'\\''"),s=await this.sandbox.exec(["bash","-c",`cat > '${o}'`],{mode:"binary"});await this.writeStdinCoalesced(s.stdin,we(t)),await s.stdin.getWriter().close(),await s.wait(),await this.chownToUser(e);}async writeFromPath(e,t){let{createReadStream:r}=await import('fs'),{Readable:o}=await import('stream'),s=o.toWeb(r(t,{highWaterMark:y}));await this.writeStream(e,s);}async uploadUrl(e,t){throw new l("files.uploadUrl","modal","Modal issues no signed upload URLs")}async downloadUrl(e,t){throw new l("files.downloadUrl","modal","Modal issues no signed download URLs")}async watchDir(e,t,r){await this.stat(e);let o=this.sandbox.filesystem.watch(e,{recursive:r?.recursive??false,filter:[...Me]})[Symbol.asyncIterator](),s=false;return (async()=>{for(;;){let{done:i,value:a}=await o.next();if(i||s)break;for(let d of Ee(a))await t(d);}})().catch(()=>{}),{stop:async()=>{s=true,o.return?.().catch(()=>{});}}}toBuffer(e){if(typeof e=="string")return Buffer.from(e,"utf-8");if(e instanceof Buffer)return e;if(e instanceof ArrayBuffer||e instanceof Uint8Array)return Buffer.from(e);throw new Error(`Unsupported data type: ${typeof e}`)}};function z(n){let e=j(n.mode),t={name:n.name,path:n.path,type:e,size:n.size,mtime:K(n.modifiedTime),mode:G(n.permissions),owner:n.owner,group:n.group};return e==="symlink"&&n.symlinkTarget!==null&&(t.target=n.symlinkTarget),t}function X(n){return !!n&&typeof n=="object"&&n.name==="SandboxFilesystemNotFoundError"}var Me=["Create","Modify","Remove"];function Ee(n){if(n.eventType==="Create")return n.paths.map(e=>({path:e,type:"create"}));if(n.eventType==="Remove")return n.paths.map(e=>({path:e,type:"remove"}));if(n.eventType==="Modify"){let e=n.paths.length>=2?"rename":"write";return n.paths.map(t=>({path:t,type:e}))}return []}var Ae=141,Y=3e5,Re=["set -m","exec 3<&0",'"$@" </dev/null 3<&- & job=$!',"( cat <&3 >/dev/null; kill -TERM -- -$job 2>/dev/null ) & eof=$!","exec 2>/dev/null","trap 'kill -TERM -- -$job' TERM","wait $job; rc=$?","kill -TERM -- -$eof","exit $rc"].join("; "),p=class{constructor(e,t,r){this.sandbox=e;this.commands=new D(e,r),this.files=new O(e,r),this.image=t;}commands;files;image;get sandboxId(){return this.sandbox.sandboxId}async metrics(){throw new l("metrics","modal","Modal has no sandbox metrics API, and /proc and the cgroup files inside its sandbox report host-wide figures (measured 2026-09-16: MemTotal 1 TiB on a 1 GiB sandbox)")}async getHost(e){let r=(await this.sandbox.tunnels())[e];if(!r)throw new Error(`No tunnel found for port ${e}`);return r.url}async isRunning(){try{return await this.sandbox.poll()===null}catch{return false}}async getInfo(){let e=await this.sandbox.getTags();return B(this.sandbox.sandboxId,e,this.image)}async updateNetwork(e){await this.sandbox.updateNetworkPolicy(I(e));}async kill(){try{await this.sandbox.terminate();}catch{await new Promise(e=>setTimeout(e,500)),await this.sandbox.terminate();}}async pause(){throw new Error("Modal does not support pause/resume. Persist progress with Evolve checkpoints and resume in a fresh sandbox, or use kill() to terminate.")}},U=class{providerType="modal";name="Modal";supportsBootCommand=true;supportsVolumes=true;client;appName;defaultTimeoutMs;imageName;imageSecretName;_app;sandboxUsers=new Map;constructor(e){!e.endpoint&&process.env.MODAL_SERVER_URL?.startsWith("unix:")?process.env.MODAL_SERVER_URL="https://api.modal.com:443":e.endpoint&&(process.env.MODAL_SERVER_URL=e.endpoint),this.client=new ModalClient({tokenId:e.tokenId,tokenSecret:e.tokenSecret}),this.appName=e.appName??"evolve-sandbox",this.defaultTimeoutMs=e.defaultTimeoutMs??36e5,this.imageName=e.imageName??`evolve-all-${x}`,this.imageSecretName=e.imageSecretName;}async getApp(){return this._app||(this._app=await this.client.apps.fromName(this.appName,{createIfMissing:true})),this._app}async resolveImage(e){let t=de(e);if(t==="registry"){let o=this.imageSecretName?await this.client.secrets.fromName(this.imageSecretName):void 0;return this.client.images.fromRegistry(e,o)}if(!this.imageSecretName)throw new Error(`Private registry image "${e}" requires config.imageSecretName \u2014 the name of a Modal Secret holding registry credentials (AWS ECR: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION with read-only ECR IAM). Create one at https://modal.com/secrets`);let r=t==="aws-ecr"?await this.client.secrets.fromName(this.imageSecretName,{requiredKeys:["AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_REGION"]}):await this.client.secrets.fromName(this.imageSecretName);return t==="aws-ecr"?this.client.images.fromAwsEcr(e,r):this.client.images.fromGcpArtifactRegistry(e,r)}async resolveAndBuildImage(e){let t=J[e]??e;if(!/[/:@]/.test(t))try{return {tag:t,image:await this.client.images.fromName(t)}}catch(s){if(!(s instanceof NotFoundError))throw s}let r=await this.getApp(),o=await this.resolveImage(t);return {tag:t,image:await o.build(r)}}async publishImageAs(e,t){if(!e.trim())throw new Error("publishImageAs requires a non-empty alias to publish under");let{image:r}=await this.resolveAndBuildImage(t||this.imageName);return await r.publish(e),e}async prepareImage(e){await this.resolveAndBuildImage(e||this.imageName);}async create(e){let t=e.timeoutMs??this.defaultTimeoutMs;te(t);let r=ne(e.idleTimeoutMs),o=re(e.bootCommand),s=oe(e.volumes);for(let g of e.phaseNetworkPolicies??[])I(g);let i=ae(e.network,e.phaseNetworkPolicies)?I(e.network):F(e.network),a=se(e.resources),d=e.user??$,m=await this.getApp(),{tag:M,image:c}=await this.resolveAndBuildImage(e.image||this.imageName),h=await this.resolveVolumes(s),E=e.envs?Object.fromEntries(Object.entries(e.envs).filter(([,g])=>g!=null)):void 0,le=E&&Object.keys(E).length>0?E:void 0,me={...e.metadata,[Z]:M,[ee]:new Date().toISOString()},A=await this.client.sandboxes.create(m,c,{cpu:a.cpu,memoryMiB:a.memoryMiB,...a.gpu!==void 0?{gpu:a.gpu}:{},timeoutMs:t,...r,...o,workdir:e.workingDirectory,env:le,tags:me,...i,...h!==void 0?{volumes:h}:{}});return e.workingDirectory&&d!=="root"&&await(await A.exec(["chown","-R",`${d}:${d}`,e.workingDirectory],{timeoutMs:3e4})).wait(),this.sandboxUsers.set(A.sandboxId,d),new p(A,M,d)}async resolveVolumes(e){if(e.length===0)return;let t={};for(let r of e){let o;try{o=await this.client.volumes.fromName(r.name,{createIfMissing:r.createIfMissing});}catch(s){let i=s instanceof Error?s.message:String(s),a=s instanceof NotFoundError||s instanceof Error&&s.name==="NotFoundError";throw new f(a?"not-found":"resolve-failed",r.mountPath,r.name,a?`Modal Volume "${r.name}" (to mount at ${r.mountPath}) does not exist and createIfMissing was not set: ${i}`:`Modal Volume "${r.name}" (to mount at ${r.mountPath}) could not be resolved: ${i}`,s)}t[r.mountPath]=o.withMountOptions({readOnly:r.readOnly});}return t}async connect(e,t){let r=await this.client.sandboxes.fromId(e),o=this.sandboxUsers.get(e)??$;return new p(r,void 0,o)}async inspect(e,t){let r=await this.attachSandbox(e),o=await r.poll();if(o!==null)throw new w(e,"modal",`exited with code ${o}`);let s=t?.user??this.sandboxUsers.get(e)??$;return new p(r,void 0,s)}async attachSandbox(e){return this.client.sandboxes.fromId(e)}async list(e){let t=await this.walk(e);if(t.error&&!t.stoppedAtLimit)throw new Error(t.error);return t.sandboxes}async listAll(e){let{stoppedAtLimit:t,...r}=await this.walk(e);return r}async walk(e){if(e?.state&&!e.state.includes("running"))return {sandboxes:[],complete:true,pagesFetched:0,stoppedAtLimit:false};let t;try{t=await this.getApp();}catch(r){return {sandboxes:[],complete:false,pagesFetched:0,stoppedAtLimit:false,error:`sandbox list failed: ${r instanceof Error?r.message:String(r)}`}}return ce(()=>this.client.sandboxes.list({appId:t.appId,tags:e?.metadata}),e?.limit)}async listSandboxIds(){let e=new Set;try{let t=await this.getApp();for await(let r of this.client.sandboxes.list({appId:t.appId}))e.add(r.sandboxId);return {ids:e,complete:!0}}catch{return {ids:new Set,complete:false}}}};async function ce(n,e){let t=[];try{for await(let r of n()){if(e!==void 0&&t.length>=e)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!0,error:`stopped at the requested limit of ${e} with more sandboxes available`};if(t.length>=H)return {sandboxes:t,complete:!1,pagesFetched:t.length,stoppedAtLimit:!1,error:`sandbox list exceeded ${H} sandboxes`};let o=await r.getTags();t.push(B(r.sandboxId,o));}}catch(r){return {sandboxes:t,complete:false,pagesFetched:t.length,stoppedAtLimit:false,error:`sandbox list failed: ${r instanceof Error?r.message:String(r)}`}}return {sandboxes:t,complete:true,pagesFetched:t.length,stoppedAtLimit:false}}function Ge(n={}){let e=n.tokenId??process.env.MODAL_TOKEN_ID,t=n.tokenSecret??process.env.MODAL_TOKEN_SECRET;if(!e||!t)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 U({...n,tokenId:e,tokenSecret:t})}var je=C,Ke=J,qe=F,He=I,ze=ae,Xe=se,Ye=de,Je=B,Qe=ce,Ze=te,et=ne,tt=re,nt=oe,rt=p;
3
+ export{x as EVOLVE_IMAGE_VERSION,Q as MODAL_MAX_LIFETIME_MS,H as MODAL_MAX_LIST_SANDBOXES,y as MODAL_STDIN_CHUNK_BYTES,N as ModalBootCommandError,D as ModalCommands,O as ModalFiles,S as ModalIdleTimeoutError,P as ModalNetworkPolicyError,U as ModalProvider,v as ModalResourcesError,T as ModalSandboxLifetimeError,f as ModalVolumeError,l as SandboxFeatureUnsupportedError,w as SandboxNotRunningError,u as SandboxPathNotFoundError,Je as _testBuildSandboxInfo,Qe as _testCollectSandboxes,He as _testDynamicNetworkPolicyParams,Ke as _testImageMap,tt as _testMapBootCommand,et as _testMapIdleTimeout,qe as _testMapNetworkPolicy,Xe as _testMapResources,nt as _testMapVolumeMounts,rt as _testModalSandboxImplCtor,ze as _testRequiresDynamicNetwork,Ye as _testResolveImageRegistry,Ze as _testValidateTimeout,je as _testWrapCommand,Ge as createModalProvider,ue as isSandboxFeatureUnsupportedError,ge as isSandboxNotRunningError,pe as isSandboxPathNotFoundError};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evolvingmachines/modal",
3
- "version": "0.0.60",
3
+ "version": "0.0.61",
4
4
  "keywords": [
5
5
  "ai",
6
6
  "agents",
@@ -34,7 +34,7 @@
34
34
  "build": "tsup --minify",
35
35
  "dev": "tsup --watch",
36
36
  "type-check": "tsc --noEmit",
37
- "test:unit": "tsx tests/unit/modal-provider.test.ts && tsx tests/unit/modal-list-pagination.test.ts"
37
+ "test:unit": "tsx tests/unit/modal-provider.test.ts && tsx tests/unit/modal-list-pagination.test.ts && tsx tests/unit/modal-files-observe.test.ts"
38
38
  },
39
39
  "dependencies": {
40
40
  "modal": "^0.9.0",