Skip to content

Commit 5f52821

Browse files
marc-vercelSchnizLukeSheardclaudecramforce
authored
feat(named-sandboxes) rebase latest commits from main (#138)
Rebasing `named-sandboxes`. Commits that I am moving: 1. cf13a34 — refactor(sdk): build @vercel/sandbox with tsdown dual outputs (#84) 2. 772989c — Support "use workflow" serialization for Sandbox and Command (#72) 3. cc74dbf — fix(sandbox): read package.json with fs instead of ESM import (#119) 4. a6b8ce9 — feat(skill): update beta documentation for default snapshot expiration (#125) 5. 184cd42 — patch(vercel-sandbox): count length by bytes and not ASCII for binaries (#127) 6. 451c42e — feat(sandbox): accept string and Uint8Array in writeFiles content (#128) 7. ad52dec — Version Packages (#122) 8. b91b9e4 — fix(sandbox): initialize API client in Command before reading output (#130) 9. 28237b8 — refactor(workflow-code-runner): inline Sandbox calls in workflow function (#129) 10. 0786e18 — Version Packages (#131) 11. 9555162 — fix(sandbox): handle abort signal and early stream close in runCommand (#135) 12. db4e5f3 — Version Packages (#137) I had to resolve multiple merge conflicts, specially with the commits 1 and 2. I've also added some tests for them because they were touching `sandbox.ts` for serializing and deserializing, and in this branch we moved most of the logic to `session.ts`. --------- Co-authored-by: Gal Schlezinger <gal@spitfire.co.il> Co-authored-by: Luke Phillips-Sheard <luke.phillips-sheard@vercel.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Luke PS <LukeSheard@users.noreply.github.com> Co-authored-by: Malte Ubl <cramforce@users.noreply.github.com> Co-authored-by: Pranay Prakash <pranay.gp@gmail.com> Co-authored-by: Nathan Rajlich <n@n8.io> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Phil Z <pbzona@users.noreply.github.com>
1 parent 90dfaa5 commit 5f52821

67 files changed

Lines changed: 7349 additions & 533 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/bumpy-keys-try.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@vercel/sandbox": minor
3+
"sandbox": minor
4+
---
5+
6+
Rebase from main

.changeset/pre.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
"@vercel/pty-tunnel": "2.0.3",
1313
"@vercel/pty-tunnel-server": "0.0.2",
1414
"sandbox": "2.5.3",
15-
"@vercel/sandbox": "1.7.1"
15+
"@vercel/sandbox": "1.7.1",
16+
"workflow-code-runner": "0.1.3"
1617
},
1718
"changesets": [
19+
"bumpy-keys-try",
1820
"clean-jeans-build",
1921
"cold-falcons-read",
2022
"common-corners-leave",

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ const sandbox = await Sandbox.create({
142142
});
143143
```
144144

145+
## Workflow DevKit integration
146+
147+
`Sandbox` and `CommandFinished` support serialization with the
148+
[Workflow DevKit](https://vercel.com/docs/workflow). When a sandbox instance
149+
crosses a step boundary the SDK serializes sandbox metadata and routes, then
150+
rehydrates synchronously from that snapshot. Deserialized instances lazily
151+
recreate an API client using OIDC or environment credentials when needed.
152+
145153
## Limitations
146154

147155
- Max resources: 8 vCPUs. You will get 2048 MB of memory per vCPU.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Vercel Sandbox credentials
2+
VERCEL_TEAM_ID=
3+
VERCEL_PROJECT_ID=
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.next
2+
/next-env.d.ts
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# workflow-code-runner
2+
3+
## 0.1.4-beta.0
4+
5+
### Patch Changes
6+
7+
- Updated dependencies []:
8+
- @vercel/sandbox@2.0.0-beta.12
9+
10+
## 0.1.3
11+
12+
### Patch Changes
13+
14+
- Updated dependencies [[`9555162f33690dfa18530aeca93af05188ebd2ed`](https://github.com/vercel/sandbox/commit/9555162f33690dfa18530aeca93af05188ebd2ed)]:
15+
- @vercel/sandbox@1.9.3
16+
17+
## 0.1.2
18+
19+
### Patch Changes
20+
21+
- Updated dependencies [[`b91b9e49fb7d2c5a4b601c07c125e6e5a2c43441`](https://github.com/vercel/sandbox/commit/b91b9e49fb7d2c5a4b601c07c125e6e5a2c43441)]:
22+
- @vercel/sandbox@1.9.2
23+
24+
## 0.1.1
25+
26+
### Patch Changes
27+
28+
- Updated dependencies [[`cf13a34221c2b83c25c73d94929d05e0a697aecf`](https://github.com/vercel/sandbox/commit/cf13a34221c2b83c25c73d94929d05e0a697aecf), [`772989c59a3c27efa98153cdc54b6e35c1c15eae`](https://github.com/vercel/sandbox/commit/772989c59a3c27efa98153cdc54b6e35c1c15eae), [`184cd42d8d3b1ea1df354529cb6ba103a33e18d3`](https://github.com/vercel/sandbox/commit/184cd42d8d3b1ea1df354529cb6ba103a33e18d3), [`451c42efb94ab9c9dc330b4742071ac01008044d`](https://github.com/vercel/sandbox/commit/451c42efb94ab9c9dc330b4742071ac01008044d)]:
29+
- @vercel/sandbox@1.9.1
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { start, getRun } from "workflow/api";
2+
import { runCode } from "@/workflows/code-runner";
3+
import { NextResponse } from "next/server";
4+
5+
export async function POST(request: Request) {
6+
const { prompt } = await request.json();
7+
8+
const run = await start(runCode, [prompt]);
9+
10+
return NextResponse.json({ runId: run.runId });
11+
}
12+
13+
export async function GET(request: Request) {
14+
const { searchParams } = new URL(request.url);
15+
const runId = searchParams.get("runId");
16+
17+
if (!runId) {
18+
return NextResponse.json({ error: "Missing runId" }, { status: 400 });
19+
}
20+
21+
const run = getRun(runId);
22+
const status = await run.status;
23+
24+
if (status === "completed") {
25+
const output = await run.returnValue;
26+
return NextResponse.json({ status, output });
27+
}
28+
29+
return NextResponse.json({ status });
30+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@import "tailwindcss";
2+
3+
:root {
4+
--background: #0a0a0a;
5+
--foreground: #ededed;
6+
}
7+
8+
@theme inline {
9+
--color-background: var(--background);
10+
--color-foreground: var(--foreground);
11+
--font-sans: var(--font-geist-sans);
12+
--font-mono: var(--font-geist-mono);
13+
}
14+
15+
body {
16+
background: var(--background);
17+
color: var(--foreground);
18+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Metadata } from "next";
2+
import { Geist, Geist_Mono } from "next/font/google";
3+
import "./globals.css";
4+
5+
const geistSans = Geist({
6+
variable: "--font-geist-sans",
7+
subsets: ["latin"],
8+
});
9+
10+
const geistMono = Geist_Mono({
11+
variable: "--font-geist-mono",
12+
subsets: ["latin"],
13+
});
14+
15+
export const metadata: Metadata = {
16+
title: "Sandbox Code Runner",
17+
description:
18+
"AI-powered code generation and execution with Vercel Workflow + Sandbox",
19+
};
20+
21+
export default function RootLayout({
22+
children,
23+
}: Readonly<{
24+
children: React.ReactNode;
25+
}>) {
26+
return (
27+
<html lang="en">
28+
<body
29+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
30+
>
31+
{children}
32+
</body>
33+
</html>
34+
);
35+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
5+
export default function Home() {
6+
const [prompt, setPrompt] = useState("");
7+
const [result, setResult] = useState<{
8+
code: string;
9+
stdout: string;
10+
stderr: string;
11+
iterations: number;
12+
} | null>(null);
13+
const [status, setStatus] = useState<"idle" | "running" | "done" | "error">(
14+
"idle",
15+
);
16+
const [error, setError] = useState("");
17+
18+
async function handleSubmit(e: React.FormEvent) {
19+
e.preventDefault();
20+
if (!prompt.trim()) return;
21+
22+
setStatus("running");
23+
setResult(null);
24+
setError("");
25+
26+
try {
27+
const res = await fetch("/api/run", {
28+
method: "POST",
29+
headers: { "Content-Type": "application/json" },
30+
body: JSON.stringify({ prompt }),
31+
});
32+
33+
if (!res.ok) {
34+
throw new Error(await res.text());
35+
}
36+
37+
const data = await res.json();
38+
39+
const pollResult = async (runId: string) => {
40+
const poll = await fetch(`/api/run?runId=${runId}`);
41+
const pollData = await poll.json();
42+
43+
if (pollData.status === "completed") {
44+
setResult(pollData.output);
45+
setStatus("done");
46+
} else if (pollData.status === "failed") {
47+
setError(pollData.error ?? "Workflow failed");
48+
setStatus("error");
49+
} else {
50+
setTimeout(() => pollResult(runId), 1000);
51+
}
52+
};
53+
54+
await pollResult(data.runId);
55+
} catch (err) {
56+
setError(err instanceof Error ? err.message : "Unknown error");
57+
setStatus("error");
58+
}
59+
}
60+
61+
return (
62+
<div className="flex min-h-screen items-center justify-center font-sans">
63+
<main className="flex w-full max-w-2xl flex-col gap-8 px-6 py-16">
64+
<div className="flex flex-col gap-2">
65+
<h1 className="text-2xl font-semibold tracking-tight">
66+
Sandbox Code Runner
67+
</h1>
68+
<p className="text-sm text-zinc-400">
69+
Describe a program and AI will generate and execute it in a sandbox.
70+
If it fails, it automatically retries with the error context.
71+
</p>
72+
</div>
73+
74+
<form onSubmit={handleSubmit} className="flex flex-col gap-3">
75+
<textarea
76+
value={prompt}
77+
onChange={(e) => setPrompt(e.target.value)}
78+
placeholder='e.g. "Write a program that computes the first 20 fibonacci numbers and prints them as a formatted table"'
79+
rows={3}
80+
className="w-full resize-none rounded-lg border border-zinc-800 bg-zinc-900 px-4 py-3 text-sm text-zinc-100 placeholder-zinc-600 outline-none focus:border-zinc-600"
81+
/>
82+
<button
83+
type="submit"
84+
disabled={status === "running" || !prompt.trim()}
85+
className="self-start rounded-lg bg-white px-4 py-2 text-sm font-medium text-black transition-colors hover:bg-zinc-200 disabled:cursor-not-allowed disabled:opacity-50"
86+
>
87+
{status === "running" ? "Running..." : "Run"}
88+
</button>
89+
</form>
90+
91+
{status === "running" && (
92+
<div className="flex items-center gap-2 text-sm text-zinc-400">
93+
<div className="h-4 w-4 animate-spin rounded-full border-2 border-zinc-600 border-t-zinc-300" />
94+
Generating and executing code in sandbox...
95+
</div>
96+
)}
97+
98+
{error && (
99+
<div className="rounded-lg border border-red-900 bg-red-950/50 px-4 py-3 text-sm text-red-400">
100+
{error}
101+
</div>
102+
)}
103+
104+
{result && (
105+
<div className="flex flex-col gap-4">
106+
<div className="flex flex-col gap-2">
107+
<div className="flex items-center justify-between">
108+
<h2 className="text-sm font-medium text-zinc-300">
109+
Generated Code
110+
</h2>
111+
{result.iterations > 1 && (
112+
<span className="text-xs text-zinc-500">
113+
{result.iterations} attempt
114+
{result.iterations > 1 ? "s" : ""}
115+
</span>
116+
)}
117+
</div>
118+
<pre className="overflow-x-auto rounded-lg border border-zinc-800 bg-zinc-900 p-4 font-mono text-sm text-zinc-300">
119+
{result.code}
120+
</pre>
121+
</div>
122+
123+
{result.stdout && (
124+
<div className="flex flex-col gap-2">
125+
<h2 className="text-sm font-medium text-zinc-300">Output</h2>
126+
<pre className="overflow-x-auto rounded-lg border border-zinc-800 bg-zinc-900 p-4 font-mono text-sm text-green-400">
127+
{result.stdout}
128+
</pre>
129+
</div>
130+
)}
131+
132+
{result.stderr && (
133+
<div className="flex flex-col gap-2">
134+
<h2 className="text-sm font-medium text-zinc-300">Stderr</h2>
135+
<pre className="overflow-x-auto rounded-lg border border-zinc-800 bg-zinc-900 p-4 font-mono text-sm text-yellow-400">
136+
{result.stderr}
137+
</pre>
138+
</div>
139+
)}
140+
</div>
141+
)}
142+
</main>
143+
</div>
144+
);
145+
}

0 commit comments

Comments
 (0)