# Thinking & Reasoning

Some models expose their internal reasoning as "thinking" content -- Claude with extended thinking, OpenAI o-series models with reasoning, and others. TanStack AI captures this as `ThinkingPart` in messages, streamed to your UI in real-time alongside text and tool calls.

Unsigned thinking stays in the UI. Signed thinking is a `ThinkingPart` with a `signature`. Anthropic extended thinking uses this. The next request sends signed thinking back in the same order as the original response, including around provider-executed tools. The next-turn body puts that signature on spec `encryptedValue` on the `role: "reasoning"` fan-out. Stream events use `REASONING_ENCRYPTED_VALUE`.

## How It Works

Read the `ThinkingPart` in `message.parts`. Thinking content comes from `REASONING_*` events and from `REASONING_ENCRYPTED_VALUE`. `STEP_STARTED` and `STEP_FINISHED` only carry `stepName`.

```typescript
interface ThinkingPart {
  type: "thinking";
  content: string;
  stepId?: string;
  signature?: string;
  redacted?: boolean;
}
```

The `ThinkingPart` appears in `UIMessage.parts` alongside `TextPart` and `ToolCallPart` entries. As reasoning tokens arrive, its `content` accumulates token by token.

Claude can also send a redacted thinking block. It is encrypted, so it has no text. It arrives as a `ThinkingPart` with `redacted: true`, an empty `content`, and the encrypted data in `signature`. Keep the part in your stored messages: the next request sends it back to Claude unchanged. In a UI, show a short placeholder such as "Thinking hidden" instead of the empty text.

## Turn thinking on

Set `reasoning` on `chat()`. It works the same on every provider:

```typescript
import {
  chat,
  chatParamsFromRequest,
  toServerSentEventsResponse,
} from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";

export async function POST(request: Request) {
  const { messages, threadId, runId } = await chatParamsFromRequest(request);
  const stream = chat({
    adapter: anthropicText("claude-sonnet-5-5"),
    messages,
    threadId,
    runId,
    reasoning: "high",
  });
  return toServerSentEventsResponse(stream);
}
```

The thinking text streams back as `ThinkingPart`s. Some models think without sending text back. With those, the part stays empty.

Want the model to think, but keep the text out of your UI? Set `summary: false`:

```typescript
import { chat } from "@tanstack/ai";
import { openaiText } from "@tanstack/ai-openai";

const stream = chat({
  adapter: openaiText("gpt-6.1-sol"),
  messages: [{ role: "user", content: "Plan a database migration." }],
  reasoning: { level: "high", summary: false },
});
```

Then no thinking text streams. See [Reasoning](./reasoning) for the levels and token budgets.

## Rendering in React

Thinking parts appear in `message.parts` just like text and tool calls. A common pattern is to render them in a collapsible element so they don't dominate the UI:

```tsx
import type { UIMessage } from "@tanstack/ai-react";

function MessageContent({ message }: { message: UIMessage }) {
  return (
    <div>
      {message.parts.map((part, idx) => {
        if (part.type === "thinking") {
          return (
            <details key={idx}>
              <summary>Thinking...</summary>
              <pre style={{ whiteSpace: "pre-wrap" }}>{part.content}</pre>
            </details>
          );
        }
        if (part.type === "text") {
          return <p key={idx}>{part.content}</p>;
        }
        return null;
      })}
    </div>
  );
}
```

## Streaming Behavior

Thinking content streams **before** the final text response. As reasoning tokens arrive, `ThinkingPart.content` accumulates token by token, the same way `TextPart.content` does for the response text.

The typical streaming order is:

1. Reasoning starts (`REASONING_START` / `REASONING_MESSAGE_START`). Encrypted blobs use `REASONING_ENCRYPTED_VALUE`.
2. Reasoning tokens stream in (`REASONING_MESSAGE_CONTENT`) and accumulate into `ThinkingPart.content`.
3. `TEXT_MESSAGE_START` starts the visible response.
4. `TEXT_MESSAGE_CONTENT` streams the response text.

`STEP_STARTED` and `STEP_FINISHED` only carry `stepName`. They do not carry thinking text.

If you use `useChat` from `@tanstack/ai-react` (or the Solid/Vue/Svelte equivalents), your `messages` array updates with both thinking and text parts as they arrive.

## Thinking and tool calls in order

Claude can think, call a tool, think again, then call another tool. You want your UI to show that order, not all the thinking in one lump at the top.

You get it by default. `message.parts` keeps the order the model used. For that answer, the part types are:

```text
thinking, tool-call, thinking, tool-call
```

What this means for your UI:

- Each thinking block is its own `ThinkingPart`. Each one gets its own `REASONING_MESSAGE_END` and `REASONING_END`.
- Text after a second thinking block starts a new text part. You get "A" and "B" as two parts, not "AB".
- The render loop from [Rendering in React](#rendering-in-react) already walks the parts in order. You do not need extra code.

The order also goes back to the model. The stored message keeps a `blockOrder` map, and the next request sends the blocks in the same order. Claude needs this, because it signs each thinking block against the blocks before it.

Gemini, Mistral, and Bedrock Converse also send thinking back on the next request, for history from the same model. History from another model goes in as plain text. See [Keep saved history when you switch](../advanced/runtime-adapter-switching#keep-saved-history-when-you-switch).

## Next Steps

- [Reasoning](./reasoning) -- Levels, budgets, and the levels of each model
- [Streaming](./streaming) -- Connection adapters and stream events
- [Agentic Cycle](./agentic-cycle) -- How thinking interacts with tool-calling loops
- [Anthropic Adapter](../adapters/anthropic) -- Full Anthropic provider options
- [OpenAI Adapter](../adapters/openai) -- Full OpenAI provider options
