- name
- ai-core/adapter-configuration
- description
- Provider adapter selection and configuration: openaiText, anthropicText, geminiText, ollamaText, grokText, groqText, openRouterText, bedrockText, byteplusText, openaiCompatible. Per-model type safety with modelOptions, reasoning/thinking configuration, runtime adapter switching, extendAdapter() for custom models, createModel(). Generic OpenAI-compatible providers (DeepSeek, Together, Fireworks, etc.) via openaiCompatible({ baseURL, apiKey, models }) from @tanstack/ai-openai/compatible. API key env vars: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY/GEMINI_API_KEY, XAI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY, OLLAMA_HOST, BEDROCK_API_KEY (or AWS_BEARER_TOKEN_BEDROCK). BytePlus needs TWO keys: ARK_API_KEY (ModelArk — chat/video/image) and BYTEPLUS_VOICE_API_KEY (Seed Speech — TTS/transcription); neither is a fallback for the other.
- type
- sub-skill
- library
- tanstack-ai
- library_version
- 0.42.0
- sources
- ["TanStack/ai:docs/adapters/openai.md","TanStack/ai:docs/adapters/anthropic.md","TanStack/ai:docs/adapters/gemini.md","TanStack/ai:docs/adapters/ollama.md","TanStack/ai:docs/advanced/per-model-type-safety.md","TanStack/ai:docs/advanced/runtime-adapter-switching.md","TanStack/ai:docs/advanced/extend-adapter.md"]
# Adapter Configuration
> **Dependency:** This skill builds on ai-core. Read it first for critical rules.
> **Before implementing:** Ask the user which provider and model they want.
> Then fetch the latest available models from the provider's source code
> (check the adapter's model metadata file, e.g. `packages/ai-openai/src/model-meta.ts`)
> or from the provider's API/docs to recommend the most current model.
> The model lists in this skill and its reference files may be outdated.
> Always verify against the source before recommending a specific model.
## Setup
Create an adapter and use it with `chat()`:
```typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
export async function POST(request: Request) {
const { messages } = await request.json()
const stream = chat({
adapter: openaiText('gpt-5.2'),
messages,
modelOptions: {
temperature: 0.7,
max_output_tokens: 1000,
},
})
return toServerSentEventsResponse(stream)
}
```
The adapter factory function takes the model name as a string literal and an
optional config object (API key, base URL, etc.). The model name is passed
into the factory, not into `chat()`.
Sampling options (`temperature`, token limits, `top_p`/`topP`, etc.) live
inside `modelOptions` using each provider's native key — they are **not**
top-level options on `chat()`. See the per-provider table in
[Configuring Sampling](#5-configuring-sampling) below.
## Core Patterns
### 1. Adapter Selection
Each provider has a dedicated package with tree-shakeable adapter factories.
The text adapter is the primary one for chat/completions:
| Provider | Package | Factory | Env Var |
| ----------------- | -------------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------- |
| OpenAI | `@tanstack/ai-openai` | `openaiText` | `OPENAI_API_KEY` |
| Anthropic | `@tanstack/ai-anthropic` | `anthropicText` | `ANTHROPIC_API_KEY` |
| Gemini | `@tanstack/ai-gemini` | `geminiText` | `GOOGLE_API_KEY` or `GEMINI_API_KEY` |
| Grok (xAI) | `@tanstack/ai-grok` | `grokText` | `XAI_API_KEY` |
| Groq | `@tanstack/ai-groq` | `groqText` | `GROQ_API_KEY` |
| OpenRouter | `@tanstack/ai-openrouter` | `openRouterText` | `OPENROUTER_API_KEY` |
| Ollama | `@tanstack/ai-ollama` | `ollamaText` | `OLLAMA_HOST` (default: `http://localhost:11434`) |
| Bedrock | `@tanstack/ai-bedrock` | `bedrockText` | `BEDROCK_API_KEY` or `AWS_BEARER_TOKEN_BEDROCK` |
| BytePlus | `@tanstack/ai-byteplus` | `byteplusText` | `ARK_API_KEY` (falls back to `BYTEPLUS_API_KEY`) |
| OpenAI-compatible | `@tanstack/ai-openai/compatible` | `openaiCompatible` / `openaiCompatibleText` | provider-specific (passed via `apiKey`) |
| Cloudflare | `@tanstack/ai-cloudflare` | `cloudflareText` | `CLOUDFLARE_ACCOUNT_ID` + `CLOUDFLARE_API_TOKEN`, or `{ binding: env.AI }` in a Worker |
> **BytePlus uses two keys.** `byteplusText` / `byteplusVideo` /
> `byteplusImage` read `ARK_API_KEY` (ModelArk, `Authorization: Bearer`), but
> `byteplusSpeech` / `byteplusTranscription` are a separate product and read
> **`BYTEPLUS_VOICE_API_KEY`** (Seed Speech, `X-Api-Key`). Ark keys are also
> region-isolated — the default base URL is the ap-southeast endpoint.
```typescript
// Each factory takes model as first arg, optional config as second
import { openaiText, createOpenaiChat } from '@tanstack/ai-openai'
import { anthropicText } from '@tanstack/ai-anthropic'
import { geminiText } from '@tanstack/ai-gemini'
import { grokText } from '@tanstack/ai-grok'
import { groqText } from '@tanstack/ai-groq'
import { openRouterText } from '@tanstack/ai-openrouter'
import { ollamaText } from '@tanstack/ai-ollama'
import { bedrockText } from '@tanstack/ai-bedrock'
import { byteplusText } from '@tanstack/ai-byteplus'
// Model string is passed to the factory, NOT to chat()
const adapter = openaiText('gpt-5.2')
const adapter2 = anthropicText('claude-sonnet-4-6')
const adapter3 = geminiText('gemini-2.5-pro')
const adapter4 = grokText('grok-4.6')
const adapter5 = groqText('llama-3.3-70b-versatile')
const adapter6 = openRouterText('anthropic/claude-sonnet-4')
const adapter7 = ollamaText('llama3.3:latest')
const adapter8 = bedrockText('us.anthropic.claude-3-7-sonnet-20250219-v1:0')
const adapter9 = byteplusText('seed-2-0-lite-260428')
// Optional: pass an explicit API key via the create* sibling
// (the plain factory reads it from the environment)
const adapterWithKey = createOpenaiChat('gpt-5.2', 'sk-...')
```
`@tanstack/ai-bedrock` (Amazon Bedrock) branches on `config.api`:
- `bedrockText(model)` or `bedrockText(model, { api: 'converse' })` (the default) — Bedrock's native Converse API via `@aws-sdk/client-bedrock-runtime` (adapter name `bedrock-converse`). Reaches the broad catalog: Claude, Nova, Llama, Mistral, DeepSeek, and more.
- `bedrockText(model, { api: 'chat' })` — OpenAI-compatible Chat Completions endpoint (adapter name `bedrock`). Open-weight models only (gpt-oss, DeepSeek V3.x, Gemma, Qwen, etc.). Does NOT reach Claude, Nova, or Llama.
- `bedrockText(model, { api: 'responses' })` — OpenAI-compatible Responses API, mantle-only (adapter name `bedrock-responses`). Currently gpt-oss and Gemma 4.
Use `createBedrockText(model, apiKey, config?)` to pass the key explicitly. Auth resolves from `BEDROCK_API_KEY` / `AWS_BEARER_TOKEN_BEDROCK`, or SigV4 via the standard AWS credential chain (no extra packages needed — handled by `@aws-sdk/client-bedrock-runtime`).
### 2. Runtime Adapter Switching
Use an adapter factory map to switch providers dynamically based on user
input or configuration:
```typescript
import { chat, toServerSentEventsResponse } from '@tanstack/ai'
import type { ModelMessage } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { anthropicText } from '@tanstack/ai-anthropic'
import { geminiText } from '@tanstack/ai-gemini'
// Define a map of provider+model to adapter factory calls
const adapters = {
'openai/gpt-5.2': () => openaiText('gpt-5.2'),
'anthropic/claude-sonnet-4-6': () => anthropicText('claude-sonnet-4-6'),
'gemini/gemini-2.5-pro': () => geminiText('gemini-2.5-pro'),
}
function isKnownProviderModel(key: string): key is keyof typeof adapters {
return key in adapters
}
export function handleChat(
providerModel: string,
messages: Array<ModelMessage>,
) {
if (!isKnownProviderModel(providerModel)) {
throw new Error(`Unknown provider/model: ${providerModel}`)
}
const stream = chat({
adapter: adapters[providerModel](),
messages,
})
return toServerSentEventsResponse(stream)
}
```
### 3. Configuring Reasoning / Thinking
Set reasoning/thinking with the `reasoning` option on `chat()`. It is the same on every provider, and the types take only the levels of the selected model:
```typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { anthropicText } from '@tanstack/ai-anthropic'
import { geminiText } from '@tanstack/ai-gemini'
const messages = [
{ role: 'user' as const, content: 'Plan a database migration.' },
]
// OpenAI: the level goes out as reasoning.effort, with a summary
const openaiStream = chat({
adapter: openaiText('gpt-5.2'),
messages,
reasoning: { level: 'high', summary: true },
})
// Anthropic: extended thinking with a token budget
const anthropicStream = chat({
adapter: anthropicText('claude-sonnet-4-6'),
messages,
modelOptions: { max_tokens: 16000 },
reasoning: { level: 'high', budgetTokens: 8000 },
})
// Anthropic: adaptive thinking (Sonnet 5, Fable 5, Opus 4.7+) — the level
// goes out as output_config.effort
const adaptiveStream = chat({
adapter: anthropicText('claude-sonnet-5'),
messages,
modelOptions: { max_tokens: 16000 },
reasoning: 'high', // 'low' | 'medium' | 'high' | 'xhigh' | 'max'
})
// Gemini: a thinking budget on Gemini 2.5, a thinking level on Gemini 3
const geminiStream = chat({
adapter: geminiText('gemini-2.5-pro'),
messages,
reasoning: { level: 'high', budgetTokens: 4096 },
})
```
### 4. Extending Adapters with Custom Models
Use `extendAdapter()` and `createModel()` to add custom or fine-tuned models
while preserving type safety for the original models:
```typescript
import { extendAdapter, createModel } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
// Define custom models
const customModels = [
createModel('ft:gpt-5.2:my-org:custom-model:abc123', ['text', 'image']),
createModel('my-local-proxy-model', ['text']),
] as const
// Create extended factory - original models still fully typed
const myOpenai = extendAdapter(openaiText, customModels)
// Use original models - full type inference preserved
const gpt5 = myOpenai('gpt-5.2')
// Use custom models - accepted by the type system
const custom = myOpenai('ft:gpt-5.2:my-org:custom-model:abc123')
// Type error: 'nonexistent-model' is not a valid model
// myOpenai('nonexistent-model')
```
At runtime, `extendAdapter` simply passes through to the original factory.
The `_customModels` parameter is only used for type inference.
### 5. Configuring Sampling
Sampling controls (`temperature`, token limits, nucleus sampling) are passed
inside `modelOptions` using each provider's **native** key. They are not
top-level fields on `chat()`/`ai()`/`generate()`.
```typescript
import { chat } from '@tanstack/ai'
import { openaiText } from '@tanstack/ai-openai'
import { anthropicText } from '@tanstack/ai-anthropic'
import { geminiText } from '@tanstack/ai-gemini'
import { ollamaText } from '@tanstack/ai-ollama'
const messages = [{ role: 'user' as const, content: 'Hello' }]
// OpenAI — native keys
chat({
adapter: openaiText('gpt-5.2'),
messages,
modelOptions: { temperature: 0.7, top_p: 0.9, max_output_tokens: 1000 },
})
// Anthropic
chat({
adapter: anthropicText('claude-sonnet-4-6'),
messages,
modelOptions: { temperature: 0.7, top_p: 0.9, max_tokens: 1000 },
})
// Gemini — camelCase
chat({
adapter: geminiText('gemini-2.5-pro'),
messages,
modelOptions: { temperature: 0.7, topP: 0.9, maxOutputTokens: 1000 },
})
// Ollama — NESTED under modelOptions.options
// (use the `family:tag` id — a bare `llama3.3` falls back to untyped options)
chat({
adapter: ollamaText('llama3.3:latest'),
messages,
modelOptions: {
options: { temperature: 0.7, top_p: 0.9, num_predict: 1000 },
},
})
```
Per-provider sampling keys (all live inside `modelOptions`):
| Provider | Temperature | Nucleus | Max output tokens |
| ----------------- | ------------- | ------- | ----------------------------------------- |
View on GitHub