@mastra/livekit 0.2.0 → 0.3.0-alpha.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/CHANGELOG.md +131 -0
- package/README.md +164 -38
- package/dist/bridge.d.ts +76 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/chunk-4O7IN74Y.js +568 -0
- package/dist/chunk-4O7IN74Y.js.map +1 -0
- package/dist/chunk-DBVKNDAQ.cjs +574 -0
- package/dist/chunk-DBVKNDAQ.cjs.map +1 -0
- package/dist/consent.d.ts +55 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/end-call.d.ts +50 -0
- package/dist/end-call.d.ts.map +1 -0
- package/dist/index.cjs +46 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/dist/llm-plugin.d.ts +112 -0
- package/dist/llm-plugin.d.ts.map +1 -0
- package/dist/messages.d.ts +26 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/plugin-entry.cjs +182 -0
- package/dist/plugin-entry.cjs.map +1 -0
- package/dist/plugin-entry.d.ts +6 -0
- package/dist/plugin-entry.d.ts.map +1 -0
- package/dist/plugin-entry.js +177 -0
- package/dist/plugin-entry.js.map +1 -0
- package/dist/remote.d.ts +68 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/worker-entry.cjs +157 -210
- package/dist/worker-entry.cjs.map +1 -1
- package/dist/worker-entry.d.ts +5 -3
- package/dist/worker-entry.d.ts.map +1 -1
- package/dist/worker-entry.js +144 -210
- package/dist/worker-entry.js.map +1 -1
- package/dist/worker.d.ts +281 -7
- package/dist/worker.d.ts.map +1 -1
- package/package.json +15 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,136 @@
|
|
|
1
1
|
# @mastra/livekit
|
|
2
2
|
|
|
3
|
+
## 0.3.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Added per-call speech-to-text and text-to-speech selection to `createLiveKitWorker`. Set the new `configuration.stt` and `configuration.tts` resolvers to pick the transcriber and voice for each call — one voice or language per tenant — keyed off the dispatch metadata and request context. Each resolver runs once per call and falls back to the top-level `stt` / `tts` option when it returns `undefined`. ([#19136](https://github.com/mastra-ai/mastra/pull/19136))
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
export default createLiveKitWorker({
|
|
11
|
+
mastra,
|
|
12
|
+
agent: 'support',
|
|
13
|
+
stt: 'deepgram/nova-3',
|
|
14
|
+
tts: 'cartesia/sonic-3', // fallback voice
|
|
15
|
+
configuration: {
|
|
16
|
+
// Give each tenant its own voice, resolved per call from the dispatch metadata.
|
|
17
|
+
tts: ({ requestContext }) => tenantVoices[requestContext?.tenant as string],
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Previously the worker's speech pipeline was fixed at construction, so a multi-tenant worker could not vary voices or transcription per call. Customers who own their LiveKit session (the `MastraLLM` plugin path) already choose STT/TTS per call by construction; this brings the same flexibility to the batteries-included worker.
|
|
23
|
+
|
|
24
|
+
- Added `MastraLLM`, a standard LiveKit LLM plugin, on the new `@mastra/livekit/plugin` entry point. Build your own `voice.AgentSession` and put a Mastra agent in the `llm` slot — the agent loop, tools, and memory run on a remote Mastra server reached over HTTP, so the worker process needs no Mastra app, database, or model provider keys. ([#19136](https://github.com/mastra-ai/mastra/pull/19136))
|
|
25
|
+
|
|
26
|
+
Before, the worker wrapper always owned the LiveKit session:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { createLiveKitWorker } from '@mastra/livekit/worker';
|
|
30
|
+
import { mastra } from './index';
|
|
31
|
+
|
|
32
|
+
export default createLiveKitWorker({
|
|
33
|
+
mastra,
|
|
34
|
+
agent: 'support',
|
|
35
|
+
stt: 'deepgram/nova-3',
|
|
36
|
+
tts: 'cartesia/sonic-3',
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Now you can own the session and keep Mastra as the LLM component:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { voice } from '@livekit/agents';
|
|
44
|
+
import { MastraLLM } from '@mastra/livekit/plugin';
|
|
45
|
+
|
|
46
|
+
const session = new voice.AgentSession({
|
|
47
|
+
llm: new MastraLLM({
|
|
48
|
+
remote: { baseUrl: process.env.MASTRA_URL!, agentId: 'support' },
|
|
49
|
+
memory: { thread: callId, resource: userId },
|
|
50
|
+
}),
|
|
51
|
+
stt: 'deepgram/nova-3',
|
|
52
|
+
tts: 'cartesia/sonic-3',
|
|
53
|
+
// Required with `memory`: LiveKit enables preemptive generation by default.
|
|
54
|
+
turnHandling: { preemptiveGeneration: { enabled: false } },
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`createLiveKitWorker` stays the batteries-included path; the plugin is the composable one. Tools keep running server-side on the Mastra agent, and interrupting the agent aborts the server-side generation.
|
|
59
|
+
|
|
60
|
+
**New transport and helpers**
|
|
61
|
+
- Added `createRemoteAgentReplyGenerator()`: streams replies from a remote Mastra server over HTTP with per-turn abort, LiveKit-typed errors, and a connect + first-token timeout. It also plugs into `createLiveKitWorker`'s `generate` option to run the existing worker against a remote server.
|
|
62
|
+
- Promoted `speakGreeting()`, `waitForAgentDoneSpeaking()`, and `runEndCall()` to public exports of `@mastra/livekit/worker`, so a worker that owns its session can rebuild the greeting and agent-initiated hang-up patterns in a few lines.
|
|
63
|
+
|
|
64
|
+
**Improvements to the existing worker**
|
|
65
|
+
- Interrupted turns now self-heal: when a caller interrupts a reply, nothing is persisted at that moment, and the part the caller actually heard is backfilled into the memory thread on the next turn — so saved transcripts match the call.
|
|
66
|
+
- Added an `onToolCall` hook that fires as each tool call starts mid-reply, the building block for tool-driven side effects such as analytics or hang-up.
|
|
67
|
+
- `onTurnComplete` now receives the turn's token usage as `result.usage`.
|
|
68
|
+
|
|
69
|
+
- Added a `configuration` option to `createLiveKitWorker` — one grouped home for conversation and compliance controls, so these don't each become a separate top-level worker option. It ships with greeting/AI-disclosure controls, a consent model, and agent-initiated hang-up, and is where further compliance controls will land. ([#19136](https://github.com/mastra-ai/mastra/pull/19136))
|
|
70
|
+
|
|
71
|
+
**Greeting and AI disclosure**
|
|
72
|
+
|
|
73
|
+
`configuration.greeting` controls the opening line spoken at call start. Set `allowInterruptions: false` so a legally-required AI disclosure plays through and can't be talked over (EU AI Act Art. 50), `awaitPlayout: true` to hold post-greeting work until it finishes, and `repeatEvery` to re-disclose periodically on long calls (spoken at the next turn boundary, never mid-sentence).
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
createLiveKitWorker({
|
|
77
|
+
mastra,
|
|
78
|
+
agent: 'support',
|
|
79
|
+
configuration: {
|
|
80
|
+
greeting: {
|
|
81
|
+
text: 'You are speaking with an AI assistant. This call may be recorded. How can I help?',
|
|
82
|
+
allowInterruptions: false,
|
|
83
|
+
awaitPlayout: true,
|
|
84
|
+
repeatEvery: 3 * 60_000, // re-disclose ~every 3 minutes
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Per-tenant greeting**
|
|
91
|
+
|
|
92
|
+
`greeting.text` also accepts a resolver, called once per call with the call context, so one multi-tenant agent can open differently per tenant based on the dispatch metadata:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
greeting: {
|
|
96
|
+
text: ({ metadata }) => `Thanks for calling ${tenantName(metadata)}. You're speaking with an AI assistant.`,
|
|
97
|
+
allowInterruptions: false,
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Consent**
|
|
102
|
+
|
|
103
|
+
`configuration.consentPolicy` declares which data-use consents a call needs, as a named, extensible set (starting with `summaryStorage`) rather than one global flag. Declaring the policy enforces nothing by itself: the new `createConsentTool` captures the caller's decision at runtime — add it to your agent and it hands each decision to your own store — and your code enforces the requirement at `onCallEnd` (or before any consent-gated step).
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { createConsentTool } from '@mastra/livekit';
|
|
107
|
+
|
|
108
|
+
// in your agent's tools:
|
|
109
|
+
recordConsent: createConsentTool({
|
|
110
|
+
items: ['summaryStorage'],
|
|
111
|
+
onGrant: async ({ item, granted, resourceId }) => {
|
|
112
|
+
if (resourceId) await db.saveConsent(resourceId, item, granted);
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Agent-initiated hang-up**
|
|
118
|
+
|
|
119
|
+
`configuration.endCall` lets the agent end the call itself. Add the new `createEndCallTool` to your agent and instruct it to say goodbye and then call the tool; the worker waits for the closing words to finish playing, holds a short audio drain (`drainMs`, default 800ms) so the tail of the goodbye isn't clipped while it's still buffered at the caller, then hangs up — running `onCallEnd` on the way out, exactly as a caller hang-up does. It works on both the agent and workflow reply paths.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { createEndCallTool } from '@mastra/livekit';
|
|
123
|
+
|
|
124
|
+
// in your agent's tools:
|
|
125
|
+
endCall: (createEndCallTool(),
|
|
126
|
+
// on the worker:
|
|
127
|
+
createLiveKitWorker({ mastra, agent: 'support', configuration: { endCall: {} } }));
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Backwards compatible**
|
|
131
|
+
|
|
132
|
+
The previous top-level `greeting` (string) and `persistGreeting` options still work as deprecated aliases for `configuration.greeting.text` and `configuration.greeting.persist`. When both are set, `configuration.greeting` wins field by field, so existing worker configs keep running unchanged.
|
|
133
|
+
|
|
3
134
|
## 0.2.0
|
|
4
135
|
|
|
5
136
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ caller speaks ─▶ VAD ─▶ STT ─▶ turn detection ─▶ [ Mastra agent
|
|
|
14
14
|
- **Two reply paths** — answer turns with a Mastra **agent** (the default, richest path) or a Mastra **workflow** (run-to-completion per turn, e.g. deterministic intent routing). A low-level `generate` escape hatch accepts any custom reply generator.
|
|
15
15
|
- **Full speech stack, pluggable** — STT/TTS as LiveKit inference model strings (`'deepgram/nova-3'`, `'cartesia/sonic-3'`) or your own plugin instances; Silero VAD and LiveKit multilingual/English turn detection; barge-in cancels in-flight generation automatically.
|
|
16
16
|
- **Memory, scoped to the call** — `thread` = call, `resource` = caller, so a returning caller is recognized across calls. Up-front thread creation and greeting persistence keep the saved thread a faithful transcript. Works on the agent path and the workflow path (via `memoryInstance`).
|
|
17
|
-
- **Lifecycle hooks** — `toolFeedback` (speak filler while a tool runs), `onTurnComplete` (post-turn, fire-and-forget, off the audio path), and `onCallEnd` (end-of-call, awaited within LiveKit's shutdown window — the place to
|
|
17
|
+
- **Lifecycle hooks** — `toolFeedback` (speak filler while a tool runs), `onTurnComplete` (post-turn, fire-and-forget, off the audio path), and `onCallEnd` (end-of-call, awaited within LiveKit's shutdown window — the place to summarize the finished call with `memory.summarizeThread()`).
|
|
18
|
+
- **Compliance controls, grouped under `configuration`** — AI-disclosure greeting that can't be barged over (with a per-tenant resolver), periodic re-disclosure on long calls (`repeatEvery`), an extensible named consent model (`consentPolicy` declared on the worker, captured at runtime with `createConsentTool`), and agent-initiated hang-up (`endCall` + `createEndCallTool`) that waits for the goodbye to play out before disconnecting.
|
|
18
19
|
- **Observability** — one `voice call` trace per session with LiveKit pipeline metrics and every Mastra run nested under it.
|
|
19
20
|
- **Connection + dispatch helpers** — `liveKitConnectionRoute` mints tokens and dispatches the worker so a frontend can join.
|
|
20
21
|
|
|
@@ -33,10 +34,11 @@ Peer dependencies (`@mastra/core` and `@livekit/agents` are required; the two pl
|
|
|
33
34
|
| `@livekit/agents-plugin-silero` | the default `vad: 'silero'` |
|
|
34
35
|
| `@livekit/agents-plugin-livekit` | `turnDetection: 'multilingual' \| 'english'` |
|
|
35
36
|
|
|
36
|
-
The package has
|
|
37
|
+
The package has three entry points:
|
|
37
38
|
|
|
38
|
-
- `@mastra/livekit` — server-side helpers (`liveKitConnectionRoute`, `dispatchVoiceSession`, `pipeAgentReplyToWriter`). Safe to import from Mastra server code; never loads the `@livekit/agents` runtime.
|
|
39
|
+
- `@mastra/livekit` — server-side helpers (`liveKitConnectionRoute`, `dispatchVoiceSession`, `pipeAgentReplyToWriter`) and the agent tool factories (`createConsentTool`, `createEndCallTool` — they go on agents defined in server/shared code). Safe to import from Mastra server code; never loads the `@livekit/agents` runtime.
|
|
39
40
|
- `@mastra/livekit/worker` — the worker runtime (`createLiveKitWorker`, `runLiveKitWorker`). Import it only from the worker entry file.
|
|
41
|
+
- `@mastra/livekit/plugin` — the `MastraLLM` plugin, for customers who own their `voice.AgentSession` and want a Mastra agent in the `llm` slot (a standard LiveKit `llm.LLM`; in-process agent, remote Mastra server, or custom generator). Also loads the `@livekit/agents` runtime — keep it out of Mastra server code.
|
|
40
42
|
|
|
41
43
|
## Quick start
|
|
42
44
|
|
|
@@ -54,7 +56,9 @@ export default createLiveKitWorker({
|
|
|
54
56
|
stt: 'deepgram/nova-3',
|
|
55
57
|
tts: 'cartesia/sonic-3',
|
|
56
58
|
turnDetection: 'multilingual',
|
|
57
|
-
|
|
59
|
+
configuration: {
|
|
60
|
+
greeting: { text: 'Thanks for calling. How can I help?' },
|
|
61
|
+
},
|
|
58
62
|
});
|
|
59
63
|
|
|
60
64
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
@@ -135,35 +139,145 @@ For full control, pass a `generate` function — any `VoiceReplyGenerator` that
|
|
|
135
139
|
|
|
136
140
|
## `createLiveKitWorker` options
|
|
137
141
|
|
|
138
|
-
| Option | Type
|
|
139
|
-
| --------------------------------------------------- |
|
|
140
|
-
| `mastra` | `Mastra`
|
|
141
|
-
| **Reply generation** (pick one) |
|
|
142
|
-
| `agent` | `string \| Agent \| (args) => …`
|
|
143
|
-
| `workflow` | `string \| Workflow \| (args) => string`
|
|
144
|
-
| `workflowInput` | `(ctx & { metadata }) => inputData`
|
|
145
|
-
| `replyStep` | `string`
|
|
146
|
-
| `resultText` | `(result) => string`
|
|
147
|
-
| `generate` | `VoiceReplyGenerator`
|
|
148
|
-
| **Speech stack** |
|
|
149
|
-
| `stt` | plugin or `'provider/model'`
|
|
150
|
-
| `tts` | plugin or `'provider/model'`
|
|
151
|
-
| `vad` | `VAD \| 'silero' \| false`
|
|
152
|
-
| `turnDetection` | `'multilingual' \| 'english' \| …`
|
|
153
|
-
| `turnHandling` | `AgentSessionOptions['turnHandling']`
|
|
154
|
-
| `sessionOptions` / `inputOptions` / `outputOptions` | partial LiveKit options
|
|
155
|
-
| **Memory** |
|
|
156
|
-
| `memory` | `false \| (args) => { thread, resource }`
|
|
157
|
-
| `memoryInstance` | `Memory \| (args) => Memory`
|
|
158
|
-
| `greeting`
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
| `
|
|
162
|
-
| `
|
|
163
|
-
| `
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
|
|
142
|
+
| Option | Type | Notes |
|
|
143
|
+
| --------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `mastra` | `Mastra` | **Required.** The instance whose agents/workflows answer sessions. |
|
|
145
|
+
| **Reply generation** (pick one) | | |
|
|
146
|
+
| `agent` | `string \| Agent \| (args) => …` | The agent that answers. Defaults to `metadata.agentId`. |
|
|
147
|
+
| `workflow` | `string \| Workflow \| (args) => string` | Answer with a workflow instead. Requires `workflowInput`. |
|
|
148
|
+
| `workflowInput` | `(ctx & { metadata }) => inputData` | Maps a turn into the workflow's `inputData`. |
|
|
149
|
+
| `replyStep` | `string` | Only stream text from this workflow step id. |
|
|
150
|
+
| `resultText` | `(result) => string` | Fallback reply text when the workflow streams nothing. |
|
|
151
|
+
| `generate` | `VoiceReplyGenerator` | Lowest-level escape hatch. |
|
|
152
|
+
| **Speech stack** | | |
|
|
153
|
+
| `stt` | plugin or `'provider/model'` | Speech-to-text. |
|
|
154
|
+
| `tts` | plugin or `'provider/model'` | Text-to-speech. |
|
|
155
|
+
| `vad` | `VAD \| 'silero' \| false` | Voice activity detection. Defaults to `'silero'`. |
|
|
156
|
+
| `turnDetection` | `'multilingual' \| 'english' \| …` | End-of-turn detection. |
|
|
157
|
+
| `turnHandling` | `AgentSessionOptions['turnHandling']` | Endpointing delays, interruption sensitivity, preemptive generation. |
|
|
158
|
+
| `sessionOptions` / `inputOptions` / `outputOptions` | partial LiveKit options | Merged over what the helper builds. |
|
|
159
|
+
| **Memory** | | |
|
|
160
|
+
| `memory` | `false \| (args) => { thread, resource }` | Memory mapping. Defaults to `{ thread: metadata.threadId ?? room, resource: metadata.resourceId ?? thread }` when the agent has memory. |
|
|
161
|
+
| `memoryInstance` | `Memory \| (args) => Memory` | The `Memory` used to bootstrap the thread + persist the greeting on the **workflow/custom** path (no agent to source it from). Mastra storage is injected if the `Memory` has none. |
|
|
162
|
+
| `configuration` | `{ greeting?, consentPolicy?, endCall?, stt?, tts? }` | Grouped conversation & compliance config (see [Configuration](#configuration)). `greeting` (`text` — a string or a per-tenant resolver — plus `allowInterruptions`, `awaitPlayout`, `persist`, `repeatEvery`/`repeatText` for periodic AI re-disclosure), `consentPolicy` (extensible consent set, e.g. `summaryStorage`, surfaced on `onCallEnd`), `endCall` (agent-initiated hang-up; pair with `createEndCallTool`), and per-call `stt` / `tts` resolvers that pick this call's transcriber and voice (fall back to the top-level options). |
|
|
163
|
+
| **Lifecycle hooks** | | |
|
|
164
|
+
| `toolFeedback` | `(toolCall) => string \| void` | Speak filler while a tool runs (agent + workflow). |
|
|
165
|
+
| `onTurnComplete` | `VoiceTurnCompleteHook` | After each turn streams, **fire-and-forget**, off the audio path. |
|
|
166
|
+
| `onCallEnd` | `VoiceCallEndHook` | When the call ends, **awaited** within LiveKit's shutdown window. |
|
|
167
|
+
| `onSessionStart` | `(args) => …` | After the session starts — attach listeners, trigger replies, etc. |
|
|
168
|
+
| **Other** | | |
|
|
169
|
+
| `observability` | `boolean` | Voice-pipeline tracing. Defaults to `true`. |
|
|
170
|
+
|
|
171
|
+
## Configuration
|
|
172
|
+
|
|
173
|
+
`configuration` groups related conversation & compliance knobs in one place, so they don't each
|
|
174
|
+
become a top-level worker option — and it's where further compliance controls land as they ship.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
createLiveKitWorker({
|
|
178
|
+
mastra,
|
|
179
|
+
agent: 'support',
|
|
180
|
+
configuration: {
|
|
181
|
+
greeting: {
|
|
182
|
+
// Spoken via TTS at call start (no model round-trip). Doubles as a required AI disclosure.
|
|
183
|
+
text: 'You are speaking with an AI assistant. This call may be recorded. How can I help?',
|
|
184
|
+
allowInterruptions: false, // caller can't barge over the disclosure (EU AI Act Art. 50)
|
|
185
|
+
awaitPlayout: true, // hold post-greeting work until the disclosure finishes
|
|
186
|
+
persist: true, // save it to the memory thread (default true)
|
|
187
|
+
// Periodic re-disclosure on long calls: once this interval elapses, the NEXT turn's reply is
|
|
188
|
+
// prefixed with a short "you're speaking with an AI" reminder (spoken at the turn boundary,
|
|
189
|
+
// never mid-turn). Omit to disable.
|
|
190
|
+
repeatEvery: 3 * 60_000, // ~every 3 minutes (California SB 243 and similar)
|
|
191
|
+
repeatText: 'Quick reminder — you are speaking with an AI assistant.', // optional; has a default
|
|
192
|
+
},
|
|
193
|
+
// Consent requirements — a named, extensible set. Each item is independently required and
|
|
194
|
+
// independently granted, so new items are added without one global "consented" flag.
|
|
195
|
+
consentPolicy: {
|
|
196
|
+
summaryStorage: true, // or { required: true, purpose: 'storing a summary of this call' }
|
|
197
|
+
},
|
|
198
|
+
// Let the agent end the call itself. Pair with a `createEndCallTool` tool on the agent; the
|
|
199
|
+
// worker waits for the agent's closing words to play out, then hangs up (running onCallEnd).
|
|
200
|
+
endCall: {
|
|
201
|
+
message: 'Thanks for calling. Goodbye!', // optional non-interruptible sign-off before hangup
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Greeting / AI disclosure.** `text` is spoken at call start; `allowInterruptions: false` makes a
|
|
208
|
+
required disclosure play through; `awaitPlayout: true` waits for it before anything else runs;
|
|
209
|
+
`repeatEvery` re-discloses periodically on long calls. Under the EU AI Act (Art. 50) a person must be
|
|
210
|
+
told they're interacting with an AI at the first interaction.
|
|
211
|
+
|
|
212
|
+
**Per-tenant greeting.** `greeting.text` also takes a resolver — a function called once per call
|
|
213
|
+
(post-connect) with the call context (`metadata`, `requestContext`, `roomName`, `ctx`). Return a
|
|
214
|
+
greeting keyed off the dispatch metadata so one multi-tenant agent opens differently per tenant (the
|
|
215
|
+
disclosure options still apply to whatever it returns; return `undefined` for no greeting):
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
configuration: {
|
|
219
|
+
greeting: {
|
|
220
|
+
text: ({ requestContext }) => {
|
|
221
|
+
const tenant = TENANTS[requestContext?.tenantId as string];
|
|
222
|
+
return `Thanks for calling ${tenant?.name ?? 'us'}. You're speaking with an AI assistant.`;
|
|
223
|
+
},
|
|
224
|
+
allowInterruptions: false, // the disclosure still can't be barged over
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Consent.** `consentPolicy` _declares_ which consents the call needs (starting with `summaryStorage`
|
|
230
|
+
— consent to store a summary of the call). Capture the caller's decision at runtime with
|
|
231
|
+
**`createConsentTool`** (exported from `@mastra/livekit`) — add it to your agent, and it reads the
|
|
232
|
+
caller identity from the tool context and hands each decision to your store:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { createConsentTool } from '@mastra/livekit';
|
|
236
|
+
|
|
237
|
+
// in your agent's tools:
|
|
238
|
+
recordConsent: createConsentTool({
|
|
239
|
+
items: ['summaryStorage'],
|
|
240
|
+
onGrant: async ({ item, granted, resourceId }) => {
|
|
241
|
+
if (resourceId) await db.saveConsent(resourceId, item, granted); // your system of record
|
|
242
|
+
},
|
|
243
|
+
}),
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Then _enforce_ it: the requirements are surfaced on the `onCallEnd` hook (`args.configuration`), so
|
|
247
|
+
you only run the consent-gated action — e.g. the `memory.summarizeThread()` call that stores the
|
|
248
|
+
summary — when it isn't required or the caller granted it. Whether to gate at all is your policy
|
|
249
|
+
call: a permissive deployment skips `consentPolicy` entirely and always summarizes, while a
|
|
250
|
+
regulated one declares → captures → enforces (the runnable example ships both flavors). Further
|
|
251
|
+
controls (recording notice, data retention, human handoff) are planned to land here too.
|
|
252
|
+
|
|
253
|
+
**Agent-initiated hang-up.** `endCall` lets the agent end the call itself — say goodbye, then hang up.
|
|
254
|
+
Enable it under `configuration`, and add a matching tool to the agent with **`createEndCallTool`**
|
|
255
|
+
(both default to the tool name `'endCall'`). The tool only _signals_ intent — from inside
|
|
256
|
+
`agent.stream()` it can't reach the room — so the worker owns the hang-up: on each turn it watches for
|
|
257
|
+
the tool, waits for the agent's closing words to finish playing, holds a short drain (`drainMs`,
|
|
258
|
+
default 800ms) so audio still buffered at the caller isn't clipped, then disconnects, running
|
|
259
|
+
`onCallEnd` on the way out exactly as a caller hang-up does. It works on the
|
|
260
|
+
agent and workflow reply paths.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { createEndCallTool } from '@mastra/livekit';
|
|
264
|
+
|
|
265
|
+
// in your agent's tools:
|
|
266
|
+
endCall: createEndCallTool({
|
|
267
|
+
// optional bookkeeping — the tool reads the caller identity from its context
|
|
268
|
+
onEndCall: ({ reason, resourceId }) => log.info('agent ended call', { reason, resourceId }),
|
|
269
|
+
}),
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Instruct the agent to say its goodbye and then call `endCall` as its final action. `endCall.message`
|
|
273
|
+
adds a guaranteed non-interruptible sign-off spoken right before hanging up; `endCall.reason` sets the
|
|
274
|
+
shutdown reason in LiveKit logs; `endCall.maxWaitMs` caps how long to wait for the closing words
|
|
275
|
+
(default 30s). This is the AI-oversight companion to a future first-class human `handoff`.
|
|
276
|
+
|
|
277
|
+
**Backwards compatibility.** The previous top-level `greeting` (string) and `persistGreeting` options
|
|
278
|
+
still work — they're deprecated aliases for `configuration.greeting.text` and
|
|
279
|
+
`configuration.greeting.persist`. If both are set, `configuration.greeting` wins field-by-field, so
|
|
280
|
+
existing worker configs keep running unchanged while you migrate.
|
|
167
281
|
|
|
168
282
|
## Lifecycle hooks
|
|
169
283
|
|
|
@@ -184,9 +298,20 @@ createLiveKitWorker({
|
|
|
184
298
|
},
|
|
185
299
|
|
|
186
300
|
// 3. End-of-call: runs when the caller hangs up, AWAITED within LiveKit's shutdown grace window
|
|
187
|
-
// (so it finishes before the process exits). The place for end-of-call
|
|
188
|
-
|
|
189
|
-
|
|
301
|
+
// (so it finishes before the process exits). The place for end-of-call work — e.g. summarize
|
|
302
|
+
// the finished call into your own records. `memory.summarizeThread()` (@mastra/memory) runs a
|
|
303
|
+
// one-shot summarization + structured extraction over the whole call, outside observational
|
|
304
|
+
// memory's lifecycle — nothing is written back to memory; you decide where the result goes
|
|
305
|
+
// (return value and/or each Extractor's `onExtracted` hook).
|
|
306
|
+
onCallEnd: async ({ memory }) => {
|
|
307
|
+
if (!memory) return;
|
|
308
|
+
await myMemory.summarizeThread({
|
|
309
|
+
// your app's `Memory` (from @mastra/memory)
|
|
310
|
+
model: 'openai/gpt-4.1-mini',
|
|
311
|
+
threadId: memory.thread,
|
|
312
|
+
resourceId: memory.resource,
|
|
313
|
+
instructions: 'Summarize this call for the business owner.',
|
|
314
|
+
});
|
|
190
315
|
},
|
|
191
316
|
});
|
|
192
317
|
```
|
|
@@ -293,7 +418,8 @@ Single-process HTTP hosts run the **Mastra server** as-is (`node .mastra/output/
|
|
|
293
418
|
|
|
294
419
|
A complete, runnable reference lives in the Mastra monorepo at **[`examples/voice-agent`](https://github.com/mastra-ai/mastra/tree/main/examples/voice-agent)** — a trades-contractor front-desk voice agent that exercises nearly every feature here:
|
|
295
420
|
|
|
296
|
-
- **
|
|
421
|
+
- **Three workers, one agent name** (run one at a time): the default **agent** worker (`pnpm worker`) and **workflow** worker (`pnpm worker:workflow`, deterministic intent routing → memory-backed reply) are deliberately **permissive** — no consent friction, the end-of-call summary always runs. The **regulated** worker (`pnpm worker:regulated`, a "Northwind Financial" line) demonstrates every compliance control at once: non-interruptible AI disclosure, 45-second re-disclosure, a four-item runtime consent sweep with an audit ledger, agent-initiated hang-up with a compliance sign-off, and a consent-gated summary (no consent → no stored summary).
|
|
422
|
+
- **End-of-call summarization** — every finished call is distilled into a structured record (summary, sentiment, requested services) via `memory.summarizeThread()` + an `Extractor` whose `onExtracted` hook writes to the app's own store, from the `onCallEnd` hook.
|
|
297
423
|
- **Three memory layers** — working memory, semantic recall, and observational memory, all scoped to the caller.
|
|
298
424
|
- **Tools + deterministic reconciliation**, a tenant-context input processor, the `toolFeedback` / `onTurnComplete` / `onCallEnd` hooks, and full observability.
|
|
299
425
|
|
|
@@ -310,7 +436,7 @@ pnpm worker:download-files # one-time model download
|
|
|
310
436
|
|
|
311
437
|
# in two terminals:
|
|
312
438
|
pnpm dev # Mastra server + Studio at http://localhost:4111
|
|
313
|
-
pnpm worker # the voice worker (or `pnpm worker:workflow`)
|
|
439
|
+
pnpm worker # the voice worker (or `pnpm worker:workflow` / `pnpm worker:regulated`)
|
|
314
440
|
```
|
|
315
441
|
|
|
316
442
|
See the example's own [`README.md`](https://github.com/mastra-ai/mastra/tree/main/examples/voice-agent) for the scenario walkthrough and the memory/latency design notes.
|
package/dist/bridge.d.ts
CHANGED
|
@@ -4,12 +4,58 @@ import type { Agent as MastraAgent, AgentExecutionOptionsBase } from '@mastra/co
|
|
|
4
4
|
import type { TracingContext } from '@mastra/core/observability';
|
|
5
5
|
import { RequestContext } from '@mastra/core/request-context';
|
|
6
6
|
import type { VoiceTurnMessage } from './messages.js';
|
|
7
|
+
/** Default spoken text for periodic AI re-disclosure. See {@link MastraVoiceAgentOptions.greetingReminder}. */
|
|
8
|
+
export declare const DEFAULT_DISCLOSURE_REMINDER = "Just a reminder, you're speaking with an AI assistant.";
|
|
9
|
+
/**
|
|
10
|
+
* Tracks periodic AI re-disclosure for a single call. `due()` returns the reminder text once
|
|
11
|
+
* `everyMs` has elapsed since the last disclosure (resetting the clock), otherwise `undefined`.
|
|
12
|
+
* Time is injectable so the interval logic is deterministically testable.
|
|
13
|
+
*/
|
|
14
|
+
export declare class DisclosureReminder {
|
|
15
|
+
private readonly everyMs;
|
|
16
|
+
private readonly text;
|
|
17
|
+
private lastAt;
|
|
18
|
+
constructor(everyMs: number, text: string, now?: number);
|
|
19
|
+
/** Call once per turn: the reminder text if it's due, else `undefined`. Does not reset the clock —
|
|
20
|
+
* call {@link DisclosureReminder.markDelivered} once the reminder is actually threaded into the
|
|
21
|
+
* outgoing reply, so a reminder that never makes it out isn't silently skipped for a full interval. */
|
|
22
|
+
due(now?: number): string | undefined;
|
|
23
|
+
/** Resets the clock. Call only once the reminder text from {@link due} was actually emitted. */
|
|
24
|
+
markDelivered(now?: number): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Wraps `source` in a stream that emits `text` as a single leading chunk (with a trailing space, so
|
|
28
|
+
* TTS pauses before the reply) before piping the rest of `source` through unchanged. Cancelling the
|
|
29
|
+
* wrapper cancels `source` — so barge-in still aborts the underlying generation.
|
|
30
|
+
*/
|
|
31
|
+
export declare function prependText(source: ReadableStream<string>, text: string): ReadableStream<string>;
|
|
7
32
|
export type MastraStreamOptions = Partial<AgentExecutionOptionsBase<unknown>>;
|
|
8
33
|
export interface VoiceToolCall {
|
|
9
34
|
toolCallId: string;
|
|
10
35
|
toolName: string;
|
|
11
36
|
args?: unknown;
|
|
12
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Token usage for one turn, captured from the model's `finish` chunk. Field names mirror LiveKit's
|
|
40
|
+
* `CompletionUsage` so the plugin can forward it into `metrics_collected` without remapping.
|
|
41
|
+
*/
|
|
42
|
+
export interface VoiceTurnUsage {
|
|
43
|
+
/** Tokens in the prompt (LiveKit `promptTokens`). */
|
|
44
|
+
promptTokens: number;
|
|
45
|
+
/** Tokens in the completion (LiveKit `completionTokens`). */
|
|
46
|
+
completionTokens: number;
|
|
47
|
+
/** Cached prompt tokens (LiveKit `promptCachedTokens`). */
|
|
48
|
+
promptCachedTokens: number;
|
|
49
|
+
/** Total tokens for the turn. */
|
|
50
|
+
totalTokens: number;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Maps a Mastra `finish` chunk's usage (`payload.output.usage`, AI-SDK `LanguageModelUsage`) to the
|
|
54
|
+
* LiveKit-shaped {@link VoiceTurnUsage}, or `undefined` when the chunk carries no token counts.
|
|
55
|
+
* Handles both the flat V2 usage shape (`inputTokens`/`outputTokens`) and the nested V3 shape
|
|
56
|
+
* (`inputTokens.total`/`outputTokens.total`).
|
|
57
|
+
*/
|
|
58
|
+
export declare function mapTurnUsage(usage: unknown): VoiceTurnUsage | undefined;
|
|
13
59
|
export interface MastraVoiceAgentMemory {
|
|
14
60
|
thread: string;
|
|
15
61
|
resource?: string;
|
|
@@ -37,6 +83,13 @@ export interface VoiceTurnContext {
|
|
|
37
83
|
requestContext?: RequestContext;
|
|
38
84
|
/** Voice-call span context, so each turn's generation nests under the call trace. */
|
|
39
85
|
tracingContext?: TracingContext;
|
|
86
|
+
/**
|
|
87
|
+
* Internal, per-turn side channel for token usage. A generator invokes this once, when the
|
|
88
|
+
* `finish` chunk carries usage, so the caller (e.g. `MastraLLMStream`) can attribute usage to
|
|
89
|
+
* exactly this turn — kept on the context (not on generator options) so overlapping turns from
|
|
90
|
+
* preemptive generation can't misattribute usage. Fire-and-forget; the generator does not await it.
|
|
91
|
+
*/
|
|
92
|
+
onUsage?: (usage: VoiceTurnUsage) => void;
|
|
40
93
|
}
|
|
41
94
|
/**
|
|
42
95
|
* What a turn produced, handed to {@link VoiceTurnCompleteHook} after the reply finishes.
|
|
@@ -48,6 +101,8 @@ export interface VoiceTurnResult {
|
|
|
48
101
|
toolCalls: VoiceToolCall[];
|
|
49
102
|
/** True when barge-in cut the turn short before it finished streaming. */
|
|
50
103
|
interrupted: boolean;
|
|
104
|
+
/** Token usage for the turn when the model reported it in its `finish` chunk. */
|
|
105
|
+
usage?: VoiceTurnUsage;
|
|
51
106
|
}
|
|
52
107
|
/** {@link VoiceTurnContext} plus the reply it produced. Passed to {@link VoiceTurnCompleteHook}. */
|
|
53
108
|
export interface VoiceTurnCompleteContext extends VoiceTurnContext {
|
|
@@ -77,6 +132,8 @@ export interface AgentReplyGeneratorOptions {
|
|
|
77
132
|
streamOptions?: MastraStreamOptions;
|
|
78
133
|
/** Speak a short phrase while a tool call runs. See {@link MastraVoiceAgentOptions.toolFeedback}. */
|
|
79
134
|
toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;
|
|
135
|
+
/** Notified as each tool-call chunk arrives, mid-stream. See {@link MastraVoiceAgentOptions.onToolCall}. */
|
|
136
|
+
onToolCall?: (toolCall: VoiceToolCall) => void;
|
|
80
137
|
/** Fired off the audio path after the reply streams. See {@link MastraVoiceAgentOptions.onTurnComplete}. */
|
|
81
138
|
onTurnComplete?: VoiceTurnCompleteHook;
|
|
82
139
|
}
|
|
@@ -112,6 +169,13 @@ export interface MastraVoiceAgentOptions {
|
|
|
112
169
|
* takes its own equivalent via `createWorkflowReplyGenerator`.
|
|
113
170
|
*/
|
|
114
171
|
toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;
|
|
172
|
+
/**
|
|
173
|
+
* Called as each tool call starts mid-reply (before the tool result is known), the building block
|
|
174
|
+
* for tool-driven side effects — analytics, agent-initiated hang-up — without waiting for the turn
|
|
175
|
+
* to finish. Runs synchronously on the stream; keep it cheap and non-throwing. Applies to the agent
|
|
176
|
+
* generator built here; the workflow generator surfaces tool calls via `onTurnComplete` instead.
|
|
177
|
+
*/
|
|
178
|
+
onToolCall?: (toolCall: VoiceToolCall) => void;
|
|
115
179
|
/**
|
|
116
180
|
* Called once per turn after the reply has finished streaming to text-to-speech. Runs off the
|
|
117
181
|
* audio path and fire-and-forget — the turn does not await it — so post-turn memory
|
|
@@ -122,6 +186,17 @@ export interface MastraVoiceAgentOptions {
|
|
|
122
186
|
* workflow generator takes its own via `createWorkflowReplyGenerator`.
|
|
123
187
|
*/
|
|
124
188
|
onTurnComplete?: VoiceTurnCompleteHook;
|
|
189
|
+
/**
|
|
190
|
+
* Periodic AI re-disclosure. When set, once `everyMs` has elapsed since the last disclosure the
|
|
191
|
+
* NEXT turn's reply is prefixed with `text` (spoken at the turn boundary, never mid-turn), so long
|
|
192
|
+
* calls keep re-disclosing the AI status. Applies to the agent and workflow/custom generators. The
|
|
193
|
+
* worker derives this from `configuration.greeting.repeatEvery` / `repeatText`; `text` defaults to
|
|
194
|
+
* {@link DEFAULT_DISCLOSURE_REMINDER}.
|
|
195
|
+
*/
|
|
196
|
+
greetingReminder?: {
|
|
197
|
+
everyMs: number;
|
|
198
|
+
text?: string;
|
|
199
|
+
};
|
|
125
200
|
/** Extra options merged into every `agent.stream()` call (agent generator only). */
|
|
126
201
|
streamOptions?: MastraStreamOptions;
|
|
127
202
|
/** LiveKit agent instructions. Unused for reply generation (the Mastra agent/workflow applies its own). */
|
|
@@ -146,6 +221,7 @@ export declare class MastraVoiceAgent extends voice.Agent {
|
|
|
146
221
|
readonly requestContext?: RequestContext;
|
|
147
222
|
readonly streamOptions?: MastraStreamOptions;
|
|
148
223
|
private readonly replyGenerator;
|
|
224
|
+
private readonly reminder?;
|
|
149
225
|
constructor(options: MastraVoiceAgentOptions);
|
|
150
226
|
llmNode(chatCtx: llm.ChatContext, _toolCtx: llm.ToolContext, _modelSettings: voice.ModelSettings): Promise<ReadableStream<llm.ChatChunk | string> | null>;
|
|
151
227
|
}
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,KAAK,EAAE,KAAK,IAAI,WAAW,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAInD,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC;AAE9E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,mGAAmG;IACnG,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC;IACzB,gFAAgF;IAChF,MAAM,EAAE,sBAAsB,GAAG,KAAK,CAAC;IACvC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,qFAAqF;IACrF,cAAc,CAAC,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|