@warlock.js/ai-bedrock 5.30.0 → 5.31.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
@@ -4,6 +4,33 @@ All notable changes to `@warlock.js/ai-bedrock` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.31.0 - 2026-10-18
8
+
9
+ ### Fixed
10
+
11
+ - Usage is marked `inputIncludesCache: false` (Converse `inputTokens` excludes cache reads and writes) and reports 1-hour cache writes from `cacheDetails` in `cacheWrite1hTokens`, so cost bills each cache token once at its own rate.
12
+ - A `toolUse` block with `type: "server_tool_use"` (a system tool run by Bedrock) is no longer returned as a tool call for the agent to dispatch.
13
+ - Converse conversion compiles with strict indexed-access checks while preserving empty and system-only histories.
14
+ - Provider body options merge plain-object fields one level deep, preserving neutral reasoning, generation and structured-output settings unless the caller overrides their keys.
15
+ - Converse conversion combines adjacent same-role turns in content order, preserving parallel tool results and automatic compaction summaries beside assistant tool calls.
16
+
17
+ ### Added
18
+
19
+ - With a `cacheControl` breakpoint, a `cachePoint` is also placed after the system prompt and, for Claude, after the tool definitions — the prefix segments Bedrock caches in `tools` → `system` → `messages` order. `ttl: "1h"` (per call, or the new `promptCacheTtl` model option) is sent only to models that accept it.
20
+ - Built-in Claude prices for `global.` and `us.` inference profiles (`BEDROCK_GLOBAL_PRICING`, `BEDROCK_US_PRICING`, `bedrockPricingFor`), from AWS's Bedrock price list as of 2026-10-17; caller pricing for Claude models gets the 1.25x / 2x cache-write rates filled (`withClaudeCacheWriteRates`). `complete()` and the streaming `done` chunk attach `usage.cost` when priced.
21
+ - Claude 5 families and Nova 2 Lite are recognised as prompt-caching capable.
22
+ - `ServiceUnavailableException` throws `ProviderOverloadedError`; wrapped errors carry `status`, `billed` and `retryable`.
23
+ - `new BedrockSDK({ apiKey, region })` authenticates with an Amazon Bedrock API key: requests carry `Authorization: Bearer <apiKey>` instead of a SigV4 signature and the AWS credential chain is not consulted (the adapter sets the AWS SDK's `token` and `authSchemePreference: ["httpBearerAuth"]`). An empty key, or a key combined with `credentials` or `token`, throws `InvalidRequestError`.
24
+ - An adapter contract test verifies that aborting `signal` rejects `complete()` and `stream()` and cancels the HTTP/2 request.
25
+ - With `streamChunks`, `stream()` yields `reasoning` for `reasoningContent.text` deltas and `tool-args` for client `toolUse.input` deltas.
26
+ - `ai.providerTool({ provider: "bedrock", definition })` appends a Converse `Tool` member such as `{ systemTool: { name: "nova_grounding" } }` verbatim; other providers' tools are skipped with a warning.
27
+ - `providerOptions.bedrock.body` forwards native per-call request options for completions and streams while protecting the model, conversation and tool definitions.
28
+ - Models expose native `countTokens()` preflight using the generation request's Converse conversation, tools and additional model fields.
29
+
30
+ ### Upgrading
31
+
32
+ - Calls that ask for `cacheControl` breakpoints now also write the system prompt and (Claude) tool definitions to the cache. Writes cost more than plain input on the first call; repeated prefixes then read at the cache rate.
33
+
7
34
  ## 5.30.0 - 2026-10-16
8
35
 
9
36
  ### Changed
@@ -85,6 +112,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
85
112
  ### Changed
86
113
 
87
114
  - Lockstep release maintenance and dependency refresh.
115
+
88
116
  ## 5.20.0 - 2026-09-24
89
117
 
90
118
  ### Changed
package/cjs/index.cjs CHANGED
@@ -147,9 +147,22 @@ function toBedrockMessages(messages) {
147
147
  }
148
148
  return {
149
149
  system: system.length > 0 ? system : void 0,
150
- messages: mapped
150
+ messages: mergeAdjacentTurns(mapped)
151
151
  };
152
152
  }
