Skip to content
Adapters

Anthropic

The Anthropic adapter provides access to Claude models, including Claude Fable 5, Claude Sonnet 5, Claude Opus 4.8, and more.

Installation

shell
npm i @tanstack/ai-anthropic

Basic Usage

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Hello!" }],
});

Basic Usage - Custom API Key

ts
import { chat } from "@tanstack/ai";
import { createAnthropicChat } from "@tanstack/ai-anthropic";

const adapter = createAnthropicChat("claude-sonnet-4-6", process.env.ANTHROPIC_API_KEY!, {
  // ... your config options
});

const stream = chat({
  adapter,
  messages: [{ role: "user", content: "Hello!" }],
});

Bearer and OAuth tokens

Got a Bearer token instead of an API key? Pass it as the credential and set auth: "bearer". The adapter sends Authorization: Bearer <token> and no x-api-key header:

ts
import { chat } from "@tanstack/ai";
import { createAnthropicChat } from "@tanstack/ai-anthropic";

const adapter = createAnthropicChat("claude-sonnet-5-5", process.env.MY_CLAUDE_TOKEN!, {
  auth: "bearer",
});

const stream = chat({
  adapter,
  messages: [{ role: "user", content: "Hello!" }],
});

auth takes one of three values:

  • "api-key": sends the x-api-key header. This is the default.
  • "bearer": sends Authorization: Bearer <token> and no x-api-key header.
  • "oauth": sends the token like "bearer", plus the Claude Code extras below.

Got a Claude OAuth token? It contains sk-ant-oat, and the adapter picks "oauth" for you, so you can leave auth out. In OAuth mode, every request also gets:

  • the Claude Code identity system block, before your own system prompts.
  • the Claude Code CLI headers (user-agent and x-app).
  • the claude-code-20250219 and oauth-2025-04-20 betas.

An explicit auth always wins. To send an sk-ant-oat token as a plain Bearer token, without the extras, set auth: "bearer".

Is the token in your environment? Then anthropicText finds it and picks auth for you. See Environment Variables for the order:

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";

// ANTHROPIC_AUTH_TOKEN=my-token in your environment
const stream = chat({
  adapter: anthropicText("claude-sonnet-5-5"),
  messages: [{ role: "user", content: "Hello!" }],
});

To call Claude models through a GitHub Copilot plan, see GitHub Copilot.

Configuration

ts
import { createAnthropicChat, type AnthropicTextConfig } from "@tanstack/ai-anthropic";

const config: Omit<AnthropicTextConfig, "apiKey"> = {
  baseURL: "https://api.anthropic.com", // Optional, for custom endpoints
};

const adapter = createAnthropicChat("claude-sonnet-4-6", process.env.ANTHROPIC_API_KEY!, config);

Need a header on each request, or a log of each model call? This adapter supports wrapFetch. An adapter with your own client (Claude on Vertex, or createAnthropicChatWithClient) ignores it, because the adapter cannot reach the fetch of that client.

Replay thinking through a gateway

Your gateway speaks the Anthropic protocol, but it returns thinking with no signature. By default the adapter does not send unsigned thinking back, so the next turn loses it. Turn on allowEmptySignature:

ts
import { anthropicText } from "@tanstack/ai-anthropic";

const gateway = anthropicText("claude-sonnet-5-5", {
  baseURL: "https://gateway.example.com",
  provider: "my-gateway",
  allowEmptySignature: true,
});

console.log(gateway.provider); // "my-gateway"
  • allowEmptySignature is false by default. It sends unsigned thinking back only for history from the same source.
  • Redacted thinking still needs its encrypted data.
  • provider names the gateway. History from the gateway then counts as a different source from Anthropic itself. See Keep saved history when you switch.

Claude on Vertex

Use @tanstack/ai-anthropic/vertex when Claude must run on Vertex AI. That is the path for regional endpoints and Google Cloud credentials.

Install the Vertex SDK next to the Anthropic adapter:

