@warlock.js/ai-anthropic 5.29.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 (56) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/cjs/index.cjs +1124 -147
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/config.type.d.mts +170 -3
  5. package/esm/config.type.d.mts.map +1 -1
  6. package/esm/index.d.mts +9 -3
  7. package/esm/index.mjs +8 -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/known-vision-models.mjs +1 -1
  15. package/esm/known-vision-models.mjs.map +1 -1
  16. package/esm/model.d.mts +322 -0
  17. package/esm/model.d.mts.map +1 -0
  18. package/esm/model.mjs +523 -73
  19. package/esm/model.mjs.map +1 -1
  20. package/esm/request.type.d.mts +25 -0
  21. package/esm/request.type.d.mts.map +1 -0
  22. package/esm/sdk.d.mts +7 -6
  23. package/esm/sdk.d.mts.map +1 -1
  24. package/esm/sdk.mjs +4 -4
  25. package/esm/sdk.mjs.map +1 -1
  26. package/esm/thinking-mode.d.mts +15 -2
  27. package/esm/thinking-mode.d.mts.map +1 -1
  28. package/esm/thinking-mode.mjs +128 -2
  29. package/esm/thinking-mode.mjs.map +1 -1
  30. package/esm/utils/cache-usage.mjs +62 -0
  31. package/esm/utils/cache-usage.mjs.map +1 -0
  32. package/esm/utils/index.d.mts +3 -0
  33. package/esm/utils/index.mjs +1 -0
  34. package/esm/utils/map-stop-reason.d.mts +1 -0
  35. package/esm/utils/map-stop-reason.mjs +17 -13
  36. package/esm/utils/map-stop-reason.mjs.map +1 -1
  37. package/esm/utils/provider-blocks.d.mts +39 -0
  38. package/esm/utils/provider-blocks.d.mts.map +1 -0
  39. package/esm/utils/provider-blocks.mjs +83 -0
  40. package/esm/utils/provider-blocks.mjs.map +1 -0
  41. package/esm/utils/to-anthropic-messages.d.mts +71 -0
  42. package/esm/utils/to-anthropic-messages.d.mts.map +1 -0
  43. package/esm/utils/to-anthropic-messages.mjs +53 -13
  44. package/esm/utils/to-anthropic-messages.mjs.map +1 -1
  45. package/esm/utils/to-anthropic-tools.d.mts +19 -0
  46. package/esm/utils/to-anthropic-tools.d.mts.map +1 -0
  47. package/esm/utils/to-anthropic-tools.mjs +1 -1
  48. package/esm/utils/to-anthropic-tools.mjs.map +1 -1
  49. package/esm/utils/wrap-anthropic-error.d.mts +1 -0
  50. package/esm/utils/wrap-anthropic-error.mjs +7 -2
  51. package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
  52. package/llms-full.txt +273 -27
  53. package/llms.txt +1 -1
  54. package/package.json +5 -4
  55. package/skills/README.md +1 -1
  56. package/skills/setup-anthropic/SKILL.md +273 -27
