{"version":3,"file":"plugin-entry.cjs","names":["RequestContext","llm","createAgentReplyGenerator","createRemoteAgentReplyGenerator","DEFAULT_API_CONNECT_OPTIONS","chatContextToMessages","extractNewTurnMessages","instrumentVoiceReply","VOICE_TURN_METADATA","VOICE_FLUSH_METADATA"],"sources":["../src/llm-plugin.ts"],"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, instrumentVoiceReply } 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';\nimport type { VoiceTurnMetricsHook } from './turn-metrics';\nimport { VOICE_FLUSH_METADATA, VOICE_TURN_METADATA } from './turn-metrics';\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  /** Per-attempt metrics; use observeVoiceSession for playback metrics. */\n  onTurnMetrics?: VoiceTurnMetricsHook;\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\n   * persists a partial user message and a partial reply before being discarded. Leave preemptive\n   * generation off when using `memory`, or set `memory.options.readOnly` and persist committed\n   * turns yourself from the session's conversation-item events.\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 #onTurnMetrics?: VoiceTurnMetricsHook;\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    this.#onTurnMetrics = options.onTurnMetrics;\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      onTurnMetrics: this.#onTurnMetrics,\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  onTurnMetrics?: VoiceTurnMetricsHook;\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 #onTurnMetrics?: VoiceTurnMetricsHook;\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.#onTurnMetrics = options.onTurnMetrics;\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, identity } = await instrumentVoiceReply(this.#generator, turnCtx, this.#onTurnMetrics);\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 = identity.attemptId;\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)\n          this.queue.put({\n            id,\n            delta: {\n              role: 'assistant',\n              ...(typeof value === 'string' ? { content: value } : {}),\n              extra: { [VOICE_TURN_METADATA]: identity, [VOICE_FLUSH_METADATA]: typeof value !== 'string' },\n            },\n          });\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"],"mappings":";;;;;;AAkEA,SAAS,iBAAiB,OAAyF;CACjH,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,IAAI,iBAAiBA,6BAAAA,gBAAgB,OAAO;CAC5C,OAAO,IAAIA,6BAAAA,eAAwB,OAAO,QAAQ,KAAK,CAAC;AAC1D;AAEA,SAAS,kBAAkB,OAA4C;CACrE,OAAO;EACL,kBAAkB,MAAM;EACxB,cAAc,MAAM;EACpB,oBAAoB,MAAM;EAC1B,aAAa,MAAM;CACrB;AACF;;;;;;AAOA,SAAS,iBAAiB,SAA2B;CACnD,MAAM,WAAW;CACjB,IAAI,OAAO,SAAS,YAAY,YAC9B,OAAQ,SAAS,QAA2B,CAAC,CAAC,KAAI,SAAQ;EACxD,MAAM,EAAE,IAAI,SAAS;EACrB,OAAO,MAAM,QAAQ;CACvB,CAAC;CAEH,OAAO,OAAO,KAAK,OAAO;AAC5B;;;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,YAAb,cAA+BC,gBAAAA,IAAI,IAAI;CACrC;CACA;CACA;CACA;;CAEA;CACA;CAKA,iBAAiB;CAEjB,YAAY,SAA2B;EACrC,MAAM;EACN,KAAK,iBAAiB,QAAQ;EAC9B,MAAM,UAAU;GACd,QAAQ,SAAS,WAAW,KAAA;GAC5B,QAAQ,QAAQ,UAAU,KAAA;GAC1B,QAAQ,WAAW,aAAa,KAAA;EAClC,CAAC,CAAC,OAAO,OAAO;EAChB,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,MACR,mHACa,QAAQ,WAAW,IAAI,SAAS,QAAQ,KAAK,KAAK,EAAE,EACnE;EAGF,KAAK,UAAU,QAAQ,UAAU;EACjC,KAAK,kBAAkB,iBAAiB,QAAQ,cAAc;EAE9D,IAAI,QAAQ,QAAQ;GAClB,KAAK,SAAS,QAAQ,OAAO;GAC7B,KAAK,iBAAiB;IACpB,GAAG,QAAQ;IACX,cAAc,QAAQ;IACtB,YAAY,QAAQ;IACpB,gBAAgB,QAAQ;GAC1B;EACF,OAAO,IAAI,QAAQ,OAAO;GACxB,KAAK,SAAS,QAAQ,MAAM,MAAM,QAAQ,MAAM;GAChD,KAAK,mBAAmBC,eAAAA,0BAA0B;IAChD,OAAO,QAAQ;IACf,cAAc,QAAQ;IACtB,YAAY,QAAQ;IACpB,gBAAgB,QAAQ;GAC1B,CAAC;EACH,OAAO;GACL,KAAK,SAAS;GACd,KAAK,mBAAmB,QAAQ;EAClC;CACF;CAEA,QAAgB;EACd,OAAO;CACT;CAEA,IAAa,QAAgB;EAC3B,OAAO,KAAK;CACd;CAEA,IAAa,WAAmB;EAC9B,OAAO;CACT;;;;;;CAOA,iBAAyB,aAAqD;EAC5E,IAAI,KAAK,kBAAkB,OAAO,KAAK;EACvC,MAAM,SAAS,KAAK;EACpB,OAAOC,eAAAA,gCAAgC;GACrC,GAAG;GACH,SAAS;GACT,WAAW,OAAO,aAAa,YAAY;EAC7C,CAAC;CACH;CAEA,KAAc,EACZ,SACA,SACA,cAAcC,gBAAAA,+BAQE;EAEhB,IAAI,CAAC,KAAK,kBAAkB,SAAS;GACnC,MAAM,UAAU,iBAAiB,OAAO;GACxC,IAAI,QAAQ,SAAS,GAAG;IACtB,KAAK,iBAAiB;IACtB,QAAQ,KACN,0DAA0D,QAAQ,KAAK,IAAI,EAAE,qFAE/E;GACF;EACF;EACA,OAAO,IAAI,gBAAgB,MAAM;GAC/B;GACA;GACA;GACA,WAAW,KAAK,iBAAiB,WAAW;GAC5C,eAAe,KAAK;GACpB,QAAQ,KAAK;GACb,gBAAgB,KAAK;EACvB,CAAC;CACH;;CAGA,UAAyB,CAAC;AAC5B;;;;;;;AAkBA,IAAM,kBAAN,cAA8BH,gBAAAA,IAAI,UAAU;CAC1C;CACA;CACA;CACA;CAEA,YAAY,WAAsB,SAAiC;EACjE,MAAM,WAAW;GAAE,SAAS,QAAQ;GAAS,SAAS,QAAQ;GAAS,aAAa,QAAQ;EAAY,CAAC;EACzG,KAAK,aAAa,QAAQ;EAC1B,KAAK,iBAAiB,QAAQ;EAC9B,KAAK,UAAU,QAAQ;EACvB,KAAK,kBAAkB,QAAQ;CACjC;CAEA,MAAgB,MAAqB;EACnC,MAAM,WACJ,KAAK,YAAY,QAAQI,eAAAA,sBAAsB,KAAK,OAAO,IAAIC,eAAAA,uBAAuB,KAAK,OAAO;EAEpG,IAAI,SAAS,WAAW,GAAG;EAE3B,IAAI;EACJ,MAAM,UAA4B;GAChC;GACA,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,gBAAgB,KAAK;GACrB,UAAS,cAAa;IACpB,QAAQ;GACV;EACF;EAEA,MAAM,EAAE,OAAO,aAAa,MAAMC,eAAAA,qBAAqB,KAAK,YAAY,SAAS,KAAK,cAAc;EACpG,IAAI,CAAC,OAAO;EACZ,IAAI,KAAK,gBAAgB,OAAO,SAAS;GACvC,MAAM,MAAM,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;GACnC;EACF;EAGA,MAAM,KAAK,SAAS;EACpB,MAAM,SAAS,MAAM,UAAU;EAC/B,MAAM,gBAAgB,KAAK,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;EACzD,KAAK,gBAAgB,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EAC7E,IAAI;GACF,SAAS;IACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;IAC1C,IAAI,MAAM;IACV,IAAI,KAAK,gBAAgB,OAAO,SAAS;IACzC,IAAI,OACF,KAAK,MAAM,IAAI;KACb;KACA,OAAO;MACL,MAAM;MACN,GAAI,OAAO,UAAU,WAAW,EAAE,SAAS,MAAM,IAAI,CAAC;MACtD,OAAO;QAAGC,qBAAAA,sBAAsB;QAAWC,qBAAAA,uBAAuB,OAAO,UAAU;MAAS;KAC9F;IACF,CAAC;GACL;EACF,SAAS,OAAO;GAEd,IAAI,KAAK,gBAAgB,OAAO,SAAS;GACzC,MAAM;EACR,UAAU;GACR,KAAK,gBAAgB,OAAO,oBAAoB,SAAS,OAAO;EAClE;EAEA,IAAI,KAAK,gBAAgB,OAAO,SAAS;EAEzC,IAAI,OAAO,KAAK,MAAM,IAAI;GAAE;GAAI,OAAO,kBAAkB,KAAK;EAAE,CAAC;CACnE;AACF"}