shell
npm i @tanstack/ai-anthropic @anthropic-ai/vertex-sdk
ts
import { chat } from "@tanstack/ai";
import { anthropicVertexText } from "@tanstack/ai-anthropic/vertex";

const stream = chat({
  adapter: anthropicVertexText("claude-sonnet-5", {
    project: "my-project",
    location: "europe-west1",
  }),
  messages: [{ role: "user", content: "Hello!" }],
});

anthropicVertexText accepts only the Claude models in the Vertex catalog. It does not accept Anthropic-only ids such as claude-opus-5-fast.

project and location use the same names as @tanstack/ai-vertex, so one auth object works for Gemini and Claude.

If you omit project, Application Default Credentials can still fill it. location is required. You can pass it on the factory or set GOOGLE_CLOUD_LOCATION, GOOGLE_VERTEX_LOCATION, or CLOUD_ML_REGION.

Gemini on Vertex lives in @tanstack/ai-vertex.

Custom Anthropic client

Use createAnthropicChatWithClient when you already have an Anthropic-compatible client. The adapter only needs beta.messages.create. Message mapping, streaming, tools, media, usage, and structured output stay on the same TanStack path.

shell
npm i @tanstack/ai-anthropic @anthropic-ai/vertex-sdk
ts
import { AnthropicVertex } from "@anthropic-ai/vertex-sdk";
import { createAnthropicChatWithClient } from "@tanstack/ai-anthropic";

const client = new AnthropicVertex({
  projectId: "my-project",
  region: "europe-west1",
});

const adapter = createAnthropicChatWithClient("claude-sonnet-5", client);

The injected client must implement the Anthropic Beta Messages protocol. Endpoint-specific model and feature support stays the caller's job.

Example: Chat Completion

ts
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-sonnet-4-6"),
    messages,
  });

  return toServerSentEventsResponse(stream);
}

Example: With Tools

ts
import { chat, toServerSentEventsResponse, toolDefinition } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { z } from "zod";

const searchDatabaseDef = toolDefinition({
  name: "search_database",
  description: "Search the database",
  inputSchema: z.object({
    query: z.string(),
  }),
});

const searchDatabase = searchDatabaseDef.server(async ({ query }) => {
  // Search database
  return { results: [] };
});

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = chat({
    adapter: anthropicText("claude-sonnet-4-6"),
    messages,
    tools: [searchDatabase],
  });

  return toServerSentEventsResponse(stream);
}

Model Options

Anthropic supports various provider-specific options. Sampling parameters live here too — temperature, top_p, and max_tokens — rather than as root-level props on chat():

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Hello!" }],
  modelOptions: {
    max_tokens: 4096,
    temperature: 0.7,
    top_p: 0.9,
    top_k: 40,
    stop_sequences: ["END"],
  },
});

If you previously passed temperature / topP / maxTokens at the root of chat(), see Moving Sampling Options into modelOptions.

max_tokens default

Anthropic's Messages API requires max_tokens on every request, so the adapter always sends a value. When you don't set modelOptions.max_tokens, it defaults to the selected model's full output ceiling (max_output_tokens from the model metadata — e.g. 64K for Sonnet, 128K for Opus), falling back to a safe constant for unrecognized models. max_tokens is a ceiling, not a reservation — billing is on tokens actually generated — so this default costs nothing extra and avoids the silent mid-response truncation (stop_reason: "max_tokens") that a low default would cause. Set max_tokens explicitly only when you want to cap output below the model ceiling. If a response is truncated while using the default cap, the adapter logs a warning (visible with debug logging enabled).

A streamed response that stops at max_tokens ends in a RUN_ERROR with code: 'max_tokens'. Anthropic bills the tokens of that call, so this RUN_ERROR carries the usage of the call. The onUsage middleware hook fires only for RUN_FINISHED. To count these tokens too, read usage from the RUN_ERROR chunk, in the stream or in an onChunk middleware.

