@mastra/livekit 0.0.0 → 0.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -0
- package/LICENSE.md +30 -0
- package/README.md +270 -7
- package/dist/bridge.d.ts +153 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/chunk-2E3MTAOA.js +133 -0
- package/dist/chunk-2E3MTAOA.js.map +1 -0
- package/dist/chunk-MWTEZOBS.cjs +139 -0
- package/dist/chunk-MWTEZOBS.cjs.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/dispatch.d.ts +20 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/index.cjs +105 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +24 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/metadata.d.ts +17 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/observability.d.ts +46 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/routes.d.ts +52 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/run.d.ts +32 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/voice-thread.d.ts +23 -0
- package/dist/voice-thread.d.ts.map +1 -0
- package/dist/worker-entry.cjs +656 -0
- package/dist/worker-entry.cjs.map +1 -0
- package/dist/worker-entry.d.ts +8 -0
- package/dist/worker-entry.d.ts.map +1 -0
- package/dist/worker-entry.js +652 -0
- package/dist/worker-entry.js.map +1 -0
- package/dist/worker-setup.d.ts +5 -0
- package/dist/worker-setup.d.ts.map +1 -0
- package/dist/worker.d.ts +187 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/workflow-generator.d.ts +92 -0
- package/dist/workflow-generator.d.ts.map +1 -0
- package/package.json +24 -14
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkMWTEZOBS_cjs = require('./chunk-MWTEZOBS.cjs');
|
|
4
|
+
var agents = require('@livekit/agents');
|
|
5
|
+
var requestContext = require('@mastra/core/request-context');
|
|
6
|
+
var web = require('stream/web');
|
|
7
|
+
var observability = require('@mastra/core/observability');
|
|
8
|
+
var crypto = require('crypto');
|
|
9
|
+
var url = require('url');
|
|
10
|
+
|
|
11
|
+
// src/messages.ts
|
|
12
|
+
function textOfMessage(message) {
|
|
13
|
+
const parts = [];
|
|
14
|
+
for (const part of message.content) {
|
|
15
|
+
if (typeof part === "string") {
|
|
16
|
+
parts.push(part);
|
|
17
|
+
} else if (part.type === "instructions") {
|
|
18
|
+
parts.push(part.value);
|
|
19
|
+
} else if (part.type === "audio_content" && part.transcript) {
|
|
20
|
+
parts.push(part.transcript);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return parts.join("\n").trim();
|
|
24
|
+
}
|
|
25
|
+
function toVoiceTurnMessage(item) {
|
|
26
|
+
if (item.type !== "message") return void 0;
|
|
27
|
+
const content = textOfMessage(item);
|
|
28
|
+
if (!content) return void 0;
|
|
29
|
+
if (item.role === "user") return { role: "user", content };
|
|
30
|
+
if (item.role === "assistant") return { role: "assistant", content };
|
|
31
|
+
return { role: "system", content };
|
|
32
|
+
}
|
|
33
|
+
function extractNewTurnMessages(chatCtx) {
|
|
34
|
+
const items = chatCtx.items;
|
|
35
|
+
let lastAssistantIdx = -1;
|
|
36
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
37
|
+
const item = items[i];
|
|
38
|
+
if (item?.type === "message" && item.role === "assistant") {
|
|
39
|
+
lastAssistantIdx = i;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const messages = [];
|
|
44
|
+
for (const item of items.slice(lastAssistantIdx + 1)) {
|
|
45
|
+
const message = toVoiceTurnMessage(item);
|
|
46
|
+
if (message) messages.push(message);
|
|
47
|
+
}
|
|
48
|
+
return messages;
|
|
49
|
+
}
|
|
50
|
+
function chatContextToMessages(chatCtx) {
|
|
51
|
+
const withoutInstructions = chatCtx.copy({ excludeInstructions: true, excludeFunctionCall: true });
|
|
52
|
+
const messages = [];
|
|
53
|
+
for (const item of withoutInstructions.items) {
|
|
54
|
+
const message = toVoiceTurnMessage(item);
|
|
55
|
+
if (message) messages.push(message);
|
|
56
|
+
}
|
|
57
|
+
return messages;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/bridge.ts
|
|
61
|
+
var DEFAULT_INSTRUCTIONS = "You are a helpful voice assistant powered by a Mastra agent.";
|
|
62
|
+
function createAgentReplyGenerator(options) {
|
|
63
|
+
const { agent, streamOptions, toolFeedback, onTurnComplete } = options;
|
|
64
|
+
return (ctx) => {
|
|
65
|
+
if (ctx.messages.length === 0) return null;
|
|
66
|
+
const abortController = new AbortController();
|
|
67
|
+
const mergedOptions = {
|
|
68
|
+
...streamOptions,
|
|
69
|
+
abortSignal: abortController.signal
|
|
70
|
+
};
|
|
71
|
+
if (ctx.memory) mergedOptions.memory = ctx.memory;
|
|
72
|
+
if (ctx.requestContext) mergedOptions.requestContext = ctx.requestContext;
|
|
73
|
+
let cancelled = false;
|
|
74
|
+
let replyText = "";
|
|
75
|
+
const toolCalls = [];
|
|
76
|
+
const emitTurnComplete = (interrupted) => {
|
|
77
|
+
if (!onTurnComplete) return;
|
|
78
|
+
const completeCtx = { ...ctx, result: { text: replyText, toolCalls, interrupted } };
|
|
79
|
+
Promise.resolve().then(() => onTurnComplete(completeCtx)).catch((error) => {
|
|
80
|
+
console.warn("@mastra/livekit: onTurnComplete hook threw", error);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
return new web.ReadableStream({
|
|
84
|
+
start: async (controller) => {
|
|
85
|
+
try {
|
|
86
|
+
const result = await agent.stream(ctx.messages, mergedOptions);
|
|
87
|
+
for await (const chunk of result.fullStream) {
|
|
88
|
+
if (cancelled) break;
|
|
89
|
+
if (chunk.type === "text-delta") {
|
|
90
|
+
if (chunk.payload.text) {
|
|
91
|
+
replyText += chunk.payload.text;
|
|
92
|
+
controller.enqueue(chunk.payload.text);
|
|
93
|
+
}
|
|
94
|
+
} else if (chunk.type === "tool-call") {
|
|
95
|
+
const toolCall = {
|
|
96
|
+
toolCallId: chunk.payload.toolCallId,
|
|
97
|
+
toolName: chunk.payload.toolName,
|
|
98
|
+
args: chunk.payload.args
|
|
99
|
+
};
|
|
100
|
+
toolCalls.push(toolCall);
|
|
101
|
+
if (toolFeedback) {
|
|
102
|
+
const filler = toolFeedback(toolCall);
|
|
103
|
+
if (filler) controller.enqueue(filler.endsWith(" ") ? filler : `${filler} `);
|
|
104
|
+
}
|
|
105
|
+
} else if (chunk.type === "error") {
|
|
106
|
+
const error = chunk.payload.error;
|
|
107
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!cancelled) controller.close();
|
|
111
|
+
emitTurnComplete(cancelled);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (cancelled || abortController.signal.aborted) {
|
|
114
|
+
emitTurnComplete(true);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
controller.error(error);
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
cancel: () => {
|
|
121
|
+
cancelled = true;
|
|
122
|
+
abortController.abort();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function toRequestContext(value) {
|
|
128
|
+
if (!value) return void 0;
|
|
129
|
+
if (value instanceof requestContext.RequestContext) return value;
|
|
130
|
+
return new requestContext.RequestContext(Object.entries(value));
|
|
131
|
+
}
|
|
132
|
+
var MastraPlaceholderLLM = class extends agents.llm.LLM {
|
|
133
|
+
label() {
|
|
134
|
+
return "mastra.MastraVoiceAgent";
|
|
135
|
+
}
|
|
136
|
+
get model() {
|
|
137
|
+
return "mastra-agent";
|
|
138
|
+
}
|
|
139
|
+
get provider() {
|
|
140
|
+
return "mastra";
|
|
141
|
+
}
|
|
142
|
+
chat() {
|
|
143
|
+
throw new Error(
|
|
144
|
+
"@mastra/livekit: reply generation runs through the Mastra agent via llmNode; the placeholder LLM cannot be used for inference."
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var MastraVoiceAgent = class extends agents.voice.Agent {
|
|
149
|
+
mastraAgent;
|
|
150
|
+
memory;
|
|
151
|
+
requestContext;
|
|
152
|
+
streamOptions;
|
|
153
|
+
replyGenerator;
|
|
154
|
+
constructor(options) {
|
|
155
|
+
if (options.agent && options.generate) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"@mastra/livekit: MastraVoiceAgent requires `agent` or `generate`, not both \u2014 they are mutually exclusive reply sources."
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
super({
|
|
161
|
+
id: options.id,
|
|
162
|
+
instructions: options.instructions ?? DEFAULT_INSTRUCTIONS,
|
|
163
|
+
stt: options.stt,
|
|
164
|
+
vad: options.vad,
|
|
165
|
+
llm: new MastraPlaceholderLLM(),
|
|
166
|
+
tts: options.tts,
|
|
167
|
+
turnHandling: options.turnHandling
|
|
168
|
+
});
|
|
169
|
+
this.memory = options.memory ?? false;
|
|
170
|
+
this.requestContext = toRequestContext(options.requestContext);
|
|
171
|
+
this.streamOptions = options.streamOptions;
|
|
172
|
+
if (options.generate) {
|
|
173
|
+
this.replyGenerator = options.generate;
|
|
174
|
+
} else if (options.agent) {
|
|
175
|
+
this.mastraAgent = options.agent;
|
|
176
|
+
this.replyGenerator = createAgentReplyGenerator({
|
|
177
|
+
agent: options.agent,
|
|
178
|
+
streamOptions: options.streamOptions,
|
|
179
|
+
toolFeedback: options.toolFeedback,
|
|
180
|
+
onTurnComplete: options.onTurnComplete
|
|
181
|
+
});
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error("@mastra/livekit: MastraVoiceAgent requires `agent` or `generate`.");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async llmNode(chatCtx, _toolCtx, _modelSettings) {
|
|
187
|
+
const messages = this.memory === false ? chatContextToMessages(chatCtx) : extractNewTurnMessages(chatCtx);
|
|
188
|
+
if (messages.length === 0) return null;
|
|
189
|
+
return this.replyGenerator({
|
|
190
|
+
messages,
|
|
191
|
+
chatCtx,
|
|
192
|
+
memory: this.memory,
|
|
193
|
+
requestContext: this.requestContext,
|
|
194
|
+
tracingContext: this.streamOptions?.tracingContext
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
function createMastraVoiceAgent(options) {
|
|
199
|
+
return new MastraVoiceAgent(options);
|
|
200
|
+
}
|
|
201
|
+
function modelMeta(metadata) {
|
|
202
|
+
const out = {};
|
|
203
|
+
if (metadata?.modelProvider) out.modelProvider = metadata.modelProvider;
|
|
204
|
+
if (metadata?.modelName) out.modelName = metadata.modelName;
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
var ms = (n) => `${Math.round(n)}ms`;
|
|
208
|
+
var secs = (n) => `${(n / 1e3).toFixed(1)}s`;
|
|
209
|
+
function describeMetric(metric) {
|
|
210
|
+
switch (metric.type) {
|
|
211
|
+
case "eou_metrics":
|
|
212
|
+
return {
|
|
213
|
+
name: `eou ${ms(metric.endOfUtteranceDelayMs)}`,
|
|
214
|
+
data: {
|
|
215
|
+
endOfUtteranceDelayMs: metric.endOfUtteranceDelayMs,
|
|
216
|
+
transcriptionDelayMs: metric.transcriptionDelayMs,
|
|
217
|
+
onUserTurnCompletedDelayMs: metric.onUserTurnCompletedDelayMs
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
case "stt_metrics":
|
|
221
|
+
return {
|
|
222
|
+
name: `stt ${secs(metric.audioDurationMs)}`,
|
|
223
|
+
data: {
|
|
224
|
+
audioDurationMs: metric.audioDurationMs,
|
|
225
|
+
durationMs: metric.durationMs,
|
|
226
|
+
streamed: metric.streamed,
|
|
227
|
+
...modelMeta(metric.metadata)
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
case "llm_metrics":
|
|
231
|
+
return {
|
|
232
|
+
name: `llm ttft ${ms(metric.ttftMs)}`,
|
|
233
|
+
data: {
|
|
234
|
+
ttftMs: metric.ttftMs,
|
|
235
|
+
durationMs: metric.durationMs,
|
|
236
|
+
tokensPerSecond: metric.tokensPerSecond,
|
|
237
|
+
promptTokens: metric.promptTokens,
|
|
238
|
+
completionTokens: metric.completionTokens,
|
|
239
|
+
totalTokens: metric.totalTokens,
|
|
240
|
+
cancelled: metric.cancelled,
|
|
241
|
+
...modelMeta(metric.metadata)
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
case "tts_metrics":
|
|
245
|
+
return {
|
|
246
|
+
name: `tts ttfb ${ms(metric.ttfbMs)}`,
|
|
247
|
+
data: {
|
|
248
|
+
ttfbMs: metric.ttfbMs,
|
|
249
|
+
durationMs: metric.durationMs,
|
|
250
|
+
audioDurationMs: metric.audioDurationMs,
|
|
251
|
+
charactersCount: metric.charactersCount,
|
|
252
|
+
cancelled: metric.cancelled,
|
|
253
|
+
streamed: metric.streamed,
|
|
254
|
+
...modelMeta(metric.metadata)
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
case "vad_metrics":
|
|
258
|
+
return {
|
|
259
|
+
name: "vad",
|
|
260
|
+
data: {
|
|
261
|
+
idleTimeMs: metric.idleTimeMs,
|
|
262
|
+
inferenceCount: metric.inferenceCount,
|
|
263
|
+
inferenceDurationTotalMs: metric.inferenceDurationTotalMs
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
case "realtime_model_metrics":
|
|
267
|
+
return {
|
|
268
|
+
name: `realtime ttft ${ms(metric.ttftMs)}`,
|
|
269
|
+
data: {
|
|
270
|
+
ttftMs: metric.ttftMs,
|
|
271
|
+
durationMs: metric.durationMs,
|
|
272
|
+
tokensPerSecond: metric.tokensPerSecond,
|
|
273
|
+
promptTokens: metric.inputTokens,
|
|
274
|
+
completionTokens: metric.outputTokens,
|
|
275
|
+
totalTokens: metric.totalTokens,
|
|
276
|
+
...modelMeta(metric.metadata)
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
default:
|
|
280
|
+
return void 0;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function startVoiceCallObservability(options) {
|
|
284
|
+
const span = observability.getOrCreateSpan({
|
|
285
|
+
mastra: options.mastra,
|
|
286
|
+
type: observability.SpanType.GENERIC,
|
|
287
|
+
name: "voice call",
|
|
288
|
+
requestContext: options.requestContext,
|
|
289
|
+
metadata: {
|
|
290
|
+
agentId: options.agentId,
|
|
291
|
+
roomName: options.roomName,
|
|
292
|
+
...options.metadata.threadId ? { threadId: options.metadata.threadId } : {},
|
|
293
|
+
...options.metadata.resourceId ? { resourceId: options.metadata.resourceId } : {}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
if (!span) return void 0;
|
|
297
|
+
const usage = new agents.metrics.ModelUsageCollector();
|
|
298
|
+
let finalized = false;
|
|
299
|
+
const finalize = (opts) => {
|
|
300
|
+
if (finalized) return;
|
|
301
|
+
finalized = true;
|
|
302
|
+
const summary = usage.flatten();
|
|
303
|
+
if (opts?.error) {
|
|
304
|
+
span.error({
|
|
305
|
+
error: opts.error instanceof Error ? opts.error : new Error(String(opts.error)),
|
|
306
|
+
metadata: { usage: summary }
|
|
307
|
+
});
|
|
308
|
+
} else {
|
|
309
|
+
span.end({ output: { usage: summary } });
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
return {
|
|
313
|
+
span,
|
|
314
|
+
tracingContext: { currentSpan: span },
|
|
315
|
+
attach(session) {
|
|
316
|
+
session.on(agents.voice.AgentSessionEventTypes.MetricsCollected, (event) => {
|
|
317
|
+
usage.collect(event.metrics);
|
|
318
|
+
const described = describeMetric(event.metrics);
|
|
319
|
+
if (!described) return;
|
|
320
|
+
span.createEventSpan({ type: observability.SpanType.GENERIC, name: described.name, output: described.data });
|
|
321
|
+
});
|
|
322
|
+
session.on(agents.voice.AgentSessionEventTypes.Close, (event) => {
|
|
323
|
+
finalize({ error: event?.error ?? void 0 });
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
finalize
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async function ensureVoiceCallThread({
|
|
330
|
+
memory,
|
|
331
|
+
threadId,
|
|
332
|
+
resourceId,
|
|
333
|
+
roomName
|
|
334
|
+
}) {
|
|
335
|
+
const existing = await memory.getThreadById({ threadId });
|
|
336
|
+
if (existing) return;
|
|
337
|
+
await memory.createThread({
|
|
338
|
+
threadId,
|
|
339
|
+
resourceId,
|
|
340
|
+
title: "Voice call",
|
|
341
|
+
metadata: { source: "livekit", roomName }
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
async function persistSpokenGreeting({
|
|
345
|
+
memory,
|
|
346
|
+
threadId,
|
|
347
|
+
resourceId,
|
|
348
|
+
greeting
|
|
349
|
+
}) {
|
|
350
|
+
if (!greeting.trim()) return;
|
|
351
|
+
const message = {
|
|
352
|
+
id: crypto.randomUUID(),
|
|
353
|
+
role: "assistant",
|
|
354
|
+
type: "text",
|
|
355
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
356
|
+
threadId,
|
|
357
|
+
resourceId,
|
|
358
|
+
content: {
|
|
359
|
+
format: 2,
|
|
360
|
+
parts: [{ type: "text", text: greeting }],
|
|
361
|
+
metadata: { source: "voice", kind: "greeting" }
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
await memory.saveMessages({ messages: [message] });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/worker-setup.ts
|
|
368
|
+
var pendingSetup = [];
|
|
369
|
+
var requestedEouMethods = /* @__PURE__ */ new Set();
|
|
370
|
+
function queueWorkerSetup(setup) {
|
|
371
|
+
pendingSetup.push(setup);
|
|
372
|
+
}
|
|
373
|
+
function workerSetupComplete() {
|
|
374
|
+
return Promise.allSettled(pendingSetup);
|
|
375
|
+
}
|
|
376
|
+
function requestEouMethod(method) {
|
|
377
|
+
requestedEouMethods.add(method);
|
|
378
|
+
}
|
|
379
|
+
function isEouMethodRequested(method) {
|
|
380
|
+
return requestedEouMethods.has(method);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// src/worker.ts
|
|
384
|
+
var EOU_METHODS = {
|
|
385
|
+
english: "lk_end_of_utterance_en",
|
|
386
|
+
multilingual: "lk_end_of_utterance_multilingual"
|
|
387
|
+
};
|
|
388
|
+
async function loadSileroVad() {
|
|
389
|
+
let silero;
|
|
390
|
+
try {
|
|
391
|
+
silero = await import('@livekit/agents-plugin-silero');
|
|
392
|
+
} catch (error) {
|
|
393
|
+
throw new Error(
|
|
394
|
+
"@mastra/livekit: voice activity detection requires '@livekit/agents-plugin-silero'. Install it, pass your own `vad` instance, or set `vad: false`.",
|
|
395
|
+
{ cause: error }
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return silero.VAD.load();
|
|
399
|
+
}
|
|
400
|
+
async function loadTurnDetector(kind) {
|
|
401
|
+
let plugin;
|
|
402
|
+
try {
|
|
403
|
+
plugin = await import('@livekit/agents-plugin-livekit');
|
|
404
|
+
} catch (error) {
|
|
405
|
+
throw new Error(
|
|
406
|
+
`@mastra/livekit: turnDetection '${kind}' requires '@livekit/agents-plugin-livekit'. Install it or use a built-in mode like 'vad' or 'stt'.`,
|
|
407
|
+
{ cause: error }
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return kind === "english" ? new plugin.turnDetector.EnglishModel() : new plugin.turnDetector.MultilingualModel();
|
|
411
|
+
}
|
|
412
|
+
async function resolveMastraAgent(options, args) {
|
|
413
|
+
let ref = typeof options.agent === "function" ? await options.agent(args) : options.agent;
|
|
414
|
+
ref ??= args.metadata.agentId;
|
|
415
|
+
if (!ref) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
"@mastra/livekit: no Mastra agent specified. Set `agent` on createLiveKitWorker or pass `agentId` in the dispatch metadata (e.g. via liveKitConnectionRoute)."
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (typeof ref !== "string") return ref;
|
|
421
|
+
try {
|
|
422
|
+
return options.mastra.getAgentById(ref);
|
|
423
|
+
} catch {
|
|
424
|
+
return options.mastra.getAgent(ref);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function resolveWorkflow(options, args) {
|
|
428
|
+
const resolver = options.workflow;
|
|
429
|
+
const ref = typeof resolver === "function" ? await resolver(args) : resolver ?? "";
|
|
430
|
+
if (!ref) {
|
|
431
|
+
throw new Error("@mastra/livekit: no workflow specified. Set `workflow` on createLiveKitWorker.");
|
|
432
|
+
}
|
|
433
|
+
if (typeof ref !== "string") return { workflow: ref };
|
|
434
|
+
return { workflow: options.mastra.getWorkflowById(ref) };
|
|
435
|
+
}
|
|
436
|
+
function resolveMemory(options, mastraAgent, args, roomName) {
|
|
437
|
+
if (options.memory === false) return false;
|
|
438
|
+
if (typeof options.memory === "function") return options.memory({ ...args, roomName });
|
|
439
|
+
if (mastraAgent && !mastraAgent.hasOwnMemory()) return false;
|
|
440
|
+
const thread = args.metadata.threadId ?? roomName;
|
|
441
|
+
return { thread, resource: args.metadata.resourceId ?? thread };
|
|
442
|
+
}
|
|
443
|
+
async function resolveMemoryInstance(options, mastraAgent, args, requestContext) {
|
|
444
|
+
if (mastraAgent) return await mastraAgent.getMemory({ requestContext }) ?? null;
|
|
445
|
+
const resolver = options.memoryInstance;
|
|
446
|
+
if (!resolver) return null;
|
|
447
|
+
const instance = typeof resolver === "function" ? await resolver(args) : resolver;
|
|
448
|
+
if (!instance) return null;
|
|
449
|
+
instance.__registerMastra(options.mastra);
|
|
450
|
+
if (!instance.hasOwnStorage) {
|
|
451
|
+
const storage = options.mastra.getStorage();
|
|
452
|
+
if (storage) instance.setStorage(storage);
|
|
453
|
+
}
|
|
454
|
+
return instance;
|
|
455
|
+
}
|
|
456
|
+
function buildTurnHandling(options, turnDetection) {
|
|
457
|
+
return {
|
|
458
|
+
...turnDetection ? { turnDetection } : {},
|
|
459
|
+
// Preemptive generation re-runs the Mastra agent on interim transcripts (up to 3
|
|
460
|
+
// times per turn), and every run persists the user message to the memory thread —
|
|
461
|
+
// duplicating and even saving partial transcripts. Off by default; opt back in via
|
|
462
|
+
// `turnHandling.preemptiveGeneration` if the latency win matters more than exact
|
|
463
|
+
// thread history.
|
|
464
|
+
preemptiveGeneration: { enabled: false },
|
|
465
|
+
...options.turnHandling
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
async function resolveInstructions(mastraAgent, requestContext) {
|
|
469
|
+
try {
|
|
470
|
+
const instructions = await mastraAgent.getInstructions({ requestContext });
|
|
471
|
+
return typeof instructions === "string" ? instructions : void 0;
|
|
472
|
+
} catch {
|
|
473
|
+
return void 0;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function createLiveKitWorker(options) {
|
|
477
|
+
if (options.generate && (options.agent || options.workflow)) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
"@mastra/livekit: set exactly one reply generator \u2014 `generate`, `agent`, or `workflow` \u2014 not a combination."
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
if (options.agent && options.workflow) {
|
|
483
|
+
throw new Error(
|
|
484
|
+
"@mastra/livekit: set `agent` or `workflow`, not both \u2014 they are mutually exclusive reply generators."
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
if (options.workflow && !options.workflowInput) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
"@mastra/livekit: `workflowInput` is required when `workflow` is set. Map the turn into the workflow inputData, e.g. workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) })."
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
const wantsSileroVad = options.vad === void 0 || options.vad === "silero";
|
|
493
|
+
if (options.turnDetection === "multilingual" || options.turnDetection === "english") {
|
|
494
|
+
requestEouMethod(EOU_METHODS[options.turnDetection]);
|
|
495
|
+
queueWorkerSetup(
|
|
496
|
+
import('@livekit/agents-plugin-livekit').then(() => {
|
|
497
|
+
for (const method of Object.values(EOU_METHODS)) {
|
|
498
|
+
if (!isEouMethodRequested(method)) {
|
|
499
|
+
delete agents.InferenceRunner.registeredRunners[method];
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}).catch(() => {
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
return agents.defineAgent({
|
|
507
|
+
prewarm: async (proc) => {
|
|
508
|
+
if (wantsSileroVad) {
|
|
509
|
+
proc.userData.vad = await loadSileroVad();
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
entry: async (ctx) => {
|
|
513
|
+
const logger = options.mastra.getLogger();
|
|
514
|
+
const metadata = chunkMWTEZOBS_cjs.parseSessionMetadata(ctx.job.metadata);
|
|
515
|
+
const args = { metadata, ctx };
|
|
516
|
+
const requestContext$1 = metadata.requestContext ? new requestContext.RequestContext(Object.entries(metadata.requestContext)) : void 0;
|
|
517
|
+
let mastraAgent;
|
|
518
|
+
let replyGenerator;
|
|
519
|
+
let agentLabel;
|
|
520
|
+
if (options.generate) {
|
|
521
|
+
replyGenerator = options.generate;
|
|
522
|
+
agentLabel = "mastra-voice";
|
|
523
|
+
} else if (options.workflow) {
|
|
524
|
+
const { workflow } = await resolveWorkflow(options, args);
|
|
525
|
+
agentLabel = workflow.id;
|
|
526
|
+
const mapInput = options.workflowInput;
|
|
527
|
+
replyGenerator = chunkMWTEZOBS_cjs.createWorkflowReplyGenerator({
|
|
528
|
+
workflow,
|
|
529
|
+
workflowInput: (turnCtx) => mapInput({ ...turnCtx, metadata }),
|
|
530
|
+
replyStep: options.replyStep,
|
|
531
|
+
resultText: options.resultText,
|
|
532
|
+
toolFeedback: options.toolFeedback,
|
|
533
|
+
onTurnComplete: options.onTurnComplete
|
|
534
|
+
});
|
|
535
|
+
} else {
|
|
536
|
+
mastraAgent = await resolveMastraAgent(options, args);
|
|
537
|
+
agentLabel = mastraAgent.id ?? mastraAgent.name;
|
|
538
|
+
}
|
|
539
|
+
await ctx.connect();
|
|
540
|
+
const roomName = ctx.room.name ?? "mastra-voice";
|
|
541
|
+
let vad;
|
|
542
|
+
if (options.vad && options.vad !== "silero") {
|
|
543
|
+
vad = options.vad;
|
|
544
|
+
} else if (wantsSileroVad) {
|
|
545
|
+
vad = ctx.proc.userData.vad ?? await loadSileroVad();
|
|
546
|
+
}
|
|
547
|
+
let turnDetection;
|
|
548
|
+
if (options.turnDetection === "multilingual" || options.turnDetection === "english") {
|
|
549
|
+
turnDetection = await loadTurnDetector(options.turnDetection);
|
|
550
|
+
} else {
|
|
551
|
+
turnDetection = options.turnDetection;
|
|
552
|
+
}
|
|
553
|
+
const memory = resolveMemory(options, mastraAgent, args, roomName);
|
|
554
|
+
const memoryInstance = memory ? await resolveMemoryInstance(options, mastraAgent, args, requestContext$1) : null;
|
|
555
|
+
if (memory && memoryInstance) {
|
|
556
|
+
try {
|
|
557
|
+
await ensureVoiceCallThread({
|
|
558
|
+
memory: memoryInstance,
|
|
559
|
+
threadId: memory.thread,
|
|
560
|
+
resourceId: memory.resource ?? memory.thread,
|
|
561
|
+
roomName
|
|
562
|
+
});
|
|
563
|
+
} catch (error) {
|
|
564
|
+
logger.warn("@mastra/livekit: failed to create the voice call thread", error);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const voiceObs = options.observability === false ? void 0 : startVoiceCallObservability({
|
|
568
|
+
mastra: options.mastra,
|
|
569
|
+
agentId: agentLabel,
|
|
570
|
+
roomName,
|
|
571
|
+
metadata,
|
|
572
|
+
requestContext: requestContext$1
|
|
573
|
+
});
|
|
574
|
+
if (voiceObs) {
|
|
575
|
+
ctx.addShutdownCallback(async () => {
|
|
576
|
+
voiceObs.finalize();
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (options.onCallEnd) {
|
|
580
|
+
const onCallEnd = options.onCallEnd;
|
|
581
|
+
ctx.addShutdownCallback(async () => {
|
|
582
|
+
try {
|
|
583
|
+
await onCallEnd({ memory, memoryInstance, metadata, requestContext: requestContext$1, roomName, ctx });
|
|
584
|
+
} catch (error) {
|
|
585
|
+
logger.warn("@mastra/livekit: onCallEnd hook threw", error);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
const agent = createMastraVoiceAgent({
|
|
590
|
+
...replyGenerator ? { generate: replyGenerator } : { agent: mastraAgent },
|
|
591
|
+
instructions: mastraAgent ? await resolveInstructions(mastraAgent, requestContext$1) : void 0,
|
|
592
|
+
memory,
|
|
593
|
+
requestContext: requestContext$1,
|
|
594
|
+
toolFeedback: options.toolFeedback,
|
|
595
|
+
onTurnComplete: options.onTurnComplete,
|
|
596
|
+
streamOptions: voiceObs ? { tracingContext: voiceObs.tracingContext } : void 0
|
|
597
|
+
});
|
|
598
|
+
const session = new agents.voice.AgentSession({
|
|
599
|
+
stt: options.stt,
|
|
600
|
+
tts: options.tts,
|
|
601
|
+
vad,
|
|
602
|
+
turnHandling: buildTurnHandling(options, turnDetection),
|
|
603
|
+
...options.sessionOptions
|
|
604
|
+
});
|
|
605
|
+
voiceObs?.attach(session);
|
|
606
|
+
try {
|
|
607
|
+
await session.start({
|
|
608
|
+
agent,
|
|
609
|
+
room: ctx.room,
|
|
610
|
+
inputOptions: options.inputOptions,
|
|
611
|
+
outputOptions: options.outputOptions
|
|
612
|
+
});
|
|
613
|
+
if (options.greeting) {
|
|
614
|
+
session.say(options.greeting);
|
|
615
|
+
if (options.persistGreeting !== false && memory && memoryInstance) {
|
|
616
|
+
try {
|
|
617
|
+
await persistSpokenGreeting({
|
|
618
|
+
memory: memoryInstance,
|
|
619
|
+
threadId: memory.thread,
|
|
620
|
+
resourceId: memory.resource ?? memory.thread,
|
|
621
|
+
greeting: options.greeting
|
|
622
|
+
});
|
|
623
|
+
} catch (error) {
|
|
624
|
+
logger.warn("@mastra/livekit: failed to persist the greeting", error);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
await options.onSessionStart?.({ session, ctx, agent, metadata });
|
|
629
|
+
} catch (error) {
|
|
630
|
+
voiceObs?.finalize({ error });
|
|
631
|
+
throw error;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function resolveWorkerEntryPath(entry) {
|
|
637
|
+
if (entry instanceof URL) return url.fileURLToPath(entry);
|
|
638
|
+
return entry.startsWith("file:") ? url.fileURLToPath(entry) : entry;
|
|
639
|
+
}
|
|
640
|
+
function runLiveKitWorker(options) {
|
|
641
|
+
void workerSetupComplete().then(() => {
|
|
642
|
+
agents.cli.runApp(
|
|
643
|
+
new agents.ServerOptions({
|
|
644
|
+
agent: resolveWorkerEntryPath(options.entry),
|
|
645
|
+
agentName: options.agentName ?? chunkMWTEZOBS_cjs.DEFAULT_LIVEKIT_AGENT_NAME,
|
|
646
|
+
...options.serverOptions
|
|
647
|
+
})
|
|
648
|
+
);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
exports.chatContextToMessages = chatContextToMessages;
|
|
653
|
+
exports.createLiveKitWorker = createLiveKitWorker;
|
|
654
|
+
exports.runLiveKitWorker = runLiveKitWorker;
|
|
655
|
+
//# sourceMappingURL=worker-entry.cjs.map
|
|
656
|
+
//# sourceMappingURL=worker-entry.cjs.map
|