@warlock.js/ai-anthropic 5.30.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +58 -6
  2. package/cjs/index.cjs +847 -138
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/config.type.d.mts +117 -8
  5. package/esm/config.type.d.mts.map +1 -1
  6. package/esm/index.d.mts +9 -3
  7. package/esm/index.mjs +7 -1
  8. package/esm/known-output-limits.mjs +46 -0
  9. package/esm/known-output-limits.mjs.map +1 -0
  10. package/esm/known-pricing.d.mts +63 -0
  11. package/esm/known-pricing.d.mts.map +1 -0
  12. package/esm/known-pricing.mjs +172 -0
  13. package/esm/known-pricing.mjs.map +1 -0
  14. package/esm/model.d.mts +90 -20
  15. package/esm/model.d.mts.map +1 -1
  16. package/esm/model.mjs +389 -88
  17. package/esm/model.mjs.map +1 -1
  18. package/esm/request.type.d.mts +23 -0
  19. package/esm/request.type.d.mts.map +1 -0
  20. package/esm/sdk.d.mts +1 -3
  21. package/esm/sdk.d.mts.map +1 -1
  22. package/esm/thinking-mode.d.mts +14 -2
  23. package/esm/thinking-mode.d.mts.map +1 -1
  24. package/esm/thinking-mode.mjs +102 -2
  25. package/esm/thinking-mode.mjs.map +1 -1
  26. package/esm/utils/cache-usage.mjs +62 -0
  27. package/esm/utils/cache-usage.mjs.map +1 -0
  28. package/esm/utils/index.d.mts +6 -0
  29. package/esm/utils/index.mjs +1 -1
  30. package/esm/utils/map-stop-reason.d.mts +1 -0
  31. package/esm/utils/provider-blocks.d.mts +37 -0
  32. package/esm/utils/provider-blocks.d.mts.map +1 -0
  33. package/esm/utils/provider-blocks.mjs +3 -3
  34. package/esm/utils/provider-blocks.mjs.map +1 -1
  35. package/esm/utils/server-tool-usage.mjs +17 -0
  36. package/esm/utils/server-tool-usage.mjs.map +1 -0
  37. package/esm/utils/to-anthropic-messages.d.mts +69 -0
  38. package/esm/utils/to-anthropic-messages.d.mts.map +1 -0
  39. package/esm/utils/to-anthropic-messages.mjs +12 -4
  40. package/esm/utils/to-anthropic-messages.mjs.map +1 -1
  41. package/esm/utils/to-anthropic-tools.d.mts +17 -0
  42. package/esm/utils/to-anthropic-tools.d.mts.map +1 -0
  43. package/esm/utils/wrap-anthropic-error.d.mts +1 -0
  44. package/esm/utils/wrap-anthropic-error.mjs +6 -1
  45. package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
  46. package/llms-full.txt +248 -32
  47. package/llms.txt +1 -1
  48. package/package.json +3 -3
  49. package/skills/README.md +1 -1
  50. package/skills/setup-anthropic/SKILL.md +248 -32
package/llms-full.txt CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ---
10
10
  name: setup-anthropic
11
- description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl (per-call ttl), provider blocks (thinking/compaction) replayed verbatim via ModelResponse.blocks / Message.blocks, providerOptions.anthropic (betas, body fields such as context_management / fallbacks, thinkingDisplay), model.countTokens(), distinct finish reasons (pause_turn, refusal, context_exceeded), cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `providerOptions`, `blocks`, `countTokens`, `pause_turn`, `refusal`, `context_management`, `compaction`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — the `setup-openai` topic of the `warlock-js-ai-openai` skill; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
11
+ description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?, maxOutputTokens?}). System-prompt hoisting, max_tokens required (default: model max output when streaming, 16384 non-streaming; maxOutputTokens for gateway ids), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl (per-call ttl), provider blocks (thinking/compaction) replayed verbatim via ModelResponse.blocks / Message.blocks, providerOptions.anthropic (betas, body fields such as context_management / fallbacks, thinkingDisplay), model.countTokens(), distinct finish reasons (pause_turn, refusal, context_exceeded), built-in Claude list prices (ANTHROPIC_MODEL_PRICING, anthropicPricingFor, withAnthropicCacheWriteRates), cost-truth usage (inputIncludesCache: false, cachedTokens/cacheWriteTokens/cacheWrite1hTokens, usage.cost on complete()), ProviderOverloadedError for 529, no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `maxOutputTokens`, `max_tokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `providerOptions`, `blocks`, `countTokens`, `pause_turn`, `refusal`, `context_management`, `contextManagement`, `serverCompaction`, `clear_tool_uses`, `clear_thinking`, `thinkingBlockBinding`, `block_binding`, `preservedThinking`, `ai.providerTool`, `web_search_20250305`, `bash_20250124`, `text_editor_20250728`, `streamChunks`, `thinking_delta`, `compaction`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `cacheWrite1hTokens`, `inputIncludesCache`, `ANTHROPIC_MODEL_PRICING`, `ProviderOverloadedError`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — the `setup-openai` topic of the `warlock-js-ai-openai` skill; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
12
12
  ---
13
13
 
14
14
  # `@warlock.js/ai-anthropic`
@@ -55,41 +55,95 @@ Explicit config always wins (`structuredOutput`, `vision`, `reasoning`, `thinkin
55
55
 
