In this exercise, we're going to explore adding metadata to messages. This is additional information you want to track about the message that doesn't necessarily fall into the parts category. For instance, we'll never want multiple metadata entries per message - it's just a single piece of information about the message.
In our case, we want to track how long each message takes to generate and then show that to the user. This is useful information, especially for really long-running workflows like deep research.
The plan is to start inside api/chat.ts and customize the UIMessage type again. The UIMessage type has three type parameters, and the first one is metadata. This is where we'll pass our type in - an object with a duration property that represents the milliseconds it took to complete streaming the message.
// TODO: Add the type of the metadata to the object here// We probably want it to be { duration: number }export type MyUIMessage = UIMessage<TODO>;
Once that's done, we'll implement the messageMetadata function in result.toUIMessageStreamResponse.
// TODO: Calculate the start time of the streamconst startTime = TODO;return result.toUIMessageStreamResponse<MyUIMessage>({// TODO: Add the messageMetadata function here// If it encounters a 'finish' part, it should return the duration// of the stream in millisecondsmessageMetadata: TODO,});
The messageMetadata callback gets called on every single text part, and when we encounter a part with type 'finish' (sent by the AI SDK when message creation is complete), we'll calculate the duration and send that as our message metadata.
We need to capture the start time of the stream so we can calculate how long it took to complete.
Check the reference material for more information.
In the frontend code, we also need to make changes. We need to update the type for the metadata parameter:
// problem/client/components.tsxexport const Message = ({role,parts,metadata,}: {role: string;parts: MyUIMessage['parts'];// TODO: Add a type for the metadata heremetadata: TODO;
And finally, we need to pass the actual metadata to the Message component.
// problem/client/root.tsx<Messagekey={message.id}role={message.role}parts={message.parts}// TODO: Pass the metadata to the Message componentmetadata={TODO}/>
I've scaffolded out the actual front-end code, so you won't need to do much there. The formatDuration function provided will handle displaying the duration nicely, as long as we have metadata.duration as a number.