@warlock.js/ai-anthropic 4.2.11 → 4.3.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 CHANGED
@@ -6,6 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 4.3.0 - 2026-06-21
10
+
11
+ ### Added
12
+
13
+ - Cost-truth wiring against the core `@warlock.js/ai` contract:
14
+ - **Usage accounting** — `usage.cacheWriteTokens` is now populated from Anthropic's `cache_creation_input_tokens` (the cache-write surcharge), alongside the existing `cachedTokens` (from `cache_read_input_tokens`). In streaming, both are seeded from `message_start` and then refreshed from the cumulative `message_delta` counts. `reasoningTokens` is intentionally left unset because Anthropic bills extended-thinking tokens inside `output_tokens`.
15
+ - **Extended thinking** — `ModelCallOptions.reasoning` now maps to Anthropic's `thinking: { type: "enabled", budget_tokens }`. `reasoning.maxTokens` sets the budget directly; `reasoning.effort` (`low`/`medium`/`high`) maps to a tiered budget (`1024`/`4096`/`12000`); any budget is floored at the `1024` minimum. `temperature` is dropped when thinking is enabled (Anthropic rejects the combination). New `reasoning?: boolean` model-config override.
16
+ - **System-prompt prompt caching** — a per-call `ModelCallOptions.cacheControl.breakpoints >= 1` now emits the hoisted system prompt as a `TextBlockParam` carrying `cache_control: { type: "ephemeral" }`, independent of the existing tools-caching `promptCaching` flag.
17
+ - **Capabilities** — `reasoning` (default `true`, overridable), `promptCaching` (`true`), and `pdf` (`true`) are now advertised on `ModelCapabilities`; `audio` stays absent (Anthropic has no audio-input block).
18
+
9
19
  ## 4.2.0
10
20
 
11
21
  ### Added
package/cjs/index.cjs CHANGED
@@ -421,6 +421,24 @@ const LOG_MODULE = "ai.anthropic";
421
421
  */
422
422
  const DEFAULT_MAX_TOKENS = 4096;
423
423
  /**
424
+ * Anthropic rejects an extended-thinking budget below 1024 tokens, so
425
+ * any resolved budget is floored at this minimum before it reaches the
426
+ * wire.
427
+ */
428
+ const MIN_THINKING_BUDGET = 1024;
429
+ /**
430
+ * Map the neutral `ReasoningEffort` level to an Anthropic
431
+ * `thinking.budget_tokens` value. Anthropic budgets reasoning by token
432
+ * count (unlike OpenAI's opaque `reasoning_effort` enum), so the three
433
+ * neutral levels translate to representative token budgets when the
434
+ * caller doesn't pass an explicit `reasoning.maxTokens`.
435
+ */
436
+ const EFFORT_THINKING_BUDGET = {
437
+ low: 1024,
438
+ medium: 4096,
439
+ high: 12e3
440
+ };
441
+ /**
424
442
  * Anthropic-backed implementation of `ModelContract`.
425
443
  *
426
444
  * **Role.** The provider-facing bridge between the vendor-neutral
@@ -468,7 +486,10 @@ var AnthropicModel = class {
468
486
  this.pricing = config.pricing;
469
487
  this.capabilities = {
470
488
  structuredOutput: config.structuredOutput ?? true,
471
- vision: config.vision ?? inferVisionCapability(config.name)
489
+ vision: config.vision ?? inferVisionCapability(config.name),
490
+ reasoning: config.reasoning ?? true,
491
+ promptCaching: true,
492
+ pdf: true
472
493
  };
473
494
  }
474
495
  /**
@@ -541,8 +562,10 @@ var AnthropicModel = class {
541
562
  for await (const event of stream) {
542
563
  if (event.type === "message_start") {
543
564
  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;
565
+ const cacheRead = event.message.usage.cache_read_input_tokens;
566
+ if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
567
+ const cacheWrite = event.message.usage.cache_creation_input_tokens;
568
+ if (cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
546
569
  continue;
547
570
  }
548
571
  if (event.type === "content_block_start") {
@@ -581,6 +604,10 @@ var AnthropicModel = class {
581
604
  if (event.type === "message_delta") {
582
605
  rawStopReason = event.delta.stop_reason ?? rawStopReason;
583
606
  usage.output = event.usage.output_tokens ?? usage.output;
607
+ const cacheRead = event.usage.cache_read_input_tokens;
608
+ if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
609
+ const cacheWrite = event.usage.cache_creation_input_tokens;
610
+ if (cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
584
611
  }
585
612
  }
586
613
  } catch (thrown) {
@@ -604,22 +631,69 @@ var AnthropicModel = class {
604
631
  * overload resolves to the right return type). Hoists the system
605
632
  * prompt out of `messages`, resolves `max_tokens` (required by
606
633
  * Anthropic) with the documented default, and conditionally attaches
607
- * temperature, tools, and native structured output.
634
+ * temperature, tools, native structured output, extended thinking
635
+ * (`reasoning`), and a system-prompt cache breakpoint (`cacheControl`).
636
+ * Temperature is dropped when thinking is enabled, since Anthropic
637
+ * rejects the two together.
608
638
  */
609
639
  buildParams(messages, options) {
610
640
  const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
641
+ const thinking = this.buildThinking(options?.reasoning);
611
642
  const temperature = options?.temperature ?? this.config.temperature;
612
643
  return {
613
644
  model: this.name,
614
645
  max_tokens: options?.maxTokens ?? this.config.maxTokens ?? DEFAULT_MAX_TOKENS,
615
646
  messages: anthropicMessages,
616
- ...system ? { system } : {},
617
- ...temperature !== void 0 ? { temperature } : {},
647
+ ...this.buildSystem(system, options?.cacheControl),
648
+ ...temperature !== void 0 && !thinking.thinking ? { temperature } : {},
618
649
  ...this.buildTools(options?.tools),
619
- ...this.buildStructuredOutput(options?.responseSchema)
650
+ ...this.buildStructuredOutput(options?.responseSchema),
651
+ ...thinking
620
652
  };
621
653
  }
622
654
  /**
655
+ * Spread-friendly `system` fragment. Returns an empty object when no
656
+ * system prompt was hoisted out of the messages.
657
+ *
658
+ * When a per-call `cacheControl.breakpoints` hint is present (≥ 1),
659
+ * the system prompt is emitted as a single `TextBlockParam` carrying
660
+ * `cache_control: ephemeral` — the system prompt is the longest stable
661
+ * prefix on a turn, so one breakpoint there lets multi-turn agents
662
+ * read it back at the ~0.1x cache-read rate. Without the hint the
663
+ * system prompt stays a plain string (left uncached) so a one-shot
664
+ * call never pays the ~1.25x cache-write surcharge.
665
+ */
666
+ buildSystem(system, cacheControl) {
667
+ if (!system) return {};
668
+ const breakpoints = cacheControl?.breakpoints ?? 0;
669
+ if (this.capabilities.promptCaching && breakpoints > 0) return { system: [{
670
+ type: "text",
671
+ text: system,
672
+ cache_control: { type: "ephemeral" }
673
+ }] };
674
+ return { system };
675
+ }
676
+ /**
677
+ * Translate the neutral `reasoning` option into Anthropic's
678
+ * `thinking` request field. Emitted only when the model declares the
679
+ * `reasoning` capability AND a reasoning option is supplied; otherwise
680
+ * returns an empty object so the caller can unconditionally spread it.
681
+ *
682
+ * Budget resolution: an explicit `reasoning.maxTokens` wins; otherwise
683
+ * the neutral `effort` level maps to a tiered token budget. Anthropic
684
+ * requires `budget_tokens` ≥ 1024, so the budget is floored at that
685
+ * minimum.
686
+ */
687
+ buildThinking(reasoning) {
688
+ if (!this.capabilities.reasoning || !reasoning) return {};
689
+ if (reasoning.maxTokens === void 0 && reasoning.effort === void 0) return {};
690
+ const budget = reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[reasoning.effort ?? "medium"];
691
+ return { thinking: {
692
+ type: "enabled",
693
+ budget_tokens: Math.max(MIN_THINKING_BUDGET, budget)
694
+ } };
695
+ }
696
+ /**
623
697
  * Spread-friendly tools fragment. Returns an empty object when no
624
698
  * tools were supplied so the caller can unconditionally spread it.
625
699
  *
@@ -690,17 +764,25 @@ var AnthropicModel = class {
690
764
  * Normalize Anthropic's `usage` block into the neutral `Usage` shape.
691
765
  * Anthropic reports `input_tokens` / `output_tokens` separately with
692
766
  * no pre-summed total, so `total` is computed. Cache-read tokens are
693
- * surfaced as `cachedTokens` only when non-zero.
767
+ * surfaced as `cachedTokens` and cache-write tokens as
768
+ * `cacheWriteTokens`, each only when non-zero.
769
+ *
770
+ * Note: Anthropic does not report a separate reasoning-token count —
771
+ * extended-thinking tokens are billed inside `output_tokens` — so
772
+ * `Usage.reasoningTokens` is intentionally left unset here. Populating
773
+ * it would double-count against `output`.
694
774
  */
695
775
  extractUsage(raw) {
696
776
  const input = raw.input_tokens ?? 0;
697
777
  const output = raw.output_tokens ?? 0;
698
- const cached = raw.cache_read_input_tokens;
778
+ const cacheRead = raw.cache_read_input_tokens;
779
+ const cacheWrite = raw.cache_creation_input_tokens;
699
780
  return {
700
781
  input,
701
782
  output,
702
783
  total: input + output,
703
- ...cached !== null && cached !== void 0 && cached > 0 ? { cachedTokens: cached } : {}
784
+ ...cacheRead !== null && cacheRead !== void 0 && cacheRead > 0 ? { cachedTokens: cacheRead } : {},
785
+ ...cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {}
704
786
  };
705
787
  }
