Vercel AI SDK skill for Codex
Streaming chat and tools that match your ai version. Codex checks which major of the ai package is installed before writing a line, then builds authenticated streaming routes, useChat UIs that render message parts and tool states, and Zod-typed tools with an explicit step limit. A version table maps the renamed APIs across AI SDK 5, 6 and 7, so upgrades run through codemods and typecheck instead of guesswork.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- vercel-ai-sdk Installs to ~/.agents/skills/vercel-ai-sdk/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install vercel-ai-sdk The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project vercel-ai-sdk Installs to .agents/skills/vercel-ai-sdk/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/vercel-ai-sdk.zipunzip -o vercel-ai-sdk.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) vercel-ai-sdk PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $vercel-ai-sdk, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds TypeScript LLM features with the Vercel AI SDK (the ai package) - streamText and generateText, useChat streaming chat UIs with message parts, tools with Zod inputSchema and multi-step stopWhen loops, structured output via Output.object, provider packages, ToolLoopAgent, telemetry and version migrations between AI SDK 5, 6 and 7. Use when adding a streaming chat route to Next.js, React, Svelte or Vue, defining AI SDK tools, generating typed objects, or upgrading code that uses deprecated AI SDK APIs.
Use it when
- A TypeScript app needs streaming LLM responses or a chat UI
- You are defining AI SDK tools or multi-step tool loops
- Typed objects must come back from a model call
- Code uses deprecated AI SDK APIs after a major upgrade
Use something else when
- You are calling the OpenAI Responses API directly without the AI SDK: use openai-responses-api
- The work is chat UX design rather than SDK wiring: use ai-chat-interface
How to check the result
Typecheck passes, a curl to the route streams text and tool parts, mock-model tests pass, and stop, error and retry work in the browser.
The mistake it prevents
Pasting examples from a different AI SDK major, or omitting stopWhen so tool results never become a final answer.
Done well means: the code matches the installed major version of ai, the server route
validates input and streams UI message parts, tools have Zod schemas and a step limit, typed
output is validated by a schema, keys stay server-side, and the chat UI handles streaming,
stop, error and retry states.
Find the version first
npm ls ai @ai-sdk/react @ai-sdk/openai zod 2>/dev/null
node -v
The API changed substantially across majors. Write code for what is installed; upgrade only when asked.
| Concept | AI SDK 5 | AI SDK 6 | AI SDK 7 |
|---|---|---|---|
| Step limit | stopWhen: stepCountIs(n) | stepCountIs(n) | isStepCount(n) |
| Typed output | generateObject / streamObject | generateText + output: Output.object(...) (generateObject deprecated) | same as 6 |
| System prompt option | system | system | instructions |
| UI messages to model | convertToModelMessages(msgs) (sync) | await convertToModelMessages(msgs) | await |
| Stream response | result.toUIMessageStreamResponse() | same | createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) }) (old helper deprecated) |
| Agent class | Experimental_Agent | ToolLoopAgent | ToolLoopAgent |
| Runtime | Node 18+ | Node 18+ | Node 22+, ESM only |
When upgrading, run the official codemods for the target major (npx @ai-sdk/codemod upgrade
or the version-specific command in the migration guide; needs network), then fix what remains
by hand, then run typecheck and tests. Read AGENTS.md for the package manager.
Server route (Next.js App Router, AI SDK 7)
// app/api/chat/route.ts
import {
streamText, convertToModelMessages, tool, isStepCount,
createUIMessageStreamResponse, toUIMessageStream, type UIMessage,
} from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
export const maxDuration = 30;
const tools = {
getOrderStatus: tool({
description: 'Look up shipping status for an order ID like ORD-1234.',
inputSchema: z.object({ orderId: z.string().regex(/^ORD-\d+$/) }),
execute: async ({ orderId }) => orders.status(orderId), // runs on the server
}),
};
export async function POST(req: Request) {
const session = await auth(req);
if (!session) return new Response('Unauthorized', { status: 401 });
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: openai(process.env.CHAT_MODEL ?? 'gpt-5-mini'),
instructions: 'You are a support assistant. Use tools for order facts.',
messages: await convertToModelMessages(messages),
tools,
stopWhen: isStepCount(5),
abortSignal: req.signal,
});
return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) });
}
On AI SDK 5/6 the last lines are system: ..., stopWhen: stepCountIs(5) and
return result.toUIMessageStreamResponse();.
Server rules:
- Authenticate before calling the model; rate-limit per user (
$api-rate-limiting). - Cap history length or tokens before
convertToModelMessages; clients can send anything. - Never trust tool inputs: the Zod schema validates shape, the
executebody must still check the user owns the order. - Tools that send, delete, pay or deploy need approval. Recent versions support tool approval
(
approval-requestedUI part states); otherwise render a confirm step client-side and only execute on an explicit user action.
Client (React)
'use client';
import { useChat } from '@ai-sdk/react';
import { useState } from 'react';
export default function Chat() {
const { messages, sendMessage, status, stop, error, regenerate } = useChat();
const [input, setInput] = useState('');
const busy = status === 'submitted' || status === 'streaming';
return (
<div>
{messages.map((m) => (
<div key={m.id}>
{m.parts.map((part, i) => {
switch (part.type) {
case 'text':
return <p key={i}>{part.text}</p>;
case 'tool-getOrderStatus':
return part.state === 'output-available'
? <OrderCard key={i} data={part.output} />
: <Spinner key={i} label="Checking order…" />;
default:
return null;
}
})}
</div>
))}
{error && <button onClick={() => regenerate()}>Retry</button>}
<form onSubmit={(e) => { e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }}>
<input value={input} onChange={(e) => setInput(e.target.value)} disabled={busy} />
{busy ? <button type="button" onClick={stop}>Stop</button> : <button type="submit">Send</button>}
</form>
</div>
);
}
- Render
message.parts, not a singlecontentstring; tool parts are typed astool-<toolName>with states such asinput-streaming,input-available,output-availableandoutput-error. Handle unknown part types with a default branch. useChatposts to/api/chatby default; configure a transport for other endpoints or extra body fields.- Persist chats server-side (save
UIMessage[]on finish); see$ai-chat-interfacefor UX details like markdown rendering and scroll behaviour.
Structured output
import { generateText, Output } from 'ai';
const schema = z.object({
vendor: z.string(),
total: z.number().describe('grand total in invoice currency'),
currency: z.string().length(3),
dueDate: z.string().nullable().describe('ISO date or null if absent'),
});
const { output } = await generateText({
model: openai('gpt-5-mini'),
output: Output.object({ schema }),
prompt: `Extract invoice fields:\n\n${text}`,
});
On AI SDK 5 use generateObject({ model, schema, prompt }) and read object. For streaming
partial objects use streamText with output and iterate partialOutputStream (v6+).
Schema design and validation: $structured-extraction.
Agents
ToolLoopAgent packages model, instructions, tools and stop condition for reuse across
routes and jobs; its default step limit is higher than streamText’s, so set stopWhen
explicitly to the budget you want. Keep plain streamText for a single chat route.
Providers
Install the provider package (@ai-sdk/openai, @ai-sdk/google, @ai-sdk/mistral, …) or
route through the AI Gateway with a "provider/model" string if the project already uses it.
Keep the model ID in config, not scattered literals, so switching is one change.
Observability
- AI SDK 7: install
@ai-sdk/otel, callregisterTelemetry(new OpenTelemetry())once at startup; telemetry is then on by default and per-calltelemetry: { isEnabled: false }opts out. - AI SDK 5/6: pass
experimental_telemetry: { isEnabled: true, functionId: 'chat' }per call. - Record usage from
onEnd/onFinish(tokens, model, user) for cost dashboards:$llm-observability.
Gotchas
- Mixing snippets from different majors is the most common breakage; the table above is the checklist when TypeScript reports a missing export.
- Without
stopWhen,streamTextstops after the first step, so tool results never get turned into a final answer. - Edge runtime: some provider SDKs and Node APIs are unavailable; prefer the Node runtime unless latency requires edge and dependencies support it.
- Forgetting
abortSignal: req.signalkeeps generating (and billing) after the user hits Stop. - Zod: keep the version the installed
aipeer dependency expects; mismatched Zod majors produce confusing type errors. - Never expose provider keys with
NEXT_PUBLIC_; all model calls go through the server.
Verification
tsc --noEmitand the project’s lint pass.- Hit the route locally:
curl -N -X POST localhost:3000/api/chat -H 'content-type: application/json' -d '{"messages":[{"id":"1","role":"user","parts":[{"type":"text","text":"status of ORD-1?"}]}]}'and watch the stream include tool and text parts. - Unit-test tools’
executefunctions directly; test the route with a mock model (MockLanguageModelV2/V3fromai/test, whichever the installed version exports). - In the browser: send, stop mid-stream, trigger an error, retry.
Output summary template
**AI SDK:** ai 7.x, @ai-sdk/react, @ai-sdk/openai; Node 22
**Route:** app/api/chat/route.ts, auth + 40-message cap, isStepCount(5), abort on disconnect
**Tools:** getOrderStatus (read-only), cancelOrder (approval required)
**UI:** parts rendering, stop/retry, tool loading states
**Checks:** tsc clean, 6 tests with mock model, manual stream verified with curl What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Vercel AI SDK"
short_description: "Streaming chat and tools that match your ai version"
default_prompt: "$vercel-ai-sdk Add a streaming chat route with tools to our Next.js app using the AI SDK" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.