Vercel AI SDK
Connect Vercel AI SDK to Mixen — pay from your balance in roubles, a catalog of 105+ models via the @ai-sdk/openai package.
Installation
Section titled “Installation”npm install ai @ai-sdk/openaiFor the UI hooks (useChat), additionally:
npm install @ai-sdk/reactConfiguration
Section titled “Configuration”Create a provider with our base URL and key:
import { createOpenAI } from '@ai-sdk/openai'import { generateText } from 'ai'
const mixen = createOpenAI({ baseURL: 'https://api.mixen.ai/v1', apiKey: process.env.MIXEN_API_KEY,})
const { text } = await generateText({ model: mixen('openai/gpt-5.6-sol'), prompt: 'Come up with a slogan for a book delivery service',})Streaming:
import { streamText } from 'ai'
const result = streamText({ model: mixen('z-ai/glm-5.3'), prompt: 'Write a short blog post about coffee',})
for await (const chunk of result.textStream) { process.stdout.write(chunk)}Key points:
- The provider is a function:
mixen('<model ID>')returns a model forgenerateText,streamTextand other AI SDK calls. - The options are exactly
baseURLandapiKey. Without them the provider falls back to the defaulthttps://api.openai.com/v1(or to theOPENAI_BASE_URL/OPENAI_API_KEYvariables). mxn-…keys are issued in the dashboard;process.envis read at process start — restart your dev server after editing.env.
Scenario: a chat UI with streaming
Section titled “Scenario: a chat UI with streaming”The typical pairing: a server route streams the model’s answer, a useChat client renders it as tokens arrive.
Server — app/api/chat/route.ts in Next.js:
import { createOpenAI } from '@ai-sdk/openai'import { streamText, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, type UIMessage,} from 'ai'
const mixen = createOpenAI({ baseURL: 'https://api.mixen.ai/v1', apiKey: process.env.MIXEN_API_KEY,})
export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json()
const result = streamText({ model: mixen('z-ai/glm-5.3'), system: 'You are a bookstore consultant. Answer briefly and to the point.', messages: await convertToModelMessages(messages), })
return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), })}Client — the chat component:
'use client'
import { useChat } from '@ai-sdk/react'import { DefaultChatTransport } from 'ai'import { useState } from 'react'
export default function Chat() { const [input, setInput] = useState('') const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }), })
return ( <div> <input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { sendMessage({ parts: [{ type: 'text', text: input }] }) setInput('') } }} /> {messages.map((message) => ( <div key={message.id}> {message.parts.map((part, i) => part.type === 'text' ? <p key={i}>{part.text}</p> : null, )} </div> ))} </div> )}What happens here:
- The client posts a UIMessage array to
/api/chat;convertToModelMessagesturns them into model messages. streamTextreturns a stream;toUIMessageStream+createUIMessageStreamResponsewrap it into the UI stream protocol —useChatconsumes it out of the box.systeminstreamTextsets the system instruction — keep it constant, a stable prefix lands in the prompt cache.- The streaming is real: deltas arrive from the model as it generates, so the UI starts printing immediately instead of waiting for the full answer.
Model choice and economics
Section titled “Model choice and economics”| Task | Model | Price per 1M tokens |
|---|---|---|
| Bulk calls: drafts, suggestions, classification | z-ai/glm-5.3-flash |
8.4 ₽ input / 27.9 ₽ output |
| Hard tasks, long context, careful code | anthropic/claude-sonnet-5 |
213.9 / 1069.5 ₽ |
A chat dialog is a perfect prompt-cache candidate: every turn repeats the system instruction and the history, and that repeating prefix is billed at ~10% of the input price (for claude-sonnet-5 — 21.4 ₽ per 1M instead of 213.9 ₽, for glm-5.3-flash — 1.7 ₽). The cache window is 5 minutes, extended by every hit, so a live dialog is cheaper than the same questions one at a time. Practice: don’t rebuild the system prompt; put the changing data into messages.
Reasoning depth is set by the API’s reasoning_effort (values off, low, medium, high, xhigh, max; supported levels are in GET /v1/models → capabilities.reasoning_efforts). Keep in mind that reasoning spends the output limit: set a generous maxOutputTokens for long outputs.
Recommended models
Section titled “Recommended models”| Model | ID |
|---|---|
| GPT-5.6 Sol | openai/gpt-5.6-sol |
| Claude Opus 5 | anthropic/claude-opus-5 |
| GLM 5.3 | z-ai/glm-5.3 |
| DeepSeek V4 Pro | deepseek/deepseek-v4-pro |
| Kimi K3 | moonshotai/kimi-k3 |
Full list — in the catalog.
Troubleshooting
Section titled “Troubleshooting”- 401 / Invalid API Key —
MIXEN_API_KEYis not set in the process environment or the key was copied incompletely; restart the app after editing.env. - Requests go to api.openai.com — a typo in the option name:
baseUrlinstead ofbaseURLis silently ignored in JavaScript, and the provider uses the default URL (TypeScript will flag it). - “model not found” — pass the model ID from the catalog verbatim, including the vendor prefix (
openai/…,anthropic/…). - useChat stays silent, no answer appears — check that the route returns
createUIMessageStreamResponseand thatapiinDefaultChatTransportpoints at it;useChatknows nothing about the provider — it only talks to your route. - The route returns text but doesn’t stream — make sure you return the stream response rather than
result.text: awaiting that property delivers the whole answer, with no stream left.