Skip to content
Chat & Streaming

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

ts
// 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:

tsx
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:

ts
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.
ts
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:

LevelShared default budget
minimal1024 tokens
low2048 tokens
medium8192 tokens
high, xhigh, max16384 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:

ts
import type { ChatMiddleware } from "@tanstack/ai";

const lowEffort: ChatMiddleware = {
  name: "low-effort",
  onConfig: () => ({ reasoning: { level: "low", summary: true } }),
};

What each provider gets

AdapterSent as
OpenAI, Azure OpenAI, Grok, Bedrock Responses, Neon Responsesreasoning: { effort, summary }
OpenAI Chat Completions, Groq, LLM Gateway, Lovable, Bedrock chat, Cloudflare, Neon Chat Completionsreasoning_effort
Anthropic, Neon Claudeadaptive thinking with the effort, or a thinking budget
GeminithinkingConfig.thinkingLevel or thinkingConfig.thinkingBudget
OpenRouter, Vercel AI Gatewaythe gateway's reasoning object
Mistralreasoning_effort or prompt_mode
BytePlusthinking.type and reasoning_effort
Ollamathink
Bedrock ConverseClaude thinking fields
Claude Code, Codex, OpenCodethe CLI or server effort setting

On 0.x with reasoning fields in modelOptions? See Moving reasoning into chat().