706
788
  /**
package/cjs/index.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["AIError","ProviderTimeoutError","ProviderAuthError","QuotaExceededError","ProviderRateLimitError","ContextLengthExceededError","InvalidRequestError","ProviderError","APIError","APIConnectionTimeoutError","log","Anthropic"],"sources":["../../../../../../@warlock.js/ai-anthropic/src/known-vision-models.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/map-stop-reason.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/to-anthropic-messages.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/to-anthropic-tools.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/wrap-anthropic-error.ts","../../../../../../@warlock.js/ai-anthropic/src/model.ts","../../../../../../@warlock.js/ai-anthropic/src/sdk.ts"],"sourcesContent":["/**\n * Model-name prefixes for Claude families that accept image input\n * (vision) on the Messages API.\n *\n * Every Claude 3, Claude 3.5/3.7, and Claude 4 family model is\n * multimodal, so the list covers both the dotted legacy naming\n * (`claude-3-haiku-...`, `claude-3-5-sonnet-...`) and the current\n * `claude-<tier>-4-*` naming (`claude-opus-4-7`, `claude-sonnet-4-6`,\n * `claude-haiku-4-5`). Pre-3 families (`claude-2`, `claude-instant`)\n * are text-only and intentionally absent.\n *\n * Matched as a prefix so dated variants (`claude-opus-4-20250514`) are\n * covered without listing every release tag. Devs can always override\n * per-model via `anthropic.model({ name, vision: true | false })` —\n * explicit config wins over inference in either direction.\n */\nconst VISION_CAPABLE_PREFIXES = [\n \"claude-3\",\n \"claude-4\",\n \"claude-opus-4\",\n \"claude-sonnet-4\",\n \"claude-haiku-4\",\n];\n\n/**\n * Infer whether a given Claude model name supports vision based on the\n * known-prefix list. Unknown models default to `false` so that passing\n * an image attachment to an unsupported model surfaces a clear,\n * agent-side capability error instead of an opaque Anthropic 400.\n *\n * @example\n * inferVisionCapability(\"claude-sonnet-4-6\"); // → true\n * inferVisionCapability(\"claude-3-5-sonnet-latest\"); // → true\n * inferVisionCapability(\"claude-2.1\"); // → false\n * inferVisionCapability(\"custom-proxy-llm\"); // → false\n */\nexport function inferVisionCapability(modelName: string): boolean {\n const normalized = modelName.toLowerCase();\n\n return VISION_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));\n}\n","import type { FinishReason } from \"@warlock.js/ai\";\n\nconst stopReasonMap: Record<string, FinishReason> = {\n end_turn: \"stop\",\n stop_sequence: \"stop\",\n max_tokens: \"length\",\n tool_use: \"tool_calls\",\n};\n\n/**\n * Map Anthropic's `stop_reason` to the normalized `FinishReason` union.\n *\n * `end_turn` / `stop_sequence` are both natural stops. `max_tokens`\n * maps to `length`. `tool_use` maps to `tool_calls`. Everything else —\n * `refusal` (policy intervention), `pause_turn` (incomplete\n * long-running turn), `null`, or any value a future API version adds —\n * falls through to `\"error\"` so the agent treats the trip as a\n * non-clean terminal rather than silently accepting a partial result.\n *\n * @example\n * mapStopReason(\"end_turn\"); // \"stop\"\n * mapStopReason(\"tool_use\"); // \"tool_calls\"\n * mapStopReason(\"refusal\"); // \"error\"\n * mapStopReason(null); // \"error\"\n */\nexport function mapStopReason(raw: string | null | undefined): FinishReason {\n return stopReasonMap[raw ?? \"\"] ?? \"error\";\n}\n","import type { ContentPart, Message } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for the Anthropic\n * Messages API: the system prompt is hoisted to a top-level `system`\n * string (Anthropic has no `\"system\"` role inside `messages`), and the\n * remaining turns are mapped to `MessageParam[]`.\n */\nexport type AnthropicMessages = {\n system: string | undefined;\n messages: Anthropic.MessageParam[];\n};\n\nconst ANTHROPIC_IMAGE_MEDIA_TYPES = [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"] as const;\n\ntype AnthropicImageMediaType = (typeof ANTHROPIC_IMAGE_MEDIA_TYPES)[number];\n\n/**\n * Convert vendor-neutral `Message[]` into Anthropic's request shape.\n *\n * Anthropic differs from the OpenAI Chat protocol in three ways this\n * function absorbs:\n *\n * 1. **No `system` role.** System messages are concatenated (newline-\n * separated) and returned separately as the top-level `system`\n * parameter.\n * 2. **Tool results are `user` turns.** A neutral `tool` message becomes\n * a `user` message whose content is a single `tool_result` block\n * keyed by `tool_use_id`.\n * 3. **Tool calls are `tool_use` content blocks.** An assistant message\n * carrying `toolCalls` becomes an `assistant` message whose content\n * is an optional leading `text` block followed by one `tool_use`\n * block per call.\n *\n * Consecutive same-role turns are left as-is — the Messages API merges\n * them server-side.\n *\n * @example\n * const { system, messages } = toAnthropicMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // system === \"Be concise.\"\n * // messages === [{ role: \"user\", content: \"Hi\" }]\n */\nexport function toAnthropicMessages(messages: Message[]): AnthropicMessages {\n const systemParts: string[] = [];\n const mapped: Anthropic.MessageParam[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n mapped.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: message.toolCallId ?? \"\",\n content: stringifyContent(message.content),\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: Anthropic.ContentBlockParam[] = [];\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 * 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 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","import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.\n * Uses the shared `extractJsonSchema` helper; Anthropic requires the\n * input schema to be a JSON-Schema object, so a non-object extraction\n * is coerced into an empty-object schema rather than rejected — the\n * tool still registers and the model simply sees no parameters.\n *\n * @example\n * const tools = toAnthropicTools([weatherTool, calculatorTool]);\n * await client.messages.create({ model, max_tokens, messages, tools });\n */\nexport function toAnthropicTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): Anthropic.Tool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: toInputSchema(tool.input),\n }));\n}\n\n/**\n * Coerce the extracted JSON Schema into Anthropic's `Tool.InputSchema`\n * shape (root must be `{ type: \"object\" }`). Anything that isn't an\n * object schema degrades to a parameterless object so registration\n * never fails on a malformed extractor result.\n */\nfunction toInputSchema(input: ToolConfig<unknown, unknown>[\"input\"]): Anthropic.Tool.InputSchema {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema as Anthropic.Tool.InputSchema;\n }\n\n return { type: \"object\" };\n}\n","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","import {\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type { AnthropicModelConfig } from \"./config.type\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapStopReason, toAnthropicMessages, toAnthropicTools, wrapAnthropicError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.anthropic\";\n\n/**\n * Anthropic requires `max_tokens` on every request (unlike OpenAI,\n * where it is optional). When neither the per-call option nor the\n * model config supplies one, fall back to a generous default so a\n * caller who never thought about token caps still gets a complete\n * answer instead of a 400.\n */\nconst DEFAULT_MAX_TOKENS = 4096;\n\n/**\n * Anthropic-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the official `@anthropic-ai/sdk`\n * Messages API. Agents, workflows, and supervisors never talk to\n * Anthropic directly — they hold a `ModelContract`, and this class is\n * what makes that contract concrete for Claude models.\n *\n * **Responsibility.**\n * - Owns: a long-lived `Anthropic` client + frozen `ModelConfig`\n * (name, temperature, maxTokens) used as defaults for every call.\n * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into\n * Anthropic wire shapes (system hoisting, `tool_use` / `tool_result`\n * blocks) on the way out, and translating Anthropic's content-block\n * response (text, tool calls, stop reason, usage) back into the\n * neutral shapes on the way in.\n * - Does NOT own: dispatching tools, deciding whether to loop, tracking\n * conversation history, or retrying on failure — those are agent\n * concerns. The model is a stateless (per-call) protocol adapter.\n *\n * Because it holds a live client and shared defaults, it is modeled as\n * a class (see §4.2 of code-style.md — \"long-lived state across\n * calls\").\n *\n * @example\n * import Anthropic from \"@anthropic-ai/sdk\";\n * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });\n * const model = new AnthropicModel(client, { name: \"claude-sonnet-4-6\" });\n *\n * const myAgent = agent({\n * model,\n * systemPrompt: \"You are a helpful assistant.\",\n * tools: [searchTool],\n * });\n *\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class AnthropicModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: Anthropic;\n private readonly config: AnthropicModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(\n client: Anthropic,\n config: AnthropicModelConfig,\n provider: string = \"anthropic\",\n ) {\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? true,\n vision: config.vision ?? inferVisionCapability(config.name),\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to the Messages\n * endpoint, waits for the terminal response, and reshapes it into a\n * vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance's `ModelConfig` defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: Anthropic.Message;\n\n try {\n response = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: false },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const finishReason = mapStopReason(response.stop_reason);\n const usage = this.extractUsage(response.usage);\n const toolCalls = this.extractToolCalls(response.content);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: this.extractText(response.content),\n finishReason,\n usage,\n toolCalls,\n };\n }\n\n /**\n * Incremental streaming completion. Yields neutral `ModelStreamChunk`s\n * — `delta` for text tokens, `tool-call` once a `tool_use` block's\n * arguments have fully accumulated, and a terminal `done` carrying the\n * final finish reason + usage totals. Callers consume it with\n * `for await`.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let stream: Awaited<ReturnType<Anthropic[\"messages\"][\"create\"]>>;\n\n try {\n stream = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: true },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n let rawStopReason: string | null = null;\n const usage: Usage = { input: 0, output: 0, total: 0 };\n const toolBlocks = new Map<number, { id: string; name: string; json: string }>();\n\n try {\n for await (const event of stream as AsyncIterable<Anthropic.RawMessageStreamEvent>) {\n if (event.type === \"message_start\") {\n usage.input = event.message.usage.input_tokens ?? 0;\n\n const cached = event.message.usage.cache_read_input_tokens;\n\n if (cached !== null && cached !== undefined && cached > 0) {\n usage.cachedTokens = cached;\n }\n\n continue;\n }\n\n if (event.type === \"content_block_start\") {\n const block = event.content_block;\n\n if (block.type === \"tool_use\") {\n toolBlocks.set(event.index, { id: block.id, name: block.name, json: \"\" });\n }\n\n continue;\n }\n\n if (event.type === \"content_block_delta\") {\n if (event.delta.type === \"text_delta\") {\n yield { type: \"delta\", content: event.delta.text };\n } else if (event.delta.type === \"input_json_delta\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n accumulator.json += event.delta.partial_json;\n }\n }\n\n continue;\n }\n\n if (event.type === \"content_block_stop\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n yield {\n type: \"tool-call\",\n id: accumulator.id,\n name: accumulator.name,\n input: safeJsonParse<Record<string, unknown>>(accumulator.json, {}),\n };\n\n toolBlocks.delete(event.index);\n }\n\n continue;\n }\n\n if (event.type === \"message_delta\") {\n rawStopReason = event.delta.stop_reason ?? rawStopReason;\n usage.output = event.usage.output_tokens ?? usage.output;\n }\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n usage.total = usage.input + usage.output;\n\n const finishReason = mapStopReason(rawStopReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the Anthropic request body shared by `complete()` and\n * `stream()` (each adds its own `stream` literal so the SDK's create\n * overload resolves to the right return type). Hoists the system\n * prompt out of `messages`, resolves `max_tokens` (required by\n * Anthropic) with the documented default, and conditionally attaches\n * temperature, tools, and native structured output.\n */\n private buildParams(\n messages: Message[],\n options: ModelCallOptions | undefined,\n ): Omit<Anthropic.MessageCreateParamsNonStreaming, \"stream\"> {\n const { system, messages: anthropicMessages } = toAnthropicMessages(messages);\n const temperature = options?.temperature ?? this.config.temperature;\n\n return {\n model: this.name,\n max_tokens: options?.maxTokens ?? this.config.maxTokens ?? DEFAULT_MAX_TOKENS,\n messages: anthropicMessages,\n ...(system ? { system } : {}),\n ...(temperature !== undefined ? { temperature } : {}),\n ...this.buildTools(options?.tools),\n ...this.buildStructuredOutput(options?.responseSchema),\n };\n }\n\n /**\n * Spread-friendly tools fragment. Returns an empty object when no\n * tools were supplied so the caller can unconditionally spread it.\n *\n * When `config.promptCaching` is on, marks the LAST tool with\n * `cache_control: ephemeral` — Anthropic caches the whole prefix up\n * to a breakpoint, and tools sit before `system` in that prefix, so\n * one breakpoint on the final tool caches every tool definition.\n * Reads bill at ~0.1x after the first write. The system prompt is\n * deliberately left uncached: it carries per-turn placeholders, so a\n * breakpoint there would pay the ~1.25x write surcharge every turn\n * with no reads.\n */\n private buildTools(tools: ModelCallOptions[\"tools\"]): { tools?: Anthropic.Tool[] } {\n const mapped = toAnthropicTools(tools);\n\n if (!mapped) {\n return {};\n }\n\n if (this.config.promptCaching && mapped.length > 0) {\n const last = mapped.length - 1;\n mapped[last] = { ...mapped[last], cache_control: { type: \"ephemeral\" } };\n }\n\n return { tools: mapped };\n }\n\n /**\n * Translate the neutral `responseSchema` option into Anthropic's\n * native `output_config.format` (JSON-schema structured outputs).\n *\n * Only emitted when the model declares the `structuredOutput`\n * capability AND the schema is a proper root-object JSON Schema —\n * Anthropic rejects non-object roots. When the capability is off\n * (config override) or the schema is non-object, returns an empty\n * object: the agent has already injected a soft schema hint into the\n * system prompt as the fallback, and client-side `validate()` still\n * enforces shape.\n */\n private buildStructuredOutput(responseSchema: Record<string, unknown> | undefined): {\n output_config?: Anthropic.OutputConfig;\n } {\n if (!responseSchema || !this.capabilities.structuredOutput) {\n return {};\n }\n\n if (responseSchema.type !== \"object\" || typeof responseSchema.properties !== \"object\") {\n return {};\n }\n\n return {\n output_config: {\n format: { type: \"json_schema\", schema: responseSchema },\n },\n };\n }\n\n /**\n * Concatenate every `text` content block into the single neutral\n * `content` string. `tool_use` and other block types are ignored\n * here — tool calls are surfaced separately via `extractToolCalls`.\n */\n private extractText(content: Anthropic.ContentBlock[]): string {\n return content\n .filter((block): block is Anthropic.TextBlock => block.type === \"text\")\n .map((block) => block.text)\n .join(\"\");\n }\n\n /**\n * Reshape Anthropic's `tool_use` content blocks into the neutral\n * `ModelToolCallRequest[]`. Returns `undefined` when the model\n * requested no tools so callers can branch on presence.\n */\n private extractToolCalls(\n content: Anthropic.ContentBlock[],\n ): ModelToolCallRequest[] | undefined {\n const toolUses = content.filter(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n\n if (toolUses.length === 0) {\n return undefined;\n }\n\n return toolUses.map((block) => ({\n id: block.id,\n name: block.name,\n input: (block.input ?? {}) as Record<string, unknown>,\n }));\n }\n\n /**\n * Normalize Anthropic's `usage` block into the neutral `Usage` shape.\n * Anthropic reports `input_tokens` / `output_tokens` separately with\n * no pre-summed total, so `total` is computed. Cache-read tokens are\n * surfaced as `cachedTokens` only when non-zero.\n */\n private extractUsage(raw: Anthropic.Usage): Usage {\n const input = raw.input_tokens ?? 0;\n const output = raw.output_tokens ?? 0;\n const cached = raw.cache_read_input_tokens;\n\n return {\n input,\n output,\n total: input + output,\n ...(cached !== null && cached !== undefined && cached > 0 ? { cachedTokens: cached } : {}),\n };\n }\n\n /**\n * Wrap a thrown provider error into the typed `AIError` hierarchy and\n * emit the standard error log line before it propagates. Shared by\n * every catch site so the log shape stays identical.\n */\n private logAndWrap(thrown: unknown) {\n const wrapped = wrapAnthropicError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n","import Anthropic from \"@anthropic-ai/sdk\";\nimport type { ModelContract, ModelPricing, SDKAdapterContract } from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type { AnthropicModelConfig, AnthropicSDKConfig } from \"./config.type\";\nimport { AnthropicModel } from \"./model\";\n\n/**\n * Anthropic-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for Claude models via the official\n * `@anthropic-ai/sdk`. A single `AnthropicSDK` instance holds one live\n * `Anthropic` client, shared by every `ModelContract` it produces via\n * `model()`. Users construct one SDK per account and reuse it across\n * all agents, workflows, and supervisors that target Anthropic.\n *\n * **Responsibility.**\n * - Owns: a long-lived `Anthropic` client (authentication, base URL)\n * and its lifetime scope. Factory for `AnthropicModel` instances —\n * each model call gets a reference to the same client.\n * - Does NOT own: anything per-call (tool execution, message history,\n * streaming loop) — those live in `AnthropicModel` and the agent\n * runtime. Does NOT implement `embedder()`: Anthropic ships no\n * first-party embeddings API (the contract marks it optional).\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"): the `Anthropic` client is heavy to construct\n * and designed to be reused; keeping it on `this` makes that reuse\n * explicit and aligns with the `new Anthropic(...)` upstream\n * convention.\n *\n * @example\n * const anthropic = new AnthropicSDK({ apiKey: process.env.ANTHROPIC_API_KEY! });\n * const model = anthropic.model({ name: \"claude-sonnet-4-6\", temperature: 0.7 });\n * const tokens = await anthropic.count(\"Hello world\");\n *\n * @example\n * // Compose into an `ai.anthropic` namespace for ergonomic agent wiring\n * const ai = { agent, tool, systemPrompt, anthropic: new AnthropicSDK({ apiKey }) };\n * const myAgent = ai.agent({ model: ai.anthropic.model({ name: \"claude-haiku-4-5\" }) });\n */\nexport class AnthropicSDK implements SDKAdapterContract {\n private readonly client: Anthropic;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: AnthropicSDKConfig) {\n this.client = new Anthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n this.provider = config.provider ?? \"anthropic\";\n this.pricing = config.pricing;\n }\n\n /**\n * Build an `AnthropicModel` bound to this SDK's client. Each call\n * returns a fresh model instance, but all instances share the\n * underlying `Anthropic` client — connection pools, rate limits, and\n * authentication stay unified across every model produced here. The\n * SDK's `provider` label is forwarded so every model self-identifies\n * as coming from the same upstream.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: AnthropicModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: AnthropicModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new AnthropicModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate for a given text. Uses the\n * character-heuristic (`approximateTokenCount`) from the core package\n * — good enough for budgeting and quota guards, not for billing.\n * Anthropic does expose a `messages.countTokens` endpoint, but that\n * is a network round-trip; `count()` is intentionally offline and\n * synchronous-cost. The optional model id is reserved for future\n * per-model tokenizer dispatch; currently ignored.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["AIError","ProviderTimeoutError","ProviderAuthError","QuotaExceededError","ProviderRateLimitError","ContextLengthExceededError","InvalidRequestError","ProviderError","APIError","APIConnectionTimeoutError","log","Anthropic"],"sources":["../../../../../../@warlock.js/ai-anthropic/src/known-vision-models.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/map-stop-reason.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/to-anthropic-messages.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/to-anthropic-tools.ts","../../../../../../@warlock.js/ai-anthropic/src/utils/wrap-anthropic-error.ts","../../../../../../@warlock.js/ai-anthropic/src/model.ts","../../../../../../@warlock.js/ai-anthropic/src/sdk.ts"],"sourcesContent":["/**\n * Model-name prefixes for Claude families that accept image input\n * (vision) on the Messages API.\n *\n * Every Claude 3, Claude 3.5/3.7, and Claude 4 family model is\n * multimodal, so the list covers both the dotted legacy naming\n * (`claude-3-haiku-...`, `claude-3-5-sonnet-...`) and the current\n * `claude-<tier>-4-*` naming (`claude-opus-4-7`, `claude-sonnet-4-6`,\n * `claude-haiku-4-5`). Pre-3 families (`claude-2`, `claude-instant`)\n * are text-only and intentionally absent.\n *\n * Matched as a prefix so dated variants (`claude-opus-4-20250514`) are\n * covered without listing every release tag. Devs can always override\n * per-model via `anthropic.model({ name, vision: true | false })` —\n * explicit config wins over inference in either direction.\n */\nconst VISION_CAPABLE_PREFIXES = [\n \"claude-3\",\n \"claude-4\",\n \"claude-opus-4\",\n \"claude-sonnet-4\",\n \"claude-haiku-4\",\n];\n\n/**\n * Infer whether a given Claude model name supports vision based on the\n * known-prefix list. Unknown models default to `false` so that passing\n * an image attachment to an unsupported model surfaces a clear,\n * agent-side capability error instead of an opaque Anthropic 400.\n *\n * @example\n * inferVisionCapability(\"claude-sonnet-4-6\"); // → true\n * inferVisionCapability(\"claude-3-5-sonnet-latest\"); // → true\n * inferVisionCapability(\"claude-2.1\"); // → false\n * inferVisionCapability(\"custom-proxy-llm\"); // → false\n */\nexport function inferVisionCapability(modelName: string): boolean {\n const normalized = modelName.toLowerCase();\n\n return VISION_CAPABLE_PREFIXES.some((prefix) => normalized.startsWith(prefix));\n}\n","import type { FinishReason } from \"@warlock.js/ai\";\n\nconst stopReasonMap: Record<string, FinishReason> = {\n end_turn: \"stop\",\n stop_sequence: \"stop\",\n max_tokens: \"length\",\n tool_use: \"tool_calls\",\n};\n\n/**\n * Map Anthropic's `stop_reason` to the normalized `FinishReason` union.\n *\n * `end_turn` / `stop_sequence` are both natural stops. `max_tokens`\n * maps to `length`. `tool_use` maps to `tool_calls`. Everything else —\n * `refusal` (policy intervention), `pause_turn` (incomplete\n * long-running turn), `null`, or any value a future API version adds —\n * falls through to `\"error\"` so the agent treats the trip as a\n * non-clean terminal rather than silently accepting a partial result.\n *\n * @example\n * mapStopReason(\"end_turn\"); // \"stop\"\n * mapStopReason(\"tool_use\"); // \"tool_calls\"\n * mapStopReason(\"refusal\"); // \"error\"\n * mapStopReason(null); // \"error\"\n */\nexport function mapStopReason(raw: string | null | undefined): FinishReason {\n return stopReasonMap[raw ?? \"\"] ?? \"error\";\n}\n","import type { ContentPart, Message } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for the Anthropic\n * Messages API: the system prompt is hoisted to a top-level `system`\n * string (Anthropic has no `\"system\"` role inside `messages`), and the\n * remaining turns are mapped to `MessageParam[]`.\n */\nexport type AnthropicMessages = {\n system: string | undefined;\n messages: Anthropic.MessageParam[];\n};\n\nconst ANTHROPIC_IMAGE_MEDIA_TYPES = [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"] as const;\n\ntype AnthropicImageMediaType = (typeof ANTHROPIC_IMAGE_MEDIA_TYPES)[number];\n\n/**\n * Convert vendor-neutral `Message[]` into Anthropic's request shape.\n *\n * Anthropic differs from the OpenAI Chat protocol in three ways this\n * function absorbs:\n *\n * 1. **No `system` role.** System messages are concatenated (newline-\n * separated) and returned separately as the top-level `system`\n * parameter.\n * 2. **Tool results are `user` turns.** A neutral `tool` message becomes\n * a `user` message whose content is a single `tool_result` block\n * keyed by `tool_use_id`.\n * 3. **Tool calls are `tool_use` content blocks.** An assistant message\n * carrying `toolCalls` becomes an `assistant` message whose content\n * is an optional leading `text` block followed by one `tool_use`\n * block per call.\n *\n * Consecutive same-role turns are left as-is — the Messages API merges\n * them server-side.\n *\n * @example\n * const { system, messages } = toAnthropicMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n * // system === \"Be concise.\"\n * // messages === [{ role: \"user\", content: \"Hi\" }]\n */\nexport function toAnthropicMessages(messages: Message[]): AnthropicMessages {\n const systemParts: string[] = [];\n const mapped: Anthropic.MessageParam[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n systemParts.push(stringifyContent(message.content));\n\n continue;\n }\n\n if (message.role === \"tool\") {\n mapped.push({\n role: \"user\",\n content: [\n {\n type: \"tool_result\",\n tool_use_id: message.toolCallId ?? \"\",\n content: stringifyContent(message.content),\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: Anthropic.ContentBlockParam[] = [];\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 * 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 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","import { extractJsonSchema, type ToolConfig } from \"@warlock.js/ai\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\n\n/**\n * Convert vendor-neutral `ToolConfig[]` into Anthropic's `tools` array.\n * Uses the shared `extractJsonSchema` helper; Anthropic requires the\n * input schema to be a JSON-Schema object, so a non-object extraction\n * is coerced into an empty-object schema rather than rejected — the\n * tool still registers and the model simply sees no parameters.\n *\n * @example\n * const tools = toAnthropicTools([weatherTool, calculatorTool]);\n * await client.messages.create({ model, max_tokens, messages, tools });\n */\nexport function toAnthropicTools(\n tools: ToolConfig<unknown, unknown>[] | undefined,\n): Anthropic.Tool[] | undefined {\n if (!tools || tools.length === 0) {\n return undefined;\n }\n\n return tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n input_schema: toInputSchema(tool.input),\n }));\n}\n\n/**\n * Coerce the extracted JSON Schema into Anthropic's `Tool.InputSchema`\n * shape (root must be `{ type: \"object\" }`). Anything that isn't an\n * object schema degrades to a parameterless object so registration\n * never fails on a malformed extractor result.\n */\nfunction toInputSchema(input: ToolConfig<unknown, unknown>[\"input\"]): Anthropic.Tool.InputSchema {\n const schema = extractJsonSchema(input);\n\n if (schema && schema.type === \"object\") {\n return schema as Anthropic.Tool.InputSchema;\n }\n\n return { type: \"object\" };\n}\n","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","import {\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type ReasoningEffort,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type { AnthropicModelConfig } from \"./config.type\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapStopReason, toAnthropicMessages, toAnthropicTools, wrapAnthropicError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.anthropic\";\n\n/**\n * Anthropic requires `max_tokens` on every request (unlike OpenAI,\n * where it is optional). When neither the per-call option nor the\n * model config supplies one, fall back to a generous default so a\n * caller who never thought about token caps still gets a complete\n * answer instead of a 400.\n */\nconst DEFAULT_MAX_TOKENS = 4096;\n\n/**\n * Anthropic rejects an extended-thinking budget below 1024 tokens, so\n * any resolved budget is floored at this minimum before it reaches the\n * wire.\n */\nconst MIN_THINKING_BUDGET = 1024;\n\n/**\n * Map the neutral `ReasoningEffort` level to an Anthropic\n * `thinking.budget_tokens` value. Anthropic budgets reasoning by token\n * count (unlike OpenAI's opaque `reasoning_effort` enum), so the three\n * neutral levels translate to representative token budgets when the\n * caller doesn't pass an explicit `reasoning.maxTokens`.\n */\nconst EFFORT_THINKING_BUDGET: Record<ReasoningEffort, number> = {\n low: 1024,\n medium: 4096,\n high: 12000,\n};\n\n/**\n * Anthropic-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the official `@anthropic-ai/sdk`\n * Messages API. Agents, workflows, and supervisors never talk to\n * Anthropic directly — they hold a `ModelContract`, and this class is\n * what makes that contract concrete for Claude models.\n *\n * **Responsibility.**\n * - Owns: a long-lived `Anthropic` client + frozen `ModelConfig`\n * (name, temperature, maxTokens) used as defaults for every call.\n * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into\n * Anthropic wire shapes (system hoisting, `tool_use` / `tool_result`\n * blocks) on the way out, and translating Anthropic's content-block\n * response (text, tool calls, stop reason, usage) back into the\n * neutral shapes on the way in.\n * - Does NOT own: dispatching tools, deciding whether to loop, tracking\n * conversation history, or retrying on failure — those are agent\n * concerns. The model is a stateless (per-call) protocol adapter.\n *\n * Because it holds a live client and shared defaults, it is modeled as\n * a class (see §4.2 of code-style.md — \"long-lived state across\n * calls\").\n *\n * @example\n * import Anthropic from \"@anthropic-ai/sdk\";\n * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });\n * const model = new AnthropicModel(client, { name: \"claude-sonnet-4-6\" });\n *\n * const myAgent = agent({\n * model,\n * systemPrompt: \"You are a helpful assistant.\",\n * tools: [searchTool],\n * });\n *\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class AnthropicModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: Anthropic;\n private readonly config: AnthropicModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(\n client: Anthropic,\n config: AnthropicModelConfig,\n provider: string = \"anthropic\",\n ) {\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? true,\n vision: config.vision ?? inferVisionCapability(config.name),\n // Every modern Claude model accepts Anthropic extended thinking\n // (`thinking: { type: \"enabled\", budget_tokens }`). Advertise the\n // reasoning channel so the agent forwards `reasoning` options;\n // explicit config override wins for proxied/legacy targets.\n reasoning: config.reasoning ?? true,\n // Anthropic prompt caching is caller-driven via `cache_control`\n // breakpoints. The adapter both places those breakpoints (tools\n // when `config.promptCaching`, system when\n // `options.cacheControl.breakpoints`) and reports the read/write\n // accounting (`cachedTokens` / `cacheWriteTokens`), so advertise\n // the capability unconditionally.\n promptCaching: true,\n // The Messages API accepts PDF/document content blocks\n // (`DocumentBlockParam`) on vision-capable Claude models.\n pdf: true,\n // Anthropic has no audio input content block, so `audio` stays\n // absent (treated as false) and the agent rejects audio\n // attachments upfront rather than dropping them at the wire layer.\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to the Messages\n * endpoint, waits for the terminal response, and reshapes it into a\n * vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance's `ModelConfig` defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: Anthropic.Message;\n\n try {\n response = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: false },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const finishReason = mapStopReason(response.stop_reason);\n const usage = this.extractUsage(response.usage);\n const toolCalls = this.extractToolCalls(response.content);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: this.extractText(response.content),\n finishReason,\n usage,\n toolCalls,\n };\n }\n\n /**\n * Incremental streaming completion. Yields neutral `ModelStreamChunk`s\n * — `delta` for text tokens, `tool-call` once a `tool_use` block's\n * arguments have fully accumulated, and a terminal `done` carrying the\n * final finish reason + usage totals. Callers consume it with\n * `for await`.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let stream: Awaited<ReturnType<Anthropic[\"messages\"][\"create\"]>>;\n\n try {\n stream = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: true },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n let rawStopReason: string | null = null;\n const usage: Usage = { input: 0, output: 0, total: 0 };\n const toolBlocks = new Map<number, { id: string; name: string; json: string }>();\n\n try {\n for await (const event of stream as AsyncIterable<Anthropic.RawMessageStreamEvent>) {\n if (event.type === \"message_start\") {\n usage.input = event.message.usage.input_tokens ?? 0;\n\n const cacheRead = event.message.usage.cache_read_input_tokens;\n\n if (cacheRead !== null && cacheRead !== undefined && cacheRead > 0) {\n usage.cachedTokens = cacheRead;\n }\n\n const cacheWrite = event.message.usage.cache_creation_input_tokens;\n\n if (cacheWrite !== null && cacheWrite !== undefined && cacheWrite > 0) {\n usage.cacheWriteTokens = cacheWrite;\n }\n\n continue;\n }\n\n if (event.type === \"content_block_start\") {\n const block = event.content_block;\n\n if (block.type === \"tool_use\") {\n toolBlocks.set(event.index, { id: block.id, name: block.name, json: \"\" });\n }\n\n continue;\n }\n\n if (event.type === \"content_block_delta\") {\n if (event.delta.type === \"text_delta\") {\n yield { type: \"delta\", content: event.delta.text };\n } else if (event.delta.type === \"input_json_delta\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n accumulator.json += event.delta.partial_json;\n }\n }\n\n continue;\n }\n\n if (event.type === \"content_block_stop\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n yield {\n type: \"tool-call\",\n id: accumulator.id,\n name: accumulator.name,\n input: safeJsonParse<Record<string, unknown>>(accumulator.json, {}),\n };\n\n toolBlocks.delete(event.index);\n }\n\n continue;\n }\n\n if (event.type === \"message_delta\") {\n rawStopReason = event.delta.stop_reason ?? rawStopReason;\n usage.output = event.usage.output_tokens ?? usage.output;\n\n // `message_delta.usage` carries the cumulative cache counts —\n // prefer them over the `message_start` snapshot when present\n // and non-zero so the terminal `done` reflects the final tally.\n const cacheRead = event.usage.cache_read_input_tokens;\n\n if (cacheRead !== null && cacheRead !== undefined && cacheRead > 0) {\n usage.cachedTokens = cacheRead;\n }\n\n const cacheWrite = event.usage.cache_creation_input_tokens;\n\n if (cacheWrite !== null && cacheWrite !== undefined && cacheWrite > 0) {\n usage.cacheWriteTokens = cacheWrite;\n }\n }\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n usage.total = usage.input + usage.output;\n\n const finishReason = mapStopReason(rawStopReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the Anthropic request body shared by `complete()` and\n * `stream()` (each adds its own `stream` literal so the SDK's create\n * overload resolves to the right return type). Hoists the system\n * prompt out of `messages`, resolves `max_tokens` (required by\n * Anthropic) with the documented default, and conditionally attaches\n * temperature, tools, native structured output, extended thinking\n * (`reasoning`), and a system-prompt cache breakpoint (`cacheControl`).\n * Temperature is dropped when thinking is enabled, since Anthropic\n * rejects the two together.\n */\n private buildParams(\n messages: Message[],\n options: ModelCallOptions | undefined,\n ): Omit<Anthropic.MessageCreateParamsNonStreaming, \"stream\"> {\n const { system, messages: anthropicMessages } = toAnthropicMessages(messages);\n const thinking = this.buildThinking(options?.reasoning);\n const temperature = options?.temperature ?? this.config.temperature;\n\n return {\n model: this.name,\n max_tokens: options?.maxTokens ?? this.config.maxTokens ?? DEFAULT_MAX_TOKENS,\n messages: anthropicMessages,\n ...this.buildSystem(system, options?.cacheControl),\n // Extended thinking pins sampling to the default temperature —\n // Anthropic 400s when `thinking` is enabled alongside any explicit\n // `temperature`. Drop temperature in that case rather than letting\n // the request fail.\n ...(temperature !== undefined && !thinking.thinking ? { temperature } : {}),\n ...this.buildTools(options?.tools),\n ...this.buildStructuredOutput(options?.responseSchema),\n ...thinking,\n };\n }\n\n /**\n * Spread-friendly `system` fragment. Returns an empty object when no\n * system prompt was hoisted out of the messages.\n *\n * When a per-call `cacheControl.breakpoints` hint is present (≥ 1),\n * the system prompt is emitted as a single `TextBlockParam` carrying\n * `cache_control: ephemeral` — the system prompt is the longest stable\n * prefix on a turn, so one breakpoint there lets multi-turn agents\n * read it back at the ~0.1x cache-read rate. Without the hint the\n * system prompt stays a plain string (left uncached) so a one-shot\n * call never pays the ~1.25x cache-write surcharge.\n */\n private buildSystem(\n system: string | undefined,\n cacheControl: ModelCallOptions[\"cacheControl\"],\n ): { system?: string | Anthropic.TextBlockParam[] } {\n if (!system) {\n return {};\n }\n\n const breakpoints = cacheControl?.breakpoints ?? 0;\n\n if (this.capabilities.promptCaching && breakpoints > 0) {\n return {\n system: [{ type: \"text\", text: system, cache_control: { type: \"ephemeral\" } }],\n };\n }\n\n return { system };\n }\n\n /**\n * Translate the neutral `reasoning` option into Anthropic's\n * `thinking` request field. Emitted only when the model declares the\n * `reasoning` capability AND a reasoning option is supplied; otherwise\n * returns an empty object so the caller can unconditionally spread it.\n *\n * Budget resolution: an explicit `reasoning.maxTokens` wins; otherwise\n * the neutral `effort` level maps to a tiered token budget. Anthropic\n * requires `budget_tokens` ≥ 1024, so the budget is floored at that\n * minimum.\n */\n private buildThinking(\n reasoning: ModelCallOptions[\"reasoning\"],\n ): { thinking?: Anthropic.ThinkingConfigParam } {\n if (!this.capabilities.reasoning || !reasoning) {\n return {};\n }\n\n if (reasoning.maxTokens === undefined && reasoning.effort === undefined) {\n return {};\n }\n\n const budget = reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[reasoning.effort ?? \"medium\"];\n\n return {\n thinking: {\n type: \"enabled\",\n budget_tokens: Math.max(MIN_THINKING_BUDGET, budget),\n },\n };\n }\n\n /**\n * Spread-friendly tools fragment. Returns an empty object when no\n * tools were supplied so the caller can unconditionally spread it.\n *\n * When `config.promptCaching` is on, marks the LAST tool with\n * `cache_control: ephemeral` — Anthropic caches the whole prefix up\n * to a breakpoint, and tools sit before `system` in that prefix, so\n * one breakpoint on the final tool caches every tool definition.\n * Reads bill at ~0.1x after the first write. The system prompt is\n * deliberately left uncached: it carries per-turn placeholders, so a\n * breakpoint there would pay the ~1.25x write surcharge every turn\n * with no reads.\n */\n private buildTools(tools: ModelCallOptions[\"tools\"]): { tools?: Anthropic.Tool[] } {\n const mapped = toAnthropicTools(tools);\n\n if (!mapped) {\n return {};\n }\n\n if (this.config.promptCaching && mapped.length > 0) {\n const last = mapped.length - 1;\n mapped[last] = { ...mapped[last], cache_control: { type: \"ephemeral\" } };\n }\n\n return { tools: mapped };\n }\n\n /**\n * Translate the neutral `responseSchema` option into Anthropic's\n * native `output_config.format` (JSON-schema structured outputs).\n *\n * Only emitted when the model declares the `structuredOutput`\n * capability AND the schema is a proper root-object JSON Schema —\n * Anthropic rejects non-object roots. When the capability is off\n * (config override) or the schema is non-object, returns an empty\n * object: the agent has already injected a soft schema hint into the\n * system prompt as the fallback, and client-side `validate()` still\n * enforces shape.\n */\n private buildStructuredOutput(responseSchema: Record<string, unknown> | undefined): {\n output_config?: Anthropic.OutputConfig;\n } {\n if (!responseSchema || !this.capabilities.structuredOutput) {\n return {};\n }\n\n if (responseSchema.type !== \"object\" || typeof responseSchema.properties !== \"object\") {\n return {};\n }\n\n return {\n output_config: {\n format: { type: \"json_schema\", schema: responseSchema },\n },\n };\n }\n\n /**\n * Concatenate every `text` content block into the single neutral\n * `content` string. `tool_use` and other block types are ignored\n * here — tool calls are surfaced separately via `extractToolCalls`.\n */\n private extractText(content: Anthropic.ContentBlock[]): string {\n return content\n .filter((block): block is Anthropic.TextBlock => block.type === \"text\")\n .map((block) => block.text)\n .join(\"\");\n }\n\n /**\n * Reshape Anthropic's `tool_use` content blocks into the neutral\n * `ModelToolCallRequest[]`. Returns `undefined` when the model\n * requested no tools so callers can branch on presence.\n */\n private extractToolCalls(\n content: Anthropic.ContentBlock[],\n ): ModelToolCallRequest[] | undefined {\n const toolUses = content.filter(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n\n if (toolUses.length === 0) {\n return undefined;\n }\n\n return toolUses.map((block) => ({\n id: block.id,\n name: block.name,\n input: (block.input ?? {}) as Record<string, unknown>,\n }));\n }\n\n /**\n * Normalize Anthropic's `usage` block into the neutral `Usage` shape.\n * Anthropic reports `input_tokens` / `output_tokens` separately with\n * no pre-summed total, so `total` is computed. Cache-read tokens are\n * surfaced as `cachedTokens` and cache-write tokens as\n * `cacheWriteTokens`, each only when non-zero.\n *\n * Note: Anthropic does not report a separate reasoning-token count —\n * extended-thinking tokens are billed inside `output_tokens` — so\n * `Usage.reasoningTokens` is intentionally left unset here. Populating\n * it would double-count against `output`.\n */\n private extractUsage(raw: Anthropic.Usage): Usage {\n const input = raw.input_tokens ?? 0;\n const output = raw.output_tokens ?? 0;\n const cacheRead = raw.cache_read_input_tokens;\n const cacheWrite = raw.cache_creation_input_tokens;\n\n return {\n input,\n output,\n total: input + output,\n ...(cacheRead !== null && cacheRead !== undefined && cacheRead > 0\n ? { cachedTokens: cacheRead }\n : {}),\n ...(cacheWrite !== null && cacheWrite !== undefined && cacheWrite > 0\n ? { cacheWriteTokens: cacheWrite }\n : {}),\n };\n }\n\n /**\n * Wrap a thrown provider error into the typed `AIError` hierarchy and\n * emit the standard error log line before it propagates. Shared by\n * every catch site so the log shape stays identical.\n */\n private logAndWrap(thrown: unknown) {\n const wrapped = wrapAnthropicError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n","import Anthropic from \"@anthropic-ai/sdk\";\nimport type { ModelContract, ModelPricing, SDKAdapterContract } from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type { AnthropicModelConfig, AnthropicSDKConfig } from \"./config.type\";\nimport { AnthropicModel } from \"./model\";\n\n/**\n * Anthropic-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for Claude models via the official\n * `@anthropic-ai/sdk`. A single `AnthropicSDK` instance holds one live\n * `Anthropic` client, shared by every `ModelContract` it produces via\n * `model()`. Users construct one SDK per account and reuse it across\n * all agents, workflows, and supervisors that target Anthropic.\n *\n * **Responsibility.**\n * - Owns: a long-lived `Anthropic` client (authentication, base URL)\n * and its lifetime scope. Factory for `AnthropicModel` instances —\n * each model call gets a reference to the same client.\n * - Does NOT own: anything per-call (tool execution, message history,\n * streaming loop) — those live in `AnthropicModel` and the agent\n * runtime. Does NOT implement `embedder()`: Anthropic ships no\n * first-party embeddings API (the contract marks it optional).\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"): the `Anthropic` client is heavy to construct\n * and designed to be reused; keeping it on `this` makes that reuse\n * explicit and aligns with the `new Anthropic(...)` upstream\n * convention.\n *\n * @example\n * const anthropic = new AnthropicSDK({ apiKey: process.env.ANTHROPIC_API_KEY! });\n * const model = anthropic.model({ name: \"claude-sonnet-4-6\", temperature: 0.7 });\n * const tokens = await anthropic.count(\"Hello world\");\n *\n * @example\n * // Compose into an `ai.anthropic` namespace for ergonomic agent wiring\n * const ai = { agent, tool, systemPrompt, anthropic: new AnthropicSDK({ apiKey }) };\n * const myAgent = ai.agent({ model: ai.anthropic.model({ name: \"claude-haiku-4-5\" }) });\n */\nexport class AnthropicSDK implements SDKAdapterContract {\n private readonly client: Anthropic;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: AnthropicSDKConfig) {\n this.client = new Anthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n this.provider = config.provider ?? \"anthropic\";\n this.pricing = config.pricing;\n }\n\n /**\n * Build an `AnthropicModel` bound to this SDK's client. Each call\n * returns a fresh model instance, but all instances share the\n * underlying `Anthropic` client — connection pools, rate limits, and\n * authentication stay unified across every model produced here. The\n * SDK's `provider` label is forwarded so every model self-identifies\n * as coming from the same upstream.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: AnthropicModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: AnthropicModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new AnthropicModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate for a given text. Uses the\n * character-heuristic (`approximateTokenCount`) from the core package\n * — good enough for budgeting and quota guards, not for billing.\n * Anthropic does expose a `messages.countTokens` endpoint, but that\n * is a network round-trip; `count()` is intentionally offline and\n * synchronous-cost. The optional model id is reserved for future\n * per-model tokenizer dispatch; currently ignored.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n}\n"],"mappings":"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"}
@@ -79,8 +79,23 @@ type AnthropicModelConfig = ModelConfig & {
79
79
  * no surcharge. The system prompt is intentionally NOT cached here:
80
80
  * it carries per-turn placeholders, so a breakpoint there would pay
81
81
  * the write surcharge every turn with no reads.
82
+ *
83
+ * Independent of this flag, a per-call
84
+ * `ModelCallOptions.cacheControl.breakpoints` still places a cache
85
+ * breakpoint on the system prompt — that hint is opt-in per request.
82
86
  */
83
87
  promptCaching?: boolean;
88
+ /**
89
+ * Override the inferred `reasoning` capability. When omitted, the
90
+ * adapter advertises reasoning as supported (every modern Claude model
91
+ * accepts Anthropic extended thinking via `thinking`), so a per-call
92
+ * `ModelCallOptions.reasoning` is forwarded as
93
+ * `thinking: { type: "enabled", budget_tokens }`. Set to `false` for
94
+ * proxied deployments or older targets that reject the `thinking`
95
+ * param — the adapter then ignores reasoning options rather than
96
+ * sending an unsupported field.
97
+ */
98
+ reasoning?: boolean;
84
99
  };
85
100
  //#endregion
86
101
  export { AnthropicModelConfig, AnthropicSDKConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-anthropic/src/config.type.ts"],"mappings":";;;;;;AAgCA;;;;;;;;;;;;;;AAOuC;AAWvC;;;;;;;;;AAgCe;;;KAlDH,kBAAA,GAAqB,aAAA;EAC/B,QAAA;;;;;;EAMA,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;;;;;;;;KAWf,oBAAA,GAAuB,WAAW;;;;;;;;EAQ5C,MAAA;;;;;;;;;EASA,gBAAA;;;;;;;;;;;;;;;EAeA,aAAA;AAAA"}
1
+ {"version":3,"file":"config.type.d.mts","names":[],"sources":["../../../../../../@warlock.js/ai-anthropic/src/config.type.ts"],"mappings":";;;;;;AAgCA;;;;;;;;;;;;;;AAOuC;AAWvC;;;;;;;;;;AA+CW;;KAjEC,kBAAA,GAAqB,aAAA;EAC/B,QAAA;;;;;;EAMA,OAAA,GAAU,MAAA,SAAe,YAAA;AAAA;;;;;;;;;KAWf,oBAAA,GAAuB,WAAW;;;;;;;;EAQ5C,MAAA;;;;;;;;;EASA,gBAAA;;;;;;;;;;;;;;;;;;;EAmBA,aAAA;;;;;;;;;;;EAWA,SAAA;AAAA"}
package/esm/model.mjs CHANGED
@@ -18,6 +18,24 @@ const LOG_MODULE = "ai.anthropic";
18
18
  */
19
19
  const DEFAULT_MAX_TOKENS = 4096;
20
20
  /**
21
+ * Anthropic rejects an extended-thinking budget below 1024 tokens, so
22
+ * any resolved budget is floored at this minimum before it reaches the
23
+ * wire.
24
+ */
25
+ const MIN_THINKING_BUDGET = 1024;
26
+ /**
27
+ * Map the neutral `ReasoningEffort` level to an Anthropic
28
+ * `thinking.budget_tokens` value. Anthropic budgets reasoning by token
29
+ * count (unlike OpenAI's opaque `reasoning_effort` enum), so the three
30
+ * neutral levels translate to representative token budgets when the
31
+ * caller doesn't pass an explicit `reasoning.maxTokens`.
32
+ */
33
+ const EFFORT_THINKING_BUDGET = {
34
+ low: 1024,
35
+ medium: 4096,
36
+ high: 12e3
37
+ };
38
+ /**
21
39
  * Anthropic-backed implementation of `ModelContract`.
22
40
  *
23
41
  * **Role.** The provider-facing bridge between the vendor-neutral
@@ -65,7 +83,10 @@ var AnthropicModel = class {
65
83
  this.pricing = config.pricing;
66
84
  this.capabilities = {
67
85
  structuredOutput: config.structuredOutput ?? true,
68
- vision: config.vision ?? inferVisionCapability(config.name)
86
+ vision: config.vision ?? inferVisionCapability(config.name),
87
+ reasoning: config.reasoning ?? true,
88
+ promptCaching: true,
89
+ pdf: true
69
90
  };
70
91
  }
71
92
  /**
@@ -138,8 +159,10 @@ var AnthropicModel = class {
138
159
  for await (const event of stream) {
139
160
  if (event.type === "message_start") {
140
161
  usage.input = event.message.usage.input_tokens ?? 0;
141
- const cached = event.message.usage.cache_read_input_tokens;
142
- if (cached !== null && cached !== void 0 && cached > 0) usage.cachedTokens = cached;
162
+ const cacheRead = event.message.usage.cache_read_input_tokens;
163
+ if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
164
+ const cacheWrite = event.message.usage.cache_creation_input_tokens;
165
+ if (cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
143
166
  continue;
144
167
  }
145
168
  if (event.type === "content_block_start") {
@@ -178,6 +201,10 @@ var AnthropicModel = class {
178
201
  if (event.type === "message_delta") {
179
202
  rawStopReason = event.delta.stop_reason ?? rawStopReason;
180
203
  usage.output = event.usage.output_tokens ?? usage.output;
204
+ const cacheRead = event.usage.cache_read_input_tokens;
205
+ if (cacheRead !== null && cacheRead !== void 0 && cacheRead > 0) usage.cachedTokens = cacheRead;
206
+ const cacheWrite = event.usage.cache_creation_input_tokens;
207
+ if (cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0) usage.cacheWriteTokens = cacheWrite;
181
208
  }
182
209
  }
183
210
  } catch (thrown) {
@@ -201,22 +228,69 @@ var AnthropicModel = class {
201
228
  * overload resolves to the right return type). Hoists the system
202
229
  * prompt out of `messages`, resolves `max_tokens` (required by
203
230
  * Anthropic) with the documented default, and conditionally attaches
204
- * temperature, tools, and native structured output.
231
+ * temperature, tools, native structured output, extended thinking
232
+ * (`reasoning`), and a system-prompt cache breakpoint (`cacheControl`).
233
+ * Temperature is dropped when thinking is enabled, since Anthropic
234
+ * rejects the two together.
205
235
  */
206
236
  buildParams(messages, options) {
207
237
  const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
238
+ const thinking = this.buildThinking(options?.reasoning);
208
239
  const temperature = options?.temperature ?? this.config.temperature;
209
240
  return {
210
241
  model: this.name,
211
242
  max_tokens: options?.maxTokens ?? this.config.maxTokens ?? DEFAULT_MAX_TOKENS,
212
243
  messages: anthropicMessages,
213
- ...system ? { system } : {},
214
- ...temperature !== void 0 ? { temperature } : {},
244
+ ...this.buildSystem(system, options?.cacheControl),
245
+ ...temperature !== void 0 && !thinking.thinking ? { temperature } : {},
215
246
  ...this.buildTools(options?.tools),
216
- ...this.buildStructuredOutput(options?.responseSchema)
247
+ ...this.buildStructuredOutput(options?.responseSchema),
248
+ ...thinking
217
249
  };
218
250
  }
219
251
  /**
252
+ * Spread-friendly `system` fragment. Returns an empty object when no
253
+ * system prompt was hoisted out of the messages.
254
+ *
255
+ * When a per-call `cacheControl.breakpoints` hint is present (≥ 1),
256
+ * the system prompt is emitted as a single `TextBlockParam` carrying
257
+ * `cache_control: ephemeral` — the system prompt is the longest stable
258
+ * prefix on a turn, so one breakpoint there lets multi-turn agents
259
+ * read it back at the ~0.1x cache-read rate. Without the hint the
260
+ * system prompt stays a plain string (left uncached) so a one-shot
261
+ * call never pays the ~1.25x cache-write surcharge.
262
+ */
263
+ buildSystem(system, cacheControl) {
264
+ if (!system) return {};
265
+ const breakpoints = cacheControl?.breakpoints ?? 0;
266
+ if (this.capabilities.promptCaching && breakpoints > 0) return { system: [{
267
+ type: "text",
268
+ text: system,
269
+ cache_control: { type: "ephemeral" }
270
+ }] };
271
+ return { system };
272
+ }
273
+ /**
274
+ * Translate the neutral `reasoning` option into Anthropic's
275
+ * `thinking` request field. Emitted only when the model declares the
276
+ * `reasoning` capability AND a reasoning option is supplied; otherwise
277
+ * returns an empty object so the caller can unconditionally spread it.
278
+ *
279
+ * Budget resolution: an explicit `reasoning.maxTokens` wins; otherwise
280
+ * the neutral `effort` level maps to a tiered token budget. Anthropic
281
+ * requires `budget_tokens` ≥ 1024, so the budget is floored at that
282
+ * minimum.
283
+ */
284
+ buildThinking(reasoning) {
285
+ if (!this.capabilities.reasoning || !reasoning) return {};
286
+ if (reasoning.maxTokens === void 0 && reasoning.effort === void 0) return {};
287
+ const budget = reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[reasoning.effort ?? "medium"];
288
+ return { thinking: {
289
+ type: "enabled",
290
+ budget_tokens: Math.max(MIN_THINKING_BUDGET, budget)
291
+ } };
292
+ }
293
+ /**
220
294
  * Spread-friendly tools fragment. Returns an empty object when no
221
295
  * tools were supplied so the caller can unconditionally spread it.
222
296
  *
@@ -287,17 +361,25 @@ var AnthropicModel = class {
287
361
  * Normalize Anthropic's `usage` block into the neutral `Usage` shape.
288
362
  * Anthropic reports `input_tokens` / `output_tokens` separately with
289
363
  * no pre-summed total, so `total` is computed. Cache-read tokens are
290
- * surfaced as `cachedTokens` only when non-zero.
364
+ * surfaced as `cachedTokens` and cache-write tokens as
365
+ * `cacheWriteTokens`, each only when non-zero.
366
+ *
367
+ * Note: Anthropic does not report a separate reasoning-token count —
368
+ * extended-thinking tokens are billed inside `output_tokens` — so
369
+ * `Usage.reasoningTokens` is intentionally left unset here. Populating
370
+ * it would double-count against `output`.
291
371
  */
292
372
  extractUsage(raw) {
293
373
  const input = raw.input_tokens ?? 0;
294
374
  const output = raw.output_tokens ?? 0;
295
- const cached = raw.cache_read_input_tokens;
375
+ const cacheRead = raw.cache_read_input_tokens;
376
+ const cacheWrite = raw.cache_creation_input_tokens;
296
377
  return {
297
378
  input,
298
379
  output,
299
380
  total: input + output,
300
- ...cached !== null && cached !== void 0 && cached > 0 ? { cachedTokens: cached } : {}
381
+ ...cacheRead !== null && cacheRead !== void 0 && cacheRead > 0 ? { cachedTokens: cacheRead } : {},
382
+ ...cacheWrite !== null && cacheWrite !== void 0 && cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {}
301
383
  };
302
384
  }
303
385
  /**
package/esm/model.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"model.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-anthropic/src/model.ts"],"sourcesContent":["import {\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type { AnthropicModelConfig } from \"./config.type\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapStopReason, toAnthropicMessages, toAnthropicTools, wrapAnthropicError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.anthropic\";\n\n/**\n * Anthropic requires `max_tokens` on every request (unlike OpenAI,\n * where it is optional). When neither the per-call option nor the\n * model config supplies one, fall back to a generous default so a\n * caller who never thought about token caps still gets a complete\n * answer instead of a 400.\n */\nconst DEFAULT_MAX_TOKENS = 4096;\n\n/**\n * Anthropic-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the official `@anthropic-ai/sdk`\n * Messages API. Agents, workflows, and supervisors never talk to\n * Anthropic directly — they hold a `ModelContract`, and this class is\n * what makes that contract concrete for Claude models.\n *\n * **Responsibility.**\n * - Owns: a long-lived `Anthropic` client + frozen `ModelConfig`\n * (name, temperature, maxTokens) used as defaults for every call.\n * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into\n * Anthropic wire shapes (system hoisting, `tool_use` / `tool_result`\n * blocks) on the way out, and translating Anthropic's content-block\n * response (text, tool calls, stop reason, usage) back into the\n * neutral shapes on the way in.\n * - Does NOT own: dispatching tools, deciding whether to loop, tracking\n * conversation history, or retrying on failure — those are agent\n * concerns. The model is a stateless (per-call) protocol adapter.\n *\n * Because it holds a live client and shared defaults, it is modeled as\n * a class (see §4.2 of code-style.md — \"long-lived state across\n * calls\").\n *\n * @example\n * import Anthropic from \"@anthropic-ai/sdk\";\n * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });\n * const model = new AnthropicModel(client, { name: \"claude-sonnet-4-6\" });\n *\n * const myAgent = agent({\n * model,\n * systemPrompt: \"You are a helpful assistant.\",\n * tools: [searchTool],\n * });\n *\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class AnthropicModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: Anthropic;\n private readonly config: AnthropicModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(\n client: Anthropic,\n config: AnthropicModelConfig,\n provider: string = \"anthropic\",\n ) {\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? true,\n vision: config.vision ?? inferVisionCapability(config.name),\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to the Messages\n * endpoint, waits for the terminal response, and reshapes it into a\n * vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance's `ModelConfig` defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: Anthropic.Message;\n\n try {\n response = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: false },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const finishReason = mapStopReason(response.stop_reason);\n const usage = this.extractUsage(response.usage);\n const toolCalls = this.extractToolCalls(response.content);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: this.extractText(response.content),\n finishReason,\n usage,\n toolCalls,\n };\n }\n\n /**\n * Incremental streaming completion. Yields neutral `ModelStreamChunk`s\n * — `delta` for text tokens, `tool-call` once a `tool_use` block's\n * arguments have fully accumulated, and a terminal `done` carrying the\n * final finish reason + usage totals. Callers consume it with\n * `for await`.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let stream: Awaited<ReturnType<Anthropic[\"messages\"][\"create\"]>>;\n\n try {\n stream = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: true },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n let rawStopReason: string | null = null;\n const usage: Usage = { input: 0, output: 0, total: 0 };\n const toolBlocks = new Map<number, { id: string; name: string; json: string }>();\n\n try {\n for await (const event of stream as AsyncIterable<Anthropic.RawMessageStreamEvent>) {\n if (event.type === \"message_start\") {\n usage.input = event.message.usage.input_tokens ?? 0;\n\n const cached = event.message.usage.cache_read_input_tokens;\n\n if (cached !== null && cached !== undefined && cached > 0) {\n usage.cachedTokens = cached;\n }\n\n continue;\n }\n\n if (event.type === \"content_block_start\") {\n const block = event.content_block;\n\n if (block.type === \"tool_use\") {\n toolBlocks.set(event.index, { id: block.id, name: block.name, json: \"\" });\n }\n\n continue;\n }\n\n if (event.type === \"content_block_delta\") {\n if (event.delta.type === \"text_delta\") {\n yield { type: \"delta\", content: event.delta.text };\n } else if (event.delta.type === \"input_json_delta\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n accumulator.json += event.delta.partial_json;\n }\n }\n\n continue;\n }\n\n if (event.type === \"content_block_stop\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n yield {\n type: \"tool-call\",\n id: accumulator.id,\n name: accumulator.name,\n input: safeJsonParse<Record<string, unknown>>(accumulator.json, {}),\n };\n\n toolBlocks.delete(event.index);\n }\n\n continue;\n }\n\n if (event.type === \"message_delta\") {\n rawStopReason = event.delta.stop_reason ?? rawStopReason;\n usage.output = event.usage.output_tokens ?? usage.output;\n }\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n usage.total = usage.input + usage.output;\n\n const finishReason = mapStopReason(rawStopReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the Anthropic request body shared by `complete()` and\n * `stream()` (each adds its own `stream` literal so the SDK's create\n * overload resolves to the right return type). Hoists the system\n * prompt out of `messages`, resolves `max_tokens` (required by\n * Anthropic) with the documented default, and conditionally attaches\n * temperature, tools, and native structured output.\n */\n private buildParams(\n messages: Message[],\n options: ModelCallOptions | undefined,\n ): Omit<Anthropic.MessageCreateParamsNonStreaming, \"stream\"> {\n const { system, messages: anthropicMessages } = toAnthropicMessages(messages);\n const temperature = options?.temperature ?? this.config.temperature;\n\n return {\n model: this.name,\n max_tokens: options?.maxTokens ?? this.config.maxTokens ?? DEFAULT_MAX_TOKENS,\n messages: anthropicMessages,\n ...(system ? { system } : {}),\n ...(temperature !== undefined ? { temperature } : {}),\n ...this.buildTools(options?.tools),\n ...this.buildStructuredOutput(options?.responseSchema),\n };\n }\n\n /**\n * Spread-friendly tools fragment. Returns an empty object when no\n * tools were supplied so the caller can unconditionally spread it.\n *\n * When `config.promptCaching` is on, marks the LAST tool with\n * `cache_control: ephemeral` — Anthropic caches the whole prefix up\n * to a breakpoint, and tools sit before `system` in that prefix, so\n * one breakpoint on the final tool caches every tool definition.\n * Reads bill at ~0.1x after the first write. The system prompt is\n * deliberately left uncached: it carries per-turn placeholders, so a\n * breakpoint there would pay the ~1.25x write surcharge every turn\n * with no reads.\n */\n private buildTools(tools: ModelCallOptions[\"tools\"]): { tools?: Anthropic.Tool[] } {\n const mapped = toAnthropicTools(tools);\n\n if (!mapped) {\n return {};\n }\n\n if (this.config.promptCaching && mapped.length > 0) {\n const last = mapped.length - 1;\n mapped[last] = { ...mapped[last], cache_control: { type: \"ephemeral\" } };\n }\n\n return { tools: mapped };\n }\n\n /**\n * Translate the neutral `responseSchema` option into Anthropic's\n * native `output_config.format` (JSON-schema structured outputs).\n *\n * Only emitted when the model declares the `structuredOutput`\n * capability AND the schema is a proper root-object JSON Schema —\n * Anthropic rejects non-object roots. When the capability is off\n * (config override) or the schema is non-object, returns an empty\n * object: the agent has already injected a soft schema hint into the\n * system prompt as the fallback, and client-side `validate()` still\n * enforces shape.\n */\n private buildStructuredOutput(responseSchema: Record<string, unknown> | undefined): {\n output_config?: Anthropic.OutputConfig;\n } {\n if (!responseSchema || !this.capabilities.structuredOutput) {\n return {};\n }\n\n if (responseSchema.type !== \"object\" || typeof responseSchema.properties !== \"object\") {\n return {};\n }\n\n return {\n output_config: {\n format: { type: \"json_schema\", schema: responseSchema },\n },\n };\n }\n\n /**\n * Concatenate every `text` content block into the single neutral\n * `content` string. `tool_use` and other block types are ignored\n * here — tool calls are surfaced separately via `extractToolCalls`.\n */\n private extractText(content: Anthropic.ContentBlock[]): string {\n return content\n .filter((block): block is Anthropic.TextBlock => block.type === \"text\")\n .map((block) => block.text)\n .join(\"\");\n }\n\n /**\n * Reshape Anthropic's `tool_use` content blocks into the neutral\n * `ModelToolCallRequest[]`. Returns `undefined` when the model\n * requested no tools so callers can branch on presence.\n */\n private extractToolCalls(\n content: Anthropic.ContentBlock[],\n ): ModelToolCallRequest[] | undefined {\n const toolUses = content.filter(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n\n if (toolUses.length === 0) {\n return undefined;\n }\n\n return toolUses.map((block) => ({\n id: block.id,\n name: block.name,\n input: (block.input ?? {}) as Record<string, unknown>,\n }));\n }\n\n /**\n * Normalize Anthropic's `usage` block into the neutral `Usage` shape.\n * Anthropic reports `input_tokens` / `output_tokens` separately with\n * no pre-summed total, so `total` is computed. Cache-read tokens are\n * surfaced as `cachedTokens` only when non-zero.\n */\n private extractUsage(raw: Anthropic.Usage): Usage {\n const input = raw.input_tokens ?? 0;\n const output = raw.output_tokens ?? 0;\n const cached = raw.cache_read_input_tokens;\n\n return {\n input,\n output,\n total: input + output,\n ...(cached !== null && cached !== undefined && cached > 0 ? { cachedTokens: cached } : {}),\n };\n }\n\n /**\n * Wrap a thrown provider error into the typed `AIError` hierarchy and\n * emit the standard error log line before it propagates. Shared by\n * every catch site so the log shape stays identical.\n */\n private logAndWrap(thrown: unknown) {\n const wrapped = wrapAnthropicError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-anthropic/src/model.ts"],"sourcesContent":["import {\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type ReasoningEffort,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport type Anthropic from \"@anthropic-ai/sdk\";\nimport type { AnthropicModelConfig } from \"./config.type\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapStopReason, toAnthropicMessages, toAnthropicTools, wrapAnthropicError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.anthropic\";\n\n/**\n * Anthropic requires `max_tokens` on every request (unlike OpenAI,\n * where it is optional). When neither the per-call option nor the\n * model config supplies one, fall back to a generous default so a\n * caller who never thought about token caps still gets a complete\n * answer instead of a 400.\n */\nconst DEFAULT_MAX_TOKENS = 4096;\n\n/**\n * Anthropic rejects an extended-thinking budget below 1024 tokens, so\n * any resolved budget is floored at this minimum before it reaches the\n * wire.\n */\nconst MIN_THINKING_BUDGET = 1024;\n\n/**\n * Map the neutral `ReasoningEffort` level to an Anthropic\n * `thinking.budget_tokens` value. Anthropic budgets reasoning by token\n * count (unlike OpenAI's opaque `reasoning_effort` enum), so the three\n * neutral levels translate to representative token budgets when the\n * caller doesn't pass an explicit `reasoning.maxTokens`.\n */\nconst EFFORT_THINKING_BUDGET: Record<ReasoningEffort, number> = {\n low: 1024,\n medium: 4096,\n high: 12000,\n};\n\n/**\n * Anthropic-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and the official `@anthropic-ai/sdk`\n * Messages API. Agents, workflows, and supervisors never talk to\n * Anthropic directly — they hold a `ModelContract`, and this class is\n * what makes that contract concrete for Claude models.\n *\n * **Responsibility.**\n * - Owns: a long-lived `Anthropic` client + frozen `ModelConfig`\n * (name, temperature, maxTokens) used as defaults for every call.\n * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into\n * Anthropic wire shapes (system hoisting, `tool_use` / `tool_result`\n * blocks) on the way out, and translating Anthropic's content-block\n * response (text, tool calls, stop reason, usage) back into the\n * neutral shapes on the way in.\n * - Does NOT own: dispatching tools, deciding whether to loop, tracking\n * conversation history, or retrying on failure — those are agent\n * concerns. The model is a stateless (per-call) protocol adapter.\n *\n * Because it holds a live client and shared defaults, it is modeled as\n * a class (see §4.2 of code-style.md — \"long-lived state across\n * calls\").\n *\n * @example\n * import Anthropic from \"@anthropic-ai/sdk\";\n * const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });\n * const model = new AnthropicModel(client, { name: \"claude-sonnet-4-6\" });\n *\n * const myAgent = agent({\n * model,\n * systemPrompt: \"You are a helpful assistant.\",\n * tools: [searchTool],\n * });\n *\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class AnthropicModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: Anthropic;\n private readonly config: AnthropicModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(\n client: Anthropic,\n config: AnthropicModelConfig,\n provider: string = \"anthropic\",\n ) {\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? true,\n vision: config.vision ?? inferVisionCapability(config.name),\n // Every modern Claude model accepts Anthropic extended thinking\n // (`thinking: { type: \"enabled\", budget_tokens }`). Advertise the\n // reasoning channel so the agent forwards `reasoning` options;\n // explicit config override wins for proxied/legacy targets.\n reasoning: config.reasoning ?? true,\n // Anthropic prompt caching is caller-driven via `cache_control`\n // breakpoints. The adapter both places those breakpoints (tools\n // when `config.promptCaching`, system when\n // `options.cacheControl.breakpoints`) and reports the read/write\n // accounting (`cachedTokens` / `cacheWriteTokens`), so advertise\n // the capability unconditionally.\n promptCaching: true,\n // The Messages API accepts PDF/document content blocks\n // (`DocumentBlockParam`) on vision-capable Claude models.\n pdf: true,\n // Anthropic has no audio input content block, so `audio` stays\n // absent (treated as false) and the agent rejects audio\n // attachments upfront rather than dropping them at the wire layer.\n };\n }\n\n /**\n * Single-shot completion. Sends the full message list to the Messages\n * endpoint, waits for the terminal response, and reshapes it into a\n * vendor-neutral `ModelResponse`. Per-call `options` override the\n * instance's `ModelConfig` defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response: Anthropic.Message;\n\n try {\n response = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: false },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const finishReason = mapStopReason(response.stop_reason);\n const usage = this.extractUsage(response.usage);\n const toolCalls = this.extractToolCalls(response.content);\n\n this.logger.debug(LOG_MODULE, \"response\", \"call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n return {\n content: this.extractText(response.content),\n finishReason,\n usage,\n toolCalls,\n };\n }\n\n /**\n * Incremental streaming completion. Yields neutral `ModelStreamChunk`s\n * — `delta` for text tokens, `tool-call` once a `tool_use` block's\n * arguments have fully accumulated, and a terminal `done` carrying the\n * final finish reason + usage totals. Callers consume it with\n * `for await`.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting streaming call to messages.create\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let stream: Awaited<ReturnType<Anthropic[\"messages\"][\"create\"]>>;\n\n try {\n stream = await this.client.messages.create(\n { ...this.buildParams(messages, options), stream: true },\n options?.signal ? { signal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n let rawStopReason: string | null = null;\n const usage: Usage = { input: 0, output: 0, total: 0 };\n const toolBlocks = new Map<number, { id: string; name: string; json: string }>();\n\n try {\n for await (const event of stream as AsyncIterable<Anthropic.RawMessageStreamEvent>) {\n if (event.type === \"message_start\") {\n usage.input = event.message.usage.input_tokens ?? 0;\n\n const cacheRead = event.message.usage.cache_read_input_tokens;\n\n if (cacheRead !== null && cacheRead !== undefined && cacheRead > 0) {\n usage.cachedTokens = cacheRead;\n }\n\n const cacheWrite = event.message.usage.cache_creation_input_tokens;\n\n if (cacheWrite !== null && cacheWrite !== undefined && cacheWrite > 0) {\n usage.cacheWriteTokens = cacheWrite;\n }\n\n continue;\n }\n\n if (event.type === \"content_block_start\") {\n const block = event.content_block;\n\n if (block.type === \"tool_use\") {\n toolBlocks.set(event.index, { id: block.id, name: block.name, json: \"\" });\n }\n\n continue;\n }\n\n if (event.type === \"content_block_delta\") {\n if (event.delta.type === \"text_delta\") {\n yield { type: \"delta\", content: event.delta.text };\n } else if (event.delta.type === \"input_json_delta\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n accumulator.json += event.delta.partial_json;\n }\n }\n\n continue;\n }\n\n if (event.type === \"content_block_stop\") {\n const accumulator = toolBlocks.get(event.index);\n\n if (accumulator) {\n yield {\n type: \"tool-call\",\n id: accumulator.id,\n name: accumulator.name,\n input: safeJsonParse<Record<string, unknown>>(accumulator.json, {}),\n };\n\n toolBlocks.delete(event.index);\n }\n\n continue;\n }\n\n if (event.type === \"message_delta\") {\n rawStopReason = event.delta.stop_reason ?? rawStopReason;\n usage.output = event.usage.output_tokens ?? usage.output;\n\n // `message_delta.usage` carries the cumulative cache counts —\n // prefer them over the `message_start` snapshot when present\n // and non-zero so the terminal `done` reflects the final tally.\n const cacheRead = event.usage.cache_read_input_tokens;\n\n if (cacheRead !== null && cacheRead !== undefined && cacheRead > 0) {\n usage.cachedTokens = cacheRead;\n }\n\n const cacheWrite = event.usage.cache_creation_input_tokens;\n\n if (cacheWrite !== null && cacheWrite !== undefined && cacheWrite > 0) {\n usage.cacheWriteTokens = cacheWrite;\n }\n }\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n usage.total = usage.input + usage.output;\n\n const finishReason = mapStopReason(rawStopReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Streaming call to messages.create succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the Anthropic request body shared by `complete()` and\n * `stream()` (each adds its own `stream` literal so the SDK's create\n * overload resolves to the right return type). Hoists the system\n * prompt out of `messages`, resolves `max_tokens` (required by\n * Anthropic) with the documented default, and conditionally attaches\n * temperature, tools, native structured output, extended thinking\n * (`reasoning`), and a system-prompt cache breakpoint (`cacheControl`).\n * Temperature is dropped when thinking is enabled, since Anthropic\n * rejects the two together.\n */\n private buildParams(\n messages: Message[],\n options: ModelCallOptions | undefined,\n ): Omit<Anthropic.MessageCreateParamsNonStreaming, \"stream\"> {\n const { system, messages: anthropicMessages } = toAnthropicMessages(messages);\n const thinking = this.buildThinking(options?.reasoning);\n const temperature = options?.temperature ?? this.config.temperature;\n\n return {\n model: this.name,\n max_tokens: options?.maxTokens ?? this.config.maxTokens ?? DEFAULT_MAX_TOKENS,\n messages: anthropicMessages,\n ...this.buildSystem(system, options?.cacheControl),\n // Extended thinking pins sampling to the default temperature —\n // Anthropic 400s when `thinking` is enabled alongside any explicit\n // `temperature`. Drop temperature in that case rather than letting\n // the request fail.\n ...(temperature !== undefined && !thinking.thinking ? { temperature } : {}),\n ...this.buildTools(options?.tools),\n ...this.buildStructuredOutput(options?.responseSchema),\n ...thinking,\n };\n }\n\n /**\n * Spread-friendly `system` fragment. Returns an empty object when no\n * system prompt was hoisted out of the messages.\n *\n * When a per-call `cacheControl.breakpoints` hint is present (≥ 1),\n * the system prompt is emitted as a single `TextBlockParam` carrying\n * `cache_control: ephemeral` — the system prompt is the longest stable\n * prefix on a turn, so one breakpoint there lets multi-turn agents\n * read it back at the ~0.1x cache-read rate. Without the hint the\n * system prompt stays a plain string (left uncached) so a one-shot\n * call never pays the ~1.25x cache-write surcharge.\n */\n private buildSystem(\n system: string | undefined,\n cacheControl: ModelCallOptions[\"cacheControl\"],\n ): { system?: string | Anthropic.TextBlockParam[] } {\n if (!system) {\n return {};\n }\n\n const breakpoints = cacheControl?.breakpoints ?? 0;\n\n if (this.capabilities.promptCaching && breakpoints > 0) {\n return {\n system: [{ type: \"text\", text: system, cache_control: { type: \"ephemeral\" } }],\n };\n }\n\n return { system };\n }\n\n /**\n * Translate the neutral `reasoning` option into Anthropic's\n * `thinking` request field. Emitted only when the model declares the\n * `reasoning` capability AND a reasoning option is supplied; otherwise\n * returns an empty object so the caller can unconditionally spread it.\n *\n * Budget resolution: an explicit `reasoning.maxTokens` wins; otherwise\n * the neutral `effort` level maps to a tiered token budget. Anthropic\n * requires `budget_tokens` ≥ 1024, so the budget is floored at that\n * minimum.\n */\n private buildThinking(\n reasoning: ModelCallOptions[\"reasoning\"],\n ): { thinking?: Anthropic.ThinkingConfigParam } {\n if (!this.capabilities.reasoning || !reasoning) {\n return {};\n }\n\n if (reasoning.maxTokens === undefined && reasoning.effort === undefined) {\n return {};\n }\n\n const budget = reasoning.maxTokens ?? EFFORT_THINKING_BUDGET[reasoning.effort ?? \"medium\"];\n\n return {\n thinking: {\n type: \"enabled\",\n budget_tokens: Math.max(MIN_THINKING_BUDGET, budget),\n },\n };\n }\n\n /**\n * Spread-friendly tools fragment. Returns an empty object when no\n * tools were supplied so the caller can unconditionally spread it.\n *\n * When `config.promptCaching` is on, marks the LAST tool with\n * `cache_control: ephemeral` — Anthropic caches the whole prefix up\n * to a breakpoint, and tools sit before `system` in that prefix, so\n * one breakpoint on the final tool caches every tool definition.\n * Reads bill at ~0.1x after the first write. The system prompt is\n * deliberately left uncached: it carries per-turn placeholders, so a\n * breakpoint there would pay the ~1.25x write surcharge every turn\n * with no reads.\n */\n private buildTools(tools: ModelCallOptions[\"tools\"]): { tools?: Anthropic.Tool[] } {\n const mapped = toAnthropicTools(tools);\n\n if (!mapped) {\n return {};\n }\n\n if (this.config.promptCaching && mapped.length > 0) {\n const last = mapped.length - 1;\n mapped[last] = { ...mapped[last], cache_control: { type: \"ephemeral\" } };\n }\n\n return { tools: mapped };\n }\n\n /**\n * Translate the neutral `responseSchema` option into Anthropic's\n * native `output_config.format` (JSON-schema structured outputs).\n *\n * Only emitted when the model declares the `structuredOutput`\n * capability AND the schema is a proper root-object JSON Schema —\n * Anthropic rejects non-object roots. When the capability is off\n * (config override) or the schema is non-object, returns an empty\n * object: the agent has already injected a soft schema hint into the\n * system prompt as the fallback, and client-side `validate()` still\n * enforces shape.\n */\n private buildStructuredOutput(responseSchema: Record<string, unknown> | undefined): {\n output_config?: Anthropic.OutputConfig;\n } {\n if (!responseSchema || !this.capabilities.structuredOutput) {\n return {};\n }\n\n if (responseSchema.type !== \"object\" || typeof responseSchema.properties !== \"object\") {\n return {};\n }\n\n return {\n output_config: {\n format: { type: \"json_schema\", schema: responseSchema },\n },\n };\n }\n\n /**\n * Concatenate every `text` content block into the single neutral\n * `content` string. `tool_use` and other block types are ignored\n * here — tool calls are surfaced separately via `extractToolCalls`.\n */\n private extractText(content: Anthropic.ContentBlock[]): string {\n return content\n .filter((block): block is Anthropic.TextBlock => block.type === \"text\")\n .map((block) => block.text)\n .join(\"\");\n }\n\n /**\n * Reshape Anthropic's `tool_use` content blocks into the neutral\n * `ModelToolCallRequest[]`. Returns `undefined` when the model\n * requested no tools so callers can branch on presence.\n */\n private extractToolCalls(\n content: Anthropic.ContentBlock[],\n ): ModelToolCallRequest[] | undefined {\n const toolUses = content.filter(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n\n if (toolUses.length === 0) {\n return undefined;\n }\n\n return toolUses.map((block) => ({\n id: block.id,\n name: block.name,\n input: (block.input ?? {}) as Record<string, unknown>,\n }));\n }\n\n /**\n * Normalize Anthropic's `usage` block into the neutral `Usage` shape.\n * Anthropic reports `input_tokens` / `output_tokens` separately with\n * no pre-summed total, so `total` is computed. Cache-read tokens are\n * surfaced as `cachedTokens` and cache-write tokens as\n * `cacheWriteTokens`, each only when non-zero.\n *\n * Note: Anthropic does not report a separate reasoning-token count —\n * extended-thinking tokens are billed inside `output_tokens` — so\n * `Usage.reasoningTokens` is intentionally left unset here. Populating\n * it would double-count against `output`.\n */\n private extractUsage(raw: Anthropic.Usage): Usage {\n const input = raw.input_tokens ?? 0;\n const output = raw.output_tokens ?? 0;\n const cacheRead = raw.cache_read_input_tokens;\n const cacheWrite = raw.cache_creation_input_tokens;\n\n return {\n input,\n output,\n total: input + output,\n ...(cacheRead !== null && cacheRead !== undefined && cacheRead > 0\n ? { cachedTokens: cacheRead }\n : {}),\n ...(cacheWrite !== null && cacheWrite !== undefined && cacheWrite > 0\n ? { cacheWriteTokens: cacheWrite }\n : {}),\n };\n }\n\n /**\n * Wrap a thrown provider error into the typed `AIError` hierarchy and\n * emit the standard error log line before it propagates. Shared by\n * every catch site so the log shape stays identical.\n */\n private logAndWrap(thrown: unknown) {\n const wrapped = wrapAnthropicError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n"],"mappings":"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"}
package/llms-full.txt CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ---
10
10
  name: setup-anthropic
11
- description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — `@warlock.js/ai-openai/setup-openai/SKILL.md`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
11
+ description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → thinking budget_tokens, prompt caching via promptCaching + options.cacheControl, cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — `@warlock.js/ai-openai/setup-openai/SKILL.md`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
12
12
  ---
13
13
 
14
14
  # `@warlock.js/ai-anthropic`
@@ -46,8 +46,12 @@ anthropic.model({ name: "claude-opus-4-7", maxTokens: 8192 }) // raise the cap
46
46
  | --- | --- |
47
47
  | `structuredOutput` | `true` (via Anthropic's native `output_config.format`) |
48
48
  | `vision` | Inferred from model name. `true` for Claude 3 / 3.5 / 3.7 / 4 family; `false` for pre-3 and unknown. |
49
+ | `reasoning` | `true` — every modern Claude model accepts extended thinking. Override with `reasoning: false` for legacy/proxied targets that reject the `thinking` param. |
50
+ | `promptCaching` | `true` — the adapter places `cache_control` breakpoints and reports both cache reads (`Usage.cachedTokens`) and writes (`Usage.cacheWriteTokens`). |
51
+ | `pdf` | `true` — the Messages API accepts PDF/document content blocks on vision-capable models. |
52
+ | `audio` | absent (`false`) — Anthropic has no audio-input block, so the agent rejects audio attachments upfront. |
49
53
 
50
- Explicit config always wins.
54
+ Explicit config always wins (`structuredOutput`, `vision`, `reasoning`).
51
55
 
52
56
  ## Pricing & cost
53
57
 
@@ -67,7 +71,19 @@ const anthropic = new AnthropicSDK({
67
71
  anthropic.model({ name: "claude-sonnet-4-6", pricing: { input: 3, output: 15 } });
68
72
  ```
69
73
 
70
- Resolution at `model()` time: per-model `pricing` > SDK registry entry for that name > `undefined`. `ModelPricing` is `{ input, output, cachedInput?, cachedOutput? }`. Anthropic meters cache **reads** — when present they surface as `usage.cachedTokens` and bill at the `cachedInput` rate (falling back to `input` when unset).
74
+ Resolution at `model()` time: per-model `pricing` > SDK registry entry for that name > `undefined`. `ModelPricing` is `{ input, output, cachedInput?, cachedOutput? }`.
75
+
76
+ ## Tokens & usage accounting
77
+
78
+ Every `ModelResponse.usage` (and the streaming terminal `done`) is normalized to the neutral `Usage` shape. Anthropic reports `input_tokens` / `output_tokens` separately with no pre-summed total, so `total` is computed (`input + output`). The two cache channels are surfaced only when non-zero:
79
+
80
+ | Neutral field | Anthropic source | Meaning / billing |
81
+ | --- | --- | --- |
82
+ | `usage.cachedTokens` | `cache_read_input_tokens` | Subset of input served from the prompt cache. Bills at `cachedInput` (falls back to `input` when unset). |
83
+ | `usage.cacheWriteTokens` | `cache_creation_input_tokens` | Input tokens **written** to the cache on this call (the ~1.25x write surcharge). Bills at `cachedOutput` when set. |
84
+ | `usage.reasoningTokens` | — *(not reported separately)* | Anthropic bills extended-thinking tokens **inside** `output_tokens`, so this stays unset — populating it would double-count against `output`. |
85
+
86
+ In streaming, `cachedTokens` / `cacheWriteTokens` are seeded from `message_start` and then **overwritten by the cumulative counts on `message_delta`** when those are present and non-zero, so the terminal `done` carries the final tally.
71
87
 
72
88
  ## `max_tokens` is required
73
89
 
@@ -88,6 +104,33 @@ Anthropic has no `"system"` role inside `messages`. The adapter hoists every neu
88
104
 
89
105
  When the agent passes `responseSchema` and the model is `structuredOutput`-capable, an **object-root** schema is forwarded as `output_config: { format: { type: "json_schema", schema } }` (Anthropic native). Non-object schemas or `structuredOutput: false` omit it; the agent's soft system-prompt hint + client-side `validate()` still enforce shape.
90
106
 
107
+ ## Extended thinking (reasoning)
108
+
109
+ When the model is `reasoning`-capable (default `true`) and the agent passes `options.reasoning`, the adapter forwards Anthropic extended thinking as `thinking: { type: "enabled", budget_tokens }`:
110
+
111
+ - `reasoning.maxTokens` → `budget_tokens` verbatim.
112
+ - `reasoning.effort` (`"low" | "medium" | "high"`) → a tiered budget (`1024` / `4096` / `12000`) when no explicit `maxTokens` is given.
113
+ - Any resolved budget is floored at Anthropic's `1024`-token minimum.
114
+ - `reasoning` with neither `effort` nor `maxTokens` emits nothing.
115
+
116
+ Because Anthropic 400s when `thinking` is combined with an explicit `temperature`, the adapter **drops `temperature`** for that request (thinking pins sampling to the default). Set `reasoning: false` on the model config to suppress the param entirely for proxied/legacy targets.
117
+
118
+ ```ts
119
+ await agent.execute("Prove it.", { reasoning: { effort: "high" } }); // budget_tokens: 12000
120
+ await model.complete(messages, { reasoning: { maxTokens: 8000 } }); // budget_tokens: 8000
121
+ ```
122
+
123
+ ## Prompt caching (`cache_control`)
124
+
125
+ Two independent breakpoint sites, both reported back via `usage.cachedTokens` / `usage.cacheWriteTokens`:
126
+
127
+ - **Tools** — set `model({ promptCaching: true })` to mark the *last* tool definition with `cache_control: ephemeral`. One breakpoint caches the whole tool prefix; off by default since a write costs ~1.25x and only pays off across multiple trips.
128
+ - **System prompt** — a per-call `options.cacheControl.breakpoints >= 1` emits the system prompt as a `TextBlockParam` carrying `cache_control: ephemeral` (the longest stable prefix on a turn). Without the hint the system prompt stays a plain string (uncached), so a one-shot call never pays the write surcharge. No system prompt → no block.
129
+
130
+ ```ts
131
+ await agent.execute("…", { cacheControl: { breakpoints: 1 } }); // cache the system prefix
132
+ ```
133
+
91
134
  ## Multipart messages (vision)
92
135
 
93
136
  `ContentPart[]` user content maps to Anthropic content blocks:
@@ -102,7 +145,7 @@ When the agent passes `responseSchema` and the model is `structuredOutput`-capab
102
145
 
103
146
  - `content_block_delta` text → `{ type: "delta", content }`
104
147
  - `tool_use` blocks accumulate `input_json_delta` fragments and emit one consolidated `{ type: "tool-call", ... }` at `content_block_stop` (no partial-arg `{}` artifact — input is parsed once, whole)
105
- - terminal `{ type: "done", finishReason, usage }` — `usage.input` from `message_start`, `usage.output` from `message_delta`, `total` computed
148
+ - terminal `{ type: "done", finishReason, usage }` — `usage.input` from `message_start`, `usage.output` from `message_delta`, `total` computed; `cachedTokens` / `cacheWriteTokens` from `message_start` then the cumulative `message_delta` counts (see [Tokens & usage accounting](#tokens--usage-accounting))
106
149
 
107
150
  ## Finish-reason mapping
108
151
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [setup-anthropic](@warlock.js/ai-anthropic/setup-anthropic/SKILL.md): Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — `@warlock.js/ai-openai/setup-openai/SKILL.md`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.
9
+ - [setup-anthropic](@warlock.js/ai-anthropic/setup-anthropic/SKILL.md): Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → thinking budget_tokens, prompt caching via promptCaching + options.cacheControl, cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — `@warlock.js/ai-openai/setup-openai/SKILL.md`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.
package/package.json CHANGED
@@ -15,12 +15,12 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@anthropic-ai/sdk": "^0.96.0",
18
- "@warlock.js/logger": "4.2.11"
18
+ "@warlock.js/logger": "4.3.0"
19
19
  },
20
20
  "peerDependencies": {
21
- "@warlock.js/ai": "4.2.11"
21
+ "@warlock.js/ai": "4.3.0"
22
22
  },
23
- "version": "4.2.11",
23
+ "version": "4.3.0",
24
24
  "main": "./cjs/index.cjs",
25
25
  "module": "./esm/index.mjs",
26
26
  "types": "./esm/index.d.mts",
package/skills/README.md CHANGED
@@ -6,4 +6,4 @@ Per-task skills. All cross-references use the form `@warlock.js/<pkg>/<skill>/SK
6
6
 
7
7
  ### [`setup-anthropic/`](./setup-anthropic/SKILL.md)
8
8
 
9
- Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), no first-party embeddings. Load when wiring a Claude-backed model into a @warlock.js agent.
9
+ Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → thinking budget_tokens, prompt caching via promptCaching + options.cacheControl with cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Load when wiring a Claude-backed model into a @warlock.js agent.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-anthropic
3
- description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — `@warlock.js/ai-openai/setup-openai/SKILL.md`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
3
+ description: 'Wire @warlock.js/ai-anthropic — new AnthropicSDK({apiKey, baseURL?, provider?}) for Claude, .model({name, vision?, structuredOutput?, reasoning?, promptCaching?, maxTokens?}). System-prompt hoisting, max_tokens required (default 4096), extended thinking via options.reasoning → thinking budget_tokens, prompt caching via promptCaching + options.cacheControl, cost-truth usage (cachedTokens/cacheWriteTokens), no first-party embeddings. Triggers: `AnthropicSDK`, `anthropic.model`, `anthropic.count`, `maxTokens`, `reasoning`, `thinking`, `budget_tokens`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`, `claude-sonnet-4-6`, `claude-haiku-4-5`, `claude-opus-4-7`; "wire claude into warlock agent", "configure anthropic provider", "use claude sonnet", "anthropic gateway baseURL", "claude extended thinking", "anthropic prompt caching cost"; typical import `import { AnthropicSDK } from "@warlock.js/ai-anthropic"`. Skip: embeddings — `@warlock.js/ai-openai/setup-openai/SKILL.md`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-bedrock`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`; raw `@anthropic-ai/sdk`; Vercel `@ai-sdk/anthropic`.'
4
4
  ---
5
5
 
6
6
  # `@warlock.js/ai-anthropic`
@@ -38,8 +38,12 @@ anthropic.model({ name: "claude-opus-4-7", maxTokens: 8192 }) // raise the cap
38
38
  | --- | --- |
39
39
  | `structuredOutput` | `true` (via Anthropic's native `output_config.format`) |
40
40
  | `vision` | Inferred from model name. `true` for Claude 3 / 3.5 / 3.7 / 4 family; `false` for pre-3 and unknown. |
41
+ | `reasoning` | `true` — every modern Claude model accepts extended thinking. Override with `reasoning: false` for legacy/proxied targets that reject the `thinking` param. |
42
+ | `promptCaching` | `true` — the adapter places `cache_control` breakpoints and reports both cache reads (`Usage.cachedTokens`) and writes (`Usage.cacheWriteTokens`). |
43
+ | `pdf` | `true` — the Messages API accepts PDF/document content blocks on vision-capable models. |
44
+ | `audio` | absent (`false`) — Anthropic has no audio-input block, so the agent rejects audio attachments upfront. |
41
45
 
42
- Explicit config always wins.
46
+ Explicit config always wins (`structuredOutput`, `vision`, `reasoning`).
43
47
 
44
48
  ## Pricing & cost
45
49
 
@@ -59,7 +63,19 @@ const anthropic = new AnthropicSDK({
59
63
  anthropic.model({ name: "claude-sonnet-4-6", pricing: { input: 3, output: 15 } });
60
64
  ```
61
65
 
62
- Resolution at `model()` time: per-model `pricing` > SDK registry entry for that name > `undefined`. `ModelPricing` is `{ input, output, cachedInput?, cachedOutput? }`. Anthropic meters cache **reads** — when present they surface as `usage.cachedTokens` and bill at the `cachedInput` rate (falling back to `input` when unset).
66
+ Resolution at `model()` time: per-model `pricing` > SDK registry entry for that name > `undefined`. `ModelPricing` is `{ input, output, cachedInput?, cachedOutput? }`.
67
+
68
+ ## Tokens & usage accounting
69
+
70
+ Every `ModelResponse.usage` (and the streaming terminal `done`) is normalized to the neutral `Usage` shape. Anthropic reports `input_tokens` / `output_tokens` separately with no pre-summed total, so `total` is computed (`input + output`). The two cache channels are surfaced only when non-zero:
71
+
72
+ | Neutral field | Anthropic source | Meaning / billing |
73
+ | --- | --- | --- |
74
+ | `usage.cachedTokens` | `cache_read_input_tokens` | Subset of input served from the prompt cache. Bills at `cachedInput` (falls back to `input` when unset). |
75
+ | `usage.cacheWriteTokens` | `cache_creation_input_tokens` | Input tokens **written** to the cache on this call (the ~1.25x write surcharge). Bills at `cachedOutput` when set. |
76
+ | `usage.reasoningTokens` | — *(not reported separately)* | Anthropic bills extended-thinking tokens **inside** `output_tokens`, so this stays unset — populating it would double-count against `output`. |
77
+
78
+ In streaming, `cachedTokens` / `cacheWriteTokens` are seeded from `message_start` and then **overwritten by the cumulative counts on `message_delta`** when those are present and non-zero, so the terminal `done` carries the final tally.
63
79
 
64
80
  ## `max_tokens` is required
65
81
 
@@ -80,6 +96,33 @@ Anthropic has no `"system"` role inside `messages`. The adapter hoists every neu
80
96
 
81
97
  When the agent passes `responseSchema` and the model is `structuredOutput`-capable, an **object-root** schema is forwarded as `output_config: { format: { type: "json_schema", schema } }` (Anthropic native). Non-object schemas or `structuredOutput: false` omit it; the agent's soft system-prompt hint + client-side `validate()` still enforce shape.
82
98
 
99
+ ## Extended thinking (reasoning)
100
+
101
+ When the model is `reasoning`-capable (default `true`) and the agent passes `options.reasoning`, the adapter forwards Anthropic extended thinking as `thinking: { type: "enabled", budget_tokens }`:
102
+
103
+ - `reasoning.maxTokens` → `budget_tokens` verbatim.
104
+ - `reasoning.effort` (`"low" | "medium" | "high"`) → a tiered budget (`1024` / `4096` / `12000`) when no explicit `maxTokens` is given.
105
+ - Any resolved budget is floored at Anthropic's `1024`-token minimum.
106
+ - `reasoning` with neither `effort` nor `maxTokens` emits nothing.
107
+
108
+ Because Anthropic 400s when `thinking` is combined with an explicit `temperature`, the adapter **drops `temperature`** for that request (thinking pins sampling to the default). Set `reasoning: false` on the model config to suppress the param entirely for proxied/legacy targets.
109
+
110
+ ```ts
111
+ await agent.execute("Prove it.", { reasoning: { effort: "high" } }); // budget_tokens: 12000
112
+ await model.complete(messages, { reasoning: { maxTokens: 8000 } }); // budget_tokens: 8000
113
+ ```
114
+
115
+ ## Prompt caching (`cache_control`)
116
+
117
+ Two independent breakpoint sites, both reported back via `usage.cachedTokens` / `usage.cacheWriteTokens`:
118
+
119
+ - **Tools** — set `model({ promptCaching: true })` to mark the *last* tool definition with `cache_control: ephemeral`. One breakpoint caches the whole tool prefix; off by default since a write costs ~1.25x and only pays off across multiple trips.
120
+ - **System prompt** — a per-call `options.cacheControl.breakpoints >= 1` emits the system prompt as a `TextBlockParam` carrying `cache_control: ephemeral` (the longest stable prefix on a turn). Without the hint the system prompt stays a plain string (uncached), so a one-shot call never pays the write surcharge. No system prompt → no block.
121
+
122
+ ```ts
123
+ await agent.execute("…", { cacheControl: { breakpoints: 1 } }); // cache the system prefix
124
+ ```
125
+
83
126
  ## Multipart messages (vision)
84
127
 
85
128
  `ContentPart[]` user content maps to Anthropic content blocks:
@@ -94,7 +137,7 @@ When the agent passes `responseSchema` and the model is `structuredOutput`-capab
94
137
 
95
138
  - `content_block_delta` text → `{ type: "delta", content }`
96
139
  - `tool_use` blocks accumulate `input_json_delta` fragments and emit one consolidated `{ type: "tool-call", ... }` at `content_block_stop` (no partial-arg `{}` artifact — input is parsed once, whole)
97
- - terminal `{ type: "done", finishReason, usage }` — `usage.input` from `message_start`, `usage.output` from `message_delta`, `total` computed
140
+ - terminal `{ type: "done", finishReason, usage }` — `usage.input` from `message_start`, `usage.output` from `message_delta`, `total` computed; `cachedTokens` / `cacheWriteTokens` from `message_start` then the cumulative `message_delta` counts (see [Tokens & usage accounting](#tokens--usage-accounting))
98
141
 
99
142
  ## Finish-reason mapping
100
143