Advanced

Deep-dive topics for stronger typing and larger integrations.

Tool Type Inference

With InferUITools in AI SDK 5, you can infer tool input/output types from your tool set and get fully typed message.parts in the UI.

Define Tools with Output Schemas

import { tool } from "ai";import { z } from "zod";import { SerializableLinkPreviewSchema } from "@/components/tool-ui/link-preview/schema";export const tools = {  previewLink: tool({    description: "Return a simple link preview",    inputSchema: z.object({ url: z.url() }),    outputSchema: SerializableLinkPreviewSchema,    async execute({ url }) {      return {        id: "link-preview-1",        href: url,        title: "React Server Components",        image:          "https://images.unsplash.com/photo-1633356122544-f134324a6cee?auto=format&fit=crop&q=80&w=1200",        domain: new URL(url).hostname,      };    },  }),} as const;// Export a type only; the client should import types, not server code.export type Tools = typeof tools;

Use Tools on the Server

import { streamText, convertToModelMessages } from "ai";import { openai } from "@ai-sdk/openai";import { tools } from "@/lib/tools";export async function POST(req: Request) {  const { messages } = await req.json();  const result = streamText({    model: openai("gpt-5-nano"),    messages: convertToModelMessages(messages),    tools,  });  return result.toUIMessageStreamResponse();}

Infer UI-Level Types in the Client

"use client";import { useChat } from "@ai-sdk/react";import { InferUITools } from "ai";import type { Tools } from "@/lib/tools";import {  LinkPreview,  parseSerializableLinkPreview,} from "@/components/tool-ui/link-preview";type MyUITools = InferUITools<Tools>;export default function Chat() {  const { messages } = useChat<MyUITools>({ api: "/api/chat" });  return (    <div>      {messages.map((m) => (        <div key={m.id}>          {m.parts.map((part, i) => {            // Fully typed: 'tool-previewLink' with correct output shape            if (              part.type === "tool-previewLink" &&              part.state === "output-available"            ) {              const preview = parseSerializableLinkPreview(part.output);              return <LinkPreview key={i} {...preview} />;            }            return null;          })}        </div>      ))}    </div>  );}

Tips

Keep runtime boundaries clean

Export only types from lib/tools.ts to the client; never import server code into the browser.

Single-tool helpers

For a single tool, InferUITool works the same way as InferUITools but for one tool definition.

End-to-end validation

Use the component's outputSchema on the server and parseSerializable{componentName} on the client to validate at both ends.

Lifecycle-aware rendering

Use part.state to respect the Tool UI lifecycle from UI Guidelines:

  • state === 'invocation' → show intent / shell
  • state === 'output-pending' → show skeleton / loading shell
  • state === 'output-available' → render the full component
  • state === 'errored' → show an error surface instead of silently failing
Advanced | Tool UI