package/esm/model.mjs CHANGED
@@ -1,23 +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 { 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";
7
+ import { toProviderBlocks } from "./utils/provider-blocks.mjs";
4
8
  import { toAnthropicMessages } from "./utils/to-anthropic-messages.mjs";
5
9
  import { toAnthropicTools } from "./utils/to-anthropic-tools.mjs";
6
10
  import { wrapAnthropicError } from "./utils/wrap-anthropic-error.mjs";
7
11
  import "./utils/index.mjs";
8
- import { safeJsonParse } from "@warlock.js/ai";
12
+ import { AIError, InvalidRequestError, computeCost, resolveStreamChunks, safeJsonParse, selectProviderTools } from "@warlock.js/ai";
9
13
  import { log } from "@warlock.js/logger";
10
14
 
11
- //#region ../ai-anthropic/src/model.ts
15
+ //#region ../../packages/ai-anthropic/src/model.ts
12
16
  const LOG_MODULE = "ai.anthropic";
17
+ /** `provider` value of `ai.providerTool()` entries this adapter sends verbatim. */
18
+ const ANTHROPIC_WIRE_FORMAT = "anthropic";
13
19
  /**
14
20
  * Anthropic requires `max_tokens` on every request (unlike OpenAI,
15
- * where it is optional). When neither the per-call option nor the
16
- * model config supplies one, fall back to a generous default so a
17
- * caller who never thought about token caps still gets a complete
18
- * 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`.
19
39
  */
20
- const DEFAULT_MAX_TOKENS = 4096;
40
+ const MIN_ANSWER_TOKENS = 4096;
21
41
  /**
22
42
  * Anthropic rejects an extended-thinking budget below 1024 tokens, so
23
43
  * any resolved budget is floored at this minimum before it reaches the
@@ -36,11 +56,10 @@ const DEFAULT_THINKING_EFFORT = "medium";
36
56
  *
37
57
  * - Budget-mode models: sent as `thinking.budget_tokens` when the caller
38
58
  * doesn't pass an explicit `reasoning.maxTokens`.
39
- * - Both modes: reserved on top of `DEFAULT_MAX_TOKENS` when the caller
40
- * sets no `maxTokens`, because thinking counts toward `max_tokens`
41
- * (installed SDK `ThinkingConfigParam` docs, `messages.d.ts:1105`).
42
- * Adaptive models therefore get the same `max_tokens` the budget path
43
- * 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`).
44
63
  */
45
64
  const EFFORT_THINKING_BUDGET = {
46
65
  minimal: 1024,
@@ -65,6 +84,12 @@ const EFFORT_TO_OUTPUT_EFFORT = {
65
84
  xhigh: "xhigh",
66
85
  max: "max"
67
86
  };
87
+ /** Content-block types whose input arrives as `input_json_delta` while streaming. */
88
+ const STREAMED_INPUT_BLOCK_TYPES = new Set([
89
+ "tool_use",
90
+ "server_tool_use",
91
+ "mcp_tool_use"
92
+ ]);
68
93
  /**
69
94
  * Anthropic-backed implementation of `ModelContract`.
70
95
  *
@@ -110,8 +135,10 @@ var AnthropicModel = class {
110
135
  this.config = config;
111
136
  this.name = config.name;
112
137
  this.provider = provider;
113
- this.pricing = config.pricing;
138
+ this.pricing = withAnthropicCacheWriteRates(config.pricing ?? anthropicPricingFor(config.name));
114
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));
115
142
  this.capabilities = {
116
143
  structuredOutput: config.structuredOutput ?? true,
117
144
  vision: config.vision ?? inferVisionCapability(config.name),
@@ -127,24 +154,52 @@ var AnthropicModel = class {
127
154
  * instance's `ModelConfig` defaults for this call only.
128
155
  */
129
156
  async complete(messages, options) {
130
- this.logger.debug(LOG_MODULE, "request", "Starting call to messages.create", {
131
- model: this.name,
132
- messageCount: messages.length,
133
- streaming: false,
134
- 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
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
135
189
  });
190
+ return request.body.stream ? this.streamRequest(request, options) : this.completeRequest(request, options);
191
+ }
192
+ async completeRequest(request, options) {
136
193
  let response;
137
194
  try {
138
- response = await this.client.messages.create({
139
- ...this.buildParams(messages, options),
140
- stream: false
141
- }, options?.signal ? { signal: options.signal } : void 0);
195
+ response = await this.dispatch(request, options, "create");
142
196
  } catch (thrown) {
143
197
  throw this.logAndWrap(thrown);
144
198
  }
145
199
  const finishReason = mapStopReason(response.stop_reason);
146
- const usage = this.extractUsage(response.usage);
200
+ const usage = this.extractUsage(response.usage, requestCacheTtl(request.body));
147
201
  const toolCalls = this.extractToolCalls(response.content);
202
+ const blocks = toProviderBlocks(response.content);
148
203
  this.logger.debug(LOG_MODULE, "response", "call to messages.create succeeded", {
149
204
  finishReason,
150
205
  usage
@@ -153,50 +208,94 @@ var AnthropicModel = class {
153
208
  content: this.extractText(response.content),
154
209
  finishReason,
155
210
  usage,
156
- toolCalls
211
+ toolCalls,
212
+ ...blocks ? { blocks } : {}
157
213
  };
158
214
  }
159
215
  /**
216
+ * Count the input tokens a request would use, through Anthropic's
217
+ * `messages.countTokens` endpoint (a network call; no tokens are
218
+ * generated or billed as output). Takes the same messages and options
219
+ * as `complete()` — system prompt, tools, thinking and
220
+ * `providerOptions.anthropic.betas` are counted exactly as they would be
221
+ * sent. For an offline estimate use `AnthropicSDK.count()`.
222
+ *
223
+ * @example
224
+ * const tokens = await model.countTokens(messages, { tools });
225
+ */
226
+ async countTokens(messages, options) {
227
+ const request = this.buildLoggedRequest(messages, {
228
+ ...options,
229
+ stream: options?.stream === true
230
+ });
231
+ const body = request.body;
232
+ const countBody = {};
233
+ for (const key of COUNT_TOKENS_FIELDS) if (body[key] !== void 0) countBody[key] = body[key];
234
+ try {
235
+ return (await this.dispatch({
236
+ ...request,
237
+ body: countBody
238
+ }, options, "countTokens")).input_tokens;
239
+ } catch (thrown) {
240
+ throw this.logAndWrap(thrown);
241
+ }
242
+ }
243
+ /**
160
244
  * Incremental streaming completion. Yields neutral `ModelStreamChunk`s
161
245
  * — `delta` for text tokens, `tool-call` once a `tool_use` block's
162
246
  * arguments have fully accumulated, and a terminal `done` carrying the
163
247
  * final finish reason + usage totals. Callers consume it with
164
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).
165
254
  */
166
255
  async *stream(messages, options) {
167
- this.logger.debug(LOG_MODULE, "request", "Starting streaming call to messages.create", {
168
- model: this.name,
169
- messageCount: messages.length,
170
- streaming: true,
171
- 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
172
262
  });
263
+ }
264
+ async *streamRequest(request, options) {
173
265
  let stream;
174
266
  try {
175
- stream = await this.client.messages.create({
176
- ...this.buildParams(messages, options),
177
- stream: true
178
- }, options?.signal ? { signal: options.signal } : void 0);
267
+ stream = await this.dispatch(request, options, "create");
179
268
  } catch (thrown) {
180
269
  throw this.logAndWrap(thrown);
181
270
  }
182
271
  let rawStopReason = null;
272
+ let compaction = compactionIterationUsage(void 0);
183
273
  const usage = {
184
274
  input: 0,
185
275
  output: 0,
186
- total: 0
276
+ total: 0,
277
+ inputIncludesCache: false
187
278
  };
279
+ const cacheTtl = requestCacheTtl(request.body);
280
+ const want = resolveStreamChunks(options);
188
281
  const toolBlocks = /* @__PURE__ */ new Map();
189
282
  const thinkingBlocks = /* @__PURE__ */ new Map();
283
+ const streamedBlocks = /* @__PURE__ */ new Map();
190
284
  try {
191
285
  for await (const event of stream) {
286
+ this.trackStreamedBlock(streamedBlocks, event);
192
287
  if (event.type === "message_start") {
193
288
  usage.input = event.message.usage.input_tokens ?? 0;
194
289
  const thinkingTokens = event.message.usage.output_tokens_details?.thinking_tokens;
195
290
  if (thinkingTokens !== null && thinkingTokens !== void 0) usage.reasoningTokens = thinkingTokens;
196
- const cacheRead = event.message.usage.cache_read_input_tokens;
197
- if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
198
- const cacheWrite = event.message.usage.cache_creation_input_tokens;
199
- 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
+ };
200
299
  continue;
201
300
  }
202
301
  if (event.type === "content_block_start") {
@@ -217,10 +316,22 @@ var AnthropicModel = class {
217
316
  };
218
317
  else if (event.delta.type === "input_json_delta") {
219
318
  const accumulator = toolBlocks.get(event.index);
220
- 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
+ }
221
328
  } else if (event.delta.type === "thinking_delta") {
222
329
  const thinking = thinkingBlocks.get(event.index);
223
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
+ };
224
335
  } else if (event.delta.type === "signature_delta") {
225
336
  const thinking = thinkingBlocks.get(event.index);
226
337
  if (thinking?.type === "thinking") thinking.signature += event.delta.signature;
@@ -243,20 +354,35 @@ var AnthropicModel = class {
243
354
  }
244
355
  if (event.type === "message_delta") {
245
356
  rawStopReason = event.delta.stop_reason ?? rawStopReason;
357
+ if (Array.isArray(event.usage.iterations)) compaction = compactionIterationUsage(event.usage);
246
358
  usage.output = event.usage.output_tokens ?? usage.output;
247
359
  const thinkingTokens = event.usage.output_tokens_details?.thinking_tokens;
248
360
  if (thinkingTokens !== null && thinkingTokens !== void 0) usage.reasoningTokens = thinkingTokens;
249
- const cacheRead = event.usage.cache_read_input_tokens;
250
- if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
251
- const cacheWrite = event.usage.cache_creation_input_tokens;
252
- 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
+ };
253
373
  }
254
374
  }
255
375
  } catch (thrown) {
256
376
  throw this.logAndWrap(thrown);
257
377
  }
378
+ usage.input += compaction.input;
379
+ usage.output += compaction.output;
380
+ addCacheCounts(usage, compaction);
258
381
  usage.total = usage.input + usage.output;
382
+ const cost = computeCost(usage, this.pricing);
383
+ if (cost) usage.cost = cost;
259
384
  const finishReason = mapStopReason(rawStopReason);
385
+ const blocks = toProviderBlocks(this.finishStreamedBlocks(streamedBlocks));
260
386
  this.logger.debug(LOG_MODULE, "response", "Streaming call to messages.create succeeded", {
261
387
  finishReason,
262
388
  usage
@@ -264,10 +390,118 @@ var AnthropicModel = class {
264
390
  yield {
265
391
  type: "done",
266
392
  finishReason,
267
- usage
393
+ usage,
394
+ ...blocks ? { blocks } : {}
268
395
  };
269
396
  }
270
397
  /**
398
+ * Fold one stream event into the per-index block accumulator. Unknown
399
+ * block and delta types (beta `compaction`, future additions) are kept
400
+ * as their start payload plus whatever their delta carries, so they
401
+ * round-trip without the adapter knowing their shape.
402
+ */
403
+ trackStreamedBlock(blocks, event) {
404
+ if (event.type === "content_block_start") {
405
+ const block = { ...event.content_block };
406
+ blocks.set(event.index, {
407
+ block,
408
+ ...STREAMED_INPUT_BLOCK_TYPES.has(String(block.type)) ? { json: "" } : {}
409
+ });
410
+ return;
411
+ }
412
+ if (event.type !== "content_block_delta") return;
413
+ const entry = blocks.get(event.index);
414
+ if (!entry) return;
415
+ const delta = event.delta;
416
+ const block = entry.block;
417
+ if (delta.type === "text_delta") block.text = String(block.text ?? "") + String(delta.text ?? "");
418
+ else if (delta.type === "thinking_delta") block.thinking = String(block.thinking ?? "") + String(delta.thinking ?? "");
419
+ else if (delta.type === "signature_delta") block.signature = String(block.signature ?? "") + String(delta.signature ?? "");
420
+ else if (delta.type === "input_json_delta") entry.json = (entry.json ?? "") + String(delta.partial_json ?? "");
421
+ else if (delta.type === "citations_delta") block.citations = [...Array.isArray(block.citations) ? block.citations : [], delta.citation];
422
+ else for (const [key, value] of Object.entries(delta)) if (key !== "type") block[key] = value;
423
+ }
424
+ /** Close the accumulated stream blocks into an ordered content array. */
425
+ finishStreamedBlocks(blocks) {
426
+ return Array.from(blocks.entries()).sort(([left], [right]) => left - right).map(([, { block, json }]) => {
427
+ if (json !== void 0) block.input = safeJsonParse(json, {});
428
+ return block;
429
+ });
430
+ }
431
+ /**
432
+ * Send a request through the SDK. With `providerOptions.anthropic.betas`
433
+ * the call goes to `client.beta.messages` (which takes the `betas` list
434
+ * and the beta-only body fields); otherwise to `client.messages`. The
435
+ * cancellation signal rides in the request options, as before.
436
+ */
437
+ dispatch(request, options, method) {
438
+ const requestOptions = options?.signal ? { signal: options.signal } : void 0;
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);
444
+ }
445
+ /**
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}.
453
+ */
454
+ buildBody(messages, options) {
455
+ const anthropic = resolveAnthropicOptions(options);
456
+ const betas = [];
457
+ const body = { ...this.buildParams(messages, options, { dropRetainedThinking: this.preservedThinking && anthropic.thinkingBlockBinding === void 0 }) };
458
+ if (anthropic.thinkingDisplay && this.thinkingMode === "adaptive" && body.thinking?.type === "adaptive") body.thinking = {
459
+ ...body.thinking,
460
+ display: anthropic.thinkingDisplay
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
+ }
470
+ const reserved = Object.keys(anthropic.body ?? {}).filter((key) => RESERVED_BODY_FIELDS.has(key));
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 } });
472
+ return {
473
+ body: {
474
+ ...body,
475
+ ...anthropic.body
476
+ },
477
+ betas
478
+ };
479
+ }
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
+ /**
271
505
  * Assemble the Anthropic request body shared by `complete()` and
272
506
  * `stream()` (each adds its own `stream` literal so the SDK's create
273
507
  * overload resolves to the right return type). Hoists the system
@@ -279,13 +513,13 @@ var AnthropicModel = class {
279
513
  * rejects the two together, and always for adaptive-mode models, which
280
514
  * reject any explicit temperature (see `acceptsTemperature`).
281
515
  */
282
- buildParams(messages, options) {
283
- const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
516
+ buildParams(messages, options, conversion) {
517
+ const { system, messages: anthropicMessages } = toAnthropicMessages(messages, conversion);
284
518
  const thinking = this.buildThinking(options?.reasoning);
285
519
  const temperature = options?.temperature ?? this.config.temperature;
286
520
  const configuredMaxTokens = options?.maxTokens ?? this.config.maxTokens;
287
521
  const thinkingBudget = thinking.thinking?.type === "enabled" ? thinking.thinking.budget_tokens : void 0;
288
- 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);
289
523
  const sendTemperature = temperature !== void 0 && !thinking.thinking && acceptsTemperature(this.thinkingMode);
290
524
  if (thinkingBudget !== void 0 && configuredMaxTokens !== void 0 && configuredMaxTokens <= thinkingBudget) throw new Error(`Anthropic maxTokens (${configuredMaxTokens}) must be greater than thinking budget_tokens (${thinkingBudget})`);
291
525
  return {
@@ -295,12 +529,31 @@ var AnthropicModel = class {
295
529
  ...this.buildAutomaticCacheControl(options?.cacheControl),
296
530
  ...this.buildSystem(system, options?.cacheControl),
297
531
  ...sendTemperature ? { temperature } : {},
298
- ...this.buildTools(options?.tools),
532
+ ...this.buildTools(options, options?.cacheControl),
299
533
  ...this.buildOutputConfig(options?.responseSchema, thinking.effort),
300
534
  ...thinking.thinking ? { thinking: thinking.thinking } : {}
301
535
  };
302
536
  }
303
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
+ /**
304
557
  * Spread-friendly `system` fragment. Returns an empty object when no
305
558
  * system prompt was hoisted out of the messages.
306
559
  *
@@ -318,7 +571,7 @@ var AnthropicModel = class {
318
571
  if (this.capabilities.promptCaching && breakpoints > 0) return { system: [{
319
572
  type: "text",
320
573
  text: system,
321
- cache_control: this.cacheControl()
574
+ cache_control: this.cacheControl(cacheControl?.ttl)
322
575
  }] };
323
576
  return { system };
324
577
  }
@@ -329,13 +582,18 @@ var AnthropicModel = class {
329
582
  */
330
583
  buildAutomaticCacheControl(cacheControl) {
331
584
  const breakpoints = cacheControl?.breakpoints ?? 0;
332
- return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl() } : {};
585
+ return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl(cacheControl?.ttl) } : {};
333
586
  }
