UNPKG

@tanstack/ai-sandbox

Version:

Provider-agnostic sandbox layer for TanStack AI — run harness adapters inside isolated sandboxes (defineSandbox, defineWorkspace, withSandbox) with a uniform SandboxHandle, workspace bootstrap, policy, and resumable lifecycle.

129 lines (128 loc) • 4.85 kB
import { ExecResult, SandboxHandle } from './contracts.js'; /** * A `WorkspaceBackend` over a live sandbox. With it, `workspaceTools()` * reads and writes files and runs commands in the sandbox, not on this * machine. * * Who stops the sandbox: * * - By default, you. `close()` of the backend stops the jobs that it * started, and leaves the sandbox running. * - With `owned: true`, the backend. `close({ reason: 'closed' })` also * destroys the sandbox. Use it for a sandbox that you create in * `workspace.bind`, one for each thread. * * - Paths are POSIX paths in the sandbox, like `/workspace/src/a.ts`. * - `exec` gives exit code 124 when `timeoutMs` stops a command, and 1 when * `signal` stops it. A sandbox with `capabilities.killableProcesses` off * cannot stop the command, so it continues in the sandbox. * - `spawn` is there only when the sandbox has * `capabilities.backgroundProcesses`. * - `stat` gives `mtimeMs: 0`. The sandbox file system does not give the * time of the last change. With `lstat`, `stat` never reads the file. * - The `signal` of a file call goes to the sandbox file system and to the * commands that the call runs, like `readlink`. * - `readLines` reads one page of a file in the sandbox with `wc`, `od`, * and `awk`, so only the page comes out of the sandbox. * - `realpath` resolves links in the sandbox: `lstat` finds each link, and * `readlink` in the sandbox reads its target. A sandbox without `lstat` * cannot find links, so `realpath` throws, and the tools refuse each path. * * - `spawn` keeps the newest 1 MiB of the output of a job, and gives each * chunk to `onOutput` as it comes. * - `close({ reason: 'moved' })` keeps the jobs and the sandbox, so the * next machine can resume them. * - `binding()` gives the provider and the id of the sandbox. The session * log keeps them, so `reattach` of `workspace` can resume the sandbox on * another machine. * - The id of a job is its pid in the sandbox and its start time from * `ps`. `killJob` stops it, also from a backend over the same sandbox on * another machine, and only when the process with that pid started at * that time. A sandbox without a `ps -o lstart` cannot check it, so * `killJob` rejects for a job that runs. * * @param handle - The sandbox, from `provider.create()` or `provider.resume()`. * @param options - `owned: true` when the backend owns the sandbox. Then * `close` destroys it. Default: `false`. * * @example * ```ts * const provider = localProcessSandbox() * workspaceTools({ * workspace: { * bind: async () => ({ * ...sandboxWorkspaceBackend(await provider.create({}), { owned: true }), * root: '/workspace', * }), * }, * }) * ``` */ export declare function sandboxWorkspaceBackend(handle: SandboxHandle, { owned }?: { owned?: boolean; }): { close: (input: { reason: "closed" | "moved"; }) => Promise<void>; spawn?: ((command: string, options?: { cwd?: string; env?: Record<string, string>; onOutput?: (text: string) => void; }) => { wait: () => Promise<{ exitCode: number; }>; kill: () => void; output: () => string; jobId?: Promise<string | undefined>; }) | undefined; shell: "sh"; binding: () => { kind: "sandbox"; provider: string; sandboxId: string; }; killJob: (jobId: string) => Promise<boolean>; readFile: (path: string, options?: { signal?: AbortSignal; } | undefined) => Promise<Uint8Array<ArrayBufferLike>>; writeFile: (path: string, data: string | Uint8Array<ArrayBufferLike>, options?: { signal?: AbortSignal; } | undefined) => Promise<void>; remove: (path: string, options?: { signal?: AbortSignal; } | undefined) => Promise<void>; readLines: (path: string, options: { offset: number; limit: number; signal?: AbortSignal; }) => Promise<{ head: Uint8Array; size: number; lines: Array<string>; total?: number; }>; stat: (path: string, options?: { signal?: AbortSignal; }) => Promise<{ type: "file" | "dir"; size: number; mtimeMs: number; } | undefined>; realpath: (path: string, options?: { signal?: AbortSignal; }) => Promise<string | undefined>; readdir: (path: string, options?: { signal?: AbortSignal; } | undefined) => Promise<{ name: string; type: "link" | "file" | "dir"; }[]>; exec: (command: string, options?: { cwd?: string; env?: Record<string, string>; timeoutMs?: number; signal?: AbortSignal; onStart?: (jobId: string) => void; } | undefined) => Promise<ExecResult>; };