@warlock.js/ai-anthropic 5.30.0 → 5.31.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 (43) hide show
  1. package/CHANGELOG.md +44 -6
  2. package/cjs/index.cjs +815 -132
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/config.type.d.mts +107 -4
  5. package/esm/config.type.d.mts.map +1 -1
  6. package/esm/index.d.mts +8 -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 +58 -0
  11. package/esm/known-pricing.d.mts.map +1 -0
  12. package/esm/known-pricing.mjs +165 -0
  13. package/esm/known-pricing.mjs.map +1 -0
  14. package/esm/model.d.mts +89 -17
  15. package/esm/model.d.mts.map +1 -1
  16. package/esm/model.mjs +383 -86
  17. package/esm/model.mjs.map +1 -1
  18. package/esm/request.type.d.mts +25 -0
  19. package/esm/request.type.d.mts.map +1 -0
  20. package/esm/thinking-mode.d.mts +14 -1
  21. package/esm/thinking-mode.d.mts.map +1 -1
  22. package/esm/thinking-mode.mjs +102 -2
  23. package/esm/thinking-mode.mjs.map +1 -1
  24. package/esm/utils/cache-usage.mjs +62 -0
  25. package/esm/utils/cache-usage.mjs.map +1 -0
  26. package/esm/utils/index.d.mts +3 -0
  27. package/esm/utils/map-stop-reason.d.mts +1 -0
  28. package/esm/utils/provider-blocks.d.mts +39 -0
  29. package/esm/utils/provider-blocks.d.mts.map +1 -0
  30. package/esm/utils/to-anthropic-messages.d.mts +71 -0
  31. package/esm/utils/to-anthropic-messages.d.mts.map +1 -0
  32. package/esm/utils/to-anthropic-messages.mjs +12 -4
  33. package/esm/utils/to-anthropic-messages.mjs.map +1 -1
  34. package/esm/utils/to-anthropic-tools.d.mts +19 -0
  35. package/esm/utils/to-anthropic-tools.d.mts.map +1 -0
  36. package/esm/utils/wrap-anthropic-error.d.mts +1 -0
  37. package/esm/utils/wrap-anthropic-error.mjs +6 -1
  38. package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
  39. package/llms-full.txt +214 -32
  40. package/llms.txt +1 -1
  41. package/package.json +3 -3
  42. package/skills/README.md +1 -1
  43. package/skills/setup-anthropic/SKILL.md +214 -32
package/esm/model.mjs CHANGED
@@ -1,24 +1,43 @@
1
+ import { anthropicPricingFor, withAnthropicCacheWriteRates } from "./known-pricing.mjs";
2
+ import { acceptsTemperature, inferPreservedThinking, inferThinkingMode, noReasoningForm } from "./thinking-mode.mjs";
3
+ import { inferMaxOutputTokens } from "./known-output-limits.mjs";
1
4
  import { inferVisionCapability } from "./known-vision-models.mjs";
2
- import { acceptsBetweenTools, acceptsTemperature, inferThinkingMode } from "./thinking-mode.mjs";
5
+ import { applyAnthropicCacheUsage, requestCacheTtl } from "./utils/cache-usage.mjs";
3
6
  import { mapStopReason } from "./utils/map-stop-reason.mjs";
4
7
  import { toProviderBlocks } from "./utils/provider-blocks.mjs";
5
8
  import { toAnthropicMessages } from "./utils/to-anthropic-messages.mjs";
6
9
  import { toAnthropicTools } from "./utils/to-anthropic-tools.mjs";
7
10
  import { wrapAnthropicError } from "./utils/wrap-anthropic-error.mjs";
8
11
  import "./utils/index.mjs";
9
- import { InvalidRequestError, safeJsonParse } from "@warlock.js/ai";
12
+ import { AIError, InvalidRequestError, computeCost, resolveStreamChunks, safeJsonParse, selectProviderTools } from "@warlock.js/ai";
10
13
  import { log } from "@warlock.js/logger";
11
14
 
12
15
  //#region ../../packages/ai-anthropic/src/model.ts
13
16
  const LOG_MODULE = "ai.anthropic";
17
+ /** `provider` value of `ai.providerTool()` entries this adapter sends verbatim. */
18
+ const ANTHROPIC_WIRE_FORMAT = "anthropic";
14
19
  /**
15
20
  * Anthropic requires `max_tokens` on every request (unlike OpenAI,
16
- * where it is optional). When neither the per-call option nor the
17
- * model config supplies one, fall back to a generous default so a
18
- * caller who never thought about token caps still gets a complete
19
- * answer instead of a 400.
21
+ * where it is optional). When neither the per-call option nor the model
22
+ * config supplies one, the adapter picks a default large enough for agent
23
+ * work (see `resolveDefaultMaxTokens`):
24
+ *
25
+ * - streaming: the model's documented max output (`known-output-limits.ts`,
26
+ * or `config.maxOutputTokens`), else this value;
27
+ * - non-streaming: this value, capped by the model's max output. The
28
+ * installed `@anthropic-ai/sdk` (0.129.0, `calculateNonstreamingTimeout`)
29
+ * refuses a non-streaming request whose `max_tokens` implies more than
30
+ * ten minutes (above ~21,333 tokens) unless the client sets a `timeout`,
31
+ * so the non-streaming default stays below that.
32
+ */
33
+ const DEFAULT_MAX_TOKENS = 16384;
34
+ /**
35
+ * Answer room kept on top of a thinking budget when the default
36
+ * `max_tokens` is sized: thinking counts toward `max_tokens` (installed
37
+ * SDK `ThinkingConfigParam` docs, `messages.d.ts:1105`), and in budget
38
+ * mode `max_tokens` must exceed `budget_tokens`.
20
39
  */
