Claude-skill-registry ai-sdk-5
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/ai-sdk-5" ~/.claude/skills/majiayu000-claude-skill-registry-ai-sdk-5 && rm -rf "$T"
manifest:
skills/data/ai-sdk-5/SKILL.mdsource content
Breaking Changes from AI SDK 4
// ❌ AI SDK 4 (OLD) import { useChat } from "ai"; const { messages, handleSubmit, input, handleInputChange } = useChat({ api: "/api/chat", }); // ✅ AI SDK 5 (NEW) import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), });
Client Setup
import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { useState } from "react"; export function Chat() { const [input, setInput] = useState(""); const { messages, sendMessage, isLoading, error } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!input.trim()) return; sendMessage({ text: input }); setInput(""); }; return ( <div> <div> {messages.map((message) => ( <Message key={message.id} message={message} /> ))} </div> <form onSubmit={handleSubmit}> <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Type a message..." disabled={isLoading} /> <button type="submit" disabled={isLoading}> Send </button> </form> {error && <div>Error: {error.message}</div>} </div> ); }
UIMessage Structure (v5)
// ❌ Old: message.content was a string // ✅ New: message.parts is an array interface UIMessage { id: string; role: "user" | "assistant" | "system"; parts: MessagePart[]; } type MessagePart = | { type: "text"; text: string } | { type: "image"; image: string } | { type: "tool-call"; toolCallId: string; toolName: string; args: unknown } | { type: "tool-result"; toolCallId: string; result: unknown }; // Extract text from parts function getMessageText(message: UIMessage): string { return message.parts .filter((part): part is { type: "text"; text: string } => part.type === "text") .map((part) => part.text) .join(""); } // Render message function Message({ message }: { message: UIMessage }) { return ( <div className={message.role === "user" ? "user" : "assistant"}> {message.parts.map((part, index) => { if (part.type === "text") { return <p key={index}>{part.text}</p>; } if (part.type === "image") { return <img key={index} src={part.image} alt="" />; } return null; })} </div> ); }
Server-Side (Route Handler)
// app/api/chat/route.ts import { openai } from "@ai-sdk/openai"; import { streamText } from "ai"; export async function POST(req: Request) { const { messages } = await req.json(); const result = await streamText({ model: openai("gpt-4o"), messages, system: "You are a helpful assistant.", }); return result.toDataStreamResponse(); }
With LangChain
// app/api/chat/route.ts import { toUIMessageStream } from "@ai-sdk/langchain"; import { ChatOpenAI } from "@langchain/openai"; import { HumanMessage, AIMessage } from "@langchain/core/messages"; export async function POST(req: Request) { const { messages } = await req.json(); const model = new ChatOpenAI({ modelName: "gpt-4o", streaming: true, }); // Convert UI messages to LangChain format const langchainMessages = messages.map((m) => { const text = m.parts .filter((p) => p.type === "text") .map((p) => p.text) .join(""); return m.role === "user" ? new HumanMessage(text) : new AIMessage(text); }); const stream = await model.stream(langchainMessages); return toUIMessageStream(stream).toDataStreamResponse(); }
Streaming with Tools
import { openai } from "@ai-sdk/openai"; import { streamText, tool } from "ai"; import { z } from "zod"; const result = await streamText({ model: openai("gpt-4o"), messages, tools: { getWeather: tool({ description: "Get weather for a location", parameters: z.object({ location: z.string().describe("City name"), }), execute: async ({ location }) => { // Fetch weather data return { temperature: 72, condition: "sunny" }; }, }), }, });
useCompletion (Text Generation)
import { useCompletion } from "@ai-sdk/react"; import { DefaultCompletionTransport } from "ai"; const { completion, complete, isLoading } = useCompletion({ transport: new DefaultCompletionTransport({ api: "/api/complete" }), }); // Trigger completion await complete("Write a haiku about");
Error Handling
const { error, messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: "/api/chat" }), onError: (error) => { console.error("Chat error:", error); toast.error("Failed to send message"); }, }); // Display error {error && ( <div className="error"> {error.message} <button onClick={() => sendMessage({ text: lastInput })}> Retry </button> </div> )}