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;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();
}"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>
);
}