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.
// 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);
}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.
The levels go from least to most thinking:
off, minimal, low, medium, high, xhigh, max
If you leave reasoning out, the provider default applies.
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 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.
Pass an object for more control:
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.
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 } }),
};| 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().