One exception: structured output (chat({ outputSchema })) on models that use the non-streaming finalization path clamps this default to ~21K tokens. The Anthropic SDK rejects a non-streaming request whose max_tokens could exceed its 10-minute timeout, so the full ceiling can't be used there. Streaming chat is unaffected. To raise the structured-output ceiling toward a model's true max, stream the response.

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.

ts
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." }],
  reasoning: "xhigh",
});

What goes on the wire:

  • 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, and 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. The adapter raises max_tokens when it is below the budget.
  • summary: false sends display: "omitted", so the thinking text does not stream.

The levels of each model:

ModelLevelsbudgetTokens
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-7low to maxno
claude-haiku-5-5, claude-sonnet-5off, low to maxno
claude-opus-5-fastoff, low to maxyes
claude-opus-4-6, claude-sonnet-4-6low, medium, high, maxyes
claude-opus-4-5low, medium, highyes
claude-haiku-4-5, claude-sonnet-4-5, claude-opus-4-1off to highyes

A budget model with budgetTokens:

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

Claude Sonnet 5, Claude Opus 4.7 and later, and the 5-generation models reject the sampling options (temperature, top_p, top_k). The types take them only on the models that accept them. See Reasoning for the levels and the nearest-level rule.

Structured output forces a tool, and Claude cannot think while a tool is forced. So the structured output call sends no thinking.

Prompt Caching

chat() caches Claude prompts by default. It adds cache_control markers to the last start tool, the last block of the last user or mid-conversation system message, and the system blocks. At most 4 markers. The last start tool is the tool before the deferred placeholder, or the last tool. Pass promptCache: 'long' for the 1-hour cache, or promptCache: 'none' to send no markers. For the key and the cost, see Prompt Caching.

Want the markers in other places? Set cache_control in the metadata of a message part, a system prompt, or a tool. One marker of your own turns the automatic markers off for that request:

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";

const stream = chat({
  adapter: anthropicText("claude-opus-5-5"),
  messages: [
    {
      role: "user",
      content: [
        {
          type: "text",
          content: "What is the capital of France?",
          metadata: {
            cache_control: {
              type: "ephemeral",
            },
          },
        },
      ],
    },
  ],
});

modelOptions.cache_control also turns the automatic markers off.

usage.promptTokens counts the full input, cached tokens included. The cache reads and writes are also on usage.promptTokensDetails. See Token usage.

Tools and prompts added during a conversation

On claude-opus-4-8, claude-opus-5, claude-opus-5-5, claude-fable-5, and claude-fable-5-1, a tool or a system prompt that you add between model calls goes into the conversation. The cached start of the request stays the same.

What the adapter sends on these models:

  • system keeps the system prompts of the first call.
  • A request with tools gets the mid-conversation-tool-changes-2026-07-01 beta and one placeholder tool, __tanstack_deferred_placeholder__. The model never calls it.
  • An added tool goes at the end of tools with defer_loading: true. A system message names it in a tool_addition block.
  • An added system prompt goes into a system message, before the next assistant message or at the end.
  • With a provider tool such as webSearchTool(), tools is the full list and has no placeholder.

The automatic tool marker stays on the last tool of the first call, so the marked start does not move.

The channels are on by default with Anthropic's own API. A custom baseURL, a custom fetch, ANTHROPIC_BASE_URL, or your own client turns them off. Set midConversationChannels to choose:

ts
import { anthropicText } from "@tanstack/ai-anthropic";

export const fullLists = anthropicText("claude-opus-5-5", {
  midConversationChannels: false,
});

export const throughProxy = anthropicText("claude-opus-5-5", {
  baseURL: "https://llm-proxy.example.com",
  midConversationChannels: true,
});

If that endpoint sends the request and the anthropic-beta header on to Anthropic as they are, set true. See Mid-Conversation Changes.

Summarization

Anthropic supports text summarization:

ts
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...",
  maxLength: 100,
  style: "concise", // "concise" | "bullet-points" | "paragraph"
});

