agent-builder-vercel-sdk

Vercel AI SDK Agent Builder

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "agent-builder-vercel-sdk" with this command: npx skills add fernandofuc/nextjs-claude-setup/fernandofuc-nextjs-claude-setup-agent-builder-vercel-sdk

Vercel AI SDK Agent Builder

Purpose

Create streaming AI chat interfaces with minimal code using Vercel AI SDK and OpenRouter provider.

When to Use

  • Building Next.js frontend with chat UI

  • Need streaming responses with SSE

  • Want type-safe tool calling in TypeScript

  • Switching between multiple AI providers

  • Building agentic loops with stopWhen/prepareStep

Quick Start

Installation

npm install ai @openrouter/ai-sdk-provider zod

Environment Variables

OPENROUTER_API_KEY=sk-or-v1-... NEXT_PUBLIC_SITE_URL=http://localhost:3000

Backend Setup (Route Handler)

Basic Chat Endpoint

// app/api/chat/route.ts import { OpenRouter } from '@openrouter/ai-sdk-provider' import { streamText } from 'ai'

const openrouter = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })

export async function POST(req: Request) { const { messages } = await req.json()

const result = streamText({ model: openrouter('openai/gpt-4o'), system: 'You are a helpful assistant', messages, })

return result.toDataStreamResponse() }

With Tool Calling

import { z } from 'zod' import { tool } from 'ai'

