How are messages and parts stored in opencode?
how do they store messages and parts? separately? is message -> UIMessage from the ai sdk?
Now I have a clear picture. Let me summarize what I found:
##Summary: Messages and Parts Storage
Yes, messages and parts are stored separately.
###Storage Structure
The storage uses a file-based system with paths like:
- Messages:
["message", sessionID, messageID]→ stored asstorage/message/{sessionID}/{messageID}.json - Parts:
["part", messageID, partID]→ stored asstorage/part/{messageID}/{partID}.json
###Key Types
From packages/opencode/src/session/message-v2.ts:
-
MessageV2.Info- The message metadata (discriminated union ofUserandAssistant):Usermessage: role, time, agent, model, tools, etc.Assistantmessage: role, time, tokens, cost, error, modelID, providerID, etc.
-
MessageV2.Part- A discriminated union of many part types:TextPart,ReasoningPart,FilePart,ToolPart,StepStartPart,StepFinishPart,SnapshotPart,PatchPart,AgentPart,RetryPart,CompactionPart,SubtaskPart
-
MessageV2.WithParts- Combines both:export const WithParts = z.object({ info: Info, parts: z.array(Part), })
###Relation to AI SDK's UIMessage
Yes, MessageV2.Info is converted to UIMessage from the AI SDK, but it's not a direct mapping.
In toModelMessages() (line 436), they:
- Take an array of
WithParts[] - Build
UIMessage[]objects (fromimport { type UIMessage } from "ai") - Transform their custom part types into the AI SDK's expected format
- Call
convertToModelMessages()from the AI SDK
So their internal Message → Info structure is their own schema, which gets converted to UIMessage when interfacing with the AI SDK for model calls.
###Storage Operations
From packages/opencode/src/session/index.ts:
// Write a message
await Storage.write(["message", msg.sessionID, msg.id], msg)
// Write a part
await Storage.write(["part", part.messageID, part.id], part)
// Remove a part
await Storage.remove(["part", input.messageID, input.partID])
Parts are retrieved by listing all files in the part directory for a message:
export const parts = fn(Identifier.schema("message"), async (messageID) => {
const result = [] as MessageV2.Part[]
for (const item of await Storage.list(["part", messageID])) {
const read = await Storage.read<MessageV2.Part>(item)
result.push(read)
}
result.sort((a, b) => (a.id > b.id ? 1 : -1))
return result
})
END OF POST