@warlock.js/ai-anthropic 5.28.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 +31 -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 +74 -10
- package/llms.txt +1 -1
- package/package.json +5 -4
- package/skills/README.md +2 -2
- package/skills/setup-anthropic/SKILL.md +74 -10
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { fromProviderBlocks } from "./provider-blocks.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts
|
|
2
4
|
/**
|
|
3
5
|
* Convert vendor-neutral `Message[]` into Anthropic's request shape.
|
|
4
6
|
*
|
|
@@ -9,15 +11,24 @@
|
|
|
9
11
|
* separated) and returned separately as the top-level `system`
|
|
10
12
|
* parameter.
|
|
11
13
|
* 2. **Tool results are `user` turns.** A neutral `tool` message becomes
|
|
12
|
-
* a `
|
|
13
|
-
*
|
|
14
|
+
* a `tool_result` block keyed by `tool_use_id`. Consecutive tool
|
|
15
|
+
* messages (the results of one parallel tool-use turn) are grouped
|
|
16
|
+
* into a single `user` message, as Anthropic expects — splitting them
|
|
17
|
+
* across messages teaches the model to stop calling tools in parallel.
|
|
14
18
|
* 3. **Tool calls are `tool_use` content blocks.** An assistant message
|
|
15
19
|
* carrying `toolCalls` becomes an `assistant` message whose content
|
|
16
20
|
* is an optional leading `text` block followed by one `tool_use`
|
|
17
21
|
* block per call.
|
|
18
22
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
23
|
+
* An assistant message that carries Anthropic `blocks` (thinking,
|
|
24
|
+
* redacted thinking, compaction, server-tool blocks — see
|
|
25
|
+
* `ProviderContentBlock`) is replayed verbatim from those blocks, which
|
|
26
|
+
* keeps signed thinking and compaction state valid. Blocks from another
|
|
27
|
+
* provider, or blocks whose `tool_use` ids no longer match `toolCalls`,
|
|
28
|
+
* are ignored and the turn is rebuilt from `content` + `toolCalls`.
|
|
29
|
+
*
|
|
30
|
+
* Other consecutive same-role turns are left as-is — the Messages API
|
|
31
|
+
* merges them server-side.
|
|
21
32
|
*
|
|
22
33
|
* @example
|
|
23
34
|
* const { system, messages } = toAnthropicMessages([
|
|
@@ -36,16 +47,29 @@ function toAnthropicMessages(messages) {
|
|
|
36
47
|
continue;
|
|
37
48
|
}
|
|
38
49
|
if (message.role === "tool") {
|
|
39
|
-
|
|
50
|
+
const toolResult = {
|
|
51
|
+
type: "tool_result",
|
|
52
|
+
tool_use_id: message.toolCallId ?? "",
|
|
53
|
+
content: stringifyContent(message.content)
|
|
54
|
+
};
|
|
55
|
+
const previous = mapped[mapped.length - 1];
|
|
56
|
+
if (previous && previous.role === "user" && isToolResultTurn(previous)) previous.content.push(toolResult);
|
|
57
|
+
else mapped.push({
|
|
40
58
|
role: "user",
|
|
41
|
-
content: [
|
|
42
|
-
type: "tool_result",
|
|
43
|
-
tool_use_id: message.toolCallId ?? "",
|
|
44
|
-
content: stringifyContent(message.content)
|
|
45
|
-
}]
|
|
59
|
+
content: [toolResult]
|
|
46
60
|
});
|
|
47
61
|
continue;
|
|
48
62
|
}
|
|
63
|
+
if (message.role === "assistant") {
|
|
64
|
+
const replayed = fromProviderBlocks(message.blocks, message.toolCalls);
|
|
65
|
+
if (replayed) {
|
|
66
|
+
mapped.push({
|
|
67
|
+
role: "assistant",
|
|
68
|
+
content: replayed
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
49
73
|
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
50
74
|
const blocks = extractThinkingBlocks(message.toolCalls);
|
|
51
75
|
const text = stringifyContent(message.content);
|
|
@@ -108,6 +132,14 @@ function toAnthropicThinkingBlock(block) {
|
|
|
108
132
|
}];
|
|
109
133
|
return [];
|
|
110
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* A `user` turn this function built from tool results — its content is a
|
|
137
|
+
* non-empty array made only of `tool_result` blocks. Later tool results
|
|
138
|
+
* of the same tool-use turn are appended to it.
|
|
139
|
+
*/
|
|
140
|
+
function isToolResultTurn(message) {
|
|
141
|
+
return Array.isArray(message.content) && message.content.length > 0 && message.content.every((block) => block.type === "tool_result");
|
|
142
|
+
}
|
|
111
143
|
function isRecord(value) {
|
|
112
144
|
return typeof value === "object" && value !== null;
|
|
113
145
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"to-anthropic-messages.mjs","names":[],"sources":["../../../../../../../ai-anthropic/src/utils/to-anthropic-messages.ts"],"sourcesContent":["import type { ContentPart, Message, ModelToolCallRequest } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for the Anthropic\n * Messages API: the system prompt is hoisted to a top-level `system`\n * string (Anthropic has no `\"system\"` role inside `messages`), and the\n * remaining turns are mapped to `MessageParam[]`.\n */\nexport type AnthropicMessages = {\n system: string | undefined;\n messages: Anthropic.MessageParam[];\n};\n\nconst ANTHROPIC_IMAGE_MEDIA_TYPES = [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"] as const;\n\ntype AnthropicImageMediaType = (typeof ANTHROPIC_IMAGE_MEDIA_TYPES)[number];\n\n/**\n * Convert vendor-neutral `Message[]` into Anthropic's request shape.\n *\n * Anthropic differs from the OpenAI Chat protocol in three ways this\n * function absorbs:\n *\n * 1. **No `system` role.** System messages are concatenated (newline-\n * separated) and returned separately as the top-level `system`\n * parameter.\n * 2. **Tool results are `user` turns.** A neutral `tool` message becomes\n * a `user` message whose content is a single `tool_result` block\n * keyed by `tool_use_id`.\n * 3. **Tool calls are `tool_use` content blocks.** An assistant message\n * carrying `toolCalls` becomes an `assistant` message whose content\n * is an optional leading `text` block followed by one `tool_use`\n * block per call.\n *\n * Consecutive same-role turns are left as-is — the Messages API merges\n * them server-side.\n *\n * @example\n * const { system, messages } = toAnthropicMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // system === \"Be concise.\"\n * // messages === [{ role: \"user\", content: \"Hi\" }]\n */\nexport function toAnthropicMessages(messages: Message[]): AnthropicMessages {\n const systemParts: string[] = [];\n const mapped: Anthropic.MessageParam[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n mapped.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: message.toolCallId ?? \"\",\n content: stringifyContent(message.content),\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: Anthropic.ContentBlockParam[] = extractThinkingBlocks(message.toolCalls);\n const text = stringifyContent(message.content);\n\n if (text) {\n blocks.push({ type: \"text\", text });\n }\n\n for (const toolCall of message.toolCalls) {\n blocks.push({\n type: \"tool_use\",\n id: toolCall.id,\n name: toolCall.name,\n input: toolCall.input ?? {},\n });\n }\n\n mapped.push({ role: \"assistant\", content: blocks });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n mapped.push({\n role: \"user\",\n content: message.content.map(toAnthropicContentBlock),\n });\n\n continue;\n }\n\n mapped.push({\n role: message.role === \"assistant\" ? \"assistant\" : \"user\",\n content: stringifyContent(message.content),\n });\n }\n\n return {\n system: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n messages: mapped,\n };\n}\n\n/**\n * Recover signed extended-thinking blocks stored by this adapter on a tool\n * request. Anthropic requires these blocks to be replayed before the\n * assistant's text and tool uses on the following request.\n */\nfunction extractThinkingBlocks(toolCalls: ModelToolCallRequest[]): Anthropic.ContentBlockParam[] {\n for (const toolCall of toolCalls) {\n const anthropic = toolCall.providerMetadata?.anthropic;\n\n if (!isRecord(anthropic) || !Array.isArray(anthropic.thinkingBlocks)) {\n continue;\n }\n\n return anthropic.thinkingBlocks.flatMap(toAnthropicThinkingBlock);\n }\n\n return [];\n}\n\nfunction toAnthropicThinkingBlock(block: unknown): Anthropic.ContentBlockParam[] {\n if (!isRecord(block) || typeof block.type !== \"string\") {\n return [];\n }\n\n if (\n block.type === \"thinking\" &&\n typeof block.thinking === \"string\" &&\n typeof block.signature === \"string\"\n ) {\n return [{ type: \"thinking\", thinking: block.thinking, signature: block.signature }];\n }\n\n if (block.type === \"redacted_thinking\" && typeof block.data === \"string\") {\n return [{ type: \"redacted_thinking\", data: block.data }];\n }\n\n return [];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any other\n * role collapse a `ContentPart[]` to its concatenated text so the wire\n * format stays valid. Plain strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Map a single resolved `ContentPart` to an Anthropic content block.\n * Text passes straight through; images become an `image` block with a\n * `url` source (remote) or a `base64` source (inlined bytes). The\n * agent has already resolved every attachment before it reaches here,\n * so this never reads files or fetches URLs.\n */\nfunction toAnthropicContentBlock(part: ContentPart): Anthropic.ContentBlockParam {\n if (part.type === \"text\") {\n return { type: \"text\", text: part.text };\n }\n\n // PDF → Anthropic `document` block (A2). The Messages API accepts a\n // url or base64 `application/pdf` source on PDF-capable Claude models.\n if (part.type === \"pdf\") {\n const source =\n \"url\" in part.source\n ? { type: \"url\", url: part.source.url }\n : { type: \"base64\", media_type: \"application/pdf\", data: part.source.base64 };\n return { type: \"document\", source } as unknown as Anthropic.ContentBlockParam;\n }\n\n // Anthropic has no audio content block; the agent's capability gate\n // normally prevents this, but stay loud rather than mis-map it.\n if (part.type === \"audio\") {\n throw new Error(\"Anthropic adapter: audio attachments are not supported\");\n }\n\n if (\"url\" in part.source) {\n return { type: \"image\", source: { type: \"url\", url: part.source.url } };\n }\n\n return {\n type: \"image\",\n source: {\n type: \"base64\",\n media_type: part.source.mediaType as AnthropicImageMediaType,\n data: part.source.base64,\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"to-anthropic-messages.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts"],"sourcesContent":["import type { ContentPart, Message, ModelToolCallRequest } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport { fromProviderBlocks } from \"./provider-blocks\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for the Anthropic\n * Messages API: the system prompt is hoisted to a top-level `system`\n * string (Anthropic has no `\"system\"` role inside `messages`), and the\n * remaining turns are mapped to `MessageParam[]`.\n */\nexport type AnthropicMessages = {\n system: string | undefined;\n messages: Anthropic.MessageParam[];\n};\n\nconst ANTHROPIC_IMAGE_MEDIA_TYPES = [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"] as const;\n\ntype AnthropicImageMediaType = (typeof ANTHROPIC_IMAGE_MEDIA_TYPES)[number];\n\n/**\n * Convert vendor-neutral `Message[]` into Anthropic's request shape.\n *\n * Anthropic differs from the OpenAI Chat protocol in three ways this\n * function absorbs:\n *\n * 1. **No `system` role.** System messages are concatenated (newline-\n * separated) and returned separately as the top-level `system`\n * parameter.\n * 2. **Tool results are `user` turns.** A neutral `tool` message becomes\n * a `tool_result` block keyed by `tool_use_id`. Consecutive tool\n * messages (the results of one parallel tool-use turn) are grouped\n * into a single `user` message, as Anthropic expects — splitting them\n * across messages teaches the model to stop calling tools in parallel.\n * 3. **Tool calls are `tool_use` content blocks.** An assistant message\n * carrying `toolCalls` becomes an `assistant` message whose content\n * is an optional leading `text` block followed by one `tool_use`\n * block per call.\n *\n * An assistant message that carries Anthropic `blocks` (thinking,\n * redacted thinking, compaction, server-tool blocks — see\n * `ProviderContentBlock`) is replayed verbatim from those blocks, which\n * keeps signed thinking and compaction state valid. Blocks from another\n * provider, or blocks whose `tool_use` ids no longer match `toolCalls`,\n * are ignored and the turn is rebuilt from `content` + `toolCalls`.\n *\n * Other consecutive same-role turns are left as-is — the Messages API\n * merges them server-side.\n *\n * @example\n * const { system, messages } = toAnthropicMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // system === \"Be concise.\"\n * // messages === [{ role: \"user\", content: \"Hi\" }]\n */\nexport function toAnthropicMessages(messages: Message[]): AnthropicMessages {\n const systemParts: string[] = [];\n const mapped: Anthropic.MessageParam[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n const toolResult: Anthropic.ToolResultBlockParam = {\n type: \"tool_result\",\n tool_use_id: message.toolCallId ?? \"\",\n content: stringifyContent(message.content),\n };\n const previous = mapped[mapped.length - 1];\n\n if (previous && previous.role === \"user\" && isToolResultTurn(previous)) {\n previous.content.push(toolResult);\n } else {\n mapped.push({ role: \"user\", content: [toolResult] });\n }\n\n continue;\n }\n\n if (message.role === \"assistant\") {\n const replayed = fromProviderBlocks(message.blocks, message.toolCalls);\n\n if (replayed) {\n mapped.push({ role: \"assistant\", content: replayed });\n\n continue;\n }\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: Anthropic.ContentBlockParam[] = extractThinkingBlocks(message.toolCalls);\n const text = stringifyContent(message.content);\n\n if (text) {\n blocks.push({ type: \"text\", text });\n }\n\n for (const toolCall of message.toolCalls) {\n blocks.push({\n type: \"tool_use\",\n id: toolCall.id,\n name: toolCall.name,\n input: toolCall.input ?? {},\n });\n }\n\n mapped.push({ role: \"assistant\", content: blocks });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n mapped.push({\n role: \"user\",\n content: message.content.map(toAnthropicContentBlock),\n });\n\n continue;\n }\n\n mapped.push({\n role: message.role === \"assistant\" ? \"assistant\" : \"user\",\n content: stringifyContent(message.content),\n });\n }\n\n return {\n system: systemParts.length > 0 ? systemParts.join(\"\\n\\n\") : undefined,\n messages: mapped,\n };\n}\n\n/**\n * Recover signed extended-thinking blocks stored by this adapter on a tool\n * request. Anthropic requires these blocks to be replayed before the\n * assistant's text and tool uses on the following request.\n */\nfunction extractThinkingBlocks(toolCalls: ModelToolCallRequest[]): Anthropic.ContentBlockParam[] {\n for (const toolCall of toolCalls) {\n const anthropic = toolCall.providerMetadata?.anthropic;\n\n if (!isRecord(anthropic) || !Array.isArray(anthropic.thinkingBlocks)) {\n continue;\n }\n\n return anthropic.thinkingBlocks.flatMap(toAnthropicThinkingBlock);\n }\n\n return [];\n}\n\nfunction toAnthropicThinkingBlock(block: unknown): Anthropic.ContentBlockParam[] {\n if (!isRecord(block) || typeof block.type !== \"string\") {\n return [];\n }\n\n if (\n block.type === \"thinking\" &&\n typeof block.thinking === \"string\" &&\n typeof block.signature === \"string\"\n ) {\n return [{ type: \"thinking\", thinking: block.thinking, signature: block.signature }];\n }\n\n if (block.type === \"redacted_thinking\" && typeof block.data === \"string\") {\n return [{ type: \"redacted_thinking\", data: block.data }];\n }\n\n return [];\n}\n\n/**\n * A `user` turn this function built from tool results — its content is a\n * non-empty array made only of `tool_result` blocks. Later tool results\n * of the same tool-use turn are appended to it.\n */\nfunction isToolResultTurn(\n message: Anthropic.MessageParam,\n): message is Anthropic.MessageParam & { content: Anthropic.ContentBlockParam[] } {\n return (\n Array.isArray(message.content) &&\n message.content.length > 0 &&\n message.content.every((block) => block.type === \"tool_result\")\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any other\n * role collapse a `ContentPart[]` to its concatenated text so the wire\n * format stays valid. Plain strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Map a single resolved `ContentPart` to an Anthropic content block.\n * Text passes straight through; images become an `image` block with a\n * `url` source (remote) or a `base64` source (inlined bytes). The\n * agent has already resolved every attachment before it reaches here,\n * so this never reads files or fetches URLs.\n */\nfunction toAnthropicContentBlock(part: ContentPart): Anthropic.ContentBlockParam {\n if (part.type === \"text\") {\n return { type: \"text\", text: part.text };\n }\n\n // PDF → Anthropic `document` block (A2). The Messages API accepts a\n // url or base64 `application/pdf` source on PDF-capable Claude models.\n if (part.type === \"pdf\") {\n const source =\n \"url\" in part.source\n ? { type: \"url\", url: part.source.url }\n : { type: \"base64\", media_type: \"application/pdf\", data: part.source.base64 };\n return { type: \"document\", source } as unknown as Anthropic.ContentBlockParam;\n }\n\n // Anthropic has no audio content block; the agent's capability gate\n // normally prevents this, but stay loud rather than mis-map it.\n if (part.type === \"audio\") {\n throw new Error(\"Anthropic adapter: audio attachments are not supported\");\n }\n\n if (\"url\" in part.source) {\n return { type: \"image\", source: { type: \"url\", url: part.source.url } };\n }\n\n return {\n type: \"image\",\n source: {\n type: \"base64\",\n media_type: part.source.mediaType as AnthropicImageMediaType,\n data: part.source.base64,\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extractJsonSchema } from "@warlock.js/ai";
|
|
2
2
|
|
|
3
|
-
//#region
|
|
3
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-tools.ts
|
|
4
4
|
/**
|
|
5
5
|
* Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.
|
|
6
6
|
* Uses the shared `extractJsonSchema` helper; Anthropic requires the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"to-anthropic-tools.mjs","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"to-anthropic-tools.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/to-anthropic-tools.ts"],"sourcesContent":["import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.\n * Uses the shared `extractJsonSchema` helper; Anthropic requires the\n * input schema to be a JSON-Schema object, so a non-object extraction\n * is coerced into an empty-object schema rather than rejected — the\n * tool still registers and the model simply sees no parameters.\n *\n * @example\n * const tools = toAnthropicTools([weatherTool, calculatorTool]);\n * await client.messages.create({ model, max_tokens, messages, tools });\n */\nexport function toAnthropicTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): Anthropic.Tool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: toInputSchema(tool.input),\n }));\n}\n\n/**\n * Coerce the extracted JSON Schema into Anthropic's `Tool.InputSchema`\n * shape (root must be `{ type: \"object\" }`). Anything that isn't an\n * object schema degrades to a parameterless object so registration\n * never fails on a malformed extractor result.\n */\nfunction toInputSchema(input: ToolConfig<unknown, unknown>[\"input\"]): Anthropic.Tool.InputSchema {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema as Anthropic.Tool.InputSchema;\n }\n\n return { type: \"object\" };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,iBACd,OAC8B;CAC9B,IAAI,CAAC,SAAS,MAAM,WAAW,GAC7B;CAGF,OAAO,MAAM,KAAK,UAAU;EAC1B,MAAM,KAAK;EACX,aAAa,KAAK;EAClB,cAAc,cAAc,KAAK,KAAK;CACxC,EAAE;AACJ;;;;;;;AAQA,SAAS,cAAc,OAA0E;CAC/F,MAAM,SAAS,kBAAkB,KAAK;CAEtC,IAAI,UAAU,OAAO,SAAS,UAC5B,OAAO;CAGT,OAAO,EAAE,MAAM,SAAS;AAC1B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { APIConnectionTimeoutError, APIError } from "@anthropic-ai/sdk";
|
|
2
2
|
import { AIError, ContextLengthExceededError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderRateLimitError, ProviderTimeoutError, QuotaExceededError } from "@warlock.js/ai";
|
|
3
3
|
|
|
4
|
-
//#region
|
|
4
|
+
//#region ../../packages/ai-anthropic/src/utils/wrap-anthropic-error.ts
|
|
5
5
|
/**
|
|
6
6
|
* Wrap any thrown value caught inside the Anthropic adapter into the
|
|
7
7
|
* appropriate `@warlock.js/ai` `AIError` subclass.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wrap-anthropic-error.mjs","names":[],"sources":["../../../../../../../ai-anthropic/src/utils/wrap-anthropic-error.ts"],"sourcesContent":["import {\n AIError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n QuotaExceededError,\n} from \"@warlock.js/ai\";\nimport { APIConnectionTimeoutError, APIError } from \"@anthropic-ai/sdk\";\n\n/**\n * Raw-error fields the wrapper reads off an Anthropic SDK error.\n *\n * `APIError` exposes `status`, `type` (the `error.type` from the\n * response body, e.g. `\"rate_limit_error\"`), `message`, `headers`, and\n * `requestID`. We duck-type because wrapped retries, proxied errors,\n * and custom subclasses sometimes lose the `instanceof` relationship.\n */\ntype AnthropicErrorShape = {\n status?: number;\n type?: string | null;\n message?: string;\n headers?: HeaderBag | undefined;\n name?: string;\n requestId?: string;\n};\n\n/** Either a fetch `Headers` instance or a plain record (duck-typed tests). */\ntype HeaderBag = Headers | Record<string, string>;\n\n/**\n * Wrap any thrown value caught inside the Anthropic adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Anthropic has no per-error machine `code`; the\n * stable identifier is `error.type` on the response body (surfaced as\n * `APIError.type`). Dispatch prefers `type`, falls back to `status`\n * when the body was stripped (common with proxies). Name-based\n * detection catches transport-layer timeouts that never produced an\n * HTTP response. The `invalid_request_error` branch additionally\n * sniffs the message for Anthropic's \"prompt is too long\" phrasing,\n * which is the only signal that a 400 was a context-length overflow.\n *\n * `AIError` instances pass through unchanged so `catch/throw wrap(e)`\n * pipelines never double-wrap.\n *\n * @example\n * try {\n * return await this.client.messages.create(...);\n * } catch (thrown) {\n * throw wrapAnthropicError(thrown);\n * }\n */\nexport function wrapAnthropicError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(thrown, shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (shape.type === \"authentication_error\" || shape.type === \"permission_error\") {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.status === 401 || shape.status === 403) {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.type === \"billing_error\") {\n return new QuotaExceededError(message, { cause: thrown, context });\n }\n\n if (shape.type === \"rate_limit_error\" || shape.status === 429) {\n return new ProviderRateLimitError(message, {\n cause: thrown,\n context,\n retryAfter: parseRetryAfter(shape.headers),\n });\n }\n\n if (shape.type === \"invalid_request_error\" || isClientStatus(shape.status)) {\n if (/prompt is too long/i.test(message)) {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape without depending on `instanceof APIError`\n * — proxies and re-wrappers strip the prototype chain. Duck-typing on\n * the visible fields is resilient to both.\n */\nfunction toShape(thrown: unknown): AnthropicErrorShape {\n if (thrown instanceof APIError) {\n return {\n status: typeof thrown.status === \"number\" ? thrown.status : undefined,\n type: thrown.type,\n message: thrown.message,\n headers: thrown.headers,\n name: thrown.name,\n requestId: thrown.requestID ?? undefined,\n };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n type: typeof raw.type === \"string\" ? raw.type : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n headers: isHeaderBag(raw.headers) ? raw.headers : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n requestId: readRequestId(raw),\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the thrown value represents a timeout. The Anthropic\n * SDK throws `APIConnectionTimeoutError` for transport-level timeouts;\n * Node surfaces `ETIMEDOUT` / `ECONNABORTED` on the socket layer.\n * Either signal counts.\n */\nfunction isTimeout(thrown: unknown, shape: AnthropicErrorShape): boolean {\n if (thrown instanceof APIConnectionTimeoutError) {\n return true;\n }\n\n if (shape.name === \"APIConnectionTimeoutError\") {\n return true;\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const code = (thrown as Record<string, unknown>).code;\n\n if (code === \"ETIMEDOUT\" || code === \"ECONNABORTED\") {\n return true;\n }\n }\n\n return false;\n}\n\n/** True for HTTP 4xx — a client-side request problem, not a server fault. */\nfunction isClientStatus(status: number | undefined): boolean {\n return typeof status === \"number\" && status >= 400 && status < 500;\n}\n\n/**\n * Attach the raw diagnostic fields to `error.context` so consumers\n * have everything the provider surfaced without each subclass having\n * to redeclare them. Never includes `cause` — that lives on\n * `error.cause`.\n */\nfunction buildContext(shape: AnthropicErrorShape): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.type) {\n context.type = shape.type;\n }\n\n if (shape.requestId) {\n context.requestId = shape.requestId;\n }\n\n return context;\n}\n\n/** Anthropic exposes the request id as `requestID`; some proxies use `request_id`. */\nfunction readRequestId(raw: Record<string, unknown>): string | undefined {\n if (typeof raw.requestID === \"string\") {\n return raw.requestID;\n }\n\n if (typeof raw.request_id === \"string\") {\n return raw.request_id;\n }\n\n return undefined;\n}\n\nfunction isHeaderBag(value: unknown): value is HeaderBag {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Read a header value from either a `Headers` instance or a plain record. */\nfunction readHeader(headers: HeaderBag, name: string): string | undefined {\n if (typeof (headers as Headers).get === \"function\") {\n return (headers as Headers).get(name) ?? undefined;\n }\n\n const record = headers as Record<string, string>;\n\n return record[name] ?? record[name.toLowerCase()];\n}\n\n/**\n * Parse the `Retry-After` response header (seconds per HTTP spec) into\n * milliseconds so consumers can feed it straight to `setTimeout`.\n * Returns `undefined` when missing or unparseable.\n */\nfunction parseRetryAfter(headers: HeaderBag | undefined): number | undefined {\n if (!headers) {\n return undefined;\n }\n\n const raw = readHeader(headers, \"retry-after\") ?? readHeader(headers, \"Retry-After\");\n\n if (!raw) {\n return undefined;\n }\n\n const seconds = Number(raw);\n\n if (!Number.isFinite(seconds) || seconds < 0) {\n return undefined;\n }\n\n return Math.round(seconds * 1000);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"wrap-anthropic-error.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/wrap-anthropic-error.ts"],"sourcesContent":["import {\n AIError,\n ContextLengthExceededError,\n InvalidRequestError,\n ProviderAuthError,\n ProviderError,\n ProviderRateLimitError,\n ProviderTimeoutError,\n QuotaExceededError,\n} from \"@warlock.js/ai\";\nimport { APIConnectionTimeoutError, APIError } from \"@anthropic-ai/sdk\";\n\n/**\n * Raw-error fields the wrapper reads off an Anthropic SDK error.\n *\n * `APIError` exposes `status`, `type` (the `error.type` from the\n * response body, e.g. `\"rate_limit_error\"`), `message`, `headers`, and\n * `requestID`. We duck-type because wrapped retries, proxied errors,\n * and custom subclasses sometimes lose the `instanceof` relationship.\n */\ntype AnthropicErrorShape = {\n status?: number;\n type?: string | null;\n message?: string;\n headers?: HeaderBag | undefined;\n name?: string;\n requestId?: string;\n};\n\n/** Either a fetch `Headers` instance or a plain record (duck-typed tests). */\ntype HeaderBag = Headers | Record<string, string>;\n\n/**\n * Wrap any thrown value caught inside the Anthropic adapter into the\n * appropriate `@warlock.js/ai` `AIError` subclass.\n *\n * **Dispatch strategy.** Anthropic has no per-error machine `code`; the\n * stable identifier is `error.type` on the response body (surfaced as\n * `APIError.type`). Dispatch prefers `type`, falls back to `status`\n * when the body was stripped (common with proxies). Name-based\n * detection catches transport-layer timeouts that never produced an\n * HTTP response. The `invalid_request_error` branch additionally\n * sniffs the message for Anthropic's \"prompt is too long\" phrasing,\n * which is the only signal that a 400 was a context-length overflow.\n *\n * `AIError` instances pass through unchanged so `catch/throw wrap(e)`\n * pipelines never double-wrap.\n *\n * @example\n * try {\n * return await this.client.messages.create(...);\n * } catch (thrown) {\n * throw wrapAnthropicError(thrown);\n * }\n */\nexport function wrapAnthropicError(thrown: unknown): AIError {\n if (thrown instanceof AIError) {\n return thrown;\n }\n\n const shape = toShape(thrown);\n const context = buildContext(shape);\n const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));\n\n if (isTimeout(thrown, shape)) {\n return new ProviderTimeoutError(message, { cause: thrown, context });\n }\n\n if (shape.type === \"authentication_error\" || shape.type === \"permission_error\") {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.status === 401 || shape.status === 403) {\n return new ProviderAuthError(message, { cause: thrown, context });\n }\n\n if (shape.type === \"billing_error\") {\n return new QuotaExceededError(message, { cause: thrown, context });\n }\n\n if (shape.type === \"rate_limit_error\" || shape.status === 429) {\n return new ProviderRateLimitError(message, {\n cause: thrown,\n context,\n retryAfter: parseRetryAfter(shape.headers),\n });\n }\n\n if (shape.type === \"invalid_request_error\" || isClientStatus(shape.status)) {\n if (/prompt is too long/i.test(message)) {\n return new ContextLengthExceededError(message, { cause: thrown, context });\n }\n\n return new InvalidRequestError(message, { cause: thrown, context });\n }\n\n return new ProviderError(message, { cause: thrown, context });\n}\n\n/**\n * Read the raw error shape without depending on `instanceof APIError`\n * — proxies and re-wrappers strip the prototype chain. Duck-typing on\n * the visible fields is resilient to both.\n */\nfunction toShape(thrown: unknown): AnthropicErrorShape {\n if (thrown instanceof APIError) {\n return {\n status: typeof thrown.status === \"number\" ? thrown.status : undefined,\n type: thrown.type,\n message: thrown.message,\n headers: thrown.headers,\n name: thrown.name,\n requestId: thrown.requestID ?? undefined,\n };\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const raw = thrown as Record<string, unknown>;\n\n return {\n status: typeof raw.status === \"number\" ? raw.status : undefined,\n type: typeof raw.type === \"string\" ? raw.type : undefined,\n message: typeof raw.message === \"string\" ? raw.message : undefined,\n headers: isHeaderBag(raw.headers) ? raw.headers : undefined,\n name: typeof raw.name === \"string\" ? raw.name : undefined,\n requestId: readRequestId(raw),\n };\n }\n\n return {};\n}\n\n/**\n * Decide whether the thrown value represents a timeout. The Anthropic\n * SDK throws `APIConnectionTimeoutError` for transport-level timeouts;\n * Node surfaces `ETIMEDOUT` / `ECONNABORTED` on the socket layer.\n * Either signal counts.\n */\nfunction isTimeout(thrown: unknown, shape: AnthropicErrorShape): boolean {\n if (thrown instanceof APIConnectionTimeoutError) {\n return true;\n }\n\n if (shape.name === \"APIConnectionTimeoutError\") {\n return true;\n }\n\n if (typeof thrown === \"object\" && thrown !== null) {\n const code = (thrown as Record<string, unknown>).code;\n\n if (code === \"ETIMEDOUT\" || code === \"ECONNABORTED\") {\n return true;\n }\n }\n\n return false;\n}\n\n/** True for HTTP 4xx — a client-side request problem, not a server fault. */\nfunction isClientStatus(status: number | undefined): boolean {\n return typeof status === \"number\" && status >= 400 && status < 500;\n}\n\n/**\n * Attach the raw diagnostic fields to `error.context` so consumers\n * have everything the provider surfaced without each subclass having\n * to redeclare them. Never includes `cause` — that lives on\n * `error.cause`.\n */\nfunction buildContext(shape: AnthropicErrorShape): Record<string, unknown> {\n const context: Record<string, unknown> = {};\n\n if (shape.status !== undefined) {\n context.status = shape.status;\n }\n\n if (shape.type) {\n context.type = shape.type;\n }\n\n if (shape.requestId) {\n context.requestId = shape.requestId;\n }\n\n return context;\n}\n\n/** Anthropic exposes the request id as `requestID`; some proxies use `request_id`. */\nfunction readRequestId(raw: Record<string, unknown>): string | undefined {\n if (typeof raw.requestID === \"string\") {\n return raw.requestID;\n }\n\n if (typeof raw.request_id === \"string\") {\n return raw.request_id;\n }\n\n return undefined;\n}\n\nfunction isHeaderBag(value: unknown): value is HeaderBag {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Read a header value from either a `Headers` instance or a plain record. */\nfunction readHeader(headers: HeaderBag, name: string): string | undefined {\n if (typeof (headers as Headers).get === \"function\") {\n return (headers as Headers).get(name) ?? undefined;\n }\n\n const record = headers as Record<string, string>;\n\n return record[name] ?? record[name.toLowerCase()];\n}\n\n/**\n * Parse the `Retry-After` response header (seconds per HTTP spec) into\n * milliseconds so consumers can feed it straight to `setTimeout`.\n * Returns `undefined` when missing or unparseable.\n */\nfunction parseRetryAfter(headers: HeaderBag | undefined): number | undefined {\n if (!headers) {\n return undefined;\n }\n\n const raw = readHeader(headers, \"retry-after\") ?? readHeader(headers, \"Retry-After\");\n\n if (!raw) {\n return undefined;\n }\n\n const seconds = Number(raw);\n\n if (!Number.isFinite(seconds) || seconds < 0) {\n return undefined;\n }\n\n return Math.round(seconds * 1000);\n}\n"],"mappings":"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"}
|
package/llms-full.txt
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: setup-anthropic
|
|
11
|
-
description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl, cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings —
|
|
11
|
+
description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl (per-call ttl), provider blocks (thinking/compaction) replayed verbatim via ModelResponse.blocks / Message.blocks, providerOptions.anthropic (betas, body fields such as context_management / fallbacks, thinkingDisplay), model.countTokens(), distinct finish reasons (pause_turn, refusal, context_exceeded), cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `providerOptions`, `blocks`, `countTokens`, `pause_turn`, `refusal`, `context_management`, `compaction`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — the `setup-openai` topic of the `warlock-js-ai-openai` skill; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# `@warlock.js/ai-anthropic`
|
|
@@ -101,7 +101,7 @@ Anthropic has no `"system"` role inside `messages`. The adapter hoists every neu
|
|
|
101
101
|
|
|
102
102
|
- Outgoing: vendor-neutral tools → Anthropic `tools` with `input_schema` (non-object schemas degrade to a parameterless object so registration never fails).
|
|
103
103
|
- Assistant tool calls in history → `assistant` message with an optional leading `text` block + one `tool_use` block per call.
|
|
104
|
-
- Tool results (`role: "tool"`) →
|
|
104
|
+
- Tool results (`role: "tool"`) → `tool_result` blocks keyed by `tool_use_id`. Consecutive tool messages (the results of one parallel tool-use turn) are grouped into **one** `user` turn, as Anthropic expects.
|
|
105
105
|
- Response `tool_use` blocks → neutral `toolCalls`; `stop_reason: "tool_use"` → `finishReason: "tool_calls"`.
|
|
106
106
|
|
|
107
107
|
## Structured output
|
|
@@ -121,7 +121,7 @@ Adaptive models reject `budget_tokens` with a 400, so the shape matters.
|
|
|
121
121
|
|
|
122
122
|
- **Adaptive:** `reasoning.effort` (`"low" | "medium" | "high"`) → `output_config.effort` of the same name. With only `reasoning.maxTokens` set, effort is `"medium"`; `maxTokens` then only sizes the reserved `max_tokens` room, it is not sent as a budget.
|
|
123
123
|
- **Budget:** `reasoning.maxTokens` → `budget_tokens` verbatim; `reasoning.effort` → a tiered budget (`1024` / `4096` / `12000`) when no `maxTokens` is given. Any resolved budget is floored at Anthropic's `1024` minimum.
|
|
124
|
-
- `reasoning` with neither `effort` nor `maxTokens`, or `effort: "none"`, emits nothing.
|
|
124
|
+
- `reasoning` with neither `effort` nor `maxTokens`, or `effort: "none"`, emits nothing — except `effort: "none"` on Claude Sonnet 5.5, which sends `thinking: { type: "between_tools" }` (see below).
|
|
125
125
|
|
|
126
126
|
### `thinkingMode` override
|
|
127
127
|
|
|
@@ -152,7 +152,63 @@ await model.complete(messages, { reasoning: { maxTokens: 8000 } });
|
|
|
152
152
|
// adaptive model: effort "medium"; budget model: budget_tokens 8000
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
**Claude 5 family (Opus 5.5, Sonnet 5.5, Fable 5 / 5.1):** thinking is always on. These models reject `thinking: { type: "disabled" }` and `budget_tokens` with a 400, so the adapter never sends either: they are adaptive and `reasoning.maxTokens` only sizes `max_tokens`. `effort: "none"` is mapped per model:
|
|
156
|
+
|
|
157
|
+
| Model | `effort: "none"` sends | Result |
|
|
158
|
+
| ----------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
159
|
+
| Sonnet 5.5 | `thinking: { type: "between_tools" }`, alone (no `output_config.effort`, no `display`) | Thinking off. `between_tools` takes no other field and needs effort `high` or below; the model default, `high`, applies. |
|
|
160
|
+
| Opus 5.5, Fable 5 / 5.1 | no `thinking` field | No thinking-off form exists, so the model still thinks at its default effort (Opus 5.5: `medium`). Pass `effort: "low"` to think less. |
|
|
161
|
+
|
|
162
|
+
`thinkingDisplay` is applied only to adaptive thinking, never beside `between_tools`.
|
|
163
|
+
|
|
164
|
+
`thinking.display` is set with `providerOptions.anthropic.thinkingDisplay` (`"summarized"` for readable summaries; the 5-family default is `"omitted"`, empty thinking text). It is applied whenever the adapter sends `thinking` to an adaptive model, so pair it with a `reasoning` option.
|
|
165
|
+
|
|
166
|
+
When a thinking-enabled response also makes tool calls, its signed `thinking` and `redacted_thinking` blocks are retained in each call's `providerMetadata.anthropic.thinkingBlocks` and replayed on the next assistant turn. The full turn is also returned as `ModelResponse.blocks` — see [Provider blocks](#provider-blocks-thinking-compaction-server-tools).
|
|
167
|
+
|
|
168
|
+
## Provider blocks (thinking, compaction, server tools)
|
|
169
|
+
|
|
170
|
+
When a response contains a block the neutral `content` / `toolCalls` cannot carry (thinking, redacted thinking, `compaction`, server-tool use/results, cited text), the adapter returns the whole ordered turn as `ModelResponse.blocks` (and on the streaming `done` chunk): `{ provider: "anthropic", type, data }` per block. Plain text / tool-use turns have no `blocks`.
|
|
171
|
+
|
|
172
|
+
The agent copies them onto the assistant `Message.blocks` (tool-call trips, `pause_turn` trips, repair turns) and onto `LLMTrip.blocks`; `AgentResult.blocks` is set only for a single-trip run. On the next request the adapter replays an assistant message **verbatim** from its `blocks` — required for preserved thinking and server-side compaction. It falls back to rebuilding from `content` + `toolCalls` when the blocks come from another provider, a block's `data.type` differs from its `type` or is not an assistant-turn type, or their `tool_use` ids no longer match `toolCalls`.
|
|
173
|
+
|
|
174
|
+
A replayed thinking block requires every earlier turn unchanged, including the run's tool-call turns and tool results. Never store just `{ content: result.text, blocks }` after a multi-trip run. Keep the run's turns:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
// agent created with `captureMessages: true`
|
|
178
|
+
const result = await agent.execute(input, { history });
|
|
179
|
+
const finalTrip = result.report.trips.at(-1);
|
|
180
|
+
|
|
181
|
+
history = [
|
|
182
|
+
...(result.report.messages ?? []).filter((message) => message.role !== "system"),
|
|
183
|
+
{ role: "assistant", content: finalTrip?.output ?? "", blocks: finalTrip?.blocks },
|
|
184
|
+
];
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Provider options (`providerOptions.anthropic`)
|
|
188
|
+
|
|
189
|
+
Anthropic-only request options, per call or in the agent's `modelOptions`. Other adapters ignore the key. Type: `AnthropicCallOptions`.
|
|
190
|
+
|
|
191
|
+
| Field | Effect |
|
|
192
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
193
|
+
| `betas` | Beta flags. When non-empty the request goes through `client.beta.messages` (needed for beta body fields and beta blocks such as `compaction`). |
|
|
194
|
+
| `body` | Extra body fields merged **last** (caller wins): `context_management`, `fallbacks`, `inference_geo`, `tool_choice`, … Not validated, except that `model`, `messages`, `system`, `tools`, `mcp_servers` and `stream` are refused (`InvalidRequestError`). |
|
|
195
|
+
| `thinkingDisplay` | `thinking.display` on adaptive models: `"summarized"`, `"omitted"`, `"updates"` (the last needs its beta). |
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
const agent = ai.agent({
|
|
199
|
+
model: anthropic.model({ name: "claude-opus-5-5" }),
|
|
200
|
+
modelOptions: {
|
|
201
|
+
providerOptions: {
|
|
202
|
+
anthropic: {
|
|
203
|
+
betas: ["compact-2026-01-12"],
|
|
204
|
+
body: { context_management: { edits: [{ type: "compact_20260112" }] } },
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Cache TTL per call: `options.cacheControl.ttl` (`"5m"` | `"1h"`) overrides the model's `promptCacheTtl` for that call.
|
|
156
212
|
|
|
157
213
|
## Prompt caching (`cache_control`)
|
|
158
214
|
|
|
@@ -190,11 +246,14 @@ await agent.execute("…", { cacheControl: { breakpoints: 1 } }); // cache the s
|
|
|
190
246
|
|
|
191
247
|
- `content_block_delta` text → `{ type: "delta", content }`
|
|
192
248
|
- `tool_use` blocks accumulate `input_json_delta` fragments and emit one consolidated `{ type: "tool-call", ... }` at `content_block_stop` (no partial-arg `{}` artifact — input is parsed once, whole)
|
|
193
|
-
-
|
|
249
|
+
- every content block is also assembled by index (text, thinking + signature, server-tool input, beta `compaction_delta`) and returned as `done.blocks` when the turn has a non-neutral block
|
|
250
|
+
- terminal `{ type: "done", finishReason, usage, blocks? }` — `usage.input` from `message_start`, `usage.output` from `message_delta`, `total` computed; `cachedTokens` / `cacheWriteTokens` from `message_start` then the cumulative `message_delta` counts (see [Tokens & usage accounting](#tokens--usage-accounting))
|
|
194
251
|
|
|
195
252
|
## Finish-reason mapping
|
|
196
253
|
|
|
197
|
-
`end_turn` / `stop_sequence` → `stop` · `max_tokens` → `length` · `
|
|
254
|
+
`end_turn` / `stop_sequence` / beta `compaction` → `stop` · `max_tokens` → `length` · `model_context_window_exceeded` → `context_exceeded` · `pause_turn` → `pause_turn` · `refusal` → `refusal` · `tool_use` → `tool_calls` · unknown / null → `error`.
|
|
255
|
+
|
|
256
|
+
The agent resumes `pause_turn` automatically (it replays the paused turn and calls again, within `maxTrips`). `refusal` and `context_exceeded` end the run like `stop`; check `report.trips.at(-1).finishReason` to fall back to another model or compact the history.
|
|
198
257
|
|
|
199
258
|
## Errors
|
|
200
259
|
|
|
@@ -211,11 +270,16 @@ Raw SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hierarchy.
|
|
|
211
270
|
|
|
212
271
|
```ts
|
|
213
272
|
await anthropic.count("some text"); // approximate heuristic, offline
|
|
273
|
+
|
|
274
|
+
const model = anthropic.model({ name: "claude-opus-5-5" }); // an AnthropicModel
|
|
275
|
+
await model.countTokens(messages, { tools }); // exact, via messages.countTokens (network)
|
|
214
276
|
```
|
|
215
277
|
|
|
278
|
+
`countTokens` sends the same system prompt, tools, thinking and `providerOptions.anthropic.betas` a real call would, keeping only the fields the endpoint accepts.
|
|
279
|
+
|
|
216
280
|
## No embeddings
|
|
217
281
|
|
|
218
|
-
Anthropic ships no first-party embeddings API, so `AnthropicSDK` intentionally does **not** implement `embedder()` (the `SDKAdapterContract.embedder` slot is optional). Use
|
|
282
|
+
Anthropic ships no first-party embeddings API, so `AnthropicSDK` intentionally does **not** implement `embedder()` (the `SDKAdapterContract.embedder` slot is optional). Use `@warlock.js/ai-openai` (or another embeddings-capable adapter; see the `setup-openai` topic of the `warlock-js-ai-openai` skill) for vectors.
|
|
219
283
|
|
|
220
284
|
## When NOT to use this skill
|
|
221
285
|
|
|
@@ -224,8 +288,8 @@ Anthropic ships no first-party embeddings API, so `AnthropicSDK` intentionally d
|
|
|
224
288
|
|
|
225
289
|
## See also
|
|
226
290
|
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
291
|
+
- The `run-ai-agent` topic of the `warlock-js-ai` skill
|
|
292
|
+
- The `pick-ai-provider` topic of the `warlock-js-ai` skill
|
|
293
|
+
- The `handle-ai-errors` topic of the `warlock-js-ai` skill
|
|
230
294
|
|
|
231
295
|
|
package/llms.txt
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [setup-anthropic](@warlock.js/ai-anthropic/setup-anthropic/SKILL.md): Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl, cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings —
|
|
9
|
+
- [setup-anthropic](@warlock.js/ai-anthropic/setup-anthropic/SKILL.md): Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl (per-call ttl), provider blocks (thinking/compaction) replayed verbatim via ModelResponse.blocks / Message.blocks, providerOptions.anthropic (betas, body fields such as context_management / fallbacks, thinkingDisplay), model.countTokens(), distinct finish reasons (pause_turn, refusal, context_exceeded), cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `providerOptions`, `blocks`, `countTokens`, `pause_turn`, `refusal`, `context_management`, `compaction`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — the `setup-openai` topic of the `warlock-js-ai-openai` skill; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.
|
package/package.json
CHANGED
|
@@ -11,16 +11,17 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "https://github.com/warlockjs/
|
|
14
|
+
"url": "git+https://github.com/warlockjs/framework.git",
|
|
15
|
+
"directory": "packages/ai-anthropic"
|
|
15
16
|
},
|
|
16
17
|
"peerDependencies": {
|
|
17
|
-
"@warlock.js/ai": "5.
|
|
18
|
+
"@warlock.js/ai": "5.30.0"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"@anthropic-ai/sdk": "^0.129.0",
|
|
21
|
-
"@warlock.js/logger": "5.
|
|
22
|
+
"@warlock.js/logger": "5.30.0"
|
|
22
23
|
},
|
|
23
|
-
"version": "5.
|
|
24
|
+
"version": "5.30.0",
|
|
24
25
|
"main": "./cjs/index.cjs",
|
|
25
26
|
"module": "./esm/index.mjs",
|
|
26
27
|
"types": "./esm/index.d.mts",
|
package/skills/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# `@warlock.js/ai-anthropic` — skills index
|
|
2
2
|
|
|
3
|
-
Per-task skills.
|
|
3
|
+
Per-task skills. Cross-references name the topic ("the `<topic>` topic"), and for another package add its skill ("the `<topic>` topic of the `warlock-js-<pkg>` skill").
|
|
4
4
|
|
|
5
5
|
## Skills
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### `setup-anthropic`
|
|
8
8
|
|
|
9
9
|
Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl with cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Load when wiring a Claude-backed model into a @warlock.js agent.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: setup-anthropic
|
|
3
|
-
description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl, cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings —
|
|
3
|
+
description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, thinkingMode?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → adaptive thinking + output_config.effort (current/unknown models) or budget_tokens (Haiku 4.5, 4.6 and older; override with thinkingMode), prompt caching via promptCaching + options.cacheControl (per-call ttl), provider blocks (thinking/compaction) replayed verbatim via ModelResponse.blocks / Message.blocks, providerOptions.anthropic (betas, body fields such as context_management / fallbacks, thinkingDisplay), model.countTokens(), distinct finish reasons (pause_turn, refusal, context_exceeded), cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `thinkingMode`, `adaptive`, `cacheControl`, `providerOptions`, `blocks`, `countTokens`, `pause_turn`, `refusal`, `context_management`, `compaction`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — the `setup-openai` topic of the `warlock-js-ai-openai` skill; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `@warlock.js/ai-anthropic`
|
|
@@ -93,7 +93,7 @@ Anthropic has no `"system"` role inside `messages`. The adapter hoists every neu
|
|
|
93
93
|
|
|
94
94
|
- Outgoing: vendor-neutral tools → Anthropic `tools` with `input_schema` (non-object schemas degrade to a parameterless object so registration never fails).
|
|
95
95
|
- Assistant tool calls in history → `assistant` message with an optional leading `text` block + one `tool_use` block per call.
|
|
96
|
-
- Tool results (`role: "tool"`) →
|
|
96
|
+
- Tool results (`role: "tool"`) → `tool_result` blocks keyed by `tool_use_id`. Consecutive tool messages (the results of one parallel tool-use turn) are grouped into **one** `user` turn, as Anthropic expects.
|
|
97
97
|
- Response `tool_use` blocks → neutral `toolCalls`; `stop_reason: "tool_use"` → `finishReason: "tool_calls"`.
|
|
98
98
|
|
|
99
99
|
## Structured output
|
|
@@ -113,7 +113,7 @@ Adaptive models reject `budget_tokens` with a 400, so the shape matters.
|
|
|
113
113
|
|
|
114
114
|
- **Adaptive:** `reasoning.effort` (`"low" | "medium" | "high"`) → `output_config.effort` of the same name. With only `reasoning.maxTokens` set, effort is `"medium"`; `maxTokens` then only sizes the reserved `max_tokens` room, it is not sent as a budget.
|
|
115
115
|
- **Budget:** `reasoning.maxTokens` → `budget_tokens` verbatim; `reasoning.effort` → a tiered budget (`1024` / `4096` / `12000`) when no `maxTokens` is given. Any resolved budget is floored at Anthropic's `1024` minimum.
|
|
116
|
-
- `reasoning` with neither `effort` nor `maxTokens`, or `effort: "none"`, emits nothing.
|
|
116
|
+
- `reasoning` with neither `effort` nor `maxTokens`, or `effort: "none"`, emits nothing — except `effort: "none"` on Claude Sonnet 5.5, which sends `thinking: { type: "between_tools" }` (see below).
|
|
117
117
|
|
|
118
118
|
### `thinkingMode` override
|
|
119
119
|
|
|
@@ -144,7 +144,63 @@ await model.complete(messages, { reasoning: { maxTokens: 8000 } });
|
|
|
144
144
|
// adaptive model: effort "medium"; budget model: budget_tokens 8000
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
**Claude 5 family (Opus 5.5, Sonnet 5.5, Fable 5 / 5.1):** thinking is always on. These models reject `thinking: { type: "disabled" }` and `budget_tokens` with a 400, so the adapter never sends either: they are adaptive and `reasoning.maxTokens` only sizes `max_tokens`. `effort: "none"` is mapped per model:
|
|
148
|
+
|
|
149
|
+
| Model | `effort: "none"` sends | Result |
|
|
150
|
+
| ----------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| Sonnet 5.5 | `thinking: { type: "between_tools" }`, alone (no `output_config.effort`, no `display`) | Thinking off. `between_tools` takes no other field and needs effort `high` or below; the model default, `high`, applies. |
|
|
152
|
+
| Opus 5.5, Fable 5 / 5.1 | no `thinking` field | No thinking-off form exists, so the model still thinks at its default effort (Opus 5.5: `medium`). Pass `effort: "low"` to think less. |
|
|
153
|
+
|
|
154
|
+
`thinkingDisplay` is applied only to adaptive thinking, never beside `between_tools`.
|
|
155
|
+
|
|
156
|
+
`thinking.display` is set with `providerOptions.anthropic.thinkingDisplay` (`"summarized"` for readable summaries; the 5-family default is `"omitted"`, empty thinking text). It is applied whenever the adapter sends `thinking` to an adaptive model, so pair it with a `reasoning` option.
|
|
157
|
+
|
|
158
|
+
When a thinking-enabled response also makes tool calls, its signed `thinking` and `redacted_thinking` blocks are retained in each call's `providerMetadata.anthropic.thinkingBlocks` and replayed on the next assistant turn. The full turn is also returned as `ModelResponse.blocks` — see [Provider blocks](#provider-blocks-thinking-compaction-server-tools).
|
|
159
|
+
|
|
160
|
+
## Provider blocks (thinking, compaction, server tools)
|
|
161
|
+
|
|
162
|
+
When a response contains a block the neutral `content` / `toolCalls` cannot carry (thinking, redacted thinking, `compaction`, server-tool use/results, cited text), the adapter returns the whole ordered turn as `ModelResponse.blocks` (and on the streaming `done` chunk): `{ provider: "anthropic", type, data }` per block. Plain text / tool-use turns have no `blocks`.
|
|
163
|
+
|
|
164
|
+
The agent copies them onto the assistant `Message.blocks` (tool-call trips, `pause_turn` trips, repair turns) and onto `LLMTrip.blocks`; `AgentResult.blocks` is set only for a single-trip run. On the next request the adapter replays an assistant message **verbatim** from its `blocks` — required for preserved thinking and server-side compaction. It falls back to rebuilding from `content` + `toolCalls` when the blocks come from another provider, a block's `data.type` differs from its `type` or is not an assistant-turn type, or their `tool_use` ids no longer match `toolCalls`.
|
|
165
|
+
|
|
166
|
+
A replayed thinking block requires every earlier turn unchanged, including the run's tool-call turns and tool results. Never store just `{ content: result.text, blocks }` after a multi-trip run. Keep the run's turns:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
// agent created with `captureMessages: true`
|
|
170
|
+
const result = await agent.execute(input, { history });
|
|
171
|
+
const finalTrip = result.report.trips.at(-1);
|
|
172
|
+
|
|
173
|
+
history = [
|
|
174
|
+
...(result.report.messages ?? []).filter((message) => message.role !== "system"),
|
|
175
|
+
{ role: "assistant", content: finalTrip?.output ?? "", blocks: finalTrip?.blocks },
|
|
176
|
+
];
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Provider options (`providerOptions.anthropic`)
|
|
180
|
+
|
|
181
|
+
Anthropic-only request options, per call or in the agent's `modelOptions`. Other adapters ignore the key. Type: `AnthropicCallOptions`.
|
|
182
|
+
|
|
183
|
+
| Field | Effect |
|
|
184
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| `betas` | Beta flags. When non-empty the request goes through `client.beta.messages` (needed for beta body fields and beta blocks such as `compaction`). |
|
|
186
|
+
| `body` | Extra body fields merged **last** (caller wins): `context_management`, `fallbacks`, `inference_geo`, `tool_choice`, … Not validated, except that `model`, `messages`, `system`, `tools`, `mcp_servers` and `stream` are refused (`InvalidRequestError`). |
|
|
187
|
+
| `thinkingDisplay` | `thinking.display` on adaptive models: `"summarized"`, `"omitted"`, `"updates"` (the last needs its beta). |
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const agent = ai.agent({
|
|
191
|
+
model: anthropic.model({ name: "claude-opus-5-5" }),
|
|
192
|
+
modelOptions: {
|
|
193
|
+
providerOptions: {
|
|
194
|
+
anthropic: {
|
|
195
|
+
betas: ["compact-2026-01-12"],
|
|
196
|
+
body: { context_management: { edits: [{ type: "compact_20260112" }] } },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Cache TTL per call: `options.cacheControl.ttl` (`"5m"` | `"1h"`) overrides the model's `promptCacheTtl` for that call.
|
|
148
204
|
|
|
149
205
|
## Prompt caching (`cache_control`)
|
|
150
206
|
|
|
@@ -182,11 +238,14 @@ await agent.execute("…", { cacheControl: { breakpoints: 1 } }); // cache the s
|
|
|
182
238
|
|
|
183
239
|
- `content_block_delta` text → `{ type: "delta", content }`
|
|
184
240
|
- `tool_use` blocks accumulate `input_json_delta` fragments and emit one consolidated `{ type: "tool-call", ... }` at `content_block_stop` (no partial-arg `{}` artifact — input is parsed once, whole)
|
|
185
|
-
-
|
|
241
|
+
- every content block is also assembled by index (text, thinking + signature, server-tool input, beta `compaction_delta`) and returned as `done.blocks` when the turn has a non-neutral block
|
|
242
|
+
- terminal `{ type: "done", finishReason, usage, blocks? }` — `usage.input` from `message_start`, `usage.output` from `message_delta`, `total` computed; `cachedTokens` / `cacheWriteTokens` from `message_start` then the cumulative `message_delta` counts (see [Tokens & usage accounting](#tokens--usage-accounting))
|
|
186
243
|
|
|
187
244
|
## Finish-reason mapping
|
|
188
245
|
|
|
189
|
-
`end_turn` / `stop_sequence` → `stop` · `max_tokens` → `length` · `
|
|
246
|
+
`end_turn` / `stop_sequence` / beta `compaction` → `stop` · `max_tokens` → `length` · `model_context_window_exceeded` → `context_exceeded` · `pause_turn` → `pause_turn` · `refusal` → `refusal` · `tool_use` → `tool_calls` · unknown / null → `error`.
|
|
247
|
+
|
|
248
|
+
The agent resumes `pause_turn` automatically (it replays the paused turn and calls again, within `maxTrips`). `refusal` and `context_exceeded` end the run like `stop`; check `report.trips.at(-1).finishReason` to fall back to another model or compact the history.
|
|
190
249
|
|
|
191
250
|
## Errors
|
|
192
251
|
|
|
@@ -203,11 +262,16 @@ Raw SDK errors are wrapped into the typed `@warlock.js/ai` `AIError` hierarchy.
|
|
|
203
262
|
|
|
204
263
|
```ts
|
|
205
264
|
await anthropic.count("some text"); // approximate heuristic, offline
|
|
265
|
+
|
|
266
|
+
const model = anthropic.model({ name: "claude-opus-5-5" }); // an AnthropicModel
|
|
267
|
+
await model.countTokens(messages, { tools }); // exact, via messages.countTokens (network)
|
|
206
268
|
```
|
|
207
269
|
|
|
270
|
+
`countTokens` sends the same system prompt, tools, thinking and `providerOptions.anthropic.betas` a real call would, keeping only the fields the endpoint accepts.
|
|
271
|
+
|
|
208
272
|
## No embeddings
|
|
209
273
|
|
|
210
|
-
Anthropic ships no first-party embeddings API, so `AnthropicSDK` intentionally does **not** implement `embedder()` (the `SDKAdapterContract.embedder` slot is optional). Use
|
|
274
|
+
Anthropic ships no first-party embeddings API, so `AnthropicSDK` intentionally does **not** implement `embedder()` (the `SDKAdapterContract.embedder` slot is optional). Use `@warlock.js/ai-openai` (or another embeddings-capable adapter; see the `setup-openai` topic of the `warlock-js-ai-openai` skill) for vectors.
|
|
211
275
|
|
|
212
276
|
## When NOT to use this skill
|
|
213
277
|
|
|
@@ -216,6 +280,6 @@ Anthropic ships no first-party embeddings API, so `AnthropicSDK` intentionally d
|
|
|
216
280
|
|
|
217
281
|
## See also
|
|
218
282
|
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
283
|
+
- The `run-ai-agent` topic of the `warlock-js-ai` skill
|
|
284
|
+
- The `pick-ai-provider` topic of the `warlock-js-ai` skill
|
|
285
|
+
- The `handle-ai-errors` topic of the `warlock-js-ai` skill
|