@mastra/livekit 0.2.0 → 0.3.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 +267 -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 +16 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,272 @@
|
|
|
1
1
|
# @mastra/livekit
|
|
2
2
|
|
|
3
|
+
## 0.3.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
|
+
|
|
134
|
+
### Patch Changes
|
|
135
|
+
|
|
136
|
+
- Updated dependencies [[`bd6d240`](https://github.com/mastra-ai/mastra/commit/bd6d2402db93dddaef0721667e7e8a030e7c6e16), [`0111486`](https://github.com/mastra-ai/mastra/commit/01114867612593eef5cfa2fda6a1194dfedda841), [`96a3749`](https://github.com/mastra-ai/mastra/commit/96a37492235f5b8076b3e3177d83ed5a5e44a640), [`fe1bda0`](https://github.com/mastra-ai/mastra/commit/fe1bda06f6af92a694a51712db747cda1e7185f0), [`25e7c12`](https://github.com/mastra-ai/mastra/commit/25e7c126a770069ae7fb7ecf1d2adb40e017b009), [`1ce5121`](https://github.com/mastra-ai/mastra/commit/1ce512155d122bb21f47d98383e82ffbf84b39e8), [`fb8aea3`](https://github.com/mastra-ai/mastra/commit/fb8aea384291e77311be3a64ee1717320d5c3c73), [`4adc391`](https://github.com/mastra-ai/mastra/commit/4adc3911075249c352bb4832d2471922826344de), [`a5c6337`](https://github.com/mastra-ai/mastra/commit/a5c6337d23c7686c81a32ce62f550f610543a240), [`3cfc47a`](https://github.com/mastra-ai/mastra/commit/3cfc47a6b89940aadd0f46fb01ae9624a73a865d), [`2bb7817`](https://github.com/mastra-ai/mastra/commit/2bb78176112fde628483de2830528f7eee911e56), [`51d9870`](https://github.com/mastra-ai/mastra/commit/51d987032c689c2855374d0f244f5d654da809d1), [`5cab274`](https://github.com/mastra-ai/mastra/commit/5cab2744250e22d12fefa7b32637dce224233cee), [`7fa27d3`](https://github.com/mastra-ai/mastra/commit/7fa27d3b6f5ed68cd34e454a4d3ad9c482a0cfbc), [`8b97958`](https://github.com/mastra-ai/mastra/commit/8b979589f9aa59ba67cac565949475f2ffeb4ac3), [`8410541`](https://github.com/mastra-ai/mastra/commit/84105412c60ecd3bb33a9838146f59c4b588228f), [`a58dcbb`](https://github.com/mastra-ai/mastra/commit/a58dcbb546d7e1d65ebdc1f39e55f0908fcd9391), [`aa38805`](https://github.com/mastra-ai/mastra/commit/aa38805b878b827403be785eb90688d7172f5a40), [`153bd3b`](https://github.com/mastra-ai/mastra/commit/153bd3b396bdfed6b74cf43de12db8fd2d83c04a), [`45a8e65`](https://github.com/mastra-ai/mastra/commit/45a8e65e1556d1362cb3f25187023c36de26661d), [`e955965`](https://github.com/mastra-ai/mastra/commit/e955965dce575a903e37cf054d28ea99aa48785e), [`2d22570`](https://github.com/mastra-ai/mastra/commit/2d22570c7dfdd02123d0ecc529efb05ccba2d9fc), [`07bb863`](https://github.com/mastra-ai/mastra/commit/07bb8631919c6f7cf377dccd45b096e0f17fbed0), [`c8ed116`](https://github.com/mastra-ai/mastra/commit/c8ed11699f62bcac70102ab4ec84d80d20541da6), [`01b338c`](https://github.com/mastra-ai/mastra/commit/01b338c56271f0219606710e3e8b26dee27ac6c2), [`a99eae8`](https://github.com/mastra-ai/mastra/commit/a99eae8908e500c1b2d12f9d277be616b98617a5), [`860ef7e`](https://github.com/mastra-ai/mastra/commit/860ef7e77d92b63469cbe5857aa1e626197e43e9), [`17e818c`](https://github.com/mastra-ai/mastra/commit/17e818c51a958ba90641b1a959dc38faf8c034e9), [`edce8d2`](https://github.com/mastra-ai/mastra/commit/edce8d2769f19e27a05737c627af2d765472a4f8), [`8a586ec`](https://github.com/mastra-ai/mastra/commit/8a586eca9a4914f31dff6140d0d45ac375b00669), [`4451dfe`](https://github.com/mastra-ai/mastra/commit/4451dfe857428e7abcc0261a507a2e186dae6d47), [`8b7361d`](https://github.com/mastra-ai/mastra/commit/8b7361d35de68b80d05d30a74e0c69e7218fd612), [`1d39058`](https://github.com/mastra-ai/mastra/commit/1d39058e548efd691799985d5c8af2737f1c3bd2), [`3927473`](https://github.com/mastra-ai/mastra/commit/392747323ddb10c643d12be7b9ae913159dfaeed), [`dce50dc`](https://github.com/mastra-ai/mastra/commit/dce50dc9a1c1fcd0f427bb5f6250ec74910cb04b), [`fd13f8e`](https://github.com/mastra-ai/mastra/commit/fd13f8e21990f9904c3eedba3a626bb4a929cdb8), [`634caff`](https://github.com/mastra-ai/mastra/commit/634caff29a9200ad058b67d53f96d9e5832fb8a2), [`f703f87`](https://github.com/mastra-ai/mastra/commit/f703f878de072d51fda557f9c50867d8252bef05), [`3e26c87`](https://github.com/mastra-ai/mastra/commit/3e26c87de0c5bc2583b795ce6ca5889b6b161acb), [`33f2b88`](https://github.com/mastra-ai/mastra/commit/33f2b88842c09a567f906fac4cb61cd5277ced59), [`177010f`](https://github.com/mastra-ai/mastra/commit/177010ff096d2e4b28d89803be5b1a4cad2a0d6b), [`0ad646f`](https://github.com/mastra-ai/mastra/commit/0ad646f71a530f2454664299e5e01bfd13fa12e5), [`b486abf`](https://github.com/mastra-ai/mastra/commit/b486abfa2a7528c6f527e4015c819ea9fa54aaad), [`54a51e0`](https://github.com/mastra-ai/mastra/commit/54a51e0a484fe1ebad3fb1f7ef5282a075709eb7), [`c43f3a9`](https://github.com/mastra-ai/mastra/commit/c43f3a9d1efde99b38789364ba4d0ba670f430e3), [`a5008f2`](https://github.com/mastra-ai/mastra/commit/a5008f22ae710ad9402ea9f2547d8c02f74d384b), [`e2d5f37`](https://github.com/mastra-ai/mastra/commit/e2d5f373bd289be534d5f8694d34465010533df6), [`4ce0163`](https://github.com/mastra-ai/mastra/commit/4ce0163dc86e675a86809685c8ce6c49f1aeb87e), [`4378341`](https://github.com/mastra-ai/mastra/commit/43783412df5ea3dd35f5b1f6e4851e79c346fc89)]:
|
|
137
|
+
- @mastra/core@1.51.0
|
|
138
|
+
|
|
139
|
+
## 0.3.0-alpha.0
|
|
140
|
+
|
|
141
|
+
### Minor Changes
|
|
142
|
+
|
|
143
|
+
- 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))
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
export default createLiveKitWorker({
|
|
147
|
+
mastra,
|
|
148
|
+
agent: 'support',
|
|
149
|
+
stt: 'deepgram/nova-3',
|
|
150
|
+
tts: 'cartesia/sonic-3', // fallback voice
|
|
151
|
+
configuration: {
|
|
152
|
+
// Give each tenant its own voice, resolved per call from the dispatch metadata.
|
|
153
|
+
tts: ({ requestContext }) => tenantVoices[requestContext?.tenant as string],
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
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.
|
|
159
|
+
|
|
160
|
+
- 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))
|
|
161
|
+
|
|
162
|
+
Before, the worker wrapper always owned the LiveKit session:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { createLiveKitWorker } from '@mastra/livekit/worker';
|
|
166
|
+
import { mastra } from './index';
|
|
167
|
+
|
|
168
|
+
export default createLiveKitWorker({
|
|
169
|
+
mastra,
|
|
170
|
+
agent: 'support',
|
|
171
|
+
stt: 'deepgram/nova-3',
|
|
172
|
+
tts: 'cartesia/sonic-3',
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Now you can own the session and keep Mastra as the LLM component:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { voice } from '@livekit/agents';
|
|
180
|
+
import { MastraLLM } from '@mastra/livekit/plugin';
|
|
181
|
+
|
|
182
|
+
const session = new voice.AgentSession({
|
|
183
|
+
llm: new MastraLLM({
|
|
184
|
+
remote: { baseUrl: process.env.MASTRA_URL!, agentId: 'support' },
|
|
185
|
+
memory: { thread: callId, resource: userId },
|
|
186
|
+
}),
|
|
187
|
+
stt: 'deepgram/nova-3',
|
|
188
|
+
tts: 'cartesia/sonic-3',
|
|
189
|
+
// Required with `memory`: LiveKit enables preemptive generation by default.
|
|
190
|
+
turnHandling: { preemptiveGeneration: { enabled: false } },
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`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.
|
|
195
|
+
|
|
196
|
+
**New transport and helpers**
|
|
197
|
+
- 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.
|
|
198
|
+
- 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.
|
|
199
|
+
|
|
200
|
+
**Improvements to the existing worker**
|
|
201
|
+
- 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.
|
|
202
|
+
- 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.
|
|
203
|
+
- `onTurnComplete` now receives the turn's token usage as `result.usage`.
|
|
204
|
+
|
|
205
|
+
- 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))
|
|
206
|
+
|
|
207
|
+
**Greeting and AI disclosure**
|
|
208
|
+
|
|
209
|
+
`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).
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
createLiveKitWorker({
|
|
213
|
+
mastra,
|
|
214
|
+
agent: 'support',
|
|
215
|
+
configuration: {
|
|
216
|
+
greeting: {
|
|
217
|
+
text: 'You are speaking with an AI assistant. This call may be recorded. How can I help?',
|
|
218
|
+
allowInterruptions: false,
|
|
219
|
+
awaitPlayout: true,
|
|
220
|
+
repeatEvery: 3 * 60_000, // re-disclose ~every 3 minutes
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Per-tenant greeting**
|
|
227
|
+
|
|
228
|
+
`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:
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
greeting: {
|
|
232
|
+
text: ({ metadata }) => `Thanks for calling ${tenantName(metadata)}. You're speaking with an AI assistant.`,
|
|
233
|
+
allowInterruptions: false,
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Consent**
|
|
238
|
+
|
|
239
|
+
`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).
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import { createConsentTool } from '@mastra/livekit';
|
|
243
|
+
|
|
244
|
+
// in your agent's tools:
|
|
245
|
+
recordConsent: createConsentTool({
|
|
246
|
+
items: ['summaryStorage'],
|
|
247
|
+
onGrant: async ({ item, granted, resourceId }) => {
|
|
248
|
+
if (resourceId) await db.saveConsent(resourceId, item, granted);
|
|
249
|
+
},
|
|
250
|
+
}),
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Agent-initiated hang-up**
|
|
254
|
+
|
|
255
|
+
`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.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { createEndCallTool } from '@mastra/livekit';
|
|
259
|
+
|
|
260
|
+
// in your agent's tools:
|
|
261
|
+
endCall: (createEndCallTool(),
|
|
262
|
+
// on the worker:
|
|
263
|
+
createLiveKitWorker({ mastra, agent: 'support', configuration: { endCall: {} } }));
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Backwards compatible**
|
|
267
|
+
|
|
268
|
+
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.
|
|
269
|
+
|
|
3
270
|
## 0.2.0
|
|
4
271
|
|
|
5
272
|
### 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.
|