@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.
- package/CHANGELOG.md +63 -0
- package/cjs/index.cjs +1124 -147
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +170 -3
- package/esm/config.type.d.mts.map +1 -1
- package/esm/index.d.mts +9 -3
- package/esm/index.mjs +8 -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/known-vision-models.mjs +1 -1
- package/esm/known-vision-models.mjs.map +1 -1
- package/esm/model.d.mts +322 -0
- package/esm/model.d.mts.map +1 -0
- package/esm/model.mjs +523 -73
- 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/sdk.d.mts +7 -6
- package/esm/sdk.d.mts.map +1 -1
- package/esm/sdk.mjs +4 -4
- package/esm/sdk.mjs.map +1 -1
- package/esm/thinking-mode.d.mts +15 -2
- package/esm/thinking-mode.d.mts.map +1 -1
- package/esm/thinking-mode.mjs +128 -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/index.mjs +1 -0
- package/esm/utils/map-stop-reason.d.mts +1 -0
- package/esm/utils/map-stop-reason.mjs +17 -13
- package/esm/utils/map-stop-reason.mjs.map +1 -1
- package/esm/utils/provider-blocks.d.mts +39 -0
- package/esm/utils/provider-blocks.d.mts.map +1 -0
- package/esm/utils/provider-blocks.mjs +83 -0
- package/esm/utils/provider-blocks.mjs.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 +53 -13
- 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/to-anthropic-tools.mjs +1 -1
- package/esm/utils/to-anthropic-tools.mjs.map +1 -1
- package/esm/utils/wrap-anthropic-error.d.mts +1 -0
- package/esm/utils/wrap-anthropic-error.mjs +7 -2
- package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
- package/llms-full.txt +273 -27
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/skills/README.md +1 -1
- 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 {
|
|
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
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
|
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:
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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.
|
|
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.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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.
|
|
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
|
-
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
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)
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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 ?? (
|
|
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?.
|
|
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
|
-
/**
|
|
335
|
-
|
|
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
|
-
...
|
|
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")
|
|
361
|
-
* `
|
|
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(
|
|
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
|
-
|
|
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.
|
|
481
|
-
*
|
|
482
|
-
* `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
|
|
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
|
|
489
|
-
const
|
|
490
|
-
const
|
|
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
|
-
|
|
793
|
+
const usage = {
|
|
494
794
|
input,
|
|
495
795
|
output,
|
|
496
796
|
total: input + output,
|
|
497
|
-
|
|
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 };
|