Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clients
- Write a client in any language
- Access and tenants
- Run in the terminal
- Deploy a harness
- Send inputs safely
- Run a tool without a model
- Durable sessions
- Run background work on workers
- List, rename, and fork sessions
- Lifecycle and retention
- Run side effects once
- Wait for a person
- Build on the session log
- Control how a turn ends
- Store settings per thread
- Compact a long session
- Fork and reset a thread
- One log for a session tree
- Write a plugin
- Track usage and cost
- Ask before risky tool calls
- Give your agent coding tools
- Sandbox and extend the coding tools
- Run the coding tools for many threads
- Undo a turn
- Build a coding agent
- Delegate to coding agents
- Switch between agent profiles
- Auth and connectors
- Share a thread between people
- Connect model providers
- Build your own UI
- Code mode in a harness
- Work until a goal is met
- Run agents from a harness
- Hand off a conversation
- Add skills
- Use MCP servers
- Give your agent a computer
- Use from any MCP client
- Use a harness from an editor (ACP)
- Overview
- Quick Start
- Providers
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files API
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-Shaking
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
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().