---
name: ft-claude
description: Anthropic provider adapter, Claude Code install, SSE/header handling for Messages API. Transport spike findings, header inventory, sigil table for claude-3 tokenizer, install adapter notes, Bun idleTimeout caveat.
---

# ft-claude — Anthropic provider adapter

Load when: implementing/debugging Claude/Anthropic wire, configuring Claude Code install, tuning SSE, adjusting message header handling.

**Transport adapter** — provider-agnostic pipeline in `/ft-dev`. Covers Anthropic-specific wire details.

---

## Transport spike findings

### Header inventory (Claude API + Claude Code)

**Request headers** (client → proxy):

| Header | Source | Usage in fewtok |
|---|---|---|
| `Authorization: Bearer sk-ant-...` | API key from env/secrets | Pass through to Anthropic. Proxy never inspects. |
| `x-api-key: sk-ant-...` | Alt API key format (older clients) | Normalize to `Authorization` before forwarding. |
| `x-claude-code-session-id: <uuid>` | **Claude Code only.** Session UUID injected by Claude Code client. | **Fewtok cache-safety boundary.** ReadCache keyed by session_id. Never serve cached output across session IDs. |
| `anthropic-version: 2023-06-01` | API version | Pass through. Fewtok does not interpret. |
| `content-type: application/json` | Standard HTTP | Proxy handles; client does not set. |

**Response headers** (proxy ← Anthropic):

| Header | Usage in fewtok |
|---|---|
| `x-ratelimit-limit-requests: <int>` | Logging/stats (if exposed to client). |
| `x-ratelimit-remaining-requests: <int>` | Logging/stats. |
| `x-ratelimit-reset-requests: <ISO8601>` | Logging/stats. |
| `x-ratelimit-limit-tokens: <int>` | Logging/stats. |
| `x-ratelimit-remaining-tokens: <int>` | Logging/stats. |
| `x-ratelimit-reset-tokens: <ISO8601>` | Logging/stats. |

Fewtok passes all rate-limit headers. Does **not** re-manipulate (no proxy-level rate-limiting yet).

### SSE event stream (Messages API)

Claude API uses **Server-Sent Events (SSE)** for streaming.

Events (in order):
1. `message_start` → `message` object with id, role, content, model, usage stub
2. `content_block_start` → `index: 0`, `content_block: {type: "text"}` (or `tool_use`)
3. `content_block_delta` → `delta: {type: "text_delta", text: "..."}` (streaming chunks)
4. `content_block_stop` → (end of block)
5. `message_delta` → final `delta: {stop_reason: "end_turn"}`, usage **update**
6. `message_stop` → (stream end)

**Fewtok's role:**
- **PassA (lazy elision)** patches `message_start.usage` to show compressed input tokens (not raw).
- **PassB (full codec)** expands macro references in `content_block_delta.text` chunks **before** streaming to client (OutputRewriteLayer).
- **Stats** accounts for usage from `message_delta` (not all compressed input expanded; some stays compressed in LLM work).

Example message_start:
```json
{
  "type": "message_start",
  "message": {
    "id": "msg-...",
    "type": "message",
    "role": "assistant",
    "content": [],
    "model": "claude-3-5-sonnet-20241022",
    "stop_reason": null,
    "usage": {
      "input_tokens": <raw count>,
      "output_tokens": 0
    }
  }
}
```

Example content_block_delta:
```json
{
  "type": "content_block_delta",
  "index": 0,
  "delta": {
    "type": "text_delta",
    "text": "some response text..."
  }
}
```

OutputRewriteLayer intercepts `delta.text` chunks, expands macros, passes to client.

---

## Sigil table for claude-3 tokenizer

claude-3 uses proprietary tokenizer. Sigil pool (reserved chars, never collide with real tokens) determined empirically:

**Safe sigils for claude-3:**
- `§§§§` (section sign, U+00A7) — **primary sigil**. Never splits inside content. Safe for macro prefixes.
- `¶` (pilcrow, U+00B6) — alternate. Backup if §§§§ overloads.
- `†` (dagger, U+2020) — backup.
- `‡` (double dagger, U+2021) — backup.

**Unsafe (DO NOT USE):**
- `$` — tokenizes standalone; unsafe for code disambiguation.
- `` ` `` (backtick) — Python/code context sensitive.
- `#` — markdown header; splits tokens.

Allocator uses `§A`, `§B`, ..., `§ZZ` for macro codes (max 676 entries with 2 chars).

Example dict entry for claude-3:
```json
{
  "code": "§A",
  "source": "source string this macro replaces",
  "hash": "<sha256 of source>",
  "freq": 42,
  "tombstone": false
}
```

When encoding: `"source string..."` → `"§A"`
When decoding: `"§A"` → `"source string..."`

---

## Install adapter notes

### Claude Code installation flow

```
$ ft install claude
  1. Detect Claude Code install location (~/.claude or ~/.cursor or editor-specific)
  2. Read ~/.claude/CLAUDE.md (or create if missing)
  3. Inject fewtok session init block (bash/shell snippet)
     - auto-spawn proxy if not running
     - export FEWTOK_PROXY_URL to point to local proxy
     - wrap `claude` CLI with fewtok-aware wrapper
  4. Write modified CLAUDE.md back to disk
  5. Spawn proxy on localhost (auto-port via SO_REUSEADDR)
  6. Export proxy URL to Claude Code process env
  7. Test: send dummy request to proxy to confirm listening
  8. Print summary: "Claude Code → fewtok proxy → Anthropic"
```

### Claude Code wrapper (`cc`)

