AI
AI setup
Configure Vercel AI SDK models in the VitNode API config, declare what each model can do and what it costs, then use them with c.get("ai").
VitNode integrates the Vercel AI SDK. Register models in vitnode.api.config.ts once, then use them inside any API handler via c.get("ai").
Quick start
1. Pick a provider
A model is either a provider instance from an AI SDK provider package or an AI Gateway id string such as 'openai/gpt-4o-mini'.
- Provider package. Install it in your API app and set its key, for example
OPENAI_API_KEYfor@ai-sdk/openai.
bun i @ai-sdk/openai- AI Gateway. Install nothing. The AI SDK sends id strings through the Vercel AI Gateway, which reads
AI_GATEWAY_API_KEYfrom your.envfile.
2. Configure models in the API config
import { openai } from '@ai-sdk/openai'
import { buildApiConfig } from '@vitnode/core/vitnode.config'
export const vitNodeApiConfig = buildApiConfig({
ai: {
models: [
{
id: 'default',
name: 'GPT-4o Mini',
model: openai('gpt-4o-mini'), // or the gateway id 'openai/gpt-4o-mini'
capabilities: ['text', 'structured-output', 'streaming'],
pricing: {
rates: { inputPerMillion: '0.15', outputPerMillion: '0.60' },
},
},
],
},
})capabilities and pricing are optional, but AI actions rely on them. Capabilities decide which model can run an action, and pricing turns token usage into money. See Model capabilities and Costs and budgets.
3. Use it in a route handler
For features people can trigger, run a registered AI action. It checks permissions, limits and budgets, and records the cost:
handler: async (c) => {
const { output } = await c.get('ai').run({
action: SUMMARIZE_TOPIC_AI_ACTION,
input: c.req.valid('json'),
})
return c.json({ text: output }, 200)
}SUMMARIZE_TOPIC_AI_ACTION is the action key you create in AI actions. For internal tooling, you can also resolve the raw model and call the SDK directly. See Direct SDK calls.
Model capabilities
A capability says what a configured language model can do. VitNode never guesses it from the model's name. A model without capabilities is treated as ["text"].
| Capability | Meaning |
|---|---|
text | Text in, text out. |
image-input | Accepts images in the prompt (vision). |
structured-output | Returns reliable JSON objects (Output.object). |
streaming | Streams tokens. Required by c.get("ai").stream() and Quick Ask. |
Every AI action declares the capabilities it needs. When a run starts, VitNode picks the first configured model that has all of them. If no model fits, the run fails with AI_MODEL_INCOMPATIBLE before anything is sent to a provider.
image-input means the model can read images, which features such as image
ALT text need. Generating images is a different thing: register those models
under imageModels and resolve them with c.get("ai").imageModel(). Add
image-input only to models whose provider docs say they accept images.
Otherwise VitNode keeps images away from them.
Model pricing
pricing is what the model costs on this connection, in USD per one million tokens. Prices are decimal strings, so money never goes through floating point:
{
id: 'default',
name: 'Claude Sonnet',
model: anthropic('claude-sonnet-4-5'),
capabilities: ['text', 'image-input', 'structured-output', 'streaming'],
pricing: {
rates: {
inputPerMillion: '3',
outputPerMillion: '15',
cacheReadPerMillion: '0.30',
cacheWritePerMillion: '3.75',
},
},
}Prices live only in this config. The AdminCP has no price editor, so a price change is a code change you deploy. A malformed price stops the server at boot.
Without pricing, a call's cost stays unknown, never zero. Under a budget cap, a run on a model with no pricing fails with AI_PRICING_MISSING. Costs and budgets covers tiers, flat fees and gateway vs direct pricing.
Model options
Prop
Type
ai also accepts providerAdapters: readers for providers that report their own cost. The AI Gateway and OpenRouter readers are built in. See Provider-reported cost.
Multiple models & embeddings
Configure dedicated models for fast completions, reasoning, and vector embeddings:
ai: {
models: [
{
id: 'default',
name: 'GPT-4o Mini',
model: openai('gpt-4o-mini'),
capabilities: ['text', 'structured-output', 'streaming'],
},
{
id: 'reasoning',
name: 'Claude Sonnet',
model: 'anthropic/claude-sonnet-5', // AI Gateway id
capabilities: ['text', 'image-input', 'structured-output', 'streaming'],
},
],
embeddingModels: [
{ id: 'default', name: 'Text Embedding 3', model: openai.embedding('text-embedding-3-small') },
],
}Resolve specific models by ID:
const smartModel = c.get('ai').model('reasoning')
const embedModel = c.get('ai').embeddingModel()
const capabilities = c.get('ai').capabilities('reasoning') // ['text', 'image-input', ...]embeddingModel() throws when no embeddingModels are configured, and imageModel() when no imageModels are. Admins can assign a different model (and a fallback) to each action in the AdminCP. Core refuses an assigned model that lacks a required capability.
Reading the model list in the browser
Provider instances, API keys and prices stay on the server. c.get("ai").models() returns only the public half of each entry:
| Field | Value |
|---|---|
id | The configured id, e.g. "default". |
name | The configured label. |
model | The provider's model id string, e.g. "gpt-4o-mini". |
provider | "gateway" for AI Gateway ids, otherwise the SDK model's provider. |
capabilities | The declared capabilities, or ["text"]. |
The middleware route publishes this list, and the browser reads it with
useMiddlewareConfigQuery():
import { useMiddlewareConfigQuery } from '@vitnode/core/tanstack/auth'
export const ModelPicker = () => {
const { data } = useMiddlewareConfigQuery()
if (data.ai.models.length === 0) return <p>AI is not configured.</p>
return (
<select className="w-full">
{data.ai.models.map((model) => (
<option key={model.id} value={model.id}>
{model.name}
</option>
))}
</select>
)
}The first entry is the default, which c.get("ai").model() resolves when a
handler gets no id. The list is empty when no ai.models are configured, so
a picker can hide itself instead of offering nothing.
Learn more
AI actions
Build your first managed AI feature with permissions, limits and history
Field suggestions
An AI button on Content Engine text fields, with no code of your own
Quick Ask
AI rewrites in the rich text editor
Costs and budgets
Pricing, AI points, budgets and what happens on failure
AI in the AdminCP
Budgets, actions, role access, history and the member's /settings/ai page
Direct SDK calls
generateText, streamText, structured output and embeddings