Skip to content
Adapters

Anthropic: Advanced

This page has the details of the Anthropic adapter. To get a first chat working, start at Anthropic.

Bearer and OAuth tokens

Got a Bearer token, not 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!" }],
  threadId: "thread-1",
});

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.

A Claude OAuth token contains sk-ant-oat. For such a token, the adapter picks "oauth", 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 Set the API key for the order of the variables:

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!" }],
  threadId: "thread-1",
});

An auth in the config wins over the kind that the adapter picks. 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-5-5", 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!" }],
  threadId: "thread-1",
});
  • anthropicVertexText accepts only the Claude models in the Vertex catalog (ANTHROPIC_VERTEX_CHAT_MODELS). It does not accept ids that only Anthropic has, such as claude-fable-5-1.
  • 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. Pass it on the factory, or set GOOGLE_CLOUD_LOCATION, GOOGLE_VERTEX_LOCATION, or CLOUD_ML_REGION.

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

Custom Anthropic client

Already have a client that speaks the Anthropic protocol? Use createAnthropicChatWithClient. 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 client must implement the Anthropic Beta Messages protocol. Model and feature support of that endpoint is your job.

max_tokens default

The Messages API of Anthropic requires max_tokens on every request, so the adapter always sends a value.

  • When you do not set modelOptions.max_tokens, it is the full output limit of the model (max_output_tokens from the model metadata, for example 64K for Sonnet and 128K for Opus).
  • For a model that this package does not know, the adapter uses a safe constant.
  • max_tokens is a limit, not a reservation. Anthropic bills the tokens that the model makes, so this default costs nothing extra. A low default can cut a response short (stop_reason: "max_tokens").
  • Set max_tokens only to cap the output below the model limit.
  • If a response stops at the default limit, the adapter logs a warning. You see it with debug logging on.

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 caps this default at about 21K tokens. The Anthropic SDK rejects a non-streaming request whose max_tokens can go past its 10-minute timeout. Streaming chat does not have this cap. To raise the structured output limit toward the true maximum of a model, stream the response.

Prompt caching

chat() caches Claude prompts by default. It adds cache_control markers, at most 4, to:

  • the last start tool. That is the tool before the deferred placeholder, or the last tool.
  • the last block of the last user message, or of the last system message in the conversation.
  • the system blocks.

Pass promptCache: 'long' for the 1-hour cache, or promptCache: 'none' to send no markers. For the key and the cost, see Prompt Caching.

To put 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",
            },
          },
        },
      ],
    },
  ],
  threadId: "thread-1",
});
  • 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 the API of Anthropic. 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 without changes, set true. See Mid-Conversation Changes.

Provider tools

Anthropic has tools of its own, next to the function tools that you define. Import them from @tanstack/ai-anthropic/tools and pass them into chat({ tools: [...] }).

For the concept, a comparison table, and type gating, see Provider Tools.

webSearchTool

Claude runs the native web search of Anthropic, with inline citations.

  • Limit the search with allowed_domains or blocked_domains. You cannot use both.
  • Set max_uses to cap the cost of each turn.
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-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,
    }),
  ],
});

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

webFetchTool

Claude gets the content of one URL. If the model must read a specific page, not run a search, use it. It needs no arguments. Pass a config object to change the 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" }],
  threadId: "thread-1",
  tools: [webFetchTool()],
});

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

codeExecutionTool

Gives Claude a sandbox that runs code. Claude can run Python, look at data, and return the results inline. Pick the version string of the API revision that you want.

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]" }],
  threadId: "thread-1",
  tools: [
    codeExecutionTool({ name: "code_execution", type: "code_execution_20250825" }),
  ],
});

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

Attaching hosted skills

Pass a skills array as the second argument to load skill bundles that the provider manages into the sandbox. The adapter moves them into the container.skills parameter of the API 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, threadId } = await request.json();

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

  return toServerSentEventsResponse(stream);
}

For the skill shape, the limits, the scope, and the OpenAI equivalent, see Provider Skills.

computerUseTool

Claude can look at a screen and use the mouse and the keyboard. Your app runs each action and sends back a screenshot. createComputerUse from @tanstack/ai-computer-use does this on a Playwright browser or an E2B desktop. Each thread gets its own machine:

ts
import { chat } from "@tanstack/ai";
import { anthropicText } from "@tanstack/ai-anthropic";
import { createComputerUse } from "@tanstack/ai-computer-use";
import { playwrightComputer } from "@tanstack/ai-computer-playwright";

declare const threadId: string;

const computerUse = createComputerUse({
  driver: playwrightComputer({ startUrl: "https://tanstack.com" }),
});

const stream = chat({
  adapter: anthropicText("claude-opus-5-5"),
  messages: [{ role: "user", content: "Find the TanStack AI docs" }],
  threadId,
  tools: computerUse.tools,
  middleware: [computerUse.middleware],
  interrupts: [computerUse.interrupt],
});

The adapter sends the computer tool to Claude as the newest native computer tool that your model takes.

The raw tool

computerUseTool from @tanstack/ai-anthropic/tools is the native tool with no execute. Use it when your app runs the actions itself. Pick the version that your model takes:

VersionModels
computer_toolset_20260801Claude Opus 5.5, Sonnet 5.5, Haiku 5.5, Fable 5.1, Fable 5, Opus 5, Sonnet 5, Opus 4.8
computer_20251124Claude Fable 5.1, Fable 5, Opus 5, Sonnet 5, Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6, Opus 4.5
computer_20250124Claude Sonnet 4.5, Haiku 4.5, Opus 4.1
ts
import { computerUseTool } from "@tanstack/ai-anthropic/tools";

const computer = computerUseTool({ type: "computer_toolset_20260801" });

The earlier versions also take a name and the screen size:

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

const computer = computerUseTool({
  type: "computer_20251124",
  name: "computer",
  display_width_px: 1280,
  display_height_px: 800,
});

Add an execute that runs the action and returns a result:

  • Every version calls execute with the action in input.action. With the toolset, Claude calls each action as its own tool. The adapter gives you each call as one computer call.
  • Return a screenshot as an image part for the screenshot action, and "OK" for the other actions.
  • Send screenshots that fit the image limits of the model. The toolset refuses a large screenshot. It does not make it smaller.
  • The toolset turns on zoom by default. Return an image for the zoom action, or turn it off with configs: { zoom: { enabled: false } }.
  • The adapter sends the beta header that an earlier version needs. The toolset needs no beta header.

Claude gives coordinates in the pixels of your screenshots.

A version that your model does not take is a type error. See Provider Tools.

bashTool

Gives Claude a bash shell session that stays open. Claude can run commands, install packages, or change files on the host. Pick the type string of 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/" }],
  threadId: "thread-1",
  tools: [bashTool({ name: "bash", type: "bash_20250124" })],
});

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

textEditorTool

Gives Claude a text editor to view and change files, with the str_replace, create, view, and undo_edit commands. Pick the type string of the API revision that you use.

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" }],
  threadId: "thread-1",
  tools: [
    textEditorTool({ type: "text_editor_20250124", name: "str_replace_editor" }),
  ],
});

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

memoryTool

Claude stores and reads facts across turns with the managed memory service of Anthropic. Call it with no arguments to use the 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" }],
  threadId: "thread-1",
  tools: [memoryTool()],
});

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

customTool

Makes a tool with an inline schema, without toolDefinition(). If you need full control of the schema shape, or want to add cache_control, use it. customTool returns a plain Tool, so every chat model accepts it.

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" }],
  threadId: "thread-1",
  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.

API reference

Each factory pair has the same shape. The short factory (anthropicText, anthropicSummarize) reads the credential from the environment. The create* factory takes an explicit credential and never reads one from the environment. Both take model as the first argument.

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

Creates an Anthropic chat adapter.

  • model: a Claude model id, for example "claude-sonnet-5-5" or "claude-opus-5-5".
  • apiKey: an API key, a Bearer token, or a Claude OAuth token (createAnthropicChat only).
  • config?.baseURL: a 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".

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.

anthropicVertexText(model, config?)

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

  • model: a Claude model id from the Vertex catalog.
  • config.project: the GCP project id. Optional if ADC can find it.
  • config.location: the Vertex region. You can also set GOOGLE_CLOUD_LOCATION.

createAnthropicChatWithClient(model, client)

Creates an Anthropic chat adapter with a client that you give.

  • model: a Claude model id.
  • client: a client with beta.messages.create.

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

Creates an Anthropic summarization adapter.

anthropicFiles / createAnthropicFiles

Creates a Files API adapter. See Files API.