56
56
  ## Pricing & cost
57
57
 
58
- Pricing is opt-in. Supply USD-per-million-token rates and every report this SDK's models produce carries `Usage.cost`; omit them and `cost` stays `undefined` (honest absence, never a false zero). Two declaration sites:
58
+ Every model is priced out of the box from the built-in Claude list prices; supply your own rates to override them. Pricing reaches `Usage.cost` on every agent report **and** on the `usage` of a direct `model.complete()` / the streaming `done` chunk, so side calls outside an agent are costed too. Unknown ids stay unpriced (`cost` is `undefined` — honest absence, never a false zero).
59
+
60
+ Resolution at `model()` time: per-model `pricing` > SDK registry entry for that name > built-in `ANTHROPIC_MODEL_PRICING` (alias or dated snapshot id) > `undefined`.
59
61
 
60
62
  ```ts
61
- // SDK-level registry — one source of truth, keyed by model name:
63
+ import {
64
+ ANTHROPIC_MODEL_PRICING,
65
+ AnthropicSDK,
66
+ anthropicPricingFor,
67
+ withAnthropicCacheWriteRates,
68
+ } from "@warlock.js/ai-anthropic";
69
+
62
70
  const anthropic = new AnthropicSDK({
63
- apiKey,
64
- pricing: {
65
- "claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1 },
66
- "claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3 },
67
- },
71
+ apiKey: process.env.ANTHROPIC_API_KEY ?? "",
72
+ // SDK-level registry — wins over the built-in table, keyed by model name.
73
+ pricing: { "claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3 } },
68
74
  });
69
75
 
70
- // Per-model override (multi-tenant / contract-specific rates):
71
- anthropic.model({ name: "claude-sonnet-4-6", pricing: { input: 3, output: 15 } });
76
+ anthropic.model({ name: "claude-opus-5-5" }).pricing; // built-in: { input: 4, output: 20, cachedInput: 0.2, cacheWrite: 5, cacheWrite1h: 8 }
77
+ anthropic.model({ name: "claude-sonnet-4-6" }).pricing; // registry entry + write rates filled: cacheWrite 3.75, cacheWrite1h 6
78
+ anthropic.model({ name: "claude-haiku-4-5", pricing: { input: 1, output: 5 } }); // per-model override
79
+
80
+ anthropicPricingFor("claude-haiku-4-5-20251001"); // dated snapshot → the alias entry
81
+ withAnthropicCacheWriteRates({ input: 2, output: 10 }); // { input: 2, output: 10, cacheWrite: 2.5, cacheWrite1h: 4 }
82
+ Object.keys(ANTHROPIC_MODEL_PRICING); // the priced ids
72
83
  ```
73
84
 
74
- Resolution at `model()` time: per-model `pricing` > SDK registry entry for that name > `undefined`. `ModelPricing` is `{ input, output, cachedInput?, cachedOutput? }`.
85
+ - **Source.** `ANTHROPIC_MODEL_PRICING` holds the "Model pricing" table of https://platform.claude.com/docs/en/about-claude/pricing (fetched 2026-10-17): input, output, cache read (`cachedInput`), 5-minute write (`cacheWrite`) and 1-hour write (`cacheWrite1h`), USD per 1M tokens, standard global routing.
86
+ - **Write rates are filled for you.** Anthropic charges 1.25x input for a 5-minute cache write and 2x for a 1-hour write on every Claude model, so any entry without `cacheWrite` / `cacheWrite1h` (your own `{ input, output }` included) gets them from those multipliers (`withAnthropicCacheWriteRates`, exported with `ANTHROPIC_CACHE_WRITE_MULTIPLIER` / `ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER`). The read rate differs per model (0.1x, 0.05x on Opus 5.5 / Sonnet 5.5, 0.025x on Fable 5.1), so it is never derived — set `cachedInput` yourself when you supply pricing, or cache reads bill at the full input rate.
87
+ - **Deliberately unpriced** (pass `pricing` yourself): Claude Haiku 5.5 (priced by prompt length — higher rates above 100,000 prompt tokens, which a flat rate cannot express), US-only inference (`inference_geo: "us"`, 1.1x), fast mode, the Batch API, limited-availability and retired models, and Claude on Bedrock / Vertex (partner-priced — see `@warlock.js/ai-bedrock`).
75
88
 
76
89
  ## Tokens & usage accounting
77
90
 