console.log(result.summary);

Environment Variables

Set one of these. anthropicText, anthropicSummarize, and anthropicFiles use the first one that is set:

  1. ANTHROPIC_AUTH_TOKEN: sent as a Bearer token. An sk-ant-oat token here turns on OAuth mode.
  2. ANTHROPIC_OAUTH_TOKEN: sent as a Bearer token, in OAuth mode.
  3. ANTHROPIC_API_KEY: sent as x-api-key.
shell
ANTHROPIC_API_KEY=sk-ant-...

If none of them is set, these factories throw. An auth in the config wins over the kind that the adapter picks. See Bearer and OAuth tokens for what OAuth mode adds.

The create* factories (createAnthropicChat, createAnthropicSummarize, createAnthropicFiles) never read a credential from the environment. They use only the credential that you pass.

API Reference

Every factory pair has the same shape. The short factory (anthropicText, anthropicSummarize) reads the credential from the environment. createAnthropicChat / createAnthropicSummarize take an explicit credential and never read one from the environment. Both take model as the first argument. For Claude on Vertex, use anthropicVertexText from @tanstack/ai-anthropic/vertex. For any other custom transport, createAnthropicChatWithClient accepts an Anthropic-compatible Messages client.

anthropicText(model, config?) / createAnthropicChat(model, apiKey, config?)

Creates an Anthropic chat adapter.

adapter.inputModalities lists the input kinds of the selected model. It is undefined for a model that this package does not know. See Check what a model accepts.

Parameters:

  • model - Claude model id (e.g. "claude-sonnet-5", "claude-fable-5", "claude-opus-4-8")
  • apiKey - API key, Bearer token, or Claude OAuth token (createAnthropicChat only)
  • config?.baseURL - Custom base URL (optional)
  • config?.auth - How to send the credential: "api-key", "bearer", or "oauth" (optional, see Bearer and OAuth tokens)
  • config?.allowEmptySignature - Send unsigned thinking back to the same source (optional, default false, see Replay thinking through a gateway)
  • config?.provider - The source name for a gateway (optional, default "anthropic")

anthropicVertexText(model, config?)

Creates an Anthropic chat adapter on Vertex. Import it from @tanstack/ai-anthropic/vertex.

Parameters:

  • model - Claude model id
  • config.project - GCP project id (optional if ADC can resolve it)
  • config.location - Vertex region (or set GOOGLE_CLOUD_LOCATION)

createAnthropicChatWithClient(model, client)

Creates an Anthropic chat adapter using an injected client.

Parameters:

  • model - Claude model id
  • client - Client exposing beta.messages.create

anthropicSummarize(model, config?) / createAnthropicSummarize(model, apiKey, config?)

Creates an Anthropic summarization adapter.

Limitations

  • Image Generation: Anthropic does not support image generation. Use OpenAI or Gemini for image generation.

Next Steps

Provider Tools

Anthropic exposes several native tools beyond user-defined function calls. Import them from @tanstack/ai-anthropic/tools and pass them into chat({ tools: [...] }).

For the full concept, a comparison matrix, and type-gating details, see Provider Tools.

webSearchTool

Enables Claude to run Anthropic's native web search with inline citations. Scope the search with allowed_domains or blocked_domains (mutually exclusive); set max_uses to cap per-turn cost.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { webSearchTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
  adapter: anthropicText("claude-opus-4-7"),
  messages: [{ role: "user", content: "What's new in AI this week?" }],
  tools: [
    webSearchTool({
      name: "web_search",
      type: "web_search_20250305",
      max_uses: 2,
    }),
  ],
});

Supported models: every registered Claude model. See Provider Tools.

webFetchTool

Lets Claude fetch the contents of a URL directly, useful when you want the model to read a specific page rather than run a search. Takes no required arguments — pass an optional config object to override defaults.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { webFetchTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Summarise https://example.com" }],
  tools: [webFetchTool()],
});

