@warlock.js/ai-anthropic 5.29.0 → 5.30.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.
@@ -0,0 +1,250 @@
1
+ import { AnthropicModelConfig } from "./config.type.mjs";
2
+ import Anthropic from "@anthropic-ai/sdk";
3
+ import { Message, ModelCallOptions, ModelCapabilities, ModelContract, ModelPricing, ModelResponse, ModelStreamChunk } from "@warlock.js/ai";
4
+
5
+ //#region ../../packages/ai-anthropic/src/model.d.ts
6
+ /**
7
+ * Anthropic-backed implementation of `ModelContract`.
8
+ *
9
+ * **Role.** The provider-facing bridge between the vendor-neutral
10
+ * `@warlock.js/ai` agent runtime and the official `@anthropic-ai/sdk`
11
+ * Messages API. Agents, workflows, and supervisors never talk to
12
+ * Anthropic directly — they hold a `ModelContract`, and this class is
13
+ * what makes that contract concrete for Claude models.
14
+ *
15
+ * **Responsibility.**
16
+ * - Owns: a long-lived `Anthropic` client + frozen `ModelConfig`
17
+ * (name, temperature, maxTokens) used as defaults for every call.
18
+ * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into
19
+ * Anthropic wire shapes (system hoisting, `tool_use` / `tool_result`
20
+ * blocks) on the way out, and translating Anthropic's content-block
21
+ * response (text, tool calls, stop reason, usage) back into the
22
+ * neutral shapes on the way in.
23
+ * - Does NOT own: dispatching tools, deciding whether to loop, tracking
24
+ * conversation history, or retrying on failure — those are agent
25
+ * concerns. The model is a stateless (per-call) protocol adapter.
26
+ *
27
+ * Because it holds a live client and shared defaults, it is modeled as
28
+ * a class (see §4.2 of code-style.md — "long-lived state across
29
+ * calls").
30
+ *
31
+ * @example
32
+ * import Anthropic from "@anthropic-ai/sdk";
33
+ * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
34
+ * const model = new AnthropicModel(client, { name: "claude-sonnet-4-6" });
35
+ *
36
+ * const myAgent = agent({
37
+ * model,
38
+ * systemPrompt: "You are a helpful assistant.",
39
+ * tools: [searchTool],
40
+ * });
41
+ *
42
+ * const result = await myAgent.execute("Summarize today's news.");
43
+ */
44
+ declare class AnthropicModel implements ModelContract {
45
+ readonly name: string;
46
+ readonly provider: string;
47
+ readonly capabilities: ModelCapabilities;
48
+ readonly pricing?: ModelPricing;
49
+ private readonly client;
50
+ private readonly config;
51
+ private readonly thinkingMode;
52
+ private readonly logger;
53
+ constructor(client: Anthropic, config: AnthropicModelConfig, provider?: string);
54
+ /**
55
+ * Single-shot completion. Sends the full message list to the Messages
56
+ * endpoint, waits for the terminal response, and reshapes it into a
57
+ * vendor-neutral `ModelResponse`. Per-call `options` override the
58
+ * instance's `ModelConfig` defaults for this call only.
59
+ */
60
+ complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse>;
61
+ /**
62
+ * Count the input tokens a request would use, through Anthropic's
63
+ * `messages.countTokens` endpoint (a network call; no tokens are
64
+ * generated or billed as output). Takes the same messages and options
65
+ * as `complete()` — system prompt, tools, thinking and
66
+ * `providerOptions.anthropic.betas` are counted exactly as they would be
67
+ * sent. For an offline estimate use `AnthropicSDK.count()`.
68
+ *
69
+ * @example
70
+ * const tokens = await model.countTokens(messages, { tools });
71
+ */
72
+ countTokens(messages: Message[], options?: ModelCallOptions): Promise<number>;
73
+ /**
74
+ * Incremental streaming completion. Yields neutral `ModelStreamChunk`s
75
+ * — `delta` for text tokens, `tool-call` once a `tool_use` block's
76
+ * arguments have fully accumulated, and a terminal `done` carrying the
77
+ * final finish reason + usage totals. Callers consume it with
78
+ * `for await`.
79
+ */
80
+ stream(messages: Message[], options?: ModelCallOptions): AsyncIterable<ModelStreamChunk>;
81
+ /**
82
+ * Fold one stream event into the per-index block accumulator. Unknown
83
+ * block and delta types (beta `compaction`, future additions) are kept
84
+ * as their start payload plus whatever their delta carries, so they
85
+ * round-trip without the adapter knowing their shape.
86
+ */
87
+ private trackStreamedBlock;
88
+ /** Close the accumulated stream blocks into an ordered content array. */
89
+ private finishStreamedBlocks;
90
+ /**
91
+ * Send a request through the SDK. With `providerOptions.anthropic.betas`
92
+ * the call goes to `client.beta.messages` (which takes the `betas` list
93
+ * and the beta-only body fields); otherwise to `client.messages`. The
94
+ * cancellation signal rides in the request options, as before.
95
+ */
96
+ private send;
97
+ /**
98
+ * The full request body: the adapter-built params, then
99
+ * `thinking.display` when thinking is sent on an adaptive model, then
100
+ * `providerOptions.anthropic.body` merged last (caller fields win). The
101
+ * body may add fields the adapter does not build (`context_management`,
102
+ * `fallbacks`, …) but may not replace the conversation or the model —
103
+ * see {@link RESERVED_BODY_FIELDS}.
104
+ */
105
+ private buildBody;
106
+ /**
107
+ * Assemble the Anthropic request body shared by `complete()` and
108
+ * `stream()` (each adds its own `stream` literal so the SDK's create
109
+ * overload resolves to the right return type). Hoists the system
110
+ * prompt out of `messages`, resolves `max_tokens` (required by
111
+ * Anthropic) with the documented default, and conditionally attaches
112
+ * temperature, tools, native structured output, extended thinking
113
+ * (`reasoning`), and automatic or explicit prompt-cache control.
114
+ * Temperature is dropped when thinking is enabled, since Anthropic
115
+ * rejects the two together, and always for adaptive-mode models, which
116
+ * reject any explicit temperature (see `acceptsTemperature`).
117
+ */
118
+ private buildParams;
119
+ /**
120
+ * Spread-friendly `system` fragment. Returns an empty object when no
121
+ * system prompt was hoisted out of the messages.
122
+ *
123
+ * When a per-call `cacheControl.breakpoints` hint is present (≥ 1),
124
+ * the system prompt is emitted as a single `TextBlockParam` carrying
125
+ * `cache_control: ephemeral` — the system prompt is the longest stable
126
+ * prefix on a turn, so one breakpoint there lets multi-turn agents
127
+ * read it back at the ~0.1x cache-read rate. Without the hint the
128
+ * system prompt stays a plain string (left uncached) so a one-shot
129
+ * call never pays the ~1.25x cache-write surcharge.
130
+ */
131
+ private buildSystem;
132
+ /**
133
+ * Let Anthropic select the last cacheable block when caching is enabled
134
+ * without a caller-selected breakpoint. An explicit breakpoint remains
135
+ * authoritative, so the request-level form is deliberately omitted then.
136
+ */
137
+ private buildAutomaticCacheControl;
138
+ /**
139
+ * Build a typed cache-control marker. The per-call
140
+ * `cacheControl.ttl` wins over the model's `promptCacheTtl`; with
141
+ * neither, TTL is omitted for Anthropic's 5-minute default.
142
+ */
143
+ private cacheControl;
144
+ /**
145
+ * Translate the neutral `reasoning` option into Anthropic's thinking
146
+ * settings, in the shape this model accepts (`thinkingMode`). Emitted
147
+ * only when the model declares the `reasoning` capability AND a
148
+ * reasoning option is supplied; otherwise returns an empty object.
149
+ *
150
+ * - Adaptive mode (Opus 4.7+, Sonnet 5+, Fable 5+, unknown ids):
151
+ * `thinking: { type: "adaptive" }` (installed SDK
152
+ * `ThinkingConfigAdaptive`, `messages.d.ts:1065`) plus
153
+ * `output_config.effort` from the neutral effort, defaulting to
154
+ * `"medium"`. `budget_tokens` is never sent — these models 400 on it
155
+ * (claude-api ref). `reasoning.maxTokens` only sizes the default
156
+ * `max_tokens` headroom.
157
+ * - Budget mode (Haiku 4.5, Opus/Sonnet 4.6, older):
158
+ * `thinking: { type: "enabled", budget_tokens }`. An explicit
159
+ * `reasoning.maxTokens` wins; otherwise the effort maps to a tiered
160
+ * budget. `budget_tokens` must be ≥ 1024 (`ThinkingConfigEnabled`,
161
+ * `messages.d.ts:1084`), so the budget is floored at that minimum.
162
+ *
163
+ * The neutral `effort: "none"` ("run without reasoning") is mapped per
164
+ * model:
165
+ * - Claude Sonnet 5.5 (`acceptsBetweenTools`): `thinking: { type:
166
+ * "between_tools" }`, its only thinking-off form. It is sent alone —
167
+ * no `output_config.effort` (the model default, `high`, is within the
168
+ * `high`-or-below range it requires) and no `display`, both of which
169
+ * the API rejects alongside it.
170
+ * - Opus 5.5 and Fable 5/5.1: no `thinking` field. These models have no
171
+ * thinking-off form (`disabled` and `budget_tokens` are a 400 at every
172
+ * effort), so thinking still runs at the model's default effort. Pass
173
+ * `effort: "low"` to spend less.
174
+ * - Every other model: no `thinking` field, as before.
175
+ */
176
+ private buildThinking;
177
+ /**
178
+ * Spread-friendly tools fragment. Returns an empty object when no
179
+ * tools were supplied so the caller can unconditionally spread it.
180
+ *
181
+ * When `config.promptCaching` is on, marks the LAST tool with
182
+ * `cache_control: ephemeral` — Anthropic caches the whole prefix up
183
+ * to a breakpoint, and tools sit before `system` in that prefix, so
184
+ * one breakpoint on the final tool caches every tool definition.
185
+ * Reads bill at ~0.1x after the first write. The system prompt is
186
+ * deliberately left uncached: it carries per-turn placeholders, so a
187
+ * breakpoint there would pay the ~1.25x write surcharge every turn
188
+ * with no reads.
189
+ */
190
+ private buildTools;
191
+ /**
192
+ * Spread-friendly `output_config` fragment carrying both the native
193
+ * structured-output `format` and the adaptive-thinking `effort`, merged
194
+ * into one object (both live under `OutputConfig`, installed SDK
195
+ * `messages.d.ts:796-806`). Returns an empty object when neither applies,
196
+ * so budget-mode requests carry `output_config` only when a schema asks
197
+ * for it.
198
+ */
199
+ private buildOutputConfig;
200
+ /**
201
+ * Translate the neutral `responseSchema` option into Anthropic's
202
+ * native `output_config.format` (JSON-schema structured outputs).
203
+ *
204
+ * Only returned when the model declares the `structuredOutput`
205
+ * capability AND the schema is a proper root-object JSON Schema —
206
+ * Anthropic rejects non-object roots. When the capability is off
207
+ * (config override) or the schema is non-object, returns `undefined`:
208
+ * the agent has already injected a soft schema hint into the system
209
+ * prompt as the fallback, and client-side `validate()` still enforces
210
+ * shape.
211
+ */
212
+ private buildStructuredOutputFormat;
213
+ /**
214
+ * Concatenate every `text` content block into the single neutral
215
+ * `content` string. `tool_use` and other block types are ignored
216
+ * here — tool calls are surfaced separately via `extractToolCalls`.
217
+ */
218
+ private extractText;
219
+ /**
220
+ * Reshape Anthropic's `tool_use` content blocks into the neutral
221
+ * `ModelToolCallRequest[]`. Returns `undefined` when the model
222
+ * requested no tools so callers can branch on presence.
223
+ */
224
+ private extractToolCalls;
225
+ /**
226
+ * Preserve Anthropic's opaque extended-thinking blocks beside a tool
227
+ * request so the next assistant turn can replay their signatures exactly.
228
+ */
229
+ private buildThinkingMetadata;
230
+ /**
231
+ * Normalize Anthropic's `usage` block into the neutral `Usage` shape.
232
+ * Anthropic reports `input_tokens` / `output_tokens` separately with
233
+ * no pre-summed total, so `total` is computed. Cache-read tokens are
234
+ * surfaced as `cachedTokens` and cache-write tokens as
235
+ * `cacheWriteTokens`, each only when non-zero. Anthropic also reports
236
+ * `output_tokens_details.thinking_tokens` as the reasoning subset of
237
+ * its inclusive `output_tokens`; it is surfaced as `reasoningTokens`
238
+ * when present, without changing the billing total.
239
+ */
240
+ private extractUsage;
241
+ /**
242
+ * Wrap a thrown provider error into the typed `AIError` hierarchy and
243
+ * emit the standard error log line before it propagates. Shared by
244
+ * every catch site so the log shape stays identical.
245
+ */
246
+ private logAndWrap;
247
+ }
248
+ //#endregion
249
+ export { AnthropicModel };
250
+ //# sourceMappingURL=model.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.mts","names":[],"sources":["../../../../../../../packages/ai-anthropic/src/model.ts"],"mappings":";;;;;;;AA8KA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,aAAA;EAAA,SACrB,IAAA;EAAA,SACA,QAAA;EAAA,SACA,YAAA,EAAc,iBAAA;EAAA,SACd,OAAA,GAAU,YAAA;EAAA,iBAET,MAAA;EAAA,iBACA,MAAA;EAAA,iBACA,YAAA;EAAA,iBACA,MAAA;cAGf,MAAA,EAAQ,SAAA,EACR,MAAA,EAAQ,oBAAA,EACR,QAAA;EA0FiC;;;;;;EAlDtB,QAAA,CAAS,QAAA,EAAU,OAAA,IAAW,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,aAAA;EA+E9E;;;;;;;;;;;EA7BW,WAAA,CAAY,QAAA,EAAU,OAAA,IAAW,OAAA,GAAU,gBAAA,GAAmB,OAAA;EAqbnE;;;;;;;EAzZM,MAAA,CACZ,QAAA,EAAU,OAAA,IACV,OAAA,GAAU,gBAAA,GACT,aAAA,CAAc,gBAAA;EA4nBT;;;AA4BU;;;EA5BV,QArdA,kBAAA;;UAoDA,oBAAA;;;;;;;UAkBA,IAAA;;;;;;;;;UAqBA,SAAA;;;;;;;;;;;;;UAgDA,WAAA;;;;;;;;;;;;;UA2DA,WAAA;;;;;;UA0BA,0BAAA;;;;;;UAeA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAyCA,aAAA;;;;;;;;;;;;;;UAkDA,UAAA;;;;;;;;;UA8BA,iBAAA;;;;;;;;;;;;;UA8BA,2BAAA;;;;;;UAmBA,WAAA;;;;;;UAYA,gBAAA;;;;;UAuBA,qBAAA;;;;;;;;;;;UAyBA,YAAA;;;;;;UA4BA,UAAA;AAAA"}
package/esm/model.mjs CHANGED
@@ -1,14 +1,15 @@
1
1
  import { inferVisionCapability } from "./known-vision-models.mjs";
