diff --git a/.changeset/swift-drives-mount.md b/.changeset/swift-drives-mount.md new file mode 100644 index 0000000000..b5d895c07f --- /dev/null +++ b/.changeset/swift-drives-mount.md @@ -0,0 +1,5 @@ +--- +"eve": minor +--- + +Expose Vercel Sandbox Drives and allow authors to mount them when creating live session sandboxes. diff --git a/packages/eve/package.json b/packages/eve/package.json index d832c62fbc..70e3094fbc 100644 --- a/packages/eve/package.json +++ b/packages/eve/package.json @@ -346,6 +346,7 @@ "@vercel/oidc": "3.8.0", "@vercel/otel": "catalog:", "@vercel/sandbox": "catalog:", + "@vercel/sandbox-drives": "catalog:", "@vercel/sdk": "1.28.8", "@workflow/core": "5.0.0-beta.41", "@workflow/errors": "5.0.0-beta.16", diff --git a/packages/eve/scripts/vendor-compiled/@vercel/sandbox-stable.mjs b/packages/eve/scripts/vendor-compiled/@vercel/sandbox-stable.mjs new file mode 100644 index 0000000000..c4943f0a82 --- /dev/null +++ b/packages/eve/scripts/vendor-compiled/@vercel/sandbox-stable.mjs @@ -0,0 +1,9 @@ +import vercelSandbox from "./sandbox.mjs"; + +/** Preserve extension contract declarations while Drives uses the beta runtime. */ +export default { + ...vercelSandbox, + packageName: "@vercel/sandbox", + compiledPath: "@vercel/sandbox-stable", + typeOnly: true, +}; diff --git a/packages/eve/scripts/vendor-compiled/@vercel/sandbox.mjs b/packages/eve/scripts/vendor-compiled/@vercel/sandbox.mjs index 2202b04746..4a3b12a39f 100644 --- a/packages/eve/scripts/vendor-compiled/@vercel/sandbox.mjs +++ b/packages/eve/scripts/vendor-compiled/@vercel/sandbox.mjs @@ -23,7 +23,8 @@ async function discoverDeclarationFiles({ distDir }) { * or local stubs, while Node built-ins remain external. */ export default { - packageName: "@vercel/sandbox", + packageName: "@vercel/sandbox-drives", + packageJsonName: "@vercel/sandbox", compiledPath: "@vercel/sandbox", plugins: [createOptionalNativeStubPlugin(["fsevents"])], copyDeclarations: createDeclarationCopier({ diff --git a/packages/eve/scripts/vendor-compiled/_shared.mjs b/packages/eve/scripts/vendor-compiled/_shared.mjs index ec095cd3b8..e33aede4d3 100644 --- a/packages/eve/scripts/vendor-compiled/_shared.mjs +++ b/packages/eve/scripts/vendor-compiled/_shared.mjs @@ -10,6 +10,7 @@ * ``` * { * packageName: string, // npm package to resolve via require + * packageJsonName?: string, // original npm name when packageName is an alias * compiledPath: string, // subdir under compiledRoot to write into * * // Declaration emission (pick one) @@ -513,7 +514,7 @@ async function pathExists(path) { } } -async function findPackageJson(packageName, packageRoot) { +async function findPackageJson(packageName, packageRoot, packageJsonName = packageName) { let currentPath; try { currentPath = dirname(require.resolve(packageName, { paths: [packageRoot] })); @@ -527,7 +528,7 @@ async function findPackageJson(packageName, packageRoot) { if (await pathExists(packageJsonPath)) { const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")); - if (packageJson.name === packageName) { + if (packageJson.name === packageJsonName) { return { packageJson, packageJsonPath, @@ -542,7 +543,7 @@ async function findPackageJson(packageName, packageRoot) { const packageJsonPath = join(currentPath, "package.json"); if (await pathExists(packageJsonPath)) { const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8")); - if (packageJson.name === packageName) { + if (packageJson.name === packageJsonName) { return { packageJson, packageJsonPath, @@ -567,7 +568,11 @@ async function copyLicense(sourceRoot, destinationRoot) { async function prepareCompiledModule({ module, compiledRoot, packageRoot }) { const destinationRoot = join(compiledRoot, module.compiledPath); - const packageInfo = await findPackageJson(module.packageName, packageRoot); + const packageInfo = await findPackageJson( + module.packageName, + packageRoot, + module.packageJsonName, + ); await rm(destinationRoot, { recursive: true, force: true }); await mkdir(destinationRoot, { recursive: true }); @@ -809,7 +814,11 @@ async function computeStamp({ scriptFiles, modules, packageRoot }) { const moduleVersions = {}; for (const module of modules) { - const { packageJson } = await findPackageJson(module.packageName, packageRoot); + const { packageJson } = await findPackageJson( + module.packageName, + packageRoot, + module.packageJsonName, + ); moduleVersions[module.packageName] = packageJson.version ?? "0.0.0"; } diff --git a/packages/eve/scripts/vendor-compiled/index.mjs b/packages/eve/scripts/vendor-compiled/index.mjs index f7548dce6e..3bae2d10ea 100644 --- a/packages/eve/scripts/vendor-compiled/index.mjs +++ b/packages/eve/scripts/vendor-compiled/index.mjs @@ -24,6 +24,7 @@ import vercelDetectAgent from "./@vercel/detect-agent.mjs"; import vercelOidc from "./@vercel/oidc.mjs"; import vercelOtel from "./@vercel/otel.mjs"; import vercelSandbox from "./@vercel/sandbox.mjs"; +import vercelSandboxStable from "./@vercel/sandbox-stable.mjs"; import workflowCore from "./@workflow/core.mjs"; import workflowErrors from "./@workflow/errors.mjs"; import workflowSerde from "./@workflow/serde.mjs"; @@ -84,6 +85,7 @@ export const MODULES = [ vercelOidc, vercelOtel, vercelSandbox, + vercelSandboxStable, workflowCore, workflowErrors, workflowSerde, diff --git a/packages/eve/src/execution/sandbox/bindings/vercel.test.ts b/packages/eve/src/execution/sandbox/bindings/vercel.test.ts index 7986221c43..cab2e0fe45 100644 --- a/packages/eve/src/execution/sandbox/bindings/vercel.test.ts +++ b/packages/eve/src/execution/sandbox/bindings/vercel.test.ts @@ -748,6 +748,63 @@ describe("createVercelSandbox", () => { expect(templateSandbox.update).toHaveBeenCalledWith({ networkPolicy: "deny-all" }); }); + it("resolves mounts only for a fresh live session sandbox", async () => { + const templateSandbox = createMockSandbox({ name: "template" }); + const sessionSandbox = createMockSandbox({ name: "session" }); + const create = vi + .fn() + .mockResolvedValueOnce(templateSandbox) + .mockResolvedValueOnce(sessionSandbox); + const resolveSessionCreateOptions = vi.fn(({ session }) => ({ + mounts: { "/workspace/repos": { drive: `e0-${session.id}` } }, + })); + const backend = createTestVercelSandbox({ + loadSandboxModule: async () => + ({ Sandbox: { create, get: vi.fn().mockResolvedValue(null) } }) as never, + resolveSessionCreateOptions, + }); + + await backend.prewarm({ + runtimeContext: { appRoot: "/tmp/test-app-root" }, + seedFiles: [], + templateKey: "template-key", + }); + await backend.create({ + runtimeContext: { appRoot: "/tmp/test-app-root" }, + sessionKey: "session-key", + tags: { sessionId: "parent-session" }, + templateKey: "template-key", + }); + + expect(resolveSessionCreateOptions).toHaveBeenCalledWith({ + session: { id: "parent-session" }, + }); + expect(create.mock.calls[0]?.[0]).not.toHaveProperty("mounts"); + expect(create.mock.calls[1]?.[0]).toMatchObject({ + mounts: { "/workspace/repos": { drive: "e0-parent-session" } }, + }); + }); + + it("does not resolve session create options when resuming a sandbox", async () => { + const existing = createMockSandbox({ name: "session-key" }); + const create = vi.fn(); + const resolveSessionCreateOptions = vi.fn(); + const backend = createTestVercelSandbox({ + loadSandboxModule: async () => + ({ Sandbox: { create, get: vi.fn().mockResolvedValue(existing) } }) as never, + resolveSessionCreateOptions, + }); + + await backend.create({ + runtimeContext: { appRoot: "/tmp/test-app-root" }, + sessionKey: "session-key", + templateKey: null, + }); + + expect(resolveSessionCreateOptions).not.toHaveBeenCalled(); + expect(create).not.toHaveBeenCalled(); + }); + it("forwards author source to template create as the base layer", async () => { /* * The real Vercel SDK pre-populates `currentSnapshotId` on a diff --git a/packages/eve/src/execution/sandbox/bindings/vercel.ts b/packages/eve/src/execution/sandbox/bindings/vercel.ts index 95b838375d..2ce7c5e653 100644 --- a/packages/eve/src/execution/sandbox/bindings/vercel.ts +++ b/packages/eve/src/execution/sandbox/bindings/vercel.ts @@ -25,6 +25,8 @@ import type { import { SandboxTemplateNotProvisionedError } from "#public/definitions/sandbox-backend.js"; import type { VercelSandboxBootstrapUseOptions, + VercelSandboxSessionCreateContext, + VercelSandboxSessionCreateOptions, VercelSandboxSessionUseOptions, } from "#public/sandbox/vercel-sandbox.js"; import { WORKSPACE_ROOT } from "#runtime/workspace/types.js"; @@ -54,6 +56,9 @@ export interface CreateVercelSandboxInput { readonly createSandbox?: CreateVercelSandbox; readonly createOptions?: VercelCreateOptions; readonly loadSandboxModule?: () => Promise; + readonly resolveSessionCreateOptions?: ( + context: VercelSandboxSessionCreateContext, + ) => Promise | VercelSandboxSessionCreateOptions; } /** * Creates the Vercel-backed sandbox backend. @@ -101,7 +106,9 @@ export function createVercelSandbox( createOptions, createSandbox, existingMetadata: createInput.existingMetadata, + resolveSessionCreateOptions: input.resolveSessionCreateOptions, sandboxModule, + sessionId: createInput.tags?.sessionId ?? createInput.sessionKey, sessionKey: createInput.sessionKey, snapshotId: template?.snapshotId, tags, @@ -357,7 +364,9 @@ interface EnsureSessionInput { readonly createOptions: VercelCreateOptions; readonly createSandbox: CreateVercelSandbox; readonly existingMetadata?: Record; + readonly resolveSessionCreateOptions?: CreateVercelSandboxInput["resolveSessionCreateOptions"]; readonly sandboxModule: VercelModule; + readonly sessionId: string; readonly sessionKey: string; readonly snapshotId?: string; readonly tags: Record | undefined; @@ -381,7 +390,10 @@ async function ensureSession(input: EnsureSessionInput): Promise>; return { - ...sessionCreateOptions, + ...baseSessionCreateOptions, name: sandboxName, persistent: true, source: { snapshotId: input.snapshotId, type: "snapshot" as const }, diff --git a/packages/eve/src/public/sandbox/backends/vercel.ts b/packages/eve/src/public/sandbox/backends/vercel.ts index b797a9a52e..717f19bb7d 100644 --- a/packages/eve/src/public/sandbox/backends/vercel.ts +++ b/packages/eve/src/public/sandbox/backends/vercel.ts @@ -2,7 +2,7 @@ import { createVercelSandbox } from "#execution/sandbox/bindings/vercel.js"; import type { SandboxBackend } from "#public/definitions/sandbox-backend.js"; import type { VercelSandboxBootstrapUseOptions, - VercelSandboxCreateOptions, + VercelSandboxOptions, VercelSandboxSessionUseOptions, } from "#public/sandbox/vercel-sandbox.js"; @@ -12,7 +12,7 @@ import type { * including for local development, where it creates real hosted * sandboxes (requires Vercel credentials). * - * The optional `opts` parameter is forwarded to Vercel Sandbox creation + * The optional provider creation fields are forwarded to Vercel Sandbox creation * for every fresh sandbox the framework creates (template at prewarm, * session at first-time create). On resume (`Sandbox.get`), no create * happens, so opts are not re-applied. `networkPolicy` is applied after @@ -30,10 +30,15 @@ import type { * `sandbox.update(...)`; those settings persist into the snapshot. * `onSession({ use })` applies its options to the live session via the * SDK's `update` under the hood, overriding any overlapping field - * from `opts`. + * from `opts`. `sessionCreateOptions`, when provided, is resolved only + * when creating a fresh live session and can attach session-specific Drives. */ export function vercel( - opts?: VercelSandboxCreateOptions, + opts?: VercelSandboxOptions, ): SandboxBackend { - return createVercelSandbox({ createOptions: opts }); + const { sessionCreateOptions, ...createOptions } = opts ?? {}; + return createVercelSandbox({ + createOptions, + resolveSessionCreateOptions: sessionCreateOptions, + }); } diff --git a/packages/eve/src/public/sandbox/vercel-sandbox.ts b/packages/eve/src/public/sandbox/vercel-sandbox.ts index 999535abf5..7b66791136 100644 --- a/packages/eve/src/public/sandbox/vercel-sandbox.ts +++ b/packages/eve/src/public/sandbox/vercel-sandbox.ts @@ -9,7 +9,7 @@ type VercelSandboxInternalCreateOptions = { }; type VercelSandboxAuthorCreateOptions = T extends unknown - ? Omit & + ? Omit & VercelSandboxInternalCreateOptions : never; @@ -44,6 +44,36 @@ type VercelSandboxAuthorCreateOptions = T extends unknown */ export type VercelSandboxCreateOptions = VercelSandboxAuthorCreateOptions; +/** Access mode for a Drive mounted into a Vercel Sandbox. */ +export type VercelSandboxMountMode = Vercel.SandboxMountMode; + +/** A Drive mounted at one absolute path in a Vercel Sandbox. */ +export type VercelSandboxMount = Vercel.SandboxMounts[string]; + +/** Drive mounts keyed by absolute sandbox path. */ +export type VercelSandboxMounts = Vercel.SandboxMounts; + +/** Options resolved when eve creates a fresh live session sandbox. */ +export interface VercelSandboxSessionCreateOptions { + readonly mounts?: VercelSandboxMounts; +} + +/** Context available while resolving fresh live-session creation options. */ +export interface VercelSandboxSessionCreateContext { + readonly session: { readonly id: string }; +} + +/** Options accepted by `vercel(opts)`. */ +export type VercelSandboxOptions = VercelSandboxCreateOptions & { + /** + * Resolves options that apply only to fresh live sessions. It is not called + * while prewarming templates or resuming an existing sandbox. + */ + readonly sessionCreateOptions?: ( + context: VercelSandboxSessionCreateContext, + ) => Promise | VercelSandboxSessionCreateOptions; +}; + /** * Options accepted by the Vercel backend's `bootstrap({ use })` hook. * Tracks the Vercel SDK's `Sandbox.update(...)` parameter because bootstrap diff --git a/packages/eve/src/public/sandbox/vercel.test.ts b/packages/eve/src/public/sandbox/vercel.test.ts new file mode 100644 index 0000000000..91b53bde00 --- /dev/null +++ b/packages/eve/src/public/sandbox/vercel.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, expectTypeOf, it } from "vitest"; + +import { + Drive, + vercel, + type VercelSandboxOptions, + type VercelSandboxSessionCreateOptions, +} from "#public/sandbox/vercel.js"; + +describe("vercel", () => { + it("exposes Drive and accepts session-scoped mounts", () => { + const options = { + sessionCreateOptions: ({ session }) => ({ + mounts: { + "/workspace": { drive: `repo-${session.id}`, mode: "read-write" }, + }, + }), + } satisfies VercelSandboxOptions; + + expect(vercel(options).name).toBe("vercel"); + expectTypeOf(Drive.getOrCreate).toBeFunction(); + expectTypeOf( + options.sessionCreateOptions({ session: { id: "acme" } }), + ).toMatchTypeOf(); + expectTypeOf().not.toHaveProperty("mounts"); + }); +}); diff --git a/packages/eve/src/public/sandbox/vercel.ts b/packages/eve/src/public/sandbox/vercel.ts index f827b262ab..47e36252dd 100644 --- a/packages/eve/src/public/sandbox/vercel.ts +++ b/packages/eve/src/public/sandbox/vercel.ts @@ -1,6 +1,13 @@ export { vercel } from "#public/sandbox/backends/vercel.js"; +export { Drive } from "#compiled/@vercel/sandbox/index.js"; export type { VercelSandboxBootstrapUseOptions, VercelSandboxCreateOptions, + VercelSandboxMount, + VercelSandboxMountMode, + VercelSandboxMounts, + VercelSandboxOptions, + VercelSandboxSessionCreateContext, + VercelSandboxSessionCreateOptions, VercelSandboxSessionUseOptions, } from "#public/sandbox/vercel-sandbox.js"; diff --git a/packages/eve/src/shared/sandbox-network-policy.ts b/packages/eve/src/shared/sandbox-network-policy.ts index 71a01f1ba9..f961474963 100644 --- a/packages/eve/src/shared/sandbox-network-policy.ts +++ b/packages/eve/src/shared/sandbox-network-policy.ts @@ -1,4 +1,4 @@ -import type * as Vercel from "#compiled/@vercel/sandbox/index.js"; +import type * as Vercel from "#compiled/@vercel/sandbox-stable/index.js"; /** * Firewall network policy applied to a live sandbox session. diff --git a/packages/eve/test/scenarios/compiled-vendor-assets.scenario.test.ts b/packages/eve/test/scenarios/compiled-vendor-assets.scenario.test.ts index 877c28511d..c8461c8c74 100644 --- a/packages/eve/test/scenarios/compiled-vendor-assets.scenario.test.ts +++ b/packages/eve/test/scenarios/compiled-vendor-assets.scenario.test.ts @@ -9,7 +9,11 @@ const EVE_PACKAGE_ROOT = fileURLToPath(new URL("../../", import.meta.url)); const COMPILED_VENDOR_ROOT = join(EVE_PACKAGE_ROOT, ".generated", "compiled"); const VENDOR_WARNING_LOG_PATH = join(EVE_PACKAGE_ROOT, "scripts", "vendor-warning-log.mjs"); const require = createRequire(import.meta.url); -const VERCEL_SANDBOX_DIST_ROOT = join( +const VERCEL_SANDBOX_DRIVES_DIST_ROOT = join( + dirname(require.resolve("@vercel/sandbox-drives/package.json")), + "dist", +); +const VERCEL_SANDBOX_STABLE_DIST_ROOT = join( dirname(require.resolve("@vercel/sandbox/package.json")), "dist", ); @@ -183,9 +187,9 @@ describe("compiled vendor assets", () => { expect(vercelWorld).toContain("createWorld"); }); - it("copies the complete @vercel/sandbox declaration tree from the installed package", async () => { + it("copies the complete Drives-capable @vercel/sandbox declaration tree", async () => { const [upstreamEntries, vendoredEntries] = await Promise.all([ - readdir(VERCEL_SANDBOX_DIST_ROOT, { recursive: true }), + readdir(VERCEL_SANDBOX_DRIVES_DIST_ROOT, { recursive: true }), readdir(join(COMPILED_VENDOR_ROOT, "@vercel/sandbox"), { recursive: true }), ]); const upstreamDeclarations = upstreamEntries.filter((entry) => entry.endsWith(".d.ts")).sort(); @@ -197,7 +201,7 @@ describe("compiled vendor assets", () => { expect(vendoredDeclarations).toEqual(upstreamDeclarations); const [upstreamIndex, vendoredIndex, vendoredSandbox, vendoredBaseClient] = await Promise.all([ - readFile(join(VERCEL_SANDBOX_DIST_ROOT, "index.d.ts"), "utf8"), + readFile(join(VERCEL_SANDBOX_DRIVES_DIST_ROOT, "index.d.ts"), "utf8"), readFile(join(COMPILED_VENDOR_ROOT, "@vercel/sandbox/index.d.ts"), "utf8"), readFile(join(COMPILED_VENDOR_ROOT, "@vercel/sandbox/sandbox.d.ts"), "utf8"), readFile(join(COMPILED_VENDOR_ROOT, "@vercel/sandbox/api-client/base-client.d.ts"), "utf8"), @@ -209,6 +213,24 @@ describe("compiled vendor assets", () => { expect(vendoredBaseClient).toContain('import "#compiled/zod/index.js"'); }); + it("copies stable @vercel/sandbox declarations without a second runtime bundle", async () => { + const [upstreamEntries, vendoredEntries] = await Promise.all([ + readdir(VERCEL_SANDBOX_STABLE_DIST_ROOT, { recursive: true }), + readdir(join(COMPILED_VENDOR_ROOT, "@vercel/sandbox-stable"), { recursive: true }), + ]); + const generatedStubNames = new Set(["_async-retry.d.ts", "_workflow-serde.d.ts"]); + const upstreamDeclarations = upstreamEntries.filter((entry) => entry.endsWith(".d.ts")).sort(); + const vendoredDeclarations = vendoredEntries + .filter((entry) => entry.endsWith(".d.ts") && !generatedStubNames.has(entry)) + .sort(); + + expect(vendoredDeclarations).toEqual(upstreamDeclarations); + expect(vendoredEntries.filter((entry) => entry.endsWith(".js"))).toEqual(["index.js"]); + await expect( + readFile(join(COMPILED_VENDOR_ROOT, "@vercel/sandbox-stable/index.js"), "utf8"), + ).resolves.toBe("export {};\n"); + }); + it("copies AI SDK declarations from the installed packages without authored stubs", async () => { const packages = [ { diff --git a/packages/eve/test/scenarios/public-api-portability.scenario.test.ts b/packages/eve/test/scenarios/public-api-portability.scenario.test.ts index 57f4029984..c56c1f0bea 100644 --- a/packages/eve/test/scenarios/public-api-portability.scenario.test.ts +++ b/packages/eve/test/scenarios/public-api-portability.scenario.test.ts @@ -46,7 +46,7 @@ const PORTABILITY_CASES: readonly PortabilityCase[] = [ import { docker } from "eve/sandbox/docker"; import { justbash } from "eve/sandbox/just-bash"; import { microsandbox } from "eve/sandbox/microsandbox"; -import { vercel } from "eve/sandbox/vercel"; +import { Drive, vercel } from "eve/sandbox/vercel"; const fallback = defaultBackend({ docker: { image: "ghcr.io/vercel/eve:latest" }, @@ -54,6 +54,7 @@ const fallback = defaultBackend({ microsandbox: {}, vercel: { resources: { vcpus: 2 } }, }); +void Drive; void docker; void justbash; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61cc596628..b20cf16aff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,9 @@ catalogs: '@vercel/sandbox': specifier: 2.8.0 version: 2.8.0 + '@vercel/sandbox-drives': + specifier: npm:@vercel/sandbox@2.10.0-beta.0 + version: 2.10.0-beta.0 '@workflow/world-postgres': specifier: 5.0.0-beta.33 version: 5.0.0-beta.33 @@ -1151,6 +1154,9 @@ importers: '@vercel/sandbox': specifier: 'catalog:' version: 2.8.0 + '@vercel/sandbox-drives': + specifier: 'catalog:' + version: '@vercel/sandbox@2.10.0-beta.0' '@vercel/sdk': specifier: 1.28.8 version: 1.28.8 @@ -7542,6 +7548,9 @@ packages: '@vercel/rust@1.4.1': resolution: {integrity: sha512-5KNA/WixZ3Ktitg8kBLLb9UTvfZmBxYbJtVOHEBX5JZApIvCeezWHhtnpYbXMpW0wPEOFNBvEMVTRqjZAiAKOQ==} + '@vercel/sandbox@2.10.0-beta.0': + resolution: {integrity: sha512-iHC29Nnq7pB5nF/LfoxS4JuTz53TYWAQGhGX3wohZ6cV6hhixL3y0QogdsH+Y7LmXdMv+i95a3hKspuR7gBsxg==} + '@vercel/sandbox@2.4.0': resolution: {integrity: sha512-pifnr8hex/rgQ3EPyLYHYwHyf0Kwmc4Vbya9kHGk4aERFVGj44P+742S8/SDdt2guMxBriXBOMkMLq1Kaiyyeg==} @@ -21817,6 +21826,23 @@ snapshots: get-port: 5.1.1 smol-toml: 1.5.2 + '@vercel/sandbox@2.10.0-beta.0': + dependencies: + '@vercel/oidc': 3.2.0 + '@workflow/serde': 4.1.0-beta.2 + async-retry: 1.3.3 + jose: 6.2.3 + jsonlines: 0.1.1 + ms: 2.1.3 + picocolors: 1.1.1 + tar-stream: 3.1.7 + undici: 7.29.0 + xdg-app-paths: 5.1.0 + zod: 4.4.3 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + '@vercel/sandbox@2.4.0': dependencies: '@vercel/oidc': 3.2.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index eee57e5e0e..fa49d6df6f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -39,6 +39,7 @@ catalog: "@vercel/connect": "0.4.3" "@vercel/otel": "2.1.3" "@vercel/sandbox": "2.8.0" + "@vercel/sandbox-drives": "npm:@vercel/sandbox@2.10.0-beta.0" "@workflow/world-postgres": "5.0.0-beta.33" ai: "^7.0.58" better-auth: "1.6.26"