diff --git a/lib/sandbox/__tests__/createSandbox.test.ts b/lib/sandbox/__tests__/createSandbox.test.ts index b80dbc44b..83feae2b7 100644 --- a/lib/sandbox/__tests__/createSandbox.test.ts +++ b/lib/sandbox/__tests__/createSandbox.test.ts @@ -1,17 +1,17 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { createSandbox } from "../createSandbox"; -import { Sandbox } from "@vercel/sandbox"; +import { VercelSandbox } from "../vercel"; const mockSandbox = { name: "sbx_test123", - status: "running", + sdkStatus: "running", timeout: 1800000, createdAt: new Date("2024-01-01T00:00:00Z"), }; -vi.mock("@vercel/sandbox", () => ({ - Sandbox: { +vi.mock("../vercel", () => ({ + VercelSandbox: { create: vi.fn(() => Promise.resolve(mockSandbox)), }, })); @@ -28,40 +28,42 @@ describe("createSandbox", () => { vi.clearAllMocks(); }); - it("creates sandbox with default configuration when no params provided", async () => { + it("creates sandbox with default configuration when no config provided", async () => { await createSandbox(); - expect(Sandbox.create).toHaveBeenCalledWith({ - resources: { vcpus: 4 }, + expect(VercelSandbox.create).toHaveBeenCalledWith({ + vcpus: 4, timeout: 1800000, runtime: "node22", }); }); - it("creates sandbox from snapshot when source is provided", async () => { - await createSandbox({ source: { type: "snapshot", snapshotId: "snap_abc123" } }); + it("restores from snapshot when restoreSnapshotId is provided", async () => { + await createSandbox({ restoreSnapshotId: "snap_abc123" }); - expect(Sandbox.create).toHaveBeenCalledWith({ - source: { type: "snapshot", snapshotId: "snap_abc123" }, + expect(VercelSandbox.create).toHaveBeenCalledWith({ + vcpus: 4, + runtime: "node22", timeout: 1800000, + restoreSnapshotId: "snap_abc123", }); }); it("allows overriding default timeout", async () => { await createSandbox({ timeout: 300000 }); - expect(Sandbox.create).toHaveBeenCalledWith({ - resources: { vcpus: 4 }, + expect(VercelSandbox.create).toHaveBeenCalledWith({ + vcpus: 4, timeout: 300000, runtime: "node22", }); }); - it("allows overriding default resources", async () => { - await createSandbox({ resources: { vcpus: 2 } }); + it("allows overriding default vcpus", async () => { + await createSandbox({ vcpus: 2 }); - expect(Sandbox.create).toHaveBeenCalledWith({ - resources: { vcpus: 2 }, + expect(VercelSandbox.create).toHaveBeenCalledWith({ + vcpus: 2, timeout: 1800000, runtime: "node22", }); @@ -84,7 +86,9 @@ describe("createSandbox", () => { ...mockSandbox, stop: vi.fn(), }; - vi.mocked(Sandbox.create).mockResolvedValue(mockSandboxWithStop as unknown as Sandbox); + vi.mocked(VercelSandbox.create).mockResolvedValue( + mockSandboxWithStop as unknown as VercelSandbox, + ); await createSandbox(); diff --git a/lib/sandbox/__tests__/createSandboxWithFallback.test.ts b/lib/sandbox/__tests__/createSandboxWithFallback.test.ts index 3af6d1fed..badc56b51 100644 --- a/lib/sandbox/__tests__/createSandboxWithFallback.test.ts +++ b/lib/sandbox/__tests__/createSandboxWithFallback.test.ts @@ -27,7 +27,7 @@ describe("createSandboxWithFallback", () => { const result = await createSandboxWithFallback("snap_abc"); expect(mockCreateSandbox).toHaveBeenCalledWith({ - source: { type: "snapshot", snapshotId: "snap_abc" }, + restoreSnapshotId: "snap_abc", }); expect(result).toEqual({ ...mockCreateResult, fromSnapshot: true }); }); diff --git a/lib/sandbox/__tests__/getActiveSandbox.test.ts b/lib/sandbox/__tests__/getActiveSandbox.test.ts index 0f6ca2e6f..db36ca821 100644 --- a/lib/sandbox/__tests__/getActiveSandbox.test.ts +++ b/lib/sandbox/__tests__/getActiveSandbox.test.ts @@ -1,13 +1,13 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import { Sandbox } from "@vercel/sandbox"; +import { VercelSandbox } from "@/lib/sandbox/vercel"; import { getActiveSandbox } from "../getActiveSandbox"; const mockSelectAccountSandboxes = vi.fn(); -vi.mock("@vercel/sandbox", () => ({ - Sandbox: { - get: vi.fn(), +vi.mock("@/lib/sandbox/vercel", () => ({ + VercelSandbox: { + connect: vi.fn(), }, })); @@ -25,17 +25,16 @@ describe("getActiveSandbox", () => { const mockSandbox = { name: "sbx_123", - status: "running", - runCommand: vi.fn(), + sdkStatus: "running", }; - vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as unknown as Sandbox); + vi.mocked(VercelSandbox.connect).mockResolvedValue(mockSandbox as unknown as VercelSandbox); const result = await getActiveSandbox("acc_1"); expect(mockSelectAccountSandboxes).toHaveBeenCalledWith({ accountIds: ["acc_1"], }); - expect(Sandbox.get).toHaveBeenCalledWith({ name: "sbx_123" }); + expect(VercelSandbox.connect).toHaveBeenCalledWith("sbx_123", {}); expect(result).toBe(mockSandbox); }); @@ -45,7 +44,7 @@ describe("getActiveSandbox", () => { const result = await getActiveSandbox("acc_1"); expect(result).toBeNull(); - expect(Sandbox.get).not.toHaveBeenCalled(); + expect(VercelSandbox.connect).not.toHaveBeenCalled(); }); it("returns null when sandbox is not running", async () => { @@ -55,21 +54,21 @@ describe("getActiveSandbox", () => { const mockSandbox = { name: "sbx_stopped", - status: "stopped", + sdkStatus: "stopped", }; - vi.mocked(Sandbox.get).mockResolvedValue(mockSandbox as unknown as Sandbox); + vi.mocked(VercelSandbox.connect).mockResolvedValue(mockSandbox as unknown as VercelSandbox); const result = await getActiveSandbox("acc_1"); expect(result).toBeNull(); }); - it("returns null when Sandbox.get throws", async () => { + it("returns null when VercelSandbox.connect throws", async () => { mockSelectAccountSandboxes.mockResolvedValue([ { sandbox_id: "sbx_expired", account_id: "acc_1" }, ]); - vi.mocked(Sandbox.get).mockRejectedValue(new Error("Sandbox not found")); + vi.mocked(VercelSandbox.connect).mockRejectedValue(new Error("Sandbox not found")); const result = await getActiveSandbox("acc_1"); diff --git a/lib/sandbox/__tests__/processCreateSandbox.test.ts b/lib/sandbox/__tests__/processCreateSandbox.test.ts index 160cf287c..789b0a998 100644 --- a/lib/sandbox/__tests__/processCreateSandbox.test.ts +++ b/lib/sandbox/__tests__/processCreateSandbox.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -import type { Sandbox } from "@vercel/sandbox"; +import type { VercelSandbox } from "@/lib/sandbox/vercel"; import { processCreateSandbox } from "../processCreateSandbox"; import { createSandboxFromSnapshot } from "@/lib/sandbox/createSandboxFromSnapshot"; @@ -15,10 +15,10 @@ vi.mock("@/lib/trigger/triggerPromptSandbox", () => ({ const mockSandbox = { name: "sbx_123", - status: "running", + sdkStatus: "running", timeout: 600000, createdAt: new Date("2024-01-01T00:00:00.000Z"), -} as unknown as Sandbox; +} as unknown as VercelSandbox; describe("processCreateSandbox", () => { beforeEach(() => { diff --git a/lib/sandbox/createSandbox.ts b/lib/sandbox/createSandbox.ts index d6c732128..cc813cf98 100644 --- a/lib/sandbox/createSandbox.ts +++ b/lib/sandbox/createSandbox.ts @@ -1,61 +1,57 @@ import ms from "ms"; -import { Sandbox } from "@vercel/sandbox"; +import { VercelSandbox, type VercelSandboxConfig } from "@/lib/sandbox/vercel"; export interface SandboxCreatedResponse { - sandboxId: Sandbox["name"]; - sandboxStatus: Sandbox["status"]; - timeout: Sandbox["timeout"]; + sandboxId: VercelSandbox["name"]; + sandboxStatus: string; + timeout: VercelSandbox["timeout"]; createdAt: string; } export interface SandboxCreateResult { - sandbox: Sandbox; + sandbox: VercelSandbox; response: SandboxCreatedResponse; } -/** Extract CreateSandboxParams from Sandbox.create method signature */ -export type CreateSandboxParams = NonNullable[0]>; +/** Parameters for the api-side createSandbox helper. Wraps the abstraction's + * VercelSandboxConfig so callers do not need to import it directly. */ +export type CreateSandboxParams = VercelSandboxConfig; const DEFAULT_TIMEOUT = ms("30m"); const DEFAULT_VCPUS = 4; -const DEFAULT_RUNTIME = "node22"; +const DEFAULT_RUNTIME = "node22" as const; /** - * Creates a Vercel Sandbox and returns its info. + * Creates a Vercel Sandbox via the open-agents abstraction and returns + * its info. The sandbox is left running so subsequent prompts can run + * against it. * - * The sandbox is left running so that prompts can be executed via the prompt_sandbox tool. - * Accepts the same parameters as Sandbox.create from @vercel/sandbox. + * Note: VercelSandbox.create applies its own defaults for vcpus and + * runtime (vcpus=4, runtime="node22") regardless of source — those + * apply to the runtime resources of the new sandbox even when restoring + * from a snapshot. We pass our preferred defaults explicitly so api's + * intent is documented at the call site. * - * @param params - Sandbox creation parameters (source, timeout, resources, runtime, ports) - * @returns The sandbox creation response + * @param config - VercelSandboxConfig (timeout, vcpus, runtime, + * restoreSnapshotId, source, ports, env, etc.) + * @returns The sandbox creation result (instance + response shape) * @throws Error if sandbox creation fails */ export async function createSandbox( - params: CreateSandboxParams = {}, + config: CreateSandboxParams = {}, ): Promise { - const hasSnapshotSource = - params.source && "type" in params.source && params.source.type === "snapshot"; - - // Pass params directly to SDK - it handles all the type variants - const sandbox = await Sandbox.create( - hasSnapshotSource - ? { - ...params, - timeout: params.timeout ?? DEFAULT_TIMEOUT, - } - : { - resources: { vcpus: DEFAULT_VCPUS }, - timeout: params.timeout ?? DEFAULT_TIMEOUT, - runtime: DEFAULT_RUNTIME, - ...params, - }, - ); + const sandbox = await VercelSandbox.create({ + vcpus: DEFAULT_VCPUS, + runtime: DEFAULT_RUNTIME, + timeout: DEFAULT_TIMEOUT, + ...config, + }); return { sandbox, response: { sandboxId: sandbox.name, - sandboxStatus: sandbox.status, + sandboxStatus: sandbox.sdkStatus, timeout: sandbox.timeout, createdAt: sandbox.createdAt.toISOString(), }, diff --git a/lib/sandbox/createSandboxFromSnapshot.ts b/lib/sandbox/createSandboxFromSnapshot.ts index 2dcfb7ef4..b1132a4b6 100644 --- a/lib/sandbox/createSandboxFromSnapshot.ts +++ b/lib/sandbox/createSandboxFromSnapshot.ts @@ -1,10 +1,10 @@ -import type { Sandbox } from "@vercel/sandbox"; +import type { VercelSandbox } from "@/lib/sandbox/vercel"; import { createSandboxWithFallback } from "@/lib/sandbox/createSandboxWithFallback"; import { getValidSnapshotId } from "@/lib/sandbox/getValidSnapshotId"; import { insertAccountSandbox } from "@/lib/supabase/account_sandboxes/insertAccountSandbox"; export interface CreateSandboxFromSnapshotResult { - sandbox: Sandbox; + sandbox: VercelSandbox; fromSnapshot: boolean; } diff --git a/lib/sandbox/createSandboxWithFallback.ts b/lib/sandbox/createSandboxWithFallback.ts index 93014ac43..d728485c5 100644 --- a/lib/sandbox/createSandboxWithFallback.ts +++ b/lib/sandbox/createSandboxWithFallback.ts @@ -14,7 +14,7 @@ export async function createSandboxWithFallback( ): Promise { if (snapshotId) { try { - const result = await createSandbox({ source: { type: "snapshot", snapshotId } }); + const result = await createSandbox({ restoreSnapshotId: snapshotId }); return { ...result, fromSnapshot: true }; } catch (error) { console.error("Snapshot sandbox creation failed, falling back to fresh sandbox:", error); diff --git a/lib/sandbox/getActiveSandbox.ts b/lib/sandbox/getActiveSandbox.ts index f47dec4f6..c0ba2796f 100644 --- a/lib/sandbox/getActiveSandbox.ts +++ b/lib/sandbox/getActiveSandbox.ts @@ -1,13 +1,14 @@ -import { Sandbox } from "@vercel/sandbox"; +import { VercelSandbox } from "@/lib/sandbox/vercel"; import { selectAccountSandboxes } from "@/lib/supabase/account_sandboxes/selectAccountSandboxes"; /** * Finds the most recent sandbox for an account and returns it if still running. + * Reconnects via the open-agents sandbox abstraction. * * @param accountId - The account ID to find an active sandbox for - * @returns The running Sandbox instance, or null if none found + * @returns The running VercelSandbox instance, or null if none found */ -export async function getActiveSandbox(accountId: string): Promise { +export async function getActiveSandbox(accountId: string): Promise { const sandboxes = await selectAccountSandboxes({ accountIds: [accountId], }); @@ -19,9 +20,9 @@ export async function getActiveSandbox(accountId: string): Promise