TanStack
Chat & Streaming

Reasoning

Every provider has its own knob for thinking: reasoning.effort on OpenAI, thinking on Anthropic, thinkingConfig on Gemini, think on Ollama. You do not want to learn all of them. Set reasoning on chat() and the adapter sends the right field for the model.

Set a level

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 levels go from least to most thinking:

off, minimal, low, medium, high, xhigh, max

Leave reasoning out and the provider default applies.

Your editor knows the model's levels

Each model has only some of the levels. The types take 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" }],
  reasoning: "max",
});

chat({
  adapter: anthropicText("claude-haiku-4-5"),
  messages: [{ role: "user", content: "Hello" }],
  // @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 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 it 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, and max on gemini-3.8-flash goes out as high.

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, such as 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." }],
  reasoning: { level: "high", budgetTokens: 8000 },
});

No budgetTokens? A budget model gets a budget from the level. The shared default is 1024 tokens for minimal, 2048 for low, 8192 for medium, and 16384 for high and up. Gemini 2.5 uses its own budgets: for example, high is 32768 on gemini-2.5-pro and 24576 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 } }),
};

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"),
  });

  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>
  ));
}

Some models think without sending any text back. Then the part stays empty. See Thinking & Reasoning for streaming order and signed thinking.

What each provider gets

AdapterSent as
OpenAI, Azure OpenAI, Grok, Bedrock Responsesreasoning: { effort, summary }
OpenAI Chat Completions, Groq, LLM Gateway, Lovable, Bedrock chat, Cloudflarereasoning_effort
Anthropicadaptive 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

Upgrading from the old modelOptions fields? See Moving reasoning into chat().