@mastra/livekit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,112 @@
1
+ import { llm } from '@livekit/agents';
2
+ import type { APIConnectOptions } from '@livekit/agents';
3
+ import type { Agent as MastraAgent } from '@mastra/core/agent';
4
+ import { RequestContext } from '@mastra/core/request-context';
5
+ import type { MastraVoiceAgentMemory, VoiceReplyGenerator, VoiceToolCall, VoiceTurnCompleteHook } from './bridge.js';
6
+ import type { RemoteMastraAgentOptions } from './remote.js';
7
+ export type { RemoteMastraAgentOptions } from './remote.js';
8
+ /**
9
+ * Options for {@link MastraLLM}. Provide **exactly one** reply source — `remote` (the headline: a
10
+ * Mastra app on a remote server), `agent` (an in-process Mastra agent), or `generate` (a custom
11
+ * {@link VoiceReplyGenerator}). The `toolFeedback` / `onToolCall` / `onTurnComplete` hooks apply to
12
+ * the `remote` and `agent` sources; a `generate` source owns its own hooks.
13
+ */
14
+ export interface MastraLLMOptions {
15
+ /** Remote Mastra server. Provide exactly one of `remote`, `agent`, `generate`. */
16
+ remote?: RemoteMastraAgentOptions;
17
+ /** In-process Mastra agent (reuses `createAgentReplyGenerator`). */
18
+ agent?: MastraAgent;
19
+ /** Custom reply source (escape hatch; owns its own tool-feedback / turn-complete behavior). */
20
+ generate?: VoiceReplyGenerator;
21
+ /**
22
+ * Conversation persistence, resolved by the customer per call (from SIP/caller identity). When set,
23
+ * only messages new since the agent last spoke are sent each turn and Mastra Memory supplies
24
+ * history. When omitted/false, the full LiveKit chat context is sent every turn.
25
+ *
26
+ * NOTE: incompatible with the session's `preemptiveGeneration` option — a speculative turn that
27
+ * completes before being discarded pollutes the thread. Leave preemptive generation off when
28
+ * using `memory`.
29
+ *
30
+ * The plugin cannot detect the combination at runtime, so this stays a documented constraint
31
+ * rather than a warning: the LLM interface never receives the session (so the option can't be
32
+ * read), a preemptive `chat()` is shape-identical to a real turn (LiveKit drives the same
33
+ * `generateReply` with a draft transcript and no marker), and the observable signature — a
34
+ * cancelled stream followed by a `chat()` whose trailing user message changed — is exactly what
35
+ * an ordinary barge-in correction looks like, so a heuristic would warn on every barge-in.
36
+ */
37
+ memory?: MastraVoiceAgentMemory | false;
38
+ /** Request context forwarded to generation (tenant, dialed number, ...). */
39
+ requestContext?: RequestContext | Record<string, unknown>;
40
+ /** Speak a short filler while a (server-side) tool runs. Applies to the `remote`/`agent` sources. */
41
+ toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;
42
+ /** Notified as each tool-call chunk arrives, mid-stream. Applies to the `remote`/`agent` sources. */
43
+ onToolCall?: (toolCall: VoiceToolCall) => void;
44
+ /** Fired off the audio path after each reply finishes. Applies to the `remote`/`agent` sources. */
45
+ onTurnComplete?: VoiceTurnCompleteHook;
46
+ }
47
+ /**
48
+ * A standard LiveKit LLM plugin (`llm.LLM`) backed by a Mastra agent. Drop it into the `llm` slot of a
49
+ * customer-owned `voice.AgentSession` and the Mastra app (agent loop, tools, memory, observability)
50
+ * runs wherever it's deployed — most importantly on a **remote** Mastra server reached over HTTP.
51
+ *
52
+ * Tools are defined and executed **server-side** on the Mastra agent; LiveKit-side `toolCtx` is
53
+ * ignored (with a one-time warning). Tool activity surfaces via `toolFeedback` (spoken) and
54
+ * `onToolCall` / `onTurnComplete` (programmatic). `voice.Agent` instructions do **not** reach the
55
+ * Mastra agent — put instructions on the Mastra agent instead.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const session = new voice.AgentSession({
60
+ * stt: 'deepgram/nova-3',
61
+ * tts: 'cartesia/sonic-3',
62
+ * llm: new MastraLLM({
63
+ * remote: { baseUrl: process.env.MASTRA_URL!, agentId: 'callCenter' },
64
+ * memory: { thread: callId, resource: callerId },
65
+ * }),
66
+ * });
67
+ * ```
68
+ */
69
+ export declare class MastraLLM extends llm.LLM {
70
+ #private;
71
+ constructor(options: MastraLLMOptions);
72
+ label(): string;
73
+ get model(): string;
74
+ get provider(): string;
75
+ /**
76
+ * Resolves the reply generator for a turn. Non-remote sources are built once; the remote transport
77
+ * is built per turn so its connect + first-token timeout can come from the session's
78
+ * `connOptions.timeoutMs`, with base-class retries owning retry (transport `retries: 0`).
79
+ */
80
+ private resolveGenerator;
81
+ chat({ chatCtx, toolCtx, connOptions, }: {
82
+ chatCtx: llm.ChatContext;
83
+ toolCtx?: llm.ToolContext;
84
+ connOptions?: APIConnectOptions;
85
+ parallelToolCalls?: boolean;
86
+ toolChoice?: llm.ToolChoice;
87
+ extraKwargs?: Record<string, unknown>;
88
+ }): llm.LLMStream;
89
+ /** No-op in v1: nothing in the LiveKit session/worker ever calls `prewarm()` automatically. */
90
+ prewarm(): void;
91
+ }
92
+ interface MastraLLMStreamOptions {
93
+ chatCtx: llm.ChatContext;
94
+ toolCtx?: llm.ToolContext;
95
+ connOptions: APIConnectOptions;
96
+ generator: VoiceReplyGenerator;
97
+ memory: MastraVoiceAgentMemory | false;
98
+ requestContext?: RequestContext;
99
+ }
100
+ /**
101
+ * The `llm.LLMStream` `MastraLLM` returns per turn. `run()` extracts the turn's messages, drives
102
+ * the reply generator, and pushes assistant `ChatChunk`s into `this.queue` (NOT `this.output` — the
103
+ * base class drains queue → output and computes TTFT / duration / usage). Barge-in aborts via
104
+ * `this.abortController` and `run()` returns silently.
105
+ */
106
+ declare class MastraLLMStream extends llm.LLMStream {
107
+ #private;
108
+ constructor(mastraLLM: MastraLLM, options: MastraLLMStreamOptions);
109
+ protected run(): Promise<void>;
110
+ }
111
+ export { MastraLLMStream };
112
+ //# sourceMappingURL=llm-plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-plugin.d.ts","sourceRoot":"","sources":["../src/llm-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+B,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EACV,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EAGtB,MAAM,UAAU,CAAC;AAGlB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAEzD,YAAY,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,oEAAoE;IACpE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAE/B;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,sBAAsB,GAAG,KAAK,CAAC;IACxC,4EAA4E;IAC5E,cAAc,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE1D,qGAAqG;IACrG,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACtE,qGAAqG;IACrG,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,mGAAmG;IACnG,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACxC;AAiCD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,SAAU,SAAQ,GAAG,CAAC,GAAG;;gBAaxB,OAAO,EAAE,gBAAgB;IAuCrC,KAAK,IAAI,MAAM;IAIf,IAAa,KAAK,IAAI,MAAM,CAE3B;IAED,IAAa,QAAQ,IAAI,MAAM,CAE9B;IAED;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAUf,IAAI,CAAC,EACZ,OAAO,EACP,OAAO,EACP,WAAyC,GAC1C,EAAE;QACD,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC;QACzB,OAAO,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC;QAC1B,WAAW,CAAC,EAAE,iBAAiB,CAAC;QAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAC5B,UAAU,CAAC,EAAE,GAAG,CAAC,UAAU,CAAC;QAC5B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACvC,GAAG,GAAG,CAAC,SAAS;IAsBjB,+FAA+F;IACtF,OAAO,IAAI,IAAI;CACzB;AAED,UAAU,sBAAsB;IAC9B,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC;IACzB,OAAO,CAAC,EAAE,GAAG,CAAC,WAAW,CAAC;IAC1B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,MAAM,EAAE,sBAAsB,GAAG,KAAK,CAAC;IACvC,cAAc,CAAC,EAAE,cAAc,CAAC;CACjC;AAED;;;;;GAKG;AACH,cAAM,eAAgB,SAAQ,GAAG,CAAC,SAAS;;gBAK7B,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,sBAAsB;cAOjD,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgDrC;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -1,18 +1,44 @@
1
1
  import type { llm } from '@livekit/agents';
