@pinecall/skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/build.mjs +204 -0
- package/package.json +29 -0
- package/skills/pinecall-concepts/SKILL.md +41 -0
- package/skills/pinecall-concepts/references/concepts/agents-and-channels.md +155 -0
- package/skills/pinecall-concepts/references/concepts/deployment-topologies.md +120 -0
- package/skills/pinecall-concepts/references/concepts/hot-reload.md +119 -0
- package/skills/pinecall-concepts/references/concepts/philosophy.md +100 -0
- package/skills/pinecall-concepts/references/concepts/server-vs-client-llm.md +119 -0
- package/skills/pinecall-examples/SKILL.md +59 -0
- package/skills/pinecall-examples/references/examples/browser-widget.md +206 -0
- package/skills/pinecall-examples/references/examples/chat-bot.md +184 -0
- package/skills/pinecall-examples/references/examples/headless-agent.md +121 -0
- package/skills/pinecall-examples/references/examples/index.md +183 -0
- package/skills/pinecall-examples/references/examples/multi-channel-bot.md +173 -0
- package/skills/pinecall-examples/references/examples/outbound-dispatch.md +109 -0
- package/skills/pinecall-examples/references/examples/turn-detection.md +150 -0
- package/skills/pinecall-guides/SKILL.md +68 -0
- package/skills/pinecall-guides/references/guides/call-ringing.md +149 -0
- package/skills/pinecall-guides/references/guides/conversation-history.md +377 -0
- package/skills/pinecall-guides/references/guides/dev-mode.md +130 -0
- package/skills/pinecall-guides/references/guides/events.md +677 -0
- package/skills/pinecall-guides/references/guides/human-takeover.md +184 -0
- package/skills/pinecall-guides/references/guides/inbound-voice.md +201 -0
- package/skills/pinecall-guides/references/guides/knowledge-bases.md +166 -0
- package/skills/pinecall-guides/references/guides/live-listening.md +199 -0
- package/skills/pinecall-guides/references/guides/multi-tenant.md +158 -0
- package/skills/pinecall-guides/references/guides/outbound-calls.md +279 -0
- package/skills/pinecall-guides/references/guides/sse-streaming.md +207 -0
- package/skills/pinecall-guides/references/guides/testing-agents.md +272 -0
- package/skills/pinecall-guides/references/guides/tools-and-functions.md +254 -0
- package/skills/pinecall-guides/references/guides/webrtc-browser.md +200 -0
- package/skills/pinecall-guides/references/guides/whatsapp.md +370 -0
- package/skills/pinecall-guides/references/guides/ws-streaming.md +235 -0
- package/skills/pinecall-quickstart/SKILL.md +54 -0
- package/skills/pinecall-quickstart/references/index.md +123 -0
- package/skills/pinecall-quickstart/references/quickstart.md +185 -0
- package/skills/pinecall-reference/SKILL.md +43 -0
- package/skills/pinecall-reference/references/reference/cli.md +578 -0
- package/skills/pinecall-reference/references/reference/events.md +366 -0
- package/skills/pinecall-reference/references/reference/llm-providers.md +263 -0
- package/skills/pinecall-reference/references/reference/rest-api.md +122 -0
- package/skills/pinecall-reference/references/reference/session-limits.md +119 -0
- package/skills/pinecall-reference/references/reference/stt-providers.md +174 -0
- package/skills/pinecall-reference/references/reference/tts-providers.md +149 -0
- package/skills/pinecall-sdk-api/SKILL.md +56 -0
- package/skills/pinecall-sdk-api/references/api/agent.md +328 -0
- package/skills/pinecall-sdk-api/references/api/call.md +324 -0
- package/skills/pinecall-sdk-api/references/api/pinecall.md +186 -0
- package/skills/pinecall-sdk-api/references/api/reply-stream.md +148 -0
- package/skills/pinecall-security/SKILL.md +37 -0
- package/skills/pinecall-security/references/security.md +138 -0
- package/skills/pinecall-web-chat/SKILL.md +38 -0
- package/skills/pinecall-web-chat/references/web/chat/chat-session.md +178 -0
- package/skills/pinecall-web-chat/references/web/chat/overview.md +98 -0
- package/skills/pinecall-web-components/SKILL.md +37 -0
- package/skills/pinecall-web-components/references/web/components/overview.md +128 -0
- package/skills/pinecall-web-voice/SKILL.md +40 -0
- package/skills/pinecall-web-voice/references/web/core/datachannel-protocol.md +149 -0
- package/skills/pinecall-web-voice/references/web/core/overview.md +70 -0
- package/skills/pinecall-web-voice/references/web/core/state-and-phases.md +153 -0
- package/skills/pinecall-web-voice/references/web/core/voice-session.md +279 -0
- package/skills/pinecall-web-widget/SKILL.md +41 -0
- package/skills/pinecall-web-widget/references/web/widget/overview.md +67 -0
- package/skills/pinecall-web-widget/references/web/widget/props.md +291 -0
- package/skills/pinecall-web-widget/references/web/widget/theming.md +131 -0
- package/skills/pinecall-web-widget/references/web/widget/tools-api.md +381 -0
- package/skills/pinecall-web-widget/references/web/widget/use-voice-session-hook.md +130 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@pinecall/web/core"
|
|
3
|
+
description: "Framework-agnostic WebRTC voice session client. Zero dependencies."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @pinecall/web/core
|
|
7
|
+
|
|
8
|
+
The browser-side counterpart to `@pinecall/sdk`. A framework-agnostic WebRTC client that handles the audio transport, mic access, and DataChannel events. Works with React, Vue, Svelte, vanilla JS, or any framework.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @pinecall/web
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> Zero runtime dependencies. Browser-only (requires `RTCPeerConnection` and `getUserMedia`).
|
|
15
|
+
|
|
16
|
+
## What it does
|
|
17
|
+
|
|
18
|
+
`@pinecall/web/core` is what the browser uses to talk to a Pinecall agent over WebRTC. It:
|
|
19
|
+
|
|
20
|
+
- Fetches a short-lived token from your backend (or the voice server, in convenience mode)
|
|
21
|
+
- Requests microphone access via `getUserMedia`
|
|
22
|
+
- Opens a peer connection to `voice.pinecall.io`
|
|
23
|
+
- Exposes the conversation as a reactive state object + an event stream
|
|
24
|
+
|
|
25
|
+
It does **not** render UI. For a drop-in React widget with an animated orb, use [`@pinecall/web`](/web/widget/overview) (which is built on top of `@pinecall/web/core`).
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { VoiceSession } from "@pinecall/web/core";
|
|
31
|
+
|
|
32
|
+
const session = new VoiceSession({ agent: "mara" });
|
|
33
|
+
|
|
34
|
+
session.addEventListener("message", (e) => {
|
|
35
|
+
console.log(`${e.detail.message.role}: ${e.detail.message.text}`);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await session.connect();
|
|
39
|
+
|
|
40
|
+
// later
|
|
41
|
+
session.disconnect();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That's it. The session manages the connection lifecycle, mic, and transcript. You get a stream of structured events you can wire into any UI.
|
|
45
|
+
|
|
46
|
+
## How it fits with the other packages
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
- **You write**: agent logic in Node.js with `@pinecall/sdk`
|
|
51
|
+
- **You drop in**: `@pinecall/web/core` (or `@pinecall/web`) in the browser
|
|
52
|
+
- **The voice server**: handles the audio pipeline (STT, LLM, TTS, VAD) and forwards events both ways
|
|
53
|
+
|
|
54
|
+
## Two interfaces: state + events
|
|
55
|
+
|
|
56
|
+
`@pinecall/web/core` exposes the session two ways. Use whichever fits your framework:
|
|
57
|
+
|
|
58
|
+
| Style | API | Best for |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| **Reactive state** | `session.subscribe(cb)` + `session.getState()` | React's `useSyncExternalStore`, Vue's `ref`, Svelte stores |
|
|
61
|
+
| **Event listeners** | `session.addEventListener("message" \| "phase" \| "event" \| ...)` | Vanilla JS, imperative code, observability |
|
|
62
|
+
|
|
63
|
+
You can mix them in the same app — they share the same underlying state machine.
|
|
64
|
+
|
|
65
|
+
## What's next
|
|
66
|
+
|
|
67
|
+
- [`VoiceSession` class](/web/core/voice-session) — constructor, methods, options
|
|
68
|
+
- [State, phases, and transcripts](/web/core/state-and-phases) — the reactive state model
|
|
69
|
+
- [DataChannel protocol](/web/core/datachannel-protocol) — every event the server emits
|
|
70
|
+
- [`@pinecall/web`](/web/widget/overview) — the React widget built on `@pinecall/web/core`
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "State and Phases"
|
|
3
|
+
description: "The reactive state model: status, phases, transcript messages, and lifecycles."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# State and Phases
|
|
7
|
+
|
|
8
|
+
The `VoiceSession` exposes a single reactive state object via `getState()`. This page covers what's in it and how it changes over the lifetime of a call.
|
|
9
|
+
|
|
10
|
+
## State shape
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
interface VoiceSessionState {
|
|
14
|
+
status: "idle" | "connecting" | "connected" | "error";
|
|
15
|
+
error: string | null;
|
|
16
|
+
isMuted: boolean;
|
|
17
|
+
phase: "idle" | "listening" | "speaking" | "pause" | "thinking";
|
|
18
|
+
userSpeaking: boolean;
|
|
19
|
+
agentSpeaking: boolean;
|
|
20
|
+
duration: number; // seconds since connected
|
|
21
|
+
messages: TranscriptMessage[];
|
|
22
|
+
toolCalls: ToolUI[]; // active tool UI entries (only for tracked tools)
|
|
23
|
+
idleWarning: number | null; // seconds until idle timeout (null = no warning)
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| Field | Meaning |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `status` | The connection lifecycle. `idle` → `connecting` → `connected` → (`idle` on disconnect, or `error`). |
|
|
30
|
+
| `error` | Populated when `status === "error"`. Always check this when handling errors. |
|
|
31
|
+
| `isMuted` | Mic state. Mirrors `setMuted()` / `toggleMute()`. |
|
|
32
|
+
| `phase` | What the conversation is doing right now (see below). |
|
|
33
|
+
| `userSpeaking` | `true` between `speech.started` and `speech.ended` events. Use for live waveform UIs. |
|
|
34
|
+
| `agentSpeaking` | `true` while TTS is playing. |
|
|
35
|
+
| `duration` | Seconds since `status` became `connected`. Updates every second. |
|
|
36
|
+
| `messages` | Full transcript — user and bot turns. See [Transcript messages](#transcript-messages) below. |
|
|
37
|
+
| `idleWarning` | When the server emits `session.idleWarning`, this holds the seconds remaining until timeout. `null` when no warning is active. |
|
|
38
|
+
|
|
39
|
+
## Call phases
|
|
40
|
+
|
|
41
|
+
`phase` tells you what the conversation is doing **right now**. It's the field you'll bind to UI state most often (orb color, animation, status label).
|
|
42
|
+
|
|
43
|
+
| Phase | Meaning | Triggered by |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `idle` | Not in a call | Initial state, after disconnect |
|
|
46
|
+
| `listening` | Mic is hot, waiting for speech | Connection established; after bot finishes; after `turn.resumed` |
|
|
47
|
+
| `speaking` | Agent is speaking (TTS playing) | First `bot.word` event |
|
|
48
|
+
| `thinking` | Processing user input, waiting for LLM | `user.message` (STT final), `turn.end` |
|
|
49
|
+
| `pause` | Turn detection pause — user may still be talking | `turn.pause` (brief silence detected) |
|
|
50
|
+
|
|
51
|
+
Typical flow during one exchange:
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
## Transcript messages
|
|
56
|
+
|
|
57
|
+
The `messages` array contains the full conversation history. Each message is structured:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
interface TranscriptMessage {
|
|
61
|
+
id: number;
|
|
62
|
+
role: "user" | "bot" | "system";
|
|
63
|
+
text: string;
|
|
64
|
+
isInterim?: boolean; // user only: STT is still processing
|
|
65
|
+
speaking?: boolean; // bot only: TTS is playing this message
|
|
66
|
+
interrupted?: boolean; // bot only: user barged in
|
|
67
|
+
messageId?: string; // bot only: server-assigned ID
|
|
68
|
+
toolCallId?: string; // system only: tool call ID (for updating with result)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Messages mutate in place as STT refines, words stream in, and the bot finishes speaking — they don't get replaced. That means if you bind to `messages` reactively, the right entry will update.
|
|
73
|
+
|
|
74
|
+
### User message lifecycle
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
user.speaking → { role: "user", text: "Hola", isInterim: true }
|
|
78
|
+
text updates as STT refines...
|
|
79
|
+
user.speaking → { role: "user", text: "Hola que", isInterim: true }
|
|
80
|
+
user.message → { role: "user", text: "Hola, ¿qué tal?", isInterim: false }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If you're rendering a transcript, render `isInterim: true` messages with reduced opacity or a "typing" indicator so the user sees that the STT is still processing.
|
|
84
|
+
|
|
85
|
+
### Bot message lifecycle (word-by-word)
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
bot.speaking → { role: "bot", text: "", speaking: true, messageId: "abc" }
|
|
89
|
+
bot.word → text: "Hello"
|
|
90
|
+
bot.word → text: "Hello there"
|
|
91
|
+
bot.word → text: "Hello there how"
|
|
92
|
+
bot.word → text: "Hello there how are"
|
|
93
|
+
bot.word → text: "Hello there how are you"
|
|
94
|
+
bot.finished → { speaking: false, text: "Hello there, how are you?" }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`bot.speaking` arrives with the full intended `text`, but the widget intentionally starts with `text: ""` and builds word-by-word so the on-screen captions stay in sync with the audio.
|
|
98
|
+
|
|
99
|
+
`bot.finished` may include a polished final text (with proper punctuation that the per-word stream doesn't have).
|
|
100
|
+
|
|
101
|
+
### Interrupted bot
|
|
102
|
+
|
|
103
|
+
When the user barges in mid-utterance:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
bot.word → text: "Hello there how"
|
|
107
|
+
bot.interrupted → { speaking: false, interrupted: true }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Render interrupted messages with a visual marker (e.g. `⚡` icon, ellipsis, gray border) so users see the bot was cut off rather than just suddenly stopping.
|
|
111
|
+
|
|
112
|
+
## Subscribing to changes
|
|
113
|
+
|
|
114
|
+
The state object is **stable by identity** — `getState()` returns the same reference until something changes. This is what makes it safe for React's `useSyncExternalStore`:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
session.subscribe(() => {
|
|
118
|
+
const next = session.getState(); // new reference only if state changed
|
|
119
|
+
// ...
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
For more targeted updates, subscribe to specific events:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
session.addEventListener("phase", (e) => {
|
|
127
|
+
// only fires when phase actually changes
|
|
128
|
+
document.body.dataset.phase = e.detail.phase;
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Driving UI from `phase` and `agentSpeaking`
|
|
133
|
+
|
|
134
|
+
A common pattern: bind your "orb" or status visual to `phase` for the overall mode, and use `agentSpeaking` for a faster-reacting animation layer.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const orb = document.getElementById("orb");
|
|
138
|
+
|
|
139
|
+
session.subscribe(() => {
|
|
140
|
+
const { phase, agentSpeaking, idleWarning } = session.getState();
|
|
141
|
+
|
|
142
|
+
orb.dataset.phase = phase; // CSS handles per-phase styling
|
|
143
|
+
orb.classList.toggle("speaking", agentSpeaking);
|
|
144
|
+
orb.classList.toggle("idle-warning", idleWarning !== null);
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The `@pinecall/web` package follows exactly this pattern — see [its theming guide](/web/widget/theming) for the full set of CSS classes.
|
|
149
|
+
|
|
150
|
+
## What's next
|
|
151
|
+
|
|
152
|
+
- [DataChannel protocol](/web/core/datachannel-protocol) — the raw events that drive state changes
|
|
153
|
+
- [`VoiceSession` class](/web/core/voice-session) — methods and constructor
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "VoiceSession"
|
|
3
|
+
description: "The core class: constructor, methods, and framework integration patterns."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# VoiceSession
|
|
7
|
+
|
|
8
|
+
The main class exported by `@pinecall/web/core`. Manages the WebRTC peer connection, mic stream, DataChannel events, and a reactive state object.
|
|
9
|
+
|
|
10
|
+
## Constructor
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
new VoiceSession(options)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
| Option | Type | Required | Description |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| `agent` | `string` | ✅ | Agent ID to connect to |
|
|
19
|
+
| `server` | `string` | — | API base URL (default: `https://voice.pinecall.io`) |
|
|
20
|
+
| `config` | `Record<string, unknown>` | — | Session config overrides (voice, STT, language, greeting) |
|
|
21
|
+
| `metadata` | `Record<string, unknown>` | — | Metadata passed to the agent (visible as `call.metadata` server-side) |
|
|
22
|
+
|
|
23
|
+
The constructor does **not** open a connection. Call `connect()` when you want the call to start.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const session = new VoiceSession({
|
|
27
|
+
agent: "mara",
|
|
28
|
+
config: {
|
|
29
|
+
voice: "elevenlabs/sarah",
|
|
30
|
+
stt: { provider: "deepgram", model: "nova-3", language: "es" },
|
|
31
|
+
language: "es",
|
|
32
|
+
greeting: "¡Hola! ¿En qué puedo ayudarte?",
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The `config` object uses Pinecall's shortcut syntax — same format the server SDK accepts. See [STT Providers](/reference/stt-providers) and [TTS Providers](/reference/tts-providers).
|
|
38
|
+
|
|
39
|
+
## Methods
|
|
40
|
+
|
|
41
|
+
### `connect()`
|
|
42
|
+
|
|
43
|
+
Opens the WebRTC connection. Returns a `Promise<void>` that resolves when the connection is established.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
await session.connect();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Internally it:
|
|
50
|
+
|
|
51
|
+
1. Fetches a short-lived token from `GET /webrtc/token?agent_id=<agent>`
|
|
52
|
+
2. Fetches ICE servers from `GET /webrtc/ice-servers` (falls back to Google STUN)
|
|
53
|
+
3. Requests microphone access via `getUserMedia`
|
|
54
|
+
4. Creates `RTCPeerConnection`, adds the mic track, opens a DataChannel
|
|
55
|
+
5. Generates an SDP offer, gathers ICE candidates
|
|
56
|
+
6. Sends the offer to `POST /webrtc/offer` with the token
|
|
57
|
+
7. Applies the remote SDP answer → connection established
|
|
58
|
+
|
|
59
|
+
State transitions: `idle` → `connecting` → `connected` (or `error`).
|
|
60
|
+
|
|
61
|
+
### `disconnect()`
|
|
62
|
+
|
|
63
|
+
Closes the connection, stops the mic, clears timers. State returns to `idle`. The `messages` array is preserved.
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
session.disconnect();
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `toggleMute()` / `setMuted(muted)`
|
|
70
|
+
|
|
71
|
+
Mute or unmute the mic. Both disable the local audio track **and** send `{ action: "mute" | "unmute" }` over the DataChannel so the server stops processing audio too.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
session.toggleMute();
|
|
75
|
+
session.setMuted(true);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `getState()`
|
|
79
|
+
|
|
80
|
+
Returns the current state snapshot. The returned object is **stable by identity** — it only changes when state mutates, which makes it safe for React's `useSyncExternalStore`.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const { status, phase, messages, isMuted, duration } = session.getState();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
See [State and Phases](/web/core/state-and-phases) for the full shape.
|
|
87
|
+
|
|
88
|
+
### `subscribe(listener)`
|
|
89
|
+
|
|
90
|
+
Subscribes to all state changes. Returns an unsubscribe function. Designed to plug directly into reactive frameworks.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const unsubscribe = session.subscribe(() => {
|
|
94
|
+
console.log(session.getState());
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// later
|
|
98
|
+
unsubscribe();
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `destroy()`
|
|
102
|
+
|
|
103
|
+
Disconnects, clears all subscribers, and marks the instance unusable. Call this on component unmount.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
session.destroy();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `configure(config)`
|
|
110
|
+
|
|
111
|
+
Sends a mid-call configuration update over the DataChannel. The server hot-swaps providers without disconnecting. Use this for live language/voice/STT switching during an active call.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
session.configure({
|
|
115
|
+
voice: "elevenlabs/george",
|
|
116
|
+
stt: { provider: "deepgram", model: "nova-3", language: "es" },
|
|
117
|
+
language: "es",
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> Only works on a connected session. For pre-connect config updates use `updateOptions()`.
|
|
122
|
+
|
|
123
|
+
### `updateOptions(patch)`
|
|
124
|
+
|
|
125
|
+
Updates options **before** the next `connect()` call. No effect on an already-connected session.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
session.updateOptions({
|
|
129
|
+
config: {
|
|
130
|
+
voice: "elevenlabs/valentina",
|
|
131
|
+
language: "es",
|
|
132
|
+
greeting: "¡Hola!",
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await session.connect(); // uses the new config
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Events (EventTarget)
|
|
140
|
+
|
|
141
|
+
`VoiceSession` extends `EventTarget`. Listen with `addEventListener`:
|
|
142
|
+
|
|
143
|
+
| Event | `detail` | When |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `status` | `{ status }` | Connection status changed |
|
|
146
|
+
| `phase` | `{ phase }` | Call phase changed (listening, speaking, thinking, etc.) |
|
|
147
|
+
| `message` | `{ message }` | New transcript message added or existing one updated |
|
|
148
|
+
| `error` | `{ error }` | An error occurred |
|
|
149
|
+
| `change` | `{ state }` | Any state mutation (most general) |
|
|
150
|
+
| `event` | raw payload | Every raw DataChannel event from the server |
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
session.addEventListener("message", (e) => {
|
|
154
|
+
const msg = e.detail.message;
|
|
155
|
+
if (msg.role === "user" && !msg.isInterim) console.log("User:", msg.text);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
session.addEventListener("event", (e) => {
|
|
159
|
+
// raw — see DataChannel protocol page for the full catalog
|
|
160
|
+
if (e.detail.event === "llm.toolCall") {
|
|
161
|
+
console.log("Tool calls:", e.detail.tool_calls);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The `event` listener is the power-user escape hatch. Every JSON message from the server's DataChannel is forwarded as-is. Use it for things the state machine doesn't expose: tool calls, audio metrics, custom events.
|
|
167
|
+
|
|
168
|
+
## Framework patterns
|
|
169
|
+
|
|
170
|
+
### Vanilla JS
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { VoiceSession } from "@pinecall/web/core";
|
|
174
|
+
|
|
175
|
+
const session = new VoiceSession({ agent: "florencia" });
|
|
176
|
+
const btn = document.getElementById("call-btn");
|
|
177
|
+
const transcript = document.getElementById("transcript");
|
|
178
|
+
|
|
179
|
+
btn.onclick = async () => {
|
|
180
|
+
if (session.getState().status === "connected") {
|
|
181
|
+
session.disconnect();
|
|
182
|
+
btn.textContent = "Start Call";
|
|
183
|
+
} else {
|
|
184
|
+
await session.connect();
|
|
185
|
+
btn.textContent = "End Call";
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
session.addEventListener("message", (e) => {
|
|
190
|
+
const msg = e.detail.message;
|
|
191
|
+
const div = document.createElement("div");
|
|
192
|
+
div.className = msg.role;
|
|
193
|
+
div.textContent = `${msg.role}: ${msg.text}`;
|
|
194
|
+
transcript.appendChild(div);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
session.addEventListener("phase", (e) => {
|
|
198
|
+
document.body.dataset.phase = e.detail.phase;
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### React (`useSyncExternalStore`)
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { useSyncExternalStore, useCallback, useState, useEffect } from "react";
|
|
206
|
+
import { VoiceSession } from "@pinecall/web/core";
|
|
207
|
+
|
|
208
|
+
function useVoiceSession(agent: string) {
|
|
209
|
+
const [session] = useState(() => new VoiceSession({ agent }));
|
|
210
|
+
|
|
211
|
+
const state = useSyncExternalStore(
|
|
212
|
+
useCallback((cb) => session.subscribe(cb), [session]),
|
|
213
|
+
() => session.getState(),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
useEffect(() => () => session.destroy(), [session]);
|
|
217
|
+
|
|
218
|
+
return { ...state, session };
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
> If you're using React and want a ready-made widget instead of building UI, use [`@pinecall/web`](/web/widget/overview) — it wraps this pattern and ships an animated orb UI.
|
|
223
|
+
|
|
224
|
+
### Vue 3
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { ref, onUnmounted } from "vue";
|
|
228
|
+
import { VoiceSession } from "@pinecall/web/core";
|
|
229
|
+
|
|
230
|
+
export function useVoiceSession(agent: string) {
|
|
231
|
+
const session = new VoiceSession({ agent });
|
|
232
|
+
const state = ref(session.getState());
|
|
233
|
+
|
|
234
|
+
session.subscribe(() => {
|
|
235
|
+
state.value = session.getState();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
onUnmounted(() => session.destroy());
|
|
239
|
+
|
|
240
|
+
return { state, session };
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Svelte
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { readable } from "svelte/store";
|
|
248
|
+
import { VoiceSession } from "@pinecall/web/core";
|
|
249
|
+
|
|
250
|
+
export function createVoiceSession(agent: string) {
|
|
251
|
+
const session = new VoiceSession({ agent });
|
|
252
|
+
|
|
253
|
+
const state = readable(session.getState(), (set) => {
|
|
254
|
+
return session.subscribe(() => set(session.getState()));
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return { state, session };
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## TypeScript types
|
|
262
|
+
|
|
263
|
+
All types are exported from the package:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import type {
|
|
267
|
+
VoiceSessionOptions,
|
|
268
|
+
VoiceSessionState,
|
|
269
|
+
SessionStatus, // "idle" | "connecting" | "connected" | "error"
|
|
270
|
+
CallPhase, // "idle" | "listening" | "speaking" | "pause" | "thinking"
|
|
271
|
+
TranscriptMessage,
|
|
272
|
+
} from "@pinecall/web/core";
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## What's next
|
|
276
|
+
|
|
277
|
+
- [State and phases](/web/core/state-and-phases) — the reactive state model in detail
|
|
278
|
+
- [DataChannel protocol](/web/core/datachannel-protocol) — every event the server emits
|
|
279
|
+
- [`@pinecall/web`](/web/widget/overview) — the React widget built on top
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pinecall-web-widget
|
|
3
|
+
description: >-
|
|
4
|
+
@pinecall/web React widget — VoiceWidget props, theming, useVoiceSession hook, client tools. Use when the user is building, configuring, or debugging with @pinecall/sdk. Keywords: react widget, voicewidget, useVoiceSession, theming, props, client tools, @pinecall/web.
|
|
5
|
+
license: MIT
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# @pinecall/web (React Widget)
|
|
9
|
+
|
|
10
|
+
@pinecall/web React widget — VoiceWidget props, theming, useVoiceSession hook, client tools.
|
|
11
|
+
|
|
12
|
+
This skill bundles the official Pinecall documentation for **@pinecall/web (React Widget)**. The
|
|
13
|
+
table below indexes every page; open the `references/…` file for the full text
|
|
14
|
+
(loaded on demand). Source of truth: <https://docs.pinecall.io>.
|
|
15
|
+
|
|
16
|
+
| Page | What it covers | Open |
|
|
17
|
+
|------|----------------|------|
|
|
18
|
+
| **@pinecall/web** | Drop-in React voice widget with animated orb UI, live transcript, themes, and multi-language support. | [`references/web/widget/overview.md`](references/web/widget/overview.md) · [docs](https://docs.pinecall.io/web/widget/overview) |
|
|
19
|
+
| **Props** | Every prop the VoiceWidget accepts — including token security, tools, theming, and multi-language. | [`references/web/widget/props.md`](references/web/widget/props.md) · [docs](https://docs.pinecall.io/web/widget/props) |
|
|
20
|
+
| **Theming** | Theme presets, CSS variables, and full customization of the orb and transcript UI. | [`references/web/widget/theming.md`](references/web/widget/theming.md) · [docs](https://docs.pinecall.io/web/widget/theming) |
|
|
21
|
+
| **useVoiceSession hook** | Build a fully custom voice UI without giving up the widget's session management. | [`references/web/widget/use-voice-session-hook.md`](references/web/widget/use-voice-session-hook.md) · [docs](https://docs.pinecall.io/web/widget/use-voice-session-hook) |
|
|
22
|
+
| **Tools API** | Render interactive UI in response to LLM tool calls. Buttons, forms, pickers — all synced to the conversation. | [`references/web/widget/tools-api.md`](references/web/widget/tools-api.md) · [docs](https://docs.pinecall.io/web/widget/tools-api) |
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## House rules — always apply
|
|
26
|
+
|
|
27
|
+
- **Example defaults** (use these exact strings unless the user asks otherwise):
|
|
28
|
+
`stt: "deepgram/flux"`, `llm: "openai/gpt-5-chat-latest"`, `voice: "elevenlabs/sarah"`.
|
|
29
|
+
**NEVER use `deepgram/nova-2`** — it is not supported. Use `deepgram/nova-3`
|
|
30
|
+
only for languages Flux doesn't support (e.g. Arabic).
|
|
31
|
+
- **Turn detection & VAD are auto-derived from the STT provider — never set
|
|
32
|
+
`turnDetection` or `vad` manually.** Flux → native turns + native VAD;
|
|
33
|
+
every other STT → `smart_turn` + `silero`.
|
|
34
|
+
- **Greeting**: inbound → `greeting` field in `pc.agent()`; outbound → `greeting`
|
|
35
|
+
field in `agent.dial()`. It is sugar for `call.say()` in `call.started`.
|
|
36
|
+
- **Auth**: `new Pinecall()` reads `PINECALL_API_KEY` from env and auto-connects.
|
|
37
|
+
- Full documentation: <https://docs.pinecall.io>
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
*Generated from `sdk/docs/` by `@pinecall/skills` — do not edit by hand; edit the
|
|
41
|
+
docs and re-run `node build.mjs`.*
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "@pinecall/web"
|
|
3
|
+
description: "Drop-in React voice widget with animated orb UI, live transcript, themes, and multi-language support."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @pinecall/web
|
|
7
|
+
|
|
8
|
+
A React voice widget for Pinecall agents. Animated orb, live transcript, theme presets, multi-language selector, and an interactive tools API for rendering UI in response to LLM tool calls.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @pinecall/web react react-dom
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> Built on top of [`@pinecall/web/core`](/web/core/overview). React ≥18 is a peer dependency.
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { VoiceWidget } from "@pinecall/web";
|
|
20
|
+
|
|
21
|
+
export default function App() {
|
|
22
|
+
return <VoiceWidget agent="mara" name="Mara" />;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That's it. The widget renders a floating orb in the bottom-right corner. Click to start a voice call, click again to end it. The orb animates through phases (idle → connecting → listening → speaking → thinking) and shows a live transcript bubble above.
|
|
27
|
+
|
|
28
|
+
## What you get out of the box
|
|
29
|
+
|
|
30
|
+
- **Animated orb** with breathing rings, pulse states, and per-phase colors
|
|
31
|
+
- **Live transcript** rendered as chat bubbles next to the orb
|
|
32
|
+
- **5 theme presets** (`dark`, `midnight`, `aurora`, `sunset`, `light`) — plus full CSS variable overrides
|
|
33
|
+
- **Multi-language pill selector** with hot-swap mid-call
|
|
34
|
+
- **Token security** via `tokenProvider` — API keys never leave your server
|
|
35
|
+
- **Idle warning state** when the user goes silent too long
|
|
36
|
+
- **Interactive Tools API** for rendering UI in response to LLM tool calls
|
|
37
|
+
- **`useVoiceSession()` hook** for building completely custom UIs
|
|
38
|
+
|
|
39
|
+
## Standalone components
|
|
40
|
+
|
|
41
|
+
The package also exports standalone components for building custom multi-channel experiences:
|
|
42
|
+
|
|
43
|
+
| Export | Purpose |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `ContactHub` | Multi-channel contact menu (voice, chat, WhatsApp, Call Me) |
|
|
46
|
+
| `ChatView` | Embedded LLM text chat with streaming markdown |
|
|
47
|
+
| `useVoiceSession()` | Headless hook — build your own UI from scratch |
|
|
48
|
+
| `useAgentInfo()` | Fetch agent channel info for auto-discovery |
|
|
49
|
+
|
|
50
|
+
These are **not** wired into `<VoiceWidget>` — you compose them yourself in your app's UI.
|
|
51
|
+
|
|
52
|
+
## When to use what
|
|
53
|
+
|
|
54
|
+
| You want to... | Use |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Drop a voice button on your site | `<VoiceWidget />` |
|
|
57
|
+
| Build a fully custom UI in React | `useVoiceSession()` hook |
|
|
58
|
+
| Build a fully custom UI in Vue/Svelte/vanilla | [`@pinecall/web/core`](/web/core/overview) directly |
|
|
59
|
+
| Render interactive UI from agent tool calls | `<VoiceWidget>` + `tools` prop or `useVoice()` + `trackedTools` |
|
|
60
|
+
| Add multi-channel contact menu | Import `ContactHub` and compose it in your layout |
|
|
61
|
+
|
|
62
|
+
## What's next
|
|
63
|
+
|
|
64
|
+
- [Props reference](/web/widget/props) — every prop with type and default
|
|
65
|
+
- [Theming](/web/widget/theming) — presets, CSS variables, custom themes
|
|
66
|
+
- [`useVoiceSession` hook](/web/widget/use-voice-session-hook) — for custom UIs
|
|
67
|
+
- [Tools API](/web/widget/tools-api) — render interactive components from tool calls
|