{"content":"# AI SDK & Simple Chat\n\nInstall the Vercel AI SDK with AI Elements components. Build a streaming chat interface with the useChat hook.\n\n### Step 1: Install AI SDK v6\n\n```bash\nbun add ai @ai-sdk/react\n```\n\n### Step 2: Install AI Elements (optional)\n\nAI Elements are pre-built UI components for AI interfaces:\n\n```bash\nbunx shadcn@latest add @ai-elements/all\n```\n\nThis adds components like:\n\n- Chat bubbles and message lists\n- Streaming text displays\n- Loading indicators\n- Code blocks with syntax highlighting\n\n### Step 3: Configure your AI provider\n\n**Option A: Using Vercel AI Gateway**\n\nThe AI Gateway supports two authentication methods. Add one of these to your `.env.development`:\n\n```env\nAI_GATEWAY_API_KEY=\"your-api-key-here\"\nVERCEL_OIDC_TOKEN=\"your-oidc-token\"\n```\n\nYou can create an API key at [Vercel AI Gateway](https://vercel.com/ai-gateway) and add it to your `.env.development` and sync to Vercel with `bun run env:push`.\n\nAlternatively, you can get a Vercel OIDC token by logging in via the Vercel CLI:\n\n```bash\nvercel login\n```\n\nThis will prompt you to authorize the Vercel CLI to access your Vercel account. Once authorized, you can run `bun run env:pull` to sync your environment variables, which will include the Vercel OIDC token.\n\nAt least one must be set when using the AI Gateway.\n\n**Option B: Using a specific provider**\n\nInstall the provider SDK directly:\n\n```bash\n# OpenAI\nbun add @ai-sdk/openai\n\n# Anthropic\nbun add @ai-sdk/anthropic\n\n# Google\nbun add @ai-sdk/google\n```\n\nAdd your API key to `.env.development`:\n\n```env\nOPENAI_API_KEY=\"sk-...\"\n# or\nANTHROPIC_API_KEY=\"sk-ant-...\"\n```\n\n### Step 4: Create the AI config\n\nCreate a type-safe config with either-or validation using the config schema pattern:\n\n```ts\n// src/lib/ai/config.ts\nimport { configSchema, server, oneOf } from \"better-env/config-schema\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport type { LanguageModelV1 } from \"ai\";\n\nconst config = configSchema(\n  \"AI\",\n  {\n    oidcToken: server({ env: \"VERCEL_OIDC_TOKEN\" }),\n    gatewayApiKey: server({ env: \"AI_GATEWAY_API_KEY\" }),\n  },\n  {\n    constraints: (s) => [oneOf([s.oidcToken, s.gatewayApiKey])],\n  },\n);\n\nfunction createProvider() {\n  const { oidcToken, gatewayApiKey } = config.server;\n\n  if (gatewayApiKey) {\n    return createOpenAI({\n      apiKey: gatewayApiKey,\n      baseURL: \"https://api.openai.com/v1\",\n    });\n  }\n\n  return createOpenAI({\n    apiKey: oidcToken,\n    baseURL: \"https://api.vercel.ai/v1\",\n  });\n}\n\nfunction getModel(model: string): LanguageModelV1 {\n  const provider = createProvider();\n  return provider(model);\n}\n\nexport const aiConfig = {\n  ...config,\n  getModel,\n};\n```\n\nThe `oneOf` constraint creates an either-or relationship: at least one of `oidcToken` or `gatewayApiKey` must be defined. See the [Environment Variable Management](/recipes/env-management) recipe for the full config schema pattern.\n\n### Step 5: Validate config on server start\n\nImport the config in `instrumentation.ts` to validate environment variables when the server starts:\n\n```ts\n// src/instrumentation.ts\n\n// Validate required configs on server start\nimport \"./lib/ai/config\";\n```\n\nThis ensures the server fails immediately on startup if neither `VERCEL_OIDC_TOKEN` nor `AI_GATEWAY_API_KEY` is set, rather than failing later when AI features are used.\n\n---\n\n## References\n\n- [AI SDK Documentation](https://ai-sdk.dev/docs/introduction)\n- [AI SDK Providers](https://ai-sdk.dev/providers/ai-sdk-providers)\n- [Vercel AI Gateway](https://vercel.com/ai-gateway)\n- [AI Elements](https://ui.shadcn.com/docs/registry/ai-elements)\n\n---\n\n## Build a Simple Chat\n\nCreate a basic chat interface with streaming responses.\n\n### Step 1: Create the API route\n\nCreate the chat API route:\n\n```ts\n// src/app/api/chat/route.ts\nimport { convertToModelMessages, streamText, UIMessage } from \"ai\";\n\nexport const maxDuration = 30;\n\nexport async function POST(req: Request) {\n  const { messages }: { messages: UIMessage[] } = await req.json();\n\n  const result = streamText({\n    model: \"anthropic/claude-sonnet-4.5\",\n    system: \"You are a helpful assistant.\",\n    messages: await convertToModelMessages(messages),\n  });\n\n  return result.toUIMessageStreamResponse();\n}\n```\n\n> **Note**: Replace the model string with your preferred model. See the [AI SDK providers docs](https://ai-sdk.dev/providers/ai-sdk-providers) for available options.\n\n### Step 2: Create the chat page\n\nCreate the chat interface:\n\n```tsx\n// src/app/page.tsx\n\"use client\";\n\nimport { useChat } from \"@ai-sdk/react\";\nimport { DefaultChatTransport } from \"ai\";\nimport { useState } from \"react\";\n\nexport default function Page() {\n  const { messages, sendMessage, status } = useChat({\n    transport: new DefaultChatTransport({\n      api: \"/api/chat\",\n    }),\n  });\n  const [input, setInput] = useState(\"\");\n\n  return (\n    <div className=\"flex flex-col min-h-screen p-4 max-w-2xl mx-auto\">\n      <div className=\"flex-1 space-y-4 pb-4\">\n        {messages.map((message) => (\n          <div\n            key={message.id}\n            className={message.role === \"user\" ? \"text-right\" : \"text-left\"}\n          >\n            <span className=\"font-medium\">\n              {message.role === \"user\" ? \"You\" : \"AI\"}:\n            </span>{\" \"}\n            {message.parts.map((part, index) =>\n              part.type === \"text\" ? (\n                <span key={index}>{part.text}</span>\n              ) : null,\n            )}\n          </div>\n        ))}\n      </div>\n\n      <form\n        className=\"flex gap-2\"\n        onSubmit={(e) => {\n          e.preventDefault();\n          if (input.trim()) {\n            sendMessage({ text: input });\n            setInput(\"\");\n          }\n        }}\n      >\n        <input\n          className=\"flex-1 px-3 py-2 border rounded-md\"\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          disabled={status !== \"ready\"}\n          placeholder=\"Say something...\"\n        />\n        <button\n          className=\"px-4 py-2 bg-primary text-primary-foreground rounded-md disabled:opacity-50\"\n          type=\"submit\"\n          disabled={status !== \"ready\"}\n        >\n          Send\n        </button>\n      </form>\n    </div>\n  );\n}\n```\n\n### Step 3: Test your chat\n\nStart the development server:\n\n```bash\nbun run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000) and start chatting.\n\n---\n\n## References\n\n- [AI SDK Chat UI](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot)\n- [AI SDK Streaming](https://ai-sdk.dev/docs/ai-sdk-core/generating-text#streaming-text)\n- [useChat Hook](https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat)"}