Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions guides/sandbox-voice-agent/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# The OIDC token is only needed for local development. When deployed to Vercel, its automatically available. Use the Vercel CLI to pull it locally
VERCEL_OIDC_TOKEN=<your_vercel_OIDC_token>

# LiveKit credentials - Required for voice agent functionality
LIVEKIT_API_KEY=<your_api_key>
LIVEKIT_API_SECRET=<your_api_secret>
LIVEKIT_URL=wss://<project-subdomain>.livekit.cloud

# Enable Vercel Sandbox mode
NEXT_PUBLIC_USE_SANDBOX=true

# Agent Configuration
AGENT_REPO_URL=https://github.com/livekit-examples/agent-starter-python.git
AGENT_RUNTIME=python3.13
SANDBOX_TIMEOUT=600000
3 changes: 3 additions & 0 deletions guides/sandbox-voice-agent/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript", "prettier"]
}
43 changes: 43 additions & 0 deletions guides/sandbox-voice-agent/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files (can opt-in for committing if needed)
.env*
!.env.example

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
.env*.local
9 changes: 9 additions & 0 deletions guides/sandbox-voice-agent/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dist/
docs/
node_modules/
pnpm-lock.yaml
.next/
.env*



19 changes: 19 additions & 0 deletions guides/sandbox-voice-agent/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"singleQuote": true,
"trailingComma": "es5",
"semi": true,
"tabWidth": 2,
"printWidth": 100,
"importOrder": [
"^react",
"^next",
"^next/(.*)$",
"<THIRD_PARTY_MODULES>",
"^@[^/](.*)$",
"^@/(.*)$",
"^[./]"
],
"importOrderSeparation": false,
"importOrderSortSpecifiers": true,
"plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"]
}
21 changes: 21 additions & 0 deletions guides/sandbox-voice-agent/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 LiveKit, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
122 changes: 122 additions & 0 deletions guides/sandbox-voice-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Voice Agent with Vercel Sandbox

A Next.js application that creates on-demand voice AI agents using Vercel Sandbox. Each user session spawns an isolated Python agent environment with Vercel sandbox that handles real-time voice conversations through LiveKit cloud.

## How to Use

You can choose from one of the following two methods to use this repository:

## One-Click Deploy

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples):

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/guides/sandbox-voice-agent&project-name=sandbox-voice-agent&repository-name=sandbox-voice-agent&env=LIVEKIT_API_KEY,LIVEKIT_API_SECRET,LIVEKIT_URL,NEXT_PUBLIC_USE_SANDBOX,AGENT_REPO_URL,AGENT_RUNTIME,SANDBOX_TIMEOUT&envDefaults=%7B%22NEXT_PUBLIC_USE_SANDBOX%22%3A%22true%22%2C%22AGENT_REPO_URL%22%3A%22https%3A%2F%2Fgithub.com%2Flivekit-examples%2Fagent-starter-python.git%22%2C%22AGENT_RUNTIME%22%3A%22python3.13%22%2C%22SANDBOX_TIMEOUT%22%3A%22600000%22%7D)


### Clone and Deploy

You can clone just this directory using:

```bash
npx degit vercel/examples/guides/sandbox-voice-agent sandbox-voice-agent
cd express-weather-mcp-server
```

## How the application works

These steps describe the interaction flow between the user and the Live voice agent in Vercel sandbox:

1. A user clicks **Start call** in their browser
2. The Next.js frontend calls `/api/sandbox/create`
3. The Next.js backend initiates the creation of a Vercel sandbox
4. The sandbox clones the [Livekit python agent repository](https://github.com/livekit-examples/agent-starter-python), installs dependencies, and starts the agent
5. The agent connects to LiveKit Cloud and waits in the room
6. The frontend polls `/api/sandbox/status` for progress
7. When ready, the frontend connects the user to the LiveKit room
8. The user and agent communicate through LiveKit Cloud
9. After 10 minutes (configurable), the sandbox expires automatically


## Prerequisites

- Node.js 18+
- Vercel account with Sandbox API access
- LiveKit Cloud account ([sign up](https://cloud.livekit.io))


## Configure Environment Variables

For `VERCEL_OIDC_TOKEN` for Vercel sandbox creation, link your application from the terminal to a new project on Vercel:

```
pnpm install -g vercel
vercel link
vercel env pull
```

Update the created `.env.local` with the remaining environment variables from `.env.example`

For `LIVEKIT_API_KEY` , `LIVEKIT_API_SECRET` and `LIVEKIT_URL` , create a [Livekit cloud account](https://cloud.livekit.io/). Then, create a free project to get these values.


### 4. Run the Development Server

```bash
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) in your browser and click **Start Call**

### 5. Deploy to Vercel

Click the button below to deploy your own instance:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/livekit-examples/agent-starter-react)

After deployment:

1. Add all environment variables from `.env.local` to your Vercel project settings
2. Ensure your Vercel account has Sandbox API access enabled
3. The app will automatically use Vercel's OIDC token in production


## Project Structure

```
sandbox-voice-agent/
├── app/
│ ├── api/
│ │ └── sandbox/
│ │ ├── create/route.ts # Creates Vercel Sandboxes
│ │ ├── status/route.ts # Polls sandbox status
│ │ └── stop/route.ts # Stops sandboxes
│ ├── (app)/
│ └── layout.tsx
├── components/
│ ├── app/
│ │ ├── app.tsx # Main app component
│ │ ├── welcome-view.tsx # Sandbox creation UI
│ │ ├── session-view.tsx # Voice conversation UI
│ │ └── sandbox-loading.tsx # Progress indicator
│ └── livekit/ # LiveKit components
├── hooks/
│ └── useSandboxCreation.ts # Sandbox lifecycle hook
├── lib/
│ ├── sandbox-manager.ts # Server-side sandbox tracking
│ └── utils.ts
├── types/
│ └── sandbox.ts # TypeScript definitions
├── app-config.ts # App configuration
└── package.json
```

## License

This project is open source under the Apache 2.0 license.

## Resources

- [LiveKit Agents Documentation](https://docs.livekit.io/agents)
- [Vercel Sandbox Documentation](https://vercel.com/docs/sandbox)
- [LiveKit JavaScript SDK](https://github.com/livekit/client-sdk-js)
- [Agent Starter Python](https://github.com/livekit-examples/agent-starter-python)
54 changes: 54 additions & 0 deletions guides/sandbox-voice-agent/app-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export interface AppConfig {
pageTitle: string;
pageDescription: string;
companyName: string;

supportsChatInput: boolean;
supportsVideoInput: boolean;
supportsScreenShare: boolean;
isPreConnectBufferEnabled: boolean;

logo: string;
startButtonText: string;
accent?: string;
logoDark?: string;
accentDark?: string;

// for LiveKit Cloud Sandbox (different from Vercel Sandbox)
sandboxId?: string;
agentName?: string;

// Vercel Sandbox Configuration (On-Demand Agent Hosting)
useVercelSandbox?: boolean;
sandboxAgentRepo?: string;
sandboxRuntime?: 'python3.13' | 'node24' | 'node22';
sandboxLoadingText?: string;
}

export const APP_CONFIG_DEFAULTS: AppConfig = {
companyName: 'Vercel',
pageTitle: 'On-Demand Voice Agent',
pageDescription: 'A voice agent with on-demand sandbox hosting',

supportsChatInput: true,
supportsVideoInput: true,
supportsScreenShare: true,
isPreConnectBufferEnabled: true,

logo: '/lk-logo.svg',
accent: '#000000',
logoDark: '/lk-logo-dark.svg',
accentDark: '#ffffff',
startButtonText: 'Start call',

// for LiveKit Cloud Sandbox (different from Vercel Sandbox)
sandboxId: undefined,
agentName: undefined,

// Vercel Sandbox Configuration
useVercelSandbox: process.env.NEXT_PUBLIC_USE_SANDBOX === 'true',
sandboxAgentRepo:
process.env.AGENT_REPO_URL || 'https://github.com/livekit-examples/agent-starter-python.git',
sandboxRuntime: (process.env.AGENT_RUNTIME as 'python3.13' | 'node24' | 'node22') || 'python3.13',
sandboxLoadingText: 'Preparing your voice agent...',
};
46 changes: 46 additions & 0 deletions guides/sandbox-voice-agent/app/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { headers } from 'next/headers';
import { getAppConfig } from '@/lib/utils';

interface LayoutProps {
children: React.ReactNode;
}

export default async function Layout({ children }: LayoutProps) {
const hdrs = await headers();
const { companyName, logo, logoDark } = await getAppConfig(hdrs);

return (
<>
<header className="fixed top-0 left-0 z-50 hidden w-full flex-row justify-between p-6 md:flex">
<a
target="_blank"
rel="noopener noreferrer"
href="https://livekit.io"
className="scale-100 transition-transform duration-300 hover:scale-110"
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={logo} alt={`${companyName} Logo`} className="block size-6 dark:hidden" />
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={logoDark ?? logo}
alt={`${companyName} Logo`}
className="hidden size-6 dark:block"
/>
</a>
<span className="text-foreground font-mono text-xs font-bold tracking-wider uppercase">
Built with{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://docs.livekit.io/agents"
className="underline underline-offset-4"
>
LiveKit Agents
</a>
</span>
</header>

{children}
</>
);
}
Loading