<!-- This document is hand-written and maintained by hand. It is not generated by any script. -->

# Anthropic Discovery: Observed vs Documented

This note records the live wire behaviour of Anthropic's `/v1/messages`
and `/v1/files` surfaces as observed by the `@intx/inference-discovery-anthropic`
plug-in. For each captured capability we contrast Anthropic's published
shape against the bytes the API actually emitted and call out every
place the two diverged.

The capture corpus lives at `packages/inference-discovery-anthropic/sessions/anthropic/`.
The model-to-capability matrix is authoritatively defined in the
`SUPPORT_MATRIX` export of `@intx/inference-discovery/catalog`
(`packages/inference-discovery/src/catalog/support-matrix.ts`). The
fixtures are the ground truth: where the docs and the wire disagree,
the wire wins. This document is the narrative companion to those bytes.

## Source of truth

The per-(model, capability) behaviors documented below are the
_narrative_ layer. The _canonical_ representation is the typed
`SUPPORT_MATRIX`. Tooling — the discovery rig, the session
parser-regression, and any future readers — must consume the matrix
programmatically, not parse this prose. When this document and the
matrix disagree, the matrix wins.

## Models in scope

Four first-party Anthropic models are exercised. The plug-in pins the
`anthropic-version: 2023-06-01` header on every request and authenticates
with `x-api-key`. Per-capability beta flags
(`anthropic-beta: files-api-2025-04-14`,
`anthropic-beta: code-execution-2025-05-22`) live on the per-step
headers map for the steps that need them, not in `buildAuthHeaders`.

`claude-sonnet-5`, `claude-opus-5`, and `claude-fable-5` reject the
classic `thinking: {type: "enabled", budget_tokens}` shape and require
the adaptive shape `thinking: {type: "adaptive"}` paired with
`output_config: {effort}`; the request builder selects the shape by
model. `claude-haiku-4-5-20251001` still uses the classic shape.

| Model                       | Tier      | Extended thinking | Vision | Document input | Code execution | Web search |
| --------------------------- | --------- | ----------------- | ------ | -------------- | -------------- | ---------- |
| `claude-fable-5`            | Flagship  | yes (adaptive)    | yes    | yes            | yes            | yes        |
| `claude-opus-5`             | Flagship  | yes (adaptive)    | yes    | yes            | yes            | yes        |
| `claude-sonnet-5`           | Workhorse | yes (adaptive)    | yes    | yes            | yes            | yes        |
| `claude-haiku-4-5-20251001` | Cheap     | yes (budget)      | yes    | yes            | yes            | yes        |

Anthropic does not expose audio input, video input, or image output on
any first-party model, so those entries land as `outcome: "unsupported"`
in `SUPPORT_MATRIX` with self-contained notes explaining why.

## Per-capability observed vs documented

### plain-text and plain-text-streaming

**Documented:** `POST /v1/messages` with `{model, max_tokens, messages: [{role: "user", content: <string>}]}`.
Streaming sets `stream: true` and emits a named SSE event stream.

**Observed** (`sessions/anthropic/claude-sonnet-5/plain-text/exchanges/0/`):
the assistant message envelope carries `usage` with cache-related
sub-fields (`cache_creation_input_tokens`, `cache_read_input_tokens`,
`cache_creation.ephemeral_5m_input_tokens`, `…ephemeral_1h_input_tokens`)
plus `service_tier: "standard"` and `inference_geo: "not_available"`.
The envelope also carries a nullable `stop_details` field alongside
`stop_reason` and `stop_sequence`. None of these are listed in the
public Messages API reference today.

### function-calling and function-calling-multi-turn

**Documented:** Tool decls go in `tools[]` with `{name, description, input_schema}`.
Assistant response includes a `tool_use` content block; multi-turn echoes
the assistant content verbatim and appends a `tool_result` user message
keyed by `tool_use_id`.

**Observed** (`sessions/anthropic/claude-sonnet-5/function-calling-multi-turn/exchanges/0/`):
the assistant `tool_use` content block carries a `caller: {type: "direct"}`
sub-object that is not in the public reference. Turn-2 echoes it back
along with the rest of the content array; Anthropic accepts it.

### function-calling-with-thinking and -streaming

**Documented:** Adds `thinking: {type: "enabled", budget_tokens: N}` to
the request (with `max_tokens > budget_tokens`). The assistant response
includes a `thinking` content block with a `signature` field; the
client must round-trip the signature in turn-2 or Anthropic rejects the
follow-up.

