@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/cjs/index.cjs
CHANGED
|
@@ -31,62 +31,170 @@ _anthropic_ai_sdk = __toESM(_anthropic_ai_sdk, 1);
|
|
|
31
31
|
let _warlock_js_ai = require("@warlock.js/ai");
|
|
32
32
|
let _warlock_js_logger = require("@warlock.js/logger");
|
|
33
33
|
|
|
34
|
-
//#region
|
|
34
|
+
//#region ../../packages/ai-anthropic/src/known-pricing.ts
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* Anthropic's prompt-cache write multipliers, relative to the base input
|
|
37
|
+
* rate: a 5-minute write costs 1.25x, a 1-hour write 2x. They hold for
|
|
38
|
+
* every Claude model on Anthropic's price list.
|
|
38
39
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
* Source: https://platform.claude.com/docs/en/about-claude/pricing
|
|
41
|
+
* (fetched 2026-10-17).
|
|
42
|
+
*/
|
|
43
|
+
const ANTHROPIC_CACHE_WRITE_MULTIPLIER = 1.25;
|
|
44
|
+
const ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = 2;
|
|
45
|
+
/**
|
|
46
|
+
* Built-in Claude API list prices, USD per 1,000,000 tokens, keyed by
|
|
47
|
+
* model id. `cachedInput` is the cache-hit rate, `cacheWrite` the
|
|
48
|
+
* 5-minute write rate and `cacheWrite1h` the 1-hour write rate.
|
|
45
49
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* high-resolution vision; Claude Mythos 5.1 has "same capabilities" as
|
|
50
|
-
* Fable 5.1. The tier-5 prefixes cover every 5.x release
|
|
51
|
-
* (`claude-opus-5`, `claude-opus-5-5`, `claude-sonnet-5-5`,
|
|
52
|
-
* `claude-fable-5-1`, …). Opus/Sonnet 4.6–4.8 are already covered by the
|
|
53
|
-
* tier-4 prefixes.
|
|
50
|
+
* Source: https://platform.claude.com/docs/en/about-claude/pricing,
|
|
51
|
+
* "Model pricing" table, fetched 2026-10-17. Standard (global) routing,
|
|
52
|
+
* non-batch. Not covered, so pass `pricing` yourself when you need them:
|
|
54
53
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
54
|
+
* - Claude Haiku 5.5 — priced by prompt length (higher rates above
|
|
55
|
+
* 100,000 prompt tokens), which a flat `ModelPricing` cannot express.
|
|
56
|
+
* - US-only inference (`inference_geo: "us"`, 1.1x), fast mode, the Batch
|
|
57
|
+
* API, and Claude on Bedrock / Vertex AI (partner-priced).
|
|
58
|
+
* - Limited-availability models (Mythos) and retired models.
|
|
59
|
+
*
|
|
60
|
+
* Dated snapshot ids (`claude-haiku-4-5-20251001`) resolve to their alias
|
|
61
|
+
* entry through {@link anthropicPricingFor}.
|
|
59
62
|
*/
|
|
60
|
-
const
|
|
61
|
-
"claude-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"claude-fable-5"
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
const ANTHROPIC_MODEL_PRICING = {
|
|
64
|
+
"claude-fable-5-1": {
|
|
65
|
+
input: 10,
|
|
66
|
+
output: 50,
|
|
67
|
+
cachedInput: .25,
|
|
68
|
+
cacheWrite: 12.5,
|
|
69
|
+
cacheWrite1h: 20
|
|
70
|
+
},
|
|
71
|
+
"claude-fable-5": {
|
|
72
|
+
input: 10,
|
|
73
|
+
output: 50,
|
|
74
|
+
cachedInput: 1,
|
|
75
|
+
cacheWrite: 12.5,
|
|
76
|
+
cacheWrite1h: 20
|
|
77
|
+
},
|
|
78
|
+
"claude-opus-5-5": {
|
|
79
|
+
input: 4,
|
|
80
|
+
output: 20,
|
|
81
|
+
cachedInput: .2,
|
|
82
|
+
cacheWrite: 5,
|
|
83
|
+
cacheWrite1h: 8
|
|
84
|
+
},
|
|
85
|
+
"claude-opus-5": {
|
|
86
|
+
input: 5,
|
|
87
|
+
output: 25,
|
|
88
|
+
cachedInput: .5,
|
|
89
|
+
cacheWrite: 6.25,
|
|
90
|
+
cacheWrite1h: 10
|
|
91
|
+
},
|
|
92
|
+
"claude-opus-4-8": {
|
|
93
|
+
input: 5,
|
|
94
|
+
output: 25,
|
|
95
|
+
cachedInput: .5,
|
|
96
|
+
cacheWrite: 6.25,
|
|
97
|
+
cacheWrite1h: 10
|
|
98
|
+
},
|
|
99
|
+
"claude-opus-4-7": {
|
|
100
|
+
input: 5,
|
|
101
|
+
output: 25,
|
|
102
|
+
cachedInput: .5,
|
|
103
|
+
cacheWrite: 6.25,
|
|
104
|
+
cacheWrite1h: 10
|
|
105
|
+
},
|
|
106
|
+
"claude-opus-4-6": {
|
|
107
|
+
input: 5,
|
|
108
|
+
output: 25,
|
|
109
|
+
cachedInput: .5,
|
|
110
|
+
cacheWrite: 6.25,
|
|
111
|
+
cacheWrite1h: 10
|
|
112
|
+
},
|
|
113
|
+
"claude-opus-4-5": {
|
|
114
|
+
input: 5,
|
|
115
|
+
output: 25,
|
|
116
|
+
cachedInput: .5,
|
|
117
|
+
cacheWrite: 6.25,
|
|
118
|
+
cacheWrite1h: 10
|
|
119
|
+
},
|
|
120
|
+
"claude-sonnet-5-5": {
|
|
121
|
+
input: 2,
|
|
122
|
+
output: 10,
|
|
123
|
+
cachedInput: .1,
|
|
124
|
+
cacheWrite: 2.5,
|
|
125
|
+
cacheWrite1h: 4
|
|
126
|
+
},
|
|
127
|
+
"claude-sonnet-5": {
|
|
128
|
+
input: 2,
|
|
129
|
+
output: 10,
|
|
130
|
+
cachedInput: .2,
|
|
131
|
+
cacheWrite: 2.5,
|
|
132
|
+
cacheWrite1h: 4
|
|
133
|
+
},
|
|
134
|
+
"claude-sonnet-4-6": {
|
|
135
|
+
input: 3,
|
|
136
|
+
output: 15,
|
|
137
|
+
cachedInput: .3,
|
|
138
|
+
cacheWrite: 3.75,
|
|
139
|
+
cacheWrite1h: 6
|
|
140
|
+
},
|
|
141
|
+
"claude-sonnet-4-5": {
|
|
142
|
+
input: 3,
|
|
143
|
+
output: 15,
|
|
144
|
+
cachedInput: .3,
|
|
145
|
+
cacheWrite: 3.75,
|
|
146
|
+
cacheWrite1h: 6
|
|
147
|
+
},
|
|
148
|
+
"claude-haiku-4-5": {
|
|
149
|
+
input: 1,
|
|
150
|
+
output: 5,
|
|
151
|
+
cachedInput: .1,
|
|
152
|
+
cacheWrite: 1.25,
|
|
153
|
+
cacheWrite1h: 2
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/** A trailing `-YYYYMMDD` snapshot date on a Claude model id. */
|
|
157
|
+
const SNAPSHOT_SUFFIX = /-\d{8}$/;
|
|
71
158
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
159
|
+
* Look up the built-in list price for a Claude model id. Accepts the
|
|
160
|
+
* alias (`claude-sonnet-4-5`) or a dated snapshot
|
|
161
|
+
* (`claude-sonnet-4-5-20250929`). Returns `undefined` for any id not in
|
|
162
|
+
* {@link ANTHROPIC_MODEL_PRICING}, so cost stays unknown rather than wrong.
|
|
76
163
|
*
|
|
77
164
|
* @example
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* inferVisionCapability("claude-2.1"); // → false
|
|
81
|
-
* inferVisionCapability("custom-proxy-llm"); // → false
|
|
165
|
+
* anthropicPricingFor("claude-haiku-4-5-20251001"); // { input: 1, output: 5, … }
|
|
166
|
+
* anthropicPricingFor("claude-haiku-5-5"); // undefined (prompt-length tiers)
|
|
82
167
|
*/
|
|
83
|
-
function
|
|
84
|
-
const
|
|
85
|
-
|
|
168
|
+
function anthropicPricingFor(modelName) {
|
|
169
|
+
const exact = ANTHROPIC_MODEL_PRICING[modelName];
|
|
170
|
+
if (exact) return exact;
|
|
171
|
+
return ANTHROPIC_MODEL_PRICING[modelName.replace(SNAPSHOT_SUFFIX, "")];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Fill the cache-write rates Anthropic derives from the input rate
|
|
175
|
+
* (5-minute writes 1.25x, 1-hour writes 2x) into a pricing entry that
|
|
176
|
+
* omits them, so a caller who supplies only `{ input, output }` is no
|
|
177
|
+
* longer billed nothing for cache writes. Explicit rates always win; the
|
|
178
|
+
* cache-read rate is left alone because it differs per model.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* withAnthropicCacheWriteRates({ input: 3, output: 15 });
|
|
182
|
+
* // { input: 3, output: 15, cacheWrite: 3.75, cacheWrite1h: 6 }
|
|
183
|
+
*/
|
|
184
|
+
function withAnthropicCacheWriteRates(pricing) {
|
|
185
|
+
if (!pricing) return;
|
|
186
|
+
const cacheWrite = pricing.cacheWrite ?? pricing.cachedOutput ?? pricing.input * 1.25;
|
|
187
|
+
const cacheWrite1h = pricing.cacheWrite1h ?? pricing.input * 2;
|
|
188
|
+
if (pricing.cacheWrite === cacheWrite && pricing.cacheWrite1h === cacheWrite1h) return pricing;
|
|
189
|
+
return {
|
|
190
|
+
...pricing,
|
|
191
|
+
cacheWrite,
|
|
192
|
+
cacheWrite1h
|
|
193
|
+
};
|
|
86
194
|
}
|
|
87
195
|
|
|
88
196
|
//#endregion
|
|
89
|
-
//#region
|
|
197
|
+
//#region ../../packages/ai-anthropic/src/thinking-mode.ts
|
|
90
198
|
/**
|
|
91
199
|
* Family prefixes that are budget-mode in their entirety — plain
|
|
92
200
|
* `startsWith` match (dated and `-latest` variants included).
|
|
@@ -121,6 +229,11 @@ const BUDGET_RELEASE_ROOTS = [
|
|
|
121
229
|
* Dates (`-20250514`) and tags (`-latest`, `-v1`) are not minor versions.
|
|
122
230
|
*/
|
|
123
231
|
const NEXT_MINOR_VERSION = /^-\d{1,2}(?!\d)/;
|
|
232
|
+
/**
|
|
233
|
+
* Whether a lower-cased model id is the release `root` itself, or a dated /
|
|
234
|
+
* tagged / `@` snapshot of it — but not a later minor version of the same
|
|
235
|
+
* line (`claude-opus-4` matches `claude-opus-4-20250514`, not `claude-opus-4-7`).
|
|
236
|
+
*/
|
|
124
237
|
function matchesReleaseRoot(name, root) {
|
|
125
238
|
if (!name.startsWith(root)) return false;
|
|
126
239
|
const rest = name.slice(root.length);
|
|
@@ -163,39 +276,402 @@ function inferThinkingMode(modelName) {
|
|
|
163
276
|
function acceptsTemperature(mode) {
|
|
164
277
|
return mode === "budget";
|
|
165
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Model release roots that accept `thinking: { type: "between_tools" }`,
|
|
281
|
+
* the only way to turn thinking off on them. Source: claude-api ref,
|
|
282
|
+
* "Thinking & Effort" per-model table (cached 2026-10-06): Claude Sonnet
|
|
283
|
+
* 5.5 returns a 400 on `{ type: "disabled" }` and takes `between_tools`
|
|
284
|
+
* instead (no other field alongside it, effort `high` or below). No other
|
|
285
|
+
* current model is documented to accept it. Opus 5.5 and Fable 5/5.1 have
|
|
286
|
+
* no thinking-off form at all: `disabled` and `budget_tokens` are a 400 at
|
|
287
|
+
* every effort, so the lowest they go is `effort: "low"`.
|
|
288
|
+
*/
|
|
289
|
+
const BETWEEN_TOOLS_RELEASE_ROOTS = ["claude-sonnet-5-5"];
|
|
290
|
+
/**
|
|
291
|
+
* Whether `effort: "none"` can turn thinking off on this model with
|
|
292
|
+
* `thinking: { type: "between_tools" }`. Matches the release id with or
|
|
293
|
+
* without a platform prefix (`anthropic.claude-sonnet-5-5` on Bedrock)
|
|
294
|
+
* and with dated or tagged suffixes, but not a later minor release.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* acceptsBetweenTools("claude-sonnet-5-5"); // → true
|
|
298
|
+
* acceptsBetweenTools("claude-opus-5-5"); // → false
|
|
299
|
+
*/
|
|
300
|
+
function acceptsBetweenTools(modelName) {
|
|
301
|
+
return matchesAnyRelease(modelName, BETWEEN_TOOLS_RELEASE_ROOTS);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Whether the model id — lower-cased, with any platform prefix
|
|
305
|
+
* (`anthropic.`, `us.anthropic.` on Bedrock) removed — is one of these
|
|
306
|
+
* releases, dated or tagged, but not a later minor release.
|
|
307
|
+
*/
|
|
308
|
+
function matchesAnyRelease(modelName, roots) {
|
|
309
|
+
const normalized = modelName.toLowerCase();
|
|
310
|
+
const id = normalized.slice(normalized.lastIndexOf(".") + 1);
|
|
311
|
+
return roots.some((root) => matchesReleaseRoot(id, root));
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Releases that accept `thinking: { type: "disabled" }` and run reliably
|
|
315
|
+
* with it. Source: claude-api ref, "Thinking & Effort" per-model table
|
|
316
|
+
* (cached 2026-10-06): Claude Sonnet 5 accepts `disabled`; Claude Haiku 5.5
|
|
317
|
+
* accepts it at effort `low`, `medium` and `high` (a 400 at `xhigh` /
|
|
318
|
+
* `max`). No effort is sent alongside it, so Haiku 5.5 runs at its default,
|
|
319
|
+
* `medium`, which is inside the accepted range.
|
|
320
|
+
*/
|
|
321
|
+
const DISABLED_THINKING_RELEASE_ROOTS = ["claude-sonnet-5", "claude-haiku-5-5"];
|
|
322
|
+
/**
|
|
323
|
+
* Releases that accept `disabled` but are kept on adaptive thinking at the
|
|
324
|
+
* lowest effort instead. Claude Opus 5 accepts `disabled` at effort `high`
|
|
325
|
+
* or below, but the claude-api ref ("Two failure modes when thinking is
|
|
326
|
+
* disabled", cached 2026-10-06) documents that with thinking disabled it
|
|
327
|
+
* occasionally writes a tool call into its visible text instead of a
|
|
328
|
+
* `tool_use` block — the call never runs and no error is raised — and can
|
|
329
|
+
* leak `<thinking>` tags into the response. The ref's primary
|
|
330
|
+
* recommendation is to keep thinking on and lower `effort`, so the adapter
|
|
331
|
+
* does exactly that.
|
|
332
|
+
*/
|
|
333
|
+
const LOWEST_EFFORT_RELEASE_ROOTS = ["claude-opus-5"];
|
|
334
|
+
/**
|
|
335
|
+
* The wire form for `reasoning: { effort: "none" }` on this model:
|
|
336
|
+
*
|
|
337
|
+
* | Model | Form |
|
|
338
|
+
* |----------------------------------------|-------------------|
|
|
339
|
+
* | Claude Sonnet 5.5 | `between_tools` |
|
|
340
|
+
* | Claude Sonnet 5, Claude Haiku 5.5 | `disabled` |
|
|
341
|
+
* | Claude Opus 5 | `lowest-effort` |
|
|
342
|
+
* | Opus 5.5, Fable 5/5.1, everything else | `omit` |
|
|
343
|
+
*
|
|
344
|
+
* Opus 5.5 and Fable 5/5.1 have no thinking-off form (`disabled` and
|
|
345
|
+
* `budget_tokens` are a 400 at every effort), so nothing is sent and the
|
|
346
|
+
* model runs at its default effort; older models run without thinking when
|
|
347
|
+
* the field is absent.
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* noReasoningForm("claude-sonnet-5"); // → "disabled"
|
|
351
|
+
* noReasoningForm("claude-opus-5"); // → "lowest-effort"
|
|
352
|
+
* noReasoningForm("claude-opus-5-5"); // → "omit"
|
|
353
|
+
*/
|
|
354
|
+
function noReasoningForm(modelName) {
|
|
355
|
+
if (acceptsBetweenTools(modelName)) return "between_tools";
|
|
356
|
+
if (matchesAnyRelease(modelName, DISABLED_THINKING_RELEASE_ROOTS)) return "disabled";
|
|
357
|
+
if (matchesAnyRelease(modelName, LOWEST_EFFORT_RELEASE_ROOTS)) return "lowest-effort";
|
|
358
|
+
return "omit";
|
|
359
|
+
}
|
|
360
|
+
const PREFIX_CHECK_RELEASE_ROOTS = [
|
|
361
|
+
"claude-fable-5-1",
|
|
362
|
+
"claude-opus-5-5",
|
|
363
|
+
"claude-sonnet-5-5",
|
|
364
|
+
"claude-haiku-5-5"
|
|
365
|
+
];
|
|
366
|
+
/** Releases documented not to run the conversation check. */
|
|
367
|
+
const NO_PREFIX_CHECK_RELEASE_ROOTS = [
|
|
368
|
+
"claude-mythos-5-1",
|
|
369
|
+
"claude-mythos-5",
|
|
370
|
+
"claude-mythos-preview",
|
|
371
|
+
"claude-fable-5",
|
|
372
|
+
"claude-opus-5",
|
|
373
|
+
"claude-sonnet-5"
|
|
374
|
+
];
|
|
375
|
+
/** Families released before Claude Fable 5.1 in their entirety. */
|
|
376
|
+
const NO_PREFIX_CHECK_FAMILY_PREFIXES = [
|
|
377
|
+
...BUDGET_FAMILY_PREFIXES,
|
|
378
|
+
"claude-opus-4",
|
|
379
|
+
"claude-sonnet-4"
|
|
380
|
+
];
|
|
381
|
+
/**
|
|
382
|
+
* Whether this model binds a replayed thinking block to the exact
|
|
383
|
+
* conversation before it (preserved thinking). Unknown and future ids are
|
|
384
|
+
* assumed to run the check — the safe direction: reasoning a kept turn
|
|
385
|
+
* can't use is left out instead of risking a 400. Matches the id with or
|
|
386
|
+
* without a platform prefix and with dated or tagged suffixes.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* inferPreservedThinking("claude-opus-5-5"); // → true
|
|
390
|
+
* inferPreservedThinking("claude-opus-5"); // → false
|
|
391
|
+
* inferPreservedThinking("claude-opus-4-8"); // → false
|
|
392
|
+
*/
|
|
393
|
+
function inferPreservedThinking(modelName) {
|
|
394
|
+
const normalized = modelName.toLowerCase();
|
|
395
|
+
const id = normalized.slice(normalized.lastIndexOf(".") + 1);
|
|
396
|
+
if (PREFIX_CHECK_RELEASE_ROOTS.some((root) => matchesReleaseRoot(id, root))) return true;
|
|
397
|
+
if (NO_PREFIX_CHECK_RELEASE_ROOTS.some((root) => matchesReleaseRoot(id, root)) || NO_PREFIX_CHECK_FAMILY_PREFIXES.some((prefix) => id.startsWith(prefix))) return false;
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
//#endregion
|
|
402
|
+
//#region ../../packages/ai-anthropic/src/known-output-limits.ts
|
|
403
|
+
/** Family prefixes whose every release has a 128K output limit. */
|
|
404
|
+
const FAMILY_128K_PREFIXES = [
|
|
405
|
+
"claude-fable-5",
|
|
406
|
+
"claude-mythos-5",
|
|
407
|
+
"claude-opus-5",
|
|
408
|
+
"claude-sonnet-5",
|
|
409
|
+
"claude-haiku-5"
|
|
410
|
+
];
|
|
411
|
+
/** Individual releases, matched as a release root (dated / `@` variants included). */
|
|
412
|
+
const RELEASE_OUTPUT_LIMITS = [
|
|
413
|
+
["claude-opus-4-8", 128e3],
|
|
414
|
+
["claude-opus-4-7", 128e3],
|
|
415
|
+
["claude-opus-4-6", 128e3],
|
|
416
|
+
["claude-sonnet-4-6", 128e3],
|
|
417
|
+
["claude-opus-4-5", 64e3],
|
|
418
|
+
["claude-sonnet-4-5", 64e3],
|
|
419
|
+
["claude-haiku-4-5", 64e3]
|
|
420
|
+
];
|
|
421
|
+
/**
|
|
422
|
+
* The documented maximum output tokens of a Claude model, or `undefined`
|
|
423
|
+
* when the id is not a known Claude model. Platform prefixes before
|
|
424
|
+
* `claude-` (`anthropic.`, `us.anthropic.`) and dated or `@` snapshot
|
|
425
|
+
* suffixes are accepted, so the Bedrock / Google Cloud ids of a known
|
|
426
|
+
* model resolve too. Override per model with
|
|
427
|
+
* `anthropic.model({ name, maxOutputTokens })`.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* inferMaxOutputTokens("claude-opus-5-5"); // → 128000
|
|
431
|
+
* inferMaxOutputTokens("claude-haiku-4-5-20251001"); // → 64000
|
|
432
|
+
* inferMaxOutputTokens("my-gateway/model"); // → undefined
|
|
433
|
+
*/
|
|
434
|
+
function inferMaxOutputTokens(modelName) {
|
|
435
|
+
const lowered = modelName.toLowerCase();
|
|
436
|
+
const start = lowered.indexOf("claude-");
|
|
437
|
+
if (start === -1) return;
|
|
438
|
+
const normalized = lowered.slice(start);
|
|
439
|
+
if (FAMILY_128K_PREFIXES.some((prefix) => normalized.startsWith(prefix))) return 128e3;
|
|
440
|
+
for (const [root, limit] of RELEASE_OUTPUT_LIMITS) if (matchesReleaseRoot(normalized, root)) return limit;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
//#endregion
|
|
444
|
+
//#region ../../packages/ai-anthropic/src/known-vision-models.ts
|
|
445
|
+
/**
|
|
446
|
+
* Model-name prefixes for Claude families that accept image input
|
|
447
|
+
* (vision) on the Messages API.
|
|
448
|
+
*
|
|
449
|
+
* Every Claude 3, Claude 3.5/3.7, Claude 4 and Claude 5 family model is
|
|
450
|
+
* multimodal, so the list covers both the dotted legacy naming
|
|
451
|
+
* (`claude-3-haiku-...`, `claude-3-5-sonnet-...`) and the current
|
|
452
|
+
* `claude-<tier>-4-*` naming (`claude-opus-4-7`, `claude-sonnet-4-6`,
|
|
453
|
+
* `claude-haiku-4-5`). Pre-3 families (`claude-2`, `claude-instant`)
|
|
454
|
+
* are text-only and intentionally absent.
|
|
455
|
+
*
|
|
456
|
+
* The Claude 5 series is multimodal too (claude-api ref, cached
|
|
457
|
+
* 2026-09-25): its vision examples use `claude-opus-5-5`; the models list
|
|
458
|
+
* credits Claude Fable 5.1 with vision and Claude Sonnet 5 with
|
|
459
|
+
* high-resolution vision; Claude Mythos 5.1 has "same capabilities" as
|
|
460
|
+
* Fable 5.1. The tier-5 prefixes cover every 5.x release
|
|
461
|
+
* (`claude-opus-5`, `claude-opus-5-5`, `claude-sonnet-5-5`,
|
|
462
|
+
* `claude-fable-5-1`, …). Opus/Sonnet 4.6–4.8 are already covered by the
|
|
463
|
+
* tier-4 prefixes.
|
|
464
|
+
*
|
|
465
|
+
* Matched as a prefix so dated variants (`claude-opus-4-20250514`) are
|
|
466
|
+
* covered without listing every release tag. Devs can always override
|
|
467
|
+
* per-model via `anthropic.model({ name, vision: true | false })` —
|
|
468
|
+
* explicit config wins over inference in either direction.
|
|
469
|
+
*/
|
|
470
|
+
const VISION_CAPABLE_PREFIXES = [
|
|
471
|
+
"claude-3",
|
|
472
|
+
"claude-4",
|
|
473
|
+
"claude-opus-4",
|
|
474
|
+
"claude-sonnet-4",
|
|
475
|
+
"claude-haiku-4",
|
|
476
|
+
"claude-opus-5",
|
|
477
|
+
"claude-sonnet-5",
|
|
478
|
+
"claude-fable-5",
|
|
479
|
+
"claude-mythos-5"
|
|
480
|
+
];
|
|
481
|
+
/**
|
|
482
|
+
* Infer whether a given Claude model name supports vision based on the
|
|
483
|
+
* known-prefix list. Unknown models default to `false` so that passing
|
|
484
|
+
* an image attachment to an unsupported model surfaces a clear,
|
|
485
|
+
* agent-side capability error instead of an opaque Anthropic 400.
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* inferVisionCapability("claude-sonnet-4-6"); // → true
|
|
489
|
+
* inferVisionCapability("claude-3-5-sonnet-latest"); // → true
|
|
490
|
+
* inferVisionCapability("claude-2.1"); // → false
|
|
491
|
+
* inferVisionCapability("custom-proxy-llm"); // → false
|
|
492
|
+
*/
|
|
493
|
+
function inferVisionCapability(modelName) {
|
|
494
|
+
const normalized = modelName.toLowerCase();
|
|
495
|
+
return VISION_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region ../../packages/ai-anthropic/src/utils/cache-usage.ts
|
|
500
|
+
/**
|
|
501
|
+
* Copy Anthropic's cache counters onto a neutral `Usage`.
|
|
502
|
+
*
|
|
503
|
+
* - `cache_read_input_tokens` → `cachedTokens`
|
|
504
|
+
* - `cache_creation_input_tokens` → `cacheWriteTokens`
|
|
505
|
+
* - `cache_creation.ephemeral_1h_input_tokens` → `cacheWrite1hTokens`
|
|
506
|
+
* (the 1-hour writes, billed at 2x input instead of 1.25x)
|
|
507
|
+
*
|
|
508
|
+
* When Anthropic omits the per-lifetime breakdown, `requestTtl` decides:
|
|
509
|
+
* a request whose every cache marker asked for `"1h"` wrote only 1-hour
|
|
510
|
+
* entries. Counters are set only when positive, so an absent channel
|
|
511
|
+
* stays undefined. Anthropic's `input_tokens` excludes all of these, so
|
|
512
|
+
* the caller marks the usage `inputIncludesCache: false`.
|
|
513
|
+
*/
|
|
514
|
+
function applyAnthropicCacheUsage(usage, raw, requestTtl) {
|
|
515
|
+
const cacheRead = raw.cache_read_input_tokens;
|
|
516
|
+
if (typeof cacheRead === "number" && cacheRead > 0) usage.cachedTokens = cacheRead;
|
|
517
|
+
const cacheWrite = raw.cache_creation_input_tokens;
|
|
518
|
+
if (typeof cacheWrite !== "number" || cacheWrite <= 0) return;
|
|
519
|
+
usage.cacheWriteTokens = cacheWrite;
|
|
520
|
+
const reported1h = raw.cache_creation?.ephemeral_1h_input_tokens;
|
|
521
|
+
const writes1h = typeof reported1h === "number" ? reported1h : requestTtl === "1h" ? cacheWrite : 0;
|
|
522
|
+
if (writes1h > 0) usage.cacheWrite1hTokens = Math.min(writes1h, cacheWrite);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Scan a request body (top level, system, tools, messages) for
|
|
526
|
+
* `cache_control` markers and report the lifetime they request. Used only
|
|
527
|
+
* as a fallback when the response lacks Anthropic's per-lifetime
|
|
528
|
+
* cache-write breakdown.
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* requestCacheTtl({ cache_control: { type: "ephemeral", ttl: "1h" } }); // "1h"
|
|
532
|
+
*/
|
|
533
|
+
function requestCacheTtl(body) {
|
|
534
|
+
let found;
|
|
535
|
+
const visit = (value, depth) => {
|
|
536
|
+
if (depth > 8 || typeof value !== "object" || value === null) return;
|
|
537
|
+
if (Array.isArray(value)) {
|
|
538
|
+
for (const item of value) visit(item, depth + 1);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const record = value;
|
|
542
|
+
const marker = record.cache_control;
|
|
543
|
+
if (typeof marker === "object" && marker !== null) {
|
|
544
|
+
const ttl = marker.ttl === "1h" ? "1h" : "5m";
|
|
545
|
+
found = found === void 0 || found === ttl ? ttl : "mixed";
|
|
546
|
+
}
|
|
547
|
+
for (const key of [
|
|
548
|
+
"system",
|
|
549
|
+
"tools",
|
|
550
|
+
"messages",
|
|
551
|
+
"content"
|
|
552
|
+
]) if (key in record) visit(record[key], depth + 1);
|
|
553
|
+
};
|
|
554
|
+
visit(body, 0);
|
|
555
|
+
return found;
|
|
556
|
+
}
|
|
166
557
|
|
|
167
558
|
//#endregion
|
|
168
|
-
//#region
|
|
559
|
+
//#region ../../packages/ai-anthropic/src/utils/map-stop-reason.ts
|
|
169
560
|
const stopReasonMap = {
|
|
170
561
|
end_turn: "stop",
|
|
171
562
|
stop_sequence: "stop",
|
|
563
|
+
compaction: "stop",
|
|
172
564
|
max_tokens: "length",
|
|
173
|
-
model_context_window_exceeded: "
|
|
565
|
+
model_context_window_exceeded: "context_exceeded",
|
|
566
|
+
pause_turn: "pause_turn",
|
|
567
|
+
refusal: "refusal",
|
|
174
568
|
tool_use: "tool_calls"
|
|
175
569
|
};
|
|
176
570
|
/**
|
|
177
571
|
* Map Anthropic's `stop_reason` to the normalized `FinishReason` union.
|
|
178
572
|
*
|
|
179
|
-
* `end_turn` / `stop_sequence` are
|
|
180
|
-
* `
|
|
181
|
-
* `
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
573
|
+
* `end_turn` / `stop_sequence` (and beta `compaction`) are natural stops,
|
|
574
|
+
* `max_tokens` maps to `length`, `tool_use` to `tool_calls`.
|
|
575
|
+
* `model_context_window_exceeded`, `pause_turn` and `refusal` keep their
|
|
576
|
+
* own reasons (`context_exceeded`, `pause_turn`, `refusal`) because each
|
|
577
|
+
* needs a different reaction: compact the history, send the turn back to
|
|
578
|
+
* resume, or fall back to another model. `null` and any value a future
|
|
579
|
+
* API version adds fall through to `"error"`.
|
|
186
580
|
*
|
|
187
581
|
* @example
|
|
188
|
-
* mapStopReason("end_turn");
|
|
189
|
-
* mapStopReason("tool_use");
|
|
190
|
-
* mapStopReason("refusal");
|
|
191
|
-
* mapStopReason(
|
|
582
|
+
* mapStopReason("end_turn"); // "stop"
|
|
583
|
+
* mapStopReason("tool_use"); // "tool_calls"
|
|
584
|
+
* mapStopReason("refusal"); // "refusal"
|
|
585
|
+
* mapStopReason("pause_turn"); // "pause_turn"
|
|
586
|
+
* mapStopReason(null); // "error"
|
|
192
587
|
*/
|
|
193
588
|
function mapStopReason(raw) {
|
|
194
589
|
return stopReasonMap[raw ?? ""] ?? "error";
|
|
195
590
|
}
|
|
196
591
|
|
|
197
592
|
//#endregion
|
|
198
|
-
//#region
|
|
593
|
+
//#region ../../packages/ai-anthropic/src/utils/provider-blocks.ts
|
|
594
|
+
/** The `ProviderContentBlock.provider` value this adapter writes and replays. */
|
|
595
|
+
const ANTHROPIC_BLOCK_FORMAT = "anthropic";
|
|
596
|
+
/** Block types the neutral `content` + `toolCalls` fields already carry losslessly. */
|
|
597
|
+
const NEUTRAL_BLOCK_TYPES = new Set(["text", "tool_use"]);
|
|
598
|
+
/** Block types that carry a model-requested call whose id must match `toolCalls`. */
|
|
599
|
+
const TOOL_USE_BLOCK_TYPES = new Set(["tool_use"]);
|
|
600
|
+
/**
|
|
601
|
+
* Assistant-turn block types that may be sent back verbatim. Server-tool
|
|
602
|
+
* results all end in `_tool_result` (`web_search_tool_result`,
|
|
603
|
+
* `code_execution_tool_result`, …) and are matched by suffix; anything
|
|
604
|
+
* else (an unknown or user-side type such as `image`, `document` or a
|
|
605
|
+
* client `tool_result`) is not replayed.
|
|
606
|
+
*/
|
|
607
|
+
const REPLAYABLE_BLOCK_TYPES = new Set([
|
|
608
|
+
"text",
|
|
609
|
+
"thinking",
|
|
610
|
+
"redacted_thinking",
|
|
611
|
+
"tool_use",
|
|
612
|
+
"server_tool_use",
|
|
613
|
+
"mcp_tool_use",
|
|
614
|
+
"mcp_tool_result",
|
|
615
|
+
"container_upload",
|
|
616
|
+
"compaction"
|
|
617
|
+
]);
|
|
618
|
+
const SERVER_TOOL_RESULT_TYPE = /^[a-z][a-z0-9_]*_tool_result$/;
|
|
619
|
+
/**
|
|
620
|
+
* Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or
|
|
621
|
+
* return `undefined` when the neutral fields already carry the whole turn
|
|
622
|
+
* (only plain text and `tool_use` blocks). Any other block — thinking,
|
|
623
|
+
* redacted thinking, compaction, server-tool use/results, cited text —
|
|
624
|
+
* makes the full ordered list worth keeping so the next request can
|
|
625
|
+
* replay the turn verbatim.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* toProviderBlocks([{ type: "text", text: "hi", citations: null }]); // undefined
|
|
629
|
+
* toProviderBlocks([{ type: "thinking", thinking: "", signature: "s" }, { type: "text", text: "hi" }]);
|
|
630
|
+
* // [{ provider: "anthropic", type: "thinking", data: {...} }, { provider: "anthropic", type: "text", data: {...} }]
|
|
631
|
+
*/
|
|
632
|
+
function toProviderBlocks(content) {
|
|
633
|
+
if (!content.some(needsVerbatimReplay)) return;
|
|
634
|
+
return content.map((block) => ({
|
|
635
|
+
provider: ANTHROPIC_BLOCK_FORMAT,
|
|
636
|
+
type: block.type,
|
|
637
|
+
data: { ...block }
|
|
638
|
+
}));
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Rebuild an assistant turn's Anthropic content from stored blocks.
|
|
642
|
+
* Returns `undefined` — so the caller falls back to `content` +
|
|
643
|
+
* `toolCalls` — when there are no blocks, when any block belongs to
|
|
644
|
+
* another provider's format, when a block is not a well-formed
|
|
645
|
+
* assistant-turn block (its `data.type` must equal its `type`, and the
|
|
646
|
+
* type must be one Anthropic returns on an assistant turn), or when the
|
|
647
|
+
* blocks' `tool_use` ids differ from the message's `toolCalls` (the turn
|
|
648
|
+
* was edited after the response, e.g. by a middleware or a recovered
|
|
649
|
+
* stream call, so a verbatim replay would no longer match the tool
|
|
650
|
+
* results that follow). The checks matter when a transcript comes from
|
|
651
|
+
* outside the process (a client, a store): the block data is sent as is.
|
|
652
|
+
*/
|
|
653
|
+
function fromProviderBlocks(blocks, toolCalls) {
|
|
654
|
+
if (!blocks || blocks.length === 0) return;
|
|
655
|
+
if (!blocks.every((block) => block.provider === "anthropic" && isReplayable(block))) return;
|
|
656
|
+
const blockToolIds = blocks.filter((block) => TOOL_USE_BLOCK_TYPES.has(block.type)).map((block) => block.data.id);
|
|
657
|
+
const requestedIds = (toolCalls ?? []).map((toolCall) => toolCall.id);
|
|
658
|
+
if (blockToolIds.length !== requestedIds.length || blockToolIds.some((id, index) => id !== requestedIds[index])) return;
|
|
659
|
+
return blocks.map((block) => ({ ...block.data }));
|
|
660
|
+
}
|
|
661
|
+
function needsVerbatimReplay(block) {
|
|
662
|
+
if (!NEUTRAL_BLOCK_TYPES.has(block.type)) return true;
|
|
663
|
+
const citations = block.citations;
|
|
664
|
+
return Array.isArray(citations) && citations.length > 0;
|
|
665
|
+
}
|
|
666
|
+
function isReplayable(block) {
|
|
667
|
+
const data = block.data;
|
|
668
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return false;
|
|
669
|
+
if (data.type !== block.type) return false;
|
|
670
|
+
return REPLAYABLE_BLOCK_TYPES.has(block.type) || SERVER_TOOL_RESULT_TYPE.test(block.type);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts
|
|
199
675
|
/**
|
|
200
676
|
* Convert vendor-neutral `Message[]` into Anthropic's request shape.
|
|
201
677
|
*
|
|
@@ -206,15 +682,27 @@ function mapStopReason(raw) {
|
|
|
206
682
|
* separated) and returned separately as the top-level `system`
|
|
207
683
|
* parameter.
|
|
208
684
|
* 2. **Tool results are `user` turns.** A neutral `tool` message becomes
|
|
209
|
-
* a `
|
|
210
|
-
*
|
|
685
|
+
* a `tool_result` block keyed by `tool_use_id`. Consecutive tool
|
|
686
|
+
* messages (the results of one parallel tool-use turn) are grouped
|
|
687
|
+
* into a single `user` message, as Anthropic expects — splitting them
|
|
688
|
+
* across messages teaches the model to stop calling tools in parallel.
|
|
211
689
|
* 3. **Tool calls are `tool_use` content blocks.** An assistant message
|
|
212
690
|
* carrying `toolCalls` becomes an `assistant` message whose content
|
|
213
691
|
* is an optional leading `text` block followed by one `tool_use`
|
|
214
692
|
* block per call.
|
|
215
693
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
694
|
+
* An assistant message that carries Anthropic `blocks` (thinking,
|
|
695
|
+
* redacted thinking, compaction, server-tool blocks — see
|
|
696
|
+
* `ProviderContentBlock`) is replayed verbatim from those blocks, which
|
|
697
|
+
* keeps signed thinking and compaction state valid. Blocks from another
|
|
698
|
+
* provider, or blocks whose `tool_use` ids no longer match `toolCalls`,
|
|
699
|
+
* are ignored and the turn is rebuilt from `content` + `toolCalls`.
|
|
700
|
+
*
|
|
701
|
+
* With `dropRetainedThinking`, a turn marked `compaction: "retained"` is
|
|
702
|
+
* replayed without its thinking blocks (see {@link ToAnthropicMessagesOptions}).
|
|
703
|
+
*
|
|
704
|
+
* Other consecutive same-role turns are left as-is — the Messages API
|
|
705
|
+
* merges them server-side.
|
|
218
706
|
*
|
|
219
707
|
* @example
|
|
220
708
|
* const { system, messages } = toAnthropicMessages([
|
|
@@ -224,7 +712,7 @@ function mapStopReason(raw) {
|
|
|
224
712
|
* // system === "Be concise."
|
|
225
713
|
* // messages === [{ role: "user", content: "Hi" }]
|
|
226
714
|
*/
|
|
227
|
-
function toAnthropicMessages(messages) {
|
|
715
|
+
function toAnthropicMessages(messages, options = {}) {
|
|
228
716
|
const systemParts = [];
|
|
229
717
|
const mapped = [];
|
|
230
718
|
for (const message of messages) {
|
|
@@ -233,18 +721,33 @@ function toAnthropicMessages(messages) {
|
|
|
233
721
|
continue;
|
|
234
722
|
}
|
|
235
723
|
if (message.role === "tool") {
|
|
236
|
-
|
|
724
|
+
const toolResult = {
|
|
725
|
+
type: "tool_result",
|
|
726
|
+
tool_use_id: message.toolCallId ?? "",
|
|
727
|
+
content: stringifyContent(message.content)
|
|
728
|
+
};
|
|
729
|
+
const previous = mapped[mapped.length - 1];
|
|
730
|
+
if (previous && previous.role === "user" && isToolResultTurn(previous)) previous.content.push(toolResult);
|
|
731
|
+
else mapped.push({
|
|
237
732
|
role: "user",
|
|
238
|
-
content: [
|
|
239
|
-
type: "tool_result",
|
|
240
|
-
tool_use_id: message.toolCallId ?? "",
|
|
241
|
-
content: stringifyContent(message.content)
|
|
242
|
-
}]
|
|
733
|
+
content: [toolResult]
|
|
243
734
|
});
|
|
244
735
|
continue;
|
|
245
736
|
}
|
|
737
|
+
const dropThinking = options.dropRetainedThinking === true && message.compaction === "retained";
|
|
738
|
+
if (message.role === "assistant") {
|
|
739
|
+
const replayed = fromProviderBlocks(message.blocks, message.toolCalls);
|
|
740
|
+
const content = dropThinking ? replayed?.filter((block) => !isThinkingBlock(block)) : replayed;
|
|
741
|
+
if (content && content.length > 0) {
|
|
742
|
+
mapped.push({
|
|
743
|
+
role: "assistant",
|
|
744
|
+
content
|
|
745
|
+
});
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
246
749
|
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
247
|
-
const blocks = extractThinkingBlocks(message.toolCalls);
|
|
750
|
+
const blocks = dropThinking ? [] : extractThinkingBlocks(message.toolCalls);
|
|
248
751
|
const text = stringifyContent(message.content);
|
|
249
752
|
if (text) blocks.push({
|
|
250
753
|
type: "text",
|
|
@@ -279,6 +782,9 @@ function toAnthropicMessages(messages) {
|
|
|
279
782
|
messages: mapped
|
|
280
783
|
};
|
|
281
784
|
}
|
|
785
|
+
function isThinkingBlock(block) {
|
|
786
|
+
return block.type === "thinking" || block.type === "redacted_thinking";
|
|
787
|
+
}
|
|
282
788
|
/**
|
|
283
789
|
* Recover signed extended-thinking blocks stored by this adapter on a tool
|
|
284
790
|
* request. Anthropic requires these blocks to be replayed before the
|
|
@@ -305,6 +811,14 @@ function toAnthropicThinkingBlock(block) {
|
|
|
305
811
|
}];
|
|
306
812
|
return [];
|
|
307
813
|
}
|
|
814
|
+
/**
|
|
815
|
+
* A `user` turn this function built from tool results — its content is a
|
|
816
|
+
* non-empty array made only of `tool_result` blocks. Later tool results
|
|
817
|
+
* of the same tool-use turn are appended to it.
|
|
818
|
+
*/
|
|
819
|
+
function isToolResultTurn(message) {
|
|
820
|
+
return Array.isArray(message.content) && message.content.length > 0 && message.content.every((block) => block.type === "tool_result");
|
|
821
|
+
}
|
|
308
822
|
function isRecord(value) {
|
|
309
823
|
return typeof value === "object" && value !== null;
|
|
310
824
|
}
|
|
@@ -359,7 +873,7 @@ function toAnthropicContentBlock(part) {
|
|
|
359
873
|
}
|
|
360
874
|
|
|
361
875
|
//#endregion
|
|
362
|
-
//#region
|
|
876
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-tools.ts
|
|
363
877
|
/**
|
|
364
878
|
* Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.
|
|
365
879
|
* Uses the shared `extractJsonSchema` helper; Anthropic requires the
|
|
@@ -392,7 +906,7 @@ function toInputSchema(input) {
|
|
|
392
906
|
}
|
|
393
907
|
|
|
394
908
|
//#endregion
|
|
395
|
-
//#region
|
|
909
|
+
//#region ../../packages/ai-anthropic/src/utils/wrap-anthropic-error.ts
|
|
396
910
|
/**
|
|
397
911
|
* Wrap any thrown value caught inside the Anthropic adapter into the
|
|
398
912
|
* appropriate `@warlock.js/ai` `AIError` subclass.
|
|
@@ -442,6 +956,11 @@ function wrapAnthropicError(thrown) {
|
|
|
442
956
|
context,
|
|
443
957
|
retryAfter: parseRetryAfter(shape.headers)
|
|
444
958
|
});
|
|
959
|
+
if (shape.type === "overloaded_error" || shape.status === 529) return new _warlock_js_ai.ProviderOverloadedError(message, {
|
|
960
|
+
cause: thrown,
|
|
961
|
+
context,
|
|
962
|
+
retryAfter: parseRetryAfter(shape.headers)
|
|
963
|
+
});
|
|
445
964
|
if (shape.type === "invalid_request_error" || isClientStatus(shape.status)) {
|
|
446
965
|
if (/prompt is too long/i.test(message)) return new _warlock_js_ai.ContextLengthExceededError(message, {
|
|
447
966
|
cause: thrown,
|
|
@@ -545,16 +1064,32 @@ function parseRetryAfter(headers) {
|
|
|
545
1064
|
}
|
|
546
1065
|
|
|
547
1066
|
//#endregion
|
|
548
|
-
//#region
|
|
1067
|
+
//#region ../../packages/ai-anthropic/src/model.ts
|
|
549
1068
|
const LOG_MODULE = "ai.anthropic";
|
|
1069
|
+
/** `provider` value of `ai.providerTool()` entries this adapter sends verbatim. */
|
|
1070
|
+
const ANTHROPIC_WIRE_FORMAT = "anthropic";
|
|
550
1071
|
/**
|
|
551
1072
|
* Anthropic requires `max_tokens` on every request (unlike OpenAI,
|
|
552
|
-
* where it is optional). When neither the per-call option nor the
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
1073
|
+
* where it is optional). When neither the per-call option nor the model
|
|
1074
|
+
* config supplies one, the adapter picks a default large enough for agent
|
|
1075
|
+
* work (see `resolveDefaultMaxTokens`):
|
|
1076
|
+
*
|
|
1077
|
+
* - streaming: the model's documented max output (`known-output-limits.ts`,
|
|
1078
|
+
* or `config.maxOutputTokens`), else this value;
|
|
1079
|
+
* - non-streaming: this value, capped by the model's max output. The
|
|
1080
|
+
* installed `@anthropic-ai/sdk` (0.129.0, `calculateNonstreamingTimeout`)
|
|
1081
|
+
* refuses a non-streaming request whose `max_tokens` implies more than
|
|
1082
|
+
* ten minutes (above ~21,333 tokens) unless the client sets a `timeout`,
|
|
1083
|
+
* so the non-streaming default stays below that.
|
|
1084
|
+
*/
|
|
1085
|
+
const DEFAULT_MAX_TOKENS = 16384;
|
|
1086
|
+
/**
|
|
1087
|
+
* Answer room kept on top of a thinking budget when the default
|
|
1088
|
+
* `max_tokens` is sized: thinking counts toward `max_tokens` (installed
|
|
1089
|
+
* SDK `ThinkingConfigParam` docs, `messages.d.ts:1105`), and in budget
|
|
1090
|
+
* mode `max_tokens` must exceed `budget_tokens`.
|
|
556
1091
|
*/
|
|
557
|
-
const
|
|
1092
|
+
const MIN_ANSWER_TOKENS = 4096;
|
|
558
1093
|
/**
|
|
559
1094
|
* Anthropic rejects an extended-thinking budget below 1024 tokens, so
|
|
560
1095
|
* any resolved budget is floored at this minimum before it reaches the
|
|
@@ -573,11 +1108,10 @@ const DEFAULT_THINKING_EFFORT = "medium";
|
|
|
573
1108
|
*
|
|
574
1109
|
* - Budget-mode models: sent as `thinking.budget_tokens` when the caller
|
|
575
1110
|
* doesn't pass an explicit `reasoning.maxTokens`.
|
|
576
|
-
* - Both modes:
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
* has always produced.
|
|
1111
|
+
* - Both modes: when the caller sets no `maxTokens`, the default
|
|
1112
|
+
* `max_tokens` keeps at least this budget plus `MIN_ANSWER_TOKENS`,
|
|
1113
|
+
* because thinking counts toward `max_tokens` (installed SDK
|
|
1114
|
+
* `ThinkingConfigParam` docs, `messages.d.ts:1105`).
|
|
581
1115
|
*/
|
|
582
1116
|
const EFFORT_THINKING_BUDGET = {
|
|
583
1117
|
minimal: 1024,
|
|
@@ -602,6 +1136,12 @@ const EFFORT_TO_OUTPUT_EFFORT = {
|
|
|
602
1136
|
xhigh: "xhigh",
|
|
603
1137
|
max: "max"
|
|
604
1138
|
};
|
|
1139
|
+
/** Content-block types whose input arrives as `input_json_delta` while streaming. */
|
|
1140
|
+
const STREAMED_INPUT_BLOCK_TYPES = new Set([
|
|
1141
|
+
"tool_use",
|
|
1142
|
+
"server_tool_use",
|
|
1143
|
+
"mcp_tool_use"
|
|
1144
|
+
]);
|
|
605
1145
|
/**
|
|
606
1146
|
* Anthropic-backed implementation of `ModelContract`.
|
|
607
1147
|
*
|
|
@@ -647,8 +1187,10 @@ var AnthropicModel = class {
|
|
|
647
1187
|
this.config = config;
|
|
648
1188
|
this.name = config.name;
|
|
649
1189
|
this.provider = provider;
|
|
650
|
-
this.pricing = config.pricing;
|
|
1190
|
+
this.pricing = withAnthropicCacheWriteRates(config.pricing ?? anthropicPricingFor(config.name));
|
|
651
1191
|
this.thinkingMode = config.thinkingMode ?? inferThinkingMode(config.name);
|
|
1192
|
+
this.maxOutputTokens = config.maxOutputTokens ?? inferMaxOutputTokens(config.name);
|
|
1193
|
+
this.preservedThinking = config.preservedThinking ?? (this.thinkingMode === "adaptive" && inferPreservedThinking(config.name));
|
|
652
1194
|
this.capabilities = {
|
|
653
1195
|
structuredOutput: config.structuredOutput ?? true,
|
|
654
1196
|
vision: config.vision ?? inferVisionCapability(config.name),
|
|
@@ -664,24 +1206,52 @@ var AnthropicModel = class {
|
|
|
664
1206
|
* instance's `ModelConfig` defaults for this call only.
|
|
665
1207
|
*/
|
|
666
1208
|
async complete(messages, options) {
|
|
667
|
-
this.
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1209
|
+
return this.send(this.buildLoggedRequest(messages, {
|
|
1210
|
+
...options,
|
|
1211
|
+
stream: false
|
|
1212
|
+
}), { signal: options?.signal });
|
|
1213
|
+
}
|
|
1214
|
+
buildRequest(messages, options) {
|
|
1215
|
+
const built = this.buildBody(messages, options);
|
|
1216
|
+
const betas = [...new Set([...resolveAnthropicOptions(options).betas ?? [], ...built.betas])];
|
|
1217
|
+
const body = structuredClone({
|
|
1218
|
+
...built.body,
|
|
1219
|
+
stream: options?.stream ?? false
|
|
1220
|
+
});
|
|
1221
|
+
return {
|
|
1222
|
+
endpoint: betas.length > 0 ? "beta.messages" : "messages",
|
|
1223
|
+
body,
|
|
1224
|
+
betas
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
buildLoggedRequest(messages, options) {
|
|
1228
|
+
try {
|
|
1229
|
+
return this.buildRequest(messages, options);
|
|
1230
|
+
} catch (thrown) {
|
|
1231
|
+
throw this.logAndWrap(thrown instanceof _warlock_js_ai.AIError ? thrown : new _warlock_js_ai.InvalidRequestError(`Could not build the Anthropic request: ${thrown instanceof Error ? thrown.message : String(thrown)}`, { cause: thrown }));
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
send(request, options) {
|
|
1235
|
+
const message = request.body.stream ? "Starting streaming call to messages.create" : "Starting call to messages.create";
|
|
1236
|
+
this.logger.debug(LOG_MODULE, "request", message, {
|
|
1237
|
+
model: request.body.model,
|
|
1238
|
+
messageCount: Array.isArray(request.body.messages) ? request.body.messages.length : 0,
|
|
1239
|
+
streaming: request.body.stream,
|
|
1240
|
+
toolCount: Array.isArray(request.body.tools) ? request.body.tools.length : 0
|
|
672
1241
|
});
|
|
1242
|
+
return request.body.stream ? this.streamRequest(request, options) : this.completeRequest(request, options);
|
|
1243
|
+
}
|
|
1244
|
+
async completeRequest(request, options) {
|
|
673
1245
|
let response;
|
|
674
1246
|
try {
|
|
675
|
-
response = await this.
|
|
676
|
-
...this.buildParams(messages, options),
|
|
677
|
-
stream: false
|
|
678
|
-
}, options?.signal ? { signal: options.signal } : void 0);
|
|
1247
|
+
response = await this.dispatch(request, options, "create");
|
|
679
1248
|
} catch (thrown) {
|
|
680
1249
|
throw this.logAndWrap(thrown);
|
|
681
1250
|
}
|
|
682
1251
|
const finishReason = mapStopReason(response.stop_reason);
|
|
683
|
-
const usage = this.extractUsage(response.usage);
|
|
1252
|
+
const usage = this.extractUsage(response.usage, requestCacheTtl(request.body));
|
|
684
1253
|
const toolCalls = this.extractToolCalls(response.content);
|
|
1254
|
+
const blocks = toProviderBlocks(response.content);
|
|
685
1255
|
this.logger.debug(LOG_MODULE, "response", "call to messages.create succeeded", {
|
|
686
1256
|
finishReason,
|
|
687
1257
|
usage
|
|
@@ -690,50 +1260,94 @@ var AnthropicModel = class {
|
|
|
690
1260
|
content: this.extractText(response.content),
|
|
691
1261
|
finishReason,
|
|
692
1262
|
usage,
|
|
693
|
-
toolCalls
|
|
1263
|
+
toolCalls,
|
|
1264
|
+
...blocks ? { blocks } : {}
|
|
694
1265
|
};
|
|
695
1266
|
}
|
|
696
1267
|
/**
|
|
1268
|
+
* Count the input tokens a request would use, through Anthropic's
|
|
1269
|
+
* `messages.countTokens` endpoint (a network call; no tokens are
|
|
1270
|
+
* generated or billed as output). Takes the same messages and options
|
|
1271
|
+
* as `complete()` — system prompt, tools, thinking and
|
|
1272
|
+
* `providerOptions.anthropic.betas` are counted exactly as they would be
|
|
1273
|
+
* sent. For an offline estimate use `AnthropicSDK.count()`.
|
|
1274
|
+
*
|
|
1275
|
+
* @example
|
|
1276
|
+
* const tokens = await model.countTokens(messages, { tools });
|
|
1277
|
+
*/
|
|
1278
|
+
async countTokens(messages, options) {
|
|
1279
|
+
const request = this.buildLoggedRequest(messages, {
|
|
1280
|
+
...options,
|
|
1281
|
+
stream: options?.stream === true
|
|
1282
|
+
});
|
|
1283
|
+
const body = request.body;
|
|
1284
|
+
const countBody = {};
|
|
1285
|
+
for (const key of COUNT_TOKENS_FIELDS) if (body[key] !== void 0) countBody[key] = body[key];
|
|
1286
|
+
try {
|
|
1287
|
+
return (await this.dispatch({
|
|
1288
|
+
...request,
|
|
1289
|
+
body: countBody
|
|
1290
|
+
}, options, "countTokens")).input_tokens;
|
|
1291
|
+
} catch (thrown) {
|
|
1292
|
+
throw this.logAndWrap(thrown);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
697
1296
|
* Incremental streaming completion. Yields neutral `ModelStreamChunk`s
|
|
698
1297
|
* — `delta` for text tokens, `tool-call` once a `tool_use` block's
|
|
699
1298
|
* arguments have fully accumulated, and a terminal `done` carrying the
|
|
700
1299
|
* final finish reason + usage totals. Callers consume it with
|
|
701
1300
|
* `for await`.
|
|
1301
|
+
*
|
|
1302
|
+
* With `options.streamChunks` it also yields `reasoning` for every
|
|
1303
|
+
* non-empty `thinking_delta`, `tool-args` for every `input_json_delta`
|
|
1304
|
+
* of a client `tool_use` block, and `usage` after `message_start` and
|
|
1305
|
+
* each `message_delta` (whose counts are cumulative).
|
|
702
1306
|
*/
|
|
703
1307
|
async *stream(messages, options) {
|
|
704
|
-
this.
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1308
|
+
yield* this.send(this.buildLoggedRequest(messages, {
|
|
1309
|
+
...options,
|
|
1310
|
+
stream: true
|
|
1311
|
+
}), {
|
|
1312
|
+
signal: options?.signal,
|
|
1313
|
+
streamChunks: options?.streamChunks
|
|
709
1314
|
});
|
|
1315
|
+
}
|
|
1316
|
+
async *streamRequest(request, options) {
|
|
710
1317
|
let stream;
|
|
711
1318
|
try {
|
|
712
|
-
stream = await this.
|
|
713
|
-
...this.buildParams(messages, options),
|
|
714
|
-
stream: true
|
|
715
|
-
}, options?.signal ? { signal: options.signal } : void 0);
|
|
1319
|
+
stream = await this.dispatch(request, options, "create");
|
|
716
1320
|
} catch (thrown) {
|
|
717
1321
|
throw this.logAndWrap(thrown);
|
|
718
1322
|
}
|
|
719
1323
|
let rawStopReason = null;
|
|
1324
|
+
let compaction = compactionIterationUsage(void 0);
|
|
720
1325
|
const usage = {
|
|
721
1326
|
input: 0,
|
|
722
1327
|
output: 0,
|
|
723
|
-
total: 0
|
|
1328
|
+
total: 0,
|
|
1329
|
+
inputIncludesCache: false
|
|
724
1330
|
};
|
|
1331
|
+
const cacheTtl = requestCacheTtl(request.body);
|
|
1332
|
+
const want = (0, _warlock_js_ai.resolveStreamChunks)(options);
|
|
725
1333
|
const toolBlocks = /* @__PURE__ */ new Map();
|
|
726
1334
|
const thinkingBlocks = /* @__PURE__ */ new Map();
|
|
1335
|
+
const streamedBlocks = /* @__PURE__ */ new Map();
|
|
727
1336
|
try {
|
|
728
1337
|
for await (const event of stream) {
|
|
1338
|
+
this.trackStreamedBlock(streamedBlocks, event);
|
|
729
1339
|
if (event.type === "message_start") {
|
|
730
1340
|
usage.input = event.message.usage.input_tokens ?? 0;
|
|
731
1341
|
const thinkingTokens = event.message.usage.output_tokens_details?.thinking_tokens;
|
|
732
1342
|
if (thinkingTokens !== null && thinkingTokens !== void 0) usage.reasoningTokens = thinkingTokens;
|
|
733
|
-
|
|
734
|
-
if (
|
|
735
|
-
|
|
736
|
-
|
|
1343
|
+
applyAnthropicCacheUsage(usage, event.message.usage, cacheTtl);
|
|
1344
|
+
if (want.usage) yield {
|
|
1345
|
+
type: "usage",
|
|
1346
|
+
usage: {
|
|
1347
|
+
...usage,
|
|
1348
|
+
total: usage.input + usage.output
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
737
1351
|
continue;
|
|
738
1352
|
}
|
|
739
1353
|
if (event.type === "content_block_start") {
|
|
@@ -754,10 +1368,22 @@ var AnthropicModel = class {
|
|
|
754
1368
|
};
|
|
755
1369
|
else if (event.delta.type === "input_json_delta") {
|
|
756
1370
|
const accumulator = toolBlocks.get(event.index);
|
|
757
|
-
if (accumulator)
|
|
1371
|
+
if (accumulator) {
|
|
1372
|
+
accumulator.json += event.delta.partial_json;
|
|
1373
|
+
if (want.toolArgs && event.delta.partial_json !== "") yield {
|
|
1374
|
+
type: "tool-args",
|
|
1375
|
+
callId: accumulator.id,
|
|
1376
|
+
name: accumulator.name,
|
|
1377
|
+
delta: event.delta.partial_json
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
758
1380
|
} else if (event.delta.type === "thinking_delta") {
|
|
759
1381
|
const thinking = thinkingBlocks.get(event.index);
|
|
760
1382
|
if (thinking?.type === "thinking") thinking.thinking += event.delta.thinking;
|
|
1383
|
+
if (want.reasoning && event.delta.thinking !== "") yield {
|
|
1384
|
+
type: "reasoning",
|
|
1385
|
+
delta: event.delta.thinking
|
|
1386
|
+
};
|
|
761
1387
|
} else if (event.delta.type === "signature_delta") {
|
|
762
1388
|
const thinking = thinkingBlocks.get(event.index);
|
|
763
1389
|
if (thinking?.type === "thinking") thinking.signature += event.delta.signature;
|
|
@@ -780,20 +1406,35 @@ var AnthropicModel = class {
|
|
|
780
1406
|
}
|
|
781
1407
|
if (event.type === "message_delta") {
|
|
782
1408
|
rawStopReason = event.delta.stop_reason ?? rawStopReason;
|
|
1409
|
+
if (Array.isArray(event.usage.iterations)) compaction = compactionIterationUsage(event.usage);
|
|
783
1410
|
usage.output = event.usage.output_tokens ?? usage.output;
|
|
784
1411
|
const thinkingTokens = event.usage.output_tokens_details?.thinking_tokens;
|
|
785
1412
|
if (thinkingTokens !== null && thinkingTokens !== void 0) usage.reasoningTokens = thinkingTokens;
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1413
|
+
applyAnthropicCacheUsage(usage, {
|
|
1414
|
+
cache_read_input_tokens: event.usage.cache_read_input_tokens,
|
|
1415
|
+
cache_creation_input_tokens: event.usage.cache_creation_input_tokens,
|
|
1416
|
+
cache_creation: usage.cacheWrite1hTokens !== void 0 ? { ephemeral_1h_input_tokens: usage.cacheWrite1hTokens } : void 0
|
|
1417
|
+
}, cacheTtl);
|
|
1418
|
+
if (want.usage) yield {
|
|
1419
|
+
type: "usage",
|
|
1420
|
+
usage: {
|
|
1421
|
+
...usage,
|
|
1422
|
+
total: usage.input + usage.output
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
790
1425
|
}
|
|
791
1426
|
}
|
|
792
1427
|
} catch (thrown) {
|
|
793
1428
|
throw this.logAndWrap(thrown);
|
|
794
1429
|
}
|
|
1430
|
+
usage.input += compaction.input;
|
|
1431
|
+
usage.output += compaction.output;
|
|
1432
|
+
addCacheCounts(usage, compaction);
|
|
795
1433
|
usage.total = usage.input + usage.output;
|
|
1434
|
+
const cost = (0, _warlock_js_ai.computeCost)(usage, this.pricing);
|
|
1435
|
+
if (cost) usage.cost = cost;
|
|
796
1436
|
const finishReason = mapStopReason(rawStopReason);
|
|
1437
|
+
const blocks = toProviderBlocks(this.finishStreamedBlocks(streamedBlocks));
|
|
797
1438
|
this.logger.debug(LOG_MODULE, "response", "Streaming call to messages.create succeeded", {
|
|
798
1439
|
finishReason,
|
|
799
1440
|
usage
|
|
@@ -801,8 +1442,116 @@ var AnthropicModel = class {
|
|
|
801
1442
|
yield {
|
|
802
1443
|
type: "done",
|
|
803
1444
|
finishReason,
|
|
804
|
-
usage
|
|
1445
|
+
usage,
|
|
1446
|
+
...blocks ? { blocks } : {}
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Fold one stream event into the per-index block accumulator. Unknown
|
|
1451
|
+
* block and delta types (beta `compaction`, future additions) are kept
|
|
1452
|
+
* as their start payload plus whatever their delta carries, so they
|
|
1453
|
+
* round-trip without the adapter knowing their shape.
|
|
1454
|
+
*/
|
|
1455
|
+
trackStreamedBlock(blocks, event) {
|
|
1456
|
+
if (event.type === "content_block_start") {
|
|
1457
|
+
const block = { ...event.content_block };
|
|
1458
|
+
blocks.set(event.index, {
|
|
1459
|
+
block,
|
|
1460
|
+
...STREAMED_INPUT_BLOCK_TYPES.has(String(block.type)) ? { json: "" } : {}
|
|
1461
|
+
});
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (event.type !== "content_block_delta") return;
|
|
1465
|
+
const entry = blocks.get(event.index);
|
|
1466
|
+
if (!entry) return;
|
|
1467
|
+
const delta = event.delta;
|
|
1468
|
+
const block = entry.block;
|
|
1469
|
+
if (delta.type === "text_delta") block.text = String(block.text ?? "") + String(delta.text ?? "");
|
|
1470
|
+
else if (delta.type === "thinking_delta") block.thinking = String(block.thinking ?? "") + String(delta.thinking ?? "");
|
|
1471
|
+
else if (delta.type === "signature_delta") block.signature = String(block.signature ?? "") + String(delta.signature ?? "");
|
|
1472
|
+
else if (delta.type === "input_json_delta") entry.json = (entry.json ?? "") + String(delta.partial_json ?? "");
|
|
1473
|
+
else if (delta.type === "citations_delta") block.citations = [...Array.isArray(block.citations) ? block.citations : [], delta.citation];
|
|
1474
|
+
else for (const [key, value] of Object.entries(delta)) if (key !== "type") block[key] = value;
|
|
1475
|
+
}
|
|
1476
|
+
/** Close the accumulated stream blocks into an ordered content array. */
|
|
1477
|
+
finishStreamedBlocks(blocks) {
|
|
1478
|
+
return Array.from(blocks.entries()).sort(([left], [right]) => left - right).map(([, { block, json }]) => {
|
|
1479
|
+
if (json !== void 0) block.input = (0, _warlock_js_ai.safeJsonParse)(json, {});
|
|
1480
|
+
return block;
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Send a request through the SDK. With `providerOptions.anthropic.betas`
|
|
1485
|
+
* the call goes to `client.beta.messages` (which takes the `betas` list
|
|
1486
|
+
* and the beta-only body fields); otherwise to `client.messages`. The
|
|
1487
|
+
* cancellation signal rides in the request options, as before.
|
|
1488
|
+
*/
|
|
1489
|
+
dispatch(request, options, method) {
|
|
1490
|
+
const requestOptions = options?.signal ? { signal: options.signal } : void 0;
|
|
1491
|
+
const api = request.endpoint === "beta.messages" ? this.client.beta.messages : this.client.messages;
|
|
1492
|
+
return api[method].call(api, request.endpoint === "beta.messages" ? {
|
|
1493
|
+
...request.body,
|
|
1494
|
+
betas: request.betas
|
|
1495
|
+
} : request.body, requestOptions);
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* The full request body and the beta flags it needs: the adapter-built
|
|
1499
|
+
* params, then `thinking.display` when thinking is sent on an adaptive
|
|
1500
|
+
* model, the typed `thinkingBlockBinding`, `contextManagement` and
|
|
1501
|
+
* `serverCompaction` options, then `providerOptions.anthropic.body`
|
|
1502
|
+
* merged last (caller fields win). The body may add fields the adapter
|
|
1503
|
+
* does not build (`fallbacks`, …) but may not replace the conversation or
|
|
1504
|
+
* the model — see {@link RESERVED_BODY_FIELDS}.
|
|
1505
|
+
*/
|
|
1506
|
+
buildBody(messages, options) {
|
|
1507
|
+
const anthropic = resolveAnthropicOptions(options);
|
|
1508
|
+
const betas = [];
|
|
1509
|
+
const body = { ...this.buildParams(messages, options, { dropRetainedThinking: this.preservedThinking && anthropic.thinkingBlockBinding === void 0 }) };
|
|
1510
|
+
if (anthropic.thinkingDisplay && this.thinkingMode === "adaptive" && body.thinking?.type === "adaptive") body.thinking = {
|
|
1511
|
+
...body.thinking,
|
|
1512
|
+
display: anthropic.thinkingDisplay
|
|
805
1513
|
};
|
|
1514
|
+
if (anthropic.thinkingBlockBinding) this.applyBlockBinding(body, anthropic.thinkingBlockBinding, betas);
|
|
1515
|
+
const edits = buildContextEdits(anthropic);
|
|
1516
|
+
if (edits.length > 0) {
|
|
1517
|
+
if (anthropic.body?.context_management !== void 0) throw new _warlock_js_ai.InvalidRequestError("providerOptions.anthropic.contextManagement / serverCompaction cannot be combined with body.context_management: set the edits in one place.", { context: { fields: ["context_management"] } });
|
|
1518
|
+
body.context_management = { edits };
|
|
1519
|
+
if (edits.some((edit) => edit.type !== "compact_20260112")) betas.push(CONTEXT_MANAGEMENT_BETA);
|
|
1520
|
+
if (edits.some((edit) => edit.type === "compact_20260112")) betas.push(COMPACTION_BETA);
|
|
1521
|
+
}
|
|
1522
|
+
const reserved = Object.keys(anthropic.body ?? {}).filter((key) => RESERVED_BODY_FIELDS.has(key));
|
|
1523
|
+
if (reserved.length > 0) throw new _warlock_js_ai.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 } });
|
|
1524
|
+
return {
|
|
1525
|
+
body: {
|
|
1526
|
+
...body,
|
|
1527
|
+
...anthropic.body
|
|
1528
|
+
},
|
|
1529
|
+
betas
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Set `thinking.block_binding.prefix_mismatch_behavior`. It rides on
|
|
1534
|
+
* adaptive or budget (`enabled`) thinking. A model that runs adaptive
|
|
1535
|
+
* thinking when `thinking` is omitted (every preserved-thinking model) gets
|
|
1536
|
+
* an explicit `{ type: "adaptive" }`, which is the same request. Any other
|
|
1537
|
+
* shape (`between_tools`, `disabled`) takes no extra field, so the option
|
|
1538
|
+
* is skipped with a debug log.
|
|
1539
|
+
*/
|
|
1540
|
+
applyBlockBinding(body, behavior, betas) {
|
|
1541
|
+
const thinking = body.thinking;
|
|
1542
|
+
const type = thinking?.type ?? (this.preservedThinking ? "adaptive" : void 0);
|
|
1543
|
+
if (type !== "adaptive" && type !== "enabled") {
|
|
1544
|
+
this.logger.debug(LOG_MODULE, "thinking-block-binding.ignored", "thinkingBlockBinding needs adaptive or budget thinking on this request; not sent", {
|
|
1545
|
+
model: this.name,
|
|
1546
|
+
thinking: thinking?.type ?? "none"
|
|
1547
|
+
});
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
body.thinking = {
|
|
1551
|
+
...thinking ?? { type },
|
|
1552
|
+
block_binding: { prefix_mismatch_behavior: behavior }
|
|
1553
|
+
};
|
|
1554
|
+
betas.push(THINKING_BINDING_BETA);
|
|
806
1555
|
}
|
|
807
1556
|
/**
|
|
808
1557
|
* Assemble the Anthropic request body shared by `complete()` and
|
|
@@ -816,13 +1565,13 @@ var AnthropicModel = class {
|
|
|
816
1565
|
* rejects the two together, and always for adaptive-mode models, which
|
|
817
1566
|
* reject any explicit temperature (see `acceptsTemperature`).
|
|
818
1567
|
*/
|
|
819
|
-
buildParams(messages, options) {
|
|
820
|
-
const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
|
|
1568
|
+
buildParams(messages, options, conversion) {
|
|
1569
|
+
const { system, messages: anthropicMessages } = toAnthropicMessages(messages, conversion);
|
|
821
1570
|
const thinking = this.buildThinking(options?.reasoning);
|
|
822
1571
|
const temperature = options?.temperature ?? this.config.temperature;
|
|
823
1572
|
const configuredMaxTokens = options?.maxTokens ?? this.config.maxTokens;
|
|
824
1573
|
const thinkingBudget = thinking.thinking?.type === "enabled" ? thinking.thinking.budget_tokens : void 0;
|
|
825
|
-
const maxTokens = configuredMaxTokens ?? (
|
|
1574
|
+
const maxTokens = configuredMaxTokens ?? this.resolveDefaultMaxTokens(options?.stream === true, thinking.reservedTokens);
|
|
826
1575
|
const sendTemperature = temperature !== void 0 && !thinking.thinking && acceptsTemperature(this.thinkingMode);
|
|
827
1576
|
if (thinkingBudget !== void 0 && configuredMaxTokens !== void 0 && configuredMaxTokens <= thinkingBudget) throw new Error(`Anthropic maxTokens (${configuredMaxTokens}) must be greater than thinking budget_tokens (${thinkingBudget})`);
|
|
828
1577
|
return {
|
|
@@ -832,12 +1581,31 @@ var AnthropicModel = class {
|
|
|
832
1581
|
...this.buildAutomaticCacheControl(options?.cacheControl),
|
|
833
1582
|
...this.buildSystem(system, options?.cacheControl),
|
|
834
1583
|
...sendTemperature ? { temperature } : {},
|
|
835
|
-
...this.buildTools(options?.
|
|
1584
|
+
...this.buildTools(options, options?.cacheControl),
|
|
836
1585
|
...this.buildOutputConfig(options?.responseSchema, thinking.effort),
|
|
837
1586
|
...thinking.thinking ? { thinking: thinking.thinking } : {}
|
|
838
1587
|
};
|
|
839
1588
|
}
|
|
840
1589
|
/**
|
|
1590
|
+
* The `max_tokens` sent when neither the call nor the model config sets
|
|
1591
|
+
* `maxTokens`:
|
|
1592
|
+
*
|
|
1593
|
+
* - streaming: the model's documented max output, or
|
|
1594
|
+
* `DEFAULT_MAX_TOKENS` (16384) for an unknown id;
|
|
1595
|
+
* - non-streaming: `DEFAULT_MAX_TOKENS`, capped by the model's max output
|
|
1596
|
+
* (the SDK requires streaming for larger non-streaming requests unless
|
|
1597
|
+
* the client sets a `timeout`; see `DEFAULT_MAX_TOKENS`).
|
|
1598
|
+
*
|
|
1599
|
+
* With thinking on, the value is raised to at least the thinking budget
|
|
1600
|
+
* plus `MIN_ANSWER_TOKENS`, and never exceeds the model's max output.
|
|
1601
|
+
*/
|
|
1602
|
+
resolveDefaultMaxTokens(stream, reservedTokens) {
|
|
1603
|
+
const limit = this.maxOutputTokens;
|
|
1604
|
+
const base = stream ? limit ?? DEFAULT_MAX_TOKENS : limit === void 0 ? DEFAULT_MAX_TOKENS : Math.min(limit, DEFAULT_MAX_TOKENS);
|
|
1605
|
+
const withThinking = reservedTokens === void 0 ? base : Math.max(base, reservedTokens + MIN_ANSWER_TOKENS);
|
|
1606
|
+
return limit === void 0 ? withThinking : Math.min(withThinking, limit);
|
|
1607
|
+
}
|
|
1608
|
+
/**
|
|
841
1609
|
* Spread-friendly `system` fragment. Returns an empty object when no
|
|
842
1610
|
* system prompt was hoisted out of the messages.
|
|
843
1611
|
*
|
|
@@ -855,7 +1623,7 @@ var AnthropicModel = class {
|
|
|
855
1623
|
if (this.capabilities.promptCaching && breakpoints > 0) return { system: [{
|
|
856
1624
|
type: "text",
|
|
857
1625
|
text: system,
|
|
858
|
-
cache_control: this.cacheControl()
|
|
1626
|
+
cache_control: this.cacheControl(cacheControl?.ttl)
|
|
859
1627
|
}] };
|
|
860
1628
|
return { system };
|
|
861
1629
|
}
|
|
@@ -866,13 +1634,18 @@ var AnthropicModel = class {
|
|
|
866
1634
|
*/
|
|
867
1635
|
buildAutomaticCacheControl(cacheControl) {
|
|
868
1636
|
const breakpoints = cacheControl?.breakpoints ?? 0;
|
|
869
|
-
return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl() } : {};
|
|
1637
|
+
return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl(cacheControl?.ttl) } : {};
|
|
870
1638
|
}
|
|
871
|
-
/**
|
|
872
|
-
|
|
1639
|
+
/**
|
|
1640
|
+
* Build a typed cache-control marker. The per-call
|
|
1641
|
+
* `cacheControl.ttl` wins over the model's `promptCacheTtl`; with
|
|
1642
|
+
* neither, TTL is omitted for Anthropic's 5-minute default.
|
|
1643
|
+
*/
|
|
1644
|
+
cacheControl(callTtl) {
|
|
1645
|
+
const ttl = callTtl ?? this.config.promptCacheTtl;
|
|
873
1646
|
return {
|
|
874
1647
|
type: "ephemeral",
|
|
875
|
-
...
|
|
1648
|
+
...ttl ? { ttl } : {}
|
|
876
1649
|
};
|
|
877
1650
|
}
|
|
878
1651
|
/**
|
|
@@ -894,12 +1667,28 @@ var AnthropicModel = class {
|
|
|
894
1667
|
* budget. `budget_tokens` must be ≥ 1024 (`ThinkingConfigEnabled`,
|
|
895
1668
|
* `messages.d.ts:1084`), so the budget is floored at that minimum.
|
|
896
1669
|
*
|
|
897
|
-
* The neutral `effort: "none"` ("run without reasoning")
|
|
898
|
-
* `
|
|
1670
|
+
* The neutral `effort: "none"` ("run without reasoning") is mapped per
|
|
1671
|
+
* model by `noReasoningForm` (see `thinking-mode.ts` for the sources):
|
|
1672
|
+
* - Claude Sonnet 5.5: `thinking: { type: "between_tools" }`, its only
|
|
1673
|
+
* thinking-off form. It is sent alone — no `output_config.effort` (the
|
|
1674
|
+
* model default, `high`, is within the `high`-or-below range it
|
|
1675
|
+
* requires) and no `display`, both of which the API rejects alongside it.
|
|
1676
|
+
* - Claude Sonnet 5 and Claude Haiku 5.5: `thinking: { type: "disabled" }`
|
|
1677
|
+
* with no effort and no `display`. Haiku 5.5 accepts `disabled` only at
|
|
1678
|
+
* effort `high` or below; its default (`medium`) applies.
|
|
1679
|
+
* - Claude Opus 5: adaptive thinking at `output_config.effort: "low"`.
|
|
1680
|
+
* Opus 5 accepts `disabled`, but with it the model can write a tool call
|
|
1681
|
+
* as plain text (the call never runs) and leak thinking tags; the lowest
|
|
1682
|
+
* effort is the documented alternative.
|
|
1683
|
+
* - Opus 5.5 and Fable 5/5.1: no `thinking` field. These models have no
|
|
1684
|
+
* thinking-off form (`disabled` and `budget_tokens` are a 400 at every
|
|
1685
|
+
* effort), so thinking still runs at the model's default effort. Pass
|
|
1686
|
+
* `effort: "low"` to spend less.
|
|
1687
|
+
* - Every other model: no `thinking` field, as before.
|
|
899
1688
|
*/
|
|
900
1689
|
buildThinking(reasoning) {
|
|
901
1690
|
if (!this.capabilities.reasoning || !reasoning) return {};
|
|
902
|
-
if (reasoning.effort === "none") return
|
|
1691
|
+
if (reasoning.effort === "none") return this.buildNoReasoning();
|
|
903
1692
|
if (reasoning.maxTokens === void 0 && reasoning.effort === void 0) return {};
|
|
904
1693
|
const effort = reasoning.effort ?? DEFAULT_THINKING_EFFORT;
|
|
905
1694
|
const budget = Math.max(MIN_THINKING_BUDGET, reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[effort]);
|
|
@@ -917,6 +1706,22 @@ var AnthropicModel = class {
|
|
|
917
1706
|
};
|
|
918
1707
|
}
|
|
919
1708
|
/**
|
|
1709
|
+
* The thinking settings for `reasoning: { effort: "none" }`, per
|
|
1710
|
+
* `noReasoningForm(this.name)` (documented on `buildThinking`).
|
|
1711
|
+
*/
|
|
1712
|
+
buildNoReasoning() {
|
|
1713
|
+
switch (noReasoningForm(this.name)) {
|
|
1714
|
+
case "between_tools": return { thinking: { type: "between_tools" } };
|
|
1715
|
+
case "disabled": return { thinking: { type: "disabled" } };
|
|
1716
|
+
case "lowest-effort": return {
|
|
1717
|
+
thinking: { type: "adaptive" },
|
|
1718
|
+
effort: EFFORT_TO_OUTPUT_EFFORT.low,
|
|
1719
|
+
reservedTokens: EFFORT_THINKING_BUDGET.low
|
|
1720
|
+
};
|
|
1721
|
+
case "omit": return {};
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
/**
|
|
920
1725
|
* Spread-friendly tools fragment. Returns an empty object when no
|
|
921
1726
|
* tools were supplied so the caller can unconditionally spread it.
|
|
922
1727
|
*
|
|
@@ -929,18 +1734,25 @@ var AnthropicModel = class {
|
|
|
929
1734
|
* breakpoint there would pay the ~1.25x write surcharge every turn
|
|
930
1735
|
* with no reads.
|
|
931
1736
|
*/
|
|
932
|
-
buildTools(
|
|
1737
|
+
buildTools(options, cacheControl) {
|
|
1738
|
+
const { tools, definitions } = (0, _warlock_js_ai.selectProviderTools)(options, {
|
|
1739
|
+
accepts: [ANTHROPIC_WIRE_FORMAT, this.provider],
|
|
1740
|
+
module: LOG_MODULE,
|
|
1741
|
+
model: this.name,
|
|
1742
|
+
logger: this.logger
|
|
1743
|
+
});
|
|
933
1744
|
const mapped = toAnthropicTools(tools);
|
|
934
|
-
|
|
1745
|
+
const providerDefined = definitions;
|
|
1746
|
+
if (!mapped) return providerDefined.length > 0 ? { tools: providerDefined } : {};
|
|
935
1747
|
if (this.config.promptCaching && mapped.length > 0) {
|
|
936
1748
|
const last = mapped.length - 1;
|
|
937
1749
|
const lastTool = mapped[last];
|
|
938
1750
|
if (lastTool) mapped[last] = {
|
|
939
1751
|
...lastTool,
|
|
940
|
-
cache_control: this.cacheControl()
|
|
1752
|
+
cache_control: this.cacheControl(cacheControl?.ttl)
|
|
941
1753
|
};
|
|
942
1754
|
}
|
|
943
|
-
return { tools: mapped };
|
|
1755
|
+
return { tools: [...providerDefined, ...mapped] };
|
|
944
1756
|
}
|
|
945
1757
|
/**
|
|
946
1758
|
* Spread-friendly `output_config` fragment carrying both the native
|
|
@@ -1014,27 +1826,34 @@ var AnthropicModel = class {
|
|
|
1014
1826
|
/**
|
|
1015
1827
|
* Normalize Anthropic's `usage` block into the neutral `Usage` shape.
|
|
1016
1828
|
* Anthropic reports `input_tokens` / `output_tokens` separately with
|
|
1017
|
-
* no pre-summed total, so `total` is computed.
|
|
1018
|
-
*
|
|
1019
|
-
* `cacheWriteTokens`,
|
|
1829
|
+
* no pre-summed total, so `total` is computed. `input_tokens` is the
|
|
1830
|
+
* uncached remainder only — cache reads (`cachedTokens`) and writes
|
|
1831
|
+
* (`cacheWriteTokens`, with the 1-hour share in `cacheWrite1hTokens`)
|
|
1832
|
+
* come on top of it — so the usage is marked
|
|
1833
|
+
* `inputIncludesCache: false`. Anthropic also reports
|
|
1020
1834
|
* `output_tokens_details.thinking_tokens` as the reasoning subset of
|
|
1021
1835
|
* its inclusive `output_tokens`; it is surfaced as `reasoningTokens`
|
|
1022
|
-
* when present, without changing the billing total.
|
|
1836
|
+
* when present, without changing the billing total. Server-side
|
|
1837
|
+
* compaction iterations are added in (see {@link compactionIterationUsage}).
|
|
1838
|
+
* `cost` is attached when the model is priced.
|
|
1023
1839
|
*/
|
|
1024
|
-
extractUsage(raw) {
|
|
1025
|
-
const
|
|
1026
|
-
const
|
|
1027
|
-
const
|
|
1028
|
-
const cacheWrite = raw.cache_creation_input_tokens;
|
|
1840
|
+
extractUsage(raw, cacheTtl) {
|
|
1841
|
+
const compaction = compactionIterationUsage(raw);
|
|
1842
|
+
const input = (raw.input_tokens ?? 0) + compaction.input;
|
|
1843
|
+
const output = (raw.output_tokens ?? 0) + compaction.output;
|
|
1029
1844
|
const thinkingTokens = raw.output_tokens_details?.thinking_tokens;
|
|
1030
|
-
|
|
1845
|
+
const usage = {
|
|
1031
1846
|
input,
|
|
1032
1847
|
output,
|
|
1033
1848
|
total: input + output,
|
|
1034
|
-
|
|
1035
|
-
...cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {},
|
|
1849
|
+
inputIncludesCache: false,
|
|
1036
1850
|
...thinkingTokens !== null && thinkingTokens !== void 0 ? { reasoningTokens: thinkingTokens } : {}
|
|
1037
1851
|
};
|
|
1852
|
+
applyAnthropicCacheUsage(usage, raw, cacheTtl);
|
|
1853
|
+
addCacheCounts(usage, compaction);
|
|
1854
|
+
const cost = (0, _warlock_js_ai.computeCost)(usage, this.pricing);
|
|
1855
|
+
if (cost) usage.cost = cost;
|
|
1856
|
+
return usage;
|
|
1038
1857
|
}
|
|
1039
1858
|
/**
|
|
1040
1859
|
* Wrap a thrown provider error into the typed `AIError` hierarchy and
|
|
@@ -1050,9 +1869,155 @@ var AnthropicModel = class {
|
|
|
1050
1869
|
return wrapped;
|
|
1051
1870
|
}
|
|
1052
1871
|
};
|
|
1872
|
+
/**
|
|
1873
|
+
* Request fields `providerOptions.anthropic.body` may not set: they carry
|
|
1874
|
+
* the conversation, the model and the tool surface the agent registered,
|
|
1875
|
+
* so an override would silently replace the agent's input (or let the
|
|
1876
|
+
* model call tools the agent never registered). `stream` is chosen per
|
|
1877
|
+
* call by `complete()` / `stream()`.
|
|
1878
|
+
*/
|
|
1879
|
+
const RESERVED_BODY_FIELDS = new Set([
|
|
1880
|
+
"model",
|
|
1881
|
+
"messages",
|
|
1882
|
+
"system",
|
|
1883
|
+
"tools",
|
|
1884
|
+
"mcp_servers",
|
|
1885
|
+
"stream"
|
|
1886
|
+
]);
|
|
1887
|
+
/** Body fields `messages.countTokens` accepts, copied from the full request body. */
|
|
1888
|
+
const COUNT_TOKENS_FIELDS = [
|
|
1889
|
+
"model",
|
|
1890
|
+
"messages",
|
|
1891
|
+
"system",
|
|
1892
|
+
"tools",
|
|
1893
|
+
"tool_choice",
|
|
1894
|
+
"thinking",
|
|
1895
|
+
"output_config",
|
|
1896
|
+
"cache_control",
|
|
1897
|
+
"context_management",
|
|
1898
|
+
"mcp_servers"
|
|
1899
|
+
];
|
|
1900
|
+
/** Beta flag for typed context editing (Anthropic docs, "Context editing", read 2026-10-17). */
|
|
1901
|
+
const CONTEXT_MANAGEMENT_BETA = "context-management-2025-06-27";
|
|
1902
|
+
/** Beta flag for threshold compaction (Anthropic docs, "Compaction at a token threshold"). */
|
|
1903
|
+
const COMPACTION_BETA = "compact-2026-01-12";
|
|
1904
|
+
/** Beta flag for `thinking.block_binding` (Anthropic docs, "Preserved thinking"). */
|
|
1905
|
+
const THINKING_BINDING_BETA = "thinking-binding-controls-2026-08-01";
|
|
1906
|
+
/** The API rejects a compaction trigger below 50,000 input tokens. */
|
|
1907
|
+
const MIN_COMPACTION_TRIGGER = 5e4;
|
|
1908
|
+
/**
|
|
1909
|
+
* Translate the typed `contextManagement` / `serverCompaction` options into
|
|
1910
|
+
* `context_management.edits`. `clear_thinking_20251015` must come first
|
|
1911
|
+
* when strategies are combined (Anthropic docs, "Context editing").
|
|
1912
|
+
*/
|
|
1913
|
+
function buildContextEdits(anthropic) {
|
|
1914
|
+
const edits = [];
|
|
1915
|
+
const { clearThinking, clearToolUses } = anthropic.contextManagement ?? {};
|
|
1916
|
+
if (clearThinking) edits.push(clearThinkingEdit(clearThinking === true ? {} : clearThinking));
|
|
1917
|
+
if (clearToolUses) edits.push(clearToolUsesEdit(clearToolUses === true ? {} : clearToolUses));
|
|
1918
|
+
if (anthropic.serverCompaction) {
|
|
1919
|
+
const compaction = anthropic.serverCompaction === true ? {} : anthropic.serverCompaction;
|
|
1920
|
+
if (compaction.triggerTokens !== void 0) assertCount("serverCompaction.triggerTokens", compaction.triggerTokens, MIN_COMPACTION_TRIGGER);
|
|
1921
|
+
edits.push({
|
|
1922
|
+
type: "compact_20260112",
|
|
1923
|
+
...compaction.triggerTokens !== void 0 ? { trigger: {
|
|
1924
|
+
type: "input_tokens",
|
|
1925
|
+
value: compaction.triggerTokens
|
|
1926
|
+
} } : {},
|
|
1927
|
+
...compaction.instructions ? { instructions: compaction.instructions } : {}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
return edits;
|
|
1931
|
+
}
|
|
1932
|
+
function clearThinkingEdit(options) {
|
|
1933
|
+
if (options.keep === void 0 || options.keep === "all") return {
|
|
1934
|
+
type: "clear_thinking_20251015",
|
|
1935
|
+
...options.keep ? { keep: "all" } : {}
|
|
1936
|
+
};
|
|
1937
|
+
assertCount("contextManagement.clearThinking.keep", options.keep, 1);
|
|
1938
|
+
return {
|
|
1939
|
+
type: "clear_thinking_20251015",
|
|
1940
|
+
keep: {
|
|
1941
|
+
type: "thinking_turns",
|
|
1942
|
+
value: options.keep
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
function clearToolUsesEdit(options) {
|
|
1947
|
+
const edit = { type: "clear_tool_uses_20250919" };
|
|
1948
|
+
if (options.trigger) {
|
|
1949
|
+
const trigger = "inputTokens" in options.trigger ? {
|
|
1950
|
+
type: "input_tokens",
|
|
1951
|
+
value: options.trigger.inputTokens
|
|
1952
|
+
} : {
|
|
1953
|
+
type: "tool_uses",
|
|
1954
|
+
value: options.trigger.toolUses
|
|
1955
|
+
};
|
|
1956
|
+
assertCount("contextManagement.clearToolUses.trigger", trigger.value, 1);
|
|
1957
|
+
edit.trigger = trigger;
|
|
1958
|
+
}
|
|
1959
|
+
if (options.keep !== void 0) {
|
|
1960
|
+
assertCount("contextManagement.clearToolUses.keep", options.keep, 0);
|
|
1961
|
+
edit.keep = {
|
|
1962
|
+
type: "tool_uses",
|
|
1963
|
+
value: options.keep
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
if (options.clearAtLeast !== void 0) {
|
|
1967
|
+
assertCount("contextManagement.clearToolUses.clearAtLeast", options.clearAtLeast, 1);
|
|
1968
|
+
edit.clear_at_least = {
|
|
1969
|
+
type: "input_tokens",
|
|
1970
|
+
value: options.clearAtLeast
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
if (options.excludeTools !== void 0) edit.exclude_tools = [...options.excludeTools];
|
|
1974
|
+
if (options.clearToolInputs !== void 0) edit.clear_tool_inputs = Array.isArray(options.clearToolInputs) ? [...options.clearToolInputs] : options.clearToolInputs;
|
|
1975
|
+
return edit;
|
|
1976
|
+
}
|
|
1977
|
+
function assertCount(field, value, minimum) {
|
|
1978
|
+
if (!Number.isSafeInteger(value) || value < minimum) throw new _warlock_js_ai.InvalidRequestError(`providerOptions.anthropic.${field} must be an integer of at least ${minimum}; got ${value}.`, { context: { field } });
|
|
1979
|
+
}
|
|
1980
|
+
/**
|
|
1981
|
+
* Tokens of the `compaction` entries in a beta response's `usage.iterations`.
|
|
1982
|
+
* With server-side compaction the top-level `input_tokens` / `output_tokens`
|
|
1983
|
+
* cover only the non-compaction iterations; the billed total is the sum
|
|
1984
|
+
* over every iteration (Anthropic docs, "Compaction at a token threshold" →
|
|
1985
|
+
* "Understanding usage", read 2026-10-17). Zeros when there are none.
|
|
1986
|
+
*/
|
|
1987
|
+
function compactionIterationUsage(raw) {
|
|
1988
|
+
const totals = {
|
|
1989
|
+
input: 0,
|
|
1990
|
+
output: 0,
|
|
1991
|
+
cacheRead: 0,
|
|
1992
|
+
cacheWrite: 0
|
|
1993
|
+
};
|
|
1994
|
+
const iterations = raw?.iterations;
|
|
1995
|
+
if (!Array.isArray(iterations)) return totals;
|
|
1996
|
+
for (const entry of iterations) {
|
|
1997
|
+
const iteration = entry;
|
|
1998
|
+
if (iteration?.type !== "compaction") continue;
|
|
1999
|
+
totals.input += numberOrZero(iteration.input_tokens);
|
|
2000
|
+
totals.output += numberOrZero(iteration.output_tokens);
|
|
2001
|
+
totals.cacheRead += numberOrZero(iteration.cache_read_input_tokens);
|
|
2002
|
+
totals.cacheWrite += numberOrZero(iteration.cache_creation_input_tokens);
|
|
2003
|
+
}
|
|
2004
|
+
return totals;
|
|
2005
|
+
}
|
|
2006
|
+
function numberOrZero(value) {
|
|
2007
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2008
|
+
}
|
|
2009
|
+
function addCacheCounts(usage, extra) {
|
|
2010
|
+
if (extra.cacheRead > 0) usage.cachedTokens = (usage.cachedTokens ?? 0) + extra.cacheRead;
|
|
2011
|
+
if (extra.cacheWrite > 0) usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + extra.cacheWrite;
|
|
2012
|
+
}
|
|
2013
|
+
/** Read `providerOptions.anthropic`, tolerating its absence or a wrong shape. */
|
|
2014
|
+
function resolveAnthropicOptions(options) {
|
|
2015
|
+
const raw = options?.providerOptions?.anthropic;
|
|
2016
|
+
return typeof raw === "object" && raw !== null ? raw : {};
|
|
2017
|
+
}
|
|
1053
2018
|
|
|
1054
2019
|
//#endregion
|
|
1055
|
-
//#region
|
|
2020
|
+
//#region ../../packages/ai-anthropic/src/sdk.ts
|
|
1056
2021
|
/**
|
|
1057
2022
|
* Anthropic-backed implementation of `SDKAdapterContract`.
|
|
1058
2023
|
*
|
|
@@ -1118,9 +2083,9 @@ var AnthropicSDK = class {
|
|
|
1118
2083
|
* Rough token-count estimate for a given text. Uses the
|
|
1119
2084
|
* character-heuristic (`approximateTokenCount`) from the core package
|
|
1120
2085
|
* — good enough for budgeting and quota guards, not for billing.
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
* synchronous-cost. The optional model id is reserved for future
|
|
2086
|
+
* For an exact count use `AnthropicModel.countTokens()`, which calls
|
|
2087
|
+
* Anthropic's `messages.countTokens` endpoint (a network round-trip);
|
|
2088
|
+
* `count()` is intentionally offline and synchronous-cost. The optional model id is reserved for future
|
|
1124
2089
|
* per-model tokenizer dispatch; currently ignored.
|
|
1125
2090
|
*/
|
|
1126
2091
|
async count(text, _model) {
|
|
@@ -1129,5 +2094,17 @@ var AnthropicSDK = class {
|
|
|
1129
2094
|
};
|
|
1130
2095
|
|
|
1131
2096
|
//#endregion
|
|
2097
|
+
exports.ANTHROPIC_BLOCK_FORMAT = ANTHROPIC_BLOCK_FORMAT;
|
|
2098
|
+
exports.ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER = ANTHROPIC_CACHE_WRITE_1H_MULTIPLIER;
|
|
2099
|
+
exports.ANTHROPIC_CACHE_WRITE_MULTIPLIER = ANTHROPIC_CACHE_WRITE_MULTIPLIER;
|
|
2100
|
+
exports.ANTHROPIC_MODEL_PRICING = ANTHROPIC_MODEL_PRICING;
|
|
2101
|
+
exports.AnthropicModel = AnthropicModel;
|
|
1132
2102
|
exports.AnthropicSDK = AnthropicSDK;
|
|
2103
|
+
exports.anthropicPricingFor = anthropicPricingFor;
|
|
2104
|
+
exports.fromProviderBlocks = fromProviderBlocks;
|
|
2105
|
+
exports.inferPreservedThinking = inferPreservedThinking;
|
|
2106
|
+
exports.toAnthropicMessages = toAnthropicMessages;
|
|
2107
|
+
exports.toAnthropicTools = toAnthropicTools;
|
|
2108
|
+
exports.toProviderBlocks = toProviderBlocks;
|
|
2109
|
+
exports.withAnthropicCacheWriteRates = withAnthropicCacheWriteRates;
|
|
1133
2110
|
//# sourceMappingURL=index.cjs.map
|