Skip to content

Commit 7b3af1d

Browse files
sweetmantechsidneyswiftclaude
authored
feat: add POST /api/sandbox endpoint (#155)
* feat: add Google Drive and Google Docs to enabled toolkits (#153) (#154) * feat: add Google Drive and Google Docs to enabled toolkits - Add googledrive and googledocs to ENABLED_TOOLKITS array - Enables Tool Router access to Google Drive and Google Docs tools * docs: update JSDoc to mention all Google integrations * feat: add POST /api/sandbox endpoint Create ephemeral sandbox environments using Vercel Sandbox SDK. - Add route handler with CORS and API key authentication - Add createSandbox function using @vercel/sandbox - Returns sandboxId, status, timeout, createdAt Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add snapshotting status to SandboxCreatedResponse The Vercel Sandbox SDK includes "snapshotting" as a valid status. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: use Sandbox SDK types for SandboxCreatedResponse DRY - reference types from @vercel/sandbox instead of duplicating. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Sidney Swift <158200036+sidneyswift@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3cb2545 commit 7b3af1d

File tree

5 files changed

+225
-2
lines changed

5 files changed

+225
-2
lines changed

app/api/sandbox/route.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
3+
import { createSandboxPostHandler } from "@/lib/sandbox/createSandboxPostHandler";
4+
5+
/**
6+
* OPTIONS handler for CORS preflight requests.
7+
*
8+
* @returns A NextResponse with CORS headers.
9+
*/
10+
export async function OPTIONS() {
11+
return new NextResponse(null, {
12+
status: 200,
13+
headers: getCorsHeaders(),
14+
});
15+
}
16+
17+
/**
18+
* POST /api/sandbox
19+
*
20+
* Creates a new ephemeral sandbox environment.
21+
* Sandboxes are isolated Linux microVMs that can be used to evaluate
22+
* account-generated code, run AI agent output safely, or execute reproducible tasks.
23+
*
24+
* Request:
25+
* - No request body required
26+
* - Authentication via x-api-key header
27+
*
28+
* Response:
29+
* - 200: { sandboxId: string, status: string, timeout: number, createdAt: string }
30+
* - 400: { error: "Failed to create sandbox" }
31+
* - 401: { status: "error", error: "x-api-key header required" or "Invalid API key" }
32+
*
33+
* @param request - The request object
34+
* @returns A NextResponse with the created sandbox data or error
35+
*/
36+
export async function POST(request: NextRequest) {
37+
return createSandboxPostHandler(request);
38+
}

lib/sandbox/createSandbox.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Sandbox } from "@vercel/sandbox";
2+
3+
/**
4+
* Response from creating a sandbox.
5+
* Uses Sandbox class types from @vercel/sandbox SDK.
6+
*/
7+
export interface SandboxCreatedResponse {
8+
sandboxId: Sandbox["sandboxId"];
9+
status: Sandbox["status"];
10+
timeout: Sandbox["timeout"];
11+
createdAt: string;
12+
}
13+
14+
/**
15+
* Creates a new ephemeral sandbox environment using Vercel Sandbox SDK.
16+
*
17+
* @returns The created sandbox details
18+
* @throws Error if sandbox creation fails
19+
*/
20+
export async function createSandbox(): Promise<SandboxCreatedResponse> {
21+
const sandbox = await Sandbox.create();
22+
23+
return {
24+
sandboxId: sandbox.sandboxId,
25+
status: sandbox.status,
26+
timeout: sandbox.timeout,
27+
createdAt: sandbox.createdAt.toISOString(),
28+
};
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { getCorsHeaders } from "@/lib/networking/getCorsHeaders";
3+
import { getApiKeyAccountId } from "@/lib/auth/getApiKeyAccountId";
4+
import { createSandbox } from "@/lib/sandbox/createSandbox";
5+
6+
/**
7+
* Handler for POST /api/sandbox.
8+
*
9+
* Creates a new ephemeral sandbox environment. Requires authentication via x-api-key header.
10+
* No request body is required.
11+
*
12+
* @param request - The request object
13+
* @returns A NextResponse with sandbox data or error
14+
*/
15+
export async function createSandboxPostHandler(request: NextRequest): Promise<NextResponse> {
16+
const accountIdOrError = await getApiKeyAccountId(request);
17+
if (accountIdOrError instanceof NextResponse) {
18+
return accountIdOrError;
19+
}
20+
21+
try {
22+
const sandbox = await createSandbox();
23+
24+
return NextResponse.json(sandbox, { status: 200, headers: getCorsHeaders() });
25+
} catch (error) {
26+
const message = error instanceof Error ? error.message : "Failed to create sandbox";
27+
return NextResponse.json({ error: message }, { status: 400, headers: getCorsHeaders() });
28+
}
29+
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
},
1818
"dependencies": {
1919
"@ai-sdk/anthropic": "^3.0.13",
20-
"autoevals": "^0.0.129",
21-
"braintrust": "^0.4.9",
2220
"@ai-sdk/gateway": "^3.0.14",
2321
"@ai-sdk/google": "^3.0.8",
2422
"@ai-sdk/mcp": "^0.0.12",
@@ -31,9 +29,12 @@
3129
"@privy-io/node": "^0.6.2",
3230
"@supabase/supabase-js": "^2.86.0",
3331
"@trigger.dev/sdk": "^4.2.0",
32+
"@vercel/sandbox": "^1.3.1",
3433
"ai": "6.0.0-beta.122",
3534
"apify-client": "^2.20.0",
3635
"arweave": "^1.15.7",
36+
"autoevals": "^0.0.129",
37+
"braintrust": "^0.4.9",
3738
"bullmq": "^5.65.1",
3839
"googleapis": "^168.0.0",
3940
"ioredis": "^5.8.2",

0 commit comments

Comments
 (0)