@warlock.js/ai-anthropic 5.29.0 → 5.30.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 +25 -0
- package/cjs/index.cjs +346 -52
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +67 -3
- package/esm/config.type.d.mts.map +1 -1
- package/esm/index.d.mts +3 -2
- package/esm/index.mjs +2 -1
- package/esm/known-vision-models.mjs +1 -1
- package/esm/known-vision-models.mjs.map +1 -1
- package/esm/model.d.mts +250 -0
- package/esm/model.d.mts.map +1 -0
- package/esm/model.mjs +175 -22
- package/esm/model.mjs.map +1 -1
- 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 +1 -1
- package/esm/thinking-mode.d.mts.map +1 -1
- package/esm/thinking-mode.mjs +28 -2
- package/esm/thinking-mode.mjs.map +1 -1
- package/esm/utils/index.mjs +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.mjs +83 -0
- package/esm/utils/provider-blocks.mjs.map +1 -0
- package/esm/utils/to-anthropic-messages.mjs +43 -11
- package/esm/utils/to-anthropic-messages.mjs.map +1 -1
- 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.mjs +1 -1
- package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
- package/llms-full.txt +70 -6
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/skills/setup-anthropic/SKILL.md +70 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to `@warlock.js/ai-anthropic` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.30.0 - 2026-10-16
|
|
8
|
+
|
|
9
|
+
### Upgrading
|
|
10
|
+
|
|
11
|
+
- `model_context_window_exceeded` now maps to `finishReason: "context_exceeded"` (was `length`), `pause_turn` to `pause_turn` and `refusal` to `refusal` (both were `error`).
|
|
12
|
+
- `providerOptions.anthropic.body` may not set `model`, `messages`, `system`, `tools`, `mcp_servers` or `stream`: the call fails with `InvalidRequestError` instead of silently replacing the conversation, the model or the registered tools.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Responses with thinking, redacted-thinking, compaction, server-tool or cited-text blocks return the whole ordered turn as `ModelResponse.blocks` (and on the streaming `done` chunk); an assistant message carrying Anthropic `blocks` is replayed verbatim (WR-022).
|
|
17
|
+
- `providerOptions.anthropic` (`AnthropicCallOptions`): `betas` routes the call through `client.beta.messages`, `body` merges extra request fields last (`context_management`, `fallbacks`, `inference_geo`, …), `thinkingDisplay` sets `thinking.display` (WR-025).
|
|
18
|
+
- `AnthropicModel.countTokens(messages, options?)` counts input tokens through `messages.countTokens`; `AnthropicModel` is exported and `AnthropicSDK.model()` returns it (WR-025).
|
|
19
|
+
- Per-call `cacheControl.ttl` overrides the model's `promptCacheTtl`.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Consecutive tool results are sent as one `user` message with several `tool_result` blocks, as Anthropic expects for parallel tool use (WR-024).
|
|
24
|
+
- Claude 5-family thinking rules: Opus 5.5, Sonnet 5.5 and Fable 5 / 5.1 never receive `budget_tokens` or `disabled` (WR-026).
|
|
25
|
+
- `effort: "none"` on Claude Sonnet 5.5 sends `thinking: { type: "between_tools" }` (its only thinking-off form, sent alone with no `output_config.effort` or `display`); it used to omit `thinking`, so the model still thought at its default effort. Opus 5.5 and Fable 5 / 5.1 have no thinking-off form and keep omitting `thinking` (WR-026).
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- `providerOptions.anthropic.thinkingDisplay` is applied only to adaptive thinking, so it is never sent beside `between_tools` (a 400).
|
|
30
|
+
- **Security:** a stored block is replayed only when its `data.type` equals its declared `type` and the type is one Claude returns on an assistant turn (text, thinking, redacted thinking, tool use, server-tool use and results, MCP, container upload, compaction). A transcript from outside the process can no longer smuggle a `tool_use` or user-side block past the tool-id check; such turns are rebuilt from `content` + `toolCalls`.
|
|
31
|
+
|
|
7
32
|
## 5.29.0 - 2026-10-01
|
|
8
33
|
|
|
9
34
|
### Changed
|
package/cjs/index.cjs
CHANGED
|
@@ -31,7 +31,7 @@ _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-vision-models.ts
|
|
35
35
|
/**
|
|
36
36
|
* Model-name prefixes for Claude families that accept image input
|
|
37
37
|
* (vision) on the Messages API.
|
|
@@ -86,7 +86,7 @@ function inferVisionCapability(modelName) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
//#endregion
|
|
89
|
-
//#region
|
|
89
|
+
//#region ../../packages/ai-anthropic/src/thinking-mode.ts
|
|
90
90
|
/**
|
|
91
91
|
* Family prefixes that are budget-mode in their entirety — plain
|
|
92
92
|
* `startsWith` match (dated and `-latest` variants included).
|
|
@@ -163,39 +163,150 @@ function inferThinkingMode(modelName) {
|
|
|
163
163
|
function acceptsTemperature(mode) {
|
|
164
164
|
return mode === "budget";
|
|
165
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Model release roots that accept `thinking: { type: "between_tools" }`,
|
|
168
|
+
* the only way to turn thinking off on them. Source: claude-api ref,
|
|
169
|
+
* "Thinking & Effort" per-model table (cached 2026-10-06): Claude Sonnet
|
|
170
|
+
* 5.5 returns a 400 on `{ type: "disabled" }` and takes `between_tools`
|
|
171
|
+
* instead (no other field alongside it, effort `high` or below). No other
|
|
172
|
+
* current model is documented to accept it. Opus 5.5 and Fable 5/5.1 have
|
|
173
|
+
* no thinking-off form at all: `disabled` and `budget_tokens` are a 400 at
|
|
174
|
+
* every effort, so the lowest they go is `effort: "low"`.
|
|
175
|
+
*/
|
|
176
|
+
const BETWEEN_TOOLS_RELEASE_ROOTS = ["claude-sonnet-5-5"];
|
|
177
|
+
/**
|
|
178
|
+
* Whether `effort: "none"` can turn thinking off on this model with
|
|
179
|
+
* `thinking: { type: "between_tools" }`. Matches the release id with or
|
|
180
|
+
* without a platform prefix (`anthropic.claude-sonnet-5-5` on Bedrock)
|
|
181
|
+
* and with dated or tagged suffixes, but not a later minor release.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* acceptsBetweenTools("claude-sonnet-5-5"); // → true
|
|
185
|
+
* acceptsBetweenTools("claude-opus-5-5"); // → false
|
|
186
|
+
*/
|
|
187
|
+
function acceptsBetweenTools(modelName) {
|
|
188
|
+
const normalized = modelName.toLowerCase();
|
|
189
|
+
const id = normalized.slice(normalized.lastIndexOf(".") + 1);
|
|
190
|
+
return BETWEEN_TOOLS_RELEASE_ROOTS.some((root) => matchesReleaseRoot(id, root));
|
|
191
|
+
}
|
|
166
192
|
|
|
167
193
|
//#endregion
|
|
168
|
-
//#region
|
|
194
|
+
//#region ../../packages/ai-anthropic/src/utils/map-stop-reason.ts
|
|
169
195
|
const stopReasonMap = {
|
|
170
196
|
end_turn: "stop",
|
|
171
197
|
stop_sequence: "stop",
|
|
198
|
+
compaction: "stop",
|
|
172
199
|
max_tokens: "length",
|
|
173
|
-
model_context_window_exceeded: "
|
|
200
|
+
model_context_window_exceeded: "context_exceeded",
|
|
201
|
+
pause_turn: "pause_turn",
|
|
202
|
+
refusal: "refusal",
|
|
174
203
|
tool_use: "tool_calls"
|
|
175
204
|
};
|
|
176
205
|
/**
|
|
177
206
|
* Map Anthropic's `stop_reason` to the normalized `FinishReason` union.
|
|
178
207
|
*
|
|
179
|
-
* `end_turn` / `stop_sequence` are
|
|
180
|
-
* `
|
|
181
|
-
* `
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
208
|
+
* `end_turn` / `stop_sequence` (and beta `compaction`) are natural stops,
|
|
209
|
+
* `max_tokens` maps to `length`, `tool_use` to `tool_calls`.
|
|
210
|
+
* `model_context_window_exceeded`, `pause_turn` and `refusal` keep their
|
|
211
|
+
* own reasons (`context_exceeded`, `pause_turn`, `refusal`) because each
|
|
212
|
+
* needs a different reaction: compact the history, send the turn back to
|
|
213
|
+
* resume, or fall back to another model. `null` and any value a future
|
|
214
|
+
* API version adds fall through to `"error"`.
|
|
186
215
|
*
|
|
187
216
|
* @example
|
|
188
|
-
* mapStopReason("end_turn");
|
|
189
|
-
* mapStopReason("tool_use");
|
|
190
|
-
* mapStopReason("refusal");
|
|
191
|
-
* mapStopReason(
|
|
217
|
+
* mapStopReason("end_turn"); // "stop"
|
|
218
|
+
* mapStopReason("tool_use"); // "tool_calls"
|
|
219
|
+
* mapStopReason("refusal"); // "refusal"
|
|
220
|
+
* mapStopReason("pause_turn"); // "pause_turn"
|
|
221
|
+
* mapStopReason(null); // "error"
|
|
192
222
|
*/
|
|
193
223
|
function mapStopReason(raw) {
|
|
194
224
|
return stopReasonMap[raw ?? ""] ?? "error";
|
|
195
225
|
}
|
|
196
226
|
|
|
197
227
|
//#endregion
|
|
198
|
-
//#region
|
|
228
|
+
//#region ../../packages/ai-anthropic/src/utils/provider-blocks.ts
|
|
229
|
+
/** The `ProviderContentBlock.provider` value this adapter writes and replays. */
|
|
230
|
+
const ANTHROPIC_BLOCK_FORMAT = "anthropic";
|
|
231
|
+
/** Block types the neutral `content` + `toolCalls` fields already carry losslessly. */
|
|
232
|
+
const NEUTRAL_BLOCK_TYPES = new Set(["text", "tool_use"]);
|
|
233
|
+
/** Block types that carry a model-requested call whose id must match `toolCalls`. */
|
|
234
|
+
const TOOL_USE_BLOCK_TYPES = new Set(["tool_use"]);
|
|
235
|
+
/**
|
|
236
|
+
* Assistant-turn block types that may be sent back verbatim. Server-tool
|
|
237
|
+
* results all end in `_tool_result` (`web_search_tool_result`,
|
|
238
|
+
* `code_execution_tool_result`, …) and are matched by suffix; anything
|
|
239
|
+
* else (an unknown or user-side type such as `image`, `document` or a
|
|
240
|
+
* client `tool_result`) is not replayed.
|
|
241
|
+
*/
|
|
242
|
+
const REPLAYABLE_BLOCK_TYPES = new Set([
|
|
243
|
+
"text",
|
|
244
|
+
"thinking",
|
|
245
|
+
"redacted_thinking",
|
|
246
|
+
"tool_use",
|
|
247
|
+
"server_tool_use",
|
|
248
|
+
"mcp_tool_use",
|
|
249
|
+
"mcp_tool_result",
|
|
250
|
+
"container_upload",
|
|
251
|
+
"compaction"
|
|
252
|
+
]);
|
|
253
|
+
const SERVER_TOOL_RESULT_TYPE = /^[a-z][a-z0-9_]*_tool_result$/;
|
|
254
|
+
/**
|
|
255
|
+
* Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or
|
|
256
|
+
* return `undefined` when the neutral fields already carry the whole turn
|
|
257
|
+
* (only plain text and `tool_use` blocks). Any other block — thinking,
|
|
258
|
+
* redacted thinking, compaction, server-tool use/results, cited text —
|
|
259
|
+
* makes the full ordered list worth keeping so the next request can
|
|
260
|
+
* replay the turn verbatim.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* toProviderBlocks([{ type: "text", text: "hi", citations: null }]); // undefined
|
|
264
|
+
* toProviderBlocks([{ type: "thinking", thinking: "", signature: "s" }, { type: "text", text: "hi" }]);
|
|
265
|
+
* // [{ provider: "anthropic", type: "thinking", data: {...} }, { provider: "anthropic", type: "text", data: {...} }]
|
|
266
|
+
*/
|
|
267
|
+
function toProviderBlocks(content) {
|
|
268
|
+
if (!content.some(needsVerbatimReplay)) return;
|
|
269
|
+
return content.map((block) => ({
|
|
270
|
+
provider: ANTHROPIC_BLOCK_FORMAT,
|
|
271
|
+
type: block.type,
|
|
272
|
+
data: { ...block }
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Rebuild an assistant turn's Anthropic content from stored blocks.
|
|
277
|
+
* Returns `undefined` — so the caller falls back to `content` +
|
|
278
|
+
* `toolCalls` — when there are no blocks, when any block belongs to
|
|
279
|
+
* another provider's format, when a block is not a well-formed
|
|
280
|
+
* assistant-turn block (its `data.type` must equal its `type`, and the
|
|
281
|
+
* type must be one Anthropic returns on an assistant turn), or when the
|
|
282
|
+
* blocks' `tool_use` ids differ from the message's `toolCalls` (the turn
|
|
283
|
+
* was edited after the response, e.g. by a middleware or a recovered
|
|
284
|
+
* stream call, so a verbatim replay would no longer match the tool
|
|
285
|
+
* results that follow). The checks matter when a transcript comes from
|
|
286
|
+
* outside the process (a client, a store): the block data is sent as is.
|
|
287
|
+
*/
|
|
288
|
+
function fromProviderBlocks(blocks, toolCalls) {
|
|
289
|
+
if (!blocks || blocks.length === 0) return;
|
|
290
|
+
if (!blocks.every((block) => block.provider === "anthropic" && isReplayable(block))) return;
|
|
291
|
+
const blockToolIds = blocks.filter((block) => TOOL_USE_BLOCK_TYPES.has(block.type)).map((block) => block.data.id);
|
|
292
|
+
const requestedIds = (toolCalls ?? []).map((toolCall) => toolCall.id);
|
|
293
|
+
if (blockToolIds.length !== requestedIds.length || blockToolIds.some((id, index) => id !== requestedIds[index])) return;
|
|
294
|
+
return blocks.map((block) => ({ ...block.data }));
|
|
295
|
+
}
|
|
296
|
+
function needsVerbatimReplay(block) {
|
|
297
|
+
if (!NEUTRAL_BLOCK_TYPES.has(block.type)) return true;
|
|
298
|
+
const citations = block.citations;
|
|
299
|
+
return Array.isArray(citations) && citations.length > 0;
|
|
300
|
+
}
|
|
301
|
+
function isReplayable(block) {
|
|
302
|
+
const data = block.data;
|
|
303
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return false;
|
|
304
|
+
if (data.type !== block.type) return false;
|
|
305
|
+
return REPLAYABLE_BLOCK_TYPES.has(block.type) || SERVER_TOOL_RESULT_TYPE.test(block.type);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts
|
|
199
310
|
/**
|
|
200
311
|
* Convert vendor-neutral `Message[]` into Anthropic's request shape.
|
|
201
312
|
*
|
|
@@ -206,15 +317,24 @@ function mapStopReason(raw) {
|
|
|
206
317
|
* separated) and returned separately as the top-level `system`
|
|
207
318
|
* parameter.
|
|
208
319
|
* 2. **Tool results are `user` turns.** A neutral `tool` message becomes
|
|
209
|
-
* a `
|
|
210
|
-
*
|
|
320
|
+
* a `tool_result` block keyed by `tool_use_id`. Consecutive tool
|
|
321
|
+
* messages (the results of one parallel tool-use turn) are grouped
|
|
322
|
+
* into a single `user` message, as Anthropic expects — splitting them
|
|
323
|
+
* across messages teaches the model to stop calling tools in parallel.
|
|
211
324
|
* 3. **Tool calls are `tool_use` content blocks.** An assistant message
|
|
212
325
|
* carrying `toolCalls` becomes an `assistant` message whose content
|
|
213
326
|
* is an optional leading `text` block followed by one `tool_use`
|
|
214
327
|
* block per call.
|
|
215
328
|
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
329
|
+
* An assistant message that carries Anthropic `blocks` (thinking,
|
|
330
|
+
* redacted thinking, compaction, server-tool blocks — see
|
|
331
|
+
* `ProviderContentBlock`) is replayed verbatim from those blocks, which
|
|
332
|
+
* keeps signed thinking and compaction state valid. Blocks from another
|
|
333
|
+
* provider, or blocks whose `tool_use` ids no longer match `toolCalls`,
|
|
334
|
+
* are ignored and the turn is rebuilt from `content` + `toolCalls`.
|
|
335
|
+
*
|
|
336
|
+
* Other consecutive same-role turns are left as-is — the Messages API
|
|
337
|
+
* merges them server-side.
|
|
218
338
|
*
|
|
219
339
|
* @example
|
|
220
340
|
* const { system, messages } = toAnthropicMessages([
|
|
@@ -233,16 +353,29 @@ function toAnthropicMessages(messages) {
|
|
|
233
353
|
continue;
|
|
234
354
|
}
|
|
235
355
|
if (message.role === "tool") {
|
|
236
|
-
|
|
356
|
+
const toolResult = {
|
|
357
|
+
type: "tool_result",
|
|
358
|
+
tool_use_id: message.toolCallId ?? "",
|
|
359
|
+
content: stringifyContent(message.content)
|
|
360
|
+
};
|
|
361
|
+
const previous = mapped[mapped.length - 1];
|
|
362
|
+
if (previous && previous.role === "user" && isToolResultTurn(previous)) previous.content.push(toolResult);
|
|
363
|
+
else mapped.push({
|
|
237
364
|
role: "user",
|
|
238
|
-
content: [
|
|
239
|
-
type: "tool_result",
|
|
240
|
-
tool_use_id: message.toolCallId ?? "",
|
|
241
|
-
content: stringifyContent(message.content)
|
|
242
|
-
}]
|
|
365
|
+
content: [toolResult]
|
|
243
366
|
});
|
|
244
367
|
continue;
|
|
245
368
|
}
|
|
369
|
+
if (message.role === "assistant") {
|
|
370
|
+
const replayed = fromProviderBlocks(message.blocks, message.toolCalls);
|
|
371
|
+
if (replayed) {
|
|
372
|
+
mapped.push({
|
|
373
|
+
role: "assistant",
|
|
374
|
+
content: replayed
|
|
375
|
+
});
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
246
379
|
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
247
380
|
const blocks = extractThinkingBlocks(message.toolCalls);
|
|
248
381
|
const text = stringifyContent(message.content);
|
|
@@ -305,6 +438,14 @@ function toAnthropicThinkingBlock(block) {
|
|
|
305
438
|
}];
|
|
306
439
|
return [];
|
|
307
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* A `user` turn this function built from tool results — its content is a
|
|
443
|
+
* non-empty array made only of `tool_result` blocks. Later tool results
|
|
444
|
+
* of the same tool-use turn are appended to it.
|
|
445
|
+
*/
|
|
446
|
+
function isToolResultTurn(message) {
|
|
447
|
+
return Array.isArray(message.content) && message.content.length > 0 && message.content.every((block) => block.type === "tool_result");
|
|
448
|
+
}
|
|
308
449
|
function isRecord(value) {
|
|
309
450
|
return typeof value === "object" && value !== null;
|
|
310
451
|
}
|
|
@@ -359,7 +500,7 @@ function toAnthropicContentBlock(part) {
|
|
|
359
500
|
}
|
|
360
501
|
|
|
361
502
|
//#endregion
|
|
362
|
-
//#region
|
|
503
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-tools.ts
|
|
363
504
|
/**
|
|
364
505
|
* Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.
|
|
365
506
|
* Uses the shared `extractJsonSchema` helper; Anthropic requires the
|
|
@@ -392,7 +533,7 @@ function toInputSchema(input) {
|
|
|
392
533
|
}
|
|
393
534
|
|
|
394
535
|
//#endregion
|
|
395
|
-
//#region
|
|
536
|
+
//#region ../../packages/ai-anthropic/src/utils/wrap-anthropic-error.ts
|
|
396
537
|
/**
|
|
397
538
|
* Wrap any thrown value caught inside the Anthropic adapter into the
|
|
398
539
|
* appropriate `@warlock.js/ai` `AIError` subclass.
|
|
@@ -545,7 +686,7 @@ function parseRetryAfter(headers) {
|
|
|
545
686
|
}
|
|
546
687
|
|
|
547
688
|
//#endregion
|
|
548
|
-
//#region
|
|
689
|
+
//#region ../../packages/ai-anthropic/src/model.ts
|
|
549
690
|
const LOG_MODULE = "ai.anthropic";
|
|
550
691
|
/**
|
|
551
692
|
* Anthropic requires `max_tokens` on every request (unlike OpenAI,
|
|
@@ -602,6 +743,12 @@ const EFFORT_TO_OUTPUT_EFFORT = {
|
|
|
602
743
|
xhigh: "xhigh",
|
|
603
744
|
max: "max"
|
|
604
745
|
};
|
|
746
|
+
/** Content-block types whose input arrives as `input_json_delta` while streaming. */
|
|
747
|
+
const STREAMED_INPUT_BLOCK_TYPES = new Set([
|
|
748
|
+
"tool_use",
|
|
749
|
+
"server_tool_use",
|
|
750
|
+
"mcp_tool_use"
|
|
751
|
+
]);
|
|
605
752
|
/**
|
|
606
753
|
* Anthropic-backed implementation of `ModelContract`.
|
|
607
754
|
*
|
|
@@ -672,16 +819,17 @@ var AnthropicModel = class {
|
|
|
672
819
|
});
|
|
673
820
|
let response;
|
|
674
821
|
try {
|
|
675
|
-
response = await this.
|
|
676
|
-
...this.
|
|
822
|
+
response = await this.send({
|
|
823
|
+
...this.buildBody(messages, options),
|
|
677
824
|
stream: false
|
|
678
|
-
}, options
|
|
825
|
+
}, options, "create");
|
|
679
826
|
} catch (thrown) {
|
|
680
827
|
throw this.logAndWrap(thrown);
|
|
681
828
|
}
|
|
682
829
|
const finishReason = mapStopReason(response.stop_reason);
|
|
683
830
|
const usage = this.extractUsage(response.usage);
|
|
684
831
|
const toolCalls = this.extractToolCalls(response.content);
|
|
832
|
+
const blocks = toProviderBlocks(response.content);
|
|
685
833
|
this.logger.debug(LOG_MODULE, "response", "call to messages.create succeeded", {
|
|
686
834
|
finishReason,
|
|
687
835
|
usage
|
|
@@ -690,10 +838,32 @@ var AnthropicModel = class {
|
|
|
690
838
|
content: this.extractText(response.content),
|
|
691
839
|
finishReason,
|
|
692
840
|
usage,
|
|
693
|
-
toolCalls
|
|
841
|
+
toolCalls,
|
|
842
|
+
...blocks ? { blocks } : {}
|
|
694
843
|
};
|
|
695
844
|
}
|
|
696
845
|
/**
|
|
846
|
+
* Count the input tokens a request would use, through Anthropic's
|
|
847
|
+
* `messages.countTokens` endpoint (a network call; no tokens are
|
|
848
|
+
* generated or billed as output). Takes the same messages and options
|
|
849
|
+
* as `complete()` — system prompt, tools, thinking and
|
|
850
|
+
* `providerOptions.anthropic.betas` are counted exactly as they would be
|
|
851
|
+
* sent. For an offline estimate use `AnthropicSDK.count()`.
|
|
852
|
+
*
|
|
853
|
+
* @example
|
|
854
|
+
* const tokens = await model.countTokens(messages, { tools });
|
|
855
|
+
*/
|
|
856
|
+
async countTokens(messages, options) {
|
|
857
|
+
const body = this.buildBody(messages, options);
|
|
858
|
+
const countBody = {};
|
|
859
|
+
for (const key of COUNT_TOKENS_FIELDS) if (body[key] !== void 0) countBody[key] = body[key];
|
|
860
|
+
try {
|
|
861
|
+
return (await this.send(countBody, options, "countTokens")).input_tokens;
|
|
862
|
+
} catch (thrown) {
|
|
863
|
+
throw this.logAndWrap(thrown);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
697
867
|
* Incremental streaming completion. Yields neutral `ModelStreamChunk`s
|
|
698
868
|
* — `delta` for text tokens, `tool-call` once a `tool_use` block's
|
|
699
869
|
* arguments have fully accumulated, and a terminal `done` carrying the
|
|
@@ -709,10 +879,10 @@ var AnthropicModel = class {
|
|
|
709
879
|
});
|
|
710
880
|
let stream;
|
|
711
881
|
try {
|
|
712
|
-
stream = await this.
|
|
713
|
-
...this.
|
|
882
|
+
stream = await this.send({
|
|
883
|
+
...this.buildBody(messages, options),
|
|
714
884
|
stream: true
|
|
715
|
-
}, options
|
|
885
|
+
}, options, "create");
|
|
716
886
|
} catch (thrown) {
|
|
717
887
|
throw this.logAndWrap(thrown);
|
|
718
888
|
}
|
|
@@ -724,8 +894,10 @@ var AnthropicModel = class {
|
|
|
724
894
|
};
|
|
725
895
|
const toolBlocks = /* @__PURE__ */ new Map();
|
|
726
896
|
const thinkingBlocks = /* @__PURE__ */ new Map();
|
|
897
|
+
const streamedBlocks = /* @__PURE__ */ new Map();
|
|
727
898
|
try {
|
|
728
899
|
for await (const event of stream) {
|
|
900
|
+
this.trackStreamedBlock(streamedBlocks, event);
|
|
729
901
|
if (event.type === "message_start") {
|
|
730
902
|
usage.input = event.message.usage.input_tokens ?? 0;
|
|
731
903
|
const thinkingTokens = event.message.usage.output_tokens_details?.thinking_tokens;
|
|
@@ -794,6 +966,7 @@ var AnthropicModel = class {
|
|
|
794
966
|
}
|
|
795
967
|
usage.total = usage.input + usage.output;
|
|
796
968
|
const finishReason = mapStopReason(rawStopReason);
|
|
969
|
+
const blocks = toProviderBlocks(this.finishStreamedBlocks(streamedBlocks));
|
|
797
970
|
this.logger.debug(LOG_MODULE, "response", "Streaming call to messages.create succeeded", {
|
|
798
971
|
finishReason,
|
|
799
972
|
usage
|
|
@@ -801,7 +974,79 @@ var AnthropicModel = class {
|
|
|
801
974
|
yield {
|
|
802
975
|
type: "done",
|
|
803
976
|
finishReason,
|
|
804
|
-
usage
|
|
977
|
+
usage,
|
|
978
|
+
...blocks ? { blocks } : {}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Fold one stream event into the per-index block accumulator. Unknown
|
|
983
|
+
* block and delta types (beta `compaction`, future additions) are kept
|
|
984
|
+
* as their start payload plus whatever their delta carries, so they
|
|
985
|
+
* round-trip without the adapter knowing their shape.
|
|
986
|
+
*/
|
|
987
|
+
trackStreamedBlock(blocks, event) {
|
|
988
|
+
if (event.type === "content_block_start") {
|
|
989
|
+
const block = { ...event.content_block };
|
|
990
|
+
blocks.set(event.index, {
|
|
991
|
+
block,
|
|
992
|
+
...STREAMED_INPUT_BLOCK_TYPES.has(String(block.type)) ? { json: "" } : {}
|
|
993
|
+
});
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
if (event.type !== "content_block_delta") return;
|
|
997
|
+
const entry = blocks.get(event.index);
|
|
998
|
+
if (!entry) return;
|
|
999
|
+
const delta = event.delta;
|
|
1000
|
+
const block = entry.block;
|
|
1001
|
+
if (delta.type === "text_delta") block.text = String(block.text ?? "") + String(delta.text ?? "");
|
|
1002
|
+
else if (delta.type === "thinking_delta") block.thinking = String(block.thinking ?? "") + String(delta.thinking ?? "");
|
|
1003
|
+
else if (delta.type === "signature_delta") block.signature = String(block.signature ?? "") + String(delta.signature ?? "");
|
|
1004
|
+
else if (delta.type === "input_json_delta") entry.json = (entry.json ?? "") + String(delta.partial_json ?? "");
|
|
1005
|
+
else if (delta.type === "citations_delta") block.citations = [...Array.isArray(block.citations) ? block.citations : [], delta.citation];
|
|
1006
|
+
else for (const [key, value] of Object.entries(delta)) if (key !== "type") block[key] = value;
|
|
1007
|
+
}
|
|
1008
|
+
/** Close the accumulated stream blocks into an ordered content array. */
|
|
1009
|
+
finishStreamedBlocks(blocks) {
|
|
1010
|
+
return Array.from(blocks.entries()).sort(([left], [right]) => left - right).map(([, { block, json }]) => {
|
|
1011
|
+
if (json !== void 0) block.input = (0, _warlock_js_ai.safeJsonParse)(json, {});
|
|
1012
|
+
return block;
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Send a request through the SDK. With `providerOptions.anthropic.betas`
|
|
1017
|
+
* the call goes to `client.beta.messages` (which takes the `betas` list
|
|
1018
|
+
* and the beta-only body fields); otherwise to `client.messages`. The
|
|
1019
|
+
* cancellation signal rides in the request options, as before.
|
|
1020
|
+
*/
|
|
1021
|
+
send(body, options, method) {
|
|
1022
|
+
const betas = resolveAnthropicOptions(options).betas;
|
|
1023
|
+
const requestOptions = options?.signal ? { signal: options.signal } : void 0;
|
|
1024
|
+
const api = betas && betas.length > 0 ? this.client.beta.messages : this.client.messages;
|
|
1025
|
+
return api[method].call(api, betas && betas.length > 0 ? {
|
|
1026
|
+
...body,
|
|
1027
|
+
betas
|
|
1028
|
+
} : body, requestOptions);
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* The full request body: the adapter-built params, then
|
|
1032
|
+
* `thinking.display` when thinking is sent on an adaptive model, then
|
|
1033
|
+
* `providerOptions.anthropic.body` merged last (caller fields win). The
|
|
1034
|
+
* body may add fields the adapter does not build (`context_management`,
|
|
1035
|
+
* `fallbacks`, …) but may not replace the conversation or the model —
|
|
1036
|
+
* see {@link RESERVED_BODY_FIELDS}.
|
|
1037
|
+
*/
|
|
1038
|
+
buildBody(messages, options) {
|
|
1039
|
+
const anthropic = resolveAnthropicOptions(options);
|
|
1040
|
+
const body = { ...this.buildParams(messages, options) };
|
|
1041
|
+
if (anthropic.thinkingDisplay && this.thinkingMode === "adaptive" && body.thinking?.type === "adaptive") body.thinking = {
|
|
1042
|
+
...body.thinking,
|
|
1043
|
+
display: anthropic.thinkingDisplay
|
|
1044
|
+
};
|
|
1045
|
+
const reserved = Object.keys(anthropic.body ?? {}).filter((key) => RESERVED_BODY_FIELDS.has(key));
|
|
1046
|
+
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 } });
|
|
1047
|
+
return {
|
|
1048
|
+
...body,
|
|
1049
|
+
...anthropic.body
|
|
805
1050
|
};
|
|
806
1051
|
}
|
|
807
1052
|
/**
|
|
@@ -832,7 +1077,7 @@ var AnthropicModel = class {
|
|
|
832
1077
|
...this.buildAutomaticCacheControl(options?.cacheControl),
|
|
833
1078
|
...this.buildSystem(system, options?.cacheControl),
|
|
834
1079
|
...sendTemperature ? { temperature } : {},
|
|
835
|
-
...this.buildTools(options?.tools),
|
|
1080
|
+
...this.buildTools(options?.tools, options?.cacheControl),
|
|
836
1081
|
...this.buildOutputConfig(options?.responseSchema, thinking.effort),
|
|
837
1082
|
...thinking.thinking ? { thinking: thinking.thinking } : {}
|
|
838
1083
|
};
|
|
@@ -855,7 +1100,7 @@ var AnthropicModel = class {
|
|
|
855
1100
|
if (this.capabilities.promptCaching && breakpoints > 0) return { system: [{
|
|
856
1101
|
type: "text",
|
|
857
1102
|
text: system,
|
|
858
|
-
cache_control: this.cacheControl()
|
|
1103
|
+
cache_control: this.cacheControl(cacheControl?.ttl)
|
|
859
1104
|
}] };
|
|
860
1105
|
return { system };
|
|
861
1106
|
}
|
|
@@ -866,13 +1111,18 @@ var AnthropicModel = class {
|
|
|
866
1111
|
*/
|
|
867
1112
|
buildAutomaticCacheControl(cacheControl) {
|
|
868
1113
|
const breakpoints = cacheControl?.breakpoints ?? 0;
|
|
869
|
-
return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl() } : {};
|
|
1114
|
+
return this.config.promptCaching && breakpoints <= 0 ? { cache_control: this.cacheControl(cacheControl?.ttl) } : {};
|
|
870
1115
|
}
|
|
871
|
-
/**
|
|
872
|
-
|
|
1116
|
+
/**
|
|
1117
|
+
* Build a typed cache-control marker. The per-call
|
|
1118
|
+
* `cacheControl.ttl` wins over the model's `promptCacheTtl`; with
|
|
1119
|
+
* neither, TTL is omitted for Anthropic's 5-minute default.
|
|
1120
|
+
*/
|
|
1121
|
+
cacheControl(callTtl) {
|
|
1122
|
+
const ttl = callTtl ?? this.config.promptCacheTtl;
|
|
873
1123
|
return {
|
|
874
1124
|
type: "ephemeral",
|
|
875
|
-
...
|
|
1125
|
+
...ttl ? { ttl } : {}
|
|
876
1126
|
};
|
|
877
1127
|
}
|
|
878
1128
|
/**
|
|
@@ -894,12 +1144,22 @@ var AnthropicModel = class {
|
|
|
894
1144
|
* budget. `budget_tokens` must be ≥ 1024 (`ThinkingConfigEnabled`,
|
|
895
1145
|
* `messages.d.ts:1084`), so the budget is floored at that minimum.
|
|
896
1146
|
*
|
|
897
|
-
* The neutral `effort: "none"` ("run without reasoning")
|
|
898
|
-
*
|
|
1147
|
+
* The neutral `effort: "none"` ("run without reasoning") is mapped per
|
|
1148
|
+
* model:
|
|
1149
|
+
* - Claude Sonnet 5.5 (`acceptsBetweenTools`): `thinking: { type:
|
|
1150
|
+
* "between_tools" }`, its only thinking-off form. It is sent alone —
|
|
1151
|
+
* no `output_config.effort` (the model default, `high`, is within the
|
|
1152
|
+
* `high`-or-below range it requires) and no `display`, both of which
|
|
1153
|
+
* the API rejects alongside it.
|
|
1154
|
+
* - Opus 5.5 and Fable 5/5.1: no `thinking` field. These models have no
|
|
1155
|
+
* thinking-off form (`disabled` and `budget_tokens` are a 400 at every
|
|
1156
|
+
* effort), so thinking still runs at the model's default effort. Pass
|
|
1157
|
+
* `effort: "low"` to spend less.
|
|
1158
|
+
* - Every other model: no `thinking` field, as before.
|
|
899
1159
|
*/
|
|
900
1160
|
buildThinking(reasoning) {
|
|
901
1161
|
if (!this.capabilities.reasoning || !reasoning) return {};
|
|
902
|
-
if (reasoning.effort === "none") return {};
|
|
1162
|
+
if (reasoning.effort === "none") return acceptsBetweenTools(this.name) ? { thinking: { type: "between_tools" } } : {};
|
|
903
1163
|
if (reasoning.maxTokens === void 0 && reasoning.effort === void 0) return {};
|
|
904
1164
|
const effort = reasoning.effort ?? DEFAULT_THINKING_EFFORT;
|
|
905
1165
|
const budget = Math.max(MIN_THINKING_BUDGET, reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[effort]);
|
|
@@ -929,7 +1189,7 @@ var AnthropicModel = class {
|
|
|
929
1189
|
* breakpoint there would pay the ~1.25x write surcharge every turn
|
|
930
1190
|
* with no reads.
|
|
931
1191
|
*/
|
|
932
|
-
buildTools(tools) {
|
|
1192
|
+
buildTools(tools, cacheControl) {
|
|
933
1193
|
const mapped = toAnthropicTools(tools);
|
|
934
1194
|
if (!mapped) return {};
|
|
935
1195
|
if (this.config.promptCaching && mapped.length > 0) {
|
|
@@ -937,7 +1197,7 @@ var AnthropicModel = class {
|
|
|
937
1197
|
const lastTool = mapped[last];
|
|
938
1198
|
if (lastTool) mapped[last] = {
|
|
939
1199
|
...lastTool,
|
|
940
|
-
cache_control: this.cacheControl()
|
|
1200
|
+
cache_control: this.cacheControl(cacheControl?.ttl)
|
|
941
1201
|
};
|
|
942
1202
|
}
|
|
943
1203
|
return { tools: mapped };
|
|
@@ -1050,9 +1310,42 @@ var AnthropicModel = class {
|
|
|
1050
1310
|
return wrapped;
|
|
1051
1311
|
}
|
|
1052
1312
|
};
|
|
1313
|
+
/**
|
|
1314
|
+
* Request fields `providerOptions.anthropic.body` may not set: they carry
|
|
1315
|
+
* the conversation, the model and the tool surface the agent registered,
|
|
1316
|
+
* so an override would silently replace the agent's input (or let the
|
|
1317
|
+
* model call tools the agent never registered). `stream` is chosen per
|
|
1318
|
+
* call by `complete()` / `stream()`.
|
|
1319
|
+
*/
|
|
1320
|
+
const RESERVED_BODY_FIELDS = new Set([
|
|
1321
|
+
"model",
|
|
1322
|
+
"messages",
|
|
1323
|
+
"system",
|
|
1324
|
+
"tools",
|
|
1325
|
+
"mcp_servers",
|
|
1326
|
+
"stream"
|
|
1327
|
+
]);
|
|
1328
|
+
/** Body fields `messages.countTokens` accepts, copied from the full request body. */
|
|
1329
|
+
const COUNT_TOKENS_FIELDS = [
|
|
1330
|
+
"model",
|
|
1331
|
+
"messages",
|
|
1332
|
+
"system",
|
|
1333
|
+
"tools",
|
|
1334
|
+
"tool_choice",
|
|
1335
|
+
"thinking",
|
|
1336
|
+
"output_config",
|
|
1337
|
+
"cache_control",
|
|
1338
|
+
"context_management",
|
|
1339
|
+
"mcp_servers"
|
|
1340
|
+
];
|
|
1341
|
+
/** Read `providerOptions.anthropic`, tolerating its absence or a wrong shape. */
|
|
1342
|
+
function resolveAnthropicOptions(options) {
|
|
1343
|
+
const raw = options?.providerOptions?.anthropic;
|
|
1344
|
+
return typeof raw === "object" && raw !== null ? raw : {};
|
|
1345
|
+
}
|
|
1053
1346
|
|
|
1054
1347
|
//#endregion
|
|
1055
|
-
//#region
|
|
1348
|
+
//#region ../../packages/ai-anthropic/src/sdk.ts
|
|
1056
1349
|
/**
|
|
1057
1350
|
* Anthropic-backed implementation of `SDKAdapterContract`.
|
|
1058
1351
|
*
|
|
@@ -1118,9 +1411,9 @@ var AnthropicSDK = class {
|
|
|
1118
1411
|
* Rough token-count estimate for a given text. Uses the
|
|
1119
1412
|
* character-heuristic (`approximateTokenCount`) from the core package
|
|
1120
1413
|
* — good enough for budgeting and quota guards, not for billing.
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
* synchronous-cost. The optional model id is reserved for future
|
|
1414
|
+
* For an exact count use `AnthropicModel.countTokens()`, which calls
|
|
1415
|
+
* Anthropic's `messages.countTokens` endpoint (a network round-trip);
|
|
1416
|
+
* `count()` is intentionally offline and synchronous-cost. The optional model id is reserved for future
|
|
1124
1417
|
* per-model tokenizer dispatch; currently ignored.
|
|
1125
1418
|
*/
|
|
1126
1419
|
async count(text, _model) {
|
|
@@ -1129,5 +1422,6 @@ var AnthropicSDK = class {
|
|
|
1129
1422
|
};
|
|
1130
1423
|
|
|
1131
1424
|
//#endregion
|
|
1425
|
+
exports.AnthropicModel = AnthropicModel;
|
|
1132
1426
|
exports.AnthropicSDK = AnthropicSDK;
|
|
1133
1427
|
//# sourceMappingURL=index.cjs.map
|