334
- /** Build a typed cache-control marker, omitting TTL for Anthropic's 5-minute default. */
335
- cacheControl() {
587
+ /**
588
+ * Build a typed cache-control marker. The per-call
589
+ * `cacheControl.ttl` wins over the model's `promptCacheTtl`; with
590
+ * neither, TTL is omitted for Anthropic's 5-minute default.
591
+ */
592
+ cacheControl(callTtl) {
593
+ const ttl = callTtl ?? this.config.promptCacheTtl;
336
594
  return {
337
595
  type: "ephemeral",
338
- ...this.config.promptCacheTtl ? { ttl: this.config.promptCacheTtl } : {}
596
+ ...ttl ? { ttl } : {}
339
597
  };
340
598
  }
341
599
  /**
@@ -357,12 +615,28 @@ var AnthropicModel = class {
357
615
  * budget. `budget_tokens` must be ≥ 1024 (`ThinkingConfigEnabled`,
358
616
  * `messages.d.ts:1084`), so the budget is floored at that minimum.
359
617
  *
360
- * The neutral `effort: "none"` ("run without reasoning") maps to no
361
- * `thinking` field at all in both modes.
618
+ * The neutral `effort: "none"` ("run without reasoning") is mapped per
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.
631
+ * - Opus 5.5 and Fable 5/5.1: no `thinking` field. These models have no
632
+ * thinking-off form (`disabled` and `budget_tokens` are a 400 at every
633
+ * effort), so thinking still runs at the model's default effort. Pass
634
+ * `effort: "low"` to spend less.
635
+ * - Every other model: no `thinking` field, as before.
362
636
  */
363
637
  buildThinking(reasoning) {
364
638
  if (!this.capabilities.reasoning || !reasoning) return {};
365
- if (reasoning.effort === "none") return {};
639
+ if (reasoning.effort === "none") return this.buildNoReasoning();
366
640
  if (reasoning.maxTokens === void 0 && reasoning.effort === void 0) return {};
367
641
  const effort = reasoning.effort ?? DEFAULT_THINKING_EFFORT;
368
642
  const budget = Math.max(MIN_THINKING_BUDGET, reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[effort]);
@@ -380,6 +654,22 @@ var AnthropicModel = class {
380
654
  };
381
655
  }
382
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
+ /**
383
673
  * Spread-friendly tools fragment. Returns an empty object when no
384
674
  * tools were supplied so the caller can unconditionally spread it.
385
675
  *
@@ -392,18 +682,25 @@ var AnthropicModel = class {
392
682
  * breakpoint there would pay the ~1.25x write surcharge every turn
393
683
  * with no reads.
394
684
  */
395
- buildTools(tools) {
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
+ });
396
692
  const mapped = toAnthropicTools(tools);
