Budibase/budibase · error · Error

AI system message must be a string

Error message

AI system message must be a string

What it means

generateJs builds an AI request via ai.generateJs and requires the resulting message list to contain a system message whose content is a plain string. If no system-role message exists or its content is not a string (e.g. array of content parts), this Error is thrown before any LLM call is made.

Source

Thrown at packages/server/src/api/controllers/ai/js.ts:22

import { ai } from "@budibase/pro"
import sdk from "../../../sdk"

const MARKDOWN_CODE_BLOCK = /```(?:\w+)?\n([\s\S]+?)\n```/

export async function generateJs(
  ctx: UserCtx<GenerateJsRequest, GenerateJsResponse>
) {
  await context.ensureSnippetContext()
  const currentContext = context.getCurrentContext()
  const snippets = currentContext?.snippets || []
  const { prompt, bindings = [] } = ctx.request.body

  const request = ai.generateJs(bindings, snippets)
  const systemMessage = request.messages.find(
    message => message.role === "system"
  )?.content
  if (typeof systemMessage !== "string") {
    throw new Error("AI system message must be a string")
  }
  const { chat, providerOptions } = await sdk.ai.llm.getDefaultLLMOrThrow()
  const result = await generateText({
    model: chat,
    instructions: systemMessage,
    prompt,
    providerOptions: providerOptions?.(false),
  })

  let code = result.text || ""
  const match = code.match(MARKDOWN_CODE_BLOCK)
  if (match) {
    code = match[1]
  }
  ctx.body = { code }
}

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Update the ai.generateJs helper (or stub) to return a system message with string content.
  2. If the content is an array of parts, join the text parts before passing: map(part => part.text).join('') or use the SDK's convertToCoreMessages-style helper.
  3. Pin/align the AI SDK version so generateJs output matches the expected message shape.

Example fix

// before
const systemMessage = request.messages.find(m => m.role === 'system')?.content
// after
const rawContent = request.messages.find(m => m.role === 'system')?.content
const systemMessage = typeof rawContent === 'string' ? rawContent : Array.isArray(rawContent) ? rawContent.map(p => p.text).join('') : undefined
Defensive patterns

Strategy: type-guard

Validate before calling

const msgs = ai.generateJs(bindings, snippets).messages
const sys = msgs.find(m => m.role === 'system')
if (!sys || typeof sys.content !== 'string') throw new Error('generateJs must return a string system message')

Type guard

function hasStringSystemMessage(messages: { role: string; content: unknown }[]): messages is { role: 'system'; content: string }[] {
  const sys = messages.find(m => m.role === 'system')
  return typeof sys?.content === 'string'
}

Try / catch

try {
  const result = await generateJs(bindings, snippets, prompt)
} catch (e) {
  if (e.message === 'AI system message must be a string') {
    // fall back to a locally-constructed system prompt string
  } else throw e
}

Prevention

When it happens

Trigger: Calling generateJs where ai.generateJs returns messages with no role==='system' entry, or where the system message content is an array/prompt-parts object rather than a string (provider/SDK version drift).

Common situations: Upgrading the AI SDK changed system message serialization to content-part arrays; a custom snippets/bindings path suppresses the system message; tests stub ai.generateJs with incomplete messages.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/4fa4f9487f1be697. Report an issue: GitHub.