Bug
In UIMessages.js, createUserUIMessage doesn't spread partCommon (which carries providerMetadata) onto parts when the message has simple string content.
Line 211 (string content path):
parts: [{ type: "text", text: content }] // partCommon NOT spread
Line 216 (array content path):
parts: content.map((part) => ({ ...partCommon, ...part })) // partCommon IS spread ✓
Compare with createSystemUIMessage and createAssistantUIMessage which both correctly spread partCommon on all paths.
Impact
When using saveMessage with prompt (string) + metadata.providerMetadata, the providerMetadata is stored in the DB but lost when constructing UIMessage parts. Consumers reading parts[].providerMetadata get undefined.
Reproduction
const { messageId } = await saveMessage(ctx, components.agent, {
threadId,
prompt: "Hello", // string → hits line 211
metadata: {
providerMetadata: {
myApp: { customField: "value" },
},
},
});
// Later, in useUIMessages:
// message.parts[0].providerMetadata → undefined (expected: { myApp: { customField: "value" } })
Workaround
Use message with array content instead of prompt:
const { messageId } = await saveMessage(ctx, components.agent, {
threadId,
message: {
role: "user",
content: [{ type: "text", text: "Hello" }], // array → hits line 216
},
metadata: {
providerMetadata: {
myApp: { customField: "value" },
},
},
});
Expected Fix
Spread partCommon on the string content path (line 211):
parts: [{ ...partCommon, type: "text", text: content }]
Version
@convex-dev/agent@0.3.2