@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.
- package/CHANGELOG.md +44 -6
- package/cjs/index.cjs +815 -132
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +107 -4
- package/esm/config.type.d.mts.map +1 -1
- package/esm/index.d.mts +8 -3
- package/esm/index.mjs +7 -1
- package/esm/known-output-limits.mjs +46 -0
- package/esm/known-output-limits.mjs.map +1 -0
- package/esm/known-pricing.d.mts +58 -0
- package/esm/known-pricing.d.mts.map +1 -0
- package/esm/known-pricing.mjs +165 -0
- package/esm/known-pricing.mjs.map +1 -0
- package/esm/model.d.mts +89 -17
- package/esm/model.d.mts.map +1 -1
- package/esm/model.mjs +383 -86
- package/esm/model.mjs.map +1 -1
- package/esm/request.type.d.mts +25 -0
- package/esm/request.type.d.mts.map +1 -0
- package/esm/thinking-mode.d.mts +14 -1
- package/esm/thinking-mode.d.mts.map +1 -1
- package/esm/thinking-mode.mjs +102 -2
- package/esm/thinking-mode.mjs.map +1 -1
- package/esm/utils/cache-usage.mjs +62 -0
- package/esm/utils/cache-usage.mjs.map +1 -0
- package/esm/utils/index.d.mts +3 -0
- package/esm/utils/map-stop-reason.d.mts +1 -0
- package/esm/utils/provider-blocks.d.mts +39 -0
- package/esm/utils/provider-blocks.d.mts.map +1 -0
- package/esm/utils/to-anthropic-messages.d.mts +71 -0
- package/esm/utils/to-anthropic-messages.d.mts.map +1 -0
- package/esm/utils/to-anthropic-messages.mjs +12 -4
- package/esm/utils/to-anthropic-messages.mjs.map +1 -1
- package/esm/utils/to-anthropic-tools.d.mts +19 -0
- package/esm/utils/to-anthropic-tools.d.mts.map +1 -0
- package/esm/utils/wrap-anthropic-error.d.mts +1 -0
- package/esm/utils/wrap-anthropic-error.mjs +6 -1
- package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
- package/llms-full.txt +214 -32
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/README.md +1 -1
- 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 {
|
|
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
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
|
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:
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
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.
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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.
|
|
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
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
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)
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
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 =
|
|
348
|
-
return api[method].call(api,
|
|
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
|
|
355
|
-
* `thinking.display` when thinking is sent on an adaptive
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
*
|
|
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
|
|
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
|
-
|
|
372
|
-
|
|
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 ?? (
|
|
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
|
|
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
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
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
|
|
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(
|
|
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
|
-
|
|
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.
|
|
601
|
-
*
|
|
602
|
-
* `cacheWriteTokens`,
|
|
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
|
|
609
|
-
const
|
|
610
|
-
const
|
|
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
|
-
|
|
793
|
+
const usage = {
|
|
614
794
|
input,
|
|
615
795
|
output,
|
|
616
796
|
total: input + output,
|
|
617
|
-
|
|
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;
|