@mastra/livekit 0.3.0 → 0.3.1-alpha.1

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.
@@ -1,603 +1,688 @@
1
- 'use strict';
2
-
3
- var chunkMWTEZOBS_cjs = require('./chunk-MWTEZOBS.cjs');
4
- var chunkDBVKNDAQ_cjs = require('./chunk-DBVKNDAQ.cjs');
5
- var agents = require('@livekit/agents');
6
- var requestContext = require('@mastra/core/request-context');
7
- var observability = require('@mastra/core/observability');
8
- var crypto = require('crypto');
9
- var url = require('url');
10
-
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_workflow_generator = require("./workflow-generator-B67QrY8L.cjs");
3
+ const require_remote = require("./remote-BZ7eyB1q.cjs");
4
+ let crypto = require("crypto");
5
+ let _livekit_agents = require("@livekit/agents");
6
+ let _mastra_core_request_context = require("@mastra/core/request-context");
7
+ let _mastra_core_observability = require("@mastra/core/observability");
8
+ let url = require("url");
9
+ //#region src/observability.ts
11
10
  function modelMeta(metadata) {
12
- const out = {};
13
- if (metadata?.modelProvider) out.modelProvider = metadata.modelProvider;
14
- if (metadata?.modelName) out.modelName = metadata.modelName;
15
- return out;
11
+ const out = {};
12
+ if (metadata?.modelProvider) out.modelProvider = metadata.modelProvider;
13
+ if (metadata?.modelName) out.modelName = metadata.modelName;
14
+ return out;
16
15
  }
17
- var ms = (n) => `${Math.round(n)}ms`;
18
- var secs = (n) => `${(n / 1e3).toFixed(1)}s`;
16
+ const ms = (n) => `${Math.round(n)}ms`;
17
+ const secs = (n) => `${(n / 1e3).toFixed(1)}s`;
18
+ /**
19
+ * Maps a LiveKit pipeline metric to an event-span name and the latency/usage fields worth
20
+ * recording. The name carries the headline number so it reads in the trace timeline without
21
+ * opening the span. Metrics this release does not surface (interruption, EOT inference, avatar)
22
+ * return `undefined` — they still feed the session usage roll-up via the ModelUsageCollector.
23
+ */
19
24
  function describeMetric(metric) {
20
- switch (metric.type) {
21
- case "eou_metrics":
22
- return {
23
- name: `eou ${ms(metric.endOfUtteranceDelayMs)}`,
24
- data: {
25
- endOfUtteranceDelayMs: metric.endOfUtteranceDelayMs,
26
- transcriptionDelayMs: metric.transcriptionDelayMs,
27
- onUserTurnCompletedDelayMs: metric.onUserTurnCompletedDelayMs
28
- }
29
- };
30
- case "stt_metrics":
31
- return {
32
- name: `stt ${secs(metric.audioDurationMs)}`,
33
- data: {
34
- audioDurationMs: metric.audioDurationMs,
35
- durationMs: metric.durationMs,
36
- streamed: metric.streamed,
37
- ...modelMeta(metric.metadata)
38
- }
39
- };
40
- case "llm_metrics":
41
- return {
42
- name: `llm ttft ${ms(metric.ttftMs)}`,
43
- data: {
44
- ttftMs: metric.ttftMs,
45
- durationMs: metric.durationMs,
46
- tokensPerSecond: metric.tokensPerSecond,
47
- promptTokens: metric.promptTokens,
48
- completionTokens: metric.completionTokens,
49
- totalTokens: metric.totalTokens,
50
- cancelled: metric.cancelled,
51
- ...modelMeta(metric.metadata)
52
- }
53
- };
54
- case "tts_metrics":
55
- return {
56
- name: `tts ttfb ${ms(metric.ttfbMs)}`,
57
- data: {
58
- ttfbMs: metric.ttfbMs,
59
- durationMs: metric.durationMs,
60
- audioDurationMs: metric.audioDurationMs,
61
- charactersCount: metric.charactersCount,
62
- cancelled: metric.cancelled,
63
- streamed: metric.streamed,
64
- ...modelMeta(metric.metadata)
65
- }
66
- };
67
- case "vad_metrics":
68
- return {
69
- name: "vad",
70
- data: {
71
- idleTimeMs: metric.idleTimeMs,
72
- inferenceCount: metric.inferenceCount,
73
- inferenceDurationTotalMs: metric.inferenceDurationTotalMs
74
- }
75
- };
76
- case "realtime_model_metrics":
77
- return {
78
- name: `realtime ttft ${ms(metric.ttftMs)}`,
79
- data: {
80
- ttftMs: metric.ttftMs,
81
- durationMs: metric.durationMs,
82
- tokensPerSecond: metric.tokensPerSecond,
83
- promptTokens: metric.inputTokens,
84
- completionTokens: metric.outputTokens,
85
- totalTokens: metric.totalTokens,
86
- ...modelMeta(metric.metadata)
87
- }
88
- };
89
- default:
90
- return void 0;
91
- }
25
+ switch (metric.type) {
26
+ case "eou_metrics": return {
27
+ name: `eou ${ms(metric.endOfUtteranceDelayMs)}`,
28
+ data: {
29
+ endOfUtteranceDelayMs: metric.endOfUtteranceDelayMs,
30
+ transcriptionDelayMs: metric.transcriptionDelayMs,
31
+ onUserTurnCompletedDelayMs: metric.onUserTurnCompletedDelayMs
32
+ }
33
+ };
34
+ case "stt_metrics": return {
35
+ name: `stt ${secs(metric.audioDurationMs)}`,
36
+ data: {
37
+ audioDurationMs: metric.audioDurationMs,
38
+ durationMs: metric.durationMs,
39
+ streamed: metric.streamed,
40
+ ...modelMeta(metric.metadata)
41
+ }
42
+ };
43
+ case "llm_metrics": return {
44
+ name: `llm ttft ${ms(metric.ttftMs)}`,
45
+ data: {
46
+ ttftMs: metric.ttftMs,
47
+ durationMs: metric.durationMs,
48
+ tokensPerSecond: metric.tokensPerSecond,
49
+ promptTokens: metric.promptTokens,
50
+ completionTokens: metric.completionTokens,
51
+ totalTokens: metric.totalTokens,
52
+ cancelled: metric.cancelled,
53
+ ...modelMeta(metric.metadata)
54
+ }
55
+ };
56
+ case "tts_metrics": return {
57
+ name: `tts ttfb ${ms(metric.ttfbMs)}`,
58
+ data: {
59
+ ttfbMs: metric.ttfbMs,
60
+ durationMs: metric.durationMs,
61
+ audioDurationMs: metric.audioDurationMs,
62
+ charactersCount: metric.charactersCount,
63
+ cancelled: metric.cancelled,
64
+ streamed: metric.streamed,
65
+ ...modelMeta(metric.metadata)
66
+ }
67
+ };
68
+ case "vad_metrics": return {
69
+ name: "vad",
70
+ data: {
71
+ idleTimeMs: metric.idleTimeMs,
72
+ inferenceCount: metric.inferenceCount,
73
+ inferenceDurationTotalMs: metric.inferenceDurationTotalMs
74
+ }
75
+ };
76
+ case "realtime_model_metrics": return {
77
+ name: `realtime ttft ${ms(metric.ttftMs)}`,
78
+ data: {
79
+ ttftMs: metric.ttftMs,
80
+ durationMs: metric.durationMs,
81
+ tokensPerSecond: metric.tokensPerSecond,
82
+ promptTokens: metric.inputTokens,
83
+ completionTokens: metric.outputTokens,
84
+ totalTokens: metric.totalTokens,
85
+ ...modelMeta(metric.metadata)
86
+ }
87
+ };
88
+ default: return;
89
+ }
92
90
  }