const tools = { generateImage: tool({ description: 'Generate images using AI', parameters: z.object({ prompt: z.string().describe('Image description'), numImages: z.number().min(1).max(10).default(1) }), execute: async ({ prompt, numImages }) => { // Your implementation const images = await generateImages(prompt, numImages) return { images } } }) }

export async function POST(req: Request) { const { messages } = await req.json()

const result = streamText({ model: openrouter('openai/gpt-4o'), system: 'You are a helpful assistant', messages, tools, maxSteps: 5 // Enable agentic loop })

return result.toDataStreamResponse() }

Frontend Integration

Using useChat Hook

'use client'

import { useChat } from 'ai/react'

export default function Chat() { const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()

return ( <div className="flex flex-col h-screen"> {/* Messages */} <div className="flex-1 overflow-y-auto p-4"> {messages.map(m => ( <div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}> <div className="inline-block p-3 rounded-lg"> {m.content} </div> </div> ))} </div>

  {/* Input */}
  &#x3C;form onSubmit={handleSubmit} className="p-4 border-t">
    &#x3C;input
      value={input}
      onChange={handleInputChange}
      placeholder="Type a message..."
      disabled={isLoading}
      className="w-full px-4 py-2 border rounded"
    />
  &#x3C;/form>
&#x3C;/div>

) }

With Tool Results Display

'use client'

import { useChat } from 'ai/react'

export default function ChatWithTools() { const { messages, input, handleInputChange, handleSubmit } = useChat()

return ( <div> {messages.map(m => ( <div key={m.id}> {m.content}

      {/* Display tool calls */}
      {m.toolInvocations?.map(tool => (
        &#x3C;div key={tool.toolCallId} className="bg-gray-100 p-2 rounded">
          &#x3C;strong>{tool.toolName}&#x3C;/strong>
          {tool.state === 'result' &#x26;&#x26; (
            &#x3C;pre>{JSON.stringify(tool.result, null, 2)}&#x3C;/pre>
          )}
        &#x3C;/div>
      ))}
    &#x3C;/div>
  ))}

  &#x3C;form onSubmit={handleSubmit}>
    &#x3C;input value={input} onChange={handleInputChange} />
  &#x3C;/form>
&#x3C;/div>

) }

Advanced Patterns

Multi-Step Agentic Loop

const result = streamText({ model: openrouter('openai/gpt-4o'), messages, tools, maxSteps: 5,

// Control loop behavior onStepFinish: ({ stepType, text, toolCalls }) => { console.log(Step finished: ${stepType}) },

// Stop condition experimental_continueSteps: true })

Custom Streaming with streamUI

import { streamUI } from 'ai/rsc'

export async function generateUI(prompt: string) { const result = streamUI({ model: openrouter('openai/gpt-4o'), prompt, text: ({ content }) => <p>{content}</p>, tools: { showImage: { description: 'Display an image', parameters: z.object({ url: z.string() }), generate: async ({ url }) => <img src={url} /> } } })

return result.value }

tldraw Agent Pattern

Based on: /Users/danielcarreon/Documents/AI/software/tldraw-agent/

// Incremental JSON parsing pattern async function* streamActions(model, prompt) { const { textStream } = streamText({ model, system: systemPrompt, messages, maxOutputTokens: 8192, temperature: 0 })

let buffer = '{"actions": [{"_type":'

for await (const text of textStream) { buffer += text

// Parse incremental JSON
const partialObject = closeAndParseJson(buffer)
if (!partialObject) continue

const actions = partialObject.actions
if (!Array.isArray(actions)) continue

// Yield actions as they complete
for (const action of actions) {
  if (action.complete) {
    yield action
  }
}

} }

OpenRouter Provider Setup

import { OpenRouter } from '@openrouter/ai-sdk-provider'

const openrouter = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY, // Optional: customize baseURL: 'https://openrouter.ai/api/v1', headers: { 'HTTP-Referer': process.env.NEXT_PUBLIC_SITE_URL, 'X-Title': 'My App' } })

// Use different models const gpt4 = openrouter('openai/gpt-4o') const claude = openrouter('anthropic/claude-3-5-sonnet') const gemini = openrouter('google/gemini-2.0-flash-exp')

Error Handling

export async function POST(req: Request) { try { const { messages } = await req.json()

const result = streamText({
  model: openrouter('openai/gpt-4o'),
  messages,
  onError: (error) => {
    console.error('Stream error:', error)
  }
})

return result.toDataStreamResponse()

} catch (error) { return new Response( JSON.stringify({ error: error.message }), { status: 500 } ) } }

Testing

import { streamText } from 'ai' import { OpenRouter } from '@openrouter/ai-sdk-provider'

describe('Chat API', () => { it('should stream response', async () => { const openrouter = new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })

const result = streamText({
  model: openrouter('openai/gpt-4o'),
  prompt: 'Say hello'
})

const chunks = []
for await (const chunk of result.textStream) {
  chunks.push(chunk)
}

expect(chunks.length).toBeGreaterThan(0)

}) })

Best Practices

  • Type Safety: Use Zod for tool parameters

  • Error Boundaries: Wrap chat UI in ErrorBoundary

  • Loading States: Show loading UI during streaming

  • Optimistic Updates: Update UI before server response

  • Tool Results: Display tool executions to user

  • Rate Limiting: Implement rate limits on API routes

  • Context Management: Limit message history to avoid token overflow

Common Patterns

Image Generation Agent

const tools = { generateAvatar: tool({ description: 'Generate avatar with DANI identity', parameters: z.object({ prompt: z.string(), numImages: z.number().default(3) }), execute: async ({ prompt, numImages }) => { const response = await fetch('/api/generate', { method: 'POST', body: JSON.stringify({ prompt, numImages }) }) return await response.json() } }),

combineImages: tool({ description: 'Combine multiple images', parameters: z.object({ imageUrls: z.array(z.string()), prompt: z.string() }), execute: async ({ imageUrls, prompt }) => { // Nano Banana integration return await combineWithNanoBanana(imageUrls, prompt) } }) }

Resources

  • Vercel AI SDK Docs

  • OpenRouter Provider

  • Tool Calling Guide

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

agent-builder-pydantic-ai

No summary provided by upstream source.

Repository SourceNeeds Review
General

nextjs-16-complete-guide

No summary provided by upstream source.

Repository SourceNeeds Review
General

replicate-integration

No summary provided by upstream source.

Repository SourceNeeds Review