convertToUIMessages
The ModelMessage to UIMessage direction the AI SDK still does not ship.
The AI SDK converts UIMessage[] to ModelMessage[]. As of ai 7 it does not ship the reverse, which is what you need when the messages you have came from a provider, a log, or an older table (vercel/ai#7180, open at the time of writing - if the SDK ships it, prefer theirs).
This is a pure function. It needs no store and no database - the playground has a panel that runs it on whatever you paste in.
import { convertToUIMessages } from "ai-sdk-threads";
const uiMessages = convertToUIMessages([
{ role: "user", content: "weather?" },
{
role: "assistant",
content: [
{ type: "tool-call", toolCallId: "c1", toolName: "getWeather", input: { city: "x" } },
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "c1",
toolName: "getWeather",
output: { type: "json", value: { temp: 21 } },
},
],
},
]);
// -> 2 messages. The tool result is folded into the assistant message as a
// `tool-getWeather` part with state 'output-available'.The mapping
textandreasoningcontent becomestextandreasoningparts.- A
tool-callbecomes atool-<name>part,input-availableuntil its result arrives, thenoutput-available- oroutput-errorfor a failure. toolrole messages are folded into the assistant message that called them, never emitted on their own.- Ids come from
options.generateId, defaulting to the SDK'sgenerateId.
What it refuses
Content this version does not model - file, image, and provider-specific parts - throws rather than being guessed at, so a lossy conversion cannot reach your database.
That is deliberate. A converter that silently dropped an image part would leave you with a row that renders differently from the conversation it came from, and no way to know which rows were affected.