From 937db04a647f3d84063e8ddd05fa1a146d19dc5c Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Thu, 12 Mar 2026 20:12:15 +0100 Subject: [PATCH 1/7] Move from v1 to v2 endpoints --- examples/ai-example/app/actions.tsx | 8 +- examples/ai-example/app/api/logs/route.ts | 6 +- examples/ai-example/app/page.tsx | 28 ++-- packages/sandbox/src/commands/remove.ts | 7 +- packages/sandbox/src/commands/sessions.ts | 2 +- packages/sandbox/src/commands/snapshots.ts | 4 +- .../src/api-client/api-client.test.ts | 76 ++++------ .../src/api-client/api-client.ts | 133 +++++++++--------- .../src/api-client/api-error.ts | 15 +- .../src/api-client/base-client.ts | 33 +++-- .../src/api-client/validators.ts | 29 ++-- packages/vercel-sandbox/src/command.ts | 28 ++-- packages/vercel-sandbox/src/sandbox.ts | 11 +- packages/vercel-sandbox/src/session.ts | 40 +++--- packages/vercel-sandbox/src/snapshot.ts | 6 +- 15 files changed, 209 insertions(+), 217 deletions(-) diff --git a/examples/ai-example/app/actions.tsx b/examples/ai-example/app/actions.tsx index 801686c..21ef09c 100644 --- a/examples/ai-example/app/actions.tsx +++ b/examples/ai-example/app/actions.tsx @@ -17,11 +17,11 @@ export async function createSandbox() { } export async function uploadFiles(params: { - sandboxId: string; + sandboxName: string; files: { path: string; content: string }[]; }) { const sandbox = await Sandbox.get({ - name: params.sandboxId, + name: params.sandboxName, }); const files = params.files.map((file) => ({ @@ -56,11 +56,11 @@ export async function uploadFiles(params: { export async function runCommand(params: { args: string[]; cmd: string; - sandboxId: string; + sandboxName: string; detached?: boolean; }) { const sandbox = await Sandbox.get({ - name: params.sandboxId, + name: params.sandboxName, }); const cmd = await sandbox.runCommand({ diff --git a/examples/ai-example/app/api/logs/route.ts b/examples/ai-example/app/api/logs/route.ts index 388ba59..f5ff019 100644 --- a/examples/ai-example/app/api/logs/route.ts +++ b/examples/ai-example/app/api/logs/route.ts @@ -5,16 +5,16 @@ export const maxDuration = 120; export async function POST(request: Request) { const body = await request.json(); const cmdId = body.cmdId; - const sandboxId = body.sandboxId; + const sandboxName = body.sandboxName; - if (!cmdId || !sandboxId) { + if (!cmdId || !sandboxName) { return new Response("Missing required parameters", { status: 400 }); } const encoder = new TextEncoder(); const stream = new ReadableStream({ async start(controller) { - const sandbox = await Sandbox.get({ name: sandboxId }); + const sandbox = await Sandbox.get({ name: sandboxName }); const command = await sandbox.getCommand(cmdId); for await (const log of command.logs()) { diff --git a/examples/ai-example/app/page.tsx b/examples/ai-example/app/page.tsx index 9b40046..3e0ac48 100644 --- a/examples/ai-example/app/page.tsx +++ b/examples/ai-example/app/page.tsx @@ -15,11 +15,11 @@ export default function SplitScreenChatOptimized() { const [input, setInput] = useState(""); const [pending, startTransition] = useTransition(); const [logs, setLogs] = useState([]); - const [sandboxId, setSandboxId] = useState(null); + const [sandboxName, setSandboxName] = useState(null); const [renderPreview, setRenderPreview] = useState(false); const isReadyRef = useRef(false); - const sandboxIdRef = useRef(null); + const sandboxNameRef = useRef(null); const sandboxRoutesRef = useRef<{ subdomain: string; port: number }[] | null>( null, ); @@ -45,41 +45,41 @@ export default function SplitScreenChatOptimized() { onFinish(message) { const msg = message.message.parts.find((part) => part.type === "text"); startTransition(async () => { - if (sandboxIdRef.current && sandboxRoutesRef.current && msg) { + if (sandboxNameRef.current && sandboxRoutesRef.current && msg) { await uploadFiles({ files: extractCodeBlocks(msg!.text), - sandboxId: sandboxIdRef.current, + sandboxName: sandboxNameRef.current, }); if (!isReadyRef.current) { isReadyRef.current = true; const install = await runCommand({ - sandboxId: sandboxIdRef.current, + sandboxName: sandboxNameRef.current, cmd: "npm", args: ["install", "--loglevel", "info"], detached: true, }); for await (const log of getLogs({ - sandboxId: sandboxIdRef.current, + sandboxName: sandboxNameRef.current, cmdId: install.cmdId, })) { setLogs((prevLogs) => [...prevLogs, log]); } const next = await runCommand({ - sandboxId: sandboxIdRef.current, + sandboxName: sandboxNameRef.current, cmd: "npm", args: ["run", "dev"], detached: true, }); (async () => { - if (!sandboxRoutesRef.current || !sandboxIdRef.current) { + if (!sandboxRoutesRef.current || !sandboxNameRef.current) { console.error("Sandbox routes or ID is missing"); return; } for await (const log of getLogs({ - sandboxId: sandboxIdRef.current, + sandboxName: sandboxNameRef.current, cmdId: next.cmdId, })) { setLogs((prevLogs) => [...prevLogs, log]); @@ -107,19 +107,19 @@ export default function SplitScreenChatOptimized() { const handleFormSubmit = useCallback( (event: React.FormEvent) => { - if (!sandboxId || !url || !routes) { + if (!sandboxName || !url || !routes) { startTransition(async () => { const { id, url, routes } = await createSandbox(); setSandboxUrl(url); setRoutes(routes); - setSandboxId(id); - sandboxIdRef.current = id; + setSandboxName(id); + sandboxNameRef.current = id; sandboxRoutesRef.current = routes; }); } return handleSubmit(event); }, - [sandboxId, url, routes, handleSubmit], + [sandboxName, url, routes, handleSubmit], ); const handleReload = useCallback(() => { @@ -225,7 +225,7 @@ export default function SplitScreenChatOptimized() { ); } -async function* getLogs(params: { cmdId: string; sandboxId: string }) { +async function* getLogs(params: { cmdId: string; sandboxName: string }) { const response = await fetch("/api/logs", { method: "POST", headers: { "Content-Type": "application/json" }, diff --git a/packages/sandbox/src/commands/remove.ts b/packages/sandbox/src/commands/remove.ts index 241e791..1d566d6 100644 --- a/packages/sandbox/src/commands/remove.ts +++ b/packages/sandbox/src/commands/remove.ts @@ -17,17 +17,12 @@ export const remove = cmd.command({ type: sandboxName, description: "more sandboxes to remove", }), - preserveSnapshots: cmd.flag({ - long: "preserve-snapshots", - description: "Keep snapshots when removing the sandbox", - }), scope, }, async handler({ scope: { token, team, project }, sandboxName, sandboxNames, - preserveSnapshots, }) { const tasks = Array.from( new Set([sandboxName, ...sandboxNames]), @@ -40,7 +35,7 @@ export const remove = cmd.command({ projectId: project, name, }); - await sandbox.delete({ preserveSnapshots }); + await sandbox.delete(); }, }), ); diff --git a/packages/sandbox/src/commands/sessions.ts b/packages/sandbox/src/commands/sessions.ts index a798144..1256f38 100644 --- a/packages/sandbox/src/commands/sessions.ts +++ b/packages/sandbox/src/commands/sessions.ts @@ -36,7 +36,7 @@ const list = cmd.command({ return sandbox.listSessions(); })(); - const sessions = sessionData.json.sandboxes; + const sessions = sessionData.json.sessions; console.log( table({ diff --git a/packages/sandbox/src/commands/snapshots.ts b/packages/sandbox/src/commands/snapshots.ts index b8b40d9..054f9b7 100644 --- a/packages/sandbox/src/commands/snapshots.ts +++ b/packages/sandbox/src/commands/snapshots.ts @@ -55,7 +55,7 @@ const list = cmd.command({ : timeAgo(s.expiresAt), }, SIZE: { value: (s) => formatBytes(s.sizeBytes) }, - ["SOURCE SESSION"]: { value: (s) => s.sourceSandboxId }, + ["SOURCE SESSION"]: { value: (s) => s.sourceSessionId }, }, }), ); @@ -98,7 +98,7 @@ const get = cmd.command({ CREATED: { value: (s) => timeAgo(s.createdAt) }, EXPIRATION: { value: (s) => s.status === 'deleted' ? chalk.gray.dim('deleted') : timeAgo(s.expiresAt) }, SIZE: { value: (s) => formatBytes(s.sizeBytes) }, - ["SOURCE SESSION"]: { value: (s) => s.sourceSandboxId }, + ["SOURCE SESSION"]: { value: (s) => s.sourceSessionId }, }, }), ); diff --git a/packages/vercel-sandbox/src/api-client/api-client.test.ts b/packages/vercel-sandbox/src/api-client/api-client.test.ts index 313a9ac..548a718 100644 --- a/packages/vercel-sandbox/src/api-client/api-client.test.ts +++ b/packages/vercel-sandbox/src/api-client/api-client.test.ts @@ -29,7 +29,7 @@ describe("APIClient", () => { }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); const results: Array<{ stream: string; data: string }> = []; for await (const log of logs) { @@ -50,7 +50,7 @@ describe("APIClient", () => { }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); const results: Array<{ stream: string; data: string }> = []; for await (const log of logs) { @@ -71,7 +71,7 @@ describe("APIClient", () => { }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); await expect(async () => { for await (const _ of logs) { @@ -87,7 +87,7 @@ describe("APIClient", () => { }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); try { for await (const _ of logs) { @@ -111,7 +111,7 @@ describe("APIClient", () => { }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); await expect(async () => { for await (const _ of logs) { @@ -137,7 +137,7 @@ describe("APIClient", () => { }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); const results: Array<{ stream: string; data: string }> = []; await expect(async () => { @@ -150,14 +150,14 @@ describe("APIClient", () => { expect(results[0]).toEqual({ stream: "stdout", data: "some logs" }); }); - it("includes sandboxId in APIError", async () => { + it("includes sessionId in APIError", async () => { mockFetch.mockResolvedValue( new Response(null, { headers: { "content-type": "application/json" }, }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); try { for await (const _ of logs) { @@ -165,11 +165,11 @@ describe("APIClient", () => { expect.fail("Expected APIError to be thrown"); } catch (err) { expect(err).toBeInstanceOf(APIError); - expect((err as APIError).sandboxId).toBe("sbx_123"); + expect((err as APIError).sessionId).toBe("sbx_123"); } }); - it("includes sandboxId in StreamError", async () => { + it("includes sessionId in StreamError", async () => { const logLines = [ { stream: "error", @@ -186,7 +186,7 @@ describe("APIClient", () => { }), ); - const logs = client.getLogs({ sandboxId: "sbx_123", cmdId: "cmd_456" }); + const logs = client.getLogs({ sessionId: "sbx_123", cmdId: "cmd_456" }); try { for await (const _ of logs) { @@ -194,7 +194,7 @@ describe("APIClient", () => { expect.fail("Expected StreamError to be thrown"); } catch (err) { expect(err).toBeInstanceOf(StreamError); - expect((err as StreamError).sandboxId).toBe("sbx_123"); + expect((err as StreamError).sessionId).toBe("sbx_123"); } }); }); @@ -219,7 +219,7 @@ describe("APIClient", () => { name: "echo", args: ["hello"], cwd: "/", - sandboxId: "sbx_123", + sessionId: "sbx_123", exitCode: null, startedAt: 1, }, @@ -238,7 +238,7 @@ describe("APIClient", () => { ); const result = await client.runCommand({ - sandboxId: "sbx_123", + sessionId: "sbx_123", command: "echo", args: ["hello"], env: {}, @@ -260,7 +260,7 @@ describe("APIClient", () => { try { await client.runCommand({ - sandboxId: "sbx_123", + sessionId: "sbx_123", command: "echo", args: ["hello"], env: {}, @@ -319,9 +319,9 @@ describe("APIClient", () => { }), ); - const result = await client.stopSandbox({ sandboxId: "sbx_123" }); + const result = await client.stopSandbox({ sessionId: "sbx_123" }); - expect(result.json.sandbox.status).toBe("stopping"); + expect(result.json.session.status).toBe("stopping"); expect(mockFetch).toHaveBeenCalledTimes(1); }); @@ -346,7 +346,7 @@ describe("APIClient", () => { ); const promise = client.stopSandbox({ - sandboxId: "sbx_123", + sessionId: "sbx_123", blocking: true, }); @@ -355,7 +355,7 @@ describe("APIClient", () => { await vi.advanceTimersByTimeAsync(500); const result = await promise; - expect(result.json.sandbox.status).toBe("stopped"); + expect(result.json.session.status).toBe("stopped"); expect(mockFetch).toHaveBeenCalledTimes(3); }); @@ -374,14 +374,14 @@ describe("APIClient", () => { ); const promise = client.stopSandbox({ - sandboxId: "sbx_123", + sessionId: "sbx_123", blocking: true, }); await vi.advanceTimersByTimeAsync(500); const result = await promise; - expect(result.json.sandbox.status).toBe("failed"); + expect(result.json.session.status).toBe("failed"); expect(mockFetch).toHaveBeenCalledTimes(2); }); @@ -400,14 +400,14 @@ describe("APIClient", () => { ); const promise = client.stopSandbox({ - sandboxId: "sbx_123", + sessionId: "sbx_123", blocking: true, }); await vi.advanceTimersByTimeAsync(500); const result = await promise; - expect(result.json.sandbox.status).toBe("aborted"); + expect(result.json.session.status).toBe("aborted"); expect(mockFetch).toHaveBeenCalledTimes(2); }); }); @@ -427,7 +427,7 @@ describe("APIClient", () => { createdAt: Date.now(), updatedAt: Date.now(), status: "running" as const, - currentSandboxId: "sbx_123", + currentSessionId: "sbx_123", }); const makeSandbox = () => ({ @@ -516,7 +516,7 @@ describe("APIClient", () => { createdAt: Date.now(), updatedAt: Date.now(), status: "running" as const, - currentSandboxId: "sbx_123", + currentSessionId: "sbx_123", }); beforeEach(() => { @@ -591,7 +591,7 @@ describe("APIClient", () => { createdAt: Date.now(), updatedAt: Date.now(), status: "running" as const, - currentSandboxId: "sbx_123", + currentSessionId: "sbx_123", }); beforeEach(() => { @@ -646,7 +646,7 @@ describe("APIClient", () => { createdAt: Date.now(), updatedAt: Date.now(), status: "running" as const, - currentSandboxId: "sbx_123", + currentSessionId: "sbx_123", }); beforeEach(() => { @@ -679,24 +679,6 @@ describe("APIClient", () => { expect(url).toContain("preserveSandboxes=false"); expect(opts.method).toBe("DELETE"); }); - - it("passes preserveSnapshots when provided", async () => { - const body = { namedSandbox: makeNamedSandbox() }; - mockFetch.mockResolvedValue( - new Response(JSON.stringify(body), { - headers: { "content-type": "application/json" }, - }), - ); - - await client.deleteNamedSandbox({ - name: "my-sandbox", - projectId: "proj_123", - preserveSnapshots: true, - }); - - const [url] = mockFetch.mock.calls[0]; - expect(url).toContain("preserveSnapshots=true"); - }); }); describe("createSnapshot", () => { @@ -731,7 +713,7 @@ describe("APIClient", () => { }, snapshot: { id: "snap_123", - sourceSandboxId: "sbx_123", + sourceSessionId: "sbx_123", region: "iad1", status: "created", sizeBytes: 1024, @@ -744,7 +726,7 @@ describe("APIClient", () => { ); await client.createSnapshot({ - sandboxId: "sbx_123", + sessionId: "sbx_123", expiration: 0, }); diff --git a/packages/vercel-sandbox/src/api-client/api-client.ts b/packages/vercel-sandbox/src/api-client/api-client.ts index 08778bc..d9e4fc3 100644 --- a/packages/vercel-sandbox/src/api-client/api-client.ts +++ b/packages/vercel-sandbox/src/api-client/api-client.ts @@ -6,18 +6,16 @@ import { } from "./base-client"; import { type CommandFinishedData, - SandboxAndRoutesResponse, - SandboxResponse, + SessionAndRoutesResponse, + SessionResponse, + SessionsResponse, CommandResponse, CommandFinishedResponse, EmptyResponse, LogLine, type LogLineStdout, type LogLineStderr, - SandboxesResponse, SnapshotsResponse, - ExtendTimeoutResponse, - UpdateNetworkPolicyResponse, SnapshotResponse, CreateSnapshotResponse, NamedSandboxAndSessionResponse, @@ -137,15 +135,15 @@ export class APIClient extends BaseClient { }); } - async getSandbox( - params: WithPrivate<{ sandboxId: string; signal?: AbortSignal }>, + async getSession( + params: WithPrivate<{ sessionId: string; signal?: AbortSignal }>, ) { const privateParams = getPrivateParams(params); let querystring = new URLSearchParams(privateParams).toString(); querystring = querystring ? `?${querystring}` : ""; return parseOrThrow( - SandboxAndRoutesResponse, - await this.request(`/v1/sandboxes/${params.sandboxId}${querystring}`, { + SessionAndRoutesResponse, + await this.request(`/v2/sandboxes/sessions/${params.sessionId}${querystring}`, { signal: params.signal, }), ); @@ -179,7 +177,7 @@ export class APIClient extends BaseClient { const privateParams = getPrivateParams(params); return parseOrThrow( NamedSandboxAndSessionResponse, - await this.request("/v1/sandboxes/named", { + await this.request("/v2/sandboxes", { method: "POST", body: JSON.stringify({ projectId: params.projectId, @@ -202,7 +200,7 @@ export class APIClient extends BaseClient { } async runCommand(params: { - sandboxId: string; + sessionId: string; cwd?: string; command: string; args: string[]; @@ -212,7 +210,7 @@ export class APIClient extends BaseClient { signal?: AbortSignal; }): Promise<{ command: CommandData; finished: Promise }>; async runCommand(params: { - sandboxId: string; + sessionId: string; cwd?: string; command: string; args: string[]; @@ -222,7 +220,7 @@ export class APIClient extends BaseClient { signal?: AbortSignal; }): Promise>>; async runCommand(params: { - sandboxId: string; + sessionId: string; cwd?: string; command: string; args: string[]; @@ -233,7 +231,7 @@ export class APIClient extends BaseClient { }) { if (params.wait) { const response = await this.request( - `/v1/sandboxes/${params.sandboxId}/cmd`, + `/v2/sandboxes/sessions/${params.sessionId}/cmd`, { method: "POST", body: JSON.stringify({ @@ -255,14 +253,14 @@ export class APIClient extends BaseClient { if (response.headers.get("content-type") !== "application/x-ndjson") { throw new APIError(response, { message: "Expected a stream of command data", - sandboxId: params.sandboxId, + sessionId: params.sessionId, }); } if (response.body === null) { throw new APIError(response, { message: "No response body", - sandboxId: params.sandboxId, + sessionId: params.sessionId, }); } @@ -276,7 +274,7 @@ export class APIClient extends BaseClient { const { command } = CommandResponse.parse(commandChunk.value); const finished = (async () => { - const finishedChunk = await iterator.next(); + const finishedChunk = await iterator.next(); const { command } = CommandFinishedResponse.parse(finishedChunk.value); return command; })(); @@ -286,7 +284,7 @@ export class APIClient extends BaseClient { return parseOrThrow( CommandResponse, - await this.request(`/v1/sandboxes/${params.sandboxId}/cmd`, { + await this.request(`/v2/sandboxes/sessions/${params.sessionId}/cmd`, { method: "POST", body: JSON.stringify({ command: params.command, @@ -301,19 +299,19 @@ export class APIClient extends BaseClient { } async getCommand(params: { - sandboxId: string; + sessionId: string; cmdId: string; wait: true; signal?: AbortSignal; }): Promise>>; async getCommand(params: { - sandboxId: string; + sessionId: string; cmdId: string; wait?: boolean; signal?: AbortSignal; }): Promise>>; async getCommand(params: { - sandboxId: string; + sessionId: string; cmdId: string; wait?: boolean; signal?: AbortSignal; @@ -322,28 +320,28 @@ export class APIClient extends BaseClient { ? parseOrThrow( CommandFinishedResponse, await this.request( - `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`, + `/v2/sandboxes/sessions/${params.sessionId}/cmd/${params.cmdId}`, { signal: params.signal, query: { wait: "true" } }, ), ) : parseOrThrow( CommandResponse, await this.request( - `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}`, + `/v2/sandboxes/sessions/${params.sessionId}/cmd/${params.cmdId}`, { signal: params.signal }, ), ); } async mkDir(params: { - sandboxId: string; + sessionId: string; path: string; cwd?: string; signal?: AbortSignal; }) { return parseOrThrow( EmptyResponse, - await this.request(`/v1/sandboxes/${params.sandboxId}/fs/mkdir`, { + await this.request(`/v2/sandboxes/sessions/${params.sessionId}/fs/mkdir`, { method: "POST", body: JSON.stringify({ path: params.path, cwd: params.cwd }), signal: params.signal, @@ -352,14 +350,14 @@ export class APIClient extends BaseClient { } getFileWriter(params: { - sandboxId: string; + sessionId: string; extractDir: string; signal?: AbortSignal; }) { const writer = new FileWriter(); return { response: (async () => { - return this.request(`/v1/sandboxes/${params.sandboxId}/fs/write`, { + return this.request(`/v2/sandboxes/sessions/${params.sessionId}/fs/write`, { method: "POST", headers: { "content-type": "application/gzip", @@ -401,8 +399,8 @@ export class APIClient extends BaseClient { signal?: AbortSignal; }) { return parseOrThrow( - SandboxesResponse, - await this.request(`/v1/sandboxes`, { + SessionsResponse, + await this.request(`/v2/sandboxes/sessions`, { query: { project: params.projectId, name: params.name, @@ -451,7 +449,7 @@ export class APIClient extends BaseClient { }) { return parseOrThrow( SnapshotsResponse, - await this.request(`/v1/sandboxes/snapshots`, { + await this.request(`/v2/sandboxes/snapshots`, { query: { project: params.projectId, name: params.name, @@ -472,14 +470,14 @@ export class APIClient extends BaseClient { } async writeFiles(params: { - sandboxId: string; + sessionId: string; cwd: string; files: { path: string; content: Buffer }[]; extractDir: string; signal?: AbortSignal; }) { const { writer, response } = this.getFileWriter({ - sandboxId: params.sandboxId, + sessionId: params.sessionId, extractDir: params.extractDir, signal: params.signal, }); @@ -500,13 +498,13 @@ export class APIClient extends BaseClient { } async readFile(params: { - sandboxId: string; + sessionId: string; path: string; cwd?: string; signal?: AbortSignal; }): Promise { const response = await this.request( - `/v1/sandboxes/${params.sandboxId}/fs/read`, + `/v2/sandboxes/sessions/${params.sessionId}/fs/read`, { method: "POST", body: JSON.stringify({ path: params.path, cwd: params.cwd }), @@ -526,7 +524,7 @@ export class APIClient extends BaseClient { } async killCommand(params: { - sandboxId: string; + sessionId: string; commandId: string; signal: number; abortSignal?: AbortSignal; @@ -534,7 +532,7 @@ export class APIClient extends BaseClient { return parseOrThrow( CommandResponse, await this.request( - `/v1/sandboxes/${params.sandboxId}/${params.commandId}/kill`, + `/v2/sandboxes/sessions/${params.sessionId}/cmd/${params.commandId}/kill`, { method: "POST", body: JSON.stringify({ signal: params.signal }), @@ -545,7 +543,7 @@ export class APIClient extends BaseClient { } getLogs(params: { - sandboxId: string; + sessionId: string; cmdId: string; signal?: AbortSignal; }): AsyncGenerator< @@ -561,7 +559,7 @@ export class APIClient extends BaseClient { : mergeSignals(params.signal, disposer.signal); const generator = (async function* () { - const url = `/v1/sandboxes/${params.sandboxId}/cmd/${params.cmdId}/logs`; + const url = `/v2/sandboxes/sessions/${params.sessionId}/cmd/${params.cmdId}/logs`; const response = await self.request(url, { method: "GET", signal, @@ -574,14 +572,14 @@ export class APIClient extends BaseClient { if (response.headers.get("content-type") !== "application/x-ndjson") { throw new APIError(response, { message: "Expected a stream of logs", - sandboxId: params.sandboxId, + sessionId: params.sessionId, }); } if (response.body === null) { throw new APIError(response, { message: "No response body", - sandboxId: params.sandboxId, + sessionId: params.sessionId, }); } @@ -596,7 +594,7 @@ export class APIClient extends BaseClient { throw new StreamError( parsed.data.code, parsed.data.message, - params.sandboxId, + params.sessionId, ); } yield parsed; @@ -612,26 +610,26 @@ export class APIClient extends BaseClient { } async stopSandbox(params: { - sandboxId: string; + sessionId: string; signal?: AbortSignal; blocking?: boolean; - }): Promise>> { - const url = `/v1/sandboxes/${params.sandboxId}/stop`; + }): Promise>> { + const url = `/v2/sandboxes/sessions/${params.sessionId}/stop`; const response = await parseOrThrow( - SandboxResponse, + SessionResponse, await this.request(url, { method: "POST", signal: params.signal }), ); if (params.blocking) { - let sandbox = response.json.sandbox; - while (sandbox.status !== "stopped" && sandbox.status !== "failed" && sandbox.status !== "aborted") { + let session = response.json.session; + while (session.status !== "stopped" && session.status !== "failed" && session.status !== "aborted") { await setTimeout(500, undefined, { signal: params.signal }); - const poll = await this.getSandbox({ - sandboxId: params.sandboxId, + const poll = await this.getSession({ + sessionId: params.sessionId, signal: params.signal, }); - sandbox = poll.json.sandbox; - response.json.sandbox = sandbox; + session = poll.json.session; + response.json.session = session; } } @@ -639,13 +637,13 @@ export class APIClient extends BaseClient { } async updateNetworkPolicy(params: { - sandboxId: string; + sessionId: string; networkPolicy: NetworkPolicy; signal?: AbortSignal; - }): Promise>> { - const url = `/v1/sandboxes/${params.sandboxId}/network-policy`; + }): Promise>> { + const url = `/v2/sandboxes/sessions/${params.sessionId}/network-policy`; return parseOrThrow( - UpdateNetworkPolicyResponse, + SessionResponse, await this.request(url, { method: "POST", body: JSON.stringify(toAPINetworkPolicy(params.networkPolicy)), @@ -655,13 +653,13 @@ export class APIClient extends BaseClient { } async extendTimeout(params: { - sandboxId: string; + sessionId: string; duration: number; signal?: AbortSignal; - }): Promise>> { - const url = `/v1/sandboxes/${params.sandboxId}/extend-timeout`; + }): Promise>> { + const url = `/v2/sandboxes/sessions/${params.sessionId}/extend-timeout`; return parseOrThrow( - ExtendTimeoutResponse, + SessionResponse, await this.request(url, { method: "POST", body: JSON.stringify({ duration: params.duration }), @@ -671,11 +669,11 @@ export class APIClient extends BaseClient { } async createSnapshot(params: { - sandboxId: string; + sessionId: string; expiration?: number; signal?: AbortSignal; }): Promise>> { - const url = `/v1/sandboxes/${params.sandboxId}/snapshot`; + const url = `/v2/sandboxes/sessions/${params.sessionId}/snapshot`; const body = params.expiration === undefined ? undefined @@ -694,7 +692,7 @@ export class APIClient extends BaseClient { snapshotId: string; signal?: AbortSignal; }): Promise>> { - const url = `/v1/sandboxes/snapshots/${params.snapshotId}`; + const url = `/v2/sandboxes/snapshots/${params.snapshotId}`; return parseOrThrow( SnapshotResponse, await this.request(url, { method: "DELETE", signal: params.signal }), @@ -705,7 +703,7 @@ export class APIClient extends BaseClient { snapshotId: string; signal?: AbortSignal; }): Promise>> { - const url = `/v1/sandboxes/snapshots/${params.snapshotId}`; + const url = `/v2/sandboxes/snapshots/${params.snapshotId}`; return parseOrThrow( SnapshotResponse, await this.request(url, { signal: params.signal }), @@ -726,7 +724,7 @@ export class APIClient extends BaseClient { } return parseOrThrow( NamedSandboxAndSessionResponse, - await this.request(`/v1/sandboxes/named/${encodeURIComponent(params.name)}`, { + await this.request(`/v2/sandboxes/${encodeURIComponent(params.name)}`, { query, signal: params.signal, }), @@ -743,7 +741,7 @@ export class APIClient extends BaseClient { }) { const result = await parseOrThrow( NamedSandboxesPaginationResponse, - await this.request(`/v1/sandboxes/named`, { + await this.request(`/v2/sandboxes`, { query: { project: params.projectId, limit: params.limit, @@ -799,7 +797,6 @@ export class APIClient extends BaseClient { async deleteNamedSandbox(params: { name: string; projectId: string; - preserveSnapshots?: boolean; signal?: AbortSignal; }) { return parseOrThrow( @@ -808,10 +805,6 @@ export class APIClient extends BaseClient { method: "DELETE", query: { projectId: params.projectId, - preserveSandboxes: "false", - preserveSnapshots: params.preserveSnapshots !== undefined - ? String(params.preserveSnapshots) - : undefined, }, signal: params.signal, }), diff --git a/packages/vercel-sandbox/src/api-client/api-error.ts b/packages/vercel-sandbox/src/api-client/api-error.ts index 64c164a..e65490a 100644 --- a/packages/vercel-sandbox/src/api-client/api-error.ts +++ b/packages/vercel-sandbox/src/api-client/api-error.ts @@ -2,7 +2,8 @@ interface Options { message?: string; json?: ErrorData; text?: string; - sandboxId?: string; + sandboxName?: string; + sessionId?: string; } export class APIError extends Error { @@ -10,7 +11,8 @@ export class APIError extends Error { public message: string; public json?: ErrorData; public text?: string; - public sandboxId?: string; + public sandboxName?: string; + public sessionId?: string constructor(response: Response, options?: Options) { super(response.statusText); @@ -22,7 +24,8 @@ export class APIError extends Error { this.message = options?.message ?? ""; this.json = options?.json; this.text = options?.text; - this.sandboxId = options?.sandboxId; + this.sandboxName = options?.sandboxName; + this.sessionId = options?.sessionId; } } @@ -32,13 +35,13 @@ export class APIError extends Error { */ export class StreamError extends Error { public code: string; - public sandboxId: string; + public sessionId: string; - constructor(code: string, message: string, sandboxId: string) { + constructor(code: string, message: string, sessionId: string) { super(message); this.name = "StreamError"; this.code = code; - this.sandboxId = sandboxId; + this.sessionId = sessionId; if (Error.captureStackTrace) { Error.captureStackTrace(this, StreamError); } diff --git a/packages/vercel-sandbox/src/api-client/base-client.ts b/packages/vercel-sandbox/src/api-client/base-client.ts index 2d24d7f..9984ed8 100644 --- a/packages/vercel-sandbox/src/api-client/base-client.ts +++ b/packages/vercel-sandbox/src/api-client/base-client.ts @@ -88,11 +88,18 @@ export interface Parsed { } /** - * Extract sandboxId from a sandbox API URL. - * URLs follow the pattern: /v1/sandboxes/{sandboxId}/... + * Extract sessionId from a sandbox API URL. */ -function extractSandboxId(url: string): string | undefined { - const match = url.match(/\/v1\/sandboxes\/([^/?]+)/); +function extractSessionId(url: string): string | undefined { + const match = url.match(/\/v2\/sandboxes\/sessions\/([^/?]+)/); + return match?.[1]; +} + +/** + * Extract sandbox name from a sandbox API url. + */ +function extractSandboxName(url: string): string | undefined { + const match = url.match(/\/v2\/sandboxes\/([^/?]+)/); return match?.[1]; } @@ -107,12 +114,17 @@ export async function parse( validator: ZodType, response: Response, ): Promise | APIError> { - const sandboxId = extractSandboxId(response.url); + const sessionId = extractSessionId(response.url); + let sandboxName: string | undefined; + if (!sessionId) { + sandboxName = extractSandboxName(response.url); + } const text = await response.text().catch((err) => { return new APIError(response, { message: `Can't read response text: ${String(err)}`, - sandboxId, + sessionId, + sandboxName }); }); @@ -128,7 +140,8 @@ export async function parse( return new APIError(response, { message: `Can't parse JSON: ${String(error)}`, text, - sandboxId, + sessionId, + sandboxName }); } @@ -137,7 +150,8 @@ export async function parse( message: `Status code ${response.status} is not ok`, json: json as ErrorData, text, - sandboxId, + sessionId, + sandboxName }); } @@ -147,7 +161,8 @@ export async function parse( message: `Response JSON is not valid: ${validated.error}`, json: json as ErrorData, text, - sandboxId, + sessionId, + sandboxName }); } diff --git a/packages/vercel-sandbox/src/api-client/validators.ts b/packages/vercel-sandbox/src/api-client/validators.ts index 69e2390..7f0ecfe 100644 --- a/packages/vercel-sandbox/src/api-client/validators.ts +++ b/packages/vercel-sandbox/src/api-client/validators.ts @@ -72,7 +72,7 @@ export type SnapshotMetadata = z.infer; export const Snapshot = z.object({ id: z.string(), - sourceSandboxId: z.string(), + sourceSessionId: z.string(), region: z.string(), status: z.enum(["created", "deleted", "failed"]), sizeBytes: z.number(), @@ -106,7 +106,7 @@ export const Command = z.object({ name: z.string(), args: z.array(z.string()), cwd: z.string(), - sandboxId: z.string(), + sessionId: z.string(), exitCode: z.number().nullable(), startedAt: z.number(), }); @@ -116,13 +116,26 @@ const CommandFinished = Command.extend({ }); export const SandboxResponse = z.object({ - sandbox: Sandbox, + sandbox: Sandbox.passthrough(), }); export const SandboxAndRoutesResponse = SandboxResponse.extend({ routes: z.array(SandboxRoute), }); +export const SessionResponse = z.object({ + session: Sandbox.passthrough(), +}); + +export const SessionAndRoutesResponse = SessionResponse.extend({ + routes: z.array(SandboxRoute), +}); + +export const SessionsResponse = z.object({ + sessions: z.array(Sandbox.passthrough()), + pagination: Pagination, +}); + export const CommandResponse = z.object({ command: Command, }); @@ -167,17 +180,9 @@ export const SnapshotsResponse = z.object({ pagination: Pagination, }); -export const ExtendTimeoutResponse = z.object({ - sandbox: Sandbox, -}); - -export const UpdateNetworkPolicyResponse = z.object({ - sandbox: Sandbox, -}); - export const CreateSnapshotResponse = z.object({ snapshot: Snapshot, - sandbox: Sandbox, + session: Sandbox.passthrough(), }); export const SnapshotResponse = z.object({ diff --git a/packages/vercel-sandbox/src/command.ts b/packages/vercel-sandbox/src/command.ts index 0729d13..dcc2aee 100644 --- a/packages/vercel-sandbox/src/command.ts +++ b/packages/vercel-sandbox/src/command.ts @@ -22,9 +22,9 @@ export class Command { protected client: APIClient; /** - * ID of the sandbox this command is running in. + * ID of the session this command is running in. */ - private sandboxId: string; + private sessionId: string; /** * Data for the command execution. @@ -55,22 +55,22 @@ export class Command { } /** - * @param params - Object containing the client, sandbox ID, and command ID. + * @param params - Object containing the client, session ID, and command ID. * @param params.client - API client used to interact with the backend. - * @param params.sandboxId - The ID of the sandbox where the command is running. + * @param params.sessionId - The ID of the session where the command is running. * @param params.cmdId - The ID of the command execution. */ constructor({ client, - sandboxId, + sessionId, cmd, }: { client: APIClient; - sandboxId: string; + sessionId: string; cmd: CommandData; }) { this.client = client; - this.sandboxId = sandboxId; + this.sessionId = sessionId; this.cmd = cmd; this.exitCode = cmd.exitCode ?? null; } @@ -97,7 +97,7 @@ export class Command { */ logs(opts?: { signal?: AbortSignal }) { return this.client.getLogs({ - sandboxId: this.sandboxId, + sessionId: this.sessionId, cmdId: this.cmd.id, signal: opts?.signal, }); @@ -126,7 +126,7 @@ export class Command { params?.signal?.throwIfAborted(); const command = await this.client.getCommand({ - sandboxId: this.sandboxId, + sessionId: this.sessionId, cmdId: this.cmd.id, wait: true, signal: params?.signal, @@ -134,7 +134,7 @@ export class Command { return new CommandFinished({ client: this.client, - sandboxId: this.sandboxId, + sessionId: this.sessionId, cmd: command.json.command, exitCode: command.json.command.exitCode, }); @@ -232,7 +232,7 @@ export class Command { */ async kill(signal?: Signal, opts?: { abortSignal?: AbortSignal }) { await this.client.killCommand({ - sandboxId: this.sandboxId, + sessionId: this.sessionId, commandId: this.cmd.id, signal: resolveSignal(signal ?? "SIGTERM"), abortSignal: opts?.abortSignal, @@ -257,15 +257,15 @@ export class CommandFinished extends Command { public exitCode: number; /** - * @param params - Object containing client, sandbox ID, command ID, and exit code. + * @param params - Object containing client, session ID, command ID, and exit code. * @param params.client - API client used to interact with the backend. - * @param params.sandboxId - The ID of the sandbox where the command ran. + * @param params.sessionId - The ID of the session where the command ran. * @param params.cmdId - The ID of the command execution. * @param params.exitCode - The exit code of the completed command. */ constructor(params: { client: APIClient; - sandboxId: string; + sessionId: string; cmd: CommandData; exitCode: number; }) { diff --git a/packages/vercel-sandbox/src/sandbox.ts b/packages/vercel-sandbox/src/sandbox.ts index 988c4dd..930905c 100644 --- a/packages/vercel-sandbox/src/sandbox.ts +++ b/packages/vercel-sandbox/src/sandbox.ts @@ -458,16 +458,16 @@ export class Sandbox { while (status === "stopping" || status === "snapshotting") { await setTimeout(pollingInterval, undefined, { signal }); - const poll = await this.client.getSandbox({ - sandboxId: this.session.sessionId, + const poll = await this.client.getSession({ + sessionId: this.session.sessionId, signal, }); this.session = new Session({ client: this.client, routes: poll.json.routes, - session: poll.json.sandbox, + session: poll.json.session, }); - status = poll.json.sandbox.status; + status = poll.json.session.status; } await this.resume(signal); } @@ -818,11 +818,10 @@ export class Sandbox { * After deletion the instance becomes inert — all further API calls will * throw immediately. */ - async delete(opts?: { preserveSnapshots?: boolean; signal?: AbortSignal }): Promise { + async delete(opts?: { signal?: AbortSignal }): Promise { await this.client.deleteNamedSandbox({ name: this.namedSandbox.name, projectId: this.projectId, - preserveSnapshots: opts?.preserveSnapshots, signal: opts?.signal, }); } diff --git a/packages/vercel-sandbox/src/session.ts b/packages/vercel-sandbox/src/session.ts index 6e80cc0..40c93fb 100644 --- a/packages/vercel-sandbox/src/session.ts +++ b/packages/vercel-sandbox/src/session.ts @@ -256,14 +256,14 @@ export class Session { opts?: { signal?: AbortSignal }, ): Promise { const command = await this.client.getCommand({ - sandboxId: this.session.id, + sessionId: this.session.id, cmdId, signal: opts?.signal, }); return new Command({ client: this.client, - sandboxId: this.session.id, + sessionId: this.session.id, cmd: command.json.command, }); } @@ -343,7 +343,7 @@ export class Session { if (wait) { const commandStream = await this.client.runCommand({ - sandboxId: this.session.id, + sessionId: this.session.id, command: params.cmd, args: params.args ?? [], cwd: params.cwd, @@ -355,7 +355,7 @@ export class Session { const command = new Command({ client: this.client, - sandboxId: this.session.id, + sessionId: this.session.id, cmd: commandStream.command, }); @@ -365,14 +365,14 @@ export class Session { ]); return new CommandFinished({ client: this.client, - sandboxId: this.session.id, + sessionId: this.session.id, cmd: finished, exitCode: finished.exitCode ?? 0, }); } const commandResponse = await this.client.runCommand({ - sandboxId: this.session.id, + sessionId: this.session.id, command: params.cmd, args: params.args ?? [], cwd: params.cwd, @@ -383,7 +383,7 @@ export class Session { const command = new Command({ client: this.client, - sandboxId: this.session.id, + sessionId: this.session.id, cmd: commandResponse.json.command, }); @@ -406,7 +406,7 @@ export class Session { */ async mkDir(path: string, opts?: { signal?: AbortSignal }): Promise { await this.client.mkDir({ - sandboxId: this.session.id, + sessionId: this.session.id, path: path, signal: opts?.signal, }); @@ -425,7 +425,7 @@ export class Session { opts?: { signal?: AbortSignal }, ): Promise { return this.client.readFile({ - sandboxId: this.session.id, + sessionId: this.session.id, path: file.path, cwd: file.cwd, signal: opts?.signal, @@ -445,7 +445,7 @@ export class Session { opts?: { signal?: AbortSignal }, ): Promise { const stream = await this.client.readFile({ - sandboxId: this.session.id, + sessionId: this.session.id, path: file.path, cwd: file.cwd, signal: opts?.signal, @@ -482,7 +482,7 @@ export class Session { } const stream = await this.client.readFile({ - sandboxId: this.session.id, + sessionId: this.session.id, path: src.path, cwd: src.cwd, signal: opts?.signal, @@ -521,7 +521,7 @@ export class Session { opts?: { signal?: AbortSignal }, ) { return this.client.writeFiles({ - sandboxId: this.session.id, + sessionId: this.session.id, cwd: this.session.cwd, extractDir: "/", files: files, @@ -555,11 +555,11 @@ export class Session { */ async stop(opts?: { signal?: AbortSignal; blocking?: boolean }): Promise { const response = await this.client.stopSandbox({ - sandboxId: this.session.id, + sessionId: this.session.id, signal: opts?.signal, blocking: opts?.blocking, }); - this.session = convertSandbox(response.json.sandbox); + this.session = convertSandbox(response.json.session); return this.session; } @@ -606,13 +606,13 @@ export class Session { ): Promise { if (params.networkPolicy !== undefined) { const response = await this.client.updateNetworkPolicy({ - sandboxId: this.session.id, + sessionId: this.session.id, networkPolicy: params.networkPolicy, signal: opts?.signal, }); // Update the internal session metadata with the new network policy - this.session = convertSandbox(response.json.sandbox); + this.session = convertSandbox(response.json.session); } } @@ -638,13 +638,13 @@ export class Session { opts?: { signal?: AbortSignal }, ): Promise { const response = await this.client.extendTimeout({ - sandboxId: this.session.id, + sessionId: this.session.id, duration, signal: opts?.signal, }); // Update the internal session metadata with the new timeout value - this.session = convertSandbox(response.json.sandbox); + this.session = convertSandbox(response.json.session); } /** @@ -663,12 +663,12 @@ export class Session { signal?: AbortSignal; }): Promise { const response = await this.client.createSnapshot({ - sandboxId: this.session.id, + sessionId: this.session.id, expiration: opts?.expiration, signal: opts?.signal, }); - this.session = convertSandbox(response.json.sandbox); + this.session = convertSandbox(response.json.session); return new Snapshot({ client: this.client, diff --git a/packages/vercel-sandbox/src/snapshot.ts b/packages/vercel-sandbox/src/snapshot.ts index 3ae1f66..44e33f7 100644 --- a/packages/vercel-sandbox/src/snapshot.ts +++ b/packages/vercel-sandbox/src/snapshot.ts @@ -32,10 +32,10 @@ export class Snapshot { } /** - * The ID the sandbox from which this snapshot was created. + * The ID of the session from which this snapshot was created. */ - public get sourceSandboxId(): string { - return this.snapshot.sourceSandboxId; + public get sourceSessionId(): string { + return this.snapshot.sourceSessionId; } /** From 1ec1e54064b80eb0e03499f03c99ac9d93d37a62 Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Thu, 12 Mar 2026 21:17:23 +0100 Subject: [PATCH 2/7] rename NamedSandbox --- packages/sandbox/src/commands/config.ts | 4 +- packages/sandbox/src/commands/list.ts | 8 +- .../extend-sandbox-timeout.ts | 22 +++-- .../src/api-client/api-client.test.ts | 88 ++++++++--------- .../src/api-client/api-client.ts | 48 ++++----- .../src/api-client/validators.ts | 54 ++++------ packages/vercel-sandbox/src/sandbox.test.ts | 8 +- packages/vercel-sandbox/src/sandbox.ts | 98 +++++++++---------- packages/vercel-sandbox/src/session.ts | 24 ++--- .../src/utils/convert-sandbox.ts | 8 +- 10 files changed, 175 insertions(+), 187 deletions(-) diff --git a/packages/sandbox/src/commands/config.ts b/packages/sandbox/src/commands/config.ts index 4946822..c77c8ee 100644 --- a/packages/sandbox/src/commands/config.ts +++ b/packages/sandbox/src/commands/config.ts @@ -177,8 +177,8 @@ const listCommand = cmd.command({ const networkPolicy = typeof sandbox.networkPolicy === "string" ? sandbox.networkPolicy : "restricted"; const rows = [ - { field: "vCPUs", value: String(sandbox.vcpus) }, - { field: "Timeout", value: ms(sandbox.timeout, { long: true }) }, + { field: "vCPUs", value: String(sandbox.vcpus ?? "-") }, + { field: "Timeout", value: sandbox.timeout != null ? ms(sandbox.timeout, { long: true }) : "-" }, { field: "Persistent", value: String(sandbox.persistent) }, { field: "Network policy", value: String(networkPolicy) }, ]; diff --git a/packages/sandbox/src/commands/list.ts b/packages/sandbox/src/commands/list.ts index dbf326f..97ee8ec 100644 --- a/packages/sandbox/src/commands/list.ts +++ b/packages/sandbox/src/commands/list.ts @@ -73,11 +73,11 @@ export const list = cmd.command({ CREATED: { value: (s) => timeAgo(s.createdAt), }, - MEMORY: { value: (s) => memoryFormatter.format(s.memory) }, - VCPUS: { value: (s) => s.vcpus }, - RUNTIME: { value: (s) => s.runtime }, + MEMORY: { value: (s) => s.memory != null ? memoryFormatter.format(s.memory) : "-" }, + VCPUS: { value: (s) => s.vcpus ?? "-" }, + RUNTIME: { value: (s) => s.runtime ?? "-" }, TIMEOUT: { - value: (s) => timeAgo(s.createdAt + s.timeout), + value: (s) => s.timeout != null ? timeAgo(s.createdAt + s.timeout) : "-", }, SNAPSHOT: { value: (s) => s.currentSnapshotId ?? "-" } }; diff --git a/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts b/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts index 5e96a9c..8f4a238 100644 --- a/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts +++ b/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts @@ -11,18 +11,26 @@ export async function extendSandboxTimeoutPeriodically( sandbox: Sandbox, signal: AbortSignal, ) { - const nextTick = sandbox.createdAt.getTime() + sandbox.timeout; + const timeout = sandbox.timeout; + if (timeout == null) return; + + const nextTick = sandbox.createdAt.getTime() + timeout; debug(`next tick: ${new Date(nextTick).toISOString()}`); while (!signal.aborted) { - const timeout = - sandbox.createdAt.getTime() + sandbox.timeout - Date.now() - BUFFER; - if (timeout > 2000) { - debug(`sleeping for ${timeout}ms until next timeout extension`); - await setTimeout(timeout, null, { signal }); + const currentTimeout = sandbox.timeout; + if (currentTimeout == null) return; + + const sleepMs = + sandbox.createdAt.getTime() + currentTimeout - Date.now() - BUFFER; + if (sleepMs > 2000) { + debug(`sleeping for ${sleepMs}ms until next timeout extension`); + await setTimeout(sleepMs, null, { signal }); } await sandbox.extendTimeout(ms("5 minutes")); - const nextTick = sandbox.createdAt.getTime() + sandbox.timeout; + const updatedTimeout = sandbox.timeout; + if (updatedTimeout == null) return; + const nextTick = sandbox.createdAt.getTime() + updatedTimeout; debug( `extended sandbox timeout by 5 minutes. next tick: ${new Date(nextTick).toISOString()}`, ); diff --git a/packages/vercel-sandbox/src/api-client/api-client.test.ts b/packages/vercel-sandbox/src/api-client/api-client.test.ts index 548a718..064216d 100644 --- a/packages/vercel-sandbox/src/api-client/api-client.test.ts +++ b/packages/vercel-sandbox/src/api-client/api-client.test.ts @@ -280,11 +280,11 @@ describe("APIClient", () => { }); }); - describe("stopSandbox", () => { + describe("stopSession", () => { let client: APIClient; let mockFetch: ReturnType; - const makeSandbox = (status: string) => ({ + const makeSession = (status: string) => ({ id: "sbx_123", memory: 2048, vcpus: 1, @@ -314,12 +314,12 @@ describe("APIClient", () => { it("returns immediately when blocking is not set", async () => { mockFetch.mockResolvedValue( - new Response(JSON.stringify({ sandbox: makeSandbox("stopping") }), { + new Response(JSON.stringify({ session: makeSession("stopping") }), { headers: { "content-type": "application/json" }, }), ); - const result = await client.stopSandbox({ sessionId: "sbx_123" }); + const result = await client.stopSession({ sessionId: "sbx_123" }); expect(result.json.session.status).toBe("stopping"); expect(mockFetch).toHaveBeenCalledTimes(1); @@ -328,24 +328,24 @@ describe("APIClient", () => { it("polls until stopped when blocking is true", async () => { mockFetch .mockResolvedValueOnce( - new Response(JSON.stringify({ sandbox: makeSandbox("stopping") }), { + new Response(JSON.stringify({ session: makeSession("stopping") }), { headers: { "content-type": "application/json" }, }), ) .mockResolvedValueOnce( new Response( - JSON.stringify({ sandbox: makeSandbox("stopping"), routes: [] }), + JSON.stringify({ session: makeSession("stopping"), routes: [] }), { headers: { "content-type": "application/json" } }, ), ) .mockResolvedValueOnce( new Response( - JSON.stringify({ sandbox: makeSandbox("stopped"), routes: [] }), + JSON.stringify({ session: makeSession("stopped"), routes: [] }), { headers: { "content-type": "application/json" } }, ), ); - const promise = client.stopSandbox({ + const promise = client.stopSession({ sessionId: "sbx_123", blocking: true, }); @@ -362,18 +362,18 @@ describe("APIClient", () => { it("stops polling on failed status", async () => { mockFetch .mockResolvedValueOnce( - new Response(JSON.stringify({ sandbox: makeSandbox("stopping") }), { + new Response(JSON.stringify({ session: makeSession("stopping") }), { headers: { "content-type": "application/json" }, }), ) .mockResolvedValueOnce( new Response( - JSON.stringify({ sandbox: makeSandbox("failed"), routes: [] }), + JSON.stringify({ session: makeSession("failed"), routes: [] }), { headers: { "content-type": "application/json" } }, ), ); - const promise = client.stopSandbox({ + const promise = client.stopSession({ sessionId: "sbx_123", blocking: true, }); @@ -388,18 +388,18 @@ describe("APIClient", () => { it("stops polling on aborted status", async () => { mockFetch .mockResolvedValueOnce( - new Response(JSON.stringify({ sandbox: makeSandbox("stopping") }), { + new Response(JSON.stringify({ session: makeSession("stopping") }), { headers: { "content-type": "application/json" }, }), ) .mockResolvedValueOnce( new Response( - JSON.stringify({ sandbox: makeSandbox("aborted"), routes: [] }), + JSON.stringify({ session: makeSession("aborted"), routes: [] }), { headers: { "content-type": "application/json" } }, ), ); - const promise = client.stopSandbox({ + const promise = client.stopSession({ sessionId: "sbx_123", blocking: true, }); @@ -412,11 +412,11 @@ describe("APIClient", () => { }); }); - describe("getNamedSandbox", () => { + describe("getSandbox", () => { let client: APIClient; let mockFetch: ReturnType; - const makeNamedSandbox = () => ({ + const makeSandboxMetadata = () => ({ name: "my-sandbox", persistent: true, region: "iad1", @@ -430,7 +430,7 @@ describe("APIClient", () => { currentSessionId: "sbx_123", }); - const makeSandbox = () => ({ + const makeSession = () => ({ id: "sbx_123", memory: 2048, vcpus: 1, @@ -453,10 +453,10 @@ describe("APIClient", () => { }); }); - it("fetches a named sandbox by name and projectId", async () => { + it("fetches a sandbox by name and projectId", async () => { const body = { - namedSandbox: makeNamedSandbox(), - sandbox: makeSandbox(), + sandbox: makeSandboxMetadata(), + session: makeSession(), routes: [{ url: "https://example.com", subdomain: "sbx", port: 3000 }], }; mockFetch.mockResolvedValue( @@ -465,23 +465,23 @@ describe("APIClient", () => { }), ); - const result = await client.getNamedSandbox({ + const result = await client.getSandbox({ name: "my-sandbox", projectId: "proj_123", }); - expect(result.json.namedSandbox.name).toBe("my-sandbox"); - expect(result.json.sandbox.id).toBe("sbx_123"); + expect(result.json.sandbox.name).toBe("my-sandbox"); + expect(result.json.session.id).toBe("sbx_123"); const [url] = mockFetch.mock.calls[0]; - expect(url).toContain("/v1/sandboxes/named/my-sandbox"); + expect(url).toContain("/v2/sandboxes/my-sandbox"); expect(url).toContain("projectId=proj_123"); }); it("passes resume query param when provided", async () => { const body = { - namedSandbox: makeNamedSandbox(), - sandbox: makeSandbox(), + sandbox: makeSandboxMetadata(), + session: makeSession(), routes: [], }; mockFetch.mockResolvedValue( @@ -490,7 +490,7 @@ describe("APIClient", () => { }), ); - await client.getNamedSandbox({ + await client.getSandbox({ name: "my-sandbox", projectId: "proj_123", resume: true, @@ -501,11 +501,11 @@ describe("APIClient", () => { }); }); - describe("listNamedSandboxes", () => { + describe("listSandboxes", () => { let client: APIClient; let mockFetch: ReturnType; - const makeNamedSandbox = (name: string) => ({ + const makeSandboxMetadata = (name: string) => ({ name, persistent: false, region: "iad1", @@ -528,9 +528,9 @@ describe("APIClient", () => { }); }); - it("lists named sandboxes with pagination", async () => { + it("lists sandboxes with pagination", async () => { const body = { - namedSandboxes: [makeNamedSandbox("sb-1"), makeNamedSandbox("sb-2")], + sandboxes: [makeSandboxMetadata("sb-1"), makeSandboxMetadata("sb-2")], pagination: { count: 2, next: null, total: 2 }, }; mockFetch.mockResolvedValue( @@ -539,7 +539,7 @@ describe("APIClient", () => { }), ); - const result = await client.listNamedSandboxes({ + const result = await client.listSandboxes({ projectId: "proj_123", }); @@ -550,7 +550,7 @@ describe("APIClient", () => { it("passes all query params", async () => { const body = { - namedSandboxes: [], + sandboxes: [], pagination: { count: 0, next: null, total: 0 }, }; mockFetch.mockResolvedValue( @@ -559,7 +559,7 @@ describe("APIClient", () => { }), ); - await client.listNamedSandboxes({ + await client.listSandboxes({ projectId: "proj_123", limit: 5, sortBy: "name", @@ -576,11 +576,11 @@ describe("APIClient", () => { }); }); - describe("updateNamedSandbox", () => { + describe("updateSandbox", () => { let client: APIClient; let mockFetch: ReturnType; - const makeNamedSandbox = () => ({ + const makeSandboxMetadata = () => ({ name: "my-sandbox", persistent: true, region: "iad1", @@ -604,21 +604,21 @@ describe("APIClient", () => { }); it("sends PATCH with update fields", async () => { - const body = { namedSandbox: makeNamedSandbox() }; + const body = { sandbox: makeSandboxMetadata() }; mockFetch.mockResolvedValue( new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, }), ); - const result = await client.updateNamedSandbox({ + const result = await client.updateSandbox({ name: "my-sandbox", projectId: "proj_123", persistent: true, timeout: 600000, }); - expect(result.json.namedSandbox.name).toBe("my-sandbox"); + expect(result.json.sandbox.name).toBe("my-sandbox"); const [url, opts] = mockFetch.mock.calls[0]; expect(url).toContain("/v2/sandboxes/my-sandbox"); @@ -631,11 +631,11 @@ describe("APIClient", () => { }); }); - describe("deleteNamedSandbox", () => { + describe("deleteSandbox", () => { let client: APIClient; let mockFetch: ReturnType; - const makeNamedSandbox = () => ({ + const makeSandboxMetadata = () => ({ name: "my-sandbox", persistent: false, region: "iad1", @@ -659,19 +659,19 @@ describe("APIClient", () => { }); it("sends DELETE with projectId and preserveSandboxes=false", async () => { - const body = { namedSandbox: makeNamedSandbox() }; + const body = { sandbox: makeSandboxMetadata() }; mockFetch.mockResolvedValue( new Response(JSON.stringify(body), { headers: { "content-type": "application/json" }, }), ); - const result = await client.deleteNamedSandbox({ + const result = await client.deleteSandbox({ name: "my-sandbox", projectId: "proj_123", }); - expect(result.json.namedSandbox.name).toBe("my-sandbox"); + expect(result.json.sandbox.name).toBe("my-sandbox"); const [url, opts] = mockFetch.mock.calls[0]; expect(url).toContain("/v2/sandboxes/my-sandbox"); diff --git a/packages/vercel-sandbox/src/api-client/api-client.ts b/packages/vercel-sandbox/src/api-client/api-client.ts index d9e4fc3..1bc17ee 100644 --- a/packages/vercel-sandbox/src/api-client/api-client.ts +++ b/packages/vercel-sandbox/src/api-client/api-client.ts @@ -18,9 +18,9 @@ type CommandFinishedData, SnapshotsResponse, SnapshotResponse, CreateSnapshotResponse, - NamedSandboxAndSessionResponse, - NamedSandboxesPaginationResponse, - UpdateNamedSandboxResponse, + SandboxAndSessionResponse, + SandboxesPaginationResponse, + UpdateSandboxResponse, type CommandData, } from "./validators"; import { APIError, StreamError } from "./api-error"; @@ -176,7 +176,7 @@ export class APIClient extends BaseClient { ) { const privateParams = getPrivateParams(params); return parseOrThrow( - NamedSandboxAndSessionResponse, + SandboxAndSessionResponse, await this.request("/v2/sandboxes", { method: "POST", body: JSON.stringify({ @@ -371,28 +371,28 @@ export class APIClient extends BaseClient { }; } - async listSandboxes(params: { + async listSessions(params: { /** - * The ID or name of the project to which the sandboxes belong. + * The ID or name of the project to which the sessions belong. * @example "my-project" */ projectId: string; /** - * Filter sandboxes by named sandbox name. + * Filter sessions by sandbox name. */ name?: string; /** - * Maximum number of sandboxes to list from a request. + * Maximum number of sessions to list from a request. * @example 10 */ limit?: number; /** - * Get sandboxes created after this JavaScript timestamp. + * Get sessions created after this JavaScript timestamp. * @example 1540095775941 */ since?: number | Date; /** - * Get sandboxes created before this JavaScript timestamp. + * Get sessions created before this JavaScript timestamp. * @example 1540095775951 */ until?: number | Date; @@ -609,7 +609,7 @@ export class APIClient extends BaseClient { }); } - async stopSandbox(params: { + async stopSession(params: { sessionId: string; signal?: AbortSignal; blocking?: boolean; @@ -710,7 +710,7 @@ export class APIClient extends BaseClient { ); } - async getNamedSandbox(params: { + async getSandbox(params: { name: string; projectId: string; resume?: boolean; @@ -723,7 +723,7 @@ export class APIClient extends BaseClient { query.resume = String(params.resume); } return parseOrThrow( - NamedSandboxAndSessionResponse, + SandboxAndSessionResponse, await this.request(`/v2/sandboxes/${encodeURIComponent(params.name)}`, { query, signal: params.signal, @@ -731,7 +731,7 @@ export class APIClient extends BaseClient { ); } - async listNamedSandboxes(params: { + async listSandboxes(params: { projectId: string; limit?: number; sortBy?: "createdAt" | "name"; @@ -739,8 +739,8 @@ export class APIClient extends BaseClient { cursor?: string; signal?: AbortSignal; }) { - const result = await parseOrThrow( - NamedSandboxesPaginationResponse, + return parseOrThrow( + SandboxesPaginationResponse, await this.request(`/v2/sandboxes`, { query: { project: params.projectId, @@ -753,17 +753,9 @@ export class APIClient extends BaseClient { signal: params.signal, }), ); - - return { - ...result, - json: { - sandboxes: result.json.namedSandboxes, - pagination: result.json.pagination, - }, - }; } - async updateNamedSandbox(params: { + async updateSandbox(params: { name: string; projectId: string; persistent?: boolean; @@ -774,7 +766,7 @@ export class APIClient extends BaseClient { signal?: AbortSignal; }) { return parseOrThrow( - UpdateNamedSandboxResponse, + UpdateSandboxResponse, await this.request(`/v2/sandboxes/${encodeURIComponent(params.name)}`, { method: "PATCH", query: { @@ -794,13 +786,13 @@ export class APIClient extends BaseClient { ); } - async deleteNamedSandbox(params: { + async deleteSandbox(params: { name: string; projectId: string; signal?: AbortSignal; }) { return parseOrThrow( - UpdateNamedSandboxResponse, + UpdateSandboxResponse, await this.request(`/v2/sandboxes/${encodeURIComponent(params.name)}`, { method: "DELETE", query: { diff --git a/packages/vercel-sandbox/src/api-client/validators.ts b/packages/vercel-sandbox/src/api-client/validators.ts index 7f0ecfe..452effb 100644 --- a/packages/vercel-sandbox/src/api-client/validators.ts +++ b/packages/vercel-sandbox/src/api-client/validators.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -export type SandboxMetaData = z.infer; +export type SessionMetaData = z.infer; export const InjectionRuleValidator = z.object({ domain: z.string(), @@ -24,7 +24,7 @@ export const NetworkPolicyValidator = z.union([ .passthrough(), ]); -export const Sandbox = z.object({ +export const Session = z.object({ id: z.string(), memory: z.number(), vcpus: z.number(), @@ -115,16 +115,8 @@ const CommandFinished = Command.extend({ exitCode: z.number(), }); -export const SandboxResponse = z.object({ - sandbox: Sandbox.passthrough(), -}); - -export const SandboxAndRoutesResponse = SandboxResponse.extend({ - routes: z.array(SandboxRoute), -}); - export const SessionResponse = z.object({ - session: Sandbox.passthrough(), + session: Session.passthrough(), }); export const SessionAndRoutesResponse = SessionResponse.extend({ @@ -132,7 +124,7 @@ export const SessionAndRoutesResponse = SessionResponse.extend({ }); export const SessionsResponse = z.object({ - sessions: z.array(Sandbox.passthrough()), + sessions: z.array(Session.passthrough()), pagination: Pagination, }); @@ -170,11 +162,6 @@ export const LogLine = z.discriminatedUnion("stream", [ LogError, ]); -export const SandboxesResponse = z.object({ - sandboxes: z.array(Sandbox), - pagination: Pagination, -}); - export const SnapshotsResponse = z.object({ snapshots: z.array(Snapshot), pagination: Pagination, @@ -182,21 +169,21 @@ export const SnapshotsResponse = z.object({ export const CreateSnapshotResponse = z.object({ snapshot: Snapshot, - session: Sandbox.passthrough(), + session: Session.passthrough(), }); export const SnapshotResponse = z.object({ snapshot: Snapshot, }); -export const NamedSandbox = z.object({ +export const Sandbox = z.object({ name: z.string(), persistent: z.boolean(), - region: z.string(), - vcpus: z.number(), - memory: z.number(), - runtime: z.string(), - timeout: z.number(), + region: z.string().optional(), + vcpus: z.number().optional(), + memory: z.number().optional(), + runtime: z.string().optional(), + timeout: z.number().optional(), networkPolicy: NetworkPolicyValidator.optional(), totalEgressBytes: z.number().optional(), totalIngressBytes: z.number().optional(), @@ -204,16 +191,17 @@ export const NamedSandbox = z.object({ totalDurationMs: z.number().optional(), createdAt: z.number(), updatedAt: z.number(), - currentSandboxId: z.string(), + currentSessionId: z.string(), currentSnapshotId: z.string().optional(), - status: Sandbox.shape.status, + status: Session.shape.status, + cwd: z.string().optional(), }); -export type NamedSandboxMetaData = z.infer; +export type SandboxMetaData = z.infer; -export const NamedSandboxAndSessionResponse = z.object({ - namedSandbox: NamedSandbox, +export const SandboxAndSessionResponse = z.object({ sandbox: Sandbox, + session: Session.passthrough(), routes: z.array(SandboxRoute), }); @@ -223,11 +211,11 @@ export const CursorPagination = z.object({ total: z.number(), }); -export const NamedSandboxesPaginationResponse = z.object({ - namedSandboxes: z.array(NamedSandbox), +export const SandboxesPaginationResponse = z.object({ + sandboxes: z.array(Sandbox), pagination: CursorPagination, }); -export const UpdateNamedSandboxResponse = z.object({ - namedSandbox: NamedSandbox, +export const UpdateSandboxResponse = z.object({ + sandbox: Sandbox, }); diff --git a/packages/vercel-sandbox/src/sandbox.test.ts b/packages/vercel-sandbox/src/sandbox.test.ts index 4f83238..fb6e735 100644 --- a/packages/vercel-sandbox/src/sandbox.test.ts +++ b/packages/vercel-sandbox/src/sandbox.test.ts @@ -15,7 +15,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - namedSandbox: { name: "test" } as any, + metadata: { name: "test" } as any, projectId: "test-project", }); await expect( @@ -28,7 +28,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - namedSandbox: { name: "test" } as any, + metadata: { name: "test" } as any, projectId: "test-project", }); await expect( @@ -41,7 +41,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - namedSandbox: { name: "test" } as any, + metadata: { name: "test" } as any, projectId: "test-project", }); await expect( @@ -54,7 +54,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - namedSandbox: { name: "test" } as any, + metadata: { name: "test" } as any, projectId: "test-project", }); await expect( diff --git a/packages/vercel-sandbox/src/sandbox.ts b/packages/vercel-sandbox/src/sandbox.ts index 930905c..3795483 100644 --- a/packages/vercel-sandbox/src/sandbox.ts +++ b/packages/vercel-sandbox/src/sandbox.ts @@ -1,4 +1,4 @@ -import type { SandboxMetaData, SandboxRouteData, NamedSandboxMetaData } from "./api-client"; +import type { SessionMetaData, SandboxRouteData, SandboxMetaData } from "./api-client"; import { APIClient } from "./api-client"; import { APIError } from "./api-client/api-error"; import { type Credentials, getCredentials } from "./utils/get-credentials"; @@ -8,7 +8,7 @@ import type { RUNTIMES } from "./constants"; import { Session, type RunCommandParams } from "./session"; import type { Command, CommandFinished } from "./command"; import type { Snapshot } from "./snapshot"; -import type { ConvertedSandbox } from "./utils/convert-sandbox"; +import type { ConvertedSession } from "./utils/convert-sandbox"; import type { NetworkPolicy, } from "./network-policy"; @@ -149,13 +149,13 @@ export class Sandbox { /** * Internal metadata about the named sandbox. */ - private namedSandbox: NamedSandboxMetaData; + private metadata: SandboxMetaData; /** * The name of this sandbox. */ public get name(): string { - return this.namedSandbox.name; + return this.metadata.name; } /** @@ -170,75 +170,75 @@ export class Sandbox { * Whether the sandbox persists the state. */ public get persistent(): boolean { - return this.namedSandbox.persistent; + return this.metadata.persistent; } /** * The region this sandbox runs in. */ - public get region(): string { - return this.namedSandbox.region; + public get region(): string | undefined { + return this.metadata.region; } /** * Number of virtual CPUs allocated. */ - public get vcpus(): number { - return this.namedSandbox.vcpus; + public get vcpus(): number | undefined { + return this.metadata.vcpus; } /** * Memory allocated in MB. */ - public get memory(): number { - return this.namedSandbox.memory; + public get memory(): number | undefined { + return this.metadata.memory; } /** Runtime identifier (e.g. "node24", "python3.13"). */ - public get runtime(): string { - return this.namedSandbox.runtime; + public get runtime(): string | undefined { + return this.metadata.runtime; } /** * Cumulative egress bytes across all sessions. */ public get totalEgressBytes(): number | undefined { - return this.namedSandbox.totalEgressBytes; + return this.metadata.totalEgressBytes; } /** * Cumulative ingress bytes across all sessions. */ public get totalIngressBytes(): number | undefined { - return this.namedSandbox.totalIngressBytes; + return this.metadata.totalIngressBytes; } /** * Cumulative active CPU duration in milliseconds across all sessions. */ public get totalActiveCpuDurationMs(): number | undefined { - return this.namedSandbox.totalActiveCpuDurationMs; + return this.metadata.totalActiveCpuDurationMs; } /** * Cumulative wall-clock duration in milliseconds across all sessions. */ public get totalDurationMs(): number | undefined { - return this.namedSandbox.totalDurationMs; + return this.metadata.totalDurationMs; } /** * When this sandbox was last updated. */ public get updatedAt(): Date { - return new Date(this.namedSandbox.updatedAt); + return new Date(this.metadata.updatedAt); } /** * When this sandbox was created. */ public get createdAt(): Date { - return new Date(this.namedSandbox.createdAt); + return new Date(this.metadata.createdAt); } /** @@ -251,23 +251,23 @@ export class Sandbox { /** * The status of the current session. */ - public get status(): SandboxMetaData["status"] { + public get status(): SessionMetaData["status"] { return this.session.status; } /** * The default timeout of this sandbox in milliseconds. */ - public get timeout(): number { - return this.namedSandbox.timeout; + public get timeout(): number | undefined { + return this.metadata.timeout; } /** * The default network policy of this sandbox. */ public get networkPolicy(): NetworkPolicy | undefined { - return this.namedSandbox.networkPolicy - ? fromAPINetworkPolicy(this.namedSandbox.networkPolicy) + return this.metadata.networkPolicy + ? fromAPINetworkPolicy(this.metadata.networkPolicy) : undefined; } @@ -282,7 +282,7 @@ export class Sandbox { * The current snapshot ID of this sandbox, if any. */ public get currentSnapshotId(): string | undefined { - return this.namedSandbox.currentSnapshotId; + return this.metadata.currentSnapshotId; } /** @@ -305,7 +305,7 @@ export class Sandbox { * the next page of results. */ static async list( - params?: Partial[0]> & + params?: Partial[0]> & Partial & WithFetchOptions, ) { @@ -315,7 +315,7 @@ export class Sandbox { token: credentials.token, fetch: params?.fetch, }); - return client.listNamedSandboxes({ + return client.listSandboxes({ ...credentials, ...params, }); @@ -364,8 +364,8 @@ export class Sandbox { return new DisposableSandbox({ client, - session: response.json.sandbox, - namedSandbox: response.json.namedSandbox, + session: response.json.session, + metadata: response.json.sandbox, routes: response.json.routes, projectId: credentials.projectId, }); @@ -388,7 +388,7 @@ export class Sandbox { fetch: params.fetch, }); - const response = await client.getNamedSandbox({ + const response = await client.getSandbox({ name: params.name, projectId: credentials.projectId, resume: params.resume, @@ -397,8 +397,8 @@ export class Sandbox { return new Sandbox({ client, - session: response.json.sandbox, - namedSandbox: response.json.namedSandbox, + session: response.json.session, + metadata: response.json.sandbox, routes: response.json.routes, projectId: credentials.projectId, }); @@ -408,18 +408,18 @@ export class Sandbox { client, routes, session, - namedSandbox, + metadata, projectId, }: { client: APIClient; routes: SandboxRouteData[]; - session: SandboxMetaData; - namedSandbox: NamedSandboxMetaData; + session: SessionMetaData; + metadata: SandboxMetaData; projectId: string; }) { this.client = client; this.session = new Session({ client, routes, session }); - this.namedSandbox = namedSandbox; + this.metadata = metadata; this.projectId = projectId; } @@ -433,11 +433,11 @@ export class Sandbox { } /** - * Resume this sandbox by creating a new session via `getNamedSandbox`. + * Resume this sandbox by creating a new session via `getSandbox`. */ private async resume(signal?: AbortSignal): Promise { - const response = await this.client.getNamedSandbox({ - name: this.namedSandbox.name, + const response = await this.client.getSandbox({ + name: this.metadata.name, projectId: this.projectId, resume: true, signal, @@ -445,7 +445,7 @@ export class Sandbox { this.session = new Session({ client: this.client, routes: response.json.routes, - session: response.json.sandbox, + session: response.json.session, }); } @@ -669,7 +669,7 @@ export class Sandbox { * @param opts.blocking - If true, poll until the sandbox has fully stopped and return the final state. * @returns The sandbox metadata at the time the stop was acknowledged, or after fully stopped if `blocking` is true. */ - async stop(opts?: { signal?: AbortSignal; blocking?: boolean }): Promise { + async stop(opts?: { signal?: AbortSignal; blocking?: boolean }): Promise { return this.session.stop(opts); } @@ -788,8 +788,8 @@ export class Sandbox { } // Update the sandbox config. This config will be used on the next session. - const response = await this.client.updateNamedSandbox({ - name: this.namedSandbox.name, + const response = await this.client.updateSandbox({ + name: this.metadata.name, projectId: this.projectId, persistent: params.persistent, resources, @@ -797,7 +797,7 @@ export class Sandbox { networkPolicy: params.networkPolicy, signal: opts?.signal, }); - this.namedSandbox = response.json.namedSandbox; + this.metadata = response.json.sandbox; // Update the current session config. This only applies to network policy. if (params.networkPolicy) { @@ -819,8 +819,8 @@ export class Sandbox { * throw immediately. */ async delete(opts?: { signal?: AbortSignal }): Promise { - await this.client.deleteNamedSandbox({ - name: this.namedSandbox.name, + await this.client.deleteSandbox({ + name: this.metadata.name, projectId: this.projectId, signal: opts?.signal, }); @@ -839,9 +839,9 @@ export class Sandbox { signal?: AbortSignal; }) { - return this.client.listSandboxes({ + return this.client.listSessions({ projectId: this.projectId, - name: this.namedSandbox.name, + name: this.metadata.name, limit: params?.limit, since: params?.since, until: params?.until, @@ -864,7 +864,7 @@ export class Sandbox { return this.client.listSnapshots({ projectId: this.projectId, - name: this.namedSandbox.name, + name: this.metadata.name, limit: params?.limit, since: params?.since, until: params?.until, diff --git a/packages/vercel-sandbox/src/session.ts b/packages/vercel-sandbox/src/session.ts index 40c93fb..edc16f9 100644 --- a/packages/vercel-sandbox/src/session.ts +++ b/packages/vercel-sandbox/src/session.ts @@ -1,4 +1,4 @@ -import type { SandboxMetaData, SandboxRouteData } from "./api-client"; +import type { SessionMetaData, SandboxRouteData } from "./api-client"; import type { Writable } from "stream"; import { pipeline } from "stream/promises"; import { createWriteStream } from "fs"; @@ -13,7 +13,7 @@ import type { NetworkPolicyRule, NetworkTransformer, } from "./network-policy"; -import { convertSandbox, type ConvertedSandbox } from "./utils/convert-sandbox"; +import { convertSession, type ConvertedSession } from "./utils/convert-sandbox"; export type { NetworkPolicy, NetworkPolicyRule, NetworkTransformer }; @@ -74,7 +74,7 @@ export class Session { /** * Internal metadata about the current session. */ - private session: ConvertedSandbox; + private session: ConvertedSession; /** * Unique ID of this session. @@ -90,7 +90,7 @@ export class Session { /** * The status of this session. */ - public get status(): SandboxMetaData["status"] { + public get status(): SessionMetaData["status"] { return this.session.status; } @@ -236,11 +236,11 @@ export class Session { }: { client: APIClient; routes: SandboxRouteData[]; - session: SandboxMetaData; + session: SessionMetaData; }) { this.client = client; this.routes = routes; - this.session = convertSandbox(session); + this.session = convertSession(session); } /** @@ -553,13 +553,13 @@ export class Session { * @param opts.blocking - If true, poll until the session has fully stopped and return the final state. * @returns The session metadata at the time the stop was acknowledged, or after fully stopped if `blocking` is true. */ - async stop(opts?: { signal?: AbortSignal; blocking?: boolean }): Promise { - const response = await this.client.stopSandbox({ + async stop(opts?: { signal?: AbortSignal; blocking?: boolean }): Promise { + const response = await this.client.stopSession({ sessionId: this.session.id, signal: opts?.signal, blocking: opts?.blocking, }); - this.session = convertSandbox(response.json.session); + this.session = convertSession(response.json.session); return this.session; } @@ -612,7 +612,7 @@ export class Session { }); // Update the internal session metadata with the new network policy - this.session = convertSandbox(response.json.session); + this.session = convertSession(response.json.session); } } @@ -644,7 +644,7 @@ export class Session { }); // Update the internal session metadata with the new timeout value - this.session = convertSandbox(response.json.session); + this.session = convertSession(response.json.session); } /** @@ -668,7 +668,7 @@ export class Session { signal: opts?.signal, }); - this.session = convertSandbox(response.json.session); + this.session = convertSession(response.json.session); return new Snapshot({ client: this.client, diff --git a/packages/vercel-sandbox/src/utils/convert-sandbox.ts b/packages/vercel-sandbox/src/utils/convert-sandbox.ts index 0996334..3c28ffb 100644 --- a/packages/vercel-sandbox/src/utils/convert-sandbox.ts +++ b/packages/vercel-sandbox/src/utils/convert-sandbox.ts @@ -1,13 +1,13 @@ -import type { SandboxMetaData } from "../api-client"; +import type { SessionMetaData } from "../api-client"; import type { NetworkPolicy } from "../network-policy"; import { fromAPINetworkPolicy } from "./network-policy"; -export type ConvertedSandbox = Omit & { +export type ConvertedSession = Omit & { networkPolicy?: NetworkPolicy; }; -export function convertSandbox(sandbox: SandboxMetaData): ConvertedSandbox { - const { networkPolicy, ...rest } = sandbox; +export function convertSession(session: SessionMetaData): ConvertedSession { + const { networkPolicy, ...rest } = session; return { ...rest, networkPolicy: networkPolicy From 0f292a636be4e5c1f4b17335134132657312e9f9 Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Thu, 12 Mar 2026 21:19:16 +0100 Subject: [PATCH 3/7] changeset --- .changeset/dirty-cows-talk.md | 6 ++++++ .changeset/pre.json | 1 + packages/sandbox/CHANGELOG.md | 11 +++++++++++ packages/sandbox/docs/index.md | 2 +- packages/sandbox/package.json | 2 +- packages/vercel-sandbox/CHANGELOG.md | 6 ++++++ packages/vercel-sandbox/package.json | 2 +- packages/vercel-sandbox/src/version.ts | 2 +- 8 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 .changeset/dirty-cows-talk.md diff --git a/.changeset/dirty-cows-talk.md b/.changeset/dirty-cows-talk.md new file mode 100644 index 0000000..e70e4ca --- /dev/null +++ b/.changeset/dirty-cows-talk.md @@ -0,0 +1,6 @@ +--- +"@vercel/sandbox": minor +"sandbox": minor +--- + +Rename sandbox to session, namedSandbox to sandbox diff --git a/.changeset/pre.json b/.changeset/pre.json index dbb157c..ebfab40 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -16,6 +16,7 @@ }, "changesets": [ "common-corners-leave", + "dirty-cows-talk", "fair-pears-dream", "fresh-rings-rescue", "legal-rings-work", diff --git a/packages/sandbox/CHANGELOG.md b/packages/sandbox/CHANGELOG.md index 3cad587..84b2429 100644 --- a/packages/sandbox/CHANGELOG.md +++ b/packages/sandbox/CHANGELOG.md @@ -1,5 +1,16 @@ # sandbox +## 3.0.0-beta.6 + +### Minor Changes + +- Rename sandbox to session, namedSandbox to sandbox + +### Patch Changes + +- Updated dependencies []: + - @vercel/sandbox@2.0.0-beta.5 + ## 3.0.0-beta.5 ### Patch Changes diff --git a/packages/sandbox/docs/index.md b/packages/sandbox/docs/index.md index 7d1211d..e4daeb7 100644 --- a/packages/sandbox/docs/index.md +++ b/packages/sandbox/docs/index.md @@ -1,7 +1,7 @@ ## `sandbox --help` ``` -sandbox 3.0.0-beta.5 +sandbox 3.0.0-beta.6 ▲ sandbox [options] diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json index 75264c8..aea35f7 100644 --- a/packages/sandbox/package.json +++ b/packages/sandbox/package.json @@ -1,7 +1,7 @@ { "name": "sandbox", "description": "Command line interface for Vercel Sandbox", - "version": "3.0.0-beta.5", + "version": "3.0.0-beta.6", "scripts": { "clean": "rm -rf node_modules dist", "sandbox": "ts-node ./src/sandbox.ts", diff --git a/packages/vercel-sandbox/CHANGELOG.md b/packages/vercel-sandbox/CHANGELOG.md index 2a2e8e2..4cb16ff 100644 --- a/packages/vercel-sandbox/CHANGELOG.md +++ b/packages/vercel-sandbox/CHANGELOG.md @@ -1,5 +1,11 @@ # @vercel/sandbox +## 2.0.0-beta.5 + +### Minor Changes + +- Rename sandbox to session, namedSandbox to sandbox + ## 2.0.0-beta.4 ### Patch Changes diff --git a/packages/vercel-sandbox/package.json b/packages/vercel-sandbox/package.json index 10e0142..4e639c5 100644 --- a/packages/vercel-sandbox/package.json +++ b/packages/vercel-sandbox/package.json @@ -1,6 +1,6 @@ { "name": "@vercel/sandbox", - "version": "2.0.0-beta.4", + "version": "2.0.0-beta.5", "description": "Software Development Kit for Vercel Sandbox", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/vercel-sandbox/src/version.ts b/packages/vercel-sandbox/src/version.ts index 65fb4e1..f8f7a15 100644 --- a/packages/vercel-sandbox/src/version.ts +++ b/packages/vercel-sandbox/src/version.ts @@ -1,2 +1,2 @@ // Autogenerated by inject-version.ts -export const VERSION = "2.0.0-beta.4"; +export const VERSION = "2.0.0-beta.5"; From 0bc0a3cf8fe87b3f30a1aa42b791288361eff57b Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Thu, 12 Mar 2026 22:08:18 +0100 Subject: [PATCH 4/7] fixes --- .../src/interactive-shell/extend-sandbox-timeout.ts | 13 +++++++------ .../src/api-client/api-client.test.ts | 5 ++--- .../vercel-sandbox/src/api-client/base-client.ts | 3 ++- packages/vercel-sandbox/src/sandbox.test.ts | 4 ++-- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts b/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts index 8f4a238..932c492 100644 --- a/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts +++ b/packages/sandbox/src/interactive-shell/extend-sandbox-timeout.ts @@ -11,26 +11,27 @@ export async function extendSandboxTimeoutPeriodically( sandbox: Sandbox, signal: AbortSignal, ) { - const timeout = sandbox.timeout; + const session = sandbox.currentSession(); + const timeout = session.timeout; if (timeout == null) return; - const nextTick = sandbox.createdAt.getTime() + timeout; + const nextTick = session.createdAt.getTime() + timeout; debug(`next tick: ${new Date(nextTick).toISOString()}`); while (!signal.aborted) { - const currentTimeout = sandbox.timeout; + const currentTimeout = session.timeout; if (currentTimeout == null) return; const sleepMs = - sandbox.createdAt.getTime() + currentTimeout - Date.now() - BUFFER; + session.createdAt.getTime() + currentTimeout - Date.now() - BUFFER; if (sleepMs > 2000) { debug(`sleeping for ${sleepMs}ms until next timeout extension`); await setTimeout(sleepMs, null, { signal }); } await sandbox.extendTimeout(ms("5 minutes")); - const updatedTimeout = sandbox.timeout; + const updatedTimeout = session.timeout; if (updatedTimeout == null) return; - const nextTick = sandbox.createdAt.getTime() + updatedTimeout; + const nextTick = session.createdAt.getTime() + updatedTimeout; debug( `extended sandbox timeout by 5 minutes. next tick: ${new Date(nextTick).toISOString()}`, ); diff --git a/packages/vercel-sandbox/src/api-client/api-client.test.ts b/packages/vercel-sandbox/src/api-client/api-client.test.ts index 064216d..a55732d 100644 --- a/packages/vercel-sandbox/src/api-client/api-client.test.ts +++ b/packages/vercel-sandbox/src/api-client/api-client.test.ts @@ -658,7 +658,7 @@ describe("APIClient", () => { }); }); - it("sends DELETE with projectId and preserveSandboxes=false", async () => { + it("sends DELETE with projectId", async () => { const body = { sandbox: makeSandboxMetadata() }; mockFetch.mockResolvedValue( new Response(JSON.stringify(body), { @@ -676,7 +676,6 @@ describe("APIClient", () => { const [url, opts] = mockFetch.mock.calls[0]; expect(url).toContain("/v2/sandboxes/my-sandbox"); expect(url).toContain("projectId=proj_123"); - expect(url).toContain("preserveSandboxes=false"); expect(opts.method).toBe("DELETE"); }); }); @@ -698,7 +697,7 @@ describe("APIClient", () => { mockFetch.mockResolvedValue( new Response( JSON.stringify({ - sandbox: { + session: { id: "sbx_123", memory: 2048, vcpus: 1, diff --git a/packages/vercel-sandbox/src/api-client/base-client.ts b/packages/vercel-sandbox/src/api-client/base-client.ts index 9984ed8..93bb74f 100644 --- a/packages/vercel-sandbox/src/api-client/base-client.ts +++ b/packages/vercel-sandbox/src/api-client/base-client.ts @@ -97,9 +97,10 @@ function extractSessionId(url: string): string | undefined { /** * Extract sandbox name from a sandbox API url. + * Excludes known sub-paths like /sessions/ and /snapshots/. */ function extractSandboxName(url: string): string | undefined { - const match = url.match(/\/v2\/sandboxes\/([^/?]+)/); + const match = url.match(/\/v2\/sandboxes\/(?!sessions(?:\/|$|\?))(?!snapshots(?:\/|$|\?))([^/?]+)/); return match?.[1]; } diff --git a/packages/vercel-sandbox/src/sandbox.test.ts b/packages/vercel-sandbox/src/sandbox.test.ts index fb6e735..2fd2580 100644 --- a/packages/vercel-sandbox/src/sandbox.test.ts +++ b/packages/vercel-sandbox/src/sandbox.test.ts @@ -384,10 +384,10 @@ for (const port of ports) { const resumed = await Sandbox.get({ name: sandbox.name }); const { json } = await resumed.listSessions(); - expect(json.sandboxes).toHaveLength(2); + expect(json.sessions).toHaveLength(2); const currentSessionId = resumed.currentSession().sessionId; - const match = json.sandboxes.find((s) => s.id === currentSessionId); + const match = json.sessions.find((s) => s.id === currentSessionId); expect(match).toBeDefined(); }); From 6534d269e01e858715adcbb3de5b695d42352c19 Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Fri, 13 Mar 2026 13:31:11 +0100 Subject: [PATCH 5/7] remove named --- .../src/api-client/api-client.ts | 2 +- packages/vercel-sandbox/src/sandbox.test.ts | 19 ++++++++++--------- packages/vercel-sandbox/src/sandbox.ts | 10 +++++----- packages/vercel-sandbox/src/session.ts | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/vercel-sandbox/src/api-client/api-client.ts b/packages/vercel-sandbox/src/api-client/api-client.ts index 1bc17ee..10b1de5 100644 --- a/packages/vercel-sandbox/src/api-client/api-client.ts +++ b/packages/vercel-sandbox/src/api-client/api-client.ts @@ -427,7 +427,7 @@ export class APIClient extends BaseClient { */ projectId: string; /** - * Filter snapshots by named sandbox name. + * Filter snapshots by sandbox name. */ name?: string; /** diff --git a/packages/vercel-sandbox/src/sandbox.test.ts b/packages/vercel-sandbox/src/sandbox.test.ts index 2fd2580..88e11d6 100644 --- a/packages/vercel-sandbox/src/sandbox.test.ts +++ b/packages/vercel-sandbox/src/sandbox.test.ts @@ -64,17 +64,18 @@ describe("downloadFile validation", () => { }); const makeSandboxMetadata = (): SandboxMetaData => ({ - id: "sbx_123", + name: "test-name", + currentSessionId: "sbx_123", + persistent: true, + status: 'running', memory: 2048, vcpus: 1, region: "iad1", runtime: "node24", timeout: 300_000, - status: "running", - requestedAt: 1, - createdAt: 1, cwd: "/", updatedAt: 1, + createdAt: 1, }); const makeCommand = (): CommandData => ({ @@ -82,7 +83,7 @@ const makeCommand = (): CommandData => ({ name: "echo", args: ["hello"], cwd: "/", - sandboxId: "sbx_123", + sessionId: "sbx_123", exitCode: null, startedAt: 1, }); @@ -92,7 +93,7 @@ describe("_runCommand error handling", () => { const command = makeCommand(); const logsError = new APIError(new Response("failed", { status: 500 }), { message: "Failed to stream logs", - sandboxId: "sbx_123", + sessionId: "sbx_123", }); const runCommandMock = vi.fn(async ({ wait }: { wait?: boolean }) => { @@ -136,7 +137,7 @@ describe("_runCommand error handling", () => { const command = makeCommand(); const logsError = new APIError(new Response("failed", { status: 500 }), { message: "Failed to stream logs", - sandboxId: "sbx_123", + sessionId: "sbx_123", }); const runCommandMock = vi.fn(async ({ wait }: { wait?: boolean }) => { @@ -162,8 +163,8 @@ describe("_runCommand error handling", () => { getLogs: getLogsMock, } as unknown as APIClient, routes: [], - session: makeSandboxMetadata(), - namedSandbox: { name: "test" } as any, + metadata: makeSandboxMetadata(), + session: {} as any, projectId: "test-project", }); diff --git a/packages/vercel-sandbox/src/sandbox.ts b/packages/vercel-sandbox/src/sandbox.ts index 3795483..7e19989 100644 --- a/packages/vercel-sandbox/src/sandbox.ts +++ b/packages/vercel-sandbox/src/sandbox.ts @@ -147,7 +147,7 @@ export class Sandbox { private session: Session; /** - * Internal metadata about the named sandbox. + * Internal metadata about the sandbox. */ private metadata: SandboxMetaData; @@ -768,7 +768,7 @@ export class Sandbox { } /** - * Update the named sandbox configuration. + * Update the sandbox configuration. * * @param params - Fields to update. * @param opts - Optional abort signal. @@ -813,7 +813,7 @@ export class Sandbox { } /** - * Delete this named sandbox. + * Delete this sandbox. * * After deletion the instance becomes inert — all further API calls will * throw immediately. @@ -827,7 +827,7 @@ export class Sandbox { } /** - * List sessions (VMs) that have been created for this named sandbox. + * List sessions (VMs) that have been created for this sandbox. * * @param params - Optional pagination parameters. * @returns The list of sessions and pagination metadata. @@ -850,7 +850,7 @@ export class Sandbox { } /** - * List snapshots that belong to this named sandbox. + * List snapshots that belong to this sandbox. * * @param params - Optional pagination parameters. * @returns The list of snapshots and pagination metadata. diff --git a/packages/vercel-sandbox/src/session.ts b/packages/vercel-sandbox/src/session.ts index edc16f9..97a9219 100644 --- a/packages/vercel-sandbox/src/session.ts +++ b/packages/vercel-sandbox/src/session.ts @@ -58,7 +58,7 @@ export interface RunCommandParams { } /** - * A Session represents a running VM instance within a named {@link Sandbox}. + * A Session represents a running VM instance within a {@link Sandbox}. * * Obtain a session via {@link Sandbox.currentSession}. */ From 5ba0554975c9f5b278890d9209dc3e621aaf5141 Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Fri, 13 Mar 2026 13:33:35 +0100 Subject: [PATCH 6/7] metadata to sandbox --- packages/vercel-sandbox/src/sandbox.test.ts | 14 +++--- packages/vercel-sandbox/src/sandbox.ts | 54 ++++++++++----------- packages/vercel-sandbox/src/session.ts | 6 +-- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/packages/vercel-sandbox/src/sandbox.test.ts b/packages/vercel-sandbox/src/sandbox.test.ts index 88e11d6..761c776 100644 --- a/packages/vercel-sandbox/src/sandbox.test.ts +++ b/packages/vercel-sandbox/src/sandbox.test.ts @@ -15,7 +15,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - metadata: { name: "test" } as any, + sandbox: { name: "test" } as any, projectId: "test-project", }); await expect( @@ -28,7 +28,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - metadata: { name: "test" } as any, + sandbox: { name: "test" } as any, projectId: "test-project", }); await expect( @@ -41,7 +41,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - metadata: { name: "test" } as any, + sandbox: { name: "test" } as any, projectId: "test-project", }); await expect( @@ -54,7 +54,7 @@ describe("downloadFile validation", () => { client: {} as any, routes: [], session: { id: "test" } as any, - metadata: { name: "test" } as any, + sandbox: { name: "test" } as any, projectId: "test-project", }); await expect( @@ -119,8 +119,8 @@ describe("_runCommand error handling", () => { getLogs: getLogsMock, } as unknown as APIClient, routes: [], - session: makeSandboxMetadata(), - namedSandbox: { name: "test" } as any, + sandbox: makeSandboxMetadata(), + session: {} as any, projectId: "test-project", }); @@ -163,7 +163,7 @@ describe("_runCommand error handling", () => { getLogs: getLogsMock, } as unknown as APIClient, routes: [], - metadata: makeSandboxMetadata(), + sandbox: makeSandboxMetadata(), session: {} as any, projectId: "test-project", }); diff --git a/packages/vercel-sandbox/src/sandbox.ts b/packages/vercel-sandbox/src/sandbox.ts index 7e19989..e717d34 100644 --- a/packages/vercel-sandbox/src/sandbox.ts +++ b/packages/vercel-sandbox/src/sandbox.ts @@ -149,13 +149,13 @@ export class Sandbox { /** * Internal metadata about the sandbox. */ - private metadata: SandboxMetaData; + private sandbox: SandboxMetaData; /** * The name of this sandbox. */ public get name(): string { - return this.metadata.name; + return this.sandbox.name; } /** @@ -170,75 +170,75 @@ export class Sandbox { * Whether the sandbox persists the state. */ public get persistent(): boolean { - return this.metadata.persistent; + return this.sandbox.persistent; } /** * The region this sandbox runs in. */ public get region(): string | undefined { - return this.metadata.region; + return this.sandbox.region; } /** * Number of virtual CPUs allocated. */ public get vcpus(): number | undefined { - return this.metadata.vcpus; + return this.sandbox.vcpus; } /** * Memory allocated in MB. */ public get memory(): number | undefined { - return this.metadata.memory; + return this.sandbox.memory; } /** Runtime identifier (e.g. "node24", "python3.13"). */ public get runtime(): string | undefined { - return this.metadata.runtime; + return this.sandbox.runtime; } /** * Cumulative egress bytes across all sessions. */ public get totalEgressBytes(): number | undefined { - return this.metadata.totalEgressBytes; + return this.sandbox.totalEgressBytes; } /** * Cumulative ingress bytes across all sessions. */ public get totalIngressBytes(): number | undefined { - return this.metadata.totalIngressBytes; + return this.sandbox.totalIngressBytes; } /** * Cumulative active CPU duration in milliseconds across all sessions. */ public get totalActiveCpuDurationMs(): number | undefined { - return this.metadata.totalActiveCpuDurationMs; + return this.sandbox.totalActiveCpuDurationMs; } /** * Cumulative wall-clock duration in milliseconds across all sessions. */ public get totalDurationMs(): number | undefined { - return this.metadata.totalDurationMs; + return this.sandbox.totalDurationMs; } /** * When this sandbox was last updated. */ public get updatedAt(): Date { - return new Date(this.metadata.updatedAt); + return new Date(this.sandbox.updatedAt); } /** * When this sandbox was created. */ public get createdAt(): Date { - return new Date(this.metadata.createdAt); + return new Date(this.sandbox.createdAt); } /** @@ -259,15 +259,15 @@ export class Sandbox { * The default timeout of this sandbox in milliseconds. */ public get timeout(): number | undefined { - return this.metadata.timeout; + return this.sandbox.timeout; } /** * The default network policy of this sandbox. */ public get networkPolicy(): NetworkPolicy | undefined { - return this.metadata.networkPolicy - ? fromAPINetworkPolicy(this.metadata.networkPolicy) + return this.sandbox.networkPolicy + ? fromAPINetworkPolicy(this.sandbox.networkPolicy) : undefined; } @@ -282,7 +282,7 @@ export class Sandbox { * The current snapshot ID of this sandbox, if any. */ public get currentSnapshotId(): string | undefined { - return this.metadata.currentSnapshotId; + return this.sandbox.currentSnapshotId; } /** @@ -408,18 +408,18 @@ export class Sandbox { client, routes, session, - metadata, + sandbox, projectId, }: { client: APIClient; routes: SandboxRouteData[]; session: SessionMetaData; - metadata: SandboxMetaData; + sandbox: SandboxMetaData; projectId: string; }) { this.client = client; this.session = new Session({ client, routes, session }); - this.metadata = metadata; + this.sandbox = sandbox; this.projectId = projectId; } @@ -437,7 +437,7 @@ export class Sandbox { */ private async resume(signal?: AbortSignal): Promise { const response = await this.client.getSandbox({ - name: this.metadata.name, + name: this.sandbox.name, projectId: this.projectId, resume: true, signal, @@ -667,7 +667,7 @@ export class Sandbox { * @param opts - Optional parameters. * @param opts.signal - An AbortSignal to cancel the operation. * @param opts.blocking - If true, poll until the sandbox has fully stopped and return the final state. - * @returns The sandbox metadata at the time the stop was acknowledged, or after fully stopped if `blocking` is true. + * @returns The sandbox at the time the stop was acknowledged, or after fully stopped if `blocking` is true. */ async stop(opts?: { signal?: AbortSignal; blocking?: boolean }): Promise { return this.session.stop(opts); @@ -789,7 +789,7 @@ export class Sandbox { // Update the sandbox config. This config will be used on the next session. const response = await this.client.updateSandbox({ - name: this.metadata.name, + name: this.sandbox.name, projectId: this.projectId, persistent: params.persistent, resources, @@ -797,7 +797,7 @@ export class Sandbox { networkPolicy: params.networkPolicy, signal: opts?.signal, }); - this.metadata = response.json.sandbox; + this.sandbox = response.json.sandbox; // Update the current session config. This only applies to network policy. if (params.networkPolicy) { @@ -820,7 +820,7 @@ export class Sandbox { */ async delete(opts?: { signal?: AbortSignal }): Promise { await this.client.deleteSandbox({ - name: this.metadata.name, + name: this.sandbox.name, projectId: this.projectId, signal: opts?.signal, }); @@ -841,7 +841,7 @@ export class Sandbox { return this.client.listSessions({ projectId: this.projectId, - name: this.metadata.name, + name: this.sandbox.name, limit: params?.limit, since: params?.since, until: params?.until, @@ -864,7 +864,7 @@ export class Sandbox { return this.client.listSnapshots({ projectId: this.projectId, - name: this.metadata.name, + name: this.sandbox.name, limit: params?.limit, since: params?.since, until: params?.until, diff --git a/packages/vercel-sandbox/src/session.ts b/packages/vercel-sandbox/src/session.ts index 97a9219..3ecca48 100644 --- a/packages/vercel-sandbox/src/session.ts +++ b/packages/vercel-sandbox/src/session.ts @@ -551,7 +551,7 @@ export class Session { * @param opts - Optional parameters. * @param opts.signal - An AbortSignal to cancel the operation. * @param opts.blocking - If true, poll until the session has fully stopped and return the final state. - * @returns The session metadata at the time the stop was acknowledged, or after fully stopped if `blocking` is true. + * @returns The session at the time the stop was acknowledged, or after fully stopped if `blocking` is true. */ async stop(opts?: { signal?: AbortSignal; blocking?: boolean }): Promise { const response = await this.client.stopSession({ @@ -611,7 +611,7 @@ export class Session { signal: opts?.signal, }); - // Update the internal session metadata with the new network policy + // Update the internal session with the new network policy this.session = convertSession(response.json.session); } } @@ -643,7 +643,7 @@ export class Session { signal: opts?.signal, }); - // Update the internal session metadata with the new timeout value + // Update the internal session with the new timeout value this.session = convertSession(response.json.session); } From 96a3a41b4b3da7b0c74279a86c6af0558b75e6d3 Mon Sep 17 00:00:00 2001 From: Marc Codina Date: Fri, 13 Mar 2026 13:37:32 +0100 Subject: [PATCH 7/7] metadata to sandbox --- packages/vercel-sandbox/src/sandbox.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vercel-sandbox/src/sandbox.ts b/packages/vercel-sandbox/src/sandbox.ts index e717d34..4b9b365 100644 --- a/packages/vercel-sandbox/src/sandbox.ts +++ b/packages/vercel-sandbox/src/sandbox.ts @@ -365,7 +365,7 @@ export class Sandbox { return new DisposableSandbox({ client, session: response.json.session, - metadata: response.json.sandbox, + sandbox: response.json.sandbox, routes: response.json.routes, projectId: credentials.projectId, }); @@ -398,7 +398,7 @@ export class Sandbox { return new Sandbox({ client, session: response.json.session, - metadata: response.json.sandbox, + sandbox: response.json.sandbox, routes: response.json.routes, projectId: credentials.projectId, });