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 { serializableMediaCardSchema } from '@/components/tool-ui/media-card/schema';export const tools = {  previewLink: tool({    description: 'Return a simple link preview',    inputSchema: z.object({ url: z.string().url() }),    outputSchema: serializableMediaCardSchema,    async execute({ url }) {      return {        id: 'link-1',        kind: 'link',        href: url,        title: 'React Server Components',        thumb:          'https://images.unsplash.com/photo-1633356122544-f134324a6cee?auto=format&fit=crop&q=80&w=1200',      };    },  }),} 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-4o'),    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 { MediaCard, parseSerializableMediaCard } from '@/components/tool-ui/media-card';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 card = parseSerializableMediaCard(part.output);              return <MediaCard key={i} {...card} />;            }            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 parseSerializableX 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