mastra-ai/mastra · error

@mastra/livekit: MastraLLM requires exactly one reply source

Error message

@mastra/livekit: MastraLLM requires exactly one reply source — `remote`, `agent`, or `generate` — but got ${sources.length === 0 ? 'none' : sources.join(' + ')}.

What it means

MastraLLM (LiveKit LLM plugin) must have exactly one way to produce replies: streaming from a remote Mastra agent (`remote`), using a local Mastra agent (`agent`), or a custom generate function (`generate`). The constructor counts which of the three options were provided and throws when the count is not exactly 1 — either none or more than one was supplied.

Source

Thrown at integrations/livekit/src/llm-plugin.ts:136

  readonly #requestContext?: RequestContext;
  /** Non-remote sources are built once; remote is built per turn so it can pick up `connOptions.timeoutMs`. */
  readonly #staticGenerator?: VoiceReplyGenerator;
  readonly #remoteOptions?: RemoteMastraAgentOptions & {
    toolFeedback?: MastraLLMOptions['toolFeedback'];
    onToolCall?: MastraLLMOptions['onToolCall'];
    onTurnComplete?: VoiceTurnCompleteHook;
  };
  #warnedToolCtx = false;

  constructor(options: MastraLLMOptions) {
    super();
    const sources = [
      options.remote ? 'remote' : undefined,
      options.agent ? 'agent' : undefined,
      options.generate ? 'generate' : undefined,
    ].filter(Boolean) as string[];
    if (sources.length !== 1) {
      throw new Error(
        `@mastra/livekit: MastraLLM requires exactly one reply source — \`remote\`, \`agent\`, or \`generate\` — ` +
          `but got ${sources.length === 0 ? 'none' : sources.join(' + ')}.`,
      );
    }

    this.#memory = options.memory ?? false;
    this.#requestContext = toRequestContext(options.requestContext);

    if (options.remote) {
      this.#model = options.remote.agentId;
      this.#remoteOptions = {
        ...options.remote,
        toolFeedback: options.toolFeedback,
        onToolCall: options.onToolCall,
        onTurnComplete: options.onTurnComplete,
      };
    } else if (options.agent) {
      this.#model = options.agent.id ?? options.agent.name;

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Pick the single intended reply source and keep only its option (remote, agent, or generate) in the constructor options.
  2. If you meant to stream from a remote Mastra agent, keep only `remote` and remove `agent`/`generate`.
  3. If you see 'got none', pass one of the three options instead of an empty object.

Example fix

// before
const llm = new MastraLLM({ remote: { agentId: 'support' }, agent: mastra.getAgent('support') });
// after
const llm = new MastraLLM({ remote: { agentId: 'support' } });
Defensive patterns

Strategy: validation

Validate before calling

function validateReplySource(options) {
  const provided = ['remote', 'agent', 'generate'].filter(k => options[k] != null);
  if (provided.length !== 1) throw new Error(`MastraLLM needs exactly one reply source, got: ${provided.join('+') || 'none'}`);
}
validateReplySource(myOptions); // before new MastraLLM(myOptions)

Try / catch

let llm;
try {
  llm = new MastraLLM(options);
} catch (e) {
  if (e instanceof Error && e.message.includes('exactly one reply source')) {
    console.error('Bad MastraLLM options keys:', Object.keys(options));
  }
  throw e;
}

Prevention

When it happens

Trigger: new MastraLLM({}) with no reply source; or passing two/three of remote, agent, generate together (e.g. remote + generate).

Common situations: Copy-pasting example code that keeps both `agent` and `remote` options; refactoring from a local agent to remote streaming and forgetting to remove the old option; instantiating the plugin with an empty options object.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/498463a4875e5e9b. Report an issue: GitHub.