153
+ /** Converse requires alternating turns; preserve every content block in order. */
154
+ function mergeAdjacentTurns(messages) {
155
+ const merged = [];
156
+ for (const message of messages) {
157
+ const previous = merged[merged.length - 1];
158
+ if (previous && previous.role === message.role) previous.content.push(...message.content ?? []);
159
+ else merged.push({
160
+ ...message,
161
+ content: [...message.content ?? []]
162
+ });
163
+ }
164
+ return merged;
165
+ }
153
166
  /**
154
167
  * Recover opaque Converse reasoning blocks retained on a prior tool call.
155
168
  * Bedrock requires them to be replayed before the assistant's text and
@@ -293,6 +306,10 @@ function wrapBedrockError(thrown) {
293
306
  cause: thrown,
294
307
  context
295
308
  });
309
+ if (shape.name === "ServiceUnavailableException" || shape.httpStatusCode === 503) return new _warlock_js_ai.ProviderOverloadedError(message, {
310
+ cause: thrown,
311
+ context
312
+ });
296
313
  if (shape.name === "ValidationException") {
297
314
  if (/too long|context window|maximum context|exceeds the maximum/i.test(message)) return new _warlock_js_ai.ContextLengthExceededError(message, {
298
315
  cause: thrown,
@@ -465,6 +482,38 @@ var BedrockEmbedder = class {
465
482
  }
466
483
  };
467
484
 
485
+ //#endregion
486
+ //#region ../../packages/ai-bedrock/src/provider-options.ts
487
+ const RESERVED_FIELDS = new Set([
488
+ "modelId",
489
+ "messages",
490
+ "system",
491
+ "toolConfig"
492
+ ]);
493
+ function mergeProviderBody(built, options) {
494
+ const body = (options?.providerOptions?.bedrock)?.body ?? {};
495
+ const reserved = Object.keys(body).filter((key) => RESERVED_FIELDS.has(key));
496
+ if (reserved.length > 0) throw new _warlock_js_ai.InvalidRequestError(`providerOptions.bedrock.body cannot set ${reserved.join(", ")}: use the call's messages, tools and model options.`, { context: { fields: reserved } });
497
+ const defaults = built;
498
+ const overrides = Object.fromEntries(Object.entries(body).map(([key, value]) => {
499
+ const previous = defaults[key];
500
+ return [key, isPlainObject(previous) && isPlainObject(value) ? {
501
+ ...previous,
502
+ ...value
503
+ } : value];
504
+ }));
505
+ return {
506
+ ...built,
507
+ ...overrides
508
+ };
509
+ }
510
+ /** Merge only plain objects; arrays, null and provider scalar values replace the field. */
511
+ function isPlainObject(value) {
512
+ if (value === null || typeof value !== "object") return false;
513
+ const prototype = Object.getPrototypeOf(value);
514
+ return prototype === Object.prototype || prototype === null;
515
+ }
516
+
468
517
  //#endregion
469
518
  //#region ../../packages/ai-bedrock/src/known-capabilities.ts
470
519
  /**
@@ -498,8 +547,10 @@ const REASONING_CAPABLE_SUBSTRINGS = [
498
547
  ];
499
548
  /**
500
549
  * Families that honor Converse `cachePoint` prompt-cache breakpoints.
501
- * Anthropic Claude 3.5+ / 3.7 / 4 and the Amazon Nova line support
502
- * cache points; text-only legacy families do not.
550
+ * Anthropic Claude 3.5+ / 3.7 / 4 / 5 and the Amazon Nova line support
551
+ * cache points; text-only legacy families do not. Per the "Supported
552
+ * models" table of https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
553
+ * (checked 2026-10-17).
503
554
  */
