Vercel AI Ecosystem \u2014 Complete Implementation Guide
This skill covers the full Vercel AI stack for building production-grade AI applications:
| Layer |
Package |
Purpose |
| AI SDK Core |
ai |
Text generation, streaming, structured output, tools, agents, embeddings |
| AI SDK UI |
@ai-sdk/react |
React hooks: useChat, useCompletion, useObject |
| AI Gateway |
@ai-sdk/gateway |
Unified model access, routing, fallbacks, caching, observability |
| AI Elements |
ai-elements |
48+ pre-built React components for AI interfaces (shadcn/ui based) |
| Chat SDK |
chat |
Cross-platform bots (Slack, Discord, Teams, GitHub, Telegram, Linear) |
| Workflow DevKit |
workflow |
Durable, resumable workflows with "use workflow" / "use step" directives |
| Security |
botid, @vercel/firewall |
Firewall/WAF, BotID bot detection, rate limiting, DDoS, abuse protection |
| Platform |
@vercel/functions, @vercel/blob |
Fluid Compute, streaming, storage, feature flags, caching, pricing |
| Sandbox |
@vercel/sandbox |
Isolated code execution in Firecracker microVMs, snapshots, network policies |
How to Use This Skill
- Start with the Quick Start Patterns below for common use cases
- Read the Critical v6 API Rules to avoid the most common mistakes
- Consult reference files (below) for detailed API docs on specific domains
Reference Files
Read reference files as needed for the specific domain you're working in:
| File |
When to read |
references/ai-sdk-core.md |
generateText, streamText, Output helpers, generateImage, generateSpeech, providers, embeddings, error handling |
references/ai-sdk-ui.md |
useChat, useCompletion, useObject hooks, streaming patterns, message types |
references/ai-elements.md |
UI components: Message, Conversation, PromptInput, Reasoning, Tool, CodeBlock, etc. |
references/agents-and-tools.md |
ToolLoopAgent, multi-step agents, tool calling, MCP integration |
references/chat-sdk.md |
Cross-platform chat bots: adapters, cards, modals, streaming to platforms |
references/workflow-sdk.md |
Durable workflows, steps, sleep, webhooks, hooks, error handling, deployment |
references/patterns.md |
Architecture decisions, best practices, production patterns, middleware |
references/ai-gateway.md |
AI Gateway: routing, fallbacks, caching, BYOK, observability |
references/data-stream-protocol.md |
SSE protocol for custom backends and native mobile clients (SwiftUI) |
references/streamdown.md |
Streamdown markdown renderer: full API, plugins, remend, performance |
references/security.md |
Firewall/WAF rules, BotID, rate limiting, DDoS, prompt injection, cost protection |
references/vercel-platform.md |
Fluid Compute, function config, streaming, @vercel/functions, storage, feature flags, pricing, limits |
references/vercel-sandbox.md |
Sandbox SDK, AI agent integration, snapshots, network policies, credential brokering |
Quick Start Patterns
Pattern 1: AI Chat with Beautiful UI (Most Common)
Server route + useChat hook + AI Elements components:
// app/api/chat/route.ts
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic('claude-sonnet-4-5'),
messages,
});
return result.toDataStreamResponse();
}
// app/page.tsx
'use client';
import { useChat } from '@ai-sdk/react';
import { Conversation, ConversationContent } from '@/components/ai-elements/conversation';
import { Message, MessageContent, MessageResponse } from '@/components/ai-elements/message';
import { PromptInput, PromptInputTextarea, PromptInputSubmit } from '@/components/ai-elements/prompt-input';
export default function Chat() {
const { messages, sendMessage, status, input, setInput } = useChat();
return (
<div className="flex flex-col h-screen">
<Conversation>
<ConversationContent>
{messages.map((message) => (
<Message from={message.role} key={message.id}>
<MessageContent>
{message.parts.map((part, i) => {
if (part.type === 'text') {
return <MessageResponse key={`${message.id}-${i}`}>{part.text}</MessageResponse>;
}
return null;
})}
</MessageContent>
</Message>
))}
</ConversationContent>
</Conversation>
<PromptInput => sendMessage({ text: value })}>
<PromptInputTextarea value={input} => setInput(e.target.value)} />
<PromptInputSubmit />
</PromptInput>
</div>
);
}
Pattern 2: Agent with Tool Display + Reasoning
// Render multi-part messages with reasoning, tools, and text
// v6: use if/else with startsWith for tool parts (dynamic type names)
{message.parts.map((part, i) => {
if (part.type === 'text') {
return <MessageResponse key={i}>{part.text}</MessageResponse>;
}
if (part.type === 'reasoning') {
return (
<Reasoning key={i}>
<ReasoningTrigger />
<ReasoningContent>{part.text}</ReasoningContent>
</Reasoning>
);
}
if (part.type.startsWith('tool-')) {
return <Tool key={i} part={part} />;
}
return null;
})}
Pattern 3: Structured Output with Tools
import { streamText, tool, Output, stepCountIs } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { z } from 'zod';
const result = await streamText({
model: anthropic('claude-sonnet-4-5'),
tools: {
getWeather: tool({
description: 'Get weather for a city',
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ temp: 72, condition: 'sunny' }),
}),
},
output: Output.object({
schema: z.object({
summary: z.string(),
recommendations: z.array(z.string()),
}),
}),
stopWhen: stepCountIs(5), // Enable multi-step tool calling
messages,
});
Pattern 4: Cross-Platform Chat Bot
import { Chat } from 'chat';
import { createSlackAdapter } from '@chat-adapter/slack';
import { createRedisState } from '@chat-adapter/state-redis';
import { streamText } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
const bot = new Chat({
userName: 'mybot',
adapters: { slack: createSlackAdapter() },
state: createRedisState(),
});
bot.onNewMention(async (thread) => {
await thread.subscribe();
const result = streamText({
model: anthropic('claude-sonnet-4-5'),
prompt: thread.message.text,
});
await thread.post(result.toTextStream());
});
Pattern 5: Durable Workflow
import { sleep } from 'workflow';
export async function userOnboarding(email: string) {
'use workflow';
const user = await createUser(email);
await sendWelcomeEmail(user);
await sleep('3 days');
await sendFollowUpEmail(user);
}
Pattern 6: Secure AI Chat Endpoint (BotID + Rate Limiting + Auth)
// instrumentation-client.ts — client-side BotID setup
import { initBotId } from 'botid/client/core';
initBotId({
protect: [{ path: '/api/chat', method: 'POST' }],
});
// app/api/chat/route.ts — layered server-side protection
import { checkBotId } from 'botid/server';
import { checkRateLimit } from '@vercel/firewall';
import { streamText, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { auth } from '@/auth';
export const maxDuration = 30;
export async function POST(req: Request) {
// Layer 1: Authentication
const session = await auth();
if (!session) return new Response('Unauthorized', { status: 401 });
// Layer 2: Bot detection
const { isBot } = await checkBotId();
if (isBot) return Response.json({ error: 'Access denied' }, { status: 403 });
// Layer 3: Rate limiting (per user)
const { rateLimited } = await checkRateLimit('ai-chat-limit', {
request: req,
rateLimitKey: session.user.id,
});
if (rateLimited) return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });
// Layer 4: Input sanitization + resource limits
const { messages } = await req.json();
const sanitized = messages.filter((m: { role: string }) => m.role !== 'system');
const result = streamText({
model: openai('gpt-4o'),
system: 'You are a helpful assistant.',
messages: sanitized,
maxTokens: 4096,
stopWhen: stepCountIs(5),
abortSignal: req.signal,
});
return result.toDataStreamResponse();
}
Critical v6 API Rules
v6 renamed and restructured many APIs from v5. These are the most common mistakes \u2014 using old names causes runtime errors:
- Tool definition: Use
tool() helper with inputSchema (NOT parameters \u2014 renamed in v5/v6)
- Model specification: Use provider functions \u2014
anthropic('claude-sonnet-4-5'), openai('gpt-4o')
- Chat hook: Use
sendMessage({ text: '...' }) (NOT append(), NOT { content } \u2014 v6 uses text property)
- Message format: Access
message.parts array, switch on part.type. UIMessage no longer has content.
- Reasoning parts: Use
part.text (NOT part.reasoning \u2014 property renamed to text in v6)
- Multi-step control: Use
stopWhen: stepCountIs(N) (NOT maxSteps \u2014 removed in v6). For useChat, use sendAutomaticallyWhen.
- Streaming response: Use
toDataStreamResponse() or toUIMessageStreamResponse()
- Agent response: Standalone
createAgentUIStreamResponse({ agent, uiMessages }) (NOT a method on the agent)
- ToolLoopAgent: Use
instructions parameter (NOT system \u2014 renamed for agents)
- Structured output:
Output.object(), Output.array(), Output.choice(), Output.json(), Output.text()
- Tool approval: Use
needsApproval: true on tool definition (NOT experimental_toolCallApproval)
- Tool part types: Parts use
tool-{toolName} pattern. States: input-streaming, input-available, approval-requested, output-available
- Embeddings: Use
provider.embeddingModel('model-name') (NOT textEmbeddingModel — renamed in v6)
- MCP: Use
createMCPClient() from @ai-sdk/mcp
- Package manager: Always detect from lockfile (pnpm-lock.yaml \u2192 pnpm, etc.)
- Message types:
CoreMessage renamed to ModelMessage. Use convertToModelMessages() (NOT convertToCoreMessages \u2014 renamed and now async)
- generateObject/streamObject: Deprecated \u2014 use
generateText/streamText with Output.* helpers instead
- Embeddings:
textEmbeddingModel() renamed to embeddingModel(), textEmbedding() to embedding()
- Token usage:
cachedInputTokens \u2192 inputTokenDetails.cacheReadTokens, reasoningTokens \u2192 outputTokenDetails.reasoningTokens
- OpenAI strict mode:
strictJsonSchema defaults to true in v6 \u2014 use .nullable() not .optional() in Zod schemas
- Azure:
azure() uses Responses API by default; use azure.chat() for Chat Completions. Metadata key: azure (not openai)
- Migration tool: Run
npx @ai-sdk/codemod v6 to automate most v5\u21926 changes
- Tool results:
addToolResult renamed to addToolOutput in v6 (useChat hook)
Installing AI Elements
# Individual components (recommended)
npx ai-elements@latest add message conversation prompt-input reasoning tool code-block
# All components via shadcn registry
npx shadcn@latest add https://elements.ai-sdk.dev/api/registry/all.json
Components install as source code into your project (typically @/components/ai-elements/), giving you full control. Requires Tailwind CSS in CSS Variables mode.
Key Streaming Architecture
Client (useChat) <--SSE--> Server (streamText) <--Gateway--> LLM Provider
| | |
AI Elements Middleware AI Gateway
(rendering) (caching, logging, (routing, fallbacks,
guardrails, RAG) caching, observability)
The Data Stream Protocol uses Server-Sent Events with typed chunks: text deltas, tool calls, tool results, reasoning, and finish signals. MessageResponse component from AI Elements handles incremental markdown rendering via Streamdown without re-parsing on each chunk.
1---2name: vercel-ai3description: Complete Vercel AI ecosystem guide: AI SDK v6 (generateText, streamText, useChat, tools, agents, MCP), AI Gateway (routing, fallbacks, caching), AI Elements (48+ React UI components), Chat SDK (Slack/Discord/Teams bots), Workflow DevKit, Data Stream Protocol, and Security (Vercel Firewall/WAF, BotID bot detection, rate limiting, DDoS, Attack Challenge Mode, prompt injection prevention, cost controls). Use when building AI chat UIs, agents, streaming, tool calling, or working with ai/@ai-sdk packages. Also use when securing AI apps: Firewall rules, BotID, @vercel/firewall rate limiting, bot blocking, geo-blocking, IP blocking, AI endpoint protection, Spend Management, or any Vercel security for AI products.4---56# Vercel AI Ecosystem \u2014 Complete Implementation Guide78This skill covers the full Vercel AI stack for building production-grade AI applications:910| Layer | Package | Purpose |11|-------|---------|---------|12| **AI SDK Core** | `ai` | Text generation, streaming, structured output, tools, agents, embeddings |13| **AI SDK UI** | `@ai-sdk/react` | React hooks: `useChat`, `useCompletion`, `useObject` |14| **AI Gateway** | `@ai-sdk/gateway` | Unified model access, routing, fallbacks, caching, observability |15| **AI Elements** | `ai-elements` | 48+ pre-built React components for AI interfaces (shadcn/ui based) |16| **Chat SDK** | `chat` | Cross-platform bots (Slack, Discord, Teams, GitHub, Telegram, Linear) |17| **Workflow DevKit** | `workflow` | Durable, resumable workflows with `"use workflow"` / `"use step"` directives |18| **Security** | `botid`, `@vercel/firewall` | Firewall/WAF, BotID bot detection, rate limiting, DDoS, abuse protection |19| **Platform** | `@vercel/functions`, `@vercel/blob` | Fluid Compute, streaming, storage, feature flags, caching, pricing |20| **Sandbox** | `@vercel/sandbox` | Isolated code execution in Firecracker microVMs, snapshots, network policies |2122## How to Use This Skill23241. Start with the Quick Start Patterns below for common use cases252. Read the Critical v6 API Rules to avoid the most common mistakes263. Consult reference files (below) for detailed API docs on specific domains2728## Reference Files2930Read reference files as needed for the specific domain you're working in:3132| File | When to read |33|------|-------------|34| `references/ai-sdk-core.md` | generateText, streamText, Output helpers, generateImage, generateSpeech, providers, embeddings, error handling |35| `references/ai-sdk-ui.md` | useChat, useCompletion, useObject hooks, streaming patterns, message types |36| `references/ai-elements.md` | UI components: Message, Conversation, PromptInput, Reasoning, Tool, CodeBlock, etc. |37| `references/agents-and-tools.md` | ToolLoopAgent, multi-step agents, tool calling, MCP integration |38| `references/chat-sdk.md` | Cross-platform chat bots: adapters, cards, modals, streaming to platforms |39| `references/workflow-sdk.md` | Durable workflows, steps, sleep, webhooks, hooks, error handling, deployment |40| `references/patterns.md` | Architecture decisions, best practices, production patterns, middleware |41| `references/ai-gateway.md` | AI Gateway: routing, fallbacks, caching, BYOK, observability |42| `references/data-stream-protocol.md` | SSE protocol for custom backends and native mobile clients (SwiftUI) |43| `references/streamdown.md` | Streamdown markdown renderer: full API, plugins, remend, performance |44| `references/security.md` | Firewall/WAF rules, BotID, rate limiting, DDoS, prompt injection, cost protection |45| `references/vercel-platform.md` | Fluid Compute, function config, streaming, @vercel/functions, storage, feature flags, pricing, limits |46| `references/vercel-sandbox.md` | Sandbox SDK, AI agent integration, snapshots, network policies, credential brokering |4748## Quick Start Patterns4950### Pattern 1: AI Chat with Beautiful UI (Most Common)5152Server route + useChat hook + AI Elements components:5354```typescript55// app/api/chat/route.ts56import { streamText } from 'ai';57import { anthropic } from '@ai-sdk/anthropic';5859export async function POST(req: Request) {60 const { messages } = await req.json();61 const result = streamText({62 model: anthropic('claude-sonnet-4-5'),63 messages,64 });65 return result.toDataStreamResponse();66}67```6869```tsx70// app/page.tsx71'use client';72import { useChat } from '@ai-sdk/react';73import { Conversation, ConversationContent } from '@/components/ai-elements/conversation';74import { Message, MessageContent, MessageResponse } from '@/components/ai-elements/message';75import { PromptInput, PromptInputTextarea, PromptInputSubmit } from '@/components/ai-elements/prompt-input';7677export default function Chat() {78 const { messages, sendMessage, status, input, setInput } = useChat();79 return (80 <div className="flex flex-col h-screen">81 <Conversation>82 <ConversationContent>83 {messages.map((message) => (84 <Message from={message.role} key={message.id}>85 <MessageContent>86 {message.parts.map((part, i) => {87 if (part.type === 'text') {88 return <MessageResponse key={`${message.id}-${i}`}>{part.text}</MessageResponse>;89 }90 return null;91 })}92 </MessageContent>93 </Message>94 ))}95 </ConversationContent>96 </Conversation>97 <PromptInput onSubmit={(value) => sendMessage({ text: value })}>98 <PromptInputTextarea value={input} onChange={(e) => setInput(e.target.value)} />99 <PromptInputSubmit />100 </PromptInput>101 </div>102 );103}104```105106### Pattern 2: Agent with Tool Display + Reasoning107108```tsx109// Render multi-part messages with reasoning, tools, and text110// v6: use if/else with startsWith for tool parts (dynamic type names)111{message.parts.map((part, i) => {112 if (part.type === 'text') {113 return <MessageResponse key={i}>{part.text}</MessageResponse>;114 }115 if (part.type === 'reasoning') {116 return (117 <Reasoning key={i}>118 <ReasoningTrigger />119 <ReasoningContent>{part.text}</ReasoningContent>120 </Reasoning>121 );122 }123 if (part.type.startsWith('tool-')) {124 return <Tool key={i} part={part} />;125 }126 return null;127})}128```129130### Pattern 3: Structured Output with Tools131132```typescript133import { streamText, tool, Output, stepCountIs } from 'ai';134import { anthropic } from '@ai-sdk/anthropic';135import { z } from 'zod';136137const result = await streamText({138 model: anthropic('claude-sonnet-4-5'),139 tools: {140 getWeather: tool({141 description: 'Get weather for a city',142 inputSchema: z.object({ city: z.string() }),143 execute: async ({ city }) => ({ temp: 72, condition: 'sunny' }),144 }),145 },146 output: Output.object({147 schema: z.object({148 summary: z.string(),149 recommendations: z.array(z.string()),150 }),151 }),152 stopWhen: stepCountIs(5), // Enable multi-step tool calling153 messages,154});155```156157### Pattern 4: Cross-Platform Chat Bot158159```typescript160import { Chat } from 'chat';161import { createSlackAdapter } from '@chat-adapter/slack';162import { createRedisState } from '@chat-adapter/state-redis';163import { streamText } from 'ai';164import { anthropic } from '@ai-sdk/anthropic';165166const bot = new Chat({167 userName: 'mybot',168 adapters: { slack: createSlackAdapter() },169 state: createRedisState(),170});171172bot.onNewMention(async (thread) => {173 await thread.subscribe();174 const result = streamText({175 model: anthropic('claude-sonnet-4-5'),176 prompt: thread.message.text,177 });178 await thread.post(result.toTextStream());179});180```181182### Pattern 5: Durable Workflow183184```typescript185import { sleep } from 'workflow';186187export async function userOnboarding(email: string) {188 'use workflow';189 const user = await createUser(email);190 await sendWelcomeEmail(user);191 await sleep('3 days');192 await sendFollowUpEmail(user);193}194```195196### Pattern 6: Secure AI Chat Endpoint (BotID + Rate Limiting + Auth)197198```typescript199// instrumentation-client.ts — client-side BotID setup200import { initBotId } from 'botid/client/core';201initBotId({202 protect: [{ path: '/api/chat', method: 'POST' }],203});204```205206```typescript207// app/api/chat/route.ts — layered server-side protection208import { checkBotId } from 'botid/server';209import { checkRateLimit } from '@vercel/firewall';210import { streamText, stepCountIs } from 'ai';211import { openai } from '@ai-sdk/openai';212import { auth } from '@/auth';213214export const maxDuration = 30;215216export async function POST(req: Request) {217 // Layer 1: Authentication218 const session = await auth();219 if (!session) return new Response('Unauthorized', { status: 401 });220221 // Layer 2: Bot detection222 const { isBot } = await checkBotId();223 if (isBot) return Response.json({ error: 'Access denied' }, { status: 403 });224225 // Layer 3: Rate limiting (per user)226 const { rateLimited } = await checkRateLimit('ai-chat-limit', {227 request: req,228 rateLimitKey: session.user.id,229 });230 if (rateLimited) return Response.json({ error: 'Rate limit exceeded' }, { status: 429 });231232 // Layer 4: Input sanitization + resource limits233 const { messages } = await req.json();234 const sanitized = messages.filter((m: { role: string }) => m.role !== 'system');235236 const result = streamText({237 model: openai('gpt-4o'),238 system: 'You are a helpful assistant.',239 messages: sanitized,240 maxTokens: 4096,241 stopWhen: stepCountIs(5),242 abortSignal: req.signal,243 });244 return result.toDataStreamResponse();245}246```247248## Critical v6 API Rules249250v6 renamed and restructured many APIs from v5. These are the most common mistakes \u2014 using old names causes runtime errors:2512521. **Tool definition**: Use `tool()` helper with `inputSchema` (NOT `parameters` \u2014 renamed in v5/v6)2532. **Model specification**: Use provider functions \u2014 `anthropic('claude-sonnet-4-5')`, `openai('gpt-4o')`2543. **Chat hook**: Use `sendMessage({ text: '...' })` (NOT `append()`, NOT `{ content }` \u2014 v6 uses `text` property)2554. **Message format**: Access `message.parts` array, switch on `part.type`. UIMessage no longer has `content`.2565. **Reasoning parts**: Use `part.text` (NOT `part.reasoning` \u2014 property renamed to `text` in v6)2576. **Multi-step control**: Use `stopWhen: stepCountIs(N)` (NOT `maxSteps` \u2014 removed in v6). For useChat, use `sendAutomaticallyWhen`.2587. **Streaming response**: Use `toDataStreamResponse()` or `toUIMessageStreamResponse()`2598. **Agent response**: Standalone `createAgentUIStreamResponse({ agent, uiMessages })` (NOT a method on the agent)2609. **ToolLoopAgent**: Use `instructions` parameter (NOT `system` \u2014 renamed for agents)26110. **Structured output**: `Output.object()`, `Output.array()`, `Output.choice()`, `Output.json()`, `Output.text()`26211. **Tool approval**: Use `needsApproval: true` on tool definition (NOT `experimental_toolCallApproval`)26312. **Tool part types**: Parts use `tool-{toolName}` pattern. States: `input-streaming`, `input-available`, `approval-requested`, `output-available`26413. **Embeddings**: Use `provider.embeddingModel('model-name')` (NOT `textEmbeddingModel` — renamed in v6)26514. **MCP**: Use `createMCPClient()` from `@ai-sdk/mcp`26615. **Package manager**: Always detect from lockfile (pnpm-lock.yaml \u2192 pnpm, etc.)26716. **Message types**: `CoreMessage` renamed to `ModelMessage`. Use `convertToModelMessages()` (NOT `convertToCoreMessages` \u2014 renamed and now **async**)26817. **generateObject/streamObject**: Deprecated \u2014 use `generateText`/`streamText` with `Output.*` helpers instead26918. **Embeddings**: `textEmbeddingModel()` renamed to `embeddingModel()`, `textEmbedding()` to `embedding()`27019. **Token usage**: `cachedInputTokens` \u2192 `inputTokenDetails.cacheReadTokens`, `reasoningTokens` \u2192 `outputTokenDetails.reasoningTokens`27120. **OpenAI strict mode**: `strictJsonSchema` defaults to `true` in v6 \u2014 use `.nullable()` not `.optional()` in Zod schemas27221. **Azure**: `azure()` uses Responses API by default; use `azure.chat()` for Chat Completions. Metadata key: `azure` (not `openai`)27322. **Migration tool**: Run `npx @ai-sdk/codemod v6` to automate most v5\u21926 changes27423. **Tool results**: `addToolResult` renamed to `addToolOutput` in v6 (useChat hook)275276## Installing AI Elements277278```bash279# Individual components (recommended)280npx ai-elements@latest add message conversation prompt-input reasoning tool code-block281282# All components via shadcn registry283npx shadcn@latest add https://elements.ai-sdk.dev/api/registry/all.json284```285286Components install as source code into your project (typically `@/components/ai-elements/`), giving you full control. Requires Tailwind CSS in CSS Variables mode.287288## Key Streaming Architecture289290```291Client (useChat) <--SSE--> Server (streamText) <--Gateway--> LLM Provider292 | | |293 AI Elements Middleware AI Gateway294 (rendering) (caching, logging, (routing, fallbacks,295 guardrails, RAG) caching, observability)296```297298The Data Stream Protocol uses Server-Sent Events with typed chunks: text deltas, tool calls, tool results, reasoning, and finish signals. MessageResponse component from AI Elements handles incremental markdown rendering via Streamdown without re-parsing on each chunk.