All right, let's test this out. I'm gonna say, "tell me what to-do items I have today." And it says
read file path today.md and path todo.md.
So it is tracking the message parts that involve some tools and showing them in the front end. Let's take a look at our backend code to start with.
We have this tools object, which has all of the tools that we had before just in the module scope instead of down here inside the streamText call.
const tools = {writeFile: tool({description: "Write to a file",inputSchema: z.object({path: z.string().describe("The path to the file to create"),content: z.string().describe("The content of the file to create"),}),
We're then taking that big tools object and we're passing it into the third type argument of UIMessage.
export type MyUIMessage = UIMessage<never, never, InferUITools<typeof tools>>
If we hover over UIMessage we can see that it takes in metadata as its first argument, then data parts as its second argument, and then tools as its third argument. And we just need to take that type of tools and pass it into InferUITools before then passing it as the third type argument to UIMessage. From there, we then pass it into useChat here.
const { messages, sendMessage } = useChat<MyUIMessage>({})
And by doing this, we make sure that the messages are typed as MyUIMessage, instead of if I just remove this, they would just be the plain old UIMessage. We do this so that we can then make sure the parts are type safe that are being passed into Message here. Because we're currently getting an error saying, whoa, what a mega long error that is. It comes down to the parts property being different on each one. So by passing this in here, we should see the UIMessage error go away.
Yes, we do. Heading into our Message component, we can see that we have MyUIMessage now instead of just plain old UIMessage.
export const Message = ({role,parts,}: {role: string;parts: MyUIMessage['parts'];}) => {
And further down, we can see we have a tool-writeFile here.
if (part.type === "tool-writeFile") {return (<divkey={index}className="bg-blue-900/20 border border-blue-700 rounded p-3 text-sm"><div className="font-semibold text-blue-300 mb-1">📝 Wrote to file</div><div className=
Now, this is really sweet because we get auto-complete on these types here. So every single tool that we have, searchFiles, readFile, listDirectory is here.
And if I change this to tool-exists
then we can see that there's no content on that input there because content is only available on writeFile and that error then goes away.
Beautiful. If I change the name of one of these tools, like WRITEFILE in uppercase for instance
then we're going to get an error on the front end here because it's not up to date with the backend code. So we need to change this to WRITEFILE uppercase.
So this pattern of type safety from the front end to the backend works great with the AI SDK and tools and it makes showing the results of those tools in the front end that much easier.
Nice work and I will see you in the next one.
if (part.type === "tool-writeFile") {
const tools = {WRITEFILE: tool({
if (part.type === "tool-WRITEFILE") {