- OpenAIUpdated
- OpenAI: AdvancedNew
- AnthropicUpdated
- Anthropic: AdvancedNew
- Google GeminiUpdated
- Google Gemini: AdvancedNew
- Google Vertex AIUpdated
- OllamaUpdated
- Grok (xAI)Updated
- GroqUpdated
- MistralUpdated
- CohereUpdated
- TypeSafeUpdated
- OllayaUpdated
- ElevenLabsUpdated
- fal.aiUpdated
- ReactorUpdated
- World LabsUpdated
- OpenRouter AdapterUpdated
- OpenRouter: AdvancedNew
- Perplexity SearchUpdated
- Vercel AI GatewayUpdated
- Lovable AI GatewayUpdated
- OpenAI-CompatibleUpdated
- OpenAI-Compatible: AdvancedNew
- LLM GatewayUpdated
- CloudflareUpdated
- Claude CodeUpdated
- CodexUpdated
- OpenCodeUpdated
- Grok BuildUpdated
- ACP-CompatibleUpdated
- Amazon BedrockUpdated
- BytePlusUpdated
- BytePlus: AdvancedNew
OpenAI-Compatible Adapter
openaiCompatible calls any server that speaks /chat/completions and has no @tanstack/ai-* package. Set baseURL and the model list, then call chat().
openaiCompatible is one generic adapter for all of them. Point it at the baseURL, list your models, and you get the same type-safe chat() as with the other adapters.
If a dedicated adapter exists, use it, because it has curated metadata for each model:
- OpenAI, Grok, Groq, and OpenRouter.
- Vercel AI Gateway: vercelGatewayText from @tanstack/ai-vercel-gateway. See Vercel AI Gateway.
- Lovable AI Gateway: lovableText (plus image, video, embeddings, and speech factories) from @tanstack/ai-lovable. See Lovable AI Gateway.
Perplexity Sonar chat uses this adapter. @tanstack/ai-perplexity is for search and grounding only. To send the X-Pplx-Integration attribution header of Perplexity, pass defaultHeaders: getPerplexityIntegrationHeaders() from that package.
1. Install#
The adapter is in @tanstack/ai-openai, under the /compatible subpath:
npm i @tanstack/ai-openaipnpm add @tanstack/ai-openaiyarn add @tanstack/ai-openaibun add @tanstack/ai-openai2. Set the API key#
Put the key of your provider in the environment of your server, with the variable name that you like:
DEEPSEEK_API_KEY=sk-...The adapter reads no environment variable itself. You pass the key as apiKey in the next step.
3. Stream a chat from the server#
Configure the provider one time with openaiCompatible({ baseURL, apiKey, models }). Then pick a model for each call:
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { openaiCompatible } from "@tanstack/ai-openai/compatible";
const deepseek = openaiCompatible({
name: "deepseek", // optional label for devtools and errors (default: "openai-compatible")
baseURL: "https://api.deepseek.com/v1",
apiKey: process.env.DEEPSEEK_API_KEY!,
models: ["deepseek-chat", "deepseek-reasoner"],
});
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: deepseek("deepseek-chat"),
messages,
threadId,
runId,
});
return toServerSentEventsResponse(stream);
}The model name is a type-safe union of the models that you declared. deepseek("deepseek-reasoner") is valid. deepseek("gpt-4o") is a type error.
4. Render the chat#
The client is the same for every provider. Point useChat at your route:
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
sendMessage(input);
setInput("");
}}
>
{messages.map((message) => (
<p key={message.id}>
{message.parts
.map((part) => (part.type === "text" ? part.content : ""))
.join("")}
</p>
))}
<input value={input} onChange={(event) => setInput(event.target.value)} />
</form>
);
}For Vue, Solid, Svelte, Angular, and the other frameworks, see Quick Start.
You now have a streaming chat with any OpenAI-compatible provider. Tools work the same as with every adapter, on models that support function calling.
One model only#
For a single model, skip the provider factory and build the adapter inline with openaiCompatibleText:
import { chat } from "@tanstack/ai";
import { openaiCompatibleText } from "@tanstack/ai-openai/compatible";
const stream = chat({
adapter: openaiCompatibleText("deepseek-chat", {
baseURL: "https://api.deepseek.com/v1",
apiKey: process.env.DEEPSEEK_API_KEY!,
}),
messages: [{ role: "user", content: "Hello!" }],
threadId: "thread-1",
});Local and self-hosted servers#
Point the adapter at any local OpenAI-compatible server. The API key is usually a placeholder:
import { openaiCompatible } from "@tanstack/ai-openai/compatible";
// LM Studio
const lmstudio = openaiCompatible({
name: "lmstudio",
baseURL: "http://localhost:1234/v1",
apiKey: "lm-studio",
models: ["local-model"],
});
// vLLM
const vllm = openaiCompatible({
name: "vllm",
baseURL: "http://localhost:8000/v1",
apiKey: "not-needed",
models: ["meta-llama/Llama-3.3-70B-Instruct"],
});
// The OpenAI-compatible endpoint of Ollama
const ollama = openaiCompatible({
name: "ollama",
baseURL: "http://localhost:11434/v1",
apiKey: "ollama",
models: ["llama3.3"],
});Ollama also has a dedicated adapter, @tanstack/ai-ollama, for its native API. If you want the OpenAI-compatible surface of Ollama, use openaiCompatible.
LiteLLM proxy#
LiteLLM is a self-hosted gateway. It gives one OpenAI Chat Completions endpoint for 100+ providers (OpenAI, Anthropic, Google, Azure, AWS Bedrock, Mistral, Groq, and more). Point openaiCompatible at your proxy (default http://localhost:4000/v1). Pick a provider with the provider/model names of LiteLLM:
import { openaiCompatible } from "@tanstack/ai-openai/compatible";
const litellm = openaiCompatible({
name: "litellm",
baseURL: "http://localhost:4000/v1", // your LiteLLM proxy
apiKey: process.env.LITELLM_API_KEY!, // a virtual key issued by the proxy
models: [
"anthropic/claude-sonnet-5",
"openai/gpt-5.5",
"gemini/gemini-3.5-flash",
],
});- litellm("anthropic/claude-sonnet-5") goes to Anthropic, and litellm("openai/gpt-5.5") goes to OpenAI, through the one proxy.
- Declare only the routes that you configured on the proxy. For exact capabilities of each route, use createModel. See Declaring models.
- The proxy holds the real credentials of each provider. The apiKey here is the virtual or master key of the proxy.
Models and options#
Declaring models#
The models array takes two forms. You can mix them:
- A string gets optimistic defaults: text and image input, with streaming, function_calling, and structured_outputs. Good for mainstream chat models.
- A createModel(name, capabilities) definition declares the exact capabilities of a model, so the types match the model. An example is a reasoning model with no image input.
import { openaiCompatible } from "@tanstack/ai-openai/compatible";
import { createModel } from "@tanstack/ai";
const provider = openaiCompatible({
baseURL: "https://api.deepseek.com/v1",
apiKey: process.env.DEEPSEEK_API_KEY!,
models: [
"deepseek-chat", // string: optimistic defaults
createModel("deepseek-reasoner", {
input: ["text"], // text only
features: ["reasoning", "structured_outputs"],
}),
],
});The types enforce the input modalities, the modelOptions, and the provider tools you declare. They do not read features. Function tools and structured output are always allowed on this path. If a provider rejects one of them at run time (for example, tools on a model without tool support), do not pass it for that model.
Providers#
Any provider with the OpenAI Chat Completions API works. Common ones are below. Check the baseURL and the model ids in the current docs of each provider, because they change.
| Provider | baseURL | Example model |
|---|---|---|
| DeepSeek | https://api.deepseek.com/v1 | deepseek-chat, deepseek-reasoner |
| Moonshot / Kimi | https://api.moonshot.ai/v1 | kimi-k2-0711-preview |
| Alibaba Qwen (DashScope, intl) | https://dashscope-intl.aliyuncs.com/compatible-mode/v1 | qwen-max, qwen-plus |
| Alibaba Qwen (DashScope, China) | https://dashscope.aliyuncs.com/compatible-mode/v1 | qwen-max |
| Together AI | https://api.together.xyz/v1 | meta-llama/Llama-3.3-70B-Instruct-Turbo |
| Fireworks AI | https://api.fireworks.ai/inference/v1 | accounts/fireworks/models/llama-v3p3-70b-instruct |
| Cerebras | https://api.cerebras.ai/v1 | llama-3.3-70b |
| DeepInfra | https://api.deepinfra.com/v1/openai | meta-llama/Llama-3.3-70B-Instruct |
| Perplexity | https://api.perplexity.ai | sonar, sonar-pro |
| Mistral | https://api.mistral.ai/v1 | mistral-large-latest |
| Nebius | https://api.studio.nebius.ai/v1 | meta-llama/Llama-3.3-70B-Instruct |
| Z.AI (GLM) | https://api.z.ai/api/paas/v4 | glm-4.6 |
| Baseten | https://inference.baseten.co/v1 | depends on the model |
| Hugging Face (router) | https://router.huggingface.co/v1 | meta-llama/Llama-3.3-70B-Instruct |
| NVIDIA NIM | https://integrate.api.nvidia.com/v1 | meta/llama-3.3-70b-instruct |
| Kilo Gateway | https://api.kilo.ai/api/gateway | anthropic/claude-opus-5.5 |
| ZenMux | https://zenmux.ai/api/v1 | openai/gpt-5.5 |
| Poe | https://api.poe.com/v1 | openai/gpt-5.5 |
| DigitalOcean | https://inference.do-ai.run/v1 | openai-gpt-5.5 |
| Modal | https://inference.us-west.modal.direct/v1 | moonshotai/Kimi-K3 |
| Snowflake Cortex | https://<account>.snowflakecomputing.com/api/v2/cortex/v1 | claude-opus-5 |
- Some providers sign the user in with OAuth, not with an API key. For Poe, DigitalOcean, and Snowflake Cortex, pass the access token from your sign-in code as apiKey.
- On Chat Completions, RUN_FINISHED.usage carries the prompt cache counts that the provider reports. Cache reads go to promptTokensDetails.cachedTokens, and cache writes go to promptTokensDetails.cacheWriteTokens. Moonshot / Kimi reports both.
Advanced#
These topics are on OpenAI-Compatible: Advanced:
- Configuration: headers, query parameters, and wrapFetch.
- The Responses API (api: "responses").
- Azure OpenAI.
- GitHub Copilot: let users pay with their Copilot plan.
- ChatGPT plan: let users pay with their ChatGPT plan.
To add custom models to any adapter, see Extending Adapters.