Skip to content
RU

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.

Terminal window
npm install ai @ai-sdk/openai

For the UI hooks (useChat), additionally:

Terminal window
npm install @ai-sdk/react

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 for generateText, streamText and other AI SDK calls.
  • The options are exactly baseURL and apiKey. Without them the provider falls back to the default https://api.openai.com/v1 (or to the OPENAI_BASE_URL / OPENAI_API_KEY variables).
  • mxn-… keys are issued in the dashboard; process.env is read at process start — restart your dev server after editing .env.

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; convertToModelMessages turns them into model messages.
  • streamText returns a stream; toUIMessageStream + createUIMessageStreamResponse wrap it into the UI stream protocol — useChat consumes it out of the box.
  • system in streamText sets 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.
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/modelscapabilities.reasoning_efforts). Keep in mind that reasoning spends the output limit: set a generous maxOutputTokens for long outputs.

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.

  • 401 / Invalid API KeyMIXEN_API_KEY is 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: baseUrl instead of baseURL is 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 createUIMessageStreamResponse and that api in DefaultChatTransport points at it; useChat knows 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.