78
- Every `ModelResponse.usage` (and the streaming terminal `done`) is normalized to the neutral `Usage` shape. Anthropic reports `input_tokens` / `output_tokens` separately with no pre-summed total, so `total` is computed (`input + output`). The two cache channels are surfaced only when non-zero:
91
+ Every `ModelResponse.usage` (and the streaming terminal `done`) is normalized to the neutral `Usage` shape. Anthropic's `input_tokens` is the **uncached remainder only** — cache reads and writes come on top of it — so the adapter marks the usage `inputIncludesCache: false` and `computeCost` bills each channel exactly once. `total` is computed (`input + output`); the full prompt size is `input + cachedTokens + cacheWriteTokens`.
92
+
93
+ | Neutral field | Anthropic source | Meaning / billing |
94
+ | -------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | `usage.input` | `input_tokens` | Uncached input. Bills at `input`. |
96
+ | `usage.inputIncludesCache` | — (always `false`) | Tells `computeCost` not to subtract cache tokens from `input`. |
97
+ | `usage.cachedTokens` | `cache_read_input_tokens` | Tokens read from the prompt cache. Bills at `cachedInput` (falls back to `input` when unset). |
98
+ | `usage.cacheWriteTokens` | `cache_creation_input_tokens` | Tokens **written** to the cache. Bills at `cacheWrite` (5-minute, 1.25x input). |
99
+ | `usage.cacheWrite1hTokens` | `cache_creation.ephemeral_1h_input_tokens` | The 1-hour share of the writes. Bills at `cacheWrite1h` (2x input). Without the breakdown, all writes count as 1-hour when every marker asked for `"1h"`. |
100
+ | `usage.reasoningTokens` | `output_tokens_details.thinking_tokens` | The thinking subset of `output_tokens`, informational; billed inside `output`. |
101
+ | `usage.cost` | computed | Per-channel USD (`input`, `output`, `cachedInput`, `cacheWrite`) when the model is priced. Sum with `totalCost(usage.cost)` from `@warlock.js/ai`. |
102
+
103
+ ```ts
104
+ import { totalCost } from "@warlock.js/ai";
105
+ import { AnthropicSDK } from "@warlock.js/ai-anthropic";
106
+
107
+ const model = new AnthropicSDK({ apiKey: process.env.ANTHROPIC_API_KEY ?? "" }).model({
108
+ name: "claude-sonnet-4-6",
109
+ });
110
+
111
+ const { usage } = await model.complete([{ role: "user", content: "Summarise this thread." }]);
79
112
 
80
- | Neutral field | Anthropic source | Meaning / billing |
81
- | ------------------------ | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
82
- | `usage.cachedTokens` | `cache_read_input_tokens` | Subset of input served from the prompt cache. Bills at `cachedInput` (falls back to `input` when unset). |
83
- | `usage.cacheWriteTokens` | `cache_creation_input_tokens` | Input tokens **written** to the cache on this call (the ~1.25x write surcharge). Bills at `cachedOutput` when set. |
84
- | `usage.reasoningTokens` | — _(not reported separately)_ | Anthropic bills extended-thinking tokens **inside** `output_tokens`, so this stays unset — populating it would double-count against `output`. |
113
+ // 100 uncached + 50,000 read + 20,000 written + 500 output is billed as
114
+ // 100×3 + 50,000×0.3 + 20,000×3.75 + 500×15 (USD per 1M tokens).
115
+ console.log(
116
+ usage.inputIncludesCache,
117
+ usage.cachedTokens,
118
+ usage.cacheWriteTokens,
119
+ totalCost(usage.cost),
120
+ );
121
+ ```
85
122
 
86
- In streaming, `cachedTokens` / `cacheWriteTokens` are seeded from `message_start` and then **overwritten by the cumulative counts on `message_delta`** when those are present and non-zero, so the terminal `done` carries the final tally.
123
+ With server-side compaction (`serverCompaction`), the `compaction` entries of `usage.iterations` are added to `input` / `output` / the cache counts, since the top-level counts leave the compaction step out.
124
+
125
+ In streaming, the cache counters are seeded from `message_start` (including the 1-hour breakdown) and then **overwritten by the cumulative counts on `message_delta`** when present and non-zero, so the terminal `done` carries the final tally and its `cost`.
87
126
 
88
127
  ## `max_tokens` is required
89
128
 
