Render every message part

Map over message.parts, not just the text.

AI SDK docsai-sdk.dev
Tool parts are always named tool-{toolName}, where {toolName} is the key you used when defining the tool.
Getting started · Next.js App RouterRead docs ↗

From ai-sdk and the Chatbot Tool Usage docs.

One message, two renderers

Press Ask. The model calls a weather tool first.

Text parts onlyBad
0 msIdle
Parts shown –Blank for –
parts.filter(p => p.type === 'text')
Switch on part.typeGood
0 msIdle
Parts shown –Blank for –
case 'tool-weather': switch (part.state)
message.parts
{message.parts.map((part, i) => {
  switch (part.type) {
    case 'text':
      return <p key={i}>{part.text}</p>;
    case 'tool-weather':
      switch (part.state) {
        case 'input-streaming':
          return <p key={i}>Preparing request…</p>;
        case 'input-available':
          return <p key={i}>Getting weather for {part.input.location}…</p>;
        case 'output-available':
          return <WeatherCard key={i} {...part.output} />;
        case 'output-error':
          return <p key={i}>Error: {part.errorText}</p>;
      }
  }
})}

Why it matters, pros and cons

Why it matters

A message is an ordered list of parts. Render only text and every tool call is a blank, silent wait.

Pros

  • The reader sees what the model is doing.
  • Each tool state gets its own UI.
  • Parts keep the order the model produced them in.

Cons

  • More cases to write per tool.
  • Approval states count too when you switch exhaustively.
  • Streaming input can be partial; guard missing fields.