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-preview-1",
assetId: url,
kind: "link",
href: url,
title: "React Server Components",
thumb:
"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-4o"),
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 {
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>
);
}