90
- Unlike OpenAI, Anthropic **requires** `max_tokens` on every request. Resolution: per-call `options.maxTokens` > `config.maxTokens` > **default `4096`**. A caller who never sets a cap still gets a complete answer instead of a 400.
129
+ Unlike OpenAI, Anthropic **requires** `max_tokens` on every request. Resolution: per-call `options.maxTokens` (or the agent's per-run / configured `modelOptions.maxTokens`) > `config.maxTokens` > a default sized for agent work:
130
+
131
+ | Request | Default `max_tokens` |
132
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------- |
133
+ | Streaming (`stream()`) | the model's documented max output: 128K (Claude 5 family, Opus 4.6–4.8, Sonnet 4.6), 64K (Opus / Sonnet / Haiku 4.5) |
134
+ | Non-streaming (`complete()`) | `16384`, capped by the model's max output |
135
+ | Unknown id | `16384`; set `maxOutputTokens` on the model for gateway / proxy ids |
136
+
137
+ The non-streaming default stays at 16384 because `@anthropic-ai/sdk` refuses a non-streaming request whose `max_tokens` implies more than ten minutes (above ~21K tokens) unless the client sets a `timeout`. Limits come from Anthropic's model pages (checked 2026-10-17).
138
+
139
+ ```ts
140
+ anthropic.model({ name: "claude-haiku-4-5" }); // stream 64000, complete 16384
141
+ anthropic.model({ name: "my-gateway/opus", maxOutputTokens: 128_000 });
142
+ ```
143
+
144
+ With extended thinking enabled and no explicit cap, the default keeps room for the thinking budget plus 4096 answer tokens (never above the model's max output). In budget mode an explicit `maxTokens` must be greater than `budget_tokens` or the call throws before it reaches Anthropic.
91
145
 
92
- With extended thinking enabled and no explicit cap, the adapter reserves answer room in both modes: `max_tokens = thinking budget + 4096`. In budget mode an explicit `maxTokens` must be greater than `budget_tokens` or the call throws before it reaches Anthropic.
146
+ A response that still hits the cap (`finishReason: "length"`) ends an agent run as incomplete (`AgentIncompleteError`) unless the agent sets `continueOnOutputLimit`.
93
147
 
94
148
  **Tiny caps + adaptive thinking:** thinking tokens count inside `max_tokens`, so a very small cap can be spent entirely on thinking. Live check: Sonnet 5.5 with `maxTokens: 60` returned `finishReason: "length"` and empty content. For tiny outputs give thinking room (raise `maxTokens`) or turn reasoning off (`reasoning: false` on the model, or no `options.reasoning`).
95
149
 
@@ -121,7 +175,7 @@ Adaptive models reject `budget_tokens` with a 400, so the shape matters.
121
175
 
122
176
  - **Adaptive:** `reasoning.effort` (`"low" | "medium" | "high"`) → `output_config.effort` of the same name. With only `reasoning.maxTokens` set, effort is `"medium"`; `maxTokens` then only sizes the reserved `max_tokens` room, it is not sent as a budget.
123
177
  - **Budget:** `reasoning.maxTokens` → `budget_tokens` verbatim; `reasoning.effort` → a tiered budget (`1024` / `4096` / `12000`) when no `maxTokens` is given. Any resolved budget is floored at Anthropic's `1024` minimum.
124
- - `reasoning` with neither `effort` nor `maxTokens`, or `effort: "none"`, emits nothing — except `effort: "none"` on Claude Sonnet 5.5, which sends `thinking: { type: "between_tools" }` (see below).
178
+ - `reasoning` with neither `effort` nor `maxTokens` emits nothing. `effort: "none"` emits nothing either, except on the Claude 5 models listed below (Sonnet 5.5, Sonnet 5, Haiku 5.5, Opus 5), which each get their own form.
125
179
 
126
180
  ### `thinkingMode` override
127
181
 
@@ -145,21 +199,40 @@ anthropic.model({ name: "claude-sonnet-4-6", thinkingMode: "adaptive" }); // opt
145
199
  `temperature` is **not sent** to models after Opus 4.6 (adaptive mode) — they reject any non-default value with a 400. It is also dropped on any request that enables thinking, since Anthropic 400s on thinking plus an explicit temperature. So it is forwarded only in budget mode on requests without thinking; `thinkingMode` decides this too. Set `reasoning: false` on the model config to suppress the thinking param entirely for proxied/legacy targets.
146
200
 
147
201
  ```ts
148
- await agent.execute("Prove it.", { reasoning: { effort: "high" } });
202
+ await agent.execute("Prove it.", { modelOptions: { reasoning: { effort: "high" } } });
149
203
  // adaptive model: thinking { type: "adaptive" }, output_config.effort "high"
150
204
  // budget model: thinking { type: "enabled", budget_tokens: 12000 }
151
205
  await model.complete(messages, { reasoning: { maxTokens: 8000 } });
152
206
  // adaptive model: effort "medium"; budget model: budget_tokens 8000
153
207
  ```
154
208
 
155
- **Claude 5 family (Opus 5.5, Sonnet 5.5, Fable 5 / 5.1):** thinking is always on. These models reject `thinking: { type: "disabled" }` and `budget_tokens` with a 400, so the adapter never sends either: they are adaptive and `reasoning.maxTokens` only sizes `max_tokens`. `effort: "none"` is mapped per model:
209
+ **Claude 5 family:** adaptive thinking runs by default and `budget_tokens` is a 400, so `reasoning.maxTokens` only sizes `max_tokens`. `effort: "none"` is mapped per model:
156
210
 
157
211
  | Model | `effort: "none"` sends | Result |
158
212
  | ----------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
159
213
  | Sonnet 5.5 | `thinking: { type: "between_tools" }`, alone (no `output_config.effort`, no `display`) | Thinking off. `between_tools` takes no other field and needs effort `high` or below; the model default, `high`, applies. |
214
+ | Sonnet 5, Haiku 5.5 | `thinking: { type: "disabled" }`, alone (no `output_config.effort`, no `display`) | Thinking off. Haiku 5.5 accepts `disabled` only at effort `high` or below; its default, `medium`, applies. |
215
+ | Opus 5 | `thinking: { type: "adaptive" }` + `output_config.effort: "low"` | Thinking stays on at the lowest effort: with `disabled`, Opus 5 can emit a tool call as plain text (never run) and leak thinking tags. |
160
216
  | Opus 5.5, Fable 5 / 5.1 | no `thinking` field | No thinking-off form exists, so the model still thinks at its default effort (Opus 5.5: `medium`). Pass `effort: "low"` to think less. |
161
217
 
162
- `thinkingDisplay` is applied only to adaptive thinking, never beside `between_tools`.
218
+ `thinkingDisplay` is applied only to adaptive thinking, never beside `between_tools` or `disabled`.
219
+
220
+ ```ts
221
+ import { AnthropicSDK } from "@warlock.js/ai-anthropic";
222
+
223
+ const anthropic = new AnthropicSDK({ apiKey: process.env.ANTHROPIC_API_KEY! });
224
+ const messages = [{ role: "user" as const, content: "Classify: great product!" }];
225
+
226
+ // Sonnet 5 / Haiku 5.5 → thinking: { type: "disabled" }
227
+ await anthropic.model({ name: "claude-haiku-5-5" }).complete(messages, {
228
+ reasoning: { effort: "none" },
229
+ });
230
+
231
+ // Opus 5 → thinking: { type: "adaptive" }, output_config: { effort: "low" }
232
+ await anthropic.model({ name: "claude-opus-5" }).complete(messages, {
233
+ reasoning: { effort: "none" },
234
+ });
235
+ ```
163
236
 
164
237
  `thinking.display` is set with `providerOptions.anthropic.thinkingDisplay` (`"summarized"` for readable summaries; the 5-family default is `"omitted"`, empty thinking text). It is applied whenever the adapter sends `thinking` to an adaptive model, so pair it with a `reasoning` option.
165
238
 
@@ -184,30 +257,52 @@ history = [
184
257
  ];
185
258
  ```
186
259
 
260
+ Or let `ai.session({ id, store, agent })` store the turns for you (the `keep-agent-conversations` topic of the `warlock-js-ai` skill).
261
+
187
262
  ## Provider options (`providerOptions.anthropic`)
188
263
 
189
264
  Anthropic-only request options, per call or in the agent's `modelOptions`. Other adapters ignore the key. Type: `AnthropicCallOptions`.
190
265
 
191
- | Field | Effect |
192
- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
193
- | `betas` | Beta flags. When non-empty the request goes through `client.beta.messages` (needed for beta body fields and beta blocks such as `compaction`). |
194
- | `body` | Extra body fields merged **last** (caller wins): `context_management`, `fallbacks`, `inference_geo`, `tool_choice`, … Not validated, except that `model`, `messages`, `system`, `tools`, `mcp_servers` and `stream` are refused (`InvalidRequestError`). |
195
- | `thinkingDisplay` | `thinking.display` on adaptive models: `"summarized"`, `"omitted"`, `"updates"` (the last needs its beta). |
266
+ | Field | Effect |
267
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
268
+ | `betas` | Beta flags. When non-empty the request goes through `client.beta.messages` (needed for beta body fields and beta blocks such as `compaction`). |
269
+ | `body` | Extra body fields merged **last** (caller wins): `context_management`, `fallbacks`, `inference_geo`, `tool_choice`, … Not validated, except that `model`, `messages`, `system`, `tools`, `mcp_servers` and `stream` are refused (`InvalidRequestError`). |
270
+ | `thinkingDisplay` | `thinking.display` on adaptive models: `"summarized"`, `"omitted"`, `"updates"` (the last needs its beta). |
271
+ | `contextManagement` | Typed context editing: `clearToolUses` (`trigger: { inputTokens } \| { toolUses }`, `keep`, `clearAtLeast`, `excludeTools`, `clearToolInputs`) → `clear_tool_uses_20250919`; `clearThinking` (`keep: n \| "all"`) → `clear_thinking_20251015`, listed first. Adds beta `context-management-2025-06-27`. |
272
+ | `serverCompaction` | `true` or `{ triggerTokens (≥ 50,000; API default 150,000), instructions }` → `compact_20260112`, beta `compact-2026-01-12`. Can't be combined with `body.context_management` (`InvalidRequestError`). |
273
+ | `thinkingBlockBinding` | `"error"` \| `"drop_block"` → `thinking.block_binding.prefix_mismatch_behavior` + beta `thinking-binding-controls-2026-08-01`; adaptive or budget thinking only (skipped with a debug log on `between_tools`). |
196
274
 
197
275
  ```ts
198
276
  const agent = ai.agent({
199
277
  model: anthropic.model({ name: "claude-opus-5-5" }),
278
+ tools,
200
279
  modelOptions: {
201
280
  providerOptions: {
202
281
  anthropic: {
203
- betas: ["compact-2026-01-12"],
204
- body: { context_management: { edits: [{ type: "compact_20260112" }] } },
282
+ contextManagement: { clearToolUses: { trigger: { inputTokens: 100_000 }, keep: 5 } },
283
+ serverCompaction: { triggerTokens: 200_000 },
205
284
  },
206
285
  },
207
286
  },
208
287
  });
209
288
  ```
210
289
 
290
+ The response then starts with a `compaction` block, kept on `blocks` and replayed (the API ignores everything before it). Compaction tokens are billed outside the top-level `input_tokens` / `output_tokens`; the adapter adds the `compaction` entries of `usage.iterations` into `usage`. Leave `pause_after_compaction` off inside an agent: a paused response reads as a final stop.
291
+
292
+ ### Preserved thinking after client-side compaction
293
+
294
+ Claude Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5 accept a replayed thinking block only while everything before it is unchanged. The agent's own compaction marks turns it kept after a summary `compaction: "retained"`; on those models (inferred, or `anthropic.model({ name, preservedThinking: true })`) the adapter replays them **without** `thinking` / `redacted_thinking` blocks. Other models (Opus 5, Sonnet 5, Fable 5, Mythos 5.1, 4.x, budget mode) keep them. Setting `thinkingBlockBinding` disables the stripping and lets the API drop (`"drop_block"`) or reject (`"error"`) instead — also the way to recover a stored conversation someone edited:
295
+
296
+ ```ts
297
+ const recovering = ai.agent({
298
+ model: anthropic.model({ name: "claude-opus-5-5" }),
299
+ modelOptions: { providerOptions: { anthropic: { thinkingBlockBinding: "drop_block" } } },
300
+ });
301
+ await recovering.execute(question, { history });
302
+
303
+ inferPreservedThinking("claude-opus-5-5"); // true (exported helper)
304
+ ```
305
+
211
306
  Cache TTL per call: `options.cacheControl.ttl` (`"5m"` | `"1h"`) overrides the model's `promptCacheTtl` for that call.
212
307
 
213
308
  ## Prompt caching (`cache_control`)
@@ -248,6 +343,30 @@ await agent.execute("…", { cacheControl: { breakpoints: 1 } }); // cache the s
248
343
  - `tool_use` blocks accumulate `input_json_delta` fragments and emit one consolidated `{ type: "tool-call", ... }` at `content_block_stop` (no partial-arg `{}` artifact — input is parsed once, whole)
249
344
  - every content block is also assembled by index (text, thinking + signature, server-tool input, beta `compaction_delta`) and returned as `done.blocks` when the turn has a non-neutral block
250
345
  - terminal `{ type: "done", finishReason, usage, blocks? }` — `usage.input` from `message_start`, `usage.output` from `message_delta`, `total` computed; `cachedTokens` / `cacheWriteTokens` from `message_start` then the cumulative `message_delta` counts (see [Tokens & usage accounting](#tokens--usage-accounting))
346
+ - opt-in (`options.streamChunks`, set by `agent.stream()`): `{ type: "reasoning", delta }` per non-empty `thinking_delta`, `{ type: "tool-args", callId, name, delta }` per `input_json_delta` of a client `tool_use` block (never for `server_tool_use`), `{ type: "usage", usage }` after `message_start` and each `message_delta` (running totals). Claude 5 models stream thinking text only with `providerOptions.anthropic.thinkingDisplay: "summarized"`.
347
+
348
+ ```ts
349
+ for await (const chunk of model.stream(messages, { streamChunks: true, reasoning: { effort: "high" } })) {
350
+ if (chunk.type === "reasoning") process.stderr.write(chunk.delta);
351
+ }
352
+ ```
353
+
354
+ ## Provider-defined tools
355
+
356
+ `ai.providerTool({ provider: "anthropic", definition })` entries are sent verbatim, before the function tools (the prompt-cache breakpoint stays on the last function tool). Server tools (`web_search_20250305`, `web_fetch_20250910`, `code_execution_20250825`) run inside the response and their `server_tool_use` / `*_tool_result` blocks round-trip via `blocks`. Client tools (`bash_20250124`, `text_editor_20250728` named `str_replace_based_edit_tool`, `memory_20250818`) come back as `tool_use` and dispatch to the provider tool's `execute`. Other providers' tools are skipped with a `providerTool.skipped` warning. `providerOptions.anthropic.body` still cannot set `tools`.
357
+
358
+ ```ts
359
+ const webSearch = ai.providerTool({
360
+ provider: "anthropic",
361
+ definition: { type: "web_search_20250305", name: "web_search", max_uses: 5 },
362
+ });
363
+ const editor = ai.providerTool({
364
+ provider: "anthropic",
365
+ definition: { type: "text_editor_20250728", name: "str_replace_based_edit_tool" },
366
+ execute: async (input: { command: string; path: string }) => files.edit(input),
367
+ });
368
+ ai.agent({ model: anthropic.model({ name: "claude-sonnet-4-6" }), tools: [webSearch, editor] });
369
+ ```
251
370
 
252
371
  ## Finish-reason mapping
253
372
 
@@ -263,9 +382,72 @@ Raw SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hierarchy.
263
382
  - `rate_limit_error` / 429 → `ProviderRateLimitError` (+`retryAfter` from `retry-after`)
264
383
  - `billing_error` → `QuotaExceededError`
265
384
  - `invalid_request_error` with "prompt is too long" → `ContextLengthExceededError`, else `InvalidRequestError`
266
- - 5xx / overloaded → `ProviderError`
385
+ - `overloaded_error` / 529 → `ProviderOverloadedError` (`code: "PROVIDER_OVERLOADED"`, `retryable: true`, `retryAfter` from `retry-after`). Also raised for an `overloaded_error` delivered mid-stream as an SSE `error` event, which has no `status`.
386
+ - other 5xx → `ProviderError` (`retryable: true`)
267
387
  - Connection timeouts → `ProviderTimeoutError`
268
388
 
389
+ Every wrapped error carries `status` (the HTTP status, when there was one), `retryable`, and `billed`: `false` for 401 / 402 / 403 (the request never reached a billable account), `"unknown"` otherwise — Anthropic publishes no rule for failed requests, so treat them as possibly billed when you settle a budget. `fallbackModel` fails over on `PROVIDER_OVERLOADED` by default.
390
+
391
+ ```ts
392
+ import { ai, ProviderOverloadedError } from "@warlock.js/ai";
393
+ import { AnthropicSDK } from "@warlock.js/ai-anthropic";
394
+
395
+ const anthropic = new AnthropicSDK({ apiKey: process.env.ANTHROPIC_API_KEY ?? "" });
396
+ const agent = ai.agent({ model: anthropic.model({ name: "claude-sonnet-4-6" }) });
397
+
398
+ const result = await agent.execute("hi");
399
+
400
+ if (result.error instanceof ProviderOverloadedError) {
401
+ console.log(
402
+ result.error.status,
403
+ result.error.retryable,
404
+ result.error.billed,
405
+ result.error.retryAfter,
406
+ );
407
+ }
408
+ ```
409
+
410
+ ## Build and send an audited request
411
+
412
+ `model.buildRequest(messages, options?)` is synchronous and makes no network call.
413
+ It returns a detached `{ endpoint, body, betas }` snapshot, including thinking,
414
+ cache markers, provider body options and merged tool results. `body.stream` is
415
+ `false` by default; pass `{ ...options, stream: true }` to build a streaming request.
416
+ `betas` identifies the beta header flags; the SDK receives these separately from
417
+ its serialized Messages body.
418
+
419
+ ```ts
420
+ const request = model.buildRequest(messages, options);
421
+ const bodyJSON = JSON.stringify(request.body);
422
+ // Hash or reserve spend against bodyJSON before sending.
423
+ const response = await model.send(request, { signal });
424
+
425
+ const streamingRequest = model.buildRequest(messages, { ...options, stream: true });
426
+ for await (const chunk of model.send(streamingRequest, { signal })) {
427
+ // Consume the same neutral chunks as model.stream().
428
+ }
429
+ ```
430
+
431
+ `send()` uses the built endpoint, body and beta flags without rebuilding the
432
+ conversation or applying new defaults. It passes `betas` only when `endpoint` is
433
+ `"beta.messages"`: a request whose `endpoint` is `"messages"` is sent without
434
+ them, even if `betas` was edited by hand. Transport cancellation stays separate
435
+ from the audited body. `complete()`, `stream()` and `countTokens()` use this same
436
+ build path and always select their respective request modes. `buildRequest()`
437
+ throws build failures raw (an `InvalidRequestError` for a reserved `body` key, a
438
+ `DataCloneError` for a `body` value `structuredClone` cannot copy, such as a
439
+ function); `complete()`, `stream()` and `countTokens()` log them like send
440
+ failures and reject with an `AIError`, wrapping anything else in an
441
+ `InvalidRequestError`.
442
+ Treat the snapshot as read-only after hashing it; SDK JSON serialization and beta
443
+ header handling still apply.
444
+
445
+ Hosts with their own transport can import `toAnthropicMessages`,
446
+ `toAnthropicTools`, `toProviderBlocks`, `fromProviderBlocks`,
447
+ `ANTHROPIC_BLOCK_FORMAT` and the `AnthropicMessages` type from the package root.
448
+ `fromProviderBlocks(blocks, toolCalls)` retains the adapter's assistant-block
449
+ allowlist and tool-id validation.
450
+
269
451
  ## Token counting
270
452
 
271
453
  ```ts
@@ -275,6 +457,13 @@ const model = anthropic.model({ name: "claude-opus-5-5" }); // an AnthropicModel
275
457
  await model.countTokens(messages, { tools }); // exact, via messages.countTokens (network)
276
458
  ```
277
459
 
460
+ For budget preflight, `countInputTokens(model, messages, { tools })` from
461
+ `@warlock.js/ai` returns `{ tokens, source }`. It estimates only when native
462
+ counting is absent or explicitly throws `InvalidRequestError` with
463
+ `reason: "count-unsupported"`; this adapter preserves that signal. System and
464
+ tools are supported natively. Ordinary validation, auth, network and rate-limit
465
+ errors propagate. See the cost/budget guide for reserve mode.
466
+
278
467
  `countTokens` sends the same system prompt, tools, thinking and `providerOptions.anthropic.betas` a real call would, keeping only the fields the endpoint accepts.
279
468
 
280
469
  ## No embeddings
@@ -292,4 +481,31 @@ Anthropic ships no first-party embeddings API, so `AnthropicSDK` intentionally d
292
481
  - The `pick-ai-provider` topic of the `warlock-js-ai` skill
293
482
  - The `handle-ai-errors` topic of the `warlock-js-ai` skill
294
483
 
484
+ ## Hosted-search usage and fees
485
+
486
+ `complete()` and streaming `done` map the provider's
487
+ `usage.server_tool_use.web_search_requests` / `web_fetch_requests` to
488
+ `usage.serverToolUse.webSearchRequests` / `webFetchRequests`. Streaming
489
+ `message_delta` counts are cumulative snapshots, never added twice. Raw
490
+ `server_tool_use` and search-result blocks remain in `response.blocks` and are
491
+ replayed in history; they never invoke local function handlers.
492
+
493
+ To add request fees to token costs, explicitly compose the verified
494
+ `ANTHROPIC_SERVER_TOOL_PRICING` export into model `pricing.serverTools`:
495
+
496
+ ```ts
497
+ import { ANTHROPIC_SERVER_TOOL_PRICING } from "@warlock.js/ai-anthropic";
498
+ const model = anthropic.model({
499
+ name: "claude-sonnet-4-6",
500
+ pricing: { input: 3, output: 15, serverTools: ANTHROPIC_SERVER_TOOL_PRICING },
501
+ });
502
+ ```
503
+
504
+ The [search fee](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool#usage-and-pricing)
505
+ is $0.01 per search; [fetch](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool#usage-and-pricing)
506
+ has no additional request fee (verified 2026-10-18). Tokens remain additional.
507
+ No code-execution request count is inferred from blocks. See
508
+ [hosted-tool costs](https://warlock.js.org/v/latest/ai/recipes/cost-tracking/#hosted-tool-request-fees)
509
+ and [fee reservations](https://warlock.js.org/v/latest/ai/recipes/cost-budgets-and-slo/#reserve-hosted-search-fees).
510
+
295
511
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [setup-anthropic](@warlock.js/ai-anthropic/setup-anthropic/SKILL.md): Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl (per-call ttl), provider blocks (thinking/compaction) replayed verbatim via ModelResponse.blocks / Message.blocks, providerOptions.anthropic (betas, body fields such as context_management / fallbacks, thinkingDisplay), model.countTokens(), distinct finish reasons (pause_turn, refusal, context_exceeded), cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `providerOptions`, `blocks`, `countTokens`, `pause_turn`, `refusal`, `context_management`, `compaction`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — the `setup-openai` topic of the `warlock-js-ai-openai` skill; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.
9
+ - [setup-anthropic](@warlock.js/ai-anthropic/setup-anthropic/SKILL.md): Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?, maxOutputTokens?}). System-prompt hoisting, max_tokens required (default: model max output when streaming, 16384 non-streaming; maxOutputTokens for gateway ids), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl (per-call ttl), provider blocks (thinking/compaction) replayed verbatim via ModelResponse.blocks / Message.blocks, providerOptions.anthropic (betas, body fields such as context_management / fallbacks, thinkingDisplay), model.countTokens(), distinct finish reasons (pause_turn, refusal, context_exceeded), built-in Claude list prices (ANTHROPIC_MODEL_PRICING, anthropicPricingFor, withAnthropicCacheWriteRates), cost-truth usage (inputIncludesCache: false, cachedTokens/cacheWriteTokens/cacheWrite1hTokens, usage.cost on complete()), ProviderOverloadedError for 529, no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `maxOutputTokens`, `max_tokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `providerOptions`, `blocks`, `countTokens`, `pause_turn`, `refusal`, `context_management`, `contextManagement`, `serverCompaction`, `clear_tool_uses`, `clear_thinking`, `thinkingBlockBinding`, `block_binding`, `preservedThinking`, `ai.providerTool`, `web_search_20250305`, `bash_20250124`, `text_editor_20250728`, `streamChunks`, `thinking_delta`, `compaction`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `cacheWrite1hTokens`, `inputIncludesCache`, `ANTHROPIC_MODEL_PRICING`, `ProviderOverloadedError`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — the `setup-openai` topic of the `warlock-js-ai-openai` skill; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.
package/package.json CHANGED
@@ -15,13 +15,13 @@
15
15
  "directory": "packages/ai-anthropic"
16
16
  },
17
17
  "peerDependencies": {
18
- "@warlock.js/ai": "5.30.0"
18
+ "@warlock.js/ai": "5.32.0"
19
19
  },
20
20
  "dependencies": {
21
21
  "@anthropic-ai/sdk": "^0.129.0",
22
- "@warlock.js/logger": "5.30.0"
22
+ "@warlock.js/logger": "5.32.0"
23
23
  },
24
- "version": "5.30.0",
24
+ "version": "5.32.0",
25
25
  "main": "./cjs/index.cjs",
26
26
  "module": "./esm/index.mjs",
27
27
  "types": "./esm/index.d.mts",
package/skills/README.md CHANGED
@@ -6,4 +6,4 @@ Per-task skills. Cross-references name the topic ("the `<topic>` topic"), and fo
6
6
 
7
7
  ### `setup-anthropic`
8
8
 
9
- Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl with cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Load when wiring a Claude-backed model into a @warlock.js agent.
9
+ Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?, maxOutputTokens?}). System-prompt hoisting, max_tokens required (default: model max output when streaming, 16384 non-streaming), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl with cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Load when wiring a Claude-backed model into a @warlock.js agent.