https://github.com/user-attachments/assets/5bbe3366-c520-4032-b7f1-09a76903b8bc
A realtime voice agent demo built with Expo and Expo Router, powered by xAI's Grok Voice Agent API. Talk to Grok in real time — spoken turns stream into a live chat UI — from a single codebase that runs on iOS, Android, and web. Ships with iOS 26 Liquid Glass support and a responsive web UI.
react-native-audio-api on iOS/Android, behind a shared coreexpo-glass-effectreact-native-keyboard-controller@legendapp/list and Reanimated-powered scroll-to-bottom button| Layer | Technology |
|---|---|
| Framework | Expo SDK 56 |
| Navigation | Expo Router (file-based) with typed routes, Legend List for virtualized chat |
| Styling | Tailwind CSS v4 via Uniwind + tailwind-merge |
| Native UI | @expo/ui (SwiftUI), expo-symbols, expo-haptics, expo-glass-effect |
| Web UI | Radix UI (context menu, dropdown menu, tooltips), Lucide icons |
| Markdown | Custom AST renderer with mdast-util-from-markdown + react-syntax-highlighter |
| Animations | react-native-reanimated, react-native-gesture-handler |
Copy .env.example to .env and fill in the values:
cp .env.example .env
| Variable | Description |
|---|---|
XAI_API_KEY |
Your xAI API key. Used by the Grok voice agent route (app/api/voice-session+api.ts) to mint realtime session tokens. |
EXPO_PUBLIC_MOCK_AI |
Set to 1 to use mock streaming responses instead of calling the API. Useful for UI development without an API key. |
Tap the audio-wave icon in the chat composer to talk to Grok's realtime Voice Agent API without leaving the conversation. The composer turns into a live voice bar (persona orb + waveform + stop) and the spoken turns stream into the same message list as typed messages. It streams microphone audio (24kHz PCM16) over a WebSocket and plays back Grok's spoken replies; server-side Voice Activity Detection handles turn-taking. The sliders icon opens Voice Settings to pick the voice and personality (both applied to the session on the next start).
The long-lived XAI_API_KEY never reaches the client: it fetches a short-lived
ephemeral token from /api/voice-session, then opens the realtime WebSocket
with that credential.
The audio layer is platform-split behind a shared core (grok-voice-core.ts):
grok-voice.ts) — browser Web Audio API (getUserMedia + an
AudioWorklet for capture, AudioContext scheduling for playback). The token
is passed via the WebSocket subprotocol, since browsers can't set headers.grok-voice.native.ts) — react-native-audio-api
(AudioRecorder for capture, AudioBufferQueueSourceNode for playback). The
token goes in an Authorization header. iOS uses the voiceChat audio-session
mode so hardware echo cancellation keeps the agent from hearing itself.Native requires a rebuild after install (
npx expo run:ios/run:android) for thereact-native-audio-apiconfig plugin (mic permission + audio session) to take effect. Echo cancellation needs a physical device.
# Install dependencies
bun install
# Start the dev server
bun start
# Run on a specific platform
bun run ios
bun run android
bun run web
Requires Bun and the Expo CLI. For iOS, you'll need Xcode and a simulator or device.
Edit global.css to change the design tokens. Colors use OKLCH for perceptual uniformity across light and dark modes. The @theme block maps CSS variables to Tailwind classes:
--app-background -> bg-background
--app-foreground -> text-foreground
--app-muted -> bg-muted
--app-border -> border-border
/* etc. */
The typed-chat backend streams from xAI's Grok models via @ai-sdk/xai in the server-side chat API route (app/api/chat+api.ts). Swap the model id or provider there to point at a different backend -- the streaming architecture (createStreamingStore + throttled token callback) is ready for any AI SDK provider.
I recommend using Convex, which you can setup in a single command:
npx eas-cli@latest integrations:convex:connect
Pair this with better-auth for authentication. Convex also has support for Expo Notifications: Learn more.
This template was made for https://agent.expo.dev and is made freely available under the MIT license.
$ claude mcp add grok-voice-demo \
-- python -m otcore.mcp_server <graph>