@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 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