504
555
  const PROMPT_CACHING_CAPABLE_SUBSTRINGS = [
505
556
  "claude-3-5",
@@ -507,6 +558,12 @@ const PROMPT_CACHING_CAPABLE_SUBSTRINGS = [
507
558
  "claude-sonnet-4",
508
559
  "claude-opus-4",
509
560
  "claude-haiku-4",
561
+ "claude-sonnet-5",
562
+ "claude-opus-5",
563
+ "claude-haiku-5",
564
+ "claude-fable-5",
565
+ "claude-mythos-5",
566
+ "nova-2-lite",
510
567
  "nova-lite",
511
568
  "nova-pro",
512
569
  "nova-premier",
@@ -570,6 +627,133 @@ function inferPromptCachingCapability(modelId) {
570
627
  function inferPdfCapability(modelId) {
571
628
  return matchesAny(modelId, PDF_CAPABLE_SUBSTRINGS);
572
629
  }
630
+ /**
631
+ * Claude on Bedrock accepts a `cachePoint` in `system`, `messages` and
632
+ * `tools`; Amazon Nova accepts one only in `system` and `messages`
633
+ * (https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html,
634
+ * "Fields that accept prompt cache checkpoints", checked 2026-10-17).
635
+ * Unknown families get no tool cache point.
636
+ *
637
+ * @example
638
+ * supportsToolCachePoint("global.anthropic.claude-sonnet-4-6"); // true
639
+ * supportsToolCachePoint("amazon.nova-pro-v1:0"); // false
640
+ */
641
+ function supportsToolCachePoint(modelId) {
642
+ return modelId.toLowerCase().includes("anthropic.claude");
643
+ }
644
+ /** Claude families whose cache points accept the 1-hour TTL. */
645
+ const ONE_HOUR_TTL_PATTERN = /anthropic\.claude-(?:(?:opus|sonnet|haiku|fable|mythos)-5|opus-4-[5-9]|sonnet-4-[5-9]|haiku-4-5)/;
646
+ /**
647
+ * Claude models whose Converse `cachePoint` accepts `ttl: "1h"`: Claude
648
+ * Haiku 4.5, the 4.5+ Sonnet / Opus line and every Claude 5 family. Any
649
+ * other model gets no `ttl` field (the 5-minute default), since AWS
650
+ * warns that sending it to a 5-minute-only model "can result in a
651
+ * ValidationException" (same page, checked 2026-10-17).
652
+ *
653
+ * @example
654
+ * supportsOneHourCacheTtl("anthropic.claude-haiku-4-5-20251001-v1:0"); // true
655
+ * supportsOneHourCacheTtl("anthropic.claude-3-7-sonnet-20250219-v1:0"); // false
656
+ */
657
+ function supportsOneHourCacheTtl(modelId) {
658
+ return ONE_HOUR_TTL_PATTERN.test(modelId.toLowerCase());
659
+ }
660
+
661
+ //#endregion
662
+ //#region ../../packages/ai-bedrock/src/known-pricing.ts
663
+ /**
664
+ * Bedrock on-demand list prices for Anthropic Claude, USD per 1,000,000
665
+ * tokens, keyed by Bedrock model id (without an inference-profile
666
+ * prefix). `cacheWrite` is the 5-minute write rate, `cacheWrite1h` the
667
+ * 1-hour write rate, `cachedInput` the cache-read rate.
668
+ *
669
+ * Source: https://aws.amazon.com/bedrock/pricing/ — the page's own price
670
+ * feed for "US East (N. Virginia)", fetched 2026-10-17 (Sonnet 5.5 cache
671
+ * reads already at the rate effective 2026-10-07). Model ids from
672
+ * https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html.
673
+ *
674
+ * Two tables, because AWS prices them differently:
675
+ * - {@link BEDROCK_GLOBAL_PRICING} — `global.` cross-Region inference
676
+ * profiles.
677
+ * - {@link BEDROCK_US_PRICING} — `us.` geographic profiles (the
678
+ * "Geo / In-region" rates, 10% above global for these models).
679
+ *
680
+ * In-Region ids without a profile prefix, other geographies (`eu.`,
681
+ * `apac.`, …), Claude Haiku 5.5 and non-Claude models stay unpriced —
682
+ * their rates vary by Region or were not verified — so pass `pricing`
683
+ * yourself for them.
684
+ */
685
+ const BEDROCK_GLOBAL_PRICING = {
686
+ "anthropic.claude-sonnet-5-5": claude(2, 10, .1),
687
+ "anthropic.claude-opus-5-5": claude(4, 20, .2),
688
+ "anthropic.claude-fable-5-1": claude(10, 50, .25),
689
+ "anthropic.claude-opus-5": claude(5, 25, .5),
690
+ "anthropic.claude-sonnet-5": claude(2, 10, .2),
691
+ "anthropic.claude-fable-5": claude(10, 50, 1),
692
+ "anthropic.claude-opus-4-8": claude(5, 25, .5),
693
+ "anthropic.claude-opus-4-7": claude(5, 25, .5),
694
+ "anthropic.claude-opus-4-6-v1": claude(5, 25, .5),
695
+ "anthropic.claude-sonnet-4-6": claude(3, 15, .3),
696
+ "anthropic.claude-opus-4-5-20251101-v1:0": claude(5, 25, .5),
697
+ "anthropic.claude-sonnet-4-5-20250929-v1:0": claude(3, 15, .3),
698
+ "anthropic.claude-haiku-4-5-20251001-v1:0": claude(1, 5, .1)
699
+ };
700
+ /** `us.` geographic-profile prices — see {@link BEDROCK_GLOBAL_PRICING}. */
701
+ const BEDROCK_US_PRICING = {
702
+ "anthropic.claude-sonnet-5-5": claude(2.2, 11, .11),
703
+ "anthropic.claude-opus-5-5": claude(4.4, 22, .22),
704
+ "anthropic.claude-fable-5-1": claude(11, 55, .275),
705
+ "anthropic.claude-opus-5": claude(5.5, 27.5, .55),
706
+ "anthropic.claude-sonnet-5": claude(2.2, 11, .22),
707
+ "anthropic.claude-fable-5": claude(11, 55, 1.1),
708
+ "anthropic.claude-opus-4-8": claude(5.5, 27.5, .55),
709
+ "anthropic.claude-opus-4-7": claude(5.5, 27.5, .55),
710
+ "anthropic.claude-opus-4-6-v1": claude(5.5, 27.5, .55),
711
+ "anthropic.claude-sonnet-4-6": claude(3.3, 16.5, .33),
712
+ "anthropic.claude-opus-4-5-20251101-v1:0": claude(5.5, 27.5, .55),
713
+ "anthropic.claude-sonnet-4-5-20250929-v1:0": claude(3.3, 16.5, .33),
714
+ "anthropic.claude-haiku-4-5-20251001-v1:0": claude(1.1, 5.5, .11)
715
+ };
716
+ /**
717
+ * Look up the built-in price for a Bedrock model id or inference-profile
718
+ * id: `global.<model>` reads {@link BEDROCK_GLOBAL_PRICING}, `us.<model>`
719
+ * reads {@link BEDROCK_US_PRICING}; anything else is `undefined`.
720
+ *
721
+ * @example
722
+ * bedrockPricingFor("global.anthropic.claude-sonnet-4-6"); // { input: 3, output: 15, … }
723
+ * bedrockPricingFor("anthropic.claude-sonnet-4-6"); // undefined — in-Region rates vary
724
+ */
725
+ function bedrockPricingFor(modelId) {
726
+ if (modelId.startsWith("global.")) return BEDROCK_GLOBAL_PRICING[modelId.slice(7)];
727
+ if (modelId.startsWith("us.")) return BEDROCK_US_PRICING[modelId.slice(3)];
728
+ }
729
+ /**
730
+ * Fill the Claude cache-write rates (5-minute 1.25x, 1-hour 2x the input
731
+ * rate — the ratio of every Claude row in the tables above) into a
732
+ * caller-supplied entry for a Claude model that omits them. Non-Claude
733
+ * models are returned unchanged.
734
+ */
735
+ function withClaudeCacheWriteRates(modelId, pricing) {
736
+ if (!pricing || !modelId.toLowerCase().includes("anthropic.claude")) return pricing;
737
+ if (pricing.cacheWrite !== void 0 && pricing.cacheWrite1h !== void 0) return pricing;
738
+ return {
739
+ ...pricing,
740
+ cacheWrite: pricing.cacheWrite ?? pricing.cachedOutput ?? round(pricing.input * 1.25),
741
+ cacheWrite1h: pricing.cacheWrite1h ?? round(pricing.input * 2)
742
+ };
743
+ }
744
+ function claude(input, output, cachedInput) {
745
+ return {
746
+ input,
747
+ output,
748
+ cachedInput,
749
+ cacheWrite: round(input * 1.25),
750
+ cacheWrite1h: round(input * 2)
751
+ };
752
+ }
753
+ /** Trim binary floating-point noise (2.2 * 1.25 = 2.7500000000000004). */
754
+ function round(value) {
755
+ return Math.round(value * 1e9) / 1e9;
756
+ }
573
757
 
574
758
  //#endregion
575
759
  //#region ../../packages/ai-bedrock/src/known-vision-models.ts
@@ -622,6 +806,10 @@ function inferVisionCapability(modelId) {
622
806
  //#endregion
623
807
  //#region ../../packages/ai-bedrock/src/model.ts
624
808
  const LOG_MODULE = "ai.bedrock";
809
+ /** `provider` value of `ai.providerTool()` entries this adapter sends verbatim. */
810
+ const BEDROCK_WIRE_FORMAT = "bedrock";
811
+ /** Converse `ToolUseBlock.type` of a tool use run by Bedrock itself (SDK `ToolUseType`). */
812
+ const SERVER_TOOL_USE = "server_tool_use";
625
813
  /** Default output allowance added to a thinking budget when maxTokens is unset. */
626
814
  const DEFAULT_MAX_TOKENS = 4096;
627
815
  const MIN_THINKING_BUDGET = 1024;
@@ -682,7 +870,7 @@ var BedrockModel = class {
682
870
  this.config = config;
683
871
  this.name = config.name;
684
872
  this.provider = provider;
685
- this.pricing = config.pricing;
873
+ this.pricing = withClaudeCacheWriteRates(config.name, config.pricing ?? bedrockPricingFor(config.name));
686
874
  this.capabilities = {
687
875
  structuredOutput: config.structuredOutput ?? true,
688
876
  vision: config.vision ?? inferVisionCapability(config.name),
@@ -713,7 +901,7 @@ var BedrockModel = class {
713
901
  }
714
902
  const blocks = response.output?.message?.content ?? [];
715
903
  const finishReason = mapStopReason(response.stopReason);
716
- const usage = this.extractUsage(response.usage);
904
+ const usage = (0, _warlock_js_ai.attachCost)(this.extractUsage(response.usage), this.pricing);
717
905
  const toolCalls = this.extractToolCalls(blocks);
718
906
  this.logger.debug(LOG_MODULE, "response", "Converse call succeeded", {
719
907
  finishReason,
@@ -726,11 +914,36 @@ var BedrockModel = class {
726
914
  toolCalls
727
915
  };
728
916
  }
917
+ /** Count native input tokens from the same Converse conversation and tool configuration. */
918
+ async countTokens(messages, options) {
919
+ const request = this.buildRequest(messages, options);
920
+ try {
921
+ const response = await this.client.send(new _aws_sdk_client_bedrock_runtime.CountTokensCommand({
922
+ modelId: request.modelId,
923
+ input: { converse: {
924
+ messages: request.messages,
925
+ system: request.system,
926
+ toolConfig: request.toolConfig,
927
+ additionalModelRequestFields: request.additionalModelRequestFields
928
+ } }
929
+ }), options?.signal ? { abortSignal: options.signal } : void 0);
930
+ if (response.inputTokens === void 0) throw new _warlock_js_ai.InvalidRequestError("Bedrock countTokens returned no inputTokens.");
931
+ return response.inputTokens;
932
+ } catch (thrown) {
933
+ throw this.logAndWrap(thrown);
934
+ }
935
+ }
729
936
  /**
730
937
  * Incremental streaming completion via ConverseStream. Yields neutral
731
938
  * `ModelStreamChunk`s — `delta` for text, `tool-call` once a
732
939
  * `toolUse` block's accumulated input JSON is complete, and a
733
940
  * terminal `done` with the final finish reason + usage totals.
941
+ *
942
+ * With `options.streamChunks` it also yields `reasoning` for each
943
+ * `reasoningContent.text` delta and `tool-args` for each client
944
+ * `toolUse.input` delta. Usage arrives once, in the final `metadata`
945
+ * event, so no `usage` chunk is emitted. A system tool's
946
+ * `server_tool_use` block never becomes a `tool-call`.
734
947
  */
735
948
  async *stream(messages, options) {
736
949
  this.logger.debug(LOG_MODULE, "request", "Starting ConverseStream call", {
@@ -746,11 +959,12 @@ var BedrockModel = class {
746
959
  throw this.logAndWrap(thrown);
747
960
  }
748
961
  let rawStopReason;
749
- const usage = {
962
+ let usage = {
750
963
  input: 0,
751
964
  output: 0,
752
965
  total: 0
753
966
  };
967
+ const want = (0, _warlock_js_ai.resolveStreamChunks)(options);
754
968
  const toolBlocks = /* @__PURE__ */ new Map();
755
969
  const reasoningBlocks = /* @__PURE__ */ new Map();
756
970
  const completedReasoningBlocks = [];
@@ -758,6 +972,7 @@ var BedrockModel = class {
758
972
  for await (const event of response.stream ?? []) {
759
973
  if (event.contentBlockStart?.start?.toolUse) {
760
974
  const start = event.contentBlockStart.start.toolUse;
975
+ if (start.type === SERVER_TOOL_USE) continue;
761
976
  toolBlocks.set(event.contentBlockStart.contentBlockIndex ?? 0, {
762
977
  id: start.toolUseId ?? "",
763
978
  name: start.name ?? "",
@@ -773,11 +988,25 @@ var BedrockModel = class {
773
988
  };
774
989
  else if (delta.toolUse) {
775
990
  const accumulator = toolBlocks.get(event.contentBlockDelta.contentBlockIndex ?? 0);
776
- if (accumulator) accumulator.json += delta.toolUse.input ?? "";
991
+ if (accumulator) {
992
+ accumulator.json += delta.toolUse.input ?? "";
993
+ if (want.toolArgs && delta.toolUse.input) yield {
994
+ type: "tool-args",
995
+ callId: accumulator.id,
996
+ name: accumulator.name,
997
+ delta: delta.toolUse.input
998
+ };
999
+ }
777
1000
  } else if (delta.reasoningContent) {
778
1001
  const index = event.contentBlockDelta.contentBlockIndex ?? 0;
779
1002
  const accumulator = reasoningBlocks.get(index) ?? {};
780
- if (delta.reasoningContent.text !== void 0) accumulator.text = (accumulator.text ?? "") + delta.reasoningContent.text;
1003
+ if (delta.reasoningContent.text !== void 0) {
1004
+ accumulator.text = (accumulator.text ?? "") + delta.reasoningContent.text;
1005
+ if (want.reasoning && delta.reasoningContent.text !== "") yield {
1006
+ type: "reasoning",
1007
+ delta: delta.reasoningContent.text
1008
+ };
1009
+ }
781
1010
  if (delta.reasoningContent.signature !== void 0) accumulator.signature = delta.reasoningContent.signature;
782
1011
  if (delta.reasoningContent.redactedContent !== void 0) accumulator.redactedContent = delta.reasoningContent.redactedContent;
783
1012
  reasoningBlocks.set(index, accumulator);
@@ -806,19 +1035,13 @@ var BedrockModel = class {
806
1035
  continue;
807
1036
  }
808
1037
  if (event.messageStop) rawStopReason = event.messageStop.stopReason;
809
- if (event.metadata?.usage) {
810
- const raw = event.metadata.usage;
811
- usage.input = raw.inputTokens ?? 0;
812
- usage.output = raw.outputTokens ?? 0;
813
- usage.total = raw.totalTokens ?? usage.input + usage.output;
814
- if (raw.cacheReadInputTokens && raw.cacheReadInputTokens > 0) usage.cachedTokens = raw.cacheReadInputTokens;
815
- if (raw.cacheWriteInputTokens && raw.cacheWriteInputTokens > 0) usage.cacheWriteTokens = raw.cacheWriteInputTokens;
816
- }
1038
+ if (event.metadata?.usage) usage = this.extractUsage(event.metadata.usage);
817
1039
  }
818
1040
  } catch (thrown) {
819
1041
  throw this.logAndWrap(thrown);
820
1042
  }
821
1043
  const finishReason = mapStopReason(rawStopReason);
1044
+ (0, _warlock_js_ai.attachCost)(usage, this.pricing);
822
1045
  this.logger.debug(LOG_MODULE, "response", "ConverseStream call succeeded", {
823
1046
  finishReason,
824
1047
  usage
@@ -841,44 +1064,58 @@ var BedrockModel = class {
841
1064
  const configuredMaxTokens = options?.maxTokens ?? this.config.maxTokens;
842
1065
  const maxTokens = this.resolveMaxTokens(configuredMaxTokens, thinkingBudget);
843
1066
  const temperature = options?.temperature ?? this.config.temperature;
844
- const cachedMessages = this.applyCacheBreakpoints(bedrockMessages, options?.cacheControl);
845
- return {
1067
+ const cachePoint = this.resolveCachePoint(options?.cacheControl);
1068
+ const cachedMessages = this.applyCacheBreakpoints(bedrockMessages, cachePoint);
1069
+ const cachedSystem = system && cachePoint ? [...system, { cachePoint }] : system;
1070
+ return mergeProviderBody({
846
1071
  modelId: this.name,
847
1072
  messages: cachedMessages,
848
- ...system ? { system } : {},
1073
+ ...cachedSystem ? { system: cachedSystem } : {},
849
1074
  inferenceConfig: {
850
1075
  ...maxTokens !== void 0 ? { maxTokens } : {},
851
1076
  ...temperature !== void 0 ? { temperature } : {}
852
1077
  },
853
- ...this.buildToolConfig(options?.tools),
1078
+ ...this.buildToolConfig(options, cachePoint),
854
1079
  ...this.buildOutputConfig(options?.responseSchema),
855
1080
  ...this.buildReasoningConfig(thinkingBudget)
856
- };
1081
+ }, options);
857
1082
  }
858
1083
  /**
859
- * Append a Converse `cachePoint` block to the LAST message when the
860
- * caller supplies a `cacheControl` write breakpoint and the model is
861
- * `promptCaching`-capable. A cache point tells Bedrock to cache the
862
- * whole prefix up to that block, so subsequent calls reusing the same
863
- * prefix bill the cached portion at the discounted read rate
864
- * (surfaced as `Usage.cachedTokens`). No-ops gracefully when caching
865
- * is unsupported, no breakpoint was requested, or there are no
866
- * messages to mark — Bedrock then prices the call normally.
1084
+ * Resolve the Converse `cachePoint` to place for this call, or
1085
+ * `undefined` when no cache point applies (the model is not
1086
+ * `promptCaching`-capable or no `cacheControl` breakpoint was asked
1087
+ * for). Bedrock only honors `type: "default"`; the neutral
1088
+ * `breakpoints` count is a presence hint.
867
1089
  *
868
- * Bedrock only honors `CachePointType.DEFAULT`; the neutral
869
- * `breakpoints` count is a presence hint (one trailing breakpoint is
870
- * the only placement Converse supports without manual block surgery),
871
- * so any positive value marks the trailing message.
1090
+ * The lifetime is the per-call `cacheControl.ttl`, else the model's
1091
+ * `promptCacheTtl`. `ttl: "1h"` is sent only to models that accept it
1092
+ * ({@link supportsOneHourCacheTtl}); any other model gets the 5-minute
1093
+ * default, because AWS rejects the field there.
872
1094
  */
873
- applyCacheBreakpoints(messages, cacheControl) {
1095
+ resolveCachePoint(cacheControl) {
874
1096
  const breakpoints = cacheControl?.breakpoints ?? 0;
875
- if (!this.capabilities.promptCaching || breakpoints <= 0 || !messages || messages.length === 0) return messages;
1097
+ if (!this.capabilities.promptCaching || breakpoints <= 0) return;
1098
+ return (cacheControl?.ttl ?? this.config.promptCacheTtl) === "1h" && supportsOneHourCacheTtl(this.name) ? {
1099
+ type: "default",
1100
+ ttl: "1h"
1101
+ } : { type: "default" };
1102
+ }
1103
+ /**
1104
+ * Append the call's `cachePoint` to the LAST message. Together with the
1105
+ * `system` and `tools` cache points (see `buildRequest` /
1106
+ * `buildToolConfig`) this caches every stable segment of the prefix —
1107
+ * Bedrock processes them in `tools` → `system` → `messages` order, at
1108
+ * most three of the four checkpoints it allows — so later calls reusing
1109
+ * the prefix bill it at the cache-read rate (`Usage.cachedTokens`).
1110
+ */
1111
+ applyCacheBreakpoints(messages, cachePoint) {
1112
+ if (!cachePoint || !messages || messages.length === 0) return messages;
876
1113
  const last = messages.length - 1;
877
1114
  const lastMessage = messages[last];
878
1115
  if (!lastMessage) return messages;
879
1116
  return [...messages.slice(0, last), {
880
1117
  ...lastMessage,
881
- content: [...lastMessage.content ?? [], { cachePoint: { type: "default" } }]
1118
+ content: [...lastMessage.content ?? [], { cachePoint }]
882
1119
  }];
883
1120
  }
884
1121
  /**
@@ -914,11 +1151,26 @@ var BedrockModel = class {
914
1151
  }
915
1152
  /**
916
1153
  * Spread-friendly tool fragment. Returns an empty object when no
917
- * tools were supplied (Bedrock rejects an empty `tools` array).
1154
+ * tools were supplied (Bedrock rejects an empty `tools` array). With a
1155
+ * cache point, a trailing `{ cachePoint }` caches every tool definition
1156
+ * — Claude only, since Nova accepts no cache point in `tools`.
918
1157
  */
919
- buildToolConfig(tools) {
1158
+ buildToolConfig(options, cachePoint) {
1159
+ const { tools, definitions } = (0, _warlock_js_ai.selectProviderTools)(options, {
1160
+ accepts: [BEDROCK_WIRE_FORMAT, this.provider],
1161
+ module: LOG_MODULE,
1162
+ model: this.name,
1163
+ logger: this.logger
1164
+ });
920
1165
  const toolConfig = toBedrockToolConfig(tools);
921
- return toolConfig ? { toolConfig } : {};
1166
+ const providerDefined = definitions;
1167
+ const allTools = [...toolConfig?.tools ?? [], ...providerDefined];
1168
+ if (allTools.length === 0) return toolConfig ? { toolConfig } : {};
1169
+ const withCachePoint = cachePoint && supportsToolCachePoint(this.name) ? [...allTools, { cachePoint }] : allTools;
1170
+ return { toolConfig: {
1171
+ ...toolConfig,
1172
+ tools: withCachePoint
1173
+ } };
922
1174
  }
923
1175
  /**
924
1176
  * Translate the neutral `responseSchema` into Converse's native
@@ -956,7 +1208,7 @@ var BedrockModel = class {
956
1208
  extractToolCalls(blocks) {
957
1209
  const toolCalls = [];
958
1210
  const reasoningBlocks = blocks.map(toReasoningMetadata).filter((block) => block !== void 0);
959
- for (const block of blocks) if ("toolUse" in block && block.toolUse) toolCalls.push({
1211
+ for (const block of blocks) if ("toolUse" in block && block.toolUse && block.toolUse.type !== SERVER_TOOL_USE) toolCalls.push({
960
1212
  id: block.toolUse.toolUseId ?? "",
961
1213
  name: block.toolUse.name ?? "",
962
1214
  input: block.toolUse.input ?? {},
@@ -976,10 +1228,13 @@ var BedrockModel = class {
976
1228
  * Normalize Converse's `TokenUsage` into the neutral `Usage` shape.
977
1229
  * Bedrock supplies a pre-summed `totalTokens`; cache-read and
978
1230
  * cache-write tokens are surfaced as `cachedTokens` /
979
- * `cacheWriteTokens` only when non-zero so callers can price the
980
- * discounted read rate and the one-time write cost separately.
981
- * Bedrock's Converse `TokenUsage` carries no reasoning-token channel,
982
- * so `Usage.reasoningTokens` is intentionally left unset here.
1231
+ * `cacheWriteTokens` only when non-zero, and the 1-hour share of the
1232
+ * writes (`cacheDetails` entries with `ttl: "1h"`) as
1233
+ * `cacheWrite1hTokens`. With caching, `inputTokens` "represents only
1234
+ * the non-cached input tokens" (AWS prompt-caching guide), so the usage
1235
+ * is marked `inputIncludesCache: false`. Bedrock's Converse `TokenUsage`
1236
+ * carries no reasoning-token channel, so `Usage.reasoningTokens` is
1237
+ * intentionally left unset here.
983
1238
  */
984
1239
  extractUsage(raw) {
985
1240
  if (!raw) return {
@@ -991,12 +1246,15 @@ var BedrockModel = class {
991
1246
  const output = raw.outputTokens ?? 0;
992
1247
  const cached = raw.cacheReadInputTokens;
993
1248
  const cacheWrite = raw.cacheWriteInputTokens;
1249
+ const cacheWrite1h = (raw.cacheDetails ?? []).filter((detail) => detail.ttl === "1h").reduce((sum, detail) => sum + (detail.inputTokens ?? 0), 0);
994
1250
  return {
995
1251
  input,
996
1252
  output,
997
1253
  total: raw.totalTokens ?? input + output,
1254
+ inputIncludesCache: false,
998
1255
  ...cached && cached > 0 ? { cachedTokens: cached } : {},
999
- ...cacheWrite && cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {}
1256
+ ...cacheWrite && cacheWrite > 0 ? { cacheWriteTokens: cacheWrite } : {},
1257
+ ...cacheWrite && cacheWrite > 0 && cacheWrite1h > 0 ? { cacheWrite1hTokens: Math.min(cacheWrite1h, cacheWrite) } : {}
1000
1258
  };
1001
1259
  }
1002
1260
  /**
@@ -1041,11 +1299,15 @@ var BedrockModel = class {
1041
1299
  * const bedrock = new BedrockSDK({ region: "us-east-1" });
1042
1300
  * const model = bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" });
1043
1301
  * const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });
1302
+ *
1303
+ * @example
1304
+ * // Amazon Bedrock API key (bearer auth) instead of the AWS credential chain
1305
+ * const bedrock = new BedrockSDK({ apiKey: process.env.BEDROCK_API_KEY!, region: "us-east-1" });
1044
1306
  */
1045
1307
  var BedrockSDK = class {
1046
1308
  constructor(config) {
1047
- const { provider, pricing, ...clientConfig } = config;
1048
- this.client = new _aws_sdk_client_bedrock_runtime.BedrockRuntimeClient(clientConfig);
1309
+ const { provider, pricing, apiKey, ...clientConfig } = config;
1310
+ this.client = new _aws_sdk_client_bedrock_runtime.BedrockRuntimeClient(apiKey === void 0 ? clientConfig : withApiKey(clientConfig, apiKey));
1049
1311
  this.provider = provider ?? "bedrock";
1050
1312
  this.pricing = pricing;
1051
1313
  }
@@ -1088,7 +1350,38 @@ var BedrockSDK = class {
1088
1350
  return new BedrockEmbedder(this.client, config, this.provider);
1089
1351
  }
1090
1352
  };
1353
+ /**
1354
+ * Configure the runtime client for an Amazon Bedrock API key.
1355
+ *
1356
+ * Bedrock API keys are bearer tokens: the request carries
1357
+ * `Authorization: Bearer <key>` instead of a SigV4 signature (AWS docs,
1358
+ * "Use an Amazon Bedrock API key",
1359
+ * https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys-use.html,
1360
+ * checked 2026-10-17). The installed `@aws-sdk/client-bedrock-runtime`
1361
+ * supports this natively: its auth scheme provider offers
1362
+ * `smithy.api#httpBearerAuth` beside SigV4, fed by the client's `token`
1363
+ * option, and `authSchemePreference: ["httpBearerAuth"]` selects it ahead
1364
+ * of SigV4, so the AWS credential chain is never consulted.
1365
+ *
1366
+ * Fails closed on an empty key, and on a key combined with `credentials`
1367
+ * or `token` — two credentials for one client leave which one signs the
1368
+ * request ambiguous.
1369
+ */
1370
+ function withApiKey(clientConfig, apiKey) {
1371
+ if (typeof apiKey !== "string" || apiKey.trim() === "") throw new _warlock_js_ai.InvalidRequestError("BedrockSDK: `apiKey` must be a non-empty string.");
1372
+ const conflicting = ["credentials", "token"].filter((field) => clientConfig[field] !== void 0);
1373
+ if (conflicting.length > 0) throw new _warlock_js_ai.InvalidRequestError(`BedrockSDK: pass either \`apiKey\` or ${conflicting.map((field) => `\`${field}\``).join(" / ")}, not both.`, { context: { fields: conflicting } });
1374
+ return {
1375
+ ...clientConfig,
1376
+ token: { token: apiKey },
1377
+ authSchemePreference: ["httpBearerAuth"]
1378
+ };
1379
+ }
1091
1380
 
1092
1381
  //#endregion
1382
+ exports.BEDROCK_GLOBAL_PRICING = BEDROCK_GLOBAL_PRICING;
1383
+ exports.BEDROCK_US_PRICING = BEDROCK_US_PRICING;
1093
1384
  exports.BedrockSDK = BedrockSDK;
1385
+ exports.bedrockPricingFor = bedrockPricingFor;
1386
+ exports.withClaudeCacheWriteRates = withClaudeCacheWriteRates;
1094
1387
  //# sourceMappingURL=index.cjs.map