**Observed** (`sessions/anthropic/claude-sonnet-5/function-calling-with-thinking/exchanges/0/`,
streaming variant at `…function-calling-with-thinking-streaming/exchanges/0/`):
the thinking block consistently arrives first in `content[]`, followed
by a free-text block and then the `tool_use` block. The streaming
variant delivers the thinking text incrementally via `thinking_delta`
events and the signature in a single `signature_delta` event before
`content_block_stop` — there is no observed case where the signature
is split across multiple deltas. The signature itself is a long
base64-encoded blob (~700 chars for the captured turns); shape pinned
in the fixture.

### vision-input and -streaming

**Documented:** User message content array contains
`{type: "image", source: {type: "base64", media_type, data}}` plus a
text block.

**Observed** (`sessions/anthropic/claude-sonnet-5/vision-input/exchanges/0/`):
exact shape per docs; the response collapses to a single text block.
No image-specific metadata in the response envelope.

### document-input and -streaming

**Documented:** User message content array contains
`{type: "document", source: {type: "base64", media_type: "application/pdf", data}}`.

**Observed** (`sessions/anthropic/claude-sonnet-5/document-input/exchanges/0/`):
exact shape per docs. The response is a single text summary of the PDF
contents — no inline citations in the captured single-turn case. (When
documents arrive via the Files API path with citations enabled, the
shape differs; see grounding below for the closest analog of inline
citation blocks.)

### code-execution and -streaming

**Documented:** `tools: [{type: "code_execution_20250522", name: "code_execution"}]`
plus `anthropic-beta: code-execution-2025-05-22`. The assistant response
includes a `server_tool_use` block describing the code and a
`code_execution_tool_result` block with the execution output.

**Observed** (`sessions/anthropic/claude-sonnet-5/code-execution/exchanges/0/`):
the response top level carries a `container: {id, expires_at}` object
that pins the sandboxed execution container; the `usage` block gains a
`server_tool_use` counter alongside the regular token counts. The
`code_execution_tool_result.content` carries `{type: "code_execution_result", stdout, stderr, return_code, content: [], abort_reason}`
— five fields, all populated even when empty.

### reasoning-content and -streaming

**Documented:** Same thinking enablement as
function-calling-with-thinking, no tools. The assistant response
surfaces thinking blocks containing the model's reasoning before the
final answer.

**Observed** (`sessions/anthropic/claude-sonnet-5/reasoning-content/exchanges/0/`):
shape matches docs; thinking precedes text as elsewhere.

### grounding and -streaming

**Documented (Anthropic):** `tools: [{type: "web_search_20250305", name: "web_search"}]`.
Assistant response includes a `server_tool_use` block for the search
and a `web_search_tool_result` block whose `content` array carries
`web_search_result` entries with `{title, url, encrypted_content, page_age}`.
Text blocks that cite the search carry inline `citations: []` arrays.

**Documented (catalog vocabulary):** `grounding` was introduced for
Gemini's Google Search grounding, which surfaces a top-level
`groundingMetadata` blob on each candidate. Anthropic's `web_search` is
structurally a tool-invocation pattern. Same semantic role (model-
augmented retrieval with citation provenance), different wire shape.

**Observed** (`sessions/anthropic/claude-sonnet-5/grounding/exchanges/0/`):
captures cleanly. The decision recorded here: keep it under the
`grounding` capability with this divergence note rather than introduce
a parallel `web-search(-streaming)` capability. The semantic match
(grounded retrieval with citations) is strong; the wire-shape
divergence is faithfully recorded in the fixture corpus. Should a
future consumer need to discriminate by wire shape, that is a
canonical-renaming exercise on top of an already-captured fixture, not
a re-capture.

### files-api-reference and -streaming

**Documented:** Two-step upload. The upload is
`POST /v1/files` with `multipart/form-data` (`file` field) plus
`anthropic-beta: files-api-2025-04-14`. The response carries
`{type: "file", id, ...}`. The generate request references the
uploaded file via `{type: "document", source: {type: "file", file_id}}`.

**Observed** (`sessions/anthropic/claude-sonnet-5/files-api-reference/exchanges/0/`,
`…/exchanges/1/`): upload response shape is
`{type: "file", id, size_bytes, created_at, filename, mime_type, downloadable}`
where `downloadable: false` for uploaded source PDFs. File IDs use the
`file_011C…` prefix. The generate step's referenced file_id is accepted
without an additional content-availability check.

### redacted-thinking and -streaming

**Documented:** Anthropic publishes a magic canary string
(`ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_…`) that
deterministically triggers a `redacted_thinking` content block carrying
an opaque encrypted `data` field. Clients must echo the block back
verbatim on subsequent turns or the conversation breaks.

**Observed** (re-capture after the current-model inventory expansion):
only the classic-budget Haiku model returned real `redacted_thinking`
blocks for the documented canary. Sonnet 5 and Opus 5 returned regular
`thinking` blocks (misled). Fable 5 refused the canary under the cyber
usage-policy category.