91
+ /**
92
+ * Opens a `voice call` trace for one LiveKit session and bridges LiveKit's voice-pipeline
93
+ * metrics into Mastra observability.
94
+ *
95
+ * The returned root span groups everything about the call: each conversation turn's Mastra
96
+ * agent run (nested via {@link VoiceCallObservability.tracingContext}) plus an event span for
97
+ * every STT, TTS, end-of-utterance, VAD, and LLM-latency metric LiveKit emits. Metrics are
98
+ * point-in-time, so they're recorded as event spans (no duration) with the value in the name.
99
+ * The root closes on the session `close` event with a per-model usage roll-up (token, character,
100
+ * and audio totals for the whole call) from a `ModelUsageCollector`.
101
+ *
102
+ * Returns `undefined` when the Mastra instance has no observability configured, so callers
103
+ * can treat instrumentation as a no-op without branching on config.
104
+ */
93
105
  function startVoiceCallObservability(options) {
94
- const span = observability.getOrCreateSpan({
95
- mastra: options.mastra,
96
- type: observability.SpanType.GENERIC,
97
- name: "voice call",
98
- requestContext: options.requestContext,
99
- metadata: {
100
- agentId: options.agentId,
101
- roomName: options.roomName,
102
- ...options.metadata.threadId ? { threadId: options.metadata.threadId } : {},
103
- ...options.metadata.resourceId ? { resourceId: options.metadata.resourceId } : {}
104
- }
105
- });
106
- if (!span) return void 0;
107
- const usage = new agents.metrics.ModelUsageCollector();
108
- let finalized = false;
109
- const finalize = (opts) => {
110
- if (finalized) return;
111
- finalized = true;
112
- const summary = usage.flatten();
113
- if (opts?.error) {
114
- span.error({
115
- error: opts.error instanceof Error ? opts.error : new Error(String(opts.error)),
116
- metadata: { usage: summary }
117
- });
118
- } else {
119
- span.end({ output: { usage: summary } });
120
- }
121
- };
122
- return {
123
- span,
124
- tracingContext: { currentSpan: span },
125
- attach(session) {
126
- session.on(agents.voice.AgentSessionEventTypes.MetricsCollected, (event) => {
127
- usage.collect(event.metrics);
128
- const described = describeMetric(event.metrics);
129
- if (!described) return;
130
- span.createEventSpan({ type: observability.SpanType.GENERIC, name: described.name, output: described.data });
131
- });
132
- session.on(agents.voice.AgentSessionEventTypes.Close, (event) => {
133
- finalize({ error: event?.error ?? void 0 });
134
- });
135
- },
136
- finalize
137
- };
106
+ const span = (0, _mastra_core_observability.getOrCreateSpan)({
107
+ mastra: options.mastra,
108
+ type: _mastra_core_observability.SpanType.GENERIC,
109
+ name: "voice call",
110
+ requestContext: options.requestContext,
111
+ metadata: {
112
+ agentId: options.agentId,
113
+ roomName: options.roomName,
114
+ ...options.metadata.threadId ? { threadId: options.metadata.threadId } : {},
115
+ ...options.metadata.resourceId ? { resourceId: options.metadata.resourceId } : {}
116
+ }
117
+ });
118
+ if (!span) return void 0;
119
+ const usage = new _livekit_agents.metrics.ModelUsageCollector();
120
+ let finalized = false;
121
+ const finalize = (opts) => {
122
+ if (finalized) return;
123
+ finalized = true;
124
+ const summary = usage.flatten();
125
+ if (opts?.error) span.error({
126
+ error: opts.error instanceof Error ? opts.error : new Error(String(opts.error)),
127
+ metadata: { usage: summary }
128
+ });
129
+ else span.end({ output: { usage: summary } });
130
+ };
131
+ return {
132
+ span,
133
+ tracingContext: { currentSpan: span },
134
+ attach(session) {
135
+ session.on(_livekit_agents.voice.AgentSessionEventTypes.MetricsCollected, (event) => {
136
+ usage.collect(event.metrics);
137
+ const described = describeMetric(event.metrics);
138
+ if (!described) return;
139
+ span.createEventSpan({
140
+ type: _mastra_core_observability.SpanType.GENERIC,
141
+ name: described.name,
142
+ output: described.data
143
+ });
144
+ });
145
+ session.on(_livekit_agents.voice.AgentSessionEventTypes.Close, (event) => {
146
+ finalize({ error: event?.error ?? void 0 });
147
+ });
148
+ },
149
+ finalize
150
+ };
138
151
  }
