Monorepo for Pinecall voice client libraries — real-time WebRTC voice sessions with AI agents.
| Package | Description | npm |
|---|---|---|
@pinecall/voice-core |
Framework-agnostic WebRTC voice session client. VoiceSession class with EventTarget. Works with any framework or vanilla JS. |
|
@pinecall/voice-widget |
Drop-in React voice widget. Animated orb UI with real-time transcript, theme presets, and full customization. |
npm install @pinecall/voice-widgetimport { VoiceWidget } from "@pinecall/voice-widget";
<VoiceWidget agent="mara" name="Mara" preset="midnight" />npm install @pinecall/voice-coreimport { VoiceSession } from "@pinecall/voice-core";
const session = new VoiceSession({ agent: "mara" });
session.subscribe(() => console.log(session.getState()));
await session.connect();pinecall-voice/
├── packages/
│ ├── core/ @pinecall/voice-core — VoiceSession class, types, events
│ └── widget/ @pinecall/voice-widget — React component, hook, presets, CSS
├── examples/
│ └── react/ Demo app with preset switcher
├── .changeset/ Versioning & changelog
├── pnpm-workspace.yaml
└── tsconfig.base.json
# Install everything
pnpm install
# Build all packages
pnpm build
# Typecheck all packages
pnpm typecheck
# Run the example app
pnpm dev:example
# Watch mode (per-package)
cd packages/core && pnpm dev
cd packages/widget && pnpm devUses Changesets for versioning.
# 1. Create a changeset describing your change
pnpm changeset
# 2. Apply versions + generate changelogs
pnpm version-packages
# 3. Build + publish
pnpm releaseThe <VoiceWidget> orb cycles through visual states as the session progresses. Each state has a configurable color (RGB triplet):
| Orb State | CSS Class | Theme Property | Default Color | When |
|---|---|---|---|---|
| Idle | — | orbFrom/orbMid/orbTo |
Pearl gradient | Not connected |
| Connecting | .connecting |
colorConnecting |
245, 158, 11 (amber) |
Establishing WebRTC |
| Active | .active |
colorActive |
76, 175, 80 (green) |
Connected, waiting |
| User speaking | .user-speaking |
colorUserSpeaking |
52, 211, 153 (emerald) |
User talking |
| Agent speaking | .speaking |
colorSpeaking |
248, 113, 113 (rose) |
Agent talking |
| Thinking | .thinking |
colorThinking |
139, 92, 246 (violet) |
Processing |
| Idle warning | .idle-warning |
colorWarning |
255, 160, 0 (orange) |
User silent too long, call will timeout |
When the server emits session.idle_warning, the orb switches to the idle-warning state — a blinking amber/orange animation. This warns the user that the call will end due to inactivity.
// Customize the warning color via theme
<VoiceWidget
agent="mara"
theme={{ colorWarning: "255, 60, 60" }} // red warning
/>The idle warning is cleared when:
- The user starts speaking
- The session disconnects
session.timeoutfires (auto-disconnect)
5 built-in presets: dark (default), midnight, aurora, sunset, light.
<VoiceWidget agent="mara" preset="midnight" />Override individual colors on top of any preset:
<VoiceWidget
agent="mara"
preset="dark"
theme={{
colorActive: "0, 200, 100",
colorWarning: "255, 80, 0",
ringColor: "100, 100, 200",
}}
/>All theme properties accept RGB triplets (e.g. "255, 160, 0") for use with CSS rgba().
Session limits are configured on the agent (server-side SDK) and flow through to the WebRTC widget automatically:
// Server-side (agent.js)
const agent = pc.deploy("my-agent", {
// ...voice, stt, llm config...
session_limits: {
idle_timeout_seconds: 20, // hang up after 20s of silence
idle_warning_seconds: 10, // warn 10s before timeout
max_duration_seconds: 600, // hard cap at 10 minutes
},
});
agent.on("session.idle_warning", (event, call) => {
call.say("Are you still there?");
});The widget receives session.idle_warning via DataChannel and:
- Switches the orb to the idle-warning state (blinking
colorWarning) - On
session.timeout, auto-disconnects and resets to idle
MIT