21
- const DEFAULT_MAX_TOKENS = 4096;
40
+ const MIN_ANSWER_TOKENS = 4096;
22
41
  /**
23
42
  * Anthropic rejects an extended-thinking budget below 1024 tokens, so
24
43
  * any resolved budget is floored at this minimum before it reaches the
@@ -37,11 +56,10 @@ const DEFAULT_THINKING_EFFORT = "medium";
37
56
  *
38
57
  * - Budget-mode models: sent as `thinking.budget_tokens` when the caller
39
58
  * doesn't pass an explicit `reasoning.maxTokens`.
40
- * - Both modes: reserved on top of `DEFAULT_MAX_TOKENS` when the caller
41
- * sets no `maxTokens`, because thinking counts toward `max_tokens`
42
- * (installed SDK `ThinkingConfigParam` docs, `messages.d.ts:1105`).
43
- * Adaptive models therefore get the same `max_tokens` the budget path
44
- * has always produced.
59
+ * - Both modes: when the caller sets no `maxTokens`, the default
60
+ * `max_tokens` keeps at least this budget plus `MIN_ANSWER_TOKENS`,
61
+ * because thinking counts toward `max_tokens` (installed SDK
62
+ * `ThinkingConfigParam` docs, `messages.d.ts:1105`).
45
63
  */
