@mastra/livekit 0.2.0 → 0.3.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -0
- package/README.md +164 -38
- package/dist/bridge.d.ts +76 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/chunk-4O7IN74Y.js +568 -0
- package/dist/chunk-4O7IN74Y.js.map +1 -0
- package/dist/chunk-DBVKNDAQ.cjs +574 -0
- package/dist/chunk-DBVKNDAQ.cjs.map +1 -0
- package/dist/consent.d.ts +55 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/end-call.d.ts +50 -0
- package/dist/end-call.d.ts.map +1 -0
- package/dist/index.cjs +46 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/dist/llm-plugin.d.ts +112 -0
- package/dist/llm-plugin.d.ts.map +1 -0
- package/dist/messages.d.ts +26 -0
- package/dist/messages.d.ts.map +1 -1
- package/dist/plugin-entry.cjs +182 -0
- package/dist/plugin-entry.cjs.map +1 -0
- package/dist/plugin-entry.d.ts +6 -0
- package/dist/plugin-entry.d.ts.map +1 -0
- package/dist/plugin-entry.js +177 -0
- package/dist/plugin-entry.js.map +1 -0
- package/dist/remote.d.ts +68 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/worker-entry.cjs +157 -210
- package/dist/worker-entry.cjs.map +1 -1
- package/dist/worker-entry.d.ts +5 -3
- package/dist/worker-entry.d.ts.map +1 -1
- package/dist/worker-entry.js +144 -210
- package/dist/worker-entry.js.map +1 -1
- package/dist/worker.d.ts +281 -7
- package/dist/worker.d.ts.map +1 -1
- package/package.json +15 -5
package/dist/remote.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { VoiceReplyGenerator, VoiceToolCall, VoiceTurnCompleteHook } from './bridge.js';
|
|
2
|
+
/** Connect + first-token budget when not overridden. Plugin mode passes `connOptions.timeoutMs`. */
|
|
3
|
+
export declare const DEFAULT_REMOTE_TIMEOUT_MS = 10000;
|
|
4
|
+
/** Standalone initial-connection retry attempts. Plugin mode forces this to 0 (base class owns retries). */
|
|
5
|
+
export declare const DEFAULT_REMOTE_RETRIES = 2;
|
|
6
|
+
/**
|
|
7
|
+
* Options for the remote Mastra transport. Shape mirrors the in-process `AgentReplyGeneratorOptions`
|
|
8
|
+
* so `MastraLLM` can accept either source interchangeably.
|
|
9
|
+
*/
|
|
10
|
+
export interface RemoteMastraAgentOptions {
|
|
11
|
+
/** Base URL of the remote Mastra server, e.g. `https://my-app.mastra.cloud`. */
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
/** Agent key in the Mastra config's `agents`. */
|
|
14
|
+
agentId: string;
|
|
15
|
+
/** Path prefix for the Mastra API. Defaults to `'/api'`. */
|
|
16
|
+
apiPrefix?: string;
|
|
17
|
+
/** Static headers, or a (possibly async) resolver invoked per turn — e.g. to mint a fresh token. */
|
|
18
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
19
|
+
/** Injectable `fetch` for tests/proxies. Defaults to `globalThis.fetch`. */
|
|
20
|
+
fetch?: typeof globalThis.fetch;
|
|
21
|
+
/**
|
|
22
|
+
* Connect + first-token timeout in ms. Plugin mode default: LiveKit's `connOptions.timeoutMs` (10s).
|
|
23
|
+
* Standalone default: {@link DEFAULT_REMOTE_TIMEOUT_MS}.
|
|
24
|
+
*/
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Initial-connection retry attempts (before the first chunk only). Standalone default:
|
|
28
|
+
* {@link DEFAULT_REMOTE_RETRIES}. In plugin mode the LiveKit base class owns retries and this is
|
|
29
|
+
* forced to 0.
|
|
30
|
+
*/
|
|
31
|
+
retries?: number;
|
|
32
|
+
/** Extra fields merged into each stream request body (advanced). */
|
|
33
|
+
body?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
/** {@link RemoteMastraAgentOptions} plus the per-turn observer hooks the generator threads through. */
|
|
36
|
+
export interface RemoteAgentReplyGeneratorOptions extends RemoteMastraAgentOptions {
|
|
37
|
+
/** Speak a short phrase while a tool runs. See {@link MastraVoiceAgentOptions.toolFeedback}. */
|
|
38
|
+
toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;
|
|
39
|
+
/** Notified as each tool-call chunk arrives, mid-stream. See {@link MastraVoiceAgentOptions.onToolCall}. */
|
|
40
|
+
onToolCall?: (toolCall: VoiceToolCall) => void;
|
|
41
|
+
/** Fired off the audio path after the reply streams. See {@link MastraVoiceAgentOptions.onTurnComplete}. */
|
|
42
|
+
onTurnComplete?: VoiceTurnCompleteHook;
|
|
43
|
+
}
|
|
44
|
+
type RawChunk = {
|
|
45
|
+
type?: string;
|
|
46
|
+
payload?: Record<string, unknown>;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Reads a Mastra SSE stream and yields each event's parsed JSON. Framing matches the server's
|
|
50
|
+
* `processMastraStream` (buffer, split on `\n\n`, strip `data: `, stop on `[DONE]`); undecodable
|
|
51
|
+
* `data:` lines are skipped. Aborting `signal` cancels the underlying reader.
|
|
52
|
+
*/
|
|
53
|
+
export declare function readMastraSSE(body: globalThis.ReadableStream<Uint8Array>, signal: AbortSignal): AsyncGenerator<RawChunk>;
|
|
54
|
+
/**
|
|
55
|
+
* A {@link VoiceReplyGenerator} that runs the Mastra agent loop on a **remote** Mastra server over
|
|
56
|
+
* HTTP/SSE. Shaped exactly like the in-process `createAgentReplyGenerator`: it consumes the same
|
|
57
|
+
* chunk vocabulary, drives the same `toolFeedback` / `onToolCall` / `onTurnComplete` seams, and
|
|
58
|
+
* cancelling the returned stream (LiveKit does this on barge-in) tears down the HTTP request so the
|
|
59
|
+
* server aborts generation.
|
|
60
|
+
*
|
|
61
|
+
* Usable standalone via the worker's `generate:` hatch (a minimum-viable remote worker mode), and as
|
|
62
|
+
* the transport `MastraLLM` wraps. Errors are thrown as LiveKit `APIError` subclasses so the plugin's
|
|
63
|
+
* base-class retry loop and `FallbackAdapter` behave; a connect + first-token watchdog prevents
|
|
64
|
+
* indefinite dead air.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createRemoteAgentReplyGenerator(options: RemoteAgentReplyGeneratorOptions): VoiceReplyGenerator;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAEb,qBAAqB,EAEtB,MAAM,UAAU,CAAC;AAGlB,oGAAoG;AACpG,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAChD,4GAA4G;AAC5G,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAQxC;;;GAGG;AACH,MAAM,WAAW,wBAAwB;IACvC,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACpG,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,uGAAuG;AACvG,MAAM,WAAW,gCAAiC,SAAQ,wBAAwB;IAChF,gGAAgG;IAChG,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACtE,4GAA4G;IAC5G,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,4GAA4G;IAC5G,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACxC;AAED,KAAK,QAAQ,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAwCrE;;;;GAIG;AACH,wBAAuB,aAAa,CAClC,IAAI,EAAE,UAAU,CAAC,cAAc,CAAC,UAAU,CAAC,EAC3C,MAAM,EAAE,WAAW,GAClB,cAAc,CAAC,QAAQ,CAAC,CAuC1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,gCAAgC,GAAG,mBAAmB,CA+N9G"}
|
package/dist/worker-entry.cjs
CHANGED
|
@@ -1,203 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkMWTEZOBS_cjs = require('./chunk-MWTEZOBS.cjs');
|
|
4
|
+
var chunkDBVKNDAQ_cjs = require('./chunk-DBVKNDAQ.cjs');
|
|
4
5
|
var agents = require('@livekit/agents');
|
|
5
6
|
var requestContext = require('@mastra/core/request-context');
|
|
6
|
-
var web = require('stream/web');
|
|
7
7
|
var observability = require('@mastra/core/observability');
|
|
8
8
|
var crypto = require('crypto');
|
|
9
9
|
var url = require('url');
|
|
10
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
11
|
function modelMeta(metadata) {
|
|
202
12
|
const out = {};
|
|
203
13
|
if (metadata?.modelProvider) out.modelProvider = metadata.modelProvider;
|
|
@@ -473,6 +283,103 @@ async function resolveInstructions(mastraAgent, requestContext) {
|
|
|
473
283
|
return void 0;
|
|
474
284
|
}
|
|
475
285
|
}
|
|
286
|
+
function resolveGreetingConfig(options) {
|
|
287
|
+
return {
|
|
288
|
+
text: options.greeting,
|
|
289
|
+
persist: options.persistGreeting,
|
|
290
|
+
...options.configuration?.greeting
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function resolveGreetingText(text, context) {
|
|
294
|
+
const resolved = typeof text === "function" ? await text(context) : text;
|
|
295
|
+
return resolved || void 0;
|
|
296
|
+
}
|
|
297
|
+
async function resolveSessionComponent(resolver, fallback, context) {
|
|
298
|
+
const resolved = resolver ? await resolver(context) : void 0;
|
|
299
|
+
return resolved ?? fallback;
|
|
300
|
+
}
|
|
301
|
+
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;
|
|
312
|
+
}
|
|
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"]);
|
|
318
|
+
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
|
+
});
|
|
338
|
+
}
|
|
339
|
+
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
|
+
}
|
|
362
|
+
}
|
|
363
|
+
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
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function composeTurnComplete(user, detector) {
|
|
377
|
+
if (!detector) return user;
|
|
378
|
+
return (ctx) => {
|
|
379
|
+
void detector(ctx);
|
|
380
|
+
return user?.(ctx);
|
|
381
|
+
};
|
|
382
|
+
}
|
|
476
383
|
function createLiveKitWorker(options) {
|
|
477
384
|
if (options.generate && (options.agent || options.workflow)) {
|
|
478
385
|
throw new Error(
|
|
@@ -489,6 +396,11 @@ function createLiveKitWorker(options) {
|
|
|
489
396
|
"@mastra/livekit: `workflowInput` is required when `workflow` is set. Map the turn into the workflow inputData, e.g. workflowInput: ({ chatCtx }) => ({ history: chatContextToMessages(chatCtx) })."
|
|
490
397
|
);
|
|
491
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
|
+
}
|
|
492
404
|
const wantsSileroVad = options.vad === void 0 || options.vad === "silero";
|
|
493
405
|
if (options.turnDetection === "multilingual" || options.turnDetection === "english") {
|
|
494
406
|
requestEouMethod(EOU_METHODS[options.turnDetection]);
|
|
@@ -514,6 +426,8 @@ function createLiveKitWorker(options) {
|
|
|
514
426
|
const metadata = chunkMWTEZOBS_cjs.parseSessionMetadata(ctx.job.metadata);
|
|
515
427
|
const args = { metadata, ctx };
|
|
516
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);
|
|
517
431
|
let mastraAgent;
|
|
518
432
|
let replyGenerator;
|
|
519
433
|
let agentLabel;
|
|
@@ -530,7 +444,7 @@ function createLiveKitWorker(options) {
|
|
|
530
444
|
replyStep: options.replyStep,
|
|
531
445
|
resultText: options.resultText,
|
|
532
446
|
toolFeedback: options.toolFeedback,
|
|
533
|
-
onTurnComplete
|
|
447
|
+
onTurnComplete
|
|
534
448
|
});
|
|
535
449
|
} else {
|
|
536
450
|
mastraAgent = await resolveMastraAgent(options, args);
|
|
@@ -580,24 +494,40 @@ function createLiveKitWorker(options) {
|
|
|
580
494
|
const onCallEnd = options.onCallEnd;
|
|
581
495
|
ctx.addShutdownCallback(async () => {
|
|
582
496
|
try {
|
|
583
|
-
await onCallEnd({
|
|
497
|
+
await onCallEnd({
|
|
498
|
+
memory,
|
|
499
|
+
memoryInstance,
|
|
500
|
+
metadata,
|
|
501
|
+
requestContext: requestContext$1,
|
|
502
|
+
configuration: options.configuration,
|
|
503
|
+
roomName,
|
|
504
|
+
ctx
|
|
505
|
+
});
|
|
584
506
|
} catch (error) {
|
|
585
507
|
logger.warn("@mastra/livekit: onCallEnd hook threw", error);
|
|
586
508
|
}
|
|
587
509
|
});
|
|
588
510
|
}
|
|
589
|
-
const
|
|
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({
|
|
590
514
|
...replyGenerator ? { generate: replyGenerator } : { agent: mastraAgent },
|
|
591
515
|
instructions: mastraAgent ? await resolveInstructions(mastraAgent, requestContext$1) : void 0,
|
|
592
516
|
memory,
|
|
593
517
|
requestContext: requestContext$1,
|
|
594
518
|
toolFeedback: options.toolFeedback,
|
|
595
|
-
onTurnComplete
|
|
519
|
+
onTurnComplete,
|
|
520
|
+
greetingReminder,
|
|
596
521
|
streamOptions: voiceObs ? { tracingContext: voiceObs.tracingContext } : void 0
|
|
597
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
|
+
]);
|
|
598
528
|
const session = new agents.voice.AgentSession({
|
|
599
|
-
stt
|
|
600
|
-
tts
|
|
529
|
+
stt,
|
|
530
|
+
tts,
|
|
601
531
|
vad,
|
|
602
532
|
turnHandling: buildTurnHandling(options, turnDetection),
|
|
603
533
|
...options.sessionOptions
|
|
@@ -610,18 +540,21 @@ function createLiveKitWorker(options) {
|
|
|
610
540
|
inputOptions: options.inputOptions,
|
|
611
541
|
outputOptions: options.outputOptions
|
|
612
542
|
});
|
|
613
|
-
if (
|
|
614
|
-
|
|
615
|
-
if (
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
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
|
+
}
|
|
625
558
|
}
|
|
626
559
|
}
|
|
627
560
|
}
|
|
@@ -649,8 +582,22 @@ function runLiveKitWorker(options) {
|
|
|
649
582
|
});
|
|
650
583
|
}
|
|
651
584
|
|
|
652
|
-
exports
|
|
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
|
+
});
|
|
593
|
+
exports.DEFAULT_END_CALL_DRAIN_MS = DEFAULT_END_CALL_DRAIN_MS;
|
|
594
|
+
exports.DEFAULT_END_CALL_MAX_WAIT_MS = DEFAULT_END_CALL_MAX_WAIT_MS;
|
|
595
|
+
exports.DEFAULT_END_CALL_REASON = DEFAULT_END_CALL_REASON;
|
|
596
|
+
exports.DEFAULT_END_CALL_TOOL = DEFAULT_END_CALL_TOOL;
|
|
653
597
|
exports.createLiveKitWorker = createLiveKitWorker;
|
|
598
|
+
exports.runEndCall = runEndCall;
|
|
654
599
|
exports.runLiveKitWorker = runLiveKitWorker;
|
|
600
|
+
exports.speakGreeting = speakGreeting;
|
|
601
|
+
exports.waitForAgentDoneSpeaking = waitForAgentDoneSpeaking;
|
|
655
602
|
//# sourceMappingURL=worker-entry.cjs.map
|
|
656
603
|
//# sourceMappingURL=worker-entry.cjs.map
|