Mike0021's picture
Deploy prebuilt static pi web agent
059f0de verified
const BOOT_FILES = {
"package.json": {
file: {
contents: JSON.stringify(
{
type: "module",
scripts: {
demo: "node hello.js",
},
dependencies: {},
devDependencies: {},
},
null,
2,
),
},
},
"README.md": {
file: {
contents:
"# Pi web sandbox\n\nThis filesystem and every spawned process run inside WebContainers in this browser tab.\n",
},
},
"hello.js": {
file: {
contents: 'console.log("hello from the browser sandbox");\nconsole.log(2 + 2);\n',
},
},
};
const MAX_OUTPUT_CHARS = 16000;
const DEFAULT_TIMEOUT_MS = 10000;
export function createSandbox({ onLog = () => {}, onStatus = () => {} } = {}) {
let instance = null;
let booting = null;
let root = "";
async function boot() {
if (instance) return instance;
if (booting) return booting;
booting = (async () => {
if (!globalThis.crossOriginIsolated) {
throw new Error("WebContainers need cross-origin isolation. Start this app through Vite or another server that sends COOP/COEP headers.");
}
onStatus("Booting WebContainer");
const { WebContainer } = await import("@webcontainer/api");
instance = await WebContainer.boot({
coep: "credentialless",
workdirName: "workspace",
});
root = instance.workdir;
await instance.mount(BOOT_FILES);
onLog(`sandbox booted at ${root}`);
onStatus("Sandbox ready");
return instance;
})();
try {
return await booting;
} finally {
booting = null;
}
}
function assertRelativePath(path) {
const value = String(path || "").trim().replace(/^\/+/, "");
if (!value || value.includes("\0")) {
throw new Error("Path is required.");
}
const segments = value.split("/").filter(Boolean);
if (segments.some((segment) => segment === "." || segment === "..")) {
throw new Error("Paths must stay inside the sandbox workspace.");
}
return segments.join("/");
}
function toWorkspacePath(path) {
return assertRelativePath(path);
}
async function reset() {
const wc = await boot();
const entries = await wc.fs.readdir(".");
await Promise.all(
entries.map((entry) =>
wc.fs.rm(entry, {
force: true,
recursive: true,
}),
),
);
await wc.mount(BOOT_FILES);
onLog("sandbox reset");
return "Sandbox reset to the starter project.";
}
async function listFiles(path = ".") {
const wc = await boot();
const target = path === "." || path === "" ? "." : toWorkspacePath(path);
const entries = await wc.fs.readdir(target, { withFileTypes: true });
return entries.map((entry) => `${entry.isDirectory() ? "dir " : "file"} ${entry.name}`).join("\n") || "(empty)";
}
async function readFile(path) {
const wc = await boot();
return await wc.fs.readFile(toWorkspacePath(path), "utf-8");
}
async function writeFile(path, content) {
const wc = await boot();
const relative = assertRelativePath(path);
const parent = relative.split("/").slice(0, -1).join("/");
if (parent) {
await wc.fs.mkdir(parent, { recursive: true });
}
await wc.fs.writeFile(relative, String(content ?? ""));
onLog(`wrote ${relative}`);
return `Wrote ${relative}`;
}
async function runCommand(command, args = [], timeoutMs = DEFAULT_TIMEOUT_MS) {
const wc = await boot();
const cmd = String(command || "").trim();
if (!cmd) throw new Error("Command is required.");
const cleanArgs = Array.isArray(args) ? args.map((arg) => String(arg)) : [];
onLog(`$ ${[cmd, ...cleanArgs].join(" ")}`);
const process = await wc.spawn(cmd, cleanArgs, {
terminal: {
cols: 96,
rows: 28,
},
});
let output = "";
const reader = process.output.getReader();
const pump = (async () => {
while (true) {
const { done, value } = await reader.read();
if (done) break;
output += value;
if (output.length > MAX_OUTPUT_CHARS) {
output = `${output.slice(0, MAX_OUTPUT_CHARS)}\n[output truncated]`;
process.kill();
break;
}
}
})();
const timeout = new Promise((resolve) => {
setTimeout(() => {
process.kill();
resolve("timeout");
}, Number(timeoutMs) || DEFAULT_TIMEOUT_MS);
});
const exitCode = await Promise.race([process.exit, timeout]);
await pump.catch(() => {});
const normalizedExitCode = exitCode === "timeout" ? 124 : exitCode;
const result = {
command: [cmd, ...cleanArgs].join(" "),
exitCode: normalizedExitCode,
output: output.trimEnd(),
};
onLog(`exit ${normalizedExitCode}`);
return result;
}
return {
boot,
reset,
listFiles,
readFile,
writeFile,
runCommand,
get isReady() {
return Boolean(instance);
},
};
}