2
+ /**
3
+ * Fixed id LiveKit gives the customer Agent's instructions when it injects them as a leading
4
+ * `role: 'system'` message into the chat context passed to `chat()` / `llmNode`. We drop this
5
+ * item so the server-side Mastra agent's own system prompt is authoritative.
6
+ */
7
+ export declare const LIVEKIT_INSTRUCTIONS_MESSAGE_ID = "lk.agent_task.instructions";
8
+ /**
9
+ * A message bound for `agent.stream(...)` (in-process) or the Mastra server stream route (remote).
10
+ * `id` carries the LiveKit `ChatMessage.id` so the server can dedupe/upsert by id — making
11
+ * base-class retries, preemptive double-sends, and the interrupted-turn reconciliation recipe idempotent.
12
+ */
2
13
  export type VoiceTurnMessage = {
3
14
  role: 'system';
4
15
  content: string;
16
+ id?: string;
5
17
  } | {
6
18
  role: 'user';
7
19
  content: string;
20
+ id?: string;
8
21
  } | {
9
22
  role: 'assistant';
10
23
  content: string;
24
+ id?: string;
11
25
  };
12
26
  /**
13
27
  * Extracts only the messages added since the agent last spoke. Used when Mastra Memory is
14
28
  * the source of truth for conversation history: prior turns are already persisted in the
15
29
  * thread, so re-sending them would duplicate history.
30
+ *
31
+ * Two extensions over the naive "slice after the last assistant message":
32
+ *
33
+ * - **Interrupted-turn self-heal:** when the last assistant message was cut off by barge-in
34
+ * (`interrupted: true`), the server never persisted it — aborted runs skip persistence — so
35
+ * its heard-only text is missing from the thread. Re-send that fragment (ordered first) this
36
+ * turn to backfill it. It stops being "the last assistant message" once a full reply lands,
37
+ * so each interrupted fragment is sent exactly once, on the following turn.
38
+ * - **Instructions filter:** LiveKit injects the customer Agent's `instructions` as a
39
+ * leading `system` message ({@link LIVEKIT_INSTRUCTIONS_MESSAGE_ID}); the server-side Mastra
40
+ * agent owns its own system prompt, so drop it (it would otherwise ship on the first turn,
41
+ * before any assistant message).
16
42
  */
17
43
  export declare function extractNewTurnMessages(chatCtx: llm.ChatContext): VoiceTurnMessage[];