2
- import { acceptsTemperature, inferThinkingMode } from "./thinking-mode.mjs";
2
+ import { acceptsBetweenTools, acceptsTemperature, inferThinkingMode } from "./thinking-mode.mjs";
3
3
  import { mapStopReason } from "./utils/map-stop-reason.mjs";
4
+ import { toProviderBlocks } from "./utils/provider-blocks.mjs";
4
5
  import { toAnthropicMessages } from "./utils/to-anthropic-messages.mjs";
5
6
  import { toAnthropicTools } from "./utils/to-anthropic-tools.mjs";
6
7
  import { wrapAnthropicError } from "./utils/wrap-anthropic-error.mjs";
7
8
  import "./utils/index.mjs";
8
- import { safeJsonParse } from "@warlock.js/ai";
9
+ import { InvalidRequestError, safeJsonParse } from "@warlock.js/ai";
9
10
  import { log } from "@warlock.js/logger";
10
11
 
11
- //#region ../ai-anthropic/src/model.ts
12
+ //#region ../../packages/ai-anthropic/src/model.ts
12
13
  const LOG_MODULE = "ai.anthropic";
13
14
  /**
14
15
  * Anthropic requires `max_tokens` on every request (unlike OpenAI,
@@ -65,6 +66,12 @@ const EFFORT_TO_OUTPUT_EFFORT = {
65
66
  xhigh: "xhigh",
66
67
  max: "max"
67
68
  };
69
+ /** Content-block types whose input arrives as `input_json_delta` while streaming. */
70
+ const STREAMED_INPUT_BLOCK_TYPES = new Set([
71
+ "tool_use",
72
+ "server_tool_use",
73
+ "mcp_tool_use"
74
+ ]);
68
75
  /**
69
76
  * Anthropic-backed implementation of `ModelContract`.
70
77
  *
@@ -135,16 +142,17 @@ var AnthropicModel = class {
135
142
  });
136
143
  let response;
137
144
  try {
138
- response = await this.client.messages.create({
139
- ...this.buildParams(messages, options),
145
+ response = await this.send({
146
+ ...this.buildBody(messages, options),
140
147
  stream: false
141
- }, options?.signal ? { signal: options.signal } : void 0);
148
+ }, options, "create");
142
149
  } catch (thrown) {
143
150
  throw this.logAndWrap(thrown);
144
151
  }
145
152
  const finishReason = mapStopReason(response.stop_reason);
146
153
  const usage = this.extractUsage(response.usage);
147
154
  const toolCalls = this.extractToolCalls(response.content);
155
+ const blocks = toProviderBlocks(response.content);
148
156
  this.logger.debug(LOG_MODULE, "response", "call to messages.create succeeded", {
149
157
  finishReason,
150
158
  usage
@@ -153,10 +161,32 @@ var AnthropicModel = class {
153
161
  content: this.extractText(response.content),
154
162
  finishReason,
155
163
  usage,
156
- toolCalls
164
+ toolCalls,
165
+ ...blocks ? { blocks } : {}
157
166
  };
158
167
  }
159
168
  /**
169
+ * Count the input tokens a request would use, through Anthropic's
170
+ * `messages.countTokens` endpoint (a network call; no tokens are
171
+ * generated or billed as output). Takes the same messages and options
172
+ * as `complete()` — system prompt, tools, thinking and
173
+ * `providerOptions.anthropic.betas` are counted exactly as they would be
174
+ * sent. For an offline estimate use `AnthropicSDK.count()`.
175
+ *
176
+ * @example
177
+ * const tokens = await model.countTokens(messages, { tools });
178
+ */
179
+ async countTokens(messages, options) {
180
+ const body = this.buildBody(messages, options);
181
+ const countBody = {};
182
+ for (const key of COUNT_TOKENS_FIELDS) if (body[key] !== void 0) countBody[key] = body[key];
183
+ try {
184
+ return (await this.send(countBody, options, "countTokens")).input_tokens;
185
+ } catch (thrown) {
186
+ throw this.logAndWrap(thrown);
187
+ }
188
+ }
189
+ /**
160
190
  * Incremental streaming completion. Yields neutral `ModelStreamChunk`s
161
191
  * — `delta` for text tokens, `tool-call` once a `tool_use` block's
162
192
  * arguments have fully accumulated, and a terminal `done` carrying the
@@ -172,10 +202,10 @@ var AnthropicModel = class {
172
202
  });
173
203
  let stream;
174
204
  try {
175
- stream = await this.client.messages.create({
176
- ...this.buildParams(messages, options),
205
+ stream = await this.send({
206
+ ...this.buildBody(messages, options),
177
207
  stream: true
178
- }, options?.signal ? { signal: options.signal } : void 0);
208
+ }, options, "create");
179
209
  } catch (thrown) {
180
210
  throw this.logAndWrap(thrown);
181
211
  }
@@ -187,8 +217,10 @@ var AnthropicModel = class {
187
217
  };
188
218
  const toolBlocks = /* @__PURE__ */ new Map();