139
- async function ensureVoiceCallThread({
140
- memory,
141
- threadId,
142
- resourceId,
143
- roomName
144
- }) {
145
- const existing = await memory.getThreadById({ threadId });
146
- if (existing) return;
147
- await memory.createThread({
148
- threadId,
149
- resourceId,
150
- title: "Voice call",
151
- metadata: { source: "livekit", roomName }
152
- });
152
+ //#endregion
153
+ //#region src/voice-thread.ts
154
+ /**
155
+ * Creates the call's memory thread up front when it doesn't exist yet, titled and tagged
156
+ * so it reads as a voice call in Studio's thread list instead of an untitled thread that
157
+ * only appears after the first exchange.
158
+ */
159
+ async function ensureVoiceCallThread({ memory, threadId, resourceId, roomName }) {
160
+ if (await memory.getThreadById({ threadId })) return;
161
+ await memory.createThread({
162
+ threadId,
163
+ resourceId,
164
+ title: "Voice call",
165
+ metadata: {
166
+ source: "livekit",
167
+ roomName
168
+ }
169
+ });
153
170
  }
154
- async function persistSpokenGreeting({
155
- memory,
156
- threadId,
157
- resourceId,
158
- greeting
159
- }) {
160
- if (!greeting.trim()) return;
161
- const message = {
162
- id: crypto.randomUUID(),
163
- role: "assistant",
164
- type: "text",
165
- createdAt: /* @__PURE__ */ new Date(),
166
- threadId,
167
- resourceId,
168
- content: {
169
- format: 2,
170
- parts: [{ type: "text", text: greeting }],
171
- metadata: { source: "voice", kind: "greeting" }
172
- }
173
- };
174
- await memory.saveMessages({ messages: [message] });
171
+ /**
172
+ * Persists the spoken greeting as an assistant message. The greeting is spoken via TTS
173
+ * without going through the Mastra agent, so without this the saved thread would start
174
+ * at the caller's first words instead of being a faithful call transcript.
175
+ */
176
+ async function persistSpokenGreeting({ memory, threadId, resourceId, greeting }) {
177
+ if (!greeting.trim()) return;
178
+ const message = {
179
+ id: (0, crypto.randomUUID)(),
180
+ role: "assistant",
181
+ type: "text",
182
+ createdAt: /* @__PURE__ */ new Date(),
183
+ threadId,
184
+ resourceId,
185
+ content: {
186
+ format: 2,
187
+ parts: [{
188
+ type: "text",
189
+ text: greeting
190
+ }],
191
+ metadata: {
192
+ source: "voice",
193
+ kind: "greeting"
194
+ }
195
+ }
196
+ };
197
+ await memory.saveMessages({ messages: [message] });
175
198
  }
176
-
177
- // src/worker-setup.ts
178
- var pendingSetup = [];
179
- var requestedEouMethods = /* @__PURE__ */ new Set();
199
+ //#endregion
200
+ //#region src/worker-setup.ts
201
+ /**
202
+ * Coordination between worker definition and worker startup. LiveKit plugins register
203
+ * their inference runners at module-import time, and the AgentServer only spawns the
204
+ * inference process for runners registered before it starts — so plugin imports kicked
205
+ * off by `createLiveKitWorker()` must complete before `runLiveKitWorker()` boots the
206
+ * server.
207
+ */
208
+ const pendingSetup = [];
209
+ const requestedEouMethods = /* @__PURE__ */ new Set();
180
210
  function queueWorkerSetup(setup) {
181
- pendingSetup.push(setup);
211
+ pendingSetup.push(setup);
182
212
  }
183
213
  function workerSetupComplete() {
184
- return Promise.allSettled(pendingSetup);
214
+ return Promise.allSettled(pendingSetup);
185
215
  }
186
216
  function requestEouMethod(method) {
187
- requestedEouMethods.add(method);
217
+ requestedEouMethods.add(method);
188
218
  }
189
219
  function isEouMethodRequested(method) {
190
- return requestedEouMethods.has(method);
220
+ return requestedEouMethods.has(method);
191
221
  }
192
-
193
- // src/worker.ts
194
- var EOU_METHODS = {
195
- english: "lk_end_of_utterance_en",
196
- multilingual: "lk_end_of_utterance_multilingual"
222
+ //#endregion
223
+ //#region src/worker.ts
224
+ const EOU_METHODS = {
225
+ english: "lk_end_of_utterance_en",
226
+ multilingual: "lk_end_of_utterance_multilingual"
197
227
  };
198
228
  async function loadSileroVad() {
199
- let silero;
200
- try {
201
- silero = await import('@livekit/agents-plugin-silero');
202
- } catch (error) {
203
- throw new Error(
204
- "@mastra/livekit: voice activity detection requires '@livekit/agents-plugin-silero'. Install it, pass your own `vad` instance, or set `vad: false`.",
205
- { cause: error }
206
- );
207
- }
208
- return silero.VAD.load();
229
+ let silero;
230
+ try {
231
+ silero = await import("@livekit/agents-plugin-silero");
232
+ } catch (error) {
233
+ throw new Error("@mastra/livekit: voice activity detection requires '@livekit/agents-plugin-silero'. Install it, pass your own `vad` instance, or set `vad: false`.", { cause: error });
234
+ }
235
+ return silero.VAD.load();
209
236
  }
210
237
  async function loadTurnDetector(kind) {
211
- let plugin;
212
- try {
213
- plugin = await import('@livekit/agents-plugin-livekit');
214
- } catch (error) {
215
- throw new Error(
216
- `@mastra/livekit: turnDetection '${kind}' requires '@livekit/agents-plugin-livekit'. Install it or use a built-in mode like 'vad' or 'stt'.`,
217
- { cause: error }
218
- );
219
- }
220
- return kind === "english" ? new plugin.turnDetector.EnglishModel() : new plugin.turnDetector.MultilingualModel();
238
+ let plugin;
239
+ try {
240
+ plugin = await import("@livekit/agents-plugin-livekit");
241
+ } catch (error) {
242
+ throw new Error(`@mastra/livekit: turnDetection '${kind}' requires '@livekit/agents-plugin-livekit'. Install it or use a built-in mode like 'vad' or 'stt'.`, { cause: error });
243
+ }
244
+ return kind === "english" ? new plugin.turnDetector.EnglishModel() : new plugin.turnDetector.MultilingualModel();
221
245
  }
222
246
  async function resolveMastraAgent(options, args) {
223
- let ref = typeof options.agent === "function" ? await options.agent(args) : options.agent;
224
- ref ??= args.metadata.agentId;
225
- if (!ref) {
226
- throw new Error(
227
- "@mastra/livekit: no Mastra agent specified. Set `agent` on createLiveKitWorker or pass `agentId` in the dispatch metadata (e.g. via liveKitConnectionRoute)."
228
- );
229
- }
230
- if (typeof ref !== "string") return ref;
231
- try {
232
- return options.mastra.getAgentById(ref);
233
- } catch {
234
- return options.mastra.getAgent(ref);
235
- }
247
+ let ref = typeof options.agent === "function" ? await options.agent(args) : options.agent;
248
+ ref ??= args.metadata.agentId;
249
+ if (!ref) throw new Error("@mastra/livekit: no Mastra agent specified. Set `agent` on createLiveKitWorker or pass `agentId` in the dispatch metadata (e.g. via liveKitConnectionRoute).");
250
+ if (typeof ref !== "string") return ref;
251
+ try {
252
+ return options.mastra.getAgentById(ref);
253
+ } catch {
254
+ return options.mastra.getAgent(ref);
255
+ }
236
256
  }
237
257
  async function resolveWorkflow(options, args) {
238
- const resolver = options.workflow;
239
- const ref = typeof resolver === "function" ? await resolver(args) : resolver ?? "";
240
- if (!ref) {
241
- throw new Error("@mastra/livekit: no workflow specified. Set `workflow` on createLiveKitWorker.");
242
- }
243
- if (typeof ref !== "string") return { workflow: ref };
244
- return { workflow: options.mastra.getWorkflowById(ref) };
258
+ const resolver = options.workflow;
259
+ const ref = typeof resolver === "function" ? await resolver(args) : resolver ?? "";
260
+ if (!ref) throw new Error("@mastra/livekit: no workflow specified. Set `workflow` on createLiveKitWorker.");
261
+ if (typeof ref !== "string") return { workflow: ref };
262
+ return { workflow: options.mastra.getWorkflowById(ref) };
245
263
  }
246
264
  function resolveMemory(options, mastraAgent, args, roomName) {
247
- if (options.memory === false) return false;
248
- if (typeof options.memory === "function") return options.memory({ ...args, roomName });
249
- if (mastraAgent && !mastraAgent.hasOwnMemory()) return false;
250
- const thread = args.metadata.threadId ?? roomName;
251
- return { thread, resource: args.metadata.resourceId ?? thread };
265
+ if (options.memory === false) return false;
266
+ if (typeof options.memory === "function") return options.memory({
267
+ ...args,
268
+ roomName
269
+ });
270
+ if (mastraAgent && !mastraAgent.hasOwnMemory()) return false;
271
+ const thread = args.metadata.threadId ?? roomName;
272
+ return {
273
+ thread,
274
+ resource: args.metadata.resourceId ?? thread
275
+ };
252
276
  }
253
277
  async function resolveMemoryInstance(options, mastraAgent, args, requestContext) {
254
- if (mastraAgent) return await mastraAgent.getMemory({ requestContext }) ?? null;
255
- const resolver = options.memoryInstance;
256
- if (!resolver) return null;
257
- const instance = typeof resolver === "function" ? await resolver(args) : resolver;
258
- if (!instance) return null;
259
- instance.__registerMastra(options.mastra);
260
- if (!instance.hasOwnStorage) {
261
- const storage = options.mastra.getStorage();
262
- if (storage) instance.setStorage(storage);
263
- }
264
- return instance;
278
+ if (mastraAgent) return await mastraAgent.getMemory({ requestContext }) ?? null;
279
+ const resolver = options.memoryInstance;
280
+ if (!resolver) return null;
281
+ const instance = typeof resolver === "function" ? await resolver(args) : resolver;
282
+ if (!instance) return null;
283
+ instance.__registerMastra(options.mastra);
284
+ if (!instance.hasOwnStorage) {
285
+ const storage = options.mastra.getStorage();
286
+ if (storage) instance.setStorage(storage);
287
+ }
288
+ return instance;
265
289
  }
266
290
  function buildTurnHandling(options, turnDetection) {
267
- return {
268
- ...turnDetection ? { turnDetection } : {},
269
- // Preemptive generation re-runs the Mastra agent on interim transcripts (up to 3
270
- // times per turn), and every run persists the user message to the memory thread —
271
- // duplicating and even saving partial transcripts. Off by default; opt back in via
272
- // `turnHandling.preemptiveGeneration` if the latency win matters more than exact
273
- // thread history.
274
- preemptiveGeneration: { enabled: false },
275
- ...options.turnHandling
276
- };
291
+ return {
292
+ ...turnDetection ? { turnDetection } : {},
293
+ preemptiveGeneration: { enabled: false },
294
+ ...options.turnHandling
295
+ };
277
296
  }
278
297
  async function resolveInstructions(mastraAgent, requestContext) {
279
- try {
280
- const instructions = await mastraAgent.getInstructions({ requestContext });
281
- return typeof instructions === "string" ? instructions : void 0;
282
- } catch {
283
- return void 0;
284
- }
298
+ try {
299
+ const instructions = await mastraAgent.getInstructions({ requestContext });
300
+ return typeof instructions === "string" ? instructions : void 0;
301
+ } catch {
302
+ return;
303
+ }
285
304
  }
305
+ /**
306
+ * Resolves the effective greeting from the canonical `configuration.greeting` and the deprecated
307
+ * top-level `greeting` / `persistGreeting` options. The legacy options are the base so existing
308
+ * worker configs keep working unchanged; `configuration.greeting` overrides field-by-field. Exported
309
+ * for unit testing.
310
+ */
286
311
  function resolveGreetingConfig(options) {
287
- return {
288
- text: options.greeting,
289
- persist: options.persistGreeting,
290
- ...options.configuration?.greeting
291
- };
312
+ return {
313
+ text: options.greeting,
314
+ persist: options.persistGreeting,
315
+ ...options.configuration?.greeting
316
+ };
292
317
  }
318
+ /**
319
+ * Resolves the greeting text for a call: returns a fixed string as-is, or invokes the resolver form
320
+ * with the call context to produce a per-tenant greeting. Empty / whitespace-nothing results
321
+ * normalize to `undefined` (no greeting). Exported for unit testing.
322
+ */
293
323
  async function resolveGreetingText(text, context) {
294
- const resolved = typeof text === "function" ? await text(context) : text;
295
- return resolved || void 0;
324
+ return (typeof text === "function" ? await text(context) : text) || void 0;
296
325
  }
326
+ /**
327
+ * Resolves a per-call session component (STT / TTS): invokes the `configuration` resolver with the
328
+ * call context, falling back to the static top-level option when there is no resolver or it
329
+ * resolves to `undefined`. Exported for unit testing.
330
+ */
297
331
  async function resolveSessionComponent(resolver, fallback, context) {
298
- const resolved = resolver ? await resolver(context) : void 0;
299
- return resolved ?? fallback;
332
+ return (resolver ? await resolver(context) : void 0) ?? fallback;
300
333
  }
334
+ /**
335
+ * Speaks the session's opening greeting, honoring the interruption / playout options. Takes the
336
+ * already-resolved greeting (see {@link resolveGreetingText}) and returns the LiveKit `SpeechHandle`,
337
+ * or `undefined` when there is no greeting text. Extracted and exported so the greeting behavior is
338
+ * unit-testable without a live room.
339
+ */
301
340
  async function speakGreeting(session, greeting) {
302
- if (!greeting.text) return void 0;
303
- const handle = session.say(
304
- greeting.text,
305
- greeting.allowInterruptions === void 0 ? void 0 : { allowInterruptions: greeting.allowInterruptions }
306
- );
307
- if (greeting.awaitPlayout) {
308
- await handle.waitForPlayout().catch(() => {
309
- });
310
- }
311
- return handle;
341
+ if (!greeting.text) return void 0;
342
+ const handle = session.say(greeting.text, greeting.allowInterruptions === void 0 ? void 0 : { allowInterruptions: greeting.allowInterruptions });
343
+ if (greeting.awaitPlayout) await handle.waitForPlayout().catch(() => {});
344
+ return handle;
312
345
  }
313
- var DEFAULT_END_CALL_TOOL = "endCall";
314
- var DEFAULT_END_CALL_REASON = "agent ended call";
315
- var DEFAULT_END_CALL_MAX_WAIT_MS = 3e4;
316
- var DEFAULT_END_CALL_DRAIN_MS = 800;
317
- var AGENT_BUSY_STATES = /* @__PURE__ */ new Set(["thinking", "speaking"]);
346
+ /** Default tool name the worker watches for to end the call. Matches `createEndCallTool`'s default. */
347
+ const DEFAULT_END_CALL_TOOL = "endCall";
348
+ /** Default shutdown reason recorded when the agent ends the call. */
349
+ const DEFAULT_END_CALL_REASON = "agent ended call";
350
+ /** Default safety cap on waiting for the agent's closing words to finish before hanging up. */
351
+ const DEFAULT_END_CALL_MAX_WAIT_MS = 3e4;
352
+ /**
353
+ * Default post-playout drain before the room is deleted. LiveKit's "playout completed" is
354
+ * worker-local; the caller's client still holds network + jitter-buffer audio, so hanging up the
355
+ * instant the state clears clips the tail of the goodbye.
356
+ */
357
+ const DEFAULT_END_CALL_DRAIN_MS = 800;
358
+ /** Agent states where the agent is still busy producing / playing a reply (not done speaking). */
359
+ const AGENT_BUSY_STATES = /* @__PURE__ */ new Set(["thinking", "speaking"]);
360
+ /**
361
+ * Resolves once the agent is no longer producing or playing a reply — i.e. its state has left
362
+ * `thinking`/`speaking` for `listening`/`idle`. Returns immediately when it's already idle. A
363
+ * `maxWaitMs` safety cap guarantees it resolves even if the speaking state never clears. Used before
364
+ * an agent-initiated hang-up so the closing words play out fully instead of being cut off by the
365
+ * session close. Exported for unit testing.
366
+ */
318
367
  function waitForAgentDoneSpeaking(session, maxWaitMs = DEFAULT_END_CALL_MAX_WAIT_MS) {
319
- if (!AGENT_BUSY_STATES.has(session.agentState)) return Promise.resolve();
320
- return new Promise((resolve) => {
321
- let timer;
322
- const onChange = (ev) => {
323
- if (AGENT_BUSY_STATES.has(ev.newState)) return;
324
- cleanup();
325
- resolve();
326
- };
327
- const cleanup = () => {
328
- session.off(agents.voice.AgentSessionEventTypes.AgentStateChanged, onChange);
329
- if (timer) clearTimeout(timer);
330
- };
331
- session.on(agents.voice.AgentSessionEventTypes.AgentStateChanged, onChange);
332
- timer = setTimeout(() => {
333
- cleanup();
334
- resolve();
335
- }, maxWaitMs);
336
- timer.unref?.();
337
- });
368
+ if (!AGENT_BUSY_STATES.has(session.agentState)) return Promise.resolve();
369
+ return new Promise((resolve) => {
370
+ let timer;
371
+ const onChange = (ev) => {
372
+ if (AGENT_BUSY_STATES.has(ev.newState)) return;
373
+ cleanup();
374
+ resolve();
375
+ };
376
+ const cleanup = () => {
377
+ session.off(_livekit_agents.voice.AgentSessionEventTypes.AgentStateChanged, onChange);
378
+ if (timer) clearTimeout(timer);
379
+ };
380
+ session.on(_livekit_agents.voice.AgentSessionEventTypes.AgentStateChanged, onChange);
381
+ timer = setTimeout(() => {
382
+ cleanup();
383
+ resolve();
384
+ }, maxWaitMs);
385
+ timer.unref?.();
386
+ });
338
387
  }
388
+ /**
389
+ * Ends the call after the agent asked to (via its end-call tool): wait for the agent's closing words
390
+ * to finish, speak an optional final `message`, hold a short drain (`drainMs`) so audio buffered at
391
+ * the caller finishes playing, then disconnect. The teardown's session close
392
+ * force-interrupts any playing speech, so the waits here MUST complete before we disconnect — that's
393
+ * the whole point of the sequence. `ctx.deleteRoom()` hangs up the caller (SIP-safe); `ctx.shutdown()`
394
+ * ends the job and runs the registered shutdown callbacks (`onCallEnd`), so end-of-call work happens
395
+ * exactly as it does on a caller hang-up. Both run in a `finally` so a hiccup while waiting still ends
396
+ * the call. Never rejects — every step is guarded and failures are logged, so callers can safely
397
+ * fire-and-forget it (`void runEndCall(...)`) from hooks. Exported for unit testing.
398
+ */
339
399
  async function runEndCall(session, ctx, config, logger) {
340
- try {
341
- await waitForAgentDoneSpeaking(session, config.maxWaitMs ?? DEFAULT_END_CALL_MAX_WAIT_MS);
342
- if (config.message) {
343
- await session.say(config.message, { allowInterruptions: false }).waitForPlayout().catch(() => {
344
- });
345
- }
346
- const drainMs = config.drainMs ?? DEFAULT_END_CALL_DRAIN_MS;
347
- if (drainMs > 0) await new Promise((resolve) => setTimeout(resolve, drainMs));
348
- } catch (error) {
349
- logger.warn("@mastra/livekit: waiting for the agent to finish before ending the call failed", error);
350
- } finally {
351
- try {
352
- await ctx.deleteRoom();
353
- } catch (error) {
354
- logger.warn("@mastra/livekit: deleteRoom while ending the call failed", error);
355
- }
356
- try {
357
- ctx.shutdown(config.reason ?? DEFAULT_END_CALL_REASON);
358
- } catch (error) {
359
- logger.warn("@mastra/livekit: shutdown while ending the call failed", error);
360
- }
361
- }
400
+ try {
401
+ await waitForAgentDoneSpeaking(session, config.maxWaitMs ?? 3e4);
402
+ if (config.message) await session.say(config.message, { allowInterruptions: false }).waitForPlayout().catch(() => {});
403
+ const drainMs = config.drainMs ?? 800;
404
+ if (drainMs > 0) await new Promise((resolve) => setTimeout(resolve, drainMs));
405
+ } catch (error) {
406
+ logger.warn("@mastra/livekit: waiting for the agent to finish before ending the call failed", error);
407
+ } finally {
408
+ try {
409
+ await ctx.deleteRoom();
410
+ } catch (error) {
411
+ logger.warn("@mastra/livekit: deleteRoom while ending the call failed", error);
412
+ }
413
+ try {
414
+ ctx.shutdown(config.reason ?? "agent ended call");
415
+ } catch (error) {
416
+ logger.warn("@mastra/livekit: shutdown while ending the call failed", error);
417
+ }
418
+ }
362
419
  }
420
+ /**
421
+ * Builds the per-turn hook that detects the agent's end-call tool and kicks off the hang-up once, or
422
+ * `undefined` when end-call isn't configured. The detector fires the teardown fire-and-forget (the
423
+ * turn never waits on it) and de-dupes so a repeated tool call can't start two hang-ups.
424
+ */
363
425
  function buildEndCallDetector(config, getSession, ctx, logger) {
364
- if (!config) return void 0;
365
- const toolName = config.tool ?? DEFAULT_END_CALL_TOOL;
366
- let triggered = false;
367
- return (turnCtx) => {
368
- if (triggered) return;
369
- if (!turnCtx.result.toolCalls.some((call) => call.toolName === toolName)) return;
370
- const session = getSession();
371
- if (!session) return;
372
- triggered = true;
373
- void runEndCall(session, ctx, config, logger);
374
- };
426
+ if (!config) return void 0;
427
+ const toolName = config.tool ?? "endCall";
428
+ let triggered = false;
429
+ return (turnCtx) => {
430
+ if (triggered) return;
431
+ if (!turnCtx.result.toolCalls.some((call) => call.toolName === toolName)) return;
432
+ const session = getSession();
433
+ if (!session) return;
434
+ triggered = true;
435
+ runEndCall(session, ctx, config, logger);
436
+ };
375
437
  }
438
+ /** Runs the worker's own turn-complete detector alongside the user's `onTurnComplete`, if any. */
376
439
  function composeTurnComplete(user, detector) {
377
- if (!detector) return user;
378
- return (ctx) => {
379
- void detector(ctx);
380
- return user?.(ctx);
381
- };
440
+ if (!detector) return user;
441
+ return (ctx) => {
442
+ detector(ctx);
443
+ return user?.(ctx);
444
+ };
382
445
  }
446
+ /**
447
+ * Builds a LiveKit agent worker definition that answers voice sessions with Mastra agents.
448
+ *
449
+ * Use as the default export of your worker entry file, then run it with the LiveKit
450
+ * agents CLI:
451
+ *
452
+ * ```ts
453
+ * // src/mastra/voice-worker.ts
454
+ * import { fileURLToPath } from 'node:url';
455
+ * import { createLiveKitWorker, runLiveKitWorker } from '@mastra/livekit/worker';
456
+ * import { mastra } from './index';
457
+ *
458
+ * export default createLiveKitWorker({
459
+ * mastra,
460
+ * stt: 'deepgram/nova-3',
461
+ * tts: 'cartesia/sonic-3',
462
+ * turnDetection: 'multilingual',
463
+ * });
464
+ *
465
+ * if (process.argv[1] === fileURLToPath(import.meta.url)) {
466
+ * runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' });
467
+ * }
468
+ * ```
469
+ */
383
470
  function createLiveKitWorker(options) {
384
- if (options.generate && (options.agent || options.workflow)) {
385
- throw new Error(
386
- "@mastra/livekit: set exactly one reply generator \u2014 `generate`, `agent`, or `workflow` \u2014 not a combination."
387
- );
388
- }
389
- if (options.agent && options.workflow) {
390
- throw new Error(
391
- "@mastra/livekit: set `agent` or `workflow`, not both \u2014 they are mutually exclusive reply generators."
392
- );
393
- }
394
- if (options.workflow && !options.workflowInput) {
395
- throw new Error(
396
- "@mastra/livekit: `workflowInput` is required when `workflow` is set. Map the turn into the workflow inputData, e.g. workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) })."
397
- );
398
- }
399
- if (options.generate && options.configuration?.endCall) {
400
- throw new Error(
401
- "@mastra/livekit: `configuration.endCall` has no effect with `generate` \u2014 the worker cannot observe tool calls from a custom reply generator. Detect the end-call tool inside your generator and call `runEndCall` directly instead."
402
- );
403
- }
404
- const wantsSileroVad = options.vad === void 0 || options.vad === "silero";
405
- if (options.turnDetection === "multilingual" || options.turnDetection === "english") {
406
- requestEouMethod(EOU_METHODS[options.turnDetection]);
407
- queueWorkerSetup(
408
- import('@livekit/agents-plugin-livekit').then(() => {
409
- for (const method of Object.values(EOU_METHODS)) {
410
- if (!isEouMethodRequested(method)) {
411
- delete agents.InferenceRunner.registeredRunners[method];
412
- }
413
- }
414
- }).catch(() => {
415
- })
416
- );
417
- }
418
- return agents.defineAgent({
419
- prewarm: async (proc) => {
420
- if (wantsSileroVad) {
421
- proc.userData.vad = await loadSileroVad();
422
- }
423
- },
424
- entry: async (ctx) => {
425
- const logger = options.mastra.getLogger();
426
- const metadata = chunkMWTEZOBS_cjs.parseSessionMetadata(ctx.job.metadata);
427
- const args = { metadata, ctx };
428
- const requestContext$1 = metadata.requestContext ? new requestContext.RequestContext(Object.entries(metadata.requestContext)) : void 0;
429
- const endCallDetector = buildEndCallDetector(options.configuration?.endCall, () => session, ctx, logger);
430
- const onTurnComplete = composeTurnComplete(options.onTurnComplete, endCallDetector);
431
- let mastraAgent;
432
- let replyGenerator;
433
- let agentLabel;
434
- if (options.generate) {
435
- replyGenerator = options.generate;
436
- agentLabel = "mastra-voice";
437
- } else if (options.workflow) {
438
- const { workflow } = await resolveWorkflow(options, args);
439
- agentLabel = workflow.id;
440
- const mapInput = options.workflowInput;
441
- replyGenerator = chunkMWTEZOBS_cjs.createWorkflowReplyGenerator({
442
- workflow,
443
- workflowInput: (turnCtx) => mapInput({ ...turnCtx, metadata }),
444
- replyStep: options.replyStep,
445
- resultText: options.resultText,
446
- toolFeedback: options.toolFeedback,
447
- onTurnComplete
448
- });
449
- } else {
450
- mastraAgent = await resolveMastraAgent(options, args);
451
- agentLabel = mastraAgent.id ?? mastraAgent.name;
452
- }
453
- await ctx.connect();
454
- const roomName = ctx.room.name ?? "mastra-voice";
455
- let vad;
456
- if (options.vad && options.vad !== "silero") {
457
- vad = options.vad;
458
- } else if (wantsSileroVad) {
459
- vad = ctx.proc.userData.vad ?? await loadSileroVad();
460
- }
461
- let turnDetection;
462
- if (options.turnDetection === "multilingual" || options.turnDetection === "english") {
463
- turnDetection = await loadTurnDetector(options.turnDetection);
464
- } else {
465
- turnDetection = options.turnDetection;
466
- }
467
- const memory = resolveMemory(options, mastraAgent, args, roomName);
468
- const memoryInstance = memory ? await resolveMemoryInstance(options, mastraAgent, args, requestContext$1) : null;
469
- if (memory && memoryInstance) {
470
- try {
471
- await ensureVoiceCallThread({
472
- memory: memoryInstance,
473
- threadId: memory.thread,
474
- resourceId: memory.resource ?? memory.thread,
475
- roomName
476
- });
477
- } catch (error) {
478
- logger.warn("@mastra/livekit: failed to create the voice call thread", error);
479
- }
480
- }
481
- const voiceObs = options.observability === false ? void 0 : startVoiceCallObservability({
482
- mastra: options.mastra,
483
- agentId: agentLabel,
484
- roomName,
485
- metadata,
486
- requestContext: requestContext$1
487
- });
488
- if (voiceObs) {
489
- ctx.addShutdownCallback(async () => {
490
- voiceObs.finalize();
491
- });
492
- }
493
- if (options.onCallEnd) {
494
- const onCallEnd = options.onCallEnd;
495
- ctx.addShutdownCallback(async () => {
496
- try {
497
- await onCallEnd({
498
- memory,
499
- memoryInstance,
500
- metadata,
501
- requestContext: requestContext$1,
502
- configuration: options.configuration,
503
- roomName,
504
- ctx
505
- });
506
- } catch (error) {
507
- logger.warn("@mastra/livekit: onCallEnd hook threw", error);
508
- }
509
- });
510
- }
511
- const greetingConfig = resolveGreetingConfig(options);
512
- const greetingReminder = greetingConfig.repeatEvery && greetingConfig.repeatEvery > 0 ? { everyMs: greetingConfig.repeatEvery, text: greetingConfig.repeatText } : void 0;
513
- const agent = chunkDBVKNDAQ_cjs.createMastraVoiceAgent({
514
- ...replyGenerator ? { generate: replyGenerator } : { agent: mastraAgent },
515
- instructions: mastraAgent ? await resolveInstructions(mastraAgent, requestContext$1) : void 0,
516
- memory,
517
- requestContext: requestContext$1,
518
- toolFeedback: options.toolFeedback,
519
- onTurnComplete,
520
- greetingReminder,
521
- streamOptions: voiceObs ? { tracingContext: voiceObs.tracingContext } : void 0
522
- });
523
- const callContext = { metadata, requestContext: requestContext$1, roomName, ctx };
524
- const [stt, tts] = await Promise.all([
525
- resolveSessionComponent(options.configuration?.stt, options.stt, callContext),
526
- resolveSessionComponent(options.configuration?.tts, options.tts, callContext)
527
- ]);
528
- const session = new agents.voice.AgentSession({
529
- stt,
530
- tts,
531
- vad,
532
- turnHandling: buildTurnHandling(options, turnDetection),
533
- ...options.sessionOptions
534
- });
535
- voiceObs?.attach(session);
536
- try {
537
- await session.start({
538
- agent,
539
- room: ctx.room,
540
- inputOptions: options.inputOptions,
541
- outputOptions: options.outputOptions
542
- });
543
- if (greetingConfig.text) {
544
- const greetingText = await resolveGreetingText(greetingConfig.text, callContext);
545
- if (greetingText) {
546
- await speakGreeting(session, { ...greetingConfig, text: greetingText });
547
- if (greetingConfig.persist !== false && memory && memoryInstance) {
548
- try {
549
- await persistSpokenGreeting({
550
- memory: memoryInstance,
551
- threadId: memory.thread,
552
- resourceId: memory.resource ?? memory.thread,
553
- greeting: greetingText
554
- });
555
- } catch (error) {
556
- logger.warn("@mastra/livekit: failed to persist the greeting", error);
557
- }
558
- }
559
- }
560
- }
561
- await options.onSessionStart?.({ session, ctx, agent, metadata });
562
- } catch (error) {
563
- voiceObs?.finalize({ error });
564
- throw error;
565
- }
566
- }
567
- });
471
+ if (options.generate && (options.agent || options.workflow)) throw new Error("@mastra/livekit: set exactly one reply generator — `generate`, `agent`, or `workflow` — not a combination.");
472
+ if (options.agent && options.workflow) throw new Error("@mastra/livekit: set `agent` or `workflow`, not both — they are mutually exclusive reply generators.");
473
+ if (options.workflow && !options.workflowInput) throw new Error("@mastra/livekit: `workflowInput` is required when `workflow` is set. Map the turn into the workflow inputData, e.g. workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) }).");
474
+ if (options.generate && options.configuration?.endCall) throw new Error("@mastra/livekit: `configuration.endCall` has no effect with `generate` — the worker cannot observe tool calls from a custom reply generator. Detect the end-call tool inside your generator and call `runEndCall` directly instead.");
475
+ const wantsSileroVad = options.vad === void 0 || options.vad === "silero";
476
+ if (options.turnDetection === "multilingual" || options.turnDetection === "english") {
477
+ requestEouMethod(EOU_METHODS[options.turnDetection]);
478
+ queueWorkerSetup(import("@livekit/agents-plugin-livekit").then(() => {
479
+ for (const method of Object.values(EOU_METHODS)) if (!isEouMethodRequested(method)) delete _livekit_agents.InferenceRunner.registeredRunners[method];
480
+ }).catch(() => {}));
481
+ }
482
+ return (0, _livekit_agents.defineAgent)({
483
+ prewarm: async (proc) => {
484
+ if (wantsSileroVad) proc.userData.vad = await loadSileroVad();
485
+ },
486
+ entry: async (ctx) => {
487
+ const logger = options.mastra.getLogger();
488
+ const metadata = require_workflow_generator.parseSessionMetadata(ctx.job.metadata);
489
+ const args = {
490
+ metadata,
491
+ ctx
492
+ };
493
+ const requestContext = metadata.requestContext ? new _mastra_core_request_context.RequestContext(Object.entries(metadata.requestContext)) : void 0;
494
+ const endCallDetector = buildEndCallDetector(options.configuration?.endCall, () => session, ctx, logger);
495
+ const onTurnComplete = composeTurnComplete(options.onTurnComplete, endCallDetector);
496
+ let mastraAgent;
497
+ let replyGenerator;
498
+ let agentLabel;
499
+ if (options.generate) {
500
+ replyGenerator = options.generate;
501
+ agentLabel = "mastra-voice";
502
+ } else if (options.workflow) {
503
+ const { workflow } = await resolveWorkflow(options, args);
504
+ agentLabel = workflow.id;
505
+ const mapInput = options.workflowInput;
506
+ replyGenerator = require_workflow_generator.createWorkflowReplyGenerator({
507
+ workflow,
508
+ workflowInput: (turnCtx) => mapInput({
509
+ ...turnCtx,
510
+ metadata
511
+ }),
512
+ replyStep: options.replyStep,
513
+ resultText: options.resultText,
514
+ toolFeedback: options.toolFeedback,
515
+ onTurnComplete
516
+ });
517
+ } else {
518
+ mastraAgent = await resolveMastraAgent(options, args);
519
+ agentLabel = mastraAgent.id ?? mastraAgent.name;
520
+ }
521
+ await ctx.connect();
522
+ const roomName = ctx.room.name ?? "mastra-voice";
523
+ let vad;
524
+ if (options.vad && options.vad !== "silero") vad = options.vad;
525
+ else if (wantsSileroVad) vad = ctx.proc.userData.vad ?? await loadSileroVad();
526
+ let turnDetection;
527
+ if (options.turnDetection === "multilingual" || options.turnDetection === "english") turnDetection = await loadTurnDetector(options.turnDetection);
528
+ else turnDetection = options.turnDetection;
529
+ const memory = resolveMemory(options, mastraAgent, args, roomName);
530
+ const memoryInstance = memory ? await resolveMemoryInstance(options, mastraAgent, args, requestContext) : null;
531
+ if (memory && memoryInstance) try {
532
+ await ensureVoiceCallThread({
533
+ memory: memoryInstance,
534
+ threadId: memory.thread,
535
+ resourceId: memory.resource ?? memory.thread,
536
+ roomName
537
+ });
538
+ } catch (error) {
539
+ logger.warn("@mastra/livekit: failed to create the voice call thread", error);
540
+ }
541
+ const voiceObs = options.observability === false ? void 0 : startVoiceCallObservability({
542
+ mastra: options.mastra,
543
+ agentId: agentLabel,
544
+ roomName,
545
+ metadata,
546
+ requestContext
547
+ });
548
+ if (voiceObs) ctx.addShutdownCallback(async () => {
549
+ voiceObs.finalize();
550
+ });
551
+ if (options.onCallEnd) {
552
+ const onCallEnd = options.onCallEnd;
553
+ ctx.addShutdownCallback(async () => {
554
+ try {
555
+ await onCallEnd({
556
+ memory,
557
+ memoryInstance,
558
+ metadata,
559
+ requestContext,
560
+ configuration: options.configuration,
561
+ roomName,
562
+ ctx
563
+ });
564
+ } catch (error) {
565
+ logger.warn("@mastra/livekit: onCallEnd hook threw", error);
566
+ }
567
+ });
568
+ }
569
+ const greetingConfig = resolveGreetingConfig(options);
570
+ const greetingReminder = greetingConfig.repeatEvery && greetingConfig.repeatEvery > 0 ? {
571
+ everyMs: greetingConfig.repeatEvery,
572
+ text: greetingConfig.repeatText
573
+ } : void 0;
574
+ const agent = require_remote.createMastraVoiceAgent({
575
+ ...replyGenerator ? { generate: replyGenerator } : { agent: mastraAgent },
576
+ instructions: mastraAgent ? await resolveInstructions(mastraAgent, requestContext) : void 0,
577
+ memory,
578
+ requestContext,
579
+ toolFeedback: options.toolFeedback,
580
+ onTurnComplete,
581
+ greetingReminder,
582
+ streamOptions: voiceObs ? { tracingContext: voiceObs.tracingContext } : void 0
583
+ });
584
+ const callContext = {
585
+ metadata,
586
+ requestContext,
587
+ roomName,
588
+ ctx
589
+ };
590
+ const [stt, tts] = await Promise.all([resolveSessionComponent(options.configuration?.stt, options.stt, callContext), resolveSessionComponent(options.configuration?.tts, options.tts, callContext)]);
591
+ const session = new _livekit_agents.voice.AgentSession({
592
+ stt,
593
+ tts,
594
+ vad,
595
+ turnHandling: buildTurnHandling(options, turnDetection),
596
+ ...options.sessionOptions
597
+ });
598
+ voiceObs?.attach(session);
599
+ try {
600
+ await session.start({
601
+ agent,
602
+ room: ctx.room,
603
+ inputOptions: options.inputOptions,
604
+ outputOptions: options.outputOptions
605
+ });
606
+ if (greetingConfig.text) {
607
+ const greetingText = await resolveGreetingText(greetingConfig.text, callContext);
608
+ if (greetingText) {
609
+ await speakGreeting(session, {
610
+ ...greetingConfig,
611
+ text: greetingText
612
+ });
613
+ if (greetingConfig.persist !== false && memory && memoryInstance) try {
614
+ await persistSpokenGreeting({
615
+ memory: memoryInstance,
616
+ threadId: memory.thread,
617
+ resourceId: memory.resource ?? memory.thread,
618
+ greeting: greetingText
619
+ });
620
+ } catch (error) {
621
+ logger.warn("@mastra/livekit: failed to persist the greeting", error);
622
+ }
623
+ }
624
+ }
625
+ await options.onSessionStart?.({
626
+ session,
627
+ ctx,
628
+ agent,
629
+ metadata
630
+ });
631
+ } catch (error) {
632
+ voiceObs?.finalize({ error });
633
+ throw error;
634
+ }
635
+ }
636
+ });
568
637
  }
638
+ //#endregion
639
+ //#region src/run.ts
569
640
  function resolveWorkerEntryPath(entry) {
570
- if (entry instanceof URL) return url.fileURLToPath(entry);
571
- return entry.startsWith("file:") ? url.fileURLToPath(entry) : entry;
641
+ if (entry instanceof URL) return (0, url.fileURLToPath)(entry);
642
+ return entry.startsWith("file:") ? (0, url.fileURLToPath)(entry) : entry;
572
643
  }
644
+ /**
645
+ * Starts the LiveKit agent worker CLI (`dev` / `start` / `connect` subcommands) for a
646
+ * worker entry file. Call it from the same file that default-exports
647
+ * {@link createLiveKitWorker}, guarded so it only runs when executed directly:
648
+ *
649
+ * ```ts
650
+ * import { fileURLToPath } from 'node:url';
651
+ * import { createLiveKitWorker, runLiveKitWorker } from '@mastra/livekit/worker';
652
+ * import { mastra } from './index';
653
+ *
654
+ * export default createLiveKitWorker({ mastra, agent: 'support' });
655
+ *
656
+ * if (process.argv[1] === fileURLToPath(import.meta.url)) {
657
+ * runLiveKitWorker({ entry: import.meta.url, agentName: 'mastra-voice' });
658
+ * }
659
+ * ```
660
+ *
661
+ * Using this helper (instead of `cli.runApp` from `@livekit/agents`) guarantees the worker
662
+ * runtime and the bridge share one copy of the LiveKit SDK.
663
+ */
573
664
  function runLiveKitWorker(options) {
574
- void workerSetupComplete().then(() => {
575
- agents.cli.runApp(
576
- new agents.ServerOptions({
577
- agent: resolveWorkerEntryPath(options.entry),
578
- agentName: options.agentName ?? chunkMWTEZOBS_cjs.DEFAULT_LIVEKIT_AGENT_NAME,
579
- ...options.serverOptions
580
- })
581
- );
582
- });
665
+ workerSetupComplete().then(() => {
666
+ _livekit_agents.cli.runApp(new _livekit_agents.ServerOptions({
667
+ agent: resolveWorkerEntryPath(options.entry),
668
+ agentName: options.agentName ?? "mastra-voice",
669
+ ...options.serverOptions
670
+ }));
671
+ });
583
672
  }
584
-
585
- Object.defineProperty(exports, "chatContextToMessages", {
586
- enumerable: true,
587
- get: function () { return chunkDBVKNDAQ_cjs.chatContextToMessages; }
588
- });
589
- Object.defineProperty(exports, "createRemoteAgentReplyGenerator", {
590
- enumerable: true,
591
- get: function () { return chunkDBVKNDAQ_cjs.createRemoteAgentReplyGenerator; }
592
- });
673
+ //#endregion
593
674
  exports.DEFAULT_END_CALL_DRAIN_MS = DEFAULT_END_CALL_DRAIN_MS;
594
675
  exports.DEFAULT_END_CALL_MAX_WAIT_MS = DEFAULT_END_CALL_MAX_WAIT_MS;
595
676
  exports.DEFAULT_END_CALL_REASON = DEFAULT_END_CALL_REASON;
596
677
  exports.DEFAULT_END_CALL_TOOL = DEFAULT_END_CALL_TOOL;
678
+ exports.MastraVoiceAgent = require_remote.MastraVoiceAgent;
679
+ exports.chatContextToMessages = require_remote.chatContextToMessages;
597
680
  exports.createLiveKitWorker = createLiveKitWorker;
681
+ exports.createMastraVoiceAgent = require_remote.createMastraVoiceAgent;
682
+ exports.createRemoteAgentReplyGenerator = require_remote.createRemoteAgentReplyGenerator;
598
683
  exports.runEndCall = runEndCall;
599
684
  exports.runLiveKitWorker = runLiveKitWorker;
600
685
  exports.speakGreeting = speakGreeting;
601
686
  exports.waitForAgentDoneSpeaking = waitForAgentDoneSpeaking;
602
- //# sourceMappingURL=worker-entry.cjs.map
687
+
603
688
  //# sourceMappingURL=worker-entry.cjs.map