Supported models: Claude Sonnet 4.x and above. See Provider Tools.

codeExecutionTool

Gives Claude a sandboxed code-execution environment so it can run Python snippets, analyse data, and return results inline. Choose the version string that matches your desired API revision.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { codeExecutionTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Plot a histogram of [1,2,2,3,3,3]" }],
  tools: [
    codeExecutionTool({ name: "code_execution", type: "code_execution_20250825" }),
  ],
});

Supported models: Claude Sonnet 4.x and above. See Provider Tools.

Attaching hosted skills

Pass a skills array as the second argument to load provider-managed skill bundles into the sandbox. The adapter auto-lifts them into the API's container.skills param and adds the required beta headers for you.

ts
import { chat, toServerSentEventsResponse } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { codeExecutionTool } from "@tanstack/ai-anthropic/tools";

export async function POST(request: Request) {
  const { messages } = await request.json();

  const stream = chat({
    adapter: anthropicText("claude-sonnet-4-5"),
    messages,
    tools: [
      codeExecutionTool(
        { type: "code_execution_20250825", name: "code_execution" },
        {
          skills: [{ type: "anthropic", skill_id: "pptx", version: "latest" }],
        },
      ),
    ],
  });

  return toServerSentEventsResponse(stream);
}

For the full reference — skill shape, constraints, scope, and the OpenAI equivalent — see Provider Skills.

computerUseTool

Allows Claude to observe a virtual desktop (screenshots) and interact with it via keyboard and mouse events. Provide the screen resolution so Claude can calculate accurate coordinates.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { computerUseTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Open the browser and go to example.com" }],
  tools: [
    computerUseTool({
      type: "computer_20250124",
      name: "computer",
      display_width_px: 1024,
      display_height_px: 768,
    }),
  ],
});

Supported models: Claude Sonnet 3.5 and above, with two exceptions. claude-opus-5-fast takes no provider tools. Claude Opus 5.5, Claude Sonnet 5.5, and Claude Haiku 5.5 accept only the computer_toolset_20260801 toolset, which the adapter does not offer yet. See Provider Tools.

bashTool

Provides Claude with a persistent bash shell session, letting it run arbitrary commands, install packages, or manipulate files on the host. Choose the type string that matches your API revision.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { bashTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "List all TypeScript files in src/" }],
  tools: [bashTool({ name: "bash", type: "bash_20250124" })],
});

Supported models: Claude Sonnet 3.5 and above. See Provider Tools.

textEditorTool

Gives Claude a structured text-editor interface for viewing and modifying files using str_replace, create, view, and undo_edit commands. Choose the type string for the API revision you target.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { textEditorTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Fix the bug in src/index.ts" }],
  tools: [
    textEditorTool({ type: "text_editor_20250124", name: "str_replace_editor" }),
  ],
});

Supported models: Claude Sonnet 3.5 and above. See Provider Tools.

memoryTool

Enables Claude to store and retrieve information across conversation turns using Anthropic's managed memory service. Call with no arguments to use default configuration.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { memoryTool } from "@tanstack/ai-anthropic/tools";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Remember that I prefer metric units" }],
  tools: [memoryTool()],
});

Supported models: Claude Sonnet 4.x and above. See Provider Tools.

customTool

Creates a tool with an inline JSON Schema input definition instead of going through toolDefinition(). Useful when you need fine-grained control over the schema shape or want to add cache_control. Unlike branded provider tools, customTool returns a plain Tool and is accepted by any chat model.

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { customTool } from "@tanstack/ai-anthropic/tools";
import { z } from "zod";

const stream = chat({
  adapter: anthropicText("claude-sonnet-4-6"),
  messages: [{ role: "user", content: "Look up user 42" }],
  tools: [
    customTool(
      "lookup_user",
      "Look up a user by ID and return their profile",
      z.object({ userId: z.number() }),
    ),
  ],
});

Supported models: all current Claude models. See Provider Tools.