46
64
  const EFFORT_THINKING_BUDGET = {
47
65
  minimal: 1024,
@@ -117,8 +135,10 @@ var AnthropicModel = class {
117
135
  this.config = config;
118
136
  this.name = config.name;
119
137
  this.provider = provider;
120
- this.pricing = config.pricing;
138
+ this.pricing = withAnthropicCacheWriteRates(config.pricing ?? anthropicPricingFor(config.name));
121
139
  this.thinkingMode = config.thinkingMode ?? inferThinkingMode(config.name);
140
+ this.maxOutputTokens = config.maxOutputTokens ?? inferMaxOutputTokens(config.name);
141
+ this.preservedThinking = config.preservedThinking ?? (this.thinkingMode === "adaptive" && inferPreservedThinking(config.name));
122
142
  this.capabilities = {
123
143
  structuredOutput: config.structuredOutput ?? true,
124
144
  vision: config.vision ?? inferVisionCapability(config.name),
@@ -134,23 +154,50 @@ var AnthropicModel = class {
134
154
  * instance's `ModelConfig` defaults for this call only.
135
155
  */
136
156
  async complete(messages, options) {
137
- this.logger.debug(LOG_MODULE, "request", "Starting call to messages.create", {
138
- model: this.name,
139
- messageCount: messages.length,
140
- streaming: false,
141
- toolCount: options?.tools?.length ?? 0
157
+ return this.send(this.buildLoggedRequest(messages, {
158
+ ...options,
159
+ stream: false
160
+ }), { signal: options?.signal });
161
+ }
162
+ buildRequest(messages, options) {
163
+ const built = this.buildBody(messages, options);
164
+ const betas = [...new Set([...resolveAnthropicOptions(options).betas ?? [], ...built.betas])];
165
+ const body = structuredClone({
166
+ ...built.body,
167
+ stream: options?.stream ?? false
142
168
  });
169
+ return {
170
+ endpoint: betas.length > 0 ? "beta.messages" : "messages",
171
+ body,
172
+ betas
173
+ };
174
+ }
175
+ buildLoggedRequest(messages, options) {
176
+ try {
177
+ return this.buildRequest(messages, options);
178
+ } catch (thrown) {
179
+ throw this.logAndWrap(thrown instanceof AIError ? thrown : new InvalidRequestError(`Could not build the Anthropic request: ${thrown instanceof Error ? thrown.message : String(thrown)}`, { cause: thrown }));
180
+ }
181
+ }
182
+ send(request, options) {
183
+ const message = request.body.stream ? "Starting streaming call to messages.create" : "Starting call to messages.create";
184
+ this.logger.debug(LOG_MODULE, "request", message, {
185
+ model: request.body.model,
186
+ messageCount: Array.isArray(request.body.messages) ? request.body.messages.length : 0,
187
+ streaming: request.body.stream,
188
+ toolCount: Array.isArray(request.body.tools) ? request.body.tools.length : 0
189
+ });
190
+ return request.body.stream ? this.streamRequest(request, options) : this.completeRequest(request, options);
191
+ }
192
+ async completeRequest(request, options) {
143
193
  let response;
144
194
  try {
145
- response = await this.send({
146
- ...this.buildBody(messages, options),
147
- stream: false
148
- }, options, "create");
195
+ response = await this.dispatch(request, options, "create");
149
196
  } catch (thrown) {
150
197
  throw this.logAndWrap(thrown);
151
198
  }
152
199
  const finishReason = mapStopReason(response.stop_reason);
153
- const usage = this.extractUsage(response.usage);
200
+ const usage = this.extractUsage(response.usage, requestCacheTtl(request.body));
154
201
  const toolCalls = this.extractToolCalls(response.content);
155
202
  const blocks = toProviderBlocks(response.content);
156
203
  this.logger.debug(LOG_MODULE, "response", "call to messages.create succeeded", {
@@ -177,11 +224,18 @@ var AnthropicModel = class {
177
224
  * const tokens = await model.countTokens(messages, { tools });
178
225
  */
179
226
  async countTokens(messages, options) {
180
- const body = this.buildBody(messages, options);
227
+ const request = this.buildLoggedRequest(messages, {
228
+ ...options,
229
+ stream: options?.stream === true
230
+ });
231
+ const body = request.body;
181
232
  const countBody = {};
182
233
  for (const key of COUNT_TOKENS_FIELDS) if (body[key] !== void 0) countBody[key] = body[key];
183
234
  try {
184
- return (await this.send(countBody, options, "countTokens")).input_tokens;
235
+ return (await this.dispatch({
236
+ ...request,
237
+ body: countBody
238
+ }, options, "countTokens")).input_tokens;
185
239
  } catch (thrown) {
186
240
  throw this.logAndWrap(thrown);
187
241
  }
@@ -192,29 +246,38 @@ var AnthropicModel = class {
192
246
  * arguments have fully accumulated, and a terminal `done` carrying the
193
247
  * final finish reason + usage totals. Callers consume it with
194
248
  * `for await`.
249
+ *
250
+ * With `options.streamChunks` it also yields `reasoning` for every
251
+ * non-empty `thinking_delta`, `tool-args` for every `input_json_delta`
252
+ * of a client `tool_use` block, and `usage` after `message_start` and
253
+ * each `message_delta` (whose counts are cumulative).
195
254
  */
196
255
  async *stream(messages, options) {
197
- this.logger.debug(LOG_MODULE, "request", "Starting streaming call to messages.create", {
198
- model: this.name,
199
- messageCount: messages.length,
200
- streaming: true,
201
- toolCount: options?.tools?.length ?? 0
256
+ yield* this.send(this.buildLoggedRequest(messages, {
257
+ ...options,
258
+ stream: true
259
+ }), {
260
+ signal: options?.signal,
261
+ streamChunks: options?.streamChunks
202
262
  });
263
+ }
264
+ async *streamRequest(request, options) {
203
265
  let stream;
204
266
  try {
205
- stream = await this.send({
206
- ...this.buildBody(messages, options),
207
- stream: true
208
- }, options, "create");
267
+ stream = await this.dispatch(request, options, "create");
209
268
  } catch (thrown) {
210
269
  throw this.logAndWrap(thrown);
211
270
  }
212
271
  let rawStopReason = null;
272
+ let compaction = compactionIterationUsage(void 0);
213
273
  const usage = {
214
274
  input: 0,
215
275
  output: 0,
216
- total: 0
276
+ total: 0,
277
+ inputIncludesCache: false
217
278
  };
279
+ const cacheTtl = requestCacheTtl(request.body);
280
+ const want = resolveStreamChunks(options);
218
281
  const toolBlocks = /* @__PURE__ */ new Map();
219
282
  const thinkingBlocks = /* @__PURE__ */ new Map();
220
283
  const streamedBlocks = /* @__PURE__ */ new Map();
@@ -225,10 +288,14 @@ var AnthropicModel = class {
225
288
  usage.input = event.message.usage.input_tokens ?? 0;
226
289
  const thinkingTokens = event.message.usage.output_tokens_details?.thinking_tokens;
227
290
  if (thinkingTokens !== null && thinkingTokens !== void 0) usage.reasoningTokens = thinkingTokens;
228
- const cacheRead = event.message.usage.cache_read_input_tokens;
229
- if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
230
- const cacheWrite = event.message.usage.cache_creation_input_tokens;
231
- if (cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
291
+ applyAnthropicCacheUsage(usage, event.message.usage, cacheTtl);
292
+ if (want.usage) yield {
293
+ type: "usage",
294
+ usage: {
295
+ ...usage,
296
+ total: usage.input + usage.output
297
+ }
298
+ };
232
299
  continue;
233
300
  }
234
301
  if (event.type === "content_block_start") {
@@ -249,10 +316,22 @@ var AnthropicModel = class {
249
316
  };
250
317
  else if (event.delta.type === "input_json_delta") {
251
318
  const accumulator = toolBlocks.get(event.index);
252
- if (accumulator) accumulator.json += event.delta.partial_json;
319
+ if (accumulator) {
320
+ accumulator.json += event.delta.partial_json;
321
+ if (want.toolArgs && event.delta.partial_json !== "") yield {
322
+ type: "tool-args",
323
+ callId: accumulator.id,
324
+ name: accumulator.name,
325
+ delta: event.delta.partial_json
326
+ };
327
+ }
253
328
  } else if (event.delta.type === "thinking_delta") {
254
329
  const thinking = thinkingBlocks.get(event.index);
255
330
  if (thinking?.type === "thinking") thinking.thinking += event.delta.thinking;
331
+ if (want.reasoning && event.delta.thinking !== "") yield {
332
+ type: "reasoning",
333
+ delta: event.delta.thinking
334
+ };
256
335
  } else if (event.delta.type === "signature_delta") {
257
336
  const thinking = thinkingBlocks.get(event.index);
258
337
  if (thinking?.type === "thinking") thinking.signature += event.delta.signature;
@@ -275,19 +354,33 @@ var AnthropicModel = class {
275
354
  }
276
355
  if (event.type === "message_delta") {
277
356
  rawStopReason = event.delta.stop_reason ?? rawStopReason;
357
+ if (Array.isArray(event.usage.iterations)) compaction = compactionIterationUsage(event.usage);
278
358
  usage.output = event.usage.output_tokens ?? usage.output;
279
359
  const thinkingTokens = event.usage.output_tokens_details?.thinking_tokens;
280
360
  if (thinkingTokens !== null && thinkingTokens !== void 0) usage.reasoningTokens = thinkingTokens;
281
- const cacheRead = event.usage.cache_read_input_tokens;
282
- if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
283
- const cacheWrite = event.usage.cache_creation_input_tokens;
284
- if (cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
361
+ applyAnthropicCacheUsage(usage, {
362
+ cache_read_input_tokens: event.usage.cache_read_input_tokens,
363
+ cache_creation_input_tokens: event.usage.cache_creation_input_tokens,
364
+ cache_creation: usage.cacheWrite1hTokens !== void 0 ? { ephemeral_1h_input_tokens: usage.cacheWrite1hTokens } : void 0
365
+ }, cacheTtl);
366
+ if (want.usage) yield {
367
+ type: "usage",
368
+ usage: {
369
+ ...usage,
370
+ total: usage.input + usage.output
371
+ }
372
+ };
285
373
  }
286
374
  }
287
375
  } catch (thrown) {
288
376
  throw this.logAndWrap(thrown);
289
377
  }
378
+ usage.input += compaction.input;
379
+ usage.output += compaction.output;
380
+ addCacheCounts(usage, compaction);
290
381
  usage.total = usage.input + usage.output;
382
+ const cost = computeCost(usage, this.pricing);
383
+ if (cost) usage.cost = cost;
291
384
  const finishReason = mapStopReason(rawStopReason);
292
385
  const blocks = toProviderBlocks(this.finishStreamedBlocks(streamedBlocks));
293
386
  this.logger.debug(LOG_MODULE, "response", "Streaming call to messages.create succeeded", {
@@ -341,38 +434,74 @@ var AnthropicModel = class {
341
434
  * and the beta-only body fields); otherwise to `client.messages`. The
342
435
  * cancellation signal rides in the request options, as before.
343
436
  */
344
- send(body, options, method) {
345
- const betas = resolveAnthropicOptions(options).betas;
437
+ dispatch(request, options, method) {
346
438
  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);
439
+ const api = request.endpoint === "beta.messages" ? this.client.beta.messages : this.client.messages;
440
+ return api[method].call(api, request.endpoint === "beta.messages" ? {
441
+ ...request.body,
442
+ betas: request.betas
443
+ } : request.body, requestOptions);
352
444
  }
353
445
  /**
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}.
446
+ * The full request body and the beta flags it needs: the adapter-built
447
+ * params, then `thinking.display` when thinking is sent on an adaptive
448
+ * model, the typed `thinkingBlockBinding`, `contextManagement` and
449
+ * `serverCompaction` options, then `providerOptions.anthropic.body`
450
+ * merged last (caller fields win). The body may add fields the adapter
451
+ * does not build (`fallbacks`, …) but may not replace the conversation or
452
+ * the model — see {@link RESERVED_BODY_FIELDS}.
360
453
  */
361
454
  buildBody(messages, options) {
362
455
  const anthropic = resolveAnthropicOptions(options);
363
- const body = { ...this.buildParams(messages, options) };
456
+ const betas = [];
457
+ const body = { ...this.buildParams(messages, options, { dropRetainedThinking: this.preservedThinking && anthropic.thinkingBlockBinding === void 0 }) };
364
458
  if (anthropic.thinkingDisplay && this.thinkingMode === "adaptive" && body.thinking?.type === "adaptive") body.thinking = {
365
459
  ...body.thinking,
366
460
  display: anthropic.thinkingDisplay
367
461
  };
462
+ if (anthropic.thinkingBlockBinding) this.applyBlockBinding(body, anthropic.thinkingBlockBinding, betas);
463
+ const edits = buildContextEdits(anthropic);
464
+ if (edits.length > 0) {
465
+ if (anthropic.body?.context_management !== void 0) throw new InvalidRequestError("providerOptions.anthropic.contextManagement / serverCompaction cannot be combined with body.context_management: set the edits in one place.", { context: { fields: ["context_management"] } });
466
+ body.context_management = { edits };
467
+ if (edits.some((edit) => edit.type !== "compact_20260112")) betas.push(CONTEXT_MANAGEMENT_BETA);
468
+ if (edits.some((edit) => edit.type === "compact_20260112")) betas.push(COMPACTION_BETA);
469
+ }
368
470
  const reserved = Object.keys(anthropic.body ?? {}).filter((key) => RESERVED_BODY_FIELDS.has(key));
369
471
  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
472
  return {
371
- ...body,
372
- ...anthropic.body
473
+ body: {
474
+ ...body,
475
+ ...anthropic.body
476
+ },
477
+ betas
373
478
  };
374
479
  }
375
480
  /**
481
+ * Set `thinking.block_binding.prefix_mismatch_behavior`. It rides on
482
+ * adaptive or budget (`enabled`) thinking. A model that runs adaptive
483
+ * thinking when `thinking` is omitted (every preserved-thinking model) gets
484
+ * an explicit `{ type: "adaptive" }`, which is the same request. Any other
485
+ * shape (`between_tools`, `disabled`) takes no extra field, so the option
486
+ * is skipped with a debug log.
487
+ */
488
+ applyBlockBinding(body, behavior, betas) {
489
+ const thinking = body.thinking;
490
+ const type = thinking?.type ?? (this.preservedThinking ? "adaptive" : void 0);
491
+ if (type !== "adaptive" && type !== "enabled") {
492
+ this.logger.debug(LOG_MODULE, "thinking-block-binding.ignored", "thinkingBlockBinding needs adaptive or budget thinking on this request; not sent", {
493
+ model: this.name,
494
+ thinking: thinking?.type ?? "none"
495
+ });
496
+ return;
497
+ }
498
+ body.thinking = {
499
+ ...thinking ?? { type },
500
+ block_binding: { prefix_mismatch_behavior: behavior }
501
+ };
502
+ betas.push(THINKING_BINDING_BETA);
503
+ }
504
+ /**
376
505
  * Assemble the Anthropic request body shared by `complete()` and
377
506
  * `stream()` (each adds its own `stream` literal so the SDK's create
378
507
  * overload resolves to the right return type). Hoists the system
@@ -384,13 +513,13 @@ var AnthropicModel = class {
384
513
  * rejects the two together, and always for adaptive-mode models, which
385
514
  * reject any explicit temperature (see `acceptsTemperature`).
386
515
  */
387
- buildParams(messages, options) {
388
- const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
516
+ buildParams(messages, options, conversion) {
517
+ const { system, messages: anthropicMessages } = toAnthropicMessages(messages, conversion);
389
518
  const thinking = this.buildThinking(options?.reasoning);
390
519
  const temperature = options?.temperature ?? this.config.temperature;
391
520
  const configuredMaxTokens = options?.maxTokens ?? this.config.maxTokens;
392
521
  const thinkingBudget = thinking.thinking?.type === "enabled" ? thinking.thinking.budget_tokens : void 0;
393
- const maxTokens = configuredMaxTokens ?? (thinking.reservedTokens !== void 0 ? thinking.reservedTokens + DEFAULT_MAX_TOKENS : DEFAULT_MAX_TOKENS);
522
+ const maxTokens = configuredMaxTokens ?? this.resolveDefaultMaxTokens(options?.stream === true, thinking.reservedTokens);
394
523
  const sendTemperature = temperature !== void 0 && !thinking.thinking && acceptsTemperature(this.thinkingMode);
395
524
  if (thinkingBudget !== void 0 && configuredMaxTokens !== void 0 && configuredMaxTokens <= thinkingBudget) throw new Error(`Anthropic maxTokens (${configuredMaxTokens}) must be greater than thinking budget_tokens (${thinkingBudget})`);
396
525
  return {
@@ -400,12 +529,31 @@ var AnthropicModel = class {
400
529
  ...this.buildAutomaticCacheControl(options?.cacheControl),
401
530
  ...this.buildSystem(system, options?.cacheControl),
402
531
  ...sendTemperature ? { temperature } : {},
403
- ...this.buildTools(options?.tools, options?.cacheControl),
532
+ ...this.buildTools(options, options?.cacheControl),
404
533
  ...this.buildOutputConfig(options?.responseSchema, thinking.effort),
405
534
  ...thinking.thinking ? { thinking: thinking.thinking } : {}
406
535
  };
407
536
  }
408
537
  /**
538
+ * The `max_tokens` sent when neither the call nor the model config sets
539
+ * `maxTokens`:
540
+ *
541
+ * - streaming: the model's documented max output, or
542
+ * `DEFAULT_MAX_TOKENS` (16384) for an unknown id;
543
+ * - non-streaming: `DEFAULT_MAX_TOKENS`, capped by the model's max output
544
+ * (the SDK requires streaming for larger non-streaming requests unless
545
+ * the client sets a `timeout`; see `DEFAULT_MAX_TOKENS`).
546
+ *
547
+ * With thinking on, the value is raised to at least the thinking budget
548
+ * plus `MIN_ANSWER_TOKENS`, and never exceeds the model's max output.
549
+ */
550
+ resolveDefaultMaxTokens(stream, reservedTokens) {
551
+ const limit = this.maxOutputTokens;
552
+ const base = stream ? limit ?? DEFAULT_MAX_TOKENS : limit === void 0 ? DEFAULT_MAX_TOKENS : Math.min(limit, DEFAULT_MAX_TOKENS);
553
+ const withThinking = reservedTokens === void 0 ? base : Math.max(base, reservedTokens + MIN_ANSWER_TOKENS);
554
+ return limit === void 0 ? withThinking : Math.min(withThinking, limit);
555
+ }
556
+ /**
409
557
  * Spread-friendly `system` fragment. Returns an empty object when no
410
558
  * system prompt was hoisted out of the messages.
411
559
  *
@@ -468,12 +616,18 @@ var AnthropicModel = class {
468
616
  * `messages.d.ts:1084`), so the budget is floored at that minimum.
469
617
  *
470
618
  * 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.
619
+ * model by `noReasoningForm` (see `thinking-mode.ts` for the sources):
620
+ * - Claude Sonnet 5.5: `thinking: { type: "between_tools" }`, its only
621
+ * thinking-off form. It is sent alone — no `output_config.effort` (the
622
+ * model default, `high`, is within the `high`-or-below range it
623
+ * requires) and no `display`, both of which the API rejects alongside it.
624
+ * - Claude Sonnet 5 and Claude Haiku 5.5: `thinking: { type: "disabled" }`
625
+ * with no effort and no `display`. Haiku 5.5 accepts `disabled` only at
626
+ * effort `high` or below; its default (`medium`) applies.
627
+ * - Claude Opus 5: adaptive thinking at `output_config.effort: "low"`.
628
+ * Opus 5 accepts `disabled`, but with it the model can write a tool call
629
+ * as plain text (the call never runs) and leak thinking tags; the lowest
630
+ * effort is the documented alternative.
477
631
  * - Opus 5.5 and Fable 5/5.1: no `thinking` field. These models have no
478
632
  * thinking-off form (`disabled` and `budget_tokens` are a 400 at every
479
633
  * effort), so thinking still runs at the model's default effort. Pass
@@ -482,7 +636,7 @@ var AnthropicModel = class {
482
636
  */
483
637
  buildThinking(reasoning) {
484
638
  if (!this.capabilities.reasoning || !reasoning) return {};
485
- if (reasoning.effort === "none") return acceptsBetweenTools(this.name) ? { thinking: { type: "between_tools" } } : {};
639
+ if (reasoning.effort === "none") return this.buildNoReasoning();
486
640
  if (reasoning.maxTokens === void 0 && reasoning.effort === void 0) return {};
487
641
  const effort = reasoning.effort ?? DEFAULT_THINKING_EFFORT;
488
642
  const budget = Math.max(MIN_THINKING_BUDGET, reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[effort]);
@@ -500,6 +654,22 @@ var AnthropicModel = class {
500
654
  };
501
655
  }
502
656
  /**
657
+ * The thinking settings for `reasoning: { effort: "none" }`, per
658
+ * `noReasoningForm(this.name)` (documented on `buildThinking`).
659
+ */
660
+ buildNoReasoning() {
661
+ switch (noReasoningForm(this.name)) {
662
+ case "between_tools": return { thinking: { type: "between_tools" } };
663
+ case "disabled": return { thinking: { type: "disabled" } };
664
+ case "lowest-effort": return {
665
+ thinking: { type: "adaptive" },
666
+ effort: EFFORT_TO_OUTPUT_EFFORT.low,
667
+ reservedTokens: EFFORT_THINKING_BUDGET.low
668
+ };
669
+ case "omit": return {};
670
+ }
671
+ }
672
+ /**
503
673
  * Spread-friendly tools fragment. Returns an empty object when no
504
674
  * tools were supplied so the caller can unconditionally spread it.
505
675
  *
@@ -512,9 +682,16 @@ var AnthropicModel = class {
512
682
  * breakpoint there would pay the ~1.25x write surcharge every turn
513
683
  * with no reads.
514
684
  */
515
- buildTools(tools, cacheControl) {
685
+ buildTools(options, cacheControl) {
686
+ const { tools, definitions } = selectProviderTools(options, {
687
+ accepts: [ANTHROPIC_WIRE_FORMAT, this.provider],
688
+ module: LOG_MODULE,
689
+ model: this.name,
690
+ logger: this.logger
691
+ });
516
692
  const mapped = toAnthropicTools(tools);
517
- if (!mapped) return {};
693
+ const providerDefined = definitions;
694
+ if (!mapped) return providerDefined.length > 0 ? { tools: providerDefined } : {};
518
695
  if (this.config.promptCaching && mapped.length > 0) {
519
696
  const last = mapped.length - 1;
520
697
  const lastTool = mapped[last];
@@ -523,7 +700,7 @@ var AnthropicModel = class {
523
700
  cache_control: this.cacheControl(cacheControl?.ttl)
524
701
  };
525
702
  }
526
- return { tools: mapped };
703
+ return { tools: [...providerDefined, ...mapped] };
527
704
  }
528
705
  /**
529
706
  * Spread-friendly `output_config` fragment carrying both the native
@@ -597,27 +774,34 @@ var AnthropicModel = class {
597
774
  /**
598
775
  * Normalize Anthropic's `usage` block into the neutral `Usage` shape.
599
776
  * Anthropic reports `input_tokens` / `output_tokens` separately with
600
- * no pre-summed total, so `total` is computed. Cache-read tokens are
601
- * surfaced as `cachedTokens` and cache-write tokens as
602
- * `cacheWriteTokens`, each only when non-zero. Anthropic also reports
777
+ * no pre-summed total, so `total` is computed. `input_tokens` is the
778
+ * uncached remainder only — cache reads (`cachedTokens`) and writes
779
+ * (`cacheWriteTokens`, with the 1-hour share in `cacheWrite1hTokens`)
780
+ * come on top of it — so the usage is marked
781
+ * `inputIncludesCache: false`. Anthropic also reports
603
782
  * `output_tokens_details.thinking_tokens` as the reasoning subset of
604
783
  * its inclusive `output_tokens`; it is surfaced as `reasoningTokens`
605
- * when present, without changing the billing total.
784
+ * when present, without changing the billing total. Server-side
785
+ * compaction iterations are added in (see {@link compactionIterationUsage}).
786
+ * `cost` is attached when the model is priced.
606
787
  */
607
- extractUsage(raw) {
608
- const input = raw.input_tokens ?? 0;
609
- const output = raw.output_tokens ?? 0;
610
- const cacheRead = raw.cache_read_input_tokens;
611
- const cacheWrite = raw.cache_creation_input_tokens;
788
+ extractUsage(raw, cacheTtl) {
789
+ const compaction = compactionIterationUsage(raw);
790
+ const input = (raw.input_tokens ?? 0) + compaction.input;
791
+ const output = (raw.output_tokens ?? 0) + compaction.output;
612
792
  const thinkingTokens = raw.output_tokens_details?.thinking_tokens;
613
- return {
793
+ const usage = {
614
794
  input,
615
795
  output,
616
796
  total: input + output,
617
- ...cacheRead !== null && cacheRead !== void 0 && cacheRead > 0 ? { cachedTokens: cacheRead } : {},
618
- ...cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {},
797
+ inputIncludesCache: false,
619
798
  ...thinkingTokens !== null && thinkingTokens !== void 0 ? { reasoningTokens: thinkingTokens } : {}
620
799
  };
800
+ applyAnthropicCacheUsage(usage, raw, cacheTtl);
801
+ addCacheCounts(usage, compaction);
802
+ const cost = computeCost(usage, this.pricing);
803
+ if (cost) usage.cost = cost;
804
+ return usage;
621
805
  }
622
806
  /**
623
807
  * Wrap a thrown provider error into the typed `AIError` hierarchy and
@@ -661,6 +845,119 @@ const COUNT_TOKENS_FIELDS = [
661
845
  "context_management",
662
846
  "mcp_servers"
663
847
  ];
848
+ /** Beta flag for typed context editing (Anthropic docs, "Context editing", read 2026-10-17). */
849
+ const CONTEXT_MANAGEMENT_BETA = "context-management-2025-06-27";
850
+ /** Beta flag for threshold compaction (Anthropic docs, "Compaction at a token threshold"). */
851
+ const COMPACTION_BETA = "compact-2026-01-12";
852
+ /** Beta flag for `thinking.block_binding` (Anthropic docs, "Preserved thinking"). */
853
+ const THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
854
+ /** The API rejects a compaction trigger below 50,000 input tokens. */
855
+ const MIN_COMPACTION_TRIGGER = 5e4;
856
+ /**
857
+ * Translate the typed `contextManagement` / `serverCompaction` options into
858
+ * `context_management.edits`. `clear_thinking_20251015` must come first
859
+ * when strategies are combined (Anthropic docs, "Context editing").
860
+ */
861
+ function buildContextEdits(anthropic) {
862
+ const edits = [];
863
+ const { clearThinking, clearToolUses } = anthropic.contextManagement ?? {};
864
+ if (clearThinking) edits.push(clearThinkingEdit(clearThinking === true ? {} : clearThinking));
865
+ if (clearToolUses) edits.push(clearToolUsesEdit(clearToolUses === true ? {} : clearToolUses));
866
+ if (anthropic.serverCompaction) {
867
+ const compaction = anthropic.serverCompaction === true ? {} : anthropic.serverCompaction;
868
+ if (compaction.triggerTokens !== void 0) assertCount("serverCompaction.triggerTokens", compaction.triggerTokens, MIN_COMPACTION_TRIGGER);
869
+ edits.push({
870
+ type: "compact_20260112",
871
+ ...compaction.triggerTokens !== void 0 ? { trigger: {
872
+ type: "input_tokens",
873
+ value: compaction.triggerTokens
874
+ } } : {},
875
+ ...compaction.instructions ? { instructions: compaction.instructions } : {}
876
+ });
877
+ }
878
+ return edits;
879
+ }
880
+ function clearThinkingEdit(options) {
881
+ if (options.keep === void 0 || options.keep === "all") return {
882
+ type: "clear_thinking_20251015",
883
+ ...options.keep ? { keep: "all" } : {}
884
+ };
885
+ assertCount("contextManagement.clearThinking.keep", options.keep, 1);
886
+ return {
887
+ type: "clear_thinking_20251015",
888
+ keep: {
889
+ type: "thinking_turns",
890
+ value: options.keep
891
+ }
892
+ };
893
+ }
894
+ function clearToolUsesEdit(options) {
895
+ const edit = { type: "clear_tool_uses_20250919" };
896
+ if (options.trigger) {
897
+ const trigger = "inputTokens" in options.trigger ? {
898
+ type: "input_tokens",
899
+ value: options.trigger.inputTokens
900
+ } : {
901
+ type: "tool_uses",
902
+ value: options.trigger.toolUses
903
+ };
904
+ assertCount("contextManagement.clearToolUses.trigger", trigger.value, 1);
905
+ edit.trigger = trigger;
906
+ }
907
+ if (options.keep !== void 0) {
908
+ assertCount("contextManagement.clearToolUses.keep", options.keep, 0);
909
+ edit.keep = {
910
+ type: "tool_uses",
911
+ value: options.keep
912
+ };
913
+ }
914
+ if (options.clearAtLeast !== void 0) {
915
+ assertCount("contextManagement.clearToolUses.clearAtLeast", options.clearAtLeast, 1);
916
+ edit.clear_at_least = {
917
+ type: "input_tokens",
918
+ value: options.clearAtLeast
919
+ };
920
+ }
921
+ if (options.excludeTools !== void 0) edit.exclude_tools = [...options.excludeTools];
922
+ if (options.clearToolInputs !== void 0) edit.clear_tool_inputs = Array.isArray(options.clearToolInputs) ? [...options.clearToolInputs] : options.clearToolInputs;
923
+ return edit;
924
+ }
925
+ function assertCount(field, value, minimum) {
926
+ if (!Number.isSafeInteger(value) || value < minimum) throw new InvalidRequestError(`providerOptions.anthropic.${field} must be an integer of at least ${minimum}; got ${value}.`, { context: { field } });
927
+ }
928
+ /**
929
+ * Tokens of the `compaction` entries in a beta response's `usage.iterations`.
930
+ * With server-side compaction the top-level `input_tokens` / `output_tokens`
931
+ * cover only the non-compaction iterations; the billed total is the sum
932
+ * over every iteration (Anthropic docs, "Compaction at a token threshold" →
933
+ * "Understanding usage", read 2026-10-17). Zeros when there are none.
934
+ */
935
+ function compactionIterationUsage(raw) {
936
+ const totals = {
937
+ input: 0,
938
+ output: 0,
939
+ cacheRead: 0,
940
+ cacheWrite: 0
941
+ };
942
+ const iterations = raw?.iterations;
943
+ if (!Array.isArray(iterations)) return totals;
944
+ for (const entry of iterations) {
945
+ const iteration = entry;
946
+ if (iteration?.type !== "compaction") continue;
947
+ totals.input += numberOrZero(iteration.input_tokens);
948
+ totals.output += numberOrZero(iteration.output_tokens);
949
+ totals.cacheRead += numberOrZero(iteration.cache_read_input_tokens);
950
+ totals.cacheWrite += numberOrZero(iteration.cache_creation_input_tokens);
951
+ }
952
+ return totals;
953
+ }
954
+ function numberOrZero(value) {
955
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
956
+ }
957
+ function addCacheCounts(usage, extra) {
958
+ if (extra.cacheRead > 0) usage.cachedTokens = (usage.cachedTokens ?? 0) + extra.cacheRead;
959
+ if (extra.cacheWrite > 0) usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + extra.cacheWrite;
960
+ }
664
961
  /** Read `providerOptions.anthropic`, tolerating its absence or a wrong shape. */
665
962
  function resolveAnthropicOptions(options) {
666
963
  const raw = options?.providerOptions?.anthropic;