@warlock.js/ai-anthropic 4.1.1
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/cjs/index.cjs +785 -0
- package/cjs/index.cjs.map +1 -0
- package/esm/config.type.d.mts +72 -0
- package/esm/config.type.d.mts.map +1 -0
- package/esm/index.d.mts +3 -0
- package/esm/index.mjs +3 -0
- package/esm/known-vision-models.mjs +44 -0
- package/esm/known-vision-models.mjs.map +1 -0
- package/esm/model.mjs +303 -0
- package/esm/model.mjs.map +1 -0
- package/esm/sdk.d.mts +70 -0
- package/esm/sdk.d.mts.map +1 -0
- package/esm/sdk.mjs +85 -0
- package/esm/sdk.mjs.map +1 -0
- package/esm/utils/index.mjs +6 -0
- package/esm/utils/map-stop-reason.mjs +30 -0
- package/esm/utils/map-stop-reason.mjs.map +1 -0
- package/esm/utils/to-anthropic-messages.mjs +125 -0
- package/esm/utils/to-anthropic-messages.mjs.map +1 -0
- package/esm/utils/to-anthropic-tools.mjs +37 -0
- package/esm/utils/to-anthropic-tools.mjs.map +1 -0
- package/esm/utils/wrap-anthropic-error.mjs +158 -0
- package/esm/utils/wrap-anthropic-error.mjs.map +1 -0
- package/llms-full.txt +143 -0
- package/llms.txt +9 -0
- package/package.json +39 -0
- package/skills/README.md +9 -0
- package/skills/setup-anthropic/SKILL.md +133 -0
package/cjs/index.cjs
ADDED
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let _anthropic_ai_sdk = require("@anthropic-ai/sdk");
|
|
30
|
+
_anthropic_ai_sdk = __toESM(_anthropic_ai_sdk, 1);
|
|
31
|
+
let _warlock_js_ai = require("@warlock.js/ai");
|
|
32
|
+
let _warlock_js_logger = require("@warlock.js/logger");
|
|
33
|
+
|
|
34
|
+
//#region ../../@warlock.js/ai-anthropic/src/known-vision-models.ts
|
|
35
|
+
/**
|
|
36
|
+
* Model-name prefixes for Claude families that accept image input
|
|
37
|
+
* (vision) on the Messages API.
|
|
38
|
+
*
|
|
39
|
+
* Every Claude 3, Claude 3.5/3.7, and Claude 4 family model is
|
|
40
|
+
* multimodal, so the list covers both the dotted legacy naming
|
|
41
|
+
* (`claude-3-haiku-...`, `claude-3-5-sonnet-...`) and the current
|
|
42
|
+
* `claude-<tier>-4-*` naming (`claude-opus-4-7`, `claude-sonnet-4-6`,
|
|
43
|
+
* `claude-haiku-4-5`). Pre-3 families (`claude-2`, `claude-instant`)
|
|
44
|
+
* are text-only and intentionally absent.
|
|
45
|
+
*
|
|
46
|
+
* Matched as a prefix so dated variants (`claude-opus-4-20250514`) are
|
|
47
|
+
* covered without listing every release tag. Devs can always override
|
|
48
|
+
* per-model via `anthropic.model({ name, vision: true | false })` —
|
|
49
|
+
* explicit config wins over inference in either direction.
|
|
50
|
+
*/
|
|
51
|
+
const VISION_CAPABLE_PREFIXES = [
|
|
52
|
+
"claude-3",
|
|
53
|
+
"claude-4",
|
|
54
|
+
"claude-opus-4",
|
|
55
|
+
"claude-sonnet-4",
|
|
56
|
+
"claude-haiku-4"
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Infer whether a given Claude model name supports vision based on the
|
|
60
|
+
* known-prefix list. Unknown models default to `false` so that passing
|
|
61
|
+
* an image attachment to an unsupported model surfaces a clear,
|
|
62
|
+
* agent-side capability error instead of an opaque Anthropic 400.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* inferVisionCapability("claude-sonnet-4-6"); // → true
|
|
66
|
+
* inferVisionCapability("claude-3-5-sonnet-latest"); // → true
|
|
67
|
+
* inferVisionCapability("claude-2.1"); // → false
|
|
68
|
+
* inferVisionCapability("custom-proxy-llm"); // → false
|
|
69
|
+
*/
|
|
70
|
+
function inferVisionCapability(modelName) {
|
|
71
|
+
const normalized = modelName.toLowerCase();
|
|
72
|
+
return VISION_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region ../../@warlock.js/ai-anthropic/src/utils/map-stop-reason.ts
|
|
77
|
+
const stopReasonMap = {
|
|
78
|
+
end_turn: "stop",
|
|
79
|
+
stop_sequence: "stop",
|
|
80
|
+
max_tokens: "length",
|
|
81
|
+
tool_use: "tool_calls"
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Map Anthropic's `stop_reason` to the normalized `FinishReason` union.
|
|
85
|
+
*
|
|
86
|
+
* `end_turn` / `stop_sequence` are both natural stops. `max_tokens`
|
|
87
|
+
* maps to `length`. `tool_use` maps to `tool_calls`. Everything else —
|
|
88
|
+
* `refusal` (policy intervention), `pause_turn` (incomplete
|
|
89
|
+
* long-running turn), `null`, or any value a future API version adds —
|
|
90
|
+
* falls through to `"error"` so the agent treats the trip as a
|
|
91
|
+
* non-clean terminal rather than silently accepting a partial result.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* mapStopReason("end_turn"); // "stop"
|
|
95
|
+
* mapStopReason("tool_use"); // "tool_calls"
|
|
96
|
+
* mapStopReason("refusal"); // "error"
|
|
97
|
+
* mapStopReason(null); // "error"
|
|
98
|
+
*/
|
|
99
|
+
function mapStopReason(raw) {
|
|
100
|
+
return stopReasonMap[raw ?? ""] ?? "error";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region ../../@warlock.js/ai-anthropic/src/utils/to-anthropic-messages.ts
|
|
105
|
+
/**
|
|
106
|
+
* Convert vendor-neutral `Message[]` into Anthropic's request shape.
|
|
107
|
+
*
|
|
108
|
+
* Anthropic differs from the OpenAI Chat protocol in three ways this
|
|
109
|
+
* function absorbs:
|
|
110
|
+
*
|
|
111
|
+
* 1. **No `system` role.** System messages are concatenated (newline-
|
|
112
|
+
* separated) and returned separately as the top-level `system`
|
|
113
|
+
* parameter.
|
|
114
|
+
* 2. **Tool results are `user` turns.** A neutral `tool` message becomes
|
|
115
|
+
* a `user` message whose content is a single `tool_result` block
|
|
116
|
+
* keyed by `tool_use_id`.
|
|
117
|
+
* 3. **Tool calls are `tool_use` content blocks.** An assistant message
|
|
118
|
+
* carrying `toolCalls` becomes an `assistant` message whose content
|
|
119
|
+
* is an optional leading `text` block followed by one `tool_use`
|
|
120
|
+
* block per call.
|
|
121
|
+
*
|
|
122
|
+
* Consecutive same-role turns are left as-is — the Messages API merges
|
|
123
|
+
* them server-side.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const { system, messages } = toAnthropicMessages([
|
|
127
|
+
* { role: "system", content: "Be concise." },
|
|
128
|
+
* { role: "user", content: "Hi" },
|
|
129
|
+
* ]);
|
|
130
|
+
* // system === "Be concise."
|
|
131
|
+
* // messages === [{ role: "user", content: "Hi" }]
|
|
132
|
+
*/
|
|
133
|
+
function toAnthropicMessages(messages) {
|
|
134
|
+
const systemParts = [];
|
|
135
|
+
const mapped = [];
|
|
136
|
+
for (const message of messages) {
|
|
137
|
+
if (message.role === "system") {
|
|
138
|
+
systemParts.push(stringifyContent(message.content));
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (message.role === "tool") {
|
|
142
|
+
mapped.push({
|
|
143
|
+
role: "user",
|
|
144
|
+
content: [{
|
|
145
|
+
type: "tool_result",
|
|
146
|
+
tool_use_id: message.toolCallId ?? "",
|
|
147
|
+
content: stringifyContent(message.content)
|
|
148
|
+
}]
|
|
149
|
+
});
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
|
|
153
|
+
const blocks = [];
|
|
154
|
+
const text = stringifyContent(message.content);
|
|
155
|
+
if (text) blocks.push({
|
|
156
|
+
type: "text",
|
|
157
|
+
text
|
|
158
|
+
});
|
|
159
|
+
for (const toolCall of message.toolCalls) blocks.push({
|
|
160
|
+
type: "tool_use",
|
|
161
|
+
id: toolCall.id,
|
|
162
|
+
name: toolCall.name,
|
|
163
|
+
input: toolCall.input ?? {}
|
|
164
|
+
});
|
|
165
|
+
mapped.push({
|
|
166
|
+
role: "assistant",
|
|
167
|
+
content: blocks
|
|
168
|
+
});
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (message.role === "user" && Array.isArray(message.content)) {
|
|
172
|
+
mapped.push({
|
|
173
|
+
role: "user",
|
|
174
|
+
content: message.content.map(toAnthropicContentBlock)
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
mapped.push({
|
|
179
|
+
role: message.role === "assistant" ? "assistant" : "user",
|
|
180
|
+
content: stringifyContent(message.content)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
system: systemParts.length > 0 ? systemParts.join("\n\n") : void 0,
|
|
185
|
+
messages: mapped
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Multipart content is only meaningful on user messages — for any other
|
|
190
|
+
* role collapse a `ContentPart[]` to its concatenated text so the wire
|
|
191
|
+
* format stays valid. Plain strings pass through unchanged.
|
|
192
|
+
*/
|
|
193
|
+
function stringifyContent(content) {
|
|
194
|
+
if (typeof content === "string") return content;
|
|
195
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Map a single resolved `ContentPart` to an Anthropic content block.
|
|
199
|
+
* Text passes straight through; images become an `image` block with a
|
|
200
|
+
* `url` source (remote) or a `base64` source (inlined bytes). The
|
|
201
|
+
* agent has already resolved every attachment before it reaches here,
|
|
202
|
+
* so this never reads files or fetches URLs.
|
|
203
|
+
*/
|
|
204
|
+
function toAnthropicContentBlock(part) {
|
|
205
|
+
if (part.type === "text") return {
|
|
206
|
+
type: "text",
|
|
207
|
+
text: part.text
|
|
208
|
+
};
|
|
209
|
+
if ("url" in part.source) return {
|
|
210
|
+
type: "image",
|
|
211
|
+
source: {
|
|
212
|
+
type: "url",
|
|
213
|
+
url: part.source.url
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
type: "image",
|
|
218
|
+
source: {
|
|
219
|
+
type: "base64",
|
|
220
|
+
media_type: part.source.mediaType,
|
|
221
|
+
data: part.source.base64
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region ../../@warlock.js/ai-anthropic/src/utils/to-anthropic-tools.ts
|
|
228
|
+
/**
|
|
229
|
+
* Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.
|
|
230
|
+
* Uses the shared `extractJsonSchema` helper; Anthropic requires the
|
|
231
|
+
* input schema to be a JSON-Schema object, so a non-object extraction
|
|
232
|
+
* is coerced into an empty-object schema rather than rejected — the
|
|
233
|
+
* tool still registers and the model simply sees no parameters.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* const tools = toAnthropicTools([weatherTool, calculatorTool]);
|
|
237
|
+
* await client.messages.create({ model, max_tokens, messages, tools });
|
|
238
|
+
*/
|
|
239
|
+
function toAnthropicTools(tools) {
|
|
240
|
+
if (!tools || tools.length === 0) return;
|
|
241
|
+
return tools.map((tool) => ({
|
|
242
|
+
name: tool.name,
|
|
243
|
+
description: tool.description,
|
|
244
|
+
input_schema: toInputSchema(tool.input)
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Coerce the extracted JSON Schema into Anthropic's `Tool.InputSchema`
|
|
249
|
+
* shape (root must be `{ type: "object" }`). Anything that isn't an
|
|
250
|
+
* object schema degrades to a parameterless object so registration
|
|
251
|
+
* never fails on a malformed extractor result.
|
|
252
|
+
*/
|
|
253
|
+
function toInputSchema(input) {
|
|
254
|
+
const schema = (0, _warlock_js_ai.extractJsonSchema)(input);
|
|
255
|
+
if (schema && schema.type === "object") return schema;
|
|
256
|
+
return { type: "object" };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
//#endregion
|
|
260
|
+
//#region ../../@warlock.js/ai-anthropic/src/utils/wrap-anthropic-error.ts
|
|
261
|
+
/**
|
|
262
|
+
* Wrap any thrown value caught inside the Anthropic adapter into the
|
|
263
|
+
* appropriate `@warlock.js/ai` `AIError` subclass.
|
|
264
|
+
*
|
|
265
|
+
* **Dispatch strategy.** Anthropic has no per-error machine `code`; the
|
|
266
|
+
* stable identifier is `error.type` on the response body (surfaced as
|
|
267
|
+
* `APIError.type`). Dispatch prefers `type`, falls back to `status`
|
|
268
|
+
* when the body was stripped (common with proxies). Name-based
|
|
269
|
+
* detection catches transport-layer timeouts that never produced an
|
|
270
|
+
* HTTP response. The `invalid_request_error` branch additionally
|
|
271
|
+
* sniffs the message for Anthropic's "prompt is too long" phrasing,
|
|
272
|
+
* which is the only signal that a 400 was a context-length overflow.
|
|
273
|
+
*
|
|
274
|
+
* `AIError` instances pass through unchanged so `catch/throw wrap(e)`
|
|
275
|
+
* pipelines never double-wrap.
|
|
276
|
+
*
|
|
277
|
+
* @example
|
|
278
|
+
* try {
|
|
279
|
+
* return await this.client.messages.create(...);
|
|
280
|
+
* } catch (thrown) {
|
|
281
|
+
* throw wrapAnthropicError(thrown);
|
|
282
|
+
* }
|
|
283
|
+
*/
|
|
284
|
+
function wrapAnthropicError(thrown) {
|
|
285
|
+
if (thrown instanceof _warlock_js_ai.AIError) return thrown;
|
|
286
|
+
const shape = toShape(thrown);
|
|
287
|
+
const context = buildContext(shape);
|
|
288
|
+
const message = shape.message ?? (thrown instanceof Error ? thrown.message : String(thrown));
|
|
289
|
+
if (isTimeout(thrown, shape)) return new _warlock_js_ai.ProviderTimeoutError(message, {
|
|
290
|
+
cause: thrown,
|
|
291
|
+
context
|
|
292
|
+
});
|
|
293
|
+
if (shape.type === "authentication_error" || shape.type === "permission_error") return new _warlock_js_ai.ProviderAuthError(message, {
|
|
294
|
+
cause: thrown,
|
|
295
|
+
context
|
|
296
|
+
});
|
|
297
|
+
if (shape.status === 401 || shape.status === 403) return new _warlock_js_ai.ProviderAuthError(message, {
|
|
298
|
+
cause: thrown,
|
|
299
|
+
context
|
|
300
|
+
});
|
|
301
|
+
if (shape.type === "billing_error") return new _warlock_js_ai.QuotaExceededError(message, {
|
|
302
|
+
cause: thrown,
|
|
303
|
+
context
|
|
304
|
+
});
|
|
305
|
+
if (shape.type === "rate_limit_error" || shape.status === 429) return new _warlock_js_ai.ProviderRateLimitError(message, {
|
|
306
|
+
cause: thrown,
|
|
307
|
+
context,
|
|
308
|
+
retryAfter: parseRetryAfter(shape.headers)
|
|
309
|
+
});
|
|
310
|
+
if (shape.type === "invalid_request_error" || isClientStatus(shape.status)) {
|
|
311
|
+
if (/prompt is too long/i.test(message)) return new _warlock_js_ai.ContextLengthExceededError(message, {
|
|
312
|
+
cause: thrown,
|
|
313
|
+
context
|
|
314
|
+
});
|
|
315
|
+
return new _warlock_js_ai.InvalidRequestError(message, {
|
|
316
|
+
cause: thrown,
|
|
317
|
+
context
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return new _warlock_js_ai.ProviderError(message, {
|
|
321
|
+
cause: thrown,
|
|
322
|
+
context
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Read the raw error shape without depending on `instanceof APIError`
|
|
327
|
+
* — proxies and re-wrappers strip the prototype chain. Duck-typing on
|
|
328
|
+
* the visible fields is resilient to both.
|
|
329
|
+
*/
|
|
330
|
+
function toShape(thrown) {
|
|
331
|
+
if (thrown instanceof _anthropic_ai_sdk.APIError) return {
|
|
332
|
+
status: typeof thrown.status === "number" ? thrown.status : void 0,
|
|
333
|
+
type: thrown.type,
|
|
334
|
+
message: thrown.message,
|
|
335
|
+
headers: thrown.headers,
|
|
336
|
+
name: thrown.name,
|
|
337
|
+
requestId: thrown.requestID ?? void 0
|
|
338
|
+
};
|
|
339
|
+
if (typeof thrown === "object" && thrown !== null) {
|
|
340
|
+
const raw = thrown;
|
|
341
|
+
return {
|
|
342
|
+
status: typeof raw.status === "number" ? raw.status : void 0,
|
|
343
|
+
type: typeof raw.type === "string" ? raw.type : void 0,
|
|
344
|
+
message: typeof raw.message === "string" ? raw.message : void 0,
|
|
345
|
+
headers: isHeaderBag(raw.headers) ? raw.headers : void 0,
|
|
346
|
+
name: typeof raw.name === "string" ? raw.name : void 0,
|
|
347
|
+
requestId: readRequestId(raw)
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
return {};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Decide whether the thrown value represents a timeout. The Anthropic
|
|
354
|
+
* SDK throws `APIConnectionTimeoutError` for transport-level timeouts;
|
|
355
|
+
* Node surfaces `ETIMEDOUT` / `ECONNABORTED` on the socket layer.
|
|
356
|
+
* Either signal counts.
|
|
357
|
+
*/
|
|
358
|
+
function isTimeout(thrown, shape) {
|
|
359
|
+
if (thrown instanceof _anthropic_ai_sdk.APIConnectionTimeoutError) return true;
|
|
360
|
+
if (shape.name === "APIConnectionTimeoutError") return true;
|
|
361
|
+
if (typeof thrown === "object" && thrown !== null) {
|
|
362
|
+
const code = thrown.code;
|
|
363
|
+
if (code === "ETIMEDOUT" || code === "ECONNABORTED") return true;
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
/** True for HTTP 4xx — a client-side request problem, not a server fault. */
|
|
368
|
+
function isClientStatus(status) {
|
|
369
|
+
return typeof status === "number" && status >= 400 && status < 500;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Attach the raw diagnostic fields to `error.context` so consumers
|
|
373
|
+
* have everything the provider surfaced without each subclass having
|
|
374
|
+
* to redeclare them. Never includes `cause` — that lives on
|
|
375
|
+
* `error.cause`.
|
|
376
|
+
*/
|
|
377
|
+
function buildContext(shape) {
|
|
378
|
+
const context = {};
|
|
379
|
+
if (shape.status !== void 0) context.status = shape.status;
|
|
380
|
+
if (shape.type) context.type = shape.type;
|
|
381
|
+
if (shape.requestId) context.requestId = shape.requestId;
|
|
382
|
+
return context;
|
|
383
|
+
}
|
|
384
|
+
/** Anthropic exposes the request id as `requestID`; some proxies use `request_id`. */
|
|
385
|
+
function readRequestId(raw) {
|
|
386
|
+
if (typeof raw.requestID === "string") return raw.requestID;
|
|
387
|
+
if (typeof raw.request_id === "string") return raw.request_id;
|
|
388
|
+
}
|
|
389
|
+
function isHeaderBag(value) {
|
|
390
|
+
return typeof value === "object" && value !== null;
|
|
391
|
+
}
|
|
392
|
+
/** Read a header value from either a `Headers` instance or a plain record. */
|
|
393
|
+
function readHeader(headers, name) {
|
|
394
|
+
if (typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
395
|
+
const record = headers;
|
|
396
|
+
return record[name] ?? record[name.toLowerCase()];
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Parse the `Retry-After` response header (seconds per HTTP spec) into
|
|
400
|
+
* milliseconds so consumers can feed it straight to `setTimeout`.
|
|
401
|
+
* Returns `undefined` when missing or unparseable.
|
|
402
|
+
*/
|
|
403
|
+
function parseRetryAfter(headers) {
|
|
404
|
+
if (!headers) return;
|
|
405
|
+
const raw = readHeader(headers, "retry-after") ?? readHeader(headers, "Retry-After");
|
|
406
|
+
if (!raw) return;
|
|
407
|
+
const seconds = Number(raw);
|
|
408
|
+
if (!Number.isFinite(seconds) || seconds < 0) return;
|
|
409
|
+
return Math.round(seconds * 1e3);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region ../../@warlock.js/ai-anthropic/src/model.ts
|
|
414
|
+
const LOG_MODULE = "ai.anthropic";
|
|
415
|
+
/**
|
|
416
|
+
* Anthropic requires `max_tokens` on every request (unlike OpenAI,
|
|
417
|
+
* where it is optional). When neither the per-call option nor the
|
|
418
|
+
* model config supplies one, fall back to a generous default so a
|
|
419
|
+
* caller who never thought about token caps still gets a complete
|
|
420
|
+
* answer instead of a 400.
|
|
421
|
+
*/
|
|
422
|
+
const DEFAULT_MAX_TOKENS = 4096;
|
|
423
|
+
/**
|
|
424
|
+
* Anthropic-backed implementation of `ModelContract`.
|
|
425
|
+
*
|
|
426
|
+
* **Role.** The provider-facing bridge between the vendor-neutral
|
|
427
|
+
* `@warlock.js/ai` agent runtime and the official `@anthropic-ai/sdk`
|
|
428
|
+
* Messages API. Agents, workflows, and supervisors never talk to
|
|
429
|
+
* Anthropic directly — they hold a `ModelContract`, and this class is
|
|
430
|
+
* what makes that contract concrete for Claude models.
|
|
431
|
+
*
|
|
432
|
+
* **Responsibility.**
|
|
433
|
+
* - Owns: a long-lived `Anthropic` client + frozen `ModelConfig`
|
|
434
|
+
* (name, temperature, maxTokens) used as defaults for every call.
|
|
435
|
+
* - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into
|
|
436
|
+
* Anthropic wire shapes (system hoisting, `tool_use` / `tool_result`
|
|
437
|
+
* blocks) on the way out, and translating Anthropic's content-block
|
|
438
|
+
* response (text, tool calls, stop reason, usage) back into the
|
|
439
|
+
* neutral shapes on the way in.
|
|
440
|
+
* - Does NOT own: dispatching tools, deciding whether to loop, tracking
|
|
441
|
+
* conversation history, or retrying on failure — those are agent
|
|
442
|
+
* concerns. The model is a stateless (per-call) protocol adapter.
|
|
443
|
+
*
|
|
444
|
+
* Because it holds a live client and shared defaults, it is modeled as
|
|
445
|
+
* a class (see §4.2 of code-style.md — "long-lived state across
|
|
446
|
+
* calls").
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* import Anthropic from "@anthropic-ai/sdk";
|
|
450
|
+
* const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
451
|
+
* const model = new AnthropicModel(client, { name: "claude-sonnet-4-6" });
|
|
452
|
+
*
|
|
453
|
+
* const myAgent = agent({
|
|
454
|
+
* model,
|
|
455
|
+
* systemPrompt: "You are a helpful assistant.",
|
|
456
|
+
* tools: [searchTool],
|
|
457
|
+
* });
|
|
458
|
+
*
|
|
459
|
+
* const result = await myAgent.execute("Summarize today's news.");
|
|
460
|
+
*/
|
|
461
|
+
var AnthropicModel = class {
|
|
462
|
+
constructor(client, config, provider = "anthropic") {
|
|
463
|
+
this.logger = _warlock_js_logger.log;
|
|
464
|
+
this.client = client;
|
|
465
|
+
this.config = config;
|
|
466
|
+
this.name = config.name;
|
|
467
|
+
this.provider = provider;
|
|
468
|
+
this.pricing = config.pricing;
|
|
469
|
+
this.capabilities = {
|
|
470
|
+
structuredOutput: config.structuredOutput ?? true,
|
|
471
|
+
vision: config.vision ?? inferVisionCapability(config.name)
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Single-shot completion. Sends the full message list to the Messages
|
|
476
|
+
* endpoint, waits for the terminal response, and reshapes it into a
|
|
477
|
+
* vendor-neutral `ModelResponse`. Per-call `options` override the
|
|
478
|
+
* instance's `ModelConfig` defaults for this call only.
|
|
479
|
+
*/
|
|
480
|
+
async complete(messages, options) {
|
|
481
|
+
this.logger.debug(LOG_MODULE, "request", "Starting call to messages.create", {
|
|
482
|
+
model: this.name,
|
|
483
|
+
messageCount: messages.length,
|
|
484
|
+
streaming: false,
|
|
485
|
+
toolCount: options?.tools?.length ?? 0
|
|
486
|
+
});
|
|
487
|
+
let response;
|
|
488
|
+
try {
|
|
489
|
+
response = await this.client.messages.create({
|
|
490
|
+
...this.buildParams(messages, options),
|
|
491
|
+
stream: false
|
|
492
|
+
}, options?.signal ? { signal: options.signal } : void 0);
|
|
493
|
+
} catch (thrown) {
|
|
494
|
+
throw this.logAndWrap(thrown);
|
|
495
|
+
}
|
|
496
|
+
const finishReason = mapStopReason(response.stop_reason);
|
|
497
|
+
const usage = this.extractUsage(response.usage);
|
|
498
|
+
const toolCalls = this.extractToolCalls(response.content);
|
|
499
|
+
this.logger.debug(LOG_MODULE, "response", "call to messages.create succeeded", {
|
|
500
|
+
finishReason,
|
|
501
|
+
usage
|
|
502
|
+
});
|
|
503
|
+
return {
|
|
504
|
+
content: this.extractText(response.content),
|
|
505
|
+
finishReason,
|
|
506
|
+
usage,
|
|
507
|
+
toolCalls
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Incremental streaming completion. Yields neutral `ModelStreamChunk`s
|
|
512
|
+
* — `delta` for text tokens, `tool-call` once a `tool_use` block's
|
|
513
|
+
* arguments have fully accumulated, and a terminal `done` carrying the
|
|
514
|
+
* final finish reason + usage totals. Callers consume it with
|
|
515
|
+
* `for await`.
|
|
516
|
+
*/
|
|
517
|
+
async *stream(messages, options) {
|
|
518
|
+
this.logger.debug(LOG_MODULE, "request", "Starting streaming call to messages.create", {
|
|
519
|
+
model: this.name,
|
|
520
|
+
messageCount: messages.length,
|
|
521
|
+
streaming: true,
|
|
522
|
+
toolCount: options?.tools?.length ?? 0
|
|
523
|
+
});
|
|
524
|
+
let stream;
|
|
525
|
+
try {
|
|
526
|
+
stream = await this.client.messages.create({
|
|
527
|
+
...this.buildParams(messages, options),
|
|
528
|
+
stream: true
|
|
529
|
+
}, options?.signal ? { signal: options.signal } : void 0);
|
|
530
|
+
} catch (thrown) {
|
|
531
|
+
throw this.logAndWrap(thrown);
|
|
532
|
+
}
|
|
533
|
+
let rawStopReason = null;
|
|
534
|
+
const usage = {
|
|
535
|
+
input: 0,
|
|
536
|
+
output: 0,
|
|
537
|
+
total: 0
|
|
538
|
+
};
|
|
539
|
+
const toolBlocks = /* @__PURE__ */ new Map();
|
|
540
|
+
try {
|
|
541
|
+
for await (const event of stream) {
|
|
542
|
+
if (event.type === "message_start") {
|
|
543
|
+
usage.input = event.message.usage.input_tokens ?? 0;
|
|
544
|
+
const cached = event.message.usage.cache_read_input_tokens;
|
|
545
|
+
if (cached !== null && cached !== void 0 && cached > 0) usage.cachedTokens = cached;
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (event.type === "content_block_start") {
|
|
549
|
+
const block = event.content_block;
|
|
550
|
+
if (block.type === "tool_use") toolBlocks.set(event.index, {
|
|
551
|
+
id: block.id,
|
|
552
|
+
name: block.name,
|
|
553
|
+
json: ""
|
|
554
|
+
});
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (event.type === "content_block_delta") {
|
|
558
|
+
if (event.delta.type === "text_delta") yield {
|
|
559
|
+
type: "delta",
|
|
560
|
+
content: event.delta.text
|
|
561
|
+
};
|
|
562
|
+
else if (event.delta.type === "input_json_delta") {
|
|
563
|
+
const accumulator = toolBlocks.get(event.index);
|
|
564
|
+
if (accumulator) accumulator.json += event.delta.partial_json;
|
|
565
|
+
}
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
if (event.type === "content_block_stop") {
|
|
569
|
+
const accumulator = toolBlocks.get(event.index);
|
|
570
|
+
if (accumulator) {
|
|
571
|
+
yield {
|
|
572
|
+
type: "tool-call",
|
|
573
|
+
id: accumulator.id,
|
|
574
|
+
name: accumulator.name,
|
|
575
|
+
input: (0, _warlock_js_ai.safeJsonParse)(accumulator.json, {})
|
|
576
|
+
};
|
|
577
|
+
toolBlocks.delete(event.index);
|
|
578
|
+
}
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (event.type === "message_delta") {
|
|
582
|
+
rawStopReason = event.delta.stop_reason ?? rawStopReason;
|
|
583
|
+
usage.output = event.usage.output_tokens ?? usage.output;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
} catch (thrown) {
|
|
587
|
+
throw this.logAndWrap(thrown);
|
|
588
|
+
}
|
|
589
|
+
usage.total = usage.input + usage.output;
|
|
590
|
+
const finishReason = mapStopReason(rawStopReason);
|
|
591
|
+
this.logger.debug(LOG_MODULE, "response", "Streaming call to messages.create succeeded", {
|
|
592
|
+
finishReason,
|
|
593
|
+
usage
|
|
594
|
+
});
|
|
595
|
+
yield {
|
|
596
|
+
type: "done",
|
|
597
|
+
finishReason,
|
|
598
|
+
usage
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Assemble the Anthropic request body shared by `complete()` and
|
|
603
|
+
* `stream()` (each adds its own `stream` literal so the SDK's create
|
|
604
|
+
* overload resolves to the right return type). Hoists the system
|
|
605
|
+
* prompt out of `messages`, resolves `max_tokens` (required by
|
|
606
|
+
* Anthropic) with the documented default, and conditionally attaches
|
|
607
|
+
* temperature, tools, and native structured output.
|
|
608
|
+
*/
|
|
609
|
+
buildParams(messages, options) {
|
|
610
|
+
const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
|
|
611
|
+
const temperature = options?.temperature ?? this.config.temperature;
|
|
612
|
+
return {
|
|
613
|
+
model: this.name,
|
|
614
|
+
max_tokens: options?.maxTokens ?? this.config.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
615
|
+
messages: anthropicMessages,
|
|
616
|
+
...system ? { system } : {},
|
|
617
|
+
...temperature !== void 0 ? { temperature } : {},
|
|
618
|
+
...this.buildTools(options?.tools),
|
|
619
|
+
...this.buildStructuredOutput(options?.responseSchema)
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Spread-friendly tools fragment. Returns an empty object when no
|
|
624
|
+
* tools were supplied so the caller can unconditionally spread it.
|
|
625
|
+
*/
|
|
626
|
+
buildTools(tools) {
|
|
627
|
+
const mapped = toAnthropicTools(tools);
|
|
628
|
+
return mapped ? { tools: mapped } : {};
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Translate the neutral `responseSchema` option into Anthropic's
|
|
632
|
+
* native `output_config.format` (JSON-schema structured outputs).
|
|
633
|
+
*
|
|
634
|
+
* Only emitted when the model declares the `structuredOutput`
|
|
635
|
+
* capability AND the schema is a proper root-object JSON Schema —
|
|
636
|
+
* Anthropic rejects non-object roots. When the capability is off
|
|
637
|
+
* (config override) or the schema is non-object, returns an empty
|
|
638
|
+
* object: the agent has already injected a soft schema hint into the
|
|
639
|
+
* system prompt as the fallback, and client-side `validate()` still
|
|
640
|
+
* enforces shape.
|
|
641
|
+
*/
|
|
642
|
+
buildStructuredOutput(responseSchema) {
|
|
643
|
+
if (!responseSchema || !this.capabilities.structuredOutput) return {};
|
|
644
|
+
if (responseSchema.type !== "object" || typeof responseSchema.properties !== "object") return {};
|
|
645
|
+
return { output_config: { format: {
|
|
646
|
+
type: "json_schema",
|
|
647
|
+
schema: responseSchema
|
|
648
|
+
} } };
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Concatenate every `text` content block into the single neutral
|
|
652
|
+
* `content` string. `tool_use` and other block types are ignored
|
|
653
|
+
* here — tool calls are surfaced separately via `extractToolCalls`.
|
|
654
|
+
*/
|
|
655
|
+
extractText(content) {
|
|
656
|
+
return content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Reshape Anthropic's `tool_use` content blocks into the neutral
|
|
660
|
+
* `ModelToolCallRequest[]`. Returns `undefined` when the model
|
|
661
|
+
* requested no tools so callers can branch on presence.
|
|
662
|
+
*/
|
|
663
|
+
extractToolCalls(content) {
|
|
664
|
+
const toolUses = content.filter((block) => block.type === "tool_use");
|
|
665
|
+
if (toolUses.length === 0) return;
|
|
666
|
+
return toolUses.map((block) => ({
|
|
667
|
+
id: block.id,
|
|
668
|
+
name: block.name,
|
|
669
|
+
input: block.input ?? {}
|
|
670
|
+
}));
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Normalize Anthropic's `usage` block into the neutral `Usage` shape.
|
|
674
|
+
* Anthropic reports `input_tokens` / `output_tokens` separately with
|
|
675
|
+
* no pre-summed total, so `total` is computed. Cache-read tokens are
|
|
676
|
+
* surfaced as `cachedTokens` only when non-zero.
|
|
677
|
+
*/
|
|
678
|
+
extractUsage(raw) {
|
|
679
|
+
const input = raw.input_tokens ?? 0;
|
|
680
|
+
const output = raw.output_tokens ?? 0;
|
|
681
|
+
const cached = raw.cache_read_input_tokens;
|
|
682
|
+
return {
|
|
683
|
+
input,
|
|
684
|
+
output,
|
|
685
|
+
total: input + output,
|
|
686
|
+
...cached !== null && cached !== void 0 && cached > 0 ? { cachedTokens: cached } : {}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Wrap a thrown provider error into the typed `AIError` hierarchy and
|
|
691
|
+
* emit the standard error log line before it propagates. Shared by
|
|
692
|
+
* every catch site so the log shape stays identical.
|
|
693
|
+
*/
|
|
694
|
+
logAndWrap(thrown) {
|
|
695
|
+
const wrapped = wrapAnthropicError(thrown);
|
|
696
|
+
this.logger.error(LOG_MODULE, "error", wrapped.message, {
|
|
697
|
+
code: wrapped.code,
|
|
698
|
+
context: wrapped.context
|
|
699
|
+
});
|
|
700
|
+
return wrapped;
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
//#endregion
|
|
705
|
+
//#region ../../@warlock.js/ai-anthropic/src/sdk.ts
|
|
706
|
+
/**
|
|
707
|
+
* Anthropic-backed implementation of `SDKAdapterContract`.
|
|
708
|
+
*
|
|
709
|
+
* **Role.** The package entry point for Claude models via the official
|
|
710
|
+
* `@anthropic-ai/sdk`. A single `AnthropicSDK` instance holds one live
|
|
711
|
+
* `Anthropic` client, shared by every `ModelContract` it produces via
|
|
712
|
+
* `model()`. Users construct one SDK per account and reuse it across
|
|
713
|
+
* all agents, workflows, and supervisors that target Anthropic.
|
|
714
|
+
*
|
|
715
|
+
* **Responsibility.**
|
|
716
|
+
* - Owns: a long-lived `Anthropic` client (authentication, base URL)
|
|
717
|
+
* and its lifetime scope. Factory for `AnthropicModel` instances —
|
|
718
|
+
* each model call gets a reference to the same client.
|
|
719
|
+
* - Does NOT own: anything per-call (tool execution, message history,
|
|
720
|
+
* streaming loop) — those live in `AnthropicModel` and the agent
|
|
721
|
+
* runtime. Does NOT implement `embedder()`: Anthropic ships no
|
|
722
|
+
* first-party embeddings API (the contract marks it optional).
|
|
723
|
+
*
|
|
724
|
+
* Modeled as a class (see §4.2 of code-style.md — "long-lived state
|
|
725
|
+
* across many calls"): the `Anthropic` client is heavy to construct
|
|
726
|
+
* and designed to be reused; keeping it on `this` makes that reuse
|
|
727
|
+
* explicit and aligns with the `new Anthropic(...)` upstream
|
|
728
|
+
* convention.
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* const anthropic = new AnthropicSDK({ apiKey: process.env.ANTHROPIC_API_KEY! });
|
|
732
|
+
* const model = anthropic.model({ name: "claude-sonnet-4-6", temperature: 0.7 });
|
|
733
|
+
* const tokens = await anthropic.count("Hello world");
|
|
734
|
+
*
|
|
735
|
+
* @example
|
|
736
|
+
* // Compose into an `ai.anthropic` namespace for ergonomic agent wiring
|
|
737
|
+
* const ai = { agent, tool, systemPrompt, anthropic: new AnthropicSDK({ apiKey }) };
|
|
738
|
+
* const myAgent = ai.agent({ model: ai.anthropic.model({ name: "claude-haiku-4-5" }) });
|
|
739
|
+
*/
|
|
740
|
+
var AnthropicSDK = class {
|
|
741
|
+
constructor(config) {
|
|
742
|
+
this.client = new _anthropic_ai_sdk.default({
|
|
743
|
+
apiKey: config.apiKey,
|
|
744
|
+
baseURL: config.baseURL
|
|
745
|
+
});
|
|
746
|
+
this.provider = config.provider ?? "anthropic";
|
|
747
|
+
this.pricing = config.pricing;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Build an `AnthropicModel` bound to this SDK's client. Each call
|
|
751
|
+
* returns a fresh model instance, but all instances share the
|
|
752
|
+
* underlying `Anthropic` client — connection pools, rate limits, and
|
|
753
|
+
* authentication stay unified across every model produced here. The
|
|
754
|
+
* SDK's `provider` label is forwarded so every model self-identifies
|
|
755
|
+
* as coming from the same upstream.
|
|
756
|
+
*
|
|
757
|
+
* Pricing resolution: per-model `config.pricing` wins; otherwise the
|
|
758
|
+
* SDK-level registry entry keyed by `config.name`; otherwise
|
|
759
|
+
* `undefined` (no cost computed).
|
|
760
|
+
*/
|
|
761
|
+
model(config) {
|
|
762
|
+
const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
|
|
763
|
+
const resolvedConfig = resolvedPricing === config.pricing ? config : {
|
|
764
|
+
...config,
|
|
765
|
+
pricing: resolvedPricing
|
|
766
|
+
};
|
|
767
|
+
return new AnthropicModel(this.client, resolvedConfig, this.provider);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Rough token-count estimate for a given text. Uses the
|
|
771
|
+
* character-heuristic (`approximateTokenCount`) from the core package
|
|
772
|
+
* — good enough for budgeting and quota guards, not for billing.
|
|
773
|
+
* Anthropic does expose a `messages.countTokens` endpoint, but that
|
|
774
|
+
* is a network round-trip; `count()` is intentionally offline and
|
|
775
|
+
* synchronous-cost. The optional model id is reserved for future
|
|
776
|
+
* per-model tokenizer dispatch; currently ignored.
|
|
777
|
+
*/
|
|
778
|
+
async count(text, _model) {
|
|
779
|
+
return (0, _warlock_js_ai.approximateTokenCount)(text);
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
//#endregion
|
|
784
|
+
exports.AnthropicSDK = AnthropicSDK;
|
|
785
|
+
//# sourceMappingURL=index.cjs.map
|