Okay, let's run the solution and see what happens. Weirdly enough, my Google Gemini account seems to be running really slow this morning. And so it appears to be just taking a while, which is kind of helping because it shows here the duration of 9.4 seconds.
Duration: 9.4s
9.4 seconds for a single paragraph is pretty brutal. So let's look at the code and see what it's doing.
We are declaring a MyUIMessage type by passing a duration object to UIMessage.
So step one, done. Then after the stream text has begun, we initialise a start time.
And then inside messageMetadata, we really have a very, very simple function. We wait until we receive the chunk of finish and then when we do we return an object where we're calculating the duration.
Inside our root.tsx file we are passing in the metadata now into the Message which itself is taking in a prop of metadata which is MyUIMessage['metadata'].
If we zoom all the way down to where it's used, we then, if it has a metadata.duration attached, we then grab the duration.
So message metadata is really convenient if you just have a couple of attributes you want to attach to your message. It's worth noting that if you want to persist these, then they are extra attributes that you need to add into your database but it's a nice tool in the toolbox for creating these kind of UIs.
Nice work and I will see you in the next one.
export type MyUIMessage = UIMessage<{duration: number}>
const result = streamText({model: google("gemini-2.5-flash"),messages: convertToModelMessages(messages),})const startTime = Date.now()
return result.toUIMessageStreamResponse<MyUIMessage>({messageMetadata({ part }) {if (part.type === "finish") {return {duration: Date.now() - startTime,}}return undefined},})
const App = () => {const { messages, sendMessage } = useChat<MyUIMessage>({});// ...return (<Wrapper>{messages.map((message) => (<Messagekey={message.id}role={message.role}parts={message.parts}metadata={message.metadata}/>))}
{metadata?.duration && (<div className="text-sm text-gray-500">Duration: {formatDuration(metadata.duration)}</div>)}