- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- Build your first harnessUpdated
- Host optionsNew
- Send and show mediaUpdated
- Connect clientsUpdated
- Write a client in any languageNew
- Access and tenantsNew
- Run in the terminalUpdated
- Deploy a harnessUpdated
- Send inputs safelyUpdated
- Run a tool without a model
- Durable sessionsUpdated
- Run background work on workersNew
- List, rename, and fork sessionsUpdated
- Lifecycle and retentionNew
- Run side effects onceUpdated
- Wait for a personNew
- Build on the session logUpdated
- Control how a turn endsUpdated
- Store settings per threadUpdated
- Compact a long sessionUpdated
- Fork and reset a threadUpdated
- One log for a session treeUpdated
- Write a pluginUpdated
- Track usage and costUpdated
- Ask before risky tool callsUpdated
- Give your agent coding toolsUpdated
- Sandbox and extend the coding toolsUpdated
- Run the coding tools for many threadsNew
- Undo a turnUpdated
- Build a coding agentUpdated
- Delegate to coding agentsUpdated
- Switch between agent profilesUpdated
- Auth and connectorsUpdated
- Share a thread between peopleUpdated
- Connect model providersUpdated
- Build your own UIUpdated
- Code mode in a harnessUpdated
- Work until a goal is metUpdated
- Run agents from a harnessUpdated
- Hand off a conversationNew
- Add skillsUpdated
- Use MCP serversUpdated
- Give your agent a computerNew
- Use from any MCP clientUpdated
- Use a harness from an editor (ACP)Updated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Keep Keys on the ServerNew
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Reasoning
Pass reasoning to chat(). The adapter maps that level onto the provider setting, such as OpenAI reasoning.effort or Anthropic thinking.
Set reasoning on chat(). The adapter sends the right field for the model.
Quick setup#
1. Set a level on the server#
// api/chat.ts
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: openaiText("gpt-6.1-sol"),
messages,
threadId,
runId,
reasoning: "high",
});
return toServerSentEventsResponse(stream);
}2. Show the thinking on the client#
The thinking text arrives as thinking parts in message.parts, next to the text parts:
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const { messages } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
});
return messages.map((message) => (
<div key={message.id}>
{message.parts.map((part, index) => {
if (part.type === "thinking") {
return (
<details key={index}>
<summary>Thinking</summary>
<pre>{part.content}</pre>
</details>
);
}
return part.type === "text" ? <p key={index}>{part.content}</p> : null;
})}
</div>
));
}Send a hard question. A "Thinking" box fills first, then the answer streams below it.
Some models think without sending any text back. For those models, the thinking part stays empty. See Thinking & Reasoning for the order of the parts and for signed thinking.
How it works#
The levels go from least to most thinking:
off, minimal, low, medium, high, xhigh, max
If you leave reasoning out, the provider default applies.
Your editor knows the levels of the model#
Each model has only some of the levels. The types accept only the levels of the model you picked:
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
chat({
adapter: anthropicText("claude-opus-5-5"),
messages: [{ role: "user", content: "Hello" }],
threadId: "thread-1",
reasoning: "max",
});
chat({
adapter: anthropicText("claude-haiku-4-5"),
messages: [{ role: "user", content: "Hello" }],
threadId: "thread-1",
// @ts-expect-error claude-haiku-4-5 has no max level
reasoning: "max",
});A model that does not reason takes no reasoning at all. Each adapter page lists the levels of its models.
A level that the model does not have#
A level can come from a settings menu or from middleware, so the types cannot always catch it. At run time, the adapter moves the level to the nearest level that the model has. It looks up first, then down.
For example, minimal on claude-opus-5-5 goes out as low. max on gemini-3.8-flash goes out as high.
Advanced#
Hide the thinking or set a budget#
Pass an object for more control:
- level: the level.
- summary: false keeps the thinking text out of the stream. The default is true.
- budgetTokens: an exact thinking budget. Only budget models take it, for example Claude Haiku 4.5 and Gemini 2.5 Pro.
import { chat } from "@tanstack/ai";
import { geminiText } from "@tanstack/ai-gemini";
const stream = chat({
adapter: geminiText("gemini-2.5-pro"),
messages: [{ role: "user", content: "Check this proof." }],
threadId: "thread-1",
reasoning: { level: "high", budgetTokens: 8000 },
});If you do not set budgetTokens, a budget model gets a budget from the level:
| Level | Shared default budget |
|---|---|
| minimal | 1024 tokens |
| low | 2048 tokens |
| medium | 8192 tokens |
| high, xhigh, max | 16384 tokens |
Gemini 2.5 uses its own budgets. For example, high is 32768 tokens on gemini-2.5-pro and 24576 tokens on gemini-2.5-flash.
Change the level in middleware#
Middleware can set the level for one model call. For example, think less when the run retries:
import type { ChatMiddleware } from "@tanstack/ai";
const lowEffort: ChatMiddleware = {
name: "low-effort",
onConfig: () => ({ reasoning: { level: "low", summary: true } }),
};What each provider gets#
| Adapter | Sent as |
|---|---|
| OpenAI, Azure OpenAI, Grok, Bedrock Responses, Neon Responses | reasoning: { effort, summary } |
| OpenAI Chat Completions, Groq, LLM Gateway, Lovable, Bedrock chat, Cloudflare, Neon Chat Completions | reasoning_effort |
| Anthropic, Neon Claude | adaptive thinking with the effort, or a thinking budget |
| Gemini | thinkingConfig.thinkingLevel or thinkingConfig.thinkingBudget |
| OpenRouter, Vercel AI Gateway | the gateway's reasoning object |
| Mistral | reasoning_effort or prompt_mode |
| BytePlus | thinking.type and reasoning_effort |
| Ollama | think |
| Bedrock Converse | Claude thinking fields |
| Claude Code, Codex, OpenCode | the CLI or server effort setting |
On 0.x with reasoning fields in modelOptions? See Moving reasoning into chat().