143 lines
5.4 KiB
TypeScript
143 lines
5.4 KiB
TypeScript
/**
|
|
* Reference: agentOS VM as a sandbox backend for Flue.
|
|
*
|
|
* This script demonstrates the core idea behind a Flue sandbox adapter that
|
|
* delegates file/shell operations to an agentOS VM. It does NOT wire into the
|
|
* Flue agent yet — it proves the primitives work end-to-end so you can see
|
|
* exactly how the adapter will map calls.
|
|
*
|
|
* Run: bun --env-file=../../.env packages/agents/scripts/agentos-sandbox-ref.ts
|
|
*
|
|
* Architecture:
|
|
*
|
|
* Flue agent (host) agentOS VM (isolated Wasm+V8)
|
|
* ┌─────────────────────┐ ┌──────────────────────┐
|
|
* │ defineAgent(...) │ │ Linux-like sandbox │
|
|
* │ sandbox: agentOs()│── readFile ──► │ persistent FS │
|
|
* │ │── writeFile ─► │ shell (sh, coreutils)│
|
|
* │ │── exec ──────► │ ~6ms cold start │
|
|
* └─────────────────────┘ └──────────────────────┘
|
|
*
|
|
* The Flue SandboxApi maps 1:1 to AgentOs methods:
|
|
*
|
|
* Flue SandboxApi AgentOs (core SDK)
|
|
* ───────────────────────── ──────────────────────────
|
|
* readFile(path) vm.readFile(path)
|
|
* readFileBuffer(path) vm.readFile(path)
|
|
* writeFile(path, content) vm.writeFile(path, content)
|
|
* stat(path) vm.stat(path)
|
|
* readdir(path) vm.readdir(path)
|
|
* exists(path) vm.exists(path)
|
|
* mkdir(path) vm.mkdir(path)
|
|
* rm(path) vm.remove(path)
|
|
* exec(command, opts) vm.exec(command, opts)
|
|
*/
|
|
|
|
import { AgentOs } from "@rivet-dev/agentos-core";
|
|
import type { VirtualStat } from "@rivet-dev/agentos-core";
|
|
|
|
const formatStat = (stat: VirtualStat): string =>
|
|
JSON.stringify({
|
|
isDirectory: stat.isDirectory,
|
|
isSymbolicLink: stat.isSymbolicLink,
|
|
mtime: new Date(stat.mtimeMs).toISOString(),
|
|
size: stat.size,
|
|
});
|
|
|
|
const main = async () => {
|
|
console.log("=== Booting agentOS VM ===\n");
|
|
|
|
// AgentOs.create() boots a local VM with the default software bundle
|
|
// (sh + coreutils). The VM sleeps when idle and wakes on the next action.
|
|
const vm = await AgentOs.create();
|
|
console.log("VM booted.\n");
|
|
|
|
// ── File operations ──────────────────────────────────────────────
|
|
|
|
console.log("=== writeFile ===");
|
|
await vm.writeFile("/workspace/hello.txt", "Hello from agentOS VM!");
|
|
console.log("wrote /workspace/hello.txt\n");
|
|
|
|
console.log("=== readFile ===");
|
|
const content = await vm.readFile("/workspace/hello.txt");
|
|
console.log("content:", new TextDecoder().decode(content), "\n");
|
|
|
|
console.log("=== stat ===");
|
|
const stat = await vm.stat("/workspace/hello.txt");
|
|
console.log("stat:", formatStat(stat), "\n");
|
|
|
|
console.log("=== exists ===");
|
|
console.log(
|
|
"exists /workspace/hello.txt:",
|
|
await vm.exists("/workspace/hello.txt")
|
|
);
|
|
console.log(
|
|
"exists /workspace/nope.txt:",
|
|
await vm.exists("/workspace/nope.txt"),
|
|
"\n"
|
|
);
|
|
|
|
console.log("=== mkdir + readdir ===");
|
|
await vm.mkdir("/workspace/src", { recursive: true });
|
|
await vm.writeFile("/workspace/src/a.ts", "export const a = 1;");
|
|
await vm.writeFile("/workspace/src/b.ts", "export const b = 2;");
|
|
console.log(
|
|
"readdir /workspace/src:",
|
|
await vm.readdir("/workspace/src"),
|
|
"\n"
|
|
);
|
|
|
|
// ── Shell exec ───────────────────────────────────────────────────
|
|
|
|
console.log("=== exec: echo ===");
|
|
const echoResult = await vm.exec("echo 'shell works!'");
|
|
console.log(
|
|
"stdout:",
|
|
echoResult.stdout.trim(),
|
|
`(exit ${echoResult.exitCode})\n`
|
|
);
|
|
|
|
console.log("=== exec: pipe + coreutils ===");
|
|
const pipeResult = await vm.exec("echo 'hello world' | tr a-z A-Z | wc -w");
|
|
console.log(
|
|
"stdout:",
|
|
pipeResult.stdout.trim(),
|
|
`(exit ${pipeResult.exitCode})\n`
|
|
);
|
|
|
|
console.log("=== exec: with cwd + env ===");
|
|
await vm.mkdir("/workspace/project", { recursive: true });
|
|
await vm.writeFile("/workspace/project/file.txt", "data");
|
|
const cwdResult = await vm.exec("pwd && ls", {
|
|
cwd: "/workspace/project",
|
|
env: { CUSTOM_VAR: "from-env" },
|
|
});
|
|
console.log(
|
|
"stdout:",
|
|
cwdResult.stdout.trim(),
|
|
`(exit ${cwdResult.exitCode})\n`
|
|
);
|
|
|
|
// ── Remove ───────────────────────────────────────────────────────
|
|
|
|
console.log("=== remove ===");
|
|
await vm.remove("/workspace/src/b.ts");
|
|
console.log(
|
|
"after remove, readdir:",
|
|
await vm.readdir("/workspace/src"),
|
|
"\n"
|
|
);
|
|
|
|
// ── Cleanup ──────────────────────────────────────────────────────
|
|
|
|
await vm.dispose();
|
|
console.log("=== VM disposed. All primitives work. ===");
|
|
};
|
|
|
|
try {
|
|
await main();
|
|
} catch (error) {
|
|
console.error(error);
|
|
process.exit(1);
|
|
}
|