Skip to content

Commit f0f7f8a

Browse files
authored
clean up code (#1398)
### Description <!-- ✍️ Write a short summary of your work. Screenshots and videos are welcome! --> ### Demo URL <!-- Provide a URL to a live deployment where we can test your PR. If a demo isn't possible feel free to omit this section. --> ### Type of Change - [ ] New Example - [ ] Example updates (Bug fixes, new features, etc.) - [ ] Other (changes to the codebase, but not to examples) ### New Example Checklist - [ ] 🛫 `npm run new-example` was used to create the example - [ ] 📚 The template wasn't used but I carefuly read the [Adding a new example](https://github.com/vercel/examples#adding-a-new-example) steps and implemented them in the example - [ ] 📱 Is it responsive? Are mobile and tablets considered?
1 parent 661c65d commit f0f7f8a

File tree

5 files changed

+201
-499
lines changed

5 files changed

+201
-499
lines changed

guides/sandbox-voice-agent/app/api/sandbox/create/route.ts

Lines changed: 72 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,17 @@
1-
/**
2-
* Sandbox Creation API Endpoint
3-
*
4-
* Creates a Vercel Sandbox on-demand to host the LiveKit voice agent.
5-
* This endpoint:
6-
* 1. Creates a new sandbox with specified runtime
7-
* 2. Clones the agent repository
8-
* 3. Installs dependencies
9-
* 4. Starts the agent process with LiveKit credentials
10-
* 5. Returns sandbox information
11-
*/
121
import { NextResponse } from 'next/server';
132
import { Sandbox } from '@vercel/sandbox';
143
import { sandboxManager } from '@/lib/sandbox-manager';
15-
import type { CreateSandboxRequest, CreateSandboxResponse, SandboxInfo } from '@/types/sandbox';
16-
17-
// Configuration type
18-
type SandboxConfig = {
19-
repoUrl: string;
20-
runtime: string;
21-
timeout: number;
22-
vcpus: number;
23-
githubToken?: string;
24-
};
4+
import type {
5+
CreateSandboxRequest,
6+
CreateSandboxResponse,
7+
SandboxInfo
8+
} from '@/types/sandbox';
259

26-
// Default configuration
2710
const DEFAULT_CONFIG = {
28-
repoUrl:
29-
process.env.AGENT_REPO_URL || 'https://github.com/livekit-examples/agent-starter-python.git',
11+
repoUrl: process.env.AGENT_REPO_URL ||
12+
'https://github.com/livekit-examples/agent-starter-python.git',
3013
runtime: (process.env.AGENT_RUNTIME as string) || 'python3.13',
31-
timeout: parseInt(process.env.SANDBOX_TIMEOUT || '600000', 10), // 10 minutes
14+
timeout: parseInt(process.env.SANDBOX_TIMEOUT || '600000', 10),
3215
vcpus: 4,
3316
};
3417

@@ -37,7 +20,6 @@ export async function POST(req: Request) {
3720
const body: CreateSandboxRequest = await req.json();
3821
const config = { ...DEFAULT_CONFIG, ...body.config };
3922

40-
// Validate required environment variables
4123
if (
4224
!process.env.LIVEKIT_API_KEY ||
4325
!process.env.LIVEKIT_API_SECRET ||
@@ -47,76 +29,62 @@ export async function POST(req: Request) {
4729
{
4830
success: false,
4931
error: 'LiveKit credentials not configured',
50-
details:
51-
'Please set LIVEKIT_API_KEY, LIVEKIT_API_SECRET, and LIVEKIT_URL environment variables',
32+
details: 'Please set LIVEKIT_API_KEY, LIVEKIT_API_SECRET, ' +
33+
'and LIVEKIT_URL environment variables',
5234
} as CreateSandboxResponse,
5335
{ status: 500 }
5436
);
5537
}
5638

57-
console.log(`[Sandbox] Creating sandbox with config:`, {
39+
console.log('[Sandbox] Creating with config:', {
5840
runtime: config.runtime,
5941
repoUrl: config.repoUrl,
6042
timeout: config.timeout,
6143
});
6244

63-
// Generate room name if not provided
64-
const roomName =
65-
body.roomName || `voice_agent_sandbox_${Math.random().toString(36).substring(7)}`;
45+
const roomName = body.roomName ||
46+
`voice_agent_sandbox_${Math.random().toString(36).substring(7)}`;
6647

67-
// Create sandbox info
6848
const sandboxInfo: SandboxInfo = {
69-
id: '', // Will be set after creation
49+
id: '',
7050
status: 'creating',
7151
roomName,
7252
agentName: 'sandbox-agent',
7353
createdAt: Date.now(),
7454
expiresAt: Date.now() + config.timeout,
7555
};
7656

77-
// Register sandbox
7857
sandboxManager.register(sandboxInfo);
7958

8059
try {
81-
// Create the Vercel Sandbox
8260
const sandbox = await Sandbox.create({
8361
source: {
8462
url: config.repoUrl,
8563
type: 'git',
86-
// For private repositories, add authentication
87-
...(config.githubToken
88-
? {
89-
username: 'x-access-token',
90-
password: config.githubToken,
91-
}
92-
: {}),
64+
...(config.githubToken ? {
65+
username: 'x-access-token',
66+
password: config.githubToken,
67+
} : {}),
9368
},
9469
runtime: config.runtime,
9570
resources: { vcpus: config.vcpus },
9671
timeout: config.timeout,
97-
ports: [], // Agent doesn't expose HTTP ports
9872
});
9973

100-
// Get sandbox ID - the SDK uses .sandboxId property
101-
const actualSandboxId = (sandbox as { sandboxId: string }).sandboxId;
102-
sandboxInfo.id = actualSandboxId;
74+
const sandboxId = (sandbox as { sandboxId: string }).sandboxId;
75+
sandboxInfo.id = sandboxId;
10376
sandboxManager.register(sandboxInfo);
10477

105-
console.log(`[Sandbox ${actualSandboxId}] Created successfully`);
78+
console.log(`[Sandbox ${sandboxId}] Created successfully`);
10679

107-
// Return immediately so frontend can start polling for progress
108-
// Continue setup in the background
109-
setupSandbox(sandbox, actualSandboxId, config, sandboxInfo).catch((error) => {
110-
console.error(`[Sandbox ${actualSandboxId}] Setup failed:`, error);
111-
sandboxManager.updateStatus(actualSandboxId, 'failed', error.message);
80+
setupSandbox(sandbox, sandboxId, config, sandboxInfo).catch((error) => {
81+
console.error(`[Sandbox ${sandboxId}] Setup failed:`, error);
82+
sandboxManager.updateStatus(sandboxId, 'failed', error.message);
11283
});
11384

11485
return NextResponse.json({
11586
success: true,
116-
sandbox: {
117-
...sandboxInfo,
118-
id: actualSandboxId,
119-
},
87+
sandbox: { ...sandboxInfo, id: sandboxId },
12088
} as CreateSandboxResponse);
12189
} catch (error) {
12290
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
@@ -150,206 +118,109 @@ export async function POST(req: Request) {
150118
}
151119
}
152120

153-
// Background setup function
121+
type RunCommandFn = (args: {
122+
cmd: string;
123+
args: string[];
124+
stdout?: NodeJS.WriteStream;
125+
stderr?: NodeJS.WriteStream;
126+
detached?: boolean;
127+
env?: Record<string, string>;
128+
}) => Promise<{ exitCode: number }>;
129+
154130
async function setupSandbox(
155-
sandbox: {
156-
runCommand: (args: {
157-
cmd: string;
158-
args: string[];
159-
stdout?: NodeJS.WriteStream;
160-
stderr?: NodeJS.WriteStream;
161-
detached?: boolean;
162-
env?: Record<string, string>;
163-
}) => Promise<{ exitCode: number }>;
164-
},
131+
sandbox: { runCommand: RunCommandFn },
165132
sandboxId: string,
166-
config: SandboxConfig,
133+
config: typeof DEFAULT_CONFIG & { githubToken?: string },
167134
sandboxInfo: SandboxInfo
168135
) {
169-
try {
170-
// Update status to installing
136+
const run = (cmd: string, args: string[], opts: Record<string, unknown> = {}) =>
137+
sandbox.runCommand({ cmd, args, ...opts });
138+
139+
const updateStatus = (status: string, msg: string) =>
171140
sandboxManager.updateStatus(
172141
sandboxId,
173-
'installing',
142+
status as 'installing' | 'starting' | 'ready' | 'failed',
174143
undefined,
175-
'Cloning repository and setting up environment'
144+
msg
176145
);
177146

178-
// First, check if files were cloned correctly
179-
console.log(`[Sandbox ${sandboxId}] Checking repository contents...`);
180-
181-
await sandbox.runCommand({
182-
cmd: 'ls',
183-
args: ['-la'],
184-
stdout: process.stdout,
185-
stderr: process.stderr,
186-
});
147+
try {
148+
updateStatus('installing', 'Setting up environment');
187149

188-
// Check what Python executables are available
189-
console.log(`[Sandbox ${sandboxId}] Checking available Python executables...`);
190-
await sandbox.runCommand({
191-
cmd: 'sh',
192-
args: ['-c', 'which python3 && python3 --version && which pip && pip --version'],
193-
stdout: process.stdout,
194-
stderr: process.stderr,
195-
});
150+
await run('ls', ['-la']);
196151

197-
// Install dependencies based on runtime
198-
let installCmd;
152+
let installCmd: { cmd: string; args: string[] };
199153

200154
if (config.runtime === 'python3.13') {
201-
// Check if pyproject.toml exists (modern Python project)
202-
const hasPyproject = await sandbox.runCommand({
203-
cmd: 'test',
204-
args: ['-f', 'pyproject.toml'],
205-
});
155+
const hasPyproject = await run('test', ['-f', 'pyproject.toml']);
206156

207157
if (hasPyproject.exitCode === 0) {
208-
// Modern Python project with pyproject.toml
209-
console.log(`[Sandbox ${sandboxId}] Detected pyproject.toml, installing with pip...`);
158+
console.log(`[Sandbox ${sandboxId}] Using pyproject.toml`);
210159
installCmd = { cmd: 'pip', args: ['install', '--user', '.'] };
211160
} else {
212-
// Traditional requirements.txt
213-
installCmd = { cmd: 'pip', args: ['install', '--user', '-r', 'requirements.txt'] };
161+
installCmd = {
162+
cmd: 'pip',
163+
args: ['install', '--user', '-r', 'requirements.txt']
164+
};
214165
}
215166
} else {
216-
// Node.js
217167
installCmd = { cmd: 'npm', args: ['install'] };
218168
}
219169

220-
console.log(`[Sandbox ${sandboxId}] Installing dependencies...`);
221-
sandboxManager.updateStatus(
222-
sandboxId,
223-
'installing',
224-
undefined,
225-
'Installing Python dependencies and packages'
226-
);
227-
228-
const install = await sandbox.runCommand({
229-
...installCmd,
230-
stdout: process.stdout,
231-
stderr: process.stderr,
232-
});
170+
updateStatus('installing', 'Installing dependencies');
233171

172+
const install = await run(installCmd.cmd, installCmd.args);
234173
if (install.exitCode !== 0) {
235-
const error = 'Dependency installation failed';
236-
sandboxManager.updateStatus(sandboxId, 'failed', error);
237-
throw new Error(`${error}: Exit code ${install.exitCode}`);
174+
throw new Error(`Dependency installation failed: Exit code ${install.exitCode}`);
238175
}
239176

240177
console.log(`[Sandbox ${sandboxId}] Dependencies installed`);
241178

242-
// Install SSL certificates for Python (fixes certificate verification errors)
243179
if (config.runtime === 'python3.13') {
244-
console.log(`[Sandbox ${sandboxId}] Installing SSL certificates...`);
245-
sandboxManager.updateStatus(
246-
sandboxId,
247-
'installing',
248-
undefined,
249-
'Configuring SSL certificates'
250-
);
251-
252-
const certInstall = await sandbox.runCommand({
253-
cmd: '/vercel/runtimes/python/bin/pip3',
254-
args: ['install', '--upgrade', 'certifi'],
255-
stdout: process.stdout,
256-
stderr: process.stderr,
257-
});
258-
259-
if (certInstall.exitCode !== 0) {
260-
console.log(`[Sandbox ${sandboxId}] SSL certificate installation failed, continuing...`);
261-
} else {
262-
console.log(`[Sandbox ${sandboxId}] SSL certificates installed`);
263-
}
180+
updateStatus('installing', 'Configuring SSL certificates');
181+
await run('/vercel/runtimes/python/bin/pip3', ['install', '--upgrade', 'certifi']);
182+
183+
updateStatus('installing', 'Downloading AI models');
184+
await run('/vercel/runtimes/python/bin/python3', [
185+
'src/agent.py',
186+
'download-files'
187+
]);
264188
}
265189

266-
// Download model files if needed (for Python agents)
267-
if (config.runtime === 'python3.13') {
268-
console.log(`[Sandbox ${sandboxId}] Downloading model files...`);
269-
sandboxManager.updateStatus(
270-
sandboxId,
271-
'installing',
272-
undefined,
273-
'Downloading AI models (this may take a moment)'
274-
);
275-
276-
const downloadCmd = await sandbox.runCommand({
277-
cmd: '/vercel/runtimes/python/bin/python3',
278-
args: ['src/agent.py', 'download-files'],
279-
stdout: process.stdout,
280-
stderr: process.stderr,
281-
});
282-
283-
if (downloadCmd.exitCode !== 0) {
284-
console.log(`[Sandbox ${sandboxId}] Model download failed (non-critical), continuing...`);
285-
} else {
286-
console.log(`[Sandbox ${sandboxId}] Model files downloaded`);
287-
}
288-
}
190+
updateStatus('starting', 'Starting voice agent');
289191

290-
// Update status to starting
291-
sandboxManager.updateStatus(
292-
sandboxId,
293-
'starting',
294-
undefined,
295-
'Starting voice agent and connecting to LiveKit'
296-
);
297-
298-
// Start agent process
299-
let agentCmd;
192+
let agentCmd: { cmd: string; args: string[] };
300193

301194
if (config.runtime === 'python3.13') {
302-
// Check if agent is in src directory or root
303-
const hasSrcAgent = await sandbox.runCommand({
304-
cmd: 'test',
305-
args: ['-f', 'src/agent.py'],
306-
});
307-
308-
// Use the runtime-specific python from /vercel/runtimes/python
195+
const hasSrcAgent = await run('test', ['-f', 'src/agent.py']);
309196
const pythonPath = '/vercel/runtimes/python/bin/python3';
310197

311-
if (hasSrcAgent.exitCode === 0) {
312-
agentCmd = { cmd: pythonPath, args: ['src/agent.py', 'start'] };
313-
} else {
314-
agentCmd = { cmd: pythonPath, args: ['agent.py', 'start'] };
315-
}
198+
agentCmd = hasSrcAgent.exitCode === 0
199+
? { cmd: pythonPath, args: ['src/agent.py', 'start'] }
200+
: { cmd: pythonPath, args: ['agent.py', 'start'] };
316201
} else {
317-
// Node.js
318202
agentCmd = { cmd: 'node', args: ['agent.js'] };
319203
}
320204

321-
console.log(
322-
`[Sandbox ${sandboxId}] Starting agent with command: ${agentCmd.cmd} ${agentCmd.args.join(' ')}...`
323-
);
205+
console.log(`[Sandbox ${sandboxId}] Starting agent: ${agentCmd.cmd} ${agentCmd.args.join(' ')}`);
324206

325207
await sandbox.runCommand({
326208
...agentCmd,
327-
detached: true, // Run in background
209+
detached: true,
328210
env: {
329211
LIVEKIT_API_KEY: process.env.LIVEKIT_API_KEY!,
330212
LIVEKIT_API_SECRET: process.env.LIVEKIT_API_SECRET!,
331213
LIVEKIT_URL: process.env.LIVEKIT_URL!,
332214
LIVEKIT_AGENT_NAME: sandboxInfo.agentName || 'sandbox-agent',
333-
// AI service API keys (required by agent-starter-python)
334215
OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
335216
ASSEMBLYAI_API_KEY: process.env.ASSEMBLYAI_API_KEY || '',
336217
CARTESIA_API_KEY: process.env.CARTESIA_API_KEY || '',
337-
// Add Python user site packages to PATH
338218
PYTHONPATH: '/home/vercel-sandbox/.local/lib/python3.13/site-packages',
339219
PATH: `${process.env.PATH}:/home/vercel-sandbox/.local/bin`,
340220
},
341-
stdout: process.stdout,
342-
stderr: process.stderr,
343221
});
344222

345-
// Update status to ready
346-
sandboxManager.updateStatus(
347-
sandboxId,
348-
'ready',
349-
undefined,
350-
'Agent is ready and waiting for connection'
351-
);
352-
223+
updateStatus('ready', 'Agent is ready');
353224
console.log(`[Sandbox ${sandboxId}] Agent started successfully`);
354225
} catch (error) {
355226
const errorMessage = error instanceof Error ? error.message : 'Unknown error';

0 commit comments

Comments
 (0)