@warlock.js/ai-anthropic 5.30.0 → 5.32.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 +58 -6
- package/cjs/index.cjs +847 -138
- package/cjs/index.cjs.map +1 -1
- package/esm/config.type.d.mts +117 -8
- package/esm/config.type.d.mts.map +1 -1
- package/esm/index.d.mts +9 -3
- package/esm/index.mjs +7 -1
- package/esm/known-output-limits.mjs +46 -0
- package/esm/known-output-limits.mjs.map +1 -0
- package/esm/known-pricing.d.mts +63 -0
- package/esm/known-pricing.d.mts.map +1 -0
- package/esm/known-pricing.mjs +172 -0
- package/esm/known-pricing.mjs.map +1 -0
- package/esm/model.d.mts +90 -20
- package/esm/model.d.mts.map +1 -1
- package/esm/model.mjs +389 -88
- package/esm/model.mjs.map +1 -1
- package/esm/request.type.d.mts +23 -0
- package/esm/request.type.d.mts.map +1 -0
- package/esm/sdk.d.mts +1 -3
- package/esm/sdk.d.mts.map +1 -1
- package/esm/thinking-mode.d.mts +14 -2
- package/esm/thinking-mode.d.mts.map +1 -1
- package/esm/thinking-mode.mjs +102 -2
- package/esm/thinking-mode.mjs.map +1 -1
- package/esm/utils/cache-usage.mjs +62 -0
- package/esm/utils/cache-usage.mjs.map +1 -0
- package/esm/utils/index.d.mts +6 -0
- package/esm/utils/index.mjs +1 -1
- package/esm/utils/map-stop-reason.d.mts +1 -0
- package/esm/utils/provider-blocks.d.mts +37 -0
- package/esm/utils/provider-blocks.d.mts.map +1 -0
- package/esm/utils/provider-blocks.mjs +3 -3
- package/esm/utils/provider-blocks.mjs.map +1 -1
- package/esm/utils/server-tool-usage.mjs +17 -0
- package/esm/utils/server-tool-usage.mjs.map +1 -0
- package/esm/utils/to-anthropic-messages.d.mts +69 -0
- package/esm/utils/to-anthropic-messages.d.mts.map +1 -0
- package/esm/utils/to-anthropic-messages.mjs +12 -4
- package/esm/utils/to-anthropic-messages.mjs.map +1 -1
- package/esm/utils/to-anthropic-tools.d.mts +17 -0
- package/esm/utils/to-anthropic-tools.d.mts.map +1 -0
- package/esm/utils/wrap-anthropic-error.d.mts +1 -0
- package/esm/utils/wrap-anthropic-error.mjs +6 -1
- package/esm/utils/wrap-anthropic-error.mjs.map +1 -1
- package/llms-full.txt +248 -32
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/skills/README.md +1 -1
- package/skills/setup-anthropic/SKILL.md +248 -32
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region ../../packages/ai-anthropic/src/utils/cache-usage.ts
|
|
2
|
+
/**
|
|
3
|
+
* Copy Anthropic's cache counters onto a neutral `Usage`.
|
|
4
|
+
*
|
|
5
|
+
* - `cache_read_input_tokens` → `cachedTokens`
|
|
6
|
+
* - `cache_creation_input_tokens` → `cacheWriteTokens`
|
|
7
|
+
* - `cache_creation.ephemeral_1h_input_tokens` → `cacheWrite1hTokens`
|
|
8
|
+
* (the 1-hour writes, billed at 2x input instead of 1.25x)
|
|
9
|
+
*
|
|
10
|
+
* When Anthropic omits the per-lifetime breakdown, `requestTtl` decides:
|
|
11
|
+
* a request whose every cache marker asked for `"1h"` wrote only 1-hour
|
|
12
|
+
* entries. Counters are set only when positive, so an absent channel
|
|
13
|
+
* stays undefined. Anthropic's `input_tokens` excludes all of these, so
|
|
14
|
+
* the caller marks the usage `inputIncludesCache: false`.
|
|
15
|
+
*/
|
|
16
|
+
function applyAnthropicCacheUsage(usage, raw, requestTtl) {
|
|
17
|
+
const cacheRead = raw.cache_read_input_tokens;
|
|
18
|
+
if (typeof cacheRead === "number" && cacheRead > 0) usage.cachedTokens = cacheRead;
|
|
19
|
+
const cacheWrite = raw.cache_creation_input_tokens;
|
|
20
|
+
if (typeof cacheWrite !== "number" || cacheWrite <= 0) return;
|
|
21
|
+
usage.cacheWriteTokens = cacheWrite;
|
|
22
|
+
const reported1h = raw.cache_creation?.ephemeral_1h_input_tokens;
|
|
23
|
+
const writes1h = typeof reported1h === "number" ? reported1h : requestTtl === "1h" ? cacheWrite : 0;
|
|
24
|
+
if (writes1h > 0) usage.cacheWrite1hTokens = Math.min(writes1h, cacheWrite);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Scan a request body (top level, system, tools, messages) for
|
|
28
|
+
* `cache_control` markers and report the lifetime they request. Used only
|
|
29
|
+
* as a fallback when the response lacks Anthropic's per-lifetime
|
|
30
|
+
* cache-write breakdown.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* requestCacheTtl({ cache_control: { type: "ephemeral", ttl: "1h" } }); // "1h"
|
|
34
|
+
*/
|
|
35
|
+
function requestCacheTtl(body) {
|
|
36
|
+
let found;
|
|
37
|
+
const visit = (value, depth) => {
|
|
38
|
+
if (depth > 8 || typeof value !== "object" || value === null) return;
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
for (const item of value) visit(item, depth + 1);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const record = value;
|
|
44
|
+
const marker = record.cache_control;
|
|
45
|
+
if (typeof marker === "object" && marker !== null) {
|
|
46
|
+
const ttl = marker.ttl === "1h" ? "1h" : "5m";
|
|
47
|
+
found = found === void 0 || found === ttl ? ttl : "mixed";
|
|
48
|
+
}
|
|
49
|
+
for (const key of [
|
|
50
|
+
"system",
|
|
51
|
+
"tools",
|
|
52
|
+
"messages",
|
|
53
|
+
"content"
|
|
54
|
+
]) if (key in record) visit(record[key], depth + 1);
|
|
55
|
+
};
|
|
56
|
+
visit(body, 0);
|
|
57
|
+
return found;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { applyAnthropicCacheUsage, requestCacheTtl };
|
|
62
|
+
//# sourceMappingURL=cache-usage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-usage.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/cache-usage.ts"],"sourcesContent":["import type { Usage } from \"@warlock.js/ai\";\n\n/** The cache-related fields of an Anthropic usage block (final or streamed). */\nexport type AnthropicCacheUsage = {\n cache_read_input_tokens?: number | null;\n cache_creation_input_tokens?: number | null;\n cache_creation?: {\n ephemeral_1h_input_tokens?: number | null;\n ephemeral_5m_input_tokens?: number | null;\n } | null;\n};\n\n/**\n * Copy Anthropic's cache counters onto a neutral `Usage`.\n *\n * - `cache_read_input_tokens` → `cachedTokens`\n * - `cache_creation_input_tokens` → `cacheWriteTokens`\n * - `cache_creation.ephemeral_1h_input_tokens` → `cacheWrite1hTokens`\n * (the 1-hour writes, billed at 2x input instead of 1.25x)\n *\n * When Anthropic omits the per-lifetime breakdown, `requestTtl` decides:\n * a request whose every cache marker asked for `\"1h\"` wrote only 1-hour\n * entries. Counters are set only when positive, so an absent channel\n * stays undefined. Anthropic's `input_tokens` excludes all of these, so\n * the caller marks the usage `inputIncludesCache: false`.\n */\nexport function applyAnthropicCacheUsage(\n usage: Usage,\n raw: AnthropicCacheUsage,\n requestTtl: RequestCacheTtl,\n): void {\n const cacheRead = raw.cache_read_input_tokens;\n\n if (typeof cacheRead === \"number\" && cacheRead > 0) {\n usage.cachedTokens = cacheRead;\n }\n\n const cacheWrite = raw.cache_creation_input_tokens;\n\n if (typeof cacheWrite !== \"number\" || cacheWrite <= 0) {\n return;\n }\n\n usage.cacheWriteTokens = cacheWrite;\n\n const reported1h = raw.cache_creation?.ephemeral_1h_input_tokens;\n const writes1h =\n typeof reported1h === \"number\" ? reported1h : requestTtl === \"1h\" ? cacheWrite : 0;\n\n if (writes1h > 0) {\n usage.cacheWrite1hTokens = Math.min(writes1h, cacheWrite);\n }\n}\n\n/**\n * The cache lifetime a request body asked for: `\"1h\"` when every\n * `cache_control` marker in it says `ttl: \"1h\"`, `\"5m\"` when every marker\n * uses the default lifetime, `\"mixed\"` when both appear, and `undefined`\n * when the body carries no marker.\n */\nexport type RequestCacheTtl = \"1h\" | \"5m\" | \"mixed\" | undefined;\n\n/**\n * Scan a request body (top level, system, tools, messages) for\n * `cache_control` markers and report the lifetime they request. Used only\n * as a fallback when the response lacks Anthropic's per-lifetime\n * cache-write breakdown.\n *\n * @example\n * requestCacheTtl({ cache_control: { type: \"ephemeral\", ttl: \"1h\" } }); // \"1h\"\n */\nexport function requestCacheTtl(body: unknown): RequestCacheTtl {\n let found: RequestCacheTtl;\n\n const visit = (value: unknown, depth: number): void => {\n if (depth > 8 || typeof value !== \"object\" || value === null) {\n return;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n visit(item, depth + 1);\n }\n\n return;\n }\n\n const record = value as Record<string, unknown>;\n const marker = record.cache_control;\n\n if (typeof marker === \"object\" && marker !== null) {\n const ttl = (marker as { ttl?: unknown }).ttl === \"1h\" ? \"1h\" : \"5m\";\n found = found === undefined || found === ttl ? ttl : \"mixed\";\n }\n\n for (const key of [\"system\", \"tools\", \"messages\", \"content\"] as const) {\n if (key in record) {\n visit(record[key], depth + 1);\n }\n }\n };\n\n visit(body, 0);\n\n return found;\n}\n"],"mappings":";;;;;;;;;;;;;;;AA0BA,SAAgB,yBACd,OACA,KACA,YACM;CACN,MAAM,YAAY,IAAI;CAEtB,IAAI,OAAO,cAAc,YAAY,YAAY,GAC/C,MAAM,eAAe;CAGvB,MAAM,aAAa,IAAI;CAEvB,IAAI,OAAO,eAAe,YAAY,cAAc,GAClD;CAGF,MAAM,mBAAmB;CAEzB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,WACJ,OAAO,eAAe,WAAW,aAAa,eAAe,OAAO,aAAa;CAEnF,IAAI,WAAW,GACb,MAAM,qBAAqB,KAAK,IAAI,UAAU,UAAU;AAE5D;;;;;;;;;;AAmBA,SAAgB,gBAAgB,MAAgC;CAC9D,IAAI;CAEJ,MAAM,SAAS,OAAgB,UAAwB;EACrD,IAAI,QAAQ,KAAK,OAAO,UAAU,YAAY,UAAU,MACtD;EAGF,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,KAAK,MAAM,QAAQ,OACjB,MAAM,MAAM,QAAQ,CAAC;GAGvB;EACF;EAEA,MAAM,SAAS;EACf,MAAM,SAAS,OAAO;EAEtB,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;GACjD,MAAM,MAAO,OAA6B,QAAQ,OAAO,OAAO;GAChE,QAAQ,UAAU,UAAa,UAAU,MAAM,MAAM;EACvD;EAEA,KAAK,MAAM,OAAO;GAAC;GAAU;GAAS;GAAY;EAAS,GACzD,IAAI,OAAO,QACT,MAAM,OAAO,MAAM,QAAQ,CAAC;CAGlC;CAEA,MAAM,MAAM,CAAC;CAEb,OAAO;AACT"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import "./map-stop-reason.mjs";
|
|
2
|
+
import { ANTHROPIC_BLOCK_FORMAT, fromProviderBlocks, toProviderBlocks } from "./provider-blocks.mjs";
|
|
3
|
+
import { AnthropicMessages, ToAnthropicMessagesOptions, toAnthropicMessages } from "./to-anthropic-messages.mjs";
|
|
4
|
+
import { toAnthropicTools } from "./to-anthropic-tools.mjs";
|
|
5
|
+
import "./wrap-anthropic-error.mjs";
|
|
6
|
+
export { ANTHROPIC_BLOCK_FORMAT, type AnthropicMessages, type ToAnthropicMessagesOptions, fromProviderBlocks, toAnthropicMessages, toAnthropicTools, toProviderBlocks };
|
package/esm/utils/index.mjs
CHANGED
|
@@ -4,4 +4,4 @@ import { toAnthropicMessages } from "./to-anthropic-messages.mjs";
|
|
|
4
4
|
import { toAnthropicTools } from "./to-anthropic-tools.mjs";
|
|
5
5
|
import { wrapAnthropicError } from "./wrap-anthropic-error.mjs";
|
|
6
6
|
|
|
7
|
-
export {
|
|
7
|
+
export { ANTHROPIC_BLOCK_FORMAT, fromProviderBlocks, mapStopReason, toAnthropicMessages, toAnthropicTools, toProviderBlocks, wrapAnthropicError };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@warlock.js/ai";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { ModelToolCallRequest, ProviderContentBlock } from "@warlock.js/ai";
|
|
3
|
+
//#region ../../packages/ai-anthropic/src/utils/provider-blocks.d.ts
|
|
4
|
+
/** The `ProviderContentBlock.provider` value this adapter writes and replays. */
|
|
5
|
+
export declare const ANTHROPIC_BLOCK_FORMAT = "anthropic";
|
|
6
|
+
/**
|
|
7
|
+
* Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or
|
|
8
|
+
* return `undefined` when the neutral fields already carry the whole turn
|
|
9
|
+
* (only plain text and `tool_use` blocks). Any other block — thinking,
|
|
10
|
+
* redacted thinking, compaction, server-tool use/results, cited text —
|
|
11
|
+
* makes the full ordered list worth keeping so the next request can
|
|
12
|
+
* replay the turn verbatim.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* toProviderBlocks([{ type: "text", text: "hi", citations: null }]); // undefined
|
|
16
|
+
* toProviderBlocks([{ type: "thinking", thinking: "", signature: "s" }, { type: "text", text: "hi" }]);
|
|
17
|
+
* // [{ provider: "anthropic", type: "thinking", data: {...} }, { provider: "anthropic", type: "text", data: {...} }]
|
|
18
|
+
*/
|
|
19
|
+
export declare function toProviderBlocks(content: ReadonlyArray<{
|
|
20
|
+
type: string;
|
|
21
|
+
}>): ProviderContentBlock[] | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Rebuild an assistant turn's Anthropic content from stored blocks.
|
|
24
|
+
* Returns `undefined` — so the caller falls back to `content` +
|
|
25
|
+
* `toolCalls` — when there are no blocks, when any block belongs to
|
|
26
|
+
* another provider's format, when a block is not a well-formed
|
|
27
|
+
* assistant-turn block (its `data.type` must equal its `type`, and the
|
|
28
|
+
* type must be one Anthropic returns on an assistant turn), or when the
|
|
29
|
+
* blocks' `tool_use` ids differ from the message's `toolCalls` (the turn
|
|
30
|
+
* was edited after the response, e.g. by a middleware or a recovered
|
|
31
|
+
* stream call, so a verbatim replay would no longer match the tool
|
|
32
|
+
* results that follow). The checks matter when a transcript comes from
|
|
33
|
+
* outside the process (a client, a store): the block data is sent as is.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fromProviderBlocks(blocks: ProviderContentBlock[] | undefined, toolCalls: ModelToolCallRequest[] | undefined): Anthropic.ContentBlockParam[] | undefined;
|
|
36
|
+
//#endregion
|
|
37
|
+
//# sourceMappingURL=provider-blocks.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider-blocks.d.mts","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/provider-blocks.ts"],"mappings":";;;;qBAIa;;;;;;;;;;;;;;wBA0CG,iBACd,SAAS;EAAgB;KACxB;;;;;;;;;;;;;;wBAyBa,mBACd,QAAQ,oCACR,WAAW,qCACV,UAAU"}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/** The `ProviderContentBlock.provider` value this adapter writes and replays. */
|
|
3
3
|
const ANTHROPIC_BLOCK_FORMAT = "anthropic";
|
|
4
4
|
/** Block types the neutral `content` + `toolCalls` fields already carry losslessly. */
|
|
5
|
-
const NEUTRAL_BLOCK_TYPES = new Set(["text", "tool_use"]);
|
|
5
|
+
const NEUTRAL_BLOCK_TYPES = /* @__PURE__ */ new Set(["text", "tool_use"]);
|
|
6
6
|
/** Block types that carry a model-requested call whose id must match `toolCalls`. */
|
|
7
|
-
const TOOL_USE_BLOCK_TYPES = new Set(["tool_use"]);
|
|
7
|
+
const TOOL_USE_BLOCK_TYPES = /* @__PURE__ */ new Set(["tool_use"]);
|
|
8
8
|
/**
|
|
9
9
|
* Assistant-turn block types that may be sent back verbatim. Server-tool
|
|
10
10
|
* results all end in `_tool_result` (`web_search_tool_result`,
|
|
@@ -12,7 +12,7 @@ const TOOL_USE_BLOCK_TYPES = new Set(["tool_use"]);
|
|
|
12
12
|
* else (an unknown or user-side type such as `image`, `document` or a
|
|
13
13
|
* client `tool_result`) is not replayed.
|
|
14
14
|
*/
|
|
15
|
-
const REPLAYABLE_BLOCK_TYPES = new Set([
|
|
15
|
+
const REPLAYABLE_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
16
16
|
"text",
|
|
17
17
|
"thinking",
|
|
18
18
|
"redacted_thinking",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider-blocks.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/provider-blocks.ts"],"sourcesContent":["import type { ModelToolCallRequest, ProviderContentBlock } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/** The `ProviderContentBlock.provider` value this adapter writes and replays. */\nexport const ANTHROPIC_BLOCK_FORMAT = \"anthropic\";\n\n/** Block types the neutral `content` + `toolCalls` fields already carry losslessly. */\nconst NEUTRAL_BLOCK_TYPES = new Set([\"text\", \"tool_use\"]);\n\n/** Block types that carry a model-requested call whose id must match `toolCalls`. */\nconst TOOL_USE_BLOCK_TYPES = new Set([\"tool_use\"]);\n\n/**\n * Assistant-turn block types that may be sent back verbatim. Server-tool\n * results all end in `_tool_result` (`web_search_tool_result`,\n * `code_execution_tool_result`, …) and are matched by suffix; anything\n * else (an unknown or user-side type such as `image`, `document` or a\n * client `tool_result`) is not replayed.\n */\nconst REPLAYABLE_BLOCK_TYPES: ReadonlySet<string> = new Set([\n \"text\",\n \"thinking\",\n \"redacted_thinking\",\n \"tool_use\",\n \"server_tool_use\",\n \"mcp_tool_use\",\n \"mcp_tool_result\",\n \"container_upload\",\n \"compaction\",\n]);\n\nconst SERVER_TOOL_RESULT_TYPE = /^[a-z][a-z0-9_]*_tool_result$/;\n\n/**\n * Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or\n * return `undefined` when the neutral fields already carry the whole turn\n * (only plain text and `tool_use` blocks). Any other block — thinking,\n * redacted thinking, compaction, server-tool use/results, cited text —\n * makes the full ordered list worth keeping so the next request can\n * replay the turn verbatim.\n *\n * @example\n * toProviderBlocks([{ type: \"text\", text: \"hi\", citations: null }]); // undefined\n * toProviderBlocks([{ type: \"thinking\", thinking: \"\", signature: \"s\" }, { type: \"text\", text: \"hi\" }]);\n * // [{ provider: \"anthropic\", type: \"thinking\", data: {...} }, { provider: \"anthropic\", type: \"text\", data: {...} }]\n */\nexport function toProviderBlocks(\n content: ReadonlyArray<{ type: string }>,\n): ProviderContentBlock[] | undefined {\n if (!content.some(needsVerbatimReplay)) {\n return undefined;\n }\n\n return content.map((block) => ({\n provider: ANTHROPIC_BLOCK_FORMAT,\n type: block.type,\n data: { ...(block as Record<string, unknown>) },\n }));\n}\n\n/**\n * Rebuild an assistant turn's Anthropic content from stored blocks.\n * Returns `undefined` — so the caller falls back to `content` +\n * `toolCalls` — when there are no blocks, when any block belongs to\n * another provider's format, when a block is not a well-formed\n * assistant-turn block (its `data.type` must equal its `type`, and the\n * type must be one Anthropic returns on an assistant turn), or when the\n * blocks' `tool_use` ids differ from the message's `toolCalls` (the turn\n * was edited after the response, e.g. by a middleware or a recovered\n * stream call, so a verbatim replay would no longer match the tool\n * results that follow). The checks matter when a transcript comes from\n * outside the process (a client, a store): the block data is sent as is.\n */\nexport function fromProviderBlocks(\n blocks: ProviderContentBlock[] | undefined,\n toolCalls: ModelToolCallRequest[] | undefined,\n): Anthropic.ContentBlockParam[] | undefined {\n if (!blocks || blocks.length === 0) {\n return undefined;\n }\n\n if (!blocks.every((block) => block.provider === ANTHROPIC_BLOCK_FORMAT && isReplayable(block))) {\n return undefined;\n }\n\n const blockToolIds = blocks\n .filter((block) => TOOL_USE_BLOCK_TYPES.has(block.type))\n .map((block) => block.data.id);\n const requestedIds = (toolCalls ?? []).map((toolCall) => toolCall.id);\n\n if (\n blockToolIds.length !== requestedIds.length ||\n blockToolIds.some((id, index) => id !== requestedIds[index])\n ) {\n return undefined;\n }\n\n return blocks.map((block) => ({ ...block.data }) as unknown as Anthropic.ContentBlockParam);\n}\n\nfunction needsVerbatimReplay(block: { type: string }): boolean {\n if (!NEUTRAL_BLOCK_TYPES.has(block.type)) {\n return true;\n }\n\n const citations = (block as { citations?: unknown }).citations;\n\n return Array.isArray(citations) && citations.length > 0;\n}\n\nfunction isReplayable(block: ProviderContentBlock): boolean {\n const data = block.data as unknown;\n\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return false;\n }\n\n if ((data as { type?: unknown }).type !== block.type) {\n return false;\n }\n\n return REPLAYABLE_BLOCK_TYPES.has(block.type) || SERVER_TOOL_RESULT_TYPE.test(block.type);\n}\n"],"mappings":";;AAIA,MAAa,yBAAyB;;AAGtC,MAAM,
|
|
1
|
+
{"version":3,"file":"provider-blocks.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/provider-blocks.ts"],"sourcesContent":["import type { ModelToolCallRequest, ProviderContentBlock } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/** The `ProviderContentBlock.provider` value this adapter writes and replays. */\nexport const ANTHROPIC_BLOCK_FORMAT = \"anthropic\";\n\n/** Block types the neutral `content` + `toolCalls` fields already carry losslessly. */\nconst NEUTRAL_BLOCK_TYPES = new Set([\"text\", \"tool_use\"]);\n\n/** Block types that carry a model-requested call whose id must match `toolCalls`. */\nconst TOOL_USE_BLOCK_TYPES = new Set([\"tool_use\"]);\n\n/**\n * Assistant-turn block types that may be sent back verbatim. Server-tool\n * results all end in `_tool_result` (`web_search_tool_result`,\n * `code_execution_tool_result`, …) and are matched by suffix; anything\n * else (an unknown or user-side type such as `image`, `document` or a\n * client `tool_result`) is not replayed.\n */\nconst REPLAYABLE_BLOCK_TYPES: ReadonlySet<string> = new Set([\n \"text\",\n \"thinking\",\n \"redacted_thinking\",\n \"tool_use\",\n \"server_tool_use\",\n \"mcp_tool_use\",\n \"mcp_tool_result\",\n \"container_upload\",\n \"compaction\",\n]);\n\nconst SERVER_TOOL_RESULT_TYPE = /^[a-z][a-z0-9_]*_tool_result$/;\n\n/**\n * Wrap an assistant turn's content blocks as `ProviderContentBlock`s, or\n * return `undefined` when the neutral fields already carry the whole turn\n * (only plain text and `tool_use` blocks). Any other block — thinking,\n * redacted thinking, compaction, server-tool use/results, cited text —\n * makes the full ordered list worth keeping so the next request can\n * replay the turn verbatim.\n *\n * @example\n * toProviderBlocks([{ type: \"text\", text: \"hi\", citations: null }]); // undefined\n * toProviderBlocks([{ type: \"thinking\", thinking: \"\", signature: \"s\" }, { type: \"text\", text: \"hi\" }]);\n * // [{ provider: \"anthropic\", type: \"thinking\", data: {...} }, { provider: \"anthropic\", type: \"text\", data: {...} }]\n */\nexport function toProviderBlocks(\n content: ReadonlyArray<{ type: string }>,\n): ProviderContentBlock[] | undefined {\n if (!content.some(needsVerbatimReplay)) {\n return undefined;\n }\n\n return content.map((block) => ({\n provider: ANTHROPIC_BLOCK_FORMAT,\n type: block.type,\n data: { ...(block as Record<string, unknown>) },\n }));\n}\n\n/**\n * Rebuild an assistant turn's Anthropic content from stored blocks.\n * Returns `undefined` — so the caller falls back to `content` +\n * `toolCalls` — when there are no blocks, when any block belongs to\n * another provider's format, when a block is not a well-formed\n * assistant-turn block (its `data.type` must equal its `type`, and the\n * type must be one Anthropic returns on an assistant turn), or when the\n * blocks' `tool_use` ids differ from the message's `toolCalls` (the turn\n * was edited after the response, e.g. by a middleware or a recovered\n * stream call, so a verbatim replay would no longer match the tool\n * results that follow). The checks matter when a transcript comes from\n * outside the process (a client, a store): the block data is sent as is.\n */\nexport function fromProviderBlocks(\n blocks: ProviderContentBlock[] | undefined,\n toolCalls: ModelToolCallRequest[] | undefined,\n): Anthropic.ContentBlockParam[] | undefined {\n if (!blocks || blocks.length === 0) {\n return undefined;\n }\n\n if (!blocks.every((block) => block.provider === ANTHROPIC_BLOCK_FORMAT && isReplayable(block))) {\n return undefined;\n }\n\n const blockToolIds = blocks\n .filter((block) => TOOL_USE_BLOCK_TYPES.has(block.type))\n .map((block) => block.data.id);\n const requestedIds = (toolCalls ?? []).map((toolCall) => toolCall.id);\n\n if (\n blockToolIds.length !== requestedIds.length ||\n blockToolIds.some((id, index) => id !== requestedIds[index])\n ) {\n return undefined;\n }\n\n return blocks.map((block) => ({ ...block.data }) as unknown as Anthropic.ContentBlockParam);\n}\n\nfunction needsVerbatimReplay(block: { type: string }): boolean {\n if (!NEUTRAL_BLOCK_TYPES.has(block.type)) {\n return true;\n }\n\n const citations = (block as { citations?: unknown }).citations;\n\n return Array.isArray(citations) && citations.length > 0;\n}\n\nfunction isReplayable(block: ProviderContentBlock): boolean {\n const data = block.data as unknown;\n\n if (data === null || typeof data !== \"object\" || Array.isArray(data)) {\n return false;\n }\n\n if ((data as { type?: unknown }).type !== block.type) {\n return false;\n }\n\n return REPLAYABLE_BLOCK_TYPES.has(block.type) || SERVER_TOOL_RESULT_TYPE.test(block.type);\n}\n"],"mappings":";;AAIA,MAAa,yBAAyB;;AAGtC,MAAM,sCAAsB,IAAI,IAAI,CAAC,QAAQ,UAAU,CAAC;;AAGxD,MAAM,uCAAuB,IAAI,IAAI,CAAC,UAAU,CAAC;;;;;;;;AASjD,MAAM,yCAA8C,IAAI,IAAI;CAC1D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,0BAA0B;;;;;;;;;;;;;;AAehC,SAAgB,iBACd,SACoC;CACpC,IAAI,CAAC,QAAQ,KAAK,mBAAmB,GACnC;CAGF,OAAO,QAAQ,KAAK,WAAW;EAC7B,UAAU;EACV,MAAM,MAAM;EACZ,MAAM,EAAE,GAAI,MAAkC;CAChD,EAAE;AACJ;;;;;;;;;;;;;;AAeA,SAAgB,mBACd,QACA,WAC2C;CAC3C,IAAI,CAAC,UAAU,OAAO,WAAW,GAC/B;CAGF,IAAI,CAAC,OAAO,OAAO,UAAU,MAAM,4BAAuC,aAAa,KAAK,CAAC,GAC3F;CAGF,MAAM,eAAe,OAClB,QAAQ,UAAU,qBAAqB,IAAI,MAAM,IAAI,CAAC,CAAC,CACvD,KAAK,UAAU,MAAM,KAAK,EAAE;CAC/B,MAAM,gBAAgB,aAAa,CAAC,EAAC,CAAE,KAAK,aAAa,SAAS,EAAE;CAEpE,IACE,aAAa,WAAW,aAAa,UACrC,aAAa,MAAM,IAAI,UAAU,OAAO,aAAa,MAAM,GAE3D;CAGF,OAAO,OAAO,KAAK,WAAW,EAAE,GAAG,MAAM,KAAK,EAA4C;AAC5F;AAEA,SAAS,oBAAoB,OAAkC;CAC7D,IAAI,CAAC,oBAAoB,IAAI,MAAM,IAAI,GACrC,OAAO;CAGT,MAAM,YAAa,MAAkC;CAErD,OAAO,MAAM,QAAQ,SAAS,KAAK,UAAU,SAAS;AACxD;AAEA,SAAS,aAAa,OAAsC;CAC1D,MAAM,OAAO,MAAM;CAEnB,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACjE,OAAO;CAGT,IAAK,KAA4B,SAAS,MAAM,MAC9C,OAAO;CAGT,OAAO,uBAAuB,IAAI,MAAM,IAAI,KAAK,wBAAwB,KAAK,MAAM,IAAI;AAC1F"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region ../../packages/ai-anthropic/src/utils/server-tool-usage.ts
|
|
2
|
+
/** Installed @anthropic-ai/sdk 0.129.0 ServerToolUsage and MessageDeltaUsage use the same cumulative counters.
|
|
3
|
+
* https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool
|
|
4
|
+
* https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool
|
|
5
|
+
* No code-execution request counter is reported: never count content blocks instead. */
|
|
6
|
+
function applyServerToolUsage(usage, raw) {
|
|
7
|
+
if (!raw) return;
|
|
8
|
+
usage.serverToolUse = {
|
|
9
|
+
...usage.serverToolUse,
|
|
10
|
+
...raw.web_search_requests !== void 0 ? { webSearchRequests: raw.web_search_requests } : {},
|
|
11
|
+
...raw.web_fetch_requests !== void 0 ? { webFetchRequests: raw.web_fetch_requests } : {}
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { applyServerToolUsage };
|
|
17
|
+
//# sourceMappingURL=server-tool-usage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-tool-usage.mjs","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/server-tool-usage.ts"],"sourcesContent":["import type Anthropic from \"@anthropic-ai/sdk\";\nimport type { Usage } from \"@warlock.js/ai\";\n\n/** Installed @anthropic-ai/sdk 0.129.0 ServerToolUsage and MessageDeltaUsage use the same cumulative counters.\n * https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool\n * https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-fetch-tool\n * No code-execution request counter is reported: never count content blocks instead. */\nexport function applyServerToolUsage(\n usage: Usage,\n raw: Partial<Anthropic.ServerToolUsage> | null | undefined,\n): void {\n if (!raw) return;\n usage.serverToolUse = {\n ...usage.serverToolUse,\n ...(raw.web_search_requests !== undefined\n ? { webSearchRequests: raw.web_search_requests }\n : {}),\n ...(raw.web_fetch_requests !== undefined ? { webFetchRequests: raw.web_fetch_requests } : {}),\n };\n}\n"],"mappings":";;;;;AAOA,SAAgB,qBACd,OACA,KACM;CACN,IAAI,CAAC,KAAK;CACV,MAAM,gBAAgB;EACpB,GAAG,MAAM;EACT,GAAI,IAAI,wBAAwB,SAC5B,EAAE,mBAAmB,IAAI,oBAAoB,IAC7C,CAAC;EACL,GAAI,IAAI,uBAAuB,SAAY,EAAE,kBAAkB,IAAI,mBAAmB,IAAI,CAAC;CAC7F;AACF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { Message } from "@warlock.js/ai";
|
|
3
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-messages.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Result of splitting a vendor-neutral `Message[]` for the Anthropic
|
|
6
|
+
* Messages API: the system prompt is hoisted to a top-level `system`
|
|
7
|
+
* string (Anthropic has no `"system"` role inside `messages`), and the
|
|
8
|
+
* remaining turns are mapped to `MessageParam[]`.
|
|
9
|
+
*/
|
|
10
|
+
export type AnthropicMessages = {
|
|
11
|
+
system: string | undefined;
|
|
12
|
+
messages: Anthropic.MessageParam[];
|
|
13
|
+
};
|
|
14
|
+
/** Options for {@link toAnthropicMessages}. */
|
|
15
|
+
export type ToAnthropicMessagesOptions = {
|
|
16
|
+
/**
|
|
17
|
+
* Leave out the `thinking` / `redacted_thinking` blocks of assistant turns
|
|
18
|
+
* marked `compaction: "retained"` — turns kept verbatim after a
|
|
19
|
+
* client-written summary. On models that run the preserved-thinking
|
|
20
|
+
* conversation check those blocks were produced against the replaced
|
|
21
|
+
* history, so replaying them is a 400 (or a server-side drop). Text and
|
|
22
|
+
* tool-use blocks stay. Removing them is the no-beta path Anthropic
|
|
23
|
+
* documents for compaction that keeps recent turns.
|
|
24
|
+
*/
|
|
25
|
+
dropRetainedThinking?: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Convert vendor-neutral `Message[]` into Anthropic's request shape.
|
|
29
|
+
*
|
|
30
|
+
* Anthropic differs from the OpenAI Chat protocol in three ways this
|
|
31
|
+
* function absorbs:
|
|
32
|
+
*
|
|
33
|
+
* 1. **No `system` role.** System messages are concatenated (newline-
|
|
34
|
+
* separated) and returned separately as the top-level `system`
|
|
35
|
+
* parameter.
|
|
36
|
+
* 2. **Tool results are `user` turns.** A neutral `tool` message becomes
|
|
37
|
+
* a `tool_result` block keyed by `tool_use_id`. Consecutive tool
|
|
38
|
+
* messages (the results of one parallel tool-use turn) are grouped
|
|
39
|
+
* into a single `user` message, as Anthropic expects — splitting them
|
|
40
|
+
* across messages teaches the model to stop calling tools in parallel.
|
|
41
|
+
* 3. **Tool calls are `tool_use` content blocks.** An assistant message
|
|
42
|
+
* carrying `toolCalls` becomes an `assistant` message whose content
|
|
43
|
+
* is an optional leading `text` block followed by one `tool_use`
|
|
44
|
+
* block per call.
|
|
45
|
+
*
|
|
46
|
+
* An assistant message that carries Anthropic `blocks` (thinking,
|
|
47
|
+
* redacted thinking, compaction, server-tool blocks — see
|
|
48
|
+
* `ProviderContentBlock`) is replayed verbatim from those blocks, which
|
|
49
|
+
* keeps signed thinking and compaction state valid. Blocks from another
|
|
50
|
+
* provider, or blocks whose `tool_use` ids no longer match `toolCalls`,
|
|
51
|
+
* are ignored and the turn is rebuilt from `content` + `toolCalls`.
|
|
52
|
+
*
|
|
53
|
+
* With `dropRetainedThinking`, a turn marked `compaction: "retained"` is
|
|
54
|
+
* replayed without its thinking blocks (see {@link ToAnthropicMessagesOptions}).
|
|
55
|
+
*
|
|
56
|
+
* Other consecutive same-role turns are left as-is — the Messages API
|
|
57
|
+
* merges them server-side.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const { system, messages } = toAnthropicMessages([
|
|
61
|
+
* { role: "system", content: "Be concise." },
|
|
62
|
+
* { role: "user", content: "Hi" },
|
|
63
|
+
* ]);
|
|
64
|
+
* // system === "Be concise."
|
|
65
|
+
* // messages === [{ role: "user", content: "Hi" }]
|
|
66
|
+
*/
|
|
67
|
+
export declare function toAnthropicMessages(messages: Message[], options?: ToAnthropicMessagesOptions): AnthropicMessages;
|
|
68
|
+
//#endregion
|
|
69
|
+
//# sourceMappingURL=to-anthropic-messages.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-anthropic-messages.d.mts","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/to-anthropic-messages.ts"],"mappings":";;;;;;;;;YAUY;EACV;EACA,UAAU,UAAU;;;YAIV;;;;;;;;;;EAUV;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;wBA+Cc,oBACd,UAAU,WACV,UAAS,6BACR"}
|
|
@@ -27,6 +27,9 @@ import { fromProviderBlocks } from "./provider-blocks.mjs";
|
|
|
27
27
|
* provider, or blocks whose `tool_use` ids no longer match `toolCalls`,
|
|
28
28
|
* are ignored and the turn is rebuilt from `content` + `toolCalls`.
|
|
29
29
|
*
|
|
30
|
+
* With `dropRetainedThinking`, a turn marked `compaction: "retained"` is
|
|
31
|
+
* replayed without its thinking blocks (see {@link ToAnthropicMessagesOptions}).
|
|
32
|
+
*
|
|
30
33
|
* Other consecutive same-role turns are left as-is — the Messages API
|
|
31
34
|
* merges them server-side.
|
|
32
35
|
*
|
|
@@ -38,7 +41,7 @@ import { fromProviderBlocks } from "./provider-blocks.mjs";
|
|
|
38
41
|
* // system === "Be concise."
|
|
39
42
|
* // messages === [{ role: "user", content: "Hi" }]
|
|
40
43
|
*/
|
|
41
|
-
function toAnthropicMessages(messages) {
|
|
44
|
+
function toAnthropicMessages(messages, options = {}) {
|
|
42
45
|
const systemParts = [];
|
|
43
46
|
const mapped = [];
|
|
44
47
|
for (const message of messages) {
|
|
@@ -60,18 +63,20 @@ function toAnthropicMessages(messages) {
|
|
|
60
63
|
});
|
|
61
64
|
continue;
|
|
62
65
|
}
|
|
66
|
+
const dropThinking = options.dropRetainedThinking === true && message.compaction === "retained";
|
|
63
67
|
if (message.role === "assistant") {
|
|
64
68
|
const replayed = fromProviderBlocks(message.blocks, message.toolCalls);
|
|
65
|
-
|
|
69
|
+
const content = dropThinking ? replayed?.filter((block) => !isThinkingBlock(block)) : replayed;
|
|
70
|
+
if (content && content.length > 0) {
|
|
66
71
|
mapped.push({
|
|
67
72
|
role: "assistant",
|
|
68
|
-
content
|
|
73
|
+
content
|
|
69
74
|
});
|
|
70
75
|
continue;
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
74
|
-
const blocks = extractThinkingBlocks(message.toolCalls);
|
|
79
|
+
const blocks = dropThinking ? [] : extractThinkingBlocks(message.toolCalls);
|
|
75
80
|
const text = stringifyContent(message.content);
|
|
76
81
|
if (text) blocks.push({
|
|
77
82
|
type: "text",
|
|
@@ -106,6 +111,9 @@ function toAnthropicMessages(messages) {
|
|
|
106
111
|
messages: mapped
|
|
107
112
|
};
|
|
108
113
|
}
|
|
114
|
+
function isThinkingBlock(block) {
|
|
115
|
+
return block.type === "thinking" || block.type === "redacted_thinking";
|
|
116
|
+
}
|
|
109
117
|
/**
|
|
110
118
|
* Recover signed extended-thinking blocks stored by this adapter on a tool
|
|
111
119
|
* request. Anthropic requires these blocks to be replayed before the
|
|
@@ -1 +1 @@
|
|
|
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
|
+
{"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\n/** Options for {@link toAnthropicMessages}. */\nexport type ToAnthropicMessagesOptions = {\n /**\n * Leave out the `thinking` / `redacted_thinking` blocks of assistant turns\n * marked `compaction: \"retained\"` — turns kept verbatim after a\n * client-written summary. On models that run the preserved-thinking\n * conversation check those blocks were produced against the replaced\n * history, so replaying them is a 400 (or a server-side drop). Text and\n * tool-use blocks stay. Removing them is the no-beta path Anthropic\n * documents for compaction that keeps recent turns.\n */\n dropRetainedThinking?: boolean;\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 * With `dropRetainedThinking`, a turn marked `compaction: \"retained\"` is\n * replayed without its thinking blocks (see {@link ToAnthropicMessagesOptions}).\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(\n messages: Message[],\n options: ToAnthropicMessagesOptions = {},\n): 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 const dropThinking = options.dropRetainedThinking === true && message.compaction === \"retained\";\n\n if (message.role === \"assistant\") {\n const replayed = fromProviderBlocks(message.blocks, message.toolCalls);\n const content = dropThinking\n ? replayed?.filter((block) => !isThinkingBlock(block))\n : replayed;\n\n if (content && content.length > 0) {\n mapped.push({ role: \"assistant\", content });\n\n continue;\n }\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: Anthropic.ContentBlockParam[] = dropThinking\n ? []\n : 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\nfunction isThinkingBlock(block: Anthropic.ContentBlockParam): boolean {\n return block.type === \"thinking\" || block.type === \"redacted_thinking\";\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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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { ToolConfig } from "@warlock.js/ai";
|
|
3
|
+
//#region ../../packages/ai-anthropic/src/utils/to-anthropic-tools.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.
|
|
6
|
+
* Uses the shared `extractJsonSchema` helper; Anthropic requires the
|
|
7
|
+
* input schema to be a JSON-Schema object, so a non-object extraction
|
|
8
|
+
* is coerced into an empty-object schema rather than rejected — the
|
|
9
|
+
* tool still registers and the model simply sees no parameters.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const tools = toAnthropicTools([weatherTool, calculatorTool]);
|
|
13
|
+
* await client.messages.create({ model, max_tokens, messages, tools });
|
|
14
|
+
*/
|
|
15
|
+
export declare function toAnthropicTools(tools: ToolConfig<unknown, unknown>[] | undefined): Anthropic.Tool[] | undefined;
|
|
16
|
+
//#endregion
|
|
17
|
+
//# sourceMappingURL=to-anthropic-tools.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"to-anthropic-tools.d.mts","names":[],"sources":["../../../../../../../../packages/ai-anthropic/src/utils/to-anthropic-tools.ts"],"mappings":";;;;;;;;;;;;;;wBAcgB,iBACd,OAAO,6CACN,UAAU"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { AIError } from "@warlock.js/ai";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { APIConnectionTimeoutError, APIError } from "@anthropic-ai/sdk";
|
|
2
|
-
import { AIError, ContextLengthExceededError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderRateLimitError, ProviderTimeoutError, QuotaExceededError } from "@warlock.js/ai";
|
|
2
|
+
import { AIError, ContextLengthExceededError, InvalidRequestError, ProviderAuthError, ProviderError, ProviderOverloadedError, ProviderRateLimitError, ProviderTimeoutError, QuotaExceededError } from "@warlock.js/ai";
|
|
3
3
|
|
|
4
4
|
//#region ../../packages/ai-anthropic/src/utils/wrap-anthropic-error.ts
|
|
5
5
|
/**
|
|
@@ -51,6 +51,11 @@ function wrapAnthropicError(thrown) {
|
|
|
51
51
|
context,
|
|
52
52
|
retryAfter: parseRetryAfter(shape.headers)
|
|
53
53
|
});
|
|
54
|
+
if (shape.type === "overloaded_error" || shape.status === 529) return new ProviderOverloadedError(message, {
|
|
55
|
+
cause: thrown,
|
|
56
|
+
context,
|
|
57
|
+
retryAfter: parseRetryAfter(shape.headers)
|
|
58
|
+
});
|
|
54
59
|
if (shape.type === "invalid_request_error" || isClientStatus(shape.status)) {
|
|
55
60
|
if (/prompt is too long/i.test(message)) return new ContextLengthExceededError(message, {
|
|
56
61
|
cause: thrown,
|
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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 ProviderOverloadedError,\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 // HTTP 529 / `overloaded_error` — also delivered as a mid-stream SSE\n // `error` event, where no status exists.\n if (shape.type === \"overloaded_error\" || shape.status === 529) {\n return new ProviderOverloadedError(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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"}
|