189
219
  const thinkingBlocks = /* @__PURE__ */ new Map();
220
+ const streamedBlocks = /* @__PURE__ */ new Map();
190
221
  try {
191
222
  for await (const event of stream) {
223
+ this.trackStreamedBlock(streamedBlocks, event);
192
224
  if (event.type === "message_start") {
193
225
  usage.input = event.message.usage.input_tokens ?? 0;
194
226
  const thinkingTokens = event.message.usage.output_tokens_details?.thinking_tokens;
@@ -257,6 +289,7 @@ var AnthropicModel = class {
257
289
  }
258
290
  usage.total = usage.input + usage.output;
259
291
  const finishReason = mapStopReason(rawStopReason);
292
+ const blocks = toProviderBlocks(this.finishStreamedBlocks(streamedBlocks));
260
293
  this.logger.debug(LOG_MODULE, "response", "Streaming call to messages.create succeeded", {
261
294
  finishReason,
262
295
  usage
@@ -264,7 +297,79 @@ var AnthropicModel = class {
264
297
  yield {
265
298
  type: "done",
266
299
  finishReason,
267
- usage
300
+ usage,
301
+ ...blocks ? { blocks } : {}
302
+ };
303
+ }
304
+ /**
305
+ * Fold one stream event into the per-index block accumulator. Unknown
306
+ * block and delta types (beta `compaction`, future additions) are kept
307
+ * as their start payload plus whatever their delta carries, so they
308
+ * round-trip without the adapter knowing their shape.
309
+ */
310
+ trackStreamedBlock(blocks, event) {
311
+ if (event.type === "content_block_start") {
312
+ const block = { ...event.content_block };
313
+ blocks.set(event.index, {
314
+ block,
315
+ ...STREAMED_INPUT_BLOCK_TYPES.has(String(block.type)) ? { json: "" } : {}
316
+ });
317
+ return;
318
+ }
319
+ if (event.type !== "content_block_delta") return;
320
+ const entry = blocks.get(event.index);
321
+ if (!entry) return;
322
+ const delta = event.delta;
323
+ const block = entry.block;
324
+ if (delta.type === "text_delta") block.text = String(block.text ?? "") + String(delta.text ?? "");
325
+ else if (delta.type === "thinking_delta") block.thinking = String(block.thinking ?? "") + String(delta.thinking ?? "");
326
+ else if (delta.type === "signature_delta") block.signature = String(block.signature ?? "") + String(delta.signature ?? "");
327
+ else if (delta.type === "input_json_delta") entry.json = (entry.json ?? "") + String(delta.partial_json ?? "");
328
+ else if (delta.type === "citations_delta") block.citations = [...Array.isArray(block.citations) ? block.citations : [], delta.citation];
329
+ else for (const [key, value] of Object.entries(delta)) if (key !== "type") block[key] = value;
330
+ }
331
+ /** Close the accumulated stream blocks into an ordered content array. */
332
+ finishStreamedBlocks(blocks) {
333
+ return Array.from(blocks.entries()).sort(([left], [right]) => left - right).map(([, { block, json }]) => {
334
+ if (json !== void 0) block.input = safeJsonParse(json, {});
335
+ return block;
336
+ });
337
+ }
338
+ /**
339
+ * Send a request through the SDK. With `providerOptions.anthropic.betas`
340
+ * the call goes to `client.beta.messages` (which takes the `betas` list
341
+ * and the beta-only body fields); otherwise to `client.messages`. The
342
+ * cancellation signal rides in the request options, as before.
343
+ */
344
+ send(body, options, method) {
345
+ const betas = resolveAnthropicOptions(options).betas;
346
+ const requestOptions = options?.signal ? { signal: options.signal } : void 0;
347
+ const api = betas && betas.length > 0 ? this.client.beta.messages : this.client.messages;
348
+ return api[method].call(api, betas && betas.length > 0 ? {
349
+ ...body,
350
+ betas
351
+ } : body, requestOptions);
352
+ }
353
+ /**
354
+ * The full request body: the adapter-built params, then
355
+ * `thinking.display` when thinking is sent on an adaptive model, then
356
+ * `providerOptions.anthropic.body` merged last (caller fields win). The
357
+ * body may add fields the adapter does not build (`context_management`,
358
+ * `fallbacks`, …) but may not replace the conversation or the model —
359
+ * see {@link RESERVED_BODY_FIELDS}.
360
+ */
361
+ buildBody(messages, options) {
362
+ const anthropic = resolveAnthropicOptions(options);
363
+ const body = { ...this.buildParams(messages, options) };
364
+ if (anthropic.thinkingDisplay && this.thinkingMode === "adaptive" && body.thinking?.type === "adaptive") body.thinking = {
365
+ ...body.thinking,
366
+ display: anthropic.thinkingDisplay
367
+ };
368
+ const reserved = Object.keys(anthropic.body ?? {}).filter((key) => RESERVED_BODY_FIELDS.has(key));
369
+ if (reserved.length > 0) throw new InvalidRequestError(`providerOptions.anthropic.body cannot set ${reserved.map((key) => `"${key}"`).join(", ")}: the adapter builds these from the call's messages, tools and model. Use the agent/model options instead.`, { context: { fields: reserved } });
370
+ return {
371
+ ...body,
372
+ ...anthropic.body
268
373
  };
269
374
  }
270
375
  /**
@@ -295,7 +400,7 @@ var AnthropicModel = class {
295
400
  ...this.buildAutomaticCacheControl(options?.cacheControl),
296
401
  ...this.buildSystem(system, options?.cacheControl),
297
402
  ...sendTemperature ? { temperature } : {},
298
- ...this.buildTools(options?.tools),
403
+ ...this.buildTools(options?.tools, options?.cacheControl),
299
404
  ...this.buildOutputConfig(options?.responseSchema, thinking.effort),
300
405
  ...thinking.thinking ? { thinking: thinking.thinking } : {}
301
406
  };
@@ -318,7 +423,7 @@ var AnthropicModel = class {
318
423
  if (this.capabilities.promptCaching && breakpoints > 0) return { system: [{
319
424
  type: "text",
320
425
  text: system,
321
- cache_control: this.cacheControl()
426
+ cache_control: this.cacheControl(cacheControl?.ttl)
322
427
  }] };
323
428
  return { system };
324
429
  }
@@ -329,13 +434,18 @@ var AnthropicModel = class {
329
434
  */
330
435
  buildAutomaticCacheControl(cacheControl) {
331
436
  const breakpoints = cacheControl?.breakpoints ?? 0;
332
- return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl() } : {};
437
+ return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl(cacheControl?.ttl) } : {};
333
438
  }
334
- /** Build a typed cache-control marker, omitting TTL for Anthropic's 5-minute default. */
335
- cacheControl() {
439
+ /**
440
+ * Build a typed cache-control marker. The per-call
441
+ * `cacheControl.ttl` wins over the model's `promptCacheTtl`; with
442
+ * neither, TTL is omitted for Anthropic's 5-minute default.
443
+ */
444
+ cacheControl(callTtl) {
445
+ const ttl = callTtl ?? this.config.promptCacheTtl;
336
446
  return {
337
447
  type: "ephemeral",
338
- ...this.config.promptCacheTtl ? { ttl: this.config.promptCacheTtl } : {}
448
+ ...ttl ? { ttl } : {}
339
449
  };
340
450
  }
341
451
  /**
@@ -357,12 +467,22 @@ var AnthropicModel = class {
357
467
  * budget. `budget_tokens` must be ≥ 1024 (`ThinkingConfigEnabled`,
358
468
  * `messages.d.ts:1084`), so the budget is floored at that minimum.
359
469
  *
360
- * The neutral `effort: "none"` ("run without reasoning") maps to no
361
- * `thinking` field at all in both modes.
470
+ * The neutral `effort: "none"` ("run without reasoning") is mapped per
471
+ * model:
472
+ * - Claude Sonnet 5.5 (`acceptsBetweenTools`): `thinking: { type:
473
+ * "between_tools" }`, its only thinking-off form. It is sent alone —
474
+ * no `output_config.effort` (the model default, `high`, is within the
475
+ * `high`-or-below range it requires) and no `display`, both of which
476
+ * the API rejects alongside it.
477
+ * - Opus 5.5 and Fable 5/5.1: no `thinking` field. These models have no
478
+ * thinking-off form (`disabled` and `budget_tokens` are a 400 at every
479
+ * effort), so thinking still runs at the model's default effort. Pass
480
+ * `effort: "low"` to spend less.
481
+ * - Every other model: no `thinking` field, as before.
362
482
  */
363
483
  buildThinking(reasoning) {
364
484
  if (!this.capabilities.reasoning || !reasoning) return {};
365
- if (reasoning.effort === "none") return {};
485
+ if (reasoning.effort === "none") return acceptsBetweenTools(this.name) ? { thinking: { type: "between_tools" } } : {};
366
486
  if (reasoning.maxTokens === void 0 && reasoning.effort === void 0) return {};
367
487
  const effort = reasoning.effort ?? DEFAULT_THINKING_EFFORT;
368
488
  const budget = Math.max(MIN_THINKING_BUDGET, reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[effort]);
@@ -392,7 +512,7 @@ var AnthropicModel = class {
392
512
  * breakpoint there would pay the ~1.25x write surcharge every turn
393
513
  * with no reads.
394
514
  */
395
- buildTools(tools) {
515
+ buildTools(tools, cacheControl) {
396
516
  const mapped = toAnthropicTools(tools);
397
517
  if (!mapped) return {};
398
518
  if (this.config.promptCaching && mapped.length > 0) {
@@ -400,7 +520,7 @@ var AnthropicModel = class {
400
520
  const lastTool = mapped[last];
401
521
  if (lastTool) mapped[last] = {
402
522
  ...lastTool,
403
- cache_control: this.cacheControl()
523
+ cache_control: this.cacheControl(cacheControl?.ttl)
404
524
  };
405
525
  }
406
526
  return { tools: mapped };
@@ -513,6 +633,39 @@ var AnthropicModel = class {
513
633
  return wrapped;
514
634
  }
515
635
  };
636
+ /**
637
+ * Request fields `providerOptions.anthropic.body` may not set: they carry
638
+ * the conversation, the model and the tool surface the agent registered,
639
+ * so an override would silently replace the agent's input (or let the
640
+ * model call tools the agent never registered). `stream` is chosen per
641
+ * call by `complete()` / `stream()`.
642
+ */
643
+ const RESERVED_BODY_FIELDS = new Set([
644
+ "model",
645
+ "messages",
646
+ "system",
647
+ "tools",
648
+ "mcp_servers",
649
+ "stream"
650
+ ]);
651
+ /** Body fields `messages.countTokens` accepts, copied from the full request body. */
652
+ const COUNT_TOKENS_FIELDS = [
653
+ "model",
654
+ "messages",
655
+ "system",
656
+ "tools",
657
+ "tool_choice",
658
+ "thinking",
659
+ "output_config",
660
+ "cache_control",
661
+ "context_management",
662
+ "mcp_servers"
663
+ ];
664
+ /** Read `providerOptions.anthropic`, tolerating its absence or a wrong shape. */
665
+ function resolveAnthropicOptions(options) {
666
+ const raw = options?.providerOptions?.anthropic;
667
+ return typeof raw === "object" && raw !== null ? raw : {};
668
+ }
516
669
 
517
670
  //#endregion
518
671
  export { AnthropicModel };