- OpenAIUpdated
- OpenAI: AdvancedNew
- AnthropicUpdated
- Anthropic: AdvancedNew
- Google GeminiUpdated
- Google Gemini: AdvancedNew
- Google Vertex AIUpdated
- OllamaUpdated
- Grok (xAI)Updated
- GroqUpdated
- MistralUpdated
- CohereUpdated
- TypeSafeUpdated
- OllayaUpdated
- ElevenLabsUpdated
- fal.aiUpdated
- ReactorUpdated
- World LabsUpdated
- OpenRouter AdapterUpdated
- OpenRouter: AdvancedNew
- Perplexity SearchUpdated
- Vercel AI GatewayUpdated
- Lovable AI GatewayUpdated
- OpenAI-CompatibleUpdated
- OpenAI-Compatible: AdvancedNew
- LLM GatewayUpdated
- CloudflareUpdated
- Claude CodeUpdated
- CodexUpdated
- OpenCodeUpdated
- Grok BuildUpdated
- ACP-CompatibleUpdated
- Amazon BedrockUpdated
- BytePlusUpdated
- BytePlus: AdvancedNew
Anthropic
The Anthropic adapter calls Claude for chat. It supports thinking, automatic prompt caching, and tools that Anthropic runs.
1. Install#
npm i @tanstack/ai-anthropicpnpm add @tanstack/ai-anthropicyarn add @tanstack/ai-anthropicbun add @tanstack/ai-anthropic2. Set the API key#
Put the key in the environment of your server:
ANTHROPIC_API_KEY=sk-ant-...anthropicText, anthropicSummarize, and anthropicFiles use the first of these variables that is set:
- ANTHROPIC_AUTH_TOKEN: sent as a Bearer token. An sk-ant-oat token here turns on OAuth mode.
- ANTHROPIC_OAUTH_TOKEN: sent as a Bearer token, in OAuth mode.
- ANTHROPIC_API_KEY: sent as x-api-key.
If none of them is set, these factories throw. To pass the credential in code, use createAnthropicChat. The create* factories never read a credential from the environment:
import { createAnthropicChat } from "@tanstack/ai-anthropic";
const adapter = createAnthropicChat("claude-sonnet-5-5", process.env.ANTHROPIC_API_KEY!);Got a Bearer or OAuth token? See Bearer and OAuth tokens.
3. Stream a chat from the server#
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,
});
return toServerSentEventsResponse(stream);
}4. Render the chat#
The client is the same for every provider. Point useChat at your route:
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
sendMessage(input);
setInput("");
}}
>
{messages.map((message) => (
<p key={message.id}>
{message.parts
.map((part) => (part.type === "text" ? part.content : ""))
.join("")}
</p>
))}
<input value={input} onChange={(event) => setInput(event.target.value)} />
</form>
);
}For Vue, Solid, Svelte, Angular, and the other frameworks, see Quick Start.
You now have a streaming Claude chat. Tools and Structured Outputs work the same as with every adapter.
Summarization#
import { summarize } from "@tanstack/ai";
import { anthropicSummarize } from "@tanstack/ai-anthropic";
const result = await summarize({
adapter: anthropicSummarize("claude-sonnet-4-6"),
text: "Your long text to summarize...",
threadId: "thread-1",
maxLength: 100,
style: "concise", // "concise" | "bullet-points" | "paragraph"
});
console.log(result.summary);Files#
anthropicFiles uploads media to the Anthropic Files API, so a message can point to the file. See Files API.
No image generation#
Anthropic does not make images. For images, use OpenAI or Gemini.
Provider tools#
Anthropic runs some tools itself, such as web search and a code sandbox. Import them from @tanstack/ai-anthropic/tools and add them to tools:
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { webSearchTool } from "@tanstack/ai-anthropic/tools";
const stream = chat({
adapter: anthropicText("claude-sonnet-5-5"),
messages: [{ role: "user", content: "What's new in AI this week?" }],
threadId: "thread-1",
tools: [
webSearchTool({
name: "web_search",
type: "web_search_20250305",
max_uses: 2,
}),
],
});Tools that Anthropic runs:
- webSearchTool: search the web, with citations.
- webFetchTool: read one URL.
- codeExecutionTool: run code in a sandbox, also with hosted skills.
- memoryTool: keep facts across turns.
Tools that your app runs when Claude asks:
- bashTool: run shell commands.
- textEditorTool: view and change files.
- computerUseTool: control a virtual desktop.
- customTool: a tool with an inline schema.
Each tool, with a full sample, is on Anthropic: Advanced. For which models take which tool, see Provider Tools.
Models and options#
Model options#
Provider options go in modelOptions, with the Anthropic names:
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
const stream = chat({
adapter: anthropicText("claude-sonnet-5-5"),
messages: [{ role: "user", content: "Hello!" }],
threadId: "thread-1",
modelOptions: {
max_tokens: 4096,
stop_sequences: ["END"],
},
});- The sampling options temperature, top_p, and top_k also go in modelOptions. Claude Sonnet 5, Claude Opus 4.7 and later, and the 5-generation models reject them. The types take them only on the models that accept them, for example claude-sonnet-4-6.
- When you do not set max_tokens, the adapter sends the full output limit of the model. See The max_tokens default.
- Sampling options do not go at the root of chat(). See Moving Sampling Options into modelOptions.
Reasoning#
Want Claude to think harder, or not at all? Set reasoning on chat(). The adapter turns the level into the right thinking fields for the model.
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
const stream = chat({
adapter: anthropicText("claude-opus-5-5"),
messages: [{ role: "user", content: "Plan a database migration." }],
threadId: "thread-1",
reasoning: "xhigh",
});What goes to Anthropic:
- off sends thinking: { type: "disabled" } on the models with off in the table below. On a model without off, such as claude-fable-5, the types reject it. At run time, it moves to the lowest level that the model has.
- Claude 4.6 and later get adaptive thinking and output_config.effort. With an outputSchema, the adapter adds output_config.format next to the effort.
- Budget models get thinking: { type: "enabled", budget_tokens }. The level picks the budget, or you set it with budgetTokens. If max_tokens is below the budget, the adapter raises it.
- summary: false sends display: "omitted", so the thinking text does not stream.
The levels of each model:
| Model | Levels | budgetTokens |
|---|---|---|
| claude-opus-5-5, claude-sonnet-5-5, claude-fable-5-1, claude-fable-5, claude-opus-5, claude-opus-4-8, claude-opus-4-7 | low to max | no |
| claude-haiku-5-5, claude-sonnet-5 | off, low to max | no |
| claude-opus-5-fast | off, low to max | yes |
| claude-opus-4-6, claude-sonnet-4-6 | low, medium, high, max | yes |
| claude-opus-4-5 | low, medium, high | yes |
| claude-haiku-4-5, claude-sonnet-4-5, claude-opus-4-1 | off to high | yes |
A budget model with budgetTokens:
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
const stream = chat({
adapter: anthropicText("claude-haiku-4-5"),
messages: [{ role: "user", content: "Check this proof." }],
threadId: "thread-1",
reasoning: { level: "high", budgetTokens: 8000 },
});Structured output forces a tool, and Claude cannot think while a tool is forced. So the structured output call sends no thinking. See Reasoning for the levels and the nearest-level rule.
Models#
Newest first. The package exports the full list as ANTHROPIC_MODELS.
| Family | Models |
|---|---|
| Claude 5.5 | claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5 |
| Claude 5 | claude-fable-5-1, claude-fable-5, claude-opus-5, claude-opus-5-fast, claude-sonnet-5 |
| Claude 4 | claude-opus-4-8, claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-6, claude-opus-4-5, claude-sonnet-4-5, claude-haiku-4-5, claude-opus-4-1 |
adapter.inputModalities lists the input kinds of the selected model. See Check what a model accepts.
Refusal fallback#
Did Claude return an empty answer to a life-science or security question? A safety classifier on the newer models can decline a request. Set fallbacks, and the Claude API sends a declined request to another Claude model in the same call:
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
export async function POST(request: Request) {
const { messages } = await request.json();
const stream = chat({
adapter: anthropicText("claude-opus-5-5"),
messages,
threadId: "thread-1",
modelOptions: { fallbacks: "default" },
});
return toServerSentEventsResponse(stream);
}What this does:
- "default" uses the model that Anthropic recommends for the refusal category. A list, such as [{ model: "claude-opus-4-8" }], names up to three models to try in order.
- The adapter adds the server-side-fallback-2026-07-01 beta header for you.
- The fallback answer streams like any other answer. When text has already started, events before the fallback block name the model that declined. That includes RUN_STARTED and that first text segment. Events after the block name to.model.
- When the classifier declines before any output, message_start already names the fallback model. RUN_STARTED uses that name too.
- The types accept fallbacks on claude-fable-5, claude-fable-5-1, claude-opus-5, claude-opus-5-5, and claude-sonnet-5-5.
Some refusals still stand. For example, a category can have no recommended model, or the fallback model can be busy. Then the run ends with finishReason: "content_filter", not with an error. The rawEvent of that RUN_FINISHED holds the Anthropic stop_details, with category and explanation.
Show the reason in the UI with onChunk:
import { useState } from "react";
import { fetchServerSentEvents, useChat } from "@tanstack/ai-react";
export function Chat() {
const [refusal, setRefusal] = useState<string | null>(null);
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
onChunk: (chunk) => {
if (chunk.type === "RUN_FINISHED" && chunk.finishReason === "content_filter") {
const details = chunk.rawEvent;
const category =
details &&
typeof details === "object" &&
"category" in details &&
typeof details.category === "string"
? details.category
: "unknown";
setRefusal(`Claude declined this request (category: ${category}).`);
}
},
});
return (
<div>
{messages.map((message) => (
<p key={message.id}>
{message.parts.map((part) => (part.type === "text" ? part.content : null))}
</p>
))}
{refusal && <p role="alert">{refusal}</p>}
<button onClick={() => sendMessage("Describe how CRISPR edits a gene.")}>Send</button>
</div>
);
}Limits:
- Fallback works only on the Claude API. Claude on Vertex does not have it.
- claude-haiku-5-5 has no fallback model, so its refusals always stand.
- The adapter does not send the fallback content block back on the next turn. A refusal before any output is not affected.
Now a declined request gets an answer from a fallback model. When no model can answer, your UI tells the user why.
Advanced#
These topics are on Anthropic: Advanced:
- Bearer and OAuth tokens, gateways, and your own Anthropic client.
- Claude on Vertex AI.
- The max_tokens default and prompt caching.
- Every provider tool, with a full sample.
- The API reference.
To call Claude through a GitHub Copilot plan, see GitHub Copilot.