langgenius/dify · error · ValidationError

text or message_id is required

Error message

text or message_id is required

What it means

Thrown by the object-form overload of textToAudio() in base.ts:252 as a ValidationError. The endpoint needs a source of speech: either free-form text or a previously generated message_id to re-render. When the request object carries neither a non-empty text nor a message_id, the SDK aborts before calling /text-to-audio.

Source

Thrown at sdks/nodejs-client/src/client/base.ts:252

      payload = {
        text: textOrRequest,
        user,
        streaming,
      }
      if (voice) {
        payload.voice = voice
      }
    } else {
      payload = { ...textOrRequest }
      ensureNonEmptyString(payload.user, 'user')
      if (payload.text !== undefined && payload.text !== null) {
        ensureNonEmptyString(payload.text, 'text')
      }
      if (payload.message_id !== undefined && payload.message_id !== null) {
        ensureNonEmptyString(payload.message_id, 'messageId')
      }
      if (!payload.text && !payload.message_id) {
        throw new ValidationError('text or message_id is required')
      }
      payload.streaming = payload.streaming ?? false
    }

    if (payload.streaming) {
      return this.http.requestBinaryStream({
        method: 'POST',
        path: '/text-to-audio',
        data: payload,
      })
    }

    return this.http.request<Buffer, 'bytes'>({
      method: 'POST',
      path: '/text-to-audio',
      data: payload,
      responseType: 'bytes',
    })

View on GitHub (pinned to ef8544b173)

Solutions

  1. Provide exactly one of text or message_id in the request object: client.textToAudio({ text: 'Hello world', user }).
  2. If re-rendering prior TTS output, pass the message_id from a prior message response: client.textToAudio({ message_id: msg.id, user }).
  3. When constructing the payload programmatically, assert at least one field is populated before calling: if (!req.text && !req.message_id) throw ... in your own layer.
  4. If you intended the positional string form, call client.textToAudio('Hello', user) instead of wrapping text in an object without a key.

Example fix

// before
await client.textToAudio({ user, voice: 'echo' })

// after
await client.textToAudio({ user, voice: 'echo', text: 'Hello world' })
Defensive patterns

Strategy: validation

Validate before calling

function assertTextToAudioRequest(req: { text?: string; message_id?: string; user: string }) {
  if (!req.text?.trim() && !req.message_id?.trim()) {
    throw new Error('textToAudio requires text or message_id')
  }
}

Type guard

function hasTextSource(req: unknown): req is { text: string } | { message_id: string } {
  if (typeof req !== 'object' || req === null) return false
  const r = req as { text?: unknown; message_id?: unknown }
  return (typeof r.text === 'string' && r.text.trim().length > 0)
    || (typeof r.message_id === 'string' && r.message_id.trim().length > 0)
}

Try / catch

try {
  await client.textToAudio(payload)
} catch (err) {
  if (err instanceof Error && err.name === 'ValidationError' && /text or message_id/.test(err.message)) {
    // prompt user to provide one of the two
  } else throw err
}

Prevention

When it happens

Trigger: Calling client.textToAudio({ user }) or client.textToAudio({ text: '', user }) — i.e. the object form where both text and message_id are missing, undefined, null, or empty strings. The check at base.ts:251 (`!payload.text && !payload.message_id`) fires after the per-field optional validators.

Common situations: Building the request payload dynamically and the field that should carry text/message_id was renamed or stripped; copy-pasting a chat request shape expecting message_id to be auto-populated; passing whitespace-only text; treating textToAudio's first arg as positional string while actually passing an object.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/072f2c8b180185db. Report an issue: GitHub.