Render every message part
Map over message.parts, not just the text.
AI SDK docsai-sdk.dev
Tool parts are always namedtool-{toolName}, where{toolName}is the key you used when defining the tool.
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.