18
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAE3C,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AA0B3C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,GAAG,gBAAgB,EAAE,CAgBnF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,GAAG,gBAAgB,EAAE,CAQlF"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAE5E;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA2BxD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,GAAG,gBAAgB,EAAE,CAuBnF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,GAAG,CAAC,WAAW,GAAG,gBAAgB,EAAE,CAQlF"}
@@ -0,0 +1,182 @@
1
+ 'use strict';
2
+
3
+ var chunkDBVKNDAQ_cjs = require('./chunk-DBVKNDAQ.cjs');
4
+ var agents = require('@livekit/agents');
5
+ var requestContext = require('@mastra/core/request-context');
6
+
7
+ function toRequestContext(value) {
8
+ if (!value) return void 0;
9
+ if (value instanceof requestContext.RequestContext) return value;
10
+ return new requestContext.RequestContext(Object.entries(value));
11
+ }
12
+ function mapUsageToLiveKit(usage) {
13
+ return {
14
+ completionTokens: usage.completionTokens,
15
+ promptTokens: usage.promptTokens,
16
+ promptCachedTokens: usage.promptCachedTokens,
17
+ totalTokens: usage.totalTokens
18
+ };
19
+ }
20
+ function livekitToolNames(toolCtx) {
21
+ const instance = toolCtx;
22
+ if (typeof instance.flatten === "function") {
23
+ return instance.flatten().map((tool) => {
24
+ const { id, name } = tool;
25
+ return id ?? name ?? "unknown";
26
+ });
27
+ }
28
+ return Object.keys(toolCtx);
29
+ }
30
+ var MastraLLM = class extends agents.llm.LLM {
31
+ #model;
32
+ #memory;
33
+ #requestContext;
34
+ /** Non-remote sources are built once; remote is built per turn so it can pick up `connOptions.timeoutMs`. */
35
+ #staticGenerator;
36
+ #remoteOptions;
37
+ #warnedToolCtx = false;
38
+ constructor(options) {
39
+ super();
40
+ const sources = [
41
+ options.remote ? "remote" : void 0,
42
+ options.agent ? "agent" : void 0,
43
+ options.generate ? "generate" : void 0
44
+ ].filter(Boolean);
45
+ if (sources.length !== 1) {
46
+ throw new Error(
47
+ `@mastra/livekit: MastraLLM requires exactly one reply source \u2014 \`remote\`, \`agent\`, or \`generate\` \u2014 but got ${sources.length === 0 ? "none" : sources.join(" + ")}.`
48
+ );
49
+ }
50
+ this.#memory = options.memory ?? false;
51
+ this.#requestContext = toRequestContext(options.requestContext);
52
+ if (options.remote) {
53
+ this.#model = options.remote.agentId;
54
+ this.#remoteOptions = {
55
+ ...options.remote,
56
+ toolFeedback: options.toolFeedback,
57
+ onToolCall: options.onToolCall,
58
+ onTurnComplete: options.onTurnComplete
59
+ };
60
+ } else if (options.agent) {
61
+ this.#model = options.agent.id ?? options.agent.name;
62
+ this.#staticGenerator = chunkDBVKNDAQ_cjs.createAgentReplyGenerator({
63
+ agent: options.agent,
64
+ toolFeedback: options.toolFeedback,
65
+ onToolCall: options.onToolCall,
66
+ onTurnComplete: options.onTurnComplete
67
+ });
68
+ } else {
69
+ this.#model = "mastra-generator";
70
+ this.#staticGenerator = options.generate;
71
+ }
72
+ }
73
+ label() {
74
+ return "mastra.MastraLLM";
75
+ }
76
+ get model() {
77
+ return this.#model;
78
+ }
79
+ get provider() {
80
+ return "mastra";
81
+ }
82
+ /**
83
+ * Resolves the reply generator for a turn. Non-remote sources are built once; the remote transport
84
+ * is built per turn so its connect + first-token timeout can come from the session's
85
+ * `connOptions.timeoutMs`, with base-class retries owning retry (transport `retries: 0`).
86
+ */
87
+ resolveGenerator(connOptions) {
88
+ if (this.#staticGenerator) return this.#staticGenerator;
89
+ const remote = this.#remoteOptions;
90
+ return chunkDBVKNDAQ_cjs.createRemoteAgentReplyGenerator({
91
+ ...remote,
92
+ retries: 0,
93
+ timeoutMs: remote.timeoutMs ?? connOptions.timeoutMs
94
+ });
95
+ }
96
+ chat({
97
+ chatCtx,
98
+ toolCtx,
99
+ connOptions = agents.DEFAULT_API_CONNECT_OPTIONS
100
+ }) {
101
+ if (!this.#warnedToolCtx && toolCtx) {
102
+ const ignored = livekitToolNames(toolCtx);
103
+ if (ignored.length > 0) {
104
+ this.#warnedToolCtx = true;
105
+ console.warn(
106
+ `@mastra/livekit: MastraLLM ignores LiveKit-side tools (${ignored.join(", ")}). Tools are defined and executed server-side on the Mastra agent \u2014 move them there.`
107
+ );
108
+ }
109
+ }
110
+ return new MastraLLMStream(this, {
111
+ chatCtx,
112
+ toolCtx,
113
+ connOptions,
114
+ generator: this.resolveGenerator(connOptions),
115
+ memory: this.#memory,
116
+ requestContext: this.#requestContext
117
+ });
118
+ }
119
+ /** No-op in v1: nothing in the LiveKit session/worker ever calls `prewarm()` automatically. */
120
+ prewarm() {
121
+ }
122
+ };
123
+ var MastraLLMStream = class extends agents.llm.LLMStream {
124
+ #generator;
125
+ #memory;
126
+ #requestContext;
127
+ constructor(mastraLLM, options) {
128
+ super(mastraLLM, { chatCtx: options.chatCtx, toolCtx: options.toolCtx, connOptions: options.connOptions });
129
+ this.#generator = options.generator;
130
+ this.#memory = options.memory;
131
+ this.#requestContext = options.requestContext;
132
+ }
133
+ async run() {
134
+ const messages = this.#memory === false ? chunkDBVKNDAQ_cjs.chatContextToMessages(this.chatCtx) : chunkDBVKNDAQ_cjs.extractNewTurnMessages(this.chatCtx);
135
+ if (messages.length === 0) return;
136
+ let usage;
137
+ const turnCtx = {
138
+ messages,
139
+ chatCtx: this.chatCtx,
140
+ memory: this.#memory,
141
+ requestContext: this.#requestContext,
142
+ onUsage: (turnUsage) => {
143
+ usage = turnUsage;
144
+ }
145
+ };
146
+ const reply = await this.#generator(turnCtx);
147
+ if (!reply) return;
148
+ if (this.abortController.signal.aborted) {
149
+ await reply.cancel().catch(() => {
150
+ });
151
+ return;
152
+ }
153
+ const id = globalThis.crypto.randomUUID();
154
+ const reader = reply.getReader();
155
+ const onAbort = () => void reader.cancel().catch(() => {
156
+ });
157
+ this.abortController.signal.addEventListener("abort", onAbort, { once: true });
158
+ try {
159
+ for (; ; ) {
160
+ const { done, value } = await reader.read();
161
+ if (done) break;
162
+ if (this.abortController.signal.aborted) break;
163
+ if (value) this.queue.put({ id, delta: { role: "assistant", content: value } });
164
+ }
165
+ } catch (error) {
166
+ if (this.abortController.signal.aborted) return;
167
+ throw error;
168
+ } finally {
169
+ this.abortController.signal.removeEventListener("abort", onAbort);
170
+ }
171
+ if (this.abortController.signal.aborted) return;
172
+ if (usage) this.queue.put({ id, usage: mapUsageToLiveKit(usage) });
173
+ }
174
+ };
175
+
176
+ Object.defineProperty(exports, "createRemoteAgentReplyGenerator", {
177
+ enumerable: true,
178
+ get: function () { return chunkDBVKNDAQ_cjs.createRemoteAgentReplyGenerator; }
179
+ });
180
+ exports.MastraLLM = MastraLLM;
181
+ //# sourceMappingURL=plugin-entry.cjs.map
182
+ //# sourceMappingURL=plugin-entry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/llm-plugin.ts"],"names":["RequestContext","llm","createAgentReplyGenerator","createRemoteAgentReplyGenerator","DEFAULT_API_CONNECT_OPTIONS","chatContextToMessages","extractNewTurnMessages"],"mappings":"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","file":"plugin-entry.cjs","sourcesContent":["import { DEFAULT_API_CONNECT_OPTIONS, llm } from '@livekit/agents';\nimport type { APIConnectOptions } from '@livekit/agents';\nimport type { Agent as MastraAgent } from '@mastra/core/agent';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { createAgentReplyGenerator } from './bridge';\nimport type {\n MastraVoiceAgentMemory,\n VoiceReplyGenerator,\n VoiceToolCall,\n VoiceTurnCompleteHook,\n VoiceTurnContext,\n VoiceTurnUsage,\n} from './bridge';\nimport { chatContextToMessages, extractNewTurnMessages } from './messages';\nimport { createRemoteAgentReplyGenerator } from './remote';\nimport type { RemoteMastraAgentOptions } from './remote';\n\nexport type { RemoteMastraAgentOptions } from './remote';\n\n/**\n * Options for {@link MastraLLM}. Provide **exactly one** reply source — `remote` (the headline: a\n * Mastra app on a remote server), `agent` (an in-process Mastra agent), or `generate` (a custom\n * {@link VoiceReplyGenerator}). The `toolFeedback` / `onToolCall` / `onTurnComplete` hooks apply to\n * the `remote` and `agent` sources; a `generate` source owns its own hooks.\n */\nexport interface MastraLLMOptions {\n /** Remote Mastra server. Provide exactly one of `remote`, `agent`, `generate`. */\n remote?: RemoteMastraAgentOptions;\n /** In-process Mastra agent (reuses `createAgentReplyGenerator`). */\n agent?: MastraAgent;\n /** Custom reply source (escape hatch; owns its own tool-feedback / turn-complete behavior). */\n generate?: VoiceReplyGenerator;\n\n /**\n * Conversation persistence, resolved by the customer per call (from SIP/caller identity). When set,\n * only messages new since the agent last spoke are sent each turn and Mastra Memory supplies\n * history. When omitted/false, the full LiveKit chat context is sent every turn.\n *\n * NOTE: incompatible with the session's `preemptiveGeneration` option — a speculative turn that\n * completes before being discarded pollutes the thread. Leave preemptive generation off when\n * using `memory`.\n *\n * The plugin cannot detect the combination at runtime, so this stays a documented constraint\n * rather than a warning: the LLM interface never receives the session (so the option can't be\n * read), a preemptive `chat()` is shape-identical to a real turn (LiveKit drives the same\n * `generateReply` with a draft transcript and no marker), and the observable signature — a\n * cancelled stream followed by a `chat()` whose trailing user message changed — is exactly what\n * an ordinary barge-in correction looks like, so a heuristic would warn on every barge-in.\n */\n memory?: MastraVoiceAgentMemory | false;\n /** Request context forwarded to generation (tenant, dialed number, ...). */\n requestContext?: RequestContext | Record<string, unknown>;\n\n /** Speak a short filler while a (server-side) tool runs. Applies to the `remote`/`agent` sources. */\n toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;\n /** Notified as each tool-call chunk arrives, mid-stream. Applies to the `remote`/`agent` sources. */\n onToolCall?: (toolCall: VoiceToolCall) => void;\n /** Fired off the audio path after each reply finishes. Applies to the `remote`/`agent` sources. */\n onTurnComplete?: VoiceTurnCompleteHook;\n}\n\nfunction toRequestContext(value: RequestContext | Record<string, unknown> | undefined): RequestContext | undefined {\n if (!value) return undefined;\n if (value instanceof RequestContext) return value;\n return new RequestContext<unknown>(Object.entries(value));\n}\n\nfunction mapUsageToLiveKit(usage: VoiceTurnUsage): llm.CompletionUsage {\n return {\n completionTokens: usage.completionTokens,\n promptTokens: usage.promptTokens,\n promptCachedTokens: usage.promptCachedTokens,\n totalTokens: usage.totalTokens,\n };\n}\n\n/**\n * Tool names carried by a `toolCtx`, across @livekit/agents versions: 1.5+ always passes a\n * `ToolContext` class instance (tools behind getters, `Object.keys` sees only private fields),\n * while 1.4 and the object shorthand pass a plain name→tool map.\n */\nfunction livekitToolNames(toolCtx: object): string[] {\n const instance = toolCtx as { flatten?: unknown };\n if (typeof instance.flatten === 'function') {\n return (instance.flatten as () => object[])().map(tool => {\n const { id, name } = tool as { id?: string; name?: string };\n return id ?? name ?? 'unknown';\n });\n }\n return Object.keys(toolCtx);\n}\n\n/**\n * A standard LiveKit LLM plugin (`llm.LLM`) backed by a Mastra agent. Drop it into the `llm` slot of a\n * customer-owned `voice.AgentSession` and the Mastra app (agent loop, tools, memory, observability)\n * runs wherever it's deployed — most importantly on a **remote** Mastra server reached over HTTP.\n *\n * Tools are defined and executed **server-side** on the Mastra agent; LiveKit-side `toolCtx` is\n * ignored (with a one-time warning). Tool activity surfaces via `toolFeedback` (spoken) and\n * `onToolCall` / `onTurnComplete` (programmatic). `voice.Agent` instructions do **not** reach the\n * Mastra agent — put instructions on the Mastra agent instead.\n *\n * @example\n * ```ts\n * const session = new voice.AgentSession({\n * stt: 'deepgram/nova-3',\n * tts: 'cartesia/sonic-3',\n * llm: new MastraLLM({\n * remote: { baseUrl: process.env.MASTRA_URL!, agentId: 'callCenter' },\n * memory: { thread: callId, resource: callerId },\n * }),\n * });\n * ```\n */\nexport class MastraLLM extends llm.LLM {\n readonly #model: string;\n readonly #memory: MastraVoiceAgentMemory | false;\n readonly #requestContext?: RequestContext;\n /** Non-remote sources are built once; remote is built per turn so it can pick up `connOptions.timeoutMs`. */\n readonly #staticGenerator?: VoiceReplyGenerator;\n readonly #remoteOptions?: RemoteMastraAgentOptions & {\n toolFeedback?: MastraLLMOptions['toolFeedback'];\n onToolCall?: MastraLLMOptions['onToolCall'];\n onTurnComplete?: VoiceTurnCompleteHook;\n };\n #warnedToolCtx = false;\n\n constructor(options: MastraLLMOptions) {\n super();\n const sources = [\n options.remote ? 'remote' : undefined,\n options.agent ? 'agent' : undefined,\n options.generate ? 'generate' : undefined,\n ].filter(Boolean) as string[];\n if (sources.length !== 1) {\n throw new Error(\n `@mastra/livekit: MastraLLM requires exactly one reply source — \\`remote\\`, \\`agent\\`, or \\`generate\\` — ` +\n `but got ${sources.length === 0 ? 'none' : sources.join(' + ')}.`,\n );\n }\n\n this.#memory = options.memory ?? false;\n this.#requestContext = toRequestContext(options.requestContext);\n\n if (options.remote) {\n this.#model = options.remote.agentId;\n this.#remoteOptions = {\n ...options.remote,\n toolFeedback: options.toolFeedback,\n onToolCall: options.onToolCall,\n onTurnComplete: options.onTurnComplete,\n };\n } else if (options.agent) {\n this.#model = options.agent.id ?? options.agent.name;\n this.#staticGenerator = createAgentReplyGenerator({\n agent: options.agent,\n toolFeedback: options.toolFeedback,\n onToolCall: options.onToolCall,\n onTurnComplete: options.onTurnComplete,\n });\n } else {\n this.#model = 'mastra-generator';\n this.#staticGenerator = options.generate;\n }\n }\n\n label(): string {\n return 'mastra.MastraLLM';\n }\n\n override get model(): string {\n return this.#model;\n }\n\n override get provider(): string {\n return 'mastra';\n }\n\n /**\n * Resolves the reply generator for a turn. Non-remote sources are built once; the remote transport\n * is built per turn so its connect + first-token timeout can come from the session's\n * `connOptions.timeoutMs`, with base-class retries owning retry (transport `retries: 0`).\n */\n private resolveGenerator(connOptions: APIConnectOptions): VoiceReplyGenerator {\n if (this.#staticGenerator) return this.#staticGenerator;\n const remote = this.#remoteOptions!;\n return createRemoteAgentReplyGenerator({\n ...remote,\n retries: 0,\n timeoutMs: remote.timeoutMs ?? connOptions.timeoutMs,\n });\n }\n\n override chat({\n chatCtx,\n toolCtx,\n connOptions = DEFAULT_API_CONNECT_OPTIONS,\n }: {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: llm.ToolChoice;\n extraKwargs?: Record<string, unknown>;\n }): llm.LLMStream {\n // Tools run server-side; warn once if the customer wired LiveKit-side tools.\n if (!this.#warnedToolCtx && toolCtx) {\n const ignored = livekitToolNames(toolCtx);\n if (ignored.length > 0) {\n this.#warnedToolCtx = true;\n console.warn(\n `@mastra/livekit: MastraLLM ignores LiveKit-side tools (${ignored.join(', ')}). ` +\n `Tools are defined and executed server-side on the Mastra agent — move them there.`,\n );\n }\n }\n return new MastraLLMStream(this, {\n chatCtx,\n toolCtx,\n connOptions,\n generator: this.resolveGenerator(connOptions),\n memory: this.#memory,\n requestContext: this.#requestContext,\n });\n }\n\n /** No-op in v1: nothing in the LiveKit session/worker ever calls `prewarm()` automatically. */\n override prewarm(): void {}\n}\n\ninterface MastraLLMStreamOptions {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions: APIConnectOptions;\n generator: VoiceReplyGenerator;\n memory: MastraVoiceAgentMemory | false;\n requestContext?: RequestContext;\n}\n\n/**\n * The `llm.LLMStream` `MastraLLM` returns per turn. `run()` extracts the turn's messages, drives\n * the reply generator, and pushes assistant `ChatChunk`s into `this.queue` (NOT `this.output` — the\n * base class drains queue → output and computes TTFT / duration / usage). Barge-in aborts via\n * `this.abortController` and `run()` returns silently.\n */\nclass MastraLLMStream extends llm.LLMStream {\n readonly #generator: VoiceReplyGenerator;\n readonly #memory: MastraVoiceAgentMemory | false;\n readonly #requestContext?: RequestContext;\n\n constructor(mastraLLM: MastraLLM, options: MastraLLMStreamOptions) {\n super(mastraLLM, { chatCtx: options.chatCtx, toolCtx: options.toolCtx, connOptions: options.connOptions });\n this.#generator = options.generator;\n this.#memory = options.memory;\n this.#requestContext = options.requestContext;\n }\n\n protected async run(): Promise<void> {\n const messages =\n this.#memory === false ? chatContextToMessages(this.chatCtx) : extractNewTurnMessages(this.chatCtx);\n // No new input to answer → close without a request (equivalent to the wrapper returning null).\n if (messages.length === 0) return;\n\n let usage: VoiceTurnUsage | undefined;\n const turnCtx: VoiceTurnContext = {\n messages,\n chatCtx: this.chatCtx,\n memory: this.#memory,\n requestContext: this.#requestContext,\n onUsage: turnUsage => {\n usage = turnUsage;\n },\n };\n\n const reply = await this.#generator(turnCtx);\n if (!reply) return;\n if (this.abortController.signal.aborted) {\n await reply.cancel().catch(() => {});\n return;\n }\n\n // A single provider response id ties all of this turn's chunks together for the base class metrics.\n const id = globalThis.crypto.randomUUID();\n const reader = reply.getReader();\n const onAbort = () => void reader.cancel().catch(() => {});\n this.abortController.signal.addEventListener('abort', onAbort, { once: true });\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (this.abortController.signal.aborted) break;\n if (value) this.queue.put({ id, delta: { role: 'assistant', content: value } });\n }\n } catch (error) {\n // Barge-in tears down the reply stream; that surfaces as a read rejection but is not a failure.\n if (this.abortController.signal.aborted) return;\n throw error;\n } finally {\n this.abortController.signal.removeEventListener('abort', onAbort);\n }\n // Return silently on barge-in — throwing would feed the base class's error/retry machinery.\n if (this.abortController.signal.aborted) return;\n // Final usage-only chunk → the base class reads usage from the last chunk that carries it.\n if (usage) this.queue.put({ id, usage: mapUsageToLiveKit(usage) });\n }\n}\n\nexport { MastraLLMStream };\n"]}
@@ -0,0 +1,6 @@
1
+ export { MastraLLM } from './llm-plugin.js';
2
+ export type { MastraLLMOptions } from './llm-plugin.js';
3
+ export { createRemoteAgentReplyGenerator } from './remote.js';
4
+ export type { RemoteMastraAgentOptions, RemoteAgentReplyGeneratorOptions } from './remote.js';
5
+ export type { MastraVoiceAgentMemory, VoiceReplyGenerator, VoiceToolCall, VoiceTurnCompleteContext, VoiceTurnCompleteHook, VoiceTurnContext, VoiceTurnResult, VoiceTurnUsage, } from './bridge.js';
6
+ //# sourceMappingURL=plugin-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-entry.d.ts","sourceRoot":"","sources":["../src/plugin-entry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,+BAA+B,EAAE,MAAM,UAAU,CAAC;AAC3D,YAAY,EAAE,wBAAwB,EAAE,gCAAgC,EAAE,MAAM,UAAU,CAAC;AAC3F,YAAY,EACV,sBAAsB,EACtB,mBAAmB,EACnB,aAAa,EACb,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,UAAU,CAAC"}
@@ -0,0 +1,177 @@
1
+ import { createAgentReplyGenerator, createRemoteAgentReplyGenerator, chatContextToMessages, extractNewTurnMessages } from './chunk-4O7IN74Y.js';
2
+ export { createRemoteAgentReplyGenerator } from './chunk-4O7IN74Y.js';
3
+ import { llm, DEFAULT_API_CONNECT_OPTIONS } from '@livekit/agents';
4
+ import { RequestContext } from '@mastra/core/request-context';
5
+
6
+ function toRequestContext(value) {
7
+ if (!value) return void 0;
8
+ if (value instanceof RequestContext) return value;
9
+ return new RequestContext(Object.entries(value));
10
+ }
11
+ function mapUsageToLiveKit(usage) {
12
+ return {
13
+ completionTokens: usage.completionTokens,
14
+ promptTokens: usage.promptTokens,
15
+ promptCachedTokens: usage.promptCachedTokens,
16
+ totalTokens: usage.totalTokens
17
+ };
18
+ }
19
+ function livekitToolNames(toolCtx) {
20
+ const instance = toolCtx;
21
+ if (typeof instance.flatten === "function") {
22
+ return instance.flatten().map((tool) => {
23
+ const { id, name } = tool;
24
+ return id ?? name ?? "unknown";
25
+ });
26
+ }
27
+ return Object.keys(toolCtx);
28
+ }
29
+ var MastraLLM = class extends llm.LLM {
30
+ #model;
31
+ #memory;
32
+ #requestContext;
33
+ /** Non-remote sources are built once; remote is built per turn so it can pick up `connOptions.timeoutMs`. */
34
+ #staticGenerator;
35
+ #remoteOptions;
36
+ #warnedToolCtx = false;
37
+ constructor(options) {
38
+ super();
39
+ const sources = [
40
+ options.remote ? "remote" : void 0,
41
+ options.agent ? "agent" : void 0,
42
+ options.generate ? "generate" : void 0
43
+ ].filter(Boolean);
44
+ if (sources.length !== 1) {
45
+ throw new Error(
46
+ `@mastra/livekit: MastraLLM requires exactly one reply source \u2014 \`remote\`, \`agent\`, or \`generate\` \u2014 but got ${sources.length === 0 ? "none" : sources.join(" + ")}.`
47
+ );
48
+ }
49
+ this.#memory = options.memory ?? false;
50
+ this.#requestContext = toRequestContext(options.requestContext);
51
+ if (options.remote) {
52
+ this.#model = options.remote.agentId;
53
+ this.#remoteOptions = {
54
+ ...options.remote,
55
+ toolFeedback: options.toolFeedback,
56
+ onToolCall: options.onToolCall,
57
+ onTurnComplete: options.onTurnComplete
58
+ };
59
+ } else if (options.agent) {
60
+ this.#model = options.agent.id ?? options.agent.name;
61
+ this.#staticGenerator = createAgentReplyGenerator({
62
+ agent: options.agent,
63
+ toolFeedback: options.toolFeedback,
64
+ onToolCall: options.onToolCall,
65
+ onTurnComplete: options.onTurnComplete
66
+ });
67
+ } else {
68
+ this.#model = "mastra-generator";
69
+ this.#staticGenerator = options.generate;
70
+ }
71
+ }
72
+ label() {
73
+ return "mastra.MastraLLM";
74
+ }
75
+ get model() {
76
+ return this.#model;
77
+ }
78
+ get provider() {
79
+ return "mastra";
80
+ }
81
+ /**
82
+ * Resolves the reply generator for a turn. Non-remote sources are built once; the remote transport
83
+ * is built per turn so its connect + first-token timeout can come from the session's
84
+ * `connOptions.timeoutMs`, with base-class retries owning retry (transport `retries: 0`).
85
+ */
86
+ resolveGenerator(connOptions) {
87
+ if (this.#staticGenerator) return this.#staticGenerator;
88
+ const remote = this.#remoteOptions;
89
+ return createRemoteAgentReplyGenerator({
90
+ ...remote,
91
+ retries: 0,
92
+ timeoutMs: remote.timeoutMs ?? connOptions.timeoutMs
93
+ });
94
+ }
95
+ chat({
96
+ chatCtx,
97
+ toolCtx,
98
+ connOptions = DEFAULT_API_CONNECT_OPTIONS
99
+ }) {
100
+ if (!this.#warnedToolCtx && toolCtx) {
101
+ const ignored = livekitToolNames(toolCtx);
102
+ if (ignored.length > 0) {
103
+ this.#warnedToolCtx = true;
104
+ console.warn(
105
+ `@mastra/livekit: MastraLLM ignores LiveKit-side tools (${ignored.join(", ")}). Tools are defined and executed server-side on the Mastra agent \u2014 move them there.`
106
+ );
107
+ }
108
+ }
109
+ return new MastraLLMStream(this, {
110
+ chatCtx,
111
+ toolCtx,
112
+ connOptions,
113
+ generator: this.resolveGenerator(connOptions),
114
+ memory: this.#memory,
115
+ requestContext: this.#requestContext
116
+ });
117
+ }
118
+ /** No-op in v1: nothing in the LiveKit session/worker ever calls `prewarm()` automatically. */
119
+ prewarm() {
120
+ }
121
+ };
122
+ var MastraLLMStream = class extends llm.LLMStream {
123
+ #generator;
124
+ #memory;
125
+ #requestContext;
126
+ constructor(mastraLLM, options) {
127
+ super(mastraLLM, { chatCtx: options.chatCtx, toolCtx: options.toolCtx, connOptions: options.connOptions });
128
+ this.#generator = options.generator;
129
+ this.#memory = options.memory;
130
+ this.#requestContext = options.requestContext;
131
+ }
132
+ async run() {
133
+ const messages = this.#memory === false ? chatContextToMessages(this.chatCtx) : extractNewTurnMessages(this.chatCtx);
134
+ if (messages.length === 0) return;
135
+ let usage;
136
+ const turnCtx = {
137
+ messages,
138
+ chatCtx: this.chatCtx,
139
+ memory: this.#memory,
140
+ requestContext: this.#requestContext,
141
+ onUsage: (turnUsage) => {
142
+ usage = turnUsage;
143
+ }
144
+ };
145
+ const reply = await this.#generator(turnCtx);
146
+ if (!reply) return;
147
+ if (this.abortController.signal.aborted) {
148
+ await reply.cancel().catch(() => {
149
+ });
150
+ return;
151
+ }
152
+ const id = globalThis.crypto.randomUUID();
153
+ const reader = reply.getReader();
154
+ const onAbort = () => void reader.cancel().catch(() => {
155
+ });
156
+ this.abortController.signal.addEventListener("abort", onAbort, { once: true });
157
+ try {
158
+ for (; ; ) {
159
+ const { done, value } = await reader.read();
160
+ if (done) break;
161
+ if (this.abortController.signal.aborted) break;
162
+ if (value) this.queue.put({ id, delta: { role: "assistant", content: value } });
163
+ }
164
+ } catch (error) {
165
+ if (this.abortController.signal.aborted) return;
166
+ throw error;
167
+ } finally {
168
+ this.abortController.signal.removeEventListener("abort", onAbort);
169
+ }
170
+ if (this.abortController.signal.aborted) return;
171
+ if (usage) this.queue.put({ id, usage: mapUsageToLiveKit(usage) });
172
+ }
173
+ };
174
+
175
+ export { MastraLLM };
176
+ //# sourceMappingURL=plugin-entry.js.map
177
+ //# sourceMappingURL=plugin-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/llm-plugin.ts"],"names":[],"mappings":"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","file":"plugin-entry.js","sourcesContent":["import { DEFAULT_API_CONNECT_OPTIONS, llm } from '@livekit/agents';\nimport type { APIConnectOptions } from '@livekit/agents';\nimport type { Agent as MastraAgent } from '@mastra/core/agent';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { createAgentReplyGenerator } from './bridge';\nimport type {\n MastraVoiceAgentMemory,\n VoiceReplyGenerator,\n VoiceToolCall,\n VoiceTurnCompleteHook,\n VoiceTurnContext,\n VoiceTurnUsage,\n} from './bridge';\nimport { chatContextToMessages, extractNewTurnMessages } from './messages';\nimport { createRemoteAgentReplyGenerator } from './remote';\nimport type { RemoteMastraAgentOptions } from './remote';\n\nexport type { RemoteMastraAgentOptions } from './remote';\n\n/**\n * Options for {@link MastraLLM}. Provide **exactly one** reply source — `remote` (the headline: a\n * Mastra app on a remote server), `agent` (an in-process Mastra agent), or `generate` (a custom\n * {@link VoiceReplyGenerator}). The `toolFeedback` / `onToolCall` / `onTurnComplete` hooks apply to\n * the `remote` and `agent` sources; a `generate` source owns its own hooks.\n */\nexport interface MastraLLMOptions {\n /** Remote Mastra server. Provide exactly one of `remote`, `agent`, `generate`. */\n remote?: RemoteMastraAgentOptions;\n /** In-process Mastra agent (reuses `createAgentReplyGenerator`). */\n agent?: MastraAgent;\n /** Custom reply source (escape hatch; owns its own tool-feedback / turn-complete behavior). */\n generate?: VoiceReplyGenerator;\n\n /**\n * Conversation persistence, resolved by the customer per call (from SIP/caller identity). When set,\n * only messages new since the agent last spoke are sent each turn and Mastra Memory supplies\n * history. When omitted/false, the full LiveKit chat context is sent every turn.\n *\n * NOTE: incompatible with the session's `preemptiveGeneration` option — a speculative turn that\n * completes before being discarded pollutes the thread. Leave preemptive generation off when\n * using `memory`.\n *\n * The plugin cannot detect the combination at runtime, so this stays a documented constraint\n * rather than a warning: the LLM interface never receives the session (so the option can't be\n * read), a preemptive `chat()` is shape-identical to a real turn (LiveKit drives the same\n * `generateReply` with a draft transcript and no marker), and the observable signature — a\n * cancelled stream followed by a `chat()` whose trailing user message changed — is exactly what\n * an ordinary barge-in correction looks like, so a heuristic would warn on every barge-in.\n */\n memory?: MastraVoiceAgentMemory | false;\n /** Request context forwarded to generation (tenant, dialed number, ...). */\n requestContext?: RequestContext | Record<string, unknown>;\n\n /** Speak a short filler while a (server-side) tool runs. Applies to the `remote`/`agent` sources. */\n toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;\n /** Notified as each tool-call chunk arrives, mid-stream. Applies to the `remote`/`agent` sources. */\n onToolCall?: (toolCall: VoiceToolCall) => void;\n /** Fired off the audio path after each reply finishes. Applies to the `remote`/`agent` sources. */\n onTurnComplete?: VoiceTurnCompleteHook;\n}\n\nfunction toRequestContext(value: RequestContext | Record<string, unknown> | undefined): RequestContext | undefined {\n if (!value) return undefined;\n if (value instanceof RequestContext) return value;\n return new RequestContext<unknown>(Object.entries(value));\n}\n\nfunction mapUsageToLiveKit(usage: VoiceTurnUsage): llm.CompletionUsage {\n return {\n completionTokens: usage.completionTokens,\n promptTokens: usage.promptTokens,\n promptCachedTokens: usage.promptCachedTokens,\n totalTokens: usage.totalTokens,\n };\n}\n\n/**\n * Tool names carried by a `toolCtx`, across @livekit/agents versions: 1.5+ always passes a\n * `ToolContext` class instance (tools behind getters, `Object.keys` sees only private fields),\n * while 1.4 and the object shorthand pass a plain name→tool map.\n */\nfunction livekitToolNames(toolCtx: object): string[] {\n const instance = toolCtx as { flatten?: unknown };\n if (typeof instance.flatten === 'function') {\n return (instance.flatten as () => object[])().map(tool => {\n const { id, name } = tool as { id?: string; name?: string };\n return id ?? name ?? 'unknown';\n });\n }\n return Object.keys(toolCtx);\n}\n\n/**\n * A standard LiveKit LLM plugin (`llm.LLM`) backed by a Mastra agent. Drop it into the `llm` slot of a\n * customer-owned `voice.AgentSession` and the Mastra app (agent loop, tools, memory, observability)\n * runs wherever it's deployed — most importantly on a **remote** Mastra server reached over HTTP.\n *\n * Tools are defined and executed **server-side** on the Mastra agent; LiveKit-side `toolCtx` is\n * ignored (with a one-time warning). Tool activity surfaces via `toolFeedback` (spoken) and\n * `onToolCall` / `onTurnComplete` (programmatic). `voice.Agent` instructions do **not** reach the\n * Mastra agent — put instructions on the Mastra agent instead.\n *\n * @example\n * ```ts\n * const session = new voice.AgentSession({\n * stt: 'deepgram/nova-3',\n * tts: 'cartesia/sonic-3',\n * llm: new MastraLLM({\n * remote: { baseUrl: process.env.MASTRA_URL!, agentId: 'callCenter' },\n * memory: { thread: callId, resource: callerId },\n * }),\n * });\n * ```\n */\nexport class MastraLLM extends llm.LLM {\n readonly #model: string;\n readonly #memory: MastraVoiceAgentMemory | false;\n readonly #requestContext?: RequestContext;\n /** Non-remote sources are built once; remote is built per turn so it can pick up `connOptions.timeoutMs`. */\n readonly #staticGenerator?: VoiceReplyGenerator;\n readonly #remoteOptions?: RemoteMastraAgentOptions & {\n toolFeedback?: MastraLLMOptions['toolFeedback'];\n onToolCall?: MastraLLMOptions['onToolCall'];\n onTurnComplete?: VoiceTurnCompleteHook;\n };\n #warnedToolCtx = false;\n\n constructor(options: MastraLLMOptions) {\n super();\n const sources = [\n options.remote ? 'remote' : undefined,\n options.agent ? 'agent' : undefined,\n options.generate ? 'generate' : undefined,\n ].filter(Boolean) as string[];\n if (sources.length !== 1) {\n throw new Error(\n `@mastra/livekit: MastraLLM requires exactly one reply source — \\`remote\\`, \\`agent\\`, or \\`generate\\` — ` +\n `but got ${sources.length === 0 ? 'none' : sources.join(' + ')}.`,\n );\n }\n\n this.#memory = options.memory ?? false;\n this.#requestContext = toRequestContext(options.requestContext);\n\n if (options.remote) {\n this.#model = options.remote.agentId;\n this.#remoteOptions = {\n ...options.remote,\n toolFeedback: options.toolFeedback,\n onToolCall: options.onToolCall,\n onTurnComplete: options.onTurnComplete,\n };\n } else if (options.agent) {\n this.#model = options.agent.id ?? options.agent.name;\n this.#staticGenerator = createAgentReplyGenerator({\n agent: options.agent,\n toolFeedback: options.toolFeedback,\n onToolCall: options.onToolCall,\n onTurnComplete: options.onTurnComplete,\n });\n } else {\n this.#model = 'mastra-generator';\n this.#staticGenerator = options.generate;\n }\n }\n\n label(): string {\n return 'mastra.MastraLLM';\n }\n\n override get model(): string {\n return this.#model;\n }\n\n override get provider(): string {\n return 'mastra';\n }\n\n /**\n * Resolves the reply generator for a turn. Non-remote sources are built once; the remote transport\n * is built per turn so its connect + first-token timeout can come from the session's\n * `connOptions.timeoutMs`, with base-class retries owning retry (transport `retries: 0`).\n */\n private resolveGenerator(connOptions: APIConnectOptions): VoiceReplyGenerator {\n if (this.#staticGenerator) return this.#staticGenerator;\n const remote = this.#remoteOptions!;\n return createRemoteAgentReplyGenerator({\n ...remote,\n retries: 0,\n timeoutMs: remote.timeoutMs ?? connOptions.timeoutMs,\n });\n }\n\n override chat({\n chatCtx,\n toolCtx,\n connOptions = DEFAULT_API_CONNECT_OPTIONS,\n }: {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions?: APIConnectOptions;\n parallelToolCalls?: boolean;\n toolChoice?: llm.ToolChoice;\n extraKwargs?: Record<string, unknown>;\n }): llm.LLMStream {\n // Tools run server-side; warn once if the customer wired LiveKit-side tools.\n if (!this.#warnedToolCtx && toolCtx) {\n const ignored = livekitToolNames(toolCtx);\n if (ignored.length > 0) {\n this.#warnedToolCtx = true;\n console.warn(\n `@mastra/livekit: MastraLLM ignores LiveKit-side tools (${ignored.join(', ')}). ` +\n `Tools are defined and executed server-side on the Mastra agent — move them there.`,\n );\n }\n }\n return new MastraLLMStream(this, {\n chatCtx,\n toolCtx,\n connOptions,\n generator: this.resolveGenerator(connOptions),\n memory: this.#memory,\n requestContext: this.#requestContext,\n });\n }\n\n /** No-op in v1: nothing in the LiveKit session/worker ever calls `prewarm()` automatically. */\n override prewarm(): void {}\n}\n\ninterface MastraLLMStreamOptions {\n chatCtx: llm.ChatContext;\n toolCtx?: llm.ToolContext;\n connOptions: APIConnectOptions;\n generator: VoiceReplyGenerator;\n memory: MastraVoiceAgentMemory | false;\n requestContext?: RequestContext;\n}\n\n/**\n * The `llm.LLMStream` `MastraLLM` returns per turn. `run()` extracts the turn's messages, drives\n * the reply generator, and pushes assistant `ChatChunk`s into `this.queue` (NOT `this.output` — the\n * base class drains queue → output and computes TTFT / duration / usage). Barge-in aborts via\n * `this.abortController` and `run()` returns silently.\n */\nclass MastraLLMStream extends llm.LLMStream {\n readonly #generator: VoiceReplyGenerator;\n readonly #memory: MastraVoiceAgentMemory | false;\n readonly #requestContext?: RequestContext;\n\n constructor(mastraLLM: MastraLLM, options: MastraLLMStreamOptions) {\n super(mastraLLM, { chatCtx: options.chatCtx, toolCtx: options.toolCtx, connOptions: options.connOptions });\n this.#generator = options.generator;\n this.#memory = options.memory;\n this.#requestContext = options.requestContext;\n }\n\n protected async run(): Promise<void> {\n const messages =\n this.#memory === false ? chatContextToMessages(this.chatCtx) : extractNewTurnMessages(this.chatCtx);\n // No new input to answer → close without a request (equivalent to the wrapper returning null).\n if (messages.length === 0) return;\n\n let usage: VoiceTurnUsage | undefined;\n const turnCtx: VoiceTurnContext = {\n messages,\n chatCtx: this.chatCtx,\n memory: this.#memory,\n requestContext: this.#requestContext,\n onUsage: turnUsage => {\n usage = turnUsage;\n },\n };\n\n const reply = await this.#generator(turnCtx);\n if (!reply) return;\n if (this.abortController.signal.aborted) {\n await reply.cancel().catch(() => {});\n return;\n }\n\n // A single provider response id ties all of this turn's chunks together for the base class metrics.\n const id = globalThis.crypto.randomUUID();\n const reader = reply.getReader();\n const onAbort = () => void reader.cancel().catch(() => {});\n this.abortController.signal.addEventListener('abort', onAbort, { once: true });\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (this.abortController.signal.aborted) break;\n if (value) this.queue.put({ id, delta: { role: 'assistant', content: value } });\n }\n } catch (error) {\n // Barge-in tears down the reply stream; that surfaces as a read rejection but is not a failure.\n if (this.abortController.signal.aborted) return;\n throw error;\n } finally {\n this.abortController.signal.removeEventListener('abort', onAbort);\n }\n // Return silently on barge-in — throwing would feed the base class's error/retry machinery.\n if (this.abortController.signal.aborted) return;\n // Final usage-only chunk → the base class reads usage from the last chunk that carries it.\n if (usage) this.queue.put({ id, usage: mapUsageToLiveKit(usage) });\n }\n}\n\nexport { MastraLLMStream };\n"]}