`cc` wrapper:
1. Sets `x-claude-code-session-id` from Claude Code session UUID (env or `.claude-session` file in project).
2. Forwards `Authorization` header (already set in Claude Code env).
3. Prepends `--proxy-url` flag pointing to fewtok proxy.
4. Invokes real `claude` binary.

File: `src/install/claude.ts`. Must export:
- `detectInstallPath()` — find Claude Code binary on system
- `installProxy()` — write wrapper + inject CLAUDE.md block
- `uninstallProxy()` — revert wrapper + remove CLAUDE.md block

---

## Bun idleTimeout caveat

**Bun HTTP server default:** `idleTimeout: 10s` (close socket after 10s inactivity).

**Fewtok + Claude Code:** Claude Code holds connections >10s (human input, large responses). Idle timeout closes prematurely → "connection reset" errors.

**Fix:** Set `idleTimeout: 300s` (5 min) or higher in fewtok Bun server config.

Code:
```typescript
const server = Bun.serve({
  port: FEWTOK_PORT,
  idleTimeout: 300, // seconds
  fetch(req) {
    // request handler
  }
});
```

Verify: `netstat -tnp | grep <proxy_port>` — connections stay open >10s without reset.

---

## Provider-specific pipeline hooks

### Request → Encode

1. Receive HTTP request from Claude Code client (Messages API call).
2. Extract `x-claude-code-session-id` from headers.
3. ReadCache lookup: `(session_id, request_hash)` → compressed payload (if hit, skip PassA).
4. **PassA (Elision):** Identify repeated spans in `system` + `messages` content.
5. **Codec encode:** Replace spans with macros (`§A`, `§B`, ...).
6. Update `usage.input_tokens` in request to reflect compressed size (PassA only; PassB compresses further).
7. Forward to Anthropic API.

### Response ← Decode

1. Receive SSE stream from Anthropic (message_start, content_block_delta, message_delta, message_stop).
2. Intercept `message_start.usage.input_tokens` → rewrite to show actual compressed input (if PassA modified it).
3. For each `content_block_delta.text` chunk:
   - **OutputRewriteLayer:** Scan for macros (`§A`, `§B`, ...).
   - Expand each macro using CodecSnapshot.
   - Forward expanded chunk to client.
4. Accumulate final usage from `message_delta` for stats.
5. Deliver stream to Claude Code client.

---

## Config / environment

`.fewtok/config.toml` does not exist yet (v1 local install only).

Env vars:
- `FEWTOK_PROXY_URL` — fewtok proxy listening address (set by install adapter). E.g., `http://localhost:9999`.
- `ANTHROPIC_API_KEY` — Anthropic API key (standard). Proxy reads from `Authorization` header; does not use env directly.
- `ANTHROPIC_BASE_URL` — Anthropic API endpoint. Defaults to `https://api.anthropic.com/v1`. Proxy respects if set.

---

## Integrations checklist

- [x] Header passthrough (Authorization, x-claude-code-session-id)
- [x] SSE event stream parsing
- [x] Usage token rewriting (message_start.usage)
- [x] OutputRewriteLayer integration (macro expansion in response chunks)
- [ ] Claude Code install adapter (in-progress, not yet merged)
- [ ] Session UUID detection from Claude Code env
- [ ] `cc` wrapper deployment
- [ ] Rate-limit header forwarding
- [ ] Proxy auto-spawn on first Claude Code request
- [ ] Uninstall flow (graceful cleanup)

---

## Debugging

### Proxy not receiving requests

1. Confirm FEWTOK_PROXY_URL exported: `echo $FEWTOK_PROXY_URL`
2. Confirm proxy listening: `netstat -tlnp | grep <port>`
3. Confirm `cc` wrapper in PATH: `which cc`
4. Confirm wrapper is calling proxy: `strace -e connect cc some-prompt` (look for localhost:<port>)

### Usage tokens not matching

1. Check PassA enabled: `ft gain` should show `input_tokens_compressed < input_tokens_raw`.
2. Check `message_start.usage` rewritten: tail proxy logs, grep `message_start`, confirm usage field changed.
3. Check OutputRewriteLayer not removing text: stream entire response to file, check char count vs. expansion count.

### Macros not expanding in output

1. Confirm macro codes in response: `grep -o '§§§§[A-Z]*'` in captured stream.
2. Confirm CodecSnapshot has codes: `ft doctor` → check dict hit rate.
3. Confirm OutputRewriteLayer running: check proxy logs for "expanding <code>".

---

## Useful links

- Anthropic Messages API: https://docs.anthropic.com/en/api/messages
- Claude Code repo: https://github.com/anthropics/claude-code (if public)
- SSE spec: https://html.spec.whatwg.org/multipage/server-sent-events.html

---

## Learned Rules

### sse-event-not-data-prefixed | fired:1 | 2026-05-21
Spec/plan/subagent assumed Anthropic SSE events start with `data:`. Real wire = `event: TYPE\ndata: {...}\n\n` — two lines. New parser used `rawEvent.startsWith("data: ")` → always false → state machine never advanced → every delta leaked verbatim. Sibling `sseRewriteStream.ts` in same dir handled this with `indexOf("data: ")` but cross-check skipped.
Prevent: writing/modifying SSE event parser in fewtok → grep existing parsers (`sseRewriteStream.ts`, `sse-usage.ts`) for `data:` extraction shape first; mirror it. Anthropic event raw bytes = `event: <type>\ndata: <json>\n\n`. Never use `rawEvent.startsWith("data:")` — scan lines for `data:` prefix or use `indexOf`.