397
- if (!mapped) return {};
693
+ const providerDefined = definitions;
694
+ if (!mapped) return providerDefined.length > 0 ? { tools: providerDefined } : {};
398
695
  if (this.config.promptCaching && mapped.length > 0) {
399
696
  const last = mapped.length - 1;
400
697
  const lastTool = mapped[last];
401
698
  if (lastTool) mapped[last] = {
402
699
  ...lastTool,
403
- cache_control: this.cacheControl()
700
+ cache_control: this.cacheControl(cacheControl?.ttl)
404
701
  };
405
702
  }
406
- return { tools: mapped };
703
+ return { tools: [...providerDefined, ...mapped] };
407
704
  }
408
705
  /**
409
706
  * Spread-friendly `output_config` fragment carrying both the native
@@ -477,27 +774,34 @@ var AnthropicModel = class {
477
774
  /**
478
775
  * Normalize Anthropic's `usage` block into the neutral `Usage` shape.
479
776
  * Anthropic reports `input_tokens` / `output_tokens` separately with
480
- * no pre-summed total, so `total` is computed. Cache-read tokens are
481
- * surfaced as `cachedTokens` and cache-write tokens as
482
- * `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
483
782
  * `output_tokens_details.thinking_tokens` as the reasoning subset of
484
783
  * its inclusive `output_tokens`; it is surfaced as `reasoningTokens`
485
- * 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.
486
787
  */
487
- extractUsage(raw) {
488
- const input = raw.input_tokens ?? 0;
489
- const output = raw.output_tokens ?? 0;
490
- const cacheRead = raw.cache_read_input_tokens;
491
- 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;
492
792
  const thinkingTokens = raw.output_tokens_details?.thinking_tokens;
493
- return {
793
+ const usage = {
494
794
  input,
495
795
  output,
496
796
  total: input + output,
497
- ...cacheRead !== null && cacheRead !== void 0 && cacheRead > 0 ? { cachedTokens: cacheRead } : {},
498
- ...cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {},
797
+ inputIncludesCache: false,
499
798
  ...thinkingTokens !== null && thinkingTokens !== void 0 ? { reasoningTokens: thinkingTokens } : {}
500
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;
501
805
  }
502
806
  /**
503
807
  * Wrap a thrown provider error into the typed `AIError` hierarchy and
@@ -513,6 +817,152 @@ var AnthropicModel = class {
513
817
  return wrapped;
514
818
  }
515
819
  };
820
+ /**
821
+ * Request fields `providerOptions.anthropic.body` may not set: they carry
822
+ * the conversation, the model and the tool surface the agent registered,
823
+ * so an override would silently replace the agent's input (or let the
824
+ * model call tools the agent never registered). `stream` is chosen per
825
+ * call by `complete()` / `stream()`.
826
+ */
827
+ const RESERVED_BODY_FIELDS = new Set([
828
+ "model",
829
+ "messages",
830
+ "system",
831
+ "tools",
832
+ "mcp_servers",
833
+ "stream"
834
+ ]);
835
+ /** Body fields `messages.countTokens` accepts, copied from the full request body. */
836
+ const COUNT_TOKENS_FIELDS = [
837
+ "model",
838
+ "messages",
839
+ "system",
840
+ "tools",
841
+ "tool_choice",
842
+ "thinking",
843
+ "output_config",
844
+ "cache_control",
845
+ "context_management",
846
+ "mcp_servers"
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
+ }
961
+ /** Read `providerOptions.anthropic`, tolerating its absence or a wrong shape. */
962
+ function resolveAnthropicOptions(options) {
963
+ const raw = options?.providerOptions?.anthropic;
964
+ return typeof raw === "object" && raw !== null ? raw : {};
965
+ }
516
966
 
517
967
  //#endregion
518
968
  export { AnthropicModel };