---
url: 'https://adk.nht.io/batteries/llm/anthropic.md'
description: >-
  Anthropic's first-party Messages wire for Claude: prompt caching, signed
  thinking replay, native refusal outcomes, token counting, image/PDF input, and
  strict Anthropic request semantics. Node-first, optional SDK peer, no request
  repair.
---

# Anthropic Messages

## LLM summary — Anthropic Messages

* [`AnthropicMessagesAdapter`](https://adk.nht.io/api/@nhtio/adk/batteries/llm/anthropic_messages/adapter/classes/AnthropicMessagesAdapter) from `@nhtio/adk/batteries/llm/anthropic_messages`. Drives Anthropic's first-party Messages API, not an OpenAI-compatible shim. Install the optional peer: `pnpm add @anthropic-ai/sdk`.
* Required: `model` and `maxTokens`. The adapter maps ADK history/tools/media to Anthropic `messages`, top-level `system`, flat `tools`, `max_tokens`, and optional `thinking`, `output_config`, `tool_choice`, `stop_sequences`, `metadata`, `service_tier`, `container`, `inference_geo`, and `userProfileId`.
* Node-first. `dangerouslyAllowBrowser` is exposed because the SDK exposes it, but putting a first-party Anthropic API key in a browser exposes it to every user. The supported browser pattern is a server/gateway that owns the Anthropic key.
* Prompt caching: `cacheBreakpoints: 'auto'|'system-only'|'off'` (default `auto`) and `cacheTtl: '5m'|'1h'` (default omitted, plain ephemeral). Auto places breakpoints on the last system block, the most recent tool-result-bearing user turn, and the final user turn, capped at four.
* Signed thinking replay uses `anthropic-messages-thinking-v1`; signatures are bound to the exact prefix, so history mutation drops stale thinking blocks instead of replaying invalid signatures. `stop_reason: 'refusal'` is an HTTP 200 terminal outcome carrying `stop_details`.
* Media: image and document input are native: base64 PDF and plain-text document sources. Audio and video are unsupported and route through `unsupportedMediaPolicy` / `E_ANTHROPIC_MESSAGES_UNSUPPORTED_MEDIA_MODALITY`.
* `countTokens(input, overrides?)` returns `{ inputTokens, raw }` and accepts either a [`DispatchContext`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext) or a pre-built Anthropic-shaped request.
* Exceptions: `E_INVALID_ANTHROPIC_MESSAGES_OPTIONS`, `E_ANTHROPIC_MESSAGES_{HTTP_ERROR,REQUEST_TIMEOUT,STREAM_ERROR,STREAM_STALLED,CONTEXT_OVERFLOW,INVALID_TOOL_CALL_ARGS,UNSUPPORTED_MEDIA_MODALITY}`.

Anthropic is not OpenAI with different header names. The Messages API has its own shape: top-level `system`,
flat tool declarations, content blocks, prompt-cache breakpoints, signed thinking blocks, a token-counting
endpoint, and a refusal stop reason that is a successful response. This battery speaks that wire directly.

`@anthropic-ai/sdk` is an **optional peer dependency**. Install it when you use this battery:

```bash
pnpm add @nhtio/adk@1.20260726.0 @anthropic-ai/sdk
```

A consumer who deep-imports `@nhtio/adk/batteries/llm/anthropic_messages` without the peer gets an honest
module-not-found for `@anthropic-ai/sdk`. The aggregate barrel `@nhtio/adk/batteries/llm` also resolves this
peer, because it re-exports the battery.

```ts
import { AnthropicMessagesAdapter } from '@nhtio/adk/batteries/llm/anthropic_messages'

const executor = new AnthropicMessagesAdapter({
  model: 'claude-sonnet-5-20260701',
  apiKey: process.env.ANTHROPIC_API_KEY,
  maxTokens: 1024,
  autoAck: true,
})

// executor.executor() is the DispatchExecutorFn you hand to TurnRunner.
```

::: warning Node-first, not browser-first
The adapter exposes `dangerouslyAllowBrowser` because the Anthropic SDK exposes it. That does not make browser
API-key use safe: shipping a first-party Anthropic key to a browser exposes it to every user of the page. The
supported browser architecture is a gateway/server that owns the Anthropic key and applies your auth, quotas,
and policy there. If you deliberately enable browser use anyway, the battery sends what you asked for; it is
not a tested target.
:::

## What this wire buys you

* **Prompt caching.** `cacheBreakpoints` controls where `cache_control` is emitted: `'auto'` (default),
  `'system-only'`, or `'off'`. `cacheTtl` is optional: omit it for normal ephemeral caching, or set `'5m'` /
  `'1h'`. Auto placement targets the last system block, then the last content block of the most recent
  `tool_result`-bearing user turn, then the final user turn's last block. Anthropic caps breakpoints at four;
  if that cap would be exceeded the adapter warns rather than pretending extra breakpoints were sent.
* **Signed thinking replay.** Thinking blocks with signatures are persisted with replay tag
  `anthropic-messages-thinking-v1`. The signature is bound to the exact conversation prefix that produced it.
  ADK history can change through thrift, compaction, shedding, different tools, or a different system prompt;
  when the prefix no longer matches, the battery drops the whole stale thinking block instead of replaying a
  block Anthropic will reject.
* **Native refusal outcomes.** `stop_reason: 'refusal'` is an HTTP 200 terminal outcome, not an exception. It
  carries `stop_details`, so handle it as a completed model turn with policy metadata.
* **Token counting.** `adapter.countTokens(input, overrides?)` calls Anthropic's count-tokens endpoint and
  returns `{ inputTokens, raw }`. `input` may be a normal ADK [`DispatchContext`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext) or a pre-built request
  with `messages` and optional `system`/`tools`.

## The gotchas, because they will get you

**`maxTokens` is required.** Anthropic requires `max_tokens`; this adapter's option is `maxTokens`, and there
is no useful default that is safe for every application.

**Deprecated sampling fields are never defaulted.** `temperature`, `topP`, and `topK` are typed but marked
`@deprecated`. Models after Opus 4.6 reject these fields with a 400 in common cases. The adapter sends them
only if you set them, and logs a warning before doing so. Prefer prompting, model selection, or a gateway that
knowingly rewrites requests.

**There is no request repair.** The battery warns and sends the request you built:

* Anthropic requires `tool_use.id` / `tool_result.tool_use_id` to match `^[a-zA-Z0-9_-]+$`. A bad `ToolCall.id`
  is warned about and sent as-is; Anthropic will usually answer 400. Mint conforming IDs in your caller.
* Unsupported JSON-Schema keywords in `output_config.format` are warned about, not stripped. Prune keywords
  such as length, numeric-bound, and item-count constraints yourself before sending to Anthropic.
* Fable-class model hazards are warned about and sent: forced `tool_choice`, `thinking: { type: 'disabled' }`,
  and `budget_tokens`. Send `{ type: 'adaptive' }` thinking on Fable-class models and avoid forced tool use
  there unless your gateway deliberately normalizes it.

**Context overflow is a 400.** Anthropic can return a 400 whose body says `prompt is too long`; the adapter
classifies that as `E_ANTHROPIC_MESSAGES_CONTEXT_OVERFLOW`, not a generic bad request.

**Upstream 529 is retriable.** The ADK uses status 529 for its own fatal option/context exceptions, but an
Anthropic HTTP 529 `overloaded_error` is a transient upstream overload and is retried according to the adapter's
retry config.

**Media support is narrower than ADK's media model.** Images and documents can be represented natively; the shipped code accepts base64 PDF and plain-text document sources.
Audio and video cannot; they follow `unsupportedMediaPolicy` and, by default, throw
`E_ANTHROPIC_MESSAGES_UNSUPPORTED_MEDIA_MODALITY`.

Server-side refusal `fallbacks` are intentionally not in v1. They live on Anthropic's beta Messages surface,
with separate request/response/stream types, so this battery does not expose a flag that silently switches API
families. Full option and exception details are in [Assembly → LLM batteries](/assembly/batteries-llm).