| Model                       | Outcome    | Notes                          |
| --------------------------- | ---------- | ------------------------------ |
| `claude-haiku-4-5-20251001` | `captured` | non-stream and streaming       |
| `claude-sonnet-5`           | `misled`   | regular `thinking` only        |
| `claude-opus-5`             | `misled`   | regular `thinking` only        |
| `claude-fable-5`            | `refused`  | `stop_reason: refusal` (cyber) |

Non-stream redacted blocks carry only `{ type: "redacted_thinking", data }`
(no `signature`, no thinking text). Streaming delivers each redacted
block as a one-shot `content_block_start` with `data` inline — no
`thinking_delta` for redacted blocks. A single stream can open **multiple**
`redacted_thinking` content blocks before the text block (Haiku
turn-1 streaming shows this).

Adaptive models under `thinking: {type: "adaptive"}` +
`output_config.effort` either return a regular `thinking` block for
the canary (Sonnet 5, Opus 5 — misled, fixtures retained) or refuse
with `stop_reason: refusal` (Fable 5 — no fixture). Haiku on classic
`budget_tokens` thinking continues to capture real `redacted_thinking`.

`SUPPORT_MATRIX` holds eight redacted-thinking rows (4 models × 2
capabilities): two `captured` (Haiku), four `misled` (Sonnet 5 and
Opus 5), two `refused` (Fable 5). The plug-in and SSE parser already
treat `redacted_thinking` as one-shot at `content_block_start` and
the turn-2 builder echoes assistant content verbatim, including
redacted blocks.

## Cross-cutting observations

### Streaming SSE protocol

Anthropic's `/v1/messages` streaming surface uses named SSE events:
`message_start`, `content_block_start`, `content_block_delta`,
`content_block_stop`, `message_delta`, `message_stop`, `ping`. Observed
behaviors worth pinning:

- Every captured stream terminates with `message_stop`. No
  post-`message_stop` chunks observed; a replay simulator can treat
  the stream as definitively closed at that event.
- `ping` events appear interleaved between content events, not on a
  fixed schedule.
- Every `data: { … }` line has trailing whitespace inside the closing
  brace (e.g. `…}   }`). `JSON.parse` tolerates it; ad-hoc parsers must.
- The streaming and non-streaming shapes for a given assistant message
  reconcile exactly: SSE reconstructs the same `content[]` array order
  and the same per-block fields (text, signature, tool_use input JSON).

### Signature carriage on thinking blocks

In non-streaming responses the signature is a single field on the
`thinking` block. In streaming, it arrives via one `signature_delta`
event near the end of the block, never split across multiple deltas in
the observed corpus. Multi-block thinking (multiple thinking blocks at
distinct content indices) was not observed in this capture set; the
single-block pattern dominated.

### Server-side tool wire shapes

Both `code_execution` and `web_search` use the same outer pattern:
`{type: "server_tool_use", id, name, input}` followed by a result block
keyed by `tool_use_id`. The result-block type names diverge
(`code_execution_tool_result` vs `web_search_tool_result`) and the
inner `content` shape diverges (`code_execution_result` with stdout /
stderr / return_code vs an array of `web_search_result` entries with
title/url/encrypted_content/page_age). The `web_search_tool_result`
block also carries a `caller: {type: "direct"}` sub-object mirroring
the one observed on regular `tool_use` blocks.

### Citation wire shape

When `web_search` runs, text blocks in the assistant response that
reference the search carry an inline `citations: []` array attached to
the same text block (not as a separate content block). The shape
pinned in `sessions/anthropic/.../grounding/exchanges/0/response.json` is
authoritative.

### Envelope fields not in the public reference

Every captured non-streaming response carries `stop_details` (nullable)
alongside `stop_reason` and `stop_sequence`, and the `usage` block
carries `cache_creation_input_tokens`, `cache_read_input_tokens`,
`cache_creation.ephemeral_5m_input_tokens`,
`cache_creation.ephemeral_1h_input_tokens`, `service_tier`, and
`inference_geo`. Code execution responses additionally carry a
top-level `container: {id, expires_at}` object and a
`usage.server_tool_use` counter. These appear stable across the models
in the current matrix and across both streaming and non-streaming.

## Regeneration

```bash
bin/discover --provider anthropic --all
```

Requires `ANTHROPIC_API_KEY` in `.env`. The plug-in hard-fails when `CI`
is set (mirrors the other rigs). Estimated cost is on the order of
$5–$20 across all captures, dominated by the extended-thinking and
multimodal requests. The `--only` and `--model` flags allow targeted
re-captures during iteration.
