@warlock.js/ai-bedrock 5.29.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/cjs/index.cjs +350 -57
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/config.type.d.mts +31 -6
  5. package/esm/config.type.d.mts.map +1 -1
  6. package/esm/embedder.mjs +1 -1
  7. package/esm/embedder.mjs.map +1 -1
  8. package/esm/index.d.mts +3 -1
  9. package/esm/index.mjs +2 -1
  10. package/esm/known-capabilities.mjs +42 -4
  11. package/esm/known-capabilities.mjs.map +1 -1
  12. package/esm/known-pricing.d.mts +48 -0
  13. package/esm/known-pricing.d.mts.map +1 -0
  14. package/esm/known-pricing.mjs +99 -0
  15. package/esm/known-pricing.mjs.map +1 -0
  16. package/esm/known-vision-models.mjs +1 -1
  17. package/esm/known-vision-models.mjs.map +1 -1
  18. package/esm/model.mjs +122 -46
  19. package/esm/model.mjs.map +1 -1
  20. package/esm/provider-options.d.mts +8 -0
  21. package/esm/provider-options.d.mts.map +1 -0
  22. package/esm/provider-options.mjs +36 -0
  23. package/esm/provider-options.mjs.map +1 -0
  24. package/esm/sdk.d.mts +5 -1
  25. package/esm/sdk.d.mts.map +1 -1
  26. package/esm/sdk.mjs +35 -4
  27. package/esm/sdk.mjs.map +1 -1
  28. package/esm/utils/map-stop-reason.mjs +1 -1
  29. package/esm/utils/map-stop-reason.mjs.map +1 -1
  30. package/esm/utils/reasoning-blocks.mjs +1 -1
  31. package/esm/utils/reasoning-blocks.mjs.map +1 -1
  32. package/esm/utils/to-bedrock-messages.mjs +15 -2
  33. package/esm/utils/to-bedrock-messages.mjs.map +1 -1
  34. package/esm/utils/to-bedrock-tools.mjs +1 -1
  35. package/esm/utils/to-bedrock-tools.mjs.map +1 -1
  36. package/esm/utils/wrap-bedrock-error.mjs +6 -2
  37. package/esm/utils/wrap-bedrock-error.mjs.map +1 -1
  38. package/llms-full.txt +152 -38
  39. package/llms.txt +1 -1
  40. package/package.json +5 -4
  41. package/skills/setup-bedrock/SKILL.md +152 -38
package/llms-full.txt CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  ---
10
10
  name: setup-bedrock
11
- description: 'Wire @warlock.js/ai-bedrock — new BedrockSDK({region, credentials?, provider?}) for AWS Bedrock Converse API + Titan embeddings. AWS credential chain (no apiKey). .model({name, vision?, reasoning?, promptCaching?, pdf?, audio?}) with cost-truth capabilities inferred per family, extended thinking via options.reasoning → Converse thinking budget_tokens, prompt caching via options.cacheControl → cachePoint, usage cachedTokens/cacheWriteTokens. Triggers: `BedrockSDK`, `bedrock.model`, `bedrock.embedder`, `bedrock.count`, `BedrockRuntimeClient`, `reasoning`, `thinking`, `budget_tokens`, `cachePoint`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`; "how do I use AWS Bedrock", "wire Claude/Nova/Llama on Bedrock", "Titan embeddings", "Bedrock Converse API", "Claude 3.7/4 extended thinking on Bedrock", "Bedrock prompt caching cost"; typical import `import { BedrockSDK } from "@warlock.js/ai-bedrock"`. Skip: agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; provider choice — the `pick-ai-provider` topic of the `warlock-js-ai` skill; embeddings concepts — the `embed-text` topic of the `warlock-js-ai` skill; competing libs `@aws-sdk/client-bedrock-runtime`, `@ai-sdk/amazon-bedrock`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`.'
11
+ description: 'Wire @warlock.js/ai-bedrock — new BedrockSDK({region, apiKey?, credentials?, provider?}) for AWS Bedrock Converse API + Titan embeddings. AWS credential chain or an Amazon Bedrock API key (bearer). .model({name, vision?, reasoning?, promptCaching?, pdf?, audio?}) with cost-truth capabilities inferred per family, extended thinking via options.reasoning → Converse thinking budget_tokens, prompt caching via options.cacheControl → cachePoint on tools (Claude), system and the last message, 1h TTL via promptCacheTtl, usage inputIncludesCache: false + cachedTokens/cacheWriteTokens/cacheWrite1hTokens, built-in Claude prices for global./us. profiles (BEDROCK_GLOBAL_PRICING, BEDROCK_US_PRICING, bedrockPricingFor), ServiceUnavailableException → ProviderOverloadedError. Triggers: `apiKey`, `AWS_BEARER_TOKEN_BEDROCK`, `providerOptions`, `BedrockCallOptions`, `countTokens`, `BedrockSDK`, `bedrock.model`, `bedrock.embedder`, `bedrock.count`, `BedrockRuntimeClient`, `reasoning`, `thinking`, `budget_tokens`, `cachePoint`, `cacheControl`, `ai.providerTool`, `systemTool`, `nova_grounding`, `server_tool_use`, `streamChunks`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`; "how do I use AWS Bedrock", "wire Claude/Nova/Llama on Bedrock", "Titan embeddings", "Bedrock Converse API", "Claude 3.7/4 extended thinking on Bedrock", "Bedrock prompt caching cost"; typical import `import { BedrockSDK } from "@warlock.js/ai-bedrock"`. Skip: agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; provider choice — the `pick-ai-provider` topic of the `warlock-js-ai` skill; embeddings concepts — the `embed-text` topic of the `warlock-js-ai` skill; competing libs `@aws-sdk/client-bedrock-runtime`, `@ai-sdk/amazon-bedrock`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`.'
12
12
  ---
13
13
 
14
14
  # `@warlock.js/ai-bedrock`
@@ -29,32 +29,38 @@ const bedrock2 = new BedrockSDK({
29
29
  credentials: { accessKeyId: "AKIA...", secretAccessKey: "..." },
30
30
  provider: "bedrock-eu",
31
31
  });
32
+
33
+ // Amazon Bedrock API key (bearer auth, no AWS credentials):
34
+ const bedrock3 = new BedrockSDK({
35
+ apiKey: process.env.BEDROCK_API_KEY!,
36
+ region: "us-east-1",
37
+ });
32
38
  ```
33
39
 
34
- Bedrock authenticates via the **AWS credential chain, not an API key** — there is no `apiKey`. `region` is required; `credentials` is optional (omit to use the standard provider chain). The whole config object is forwarded to `BedrockRuntimeClient`, so any AWS client option (`endpoint`, `requestHandler`, retry config) is accepted. `provider` defaults to `"bedrock"`.
40
+ Bedrock authenticates via the **AWS credential chain** or an **Amazon Bedrock API key**. `apiKey` sends `Authorization: Bearer <key>` instead of a SigV4 signature (the adapter sets the AWS SDK's `token` + `authSchemePreference: ["httpBearerAuth"]`); combining it with `credentials` or `token`, or passing an empty key, throws `InvalidRequestError`. Without `apiKey`, the AWS SDK still reads a key from `AWS_BEARER_TOKEN_BEDROCK` on its own. `region` is required; `credentials` is optional (omit to use the standard provider chain). The whole config object is forwarded to `BedrockRuntimeClient`, so any AWS client option (`endpoint`, `requestHandler`, retry config) is accepted. `provider` defaults to `"bedrock"`.
35
41
 
36
42
  ## Producing a model
37
43
 
38
44
  `name` is the Bedrock **model id or inference-profile id**:
39
45
 
40
46
  ```ts
41
- bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" })
42
- bedrock.model({ name: "us.amazon.nova-pro-v1:0", temperature: 0.2 })
43
- bedrock.model({ name: "meta.llama3-1-8b-instruct-v1:0", maxTokens: 1024 })
47
+ bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" });
48
+ bedrock.model({ name: "us.amazon.nova-pro-v1:0", temperature: 0.2 });
49
+ bedrock.model({ name: "meta.llama3-1-8b-instruct-v1:0", maxTokens: 1024 });
44
50
  ```
45
51
 
46
52
  Uses the model-agnostic **Converse / ConverseStream** API — one wire mapping covers every Bedrock family (Claude, Nova, Llama, Mistral, Cohere) instead of per-family `InvokeModel` body shapes.
47
53
 
48
54
  ## Capabilities — what's auto-set
49
55
 
50
- | Flag | Default |
51
- | --- | --- |
52
- | `structuredOutput` | `true` (via Converse `outputConfig.textFormat`) |
53
- | `vision` | Inferred from model id substring. `true` for Claude 3/3.5/3.7/4, Nova Lite/Pro/Premier, Llama 3.2-11B/90B, Llama 4. |
54
- | `reasoning` | Inferred from model id. `true` for Claude 3.7 + Claude 4 (Sonnet/Opus/Haiku) — they expose a configurable thinking budget on Converse. |
55
- | `promptCaching` | Inferred from model id. `true` for Claude 3.5+/3.7/4 and Nova Micro/Lite/Pro/Premier — the families that honor Converse `cachePoint`. |
56
- | `pdf` | Inferred from model id. `true` for Claude 3+ and Nova Lite/Pro/Premier (Converse `document` blocks). |
57
- | `audio` | `false` by default — Converse does not accept audio blocks for the families this adapter targets. Pass `audio: true` for a confirmed audio model. |
56
+ | Flag | Default |
57
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
58
+ | `structuredOutput` | `true` (via Converse `outputConfig.textFormat`) |
59
+ | `vision` | Inferred from model id substring. `true` for Claude 3/3.5/3.7/4, Nova Lite/Pro/Premier, Llama 3.2-11B/90B, Llama 4. |
60
+ | `reasoning` | Inferred from model id. `true` for Claude 3.7 + Claude 4 (Sonnet/Opus/Haiku) — they expose a configurable thinking budget on Converse. |
61
+ | `promptCaching` | Inferred from model id. `true` for Claude 3.5+/3.7/4/5 and Nova 2 Lite / Micro / Lite / Pro / Premier — the families that honor Converse `cachePoint`. |
62
+ | `pdf` | Inferred from model id. `true` for Claude 3+ and Nova Lite/Pro/Premier (Converse `document` blocks). |
63
+ | `audio` | `false` by default — Converse does not accept audio blocks for the families this adapter targets. Pass `audio: true` for a confirmed audio model. |
58
64
 
59
65
  Explicit config always wins — every flag is overridable per model, e.g. `bedrock.model({ name, reasoning: false, promptCaching: true, pdf: false, audio: true })`.
60
66
 
@@ -76,13 +82,38 @@ A thinking budget below `1024` throws before the request. When thinking is enabl
76
82
 
77
83
  ## Prompt caching
78
84
 
79
- For a `promptCaching`-capable model, `ModelCallOptions.cacheControl.breakpoints` (any positive value) appends one Converse `cachePoint: { type: "default" }` block to the LAST message — Bedrock then caches the whole prefix up to that point:
85
+ For a `promptCaching`-capable model, `ModelCallOptions.cacheControl.breakpoints` (any positive value) places a Converse `cachePoint` on every stable segment of the prefix, in the order Bedrock processes them (`tools` → `system` → `messages`, three of the four checkpoints it allows):
86
+
87
+ | Segment | Where the `cachePoint` goes | Models |
88
+ | -------- | --------------------------------------- | ---------------------------------------------------- |
89
+ | tools | after the last `toolConfig.tools` entry | Claude only — Nova accepts no cache point in `tools` |
90
+ | system | after the system blocks | every caching-capable model |
91
+ | messages | appended to the LAST message | every caching-capable model |
92
+
93
+ The lifetime is the per-call `cacheControl.ttl`, else the model's `promptCacheTtl`. `ttl: "1h"` is sent only to models that accept it — Claude Haiku 4.5, Sonnet / Opus 4.5+ and every Claude 5 family; any other model gets the 5-minute default, because AWS warns that the field "can result in a ValidationException" on 5-minute-only models.
80
94
 
81
95
  ```ts
82
- await model.complete(messages, { cacheControl: { breakpoints: 1 } });
96
+ import { BedrockSDK } from "@warlock.js/ai-bedrock";
97
+
98
+ const bedrock = new BedrockSDK({ region: "us-east-1" });
99
+ const model = bedrock.model({ name: "global.anthropic.claude-sonnet-4-6", promptCacheTtl: "1h" });
100
+
101
+ // tools + system + last message are cached for an hour:
102
+ await model.complete(
103
+ [
104
+ { role: "system", content: "Long, stable instructions…" },
105
+ { role: "user", content: "First question" },
106
+ ],
107
+ { cacheControl: { breakpoints: 1 } },
108
+ );
109
+
110
+ // Per call, the TTL can be overridden:
111
+ await model.complete([{ role: "user", content: "hi" }], {
112
+ cacheControl: { breakpoints: 1, ttl: "5m" },
113
+ });
83
114
  ```
84
115
 
85
- Earlier turns are untouched; only the trailing message is marked. No-ops gracefully when the model is not caching-capable or no breakpoint was requested. Read-side accounting works without any breakpoint — see the usage section below.
116
+ Source for the placement rules and TTL support: https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html (checked 2026-10-17). No-ops gracefully when the model is not caching-capable or no breakpoint was requested. Read-side accounting works without any breakpoint — see the usage section below.
86
117
 
87
118
  ## System prompt
88
119
 
@@ -112,6 +143,17 @@ Object-root `responseSchema` + `structuredOutput`-capable → `outputConfig.text
112
143
  - `contentBlockDelta.delta.text` → `{ type: "delta", content }`
113
144
  - `toolUse`: `contentBlockStart` opens an accumulator, `contentBlockDelta.delta.toolUse.input` fragments append, `contentBlockStop` emits one consolidated `{ type: "tool-call", ... }`
114
145
  - terminal `{ type: "done", finishReason, usage }` — usage from `metadata.usage` (a `TokenUsage`), including `cachedTokens` / `cacheWriteTokens` when reported (see the usage table below)
146
+ - opt-in (`options.streamChunks`, set by `agent.stream()`): `{ type: "reasoning", delta }` per `reasoningContent.text` delta, `{ type: "tool-args", callId, name, delta }` per client `toolUse.input` delta; no `usage` chunk (usage arrives once at the end)
147
+
148
+ **System tools:** `ai.providerTool({ provider: "bedrock", definition: { systemTool: { name: "nova_grounding" } } })` is appended to `toolConfig.tools` verbatim. `toolUse` blocks with `type: "server_tool_use"` are never dispatched as tool calls.
149
+
150
+ ```ts
151
+ const grounding = ai.providerTool({
152
+ provider: "bedrock",
153
+ definition: { systemTool: { name: "nova_grounding" } },
154
+ });
155
+ ai.agent({ model: bedrock.model({ name: "us.amazon.nova-premier-v1:0" }), tools: [grounding] });
156
+ ```
115
157
 
116
158
  ## Finish-reason mapping
117
159
 
@@ -122,7 +164,7 @@ Object-root `responseSchema` + `structuredOutput`-capable → `outputConfig.text
122
164
  ```ts
123
165
  const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });
124
166
  const { vector } = await embedder.embed("Hello world");
125
- const { vectors } = await embedder.embedMany(["a", "b"]); // N sequential calls
167
+ const { vectors } = await embedder.embedMany(["a", "b"]); // N sequential calls
126
168
  ```
127
169
 
128
170
  Targets the **Titan Text Embeddings** family via `InvokeModel`. **Titan has no batch endpoint** — `embedMany` issues one request per input sequentially and aggregates token usage. Cohere-on-Bedrock batch embeddings use an incompatible body shape and are out of scope — use the OpenAI adapter when batch throughput matters.
@@ -138,50 +180,111 @@ Raw AWS SDK exceptions are wrapped into the typed `@warlock.js/ai` `AIError` hie
138
180
  - `ServiceQuotaExceededException` → `QuotaExceededError`
139
181
  - `ValidationException` with "too long / context window" → `ContextLengthExceededError`, else `InvalidRequestError`
140
182
  - `ModelTimeoutException` / `ETIMEDOUT` → `ProviderTimeoutError`
141
- - 5xx / `ModelError` / `ServiceUnavailable` → `ProviderError`
183
+ - `ServiceUnavailableException` / 503 → `ProviderOverloadedError` (`code: "PROVIDER_OVERLOADED"`, `retryable: true`) — the service is temporarily unable to serve the request
184
+ - other 5xx / `ModelError` → `ProviderError`
185
+
186
+ Every wrapped error carries `status`, `retryable` and `billed` (`false` for 401 / 402 / 403, otherwise `"unknown"` — AWS publishes no rule for failed requests). `fallbackModel` fails over on `PROVIDER_OVERLOADED` by default.
142
187
 
143
188
  ## Token counting
144
189
 
145
190
  ```ts
146
- await bedrock.count("some text") // approximate heuristic, offline
191
+ await bedrock.count("some text"); // approximate heuristic, offline
147
192
  ```
148
193
 
149
194
  Per-model tokenizers differ across Bedrock families.
150
195
 
151
- ## Pricing — per-model registry
196
+ ## Pricing — built-in Claude prices and registry
152
197
 
153
- `pricing` is an optional **registry keyed by Bedrock model id** — one entry per model, all rates in **USD per 1,000,000 tokens** (`ModelPricing`: `input`, `output`, optional `cachedInput` / `cachedOutput`). Set it on the SDK so every model it produces inherits the rates:
198
+ All rates are **USD per 1,000,000 tokens** (`ModelPricing`: `input`, `output`, optional `cachedInput`, `cacheWrite`, `cacheWrite1h`). Resolution at `model()` time: per-model `pricing` (`bedrock.model({ name, pricing })`) > SDK registry (exact id) > built-in profile price > `undefined` (no cost computed).
154
199
 
155
200
  ```ts
201
+ import { ai, totalCost } from "@warlock.js/ai";
202
+ import {
203
+ BEDROCK_GLOBAL_PRICING,
204
+ BedrockSDK,
205
+ bedrockPricingFor,
206
+ withClaudeCacheWriteRates,
207
+ } from "@warlock.js/ai-bedrock";
208
+
156
209
  const bedrock = new BedrockSDK({
157
- region: "us-east-1",
210
+ region: "eu-west-1",
158
211
  pricing: {
159
- // USD per 1M tokens. Key = exact Bedrock model id / inference-profile id.
160
- "anthropic.claude-sonnet-4-5-20250929-v1:0": { input: 3, output: 15 },
161
- "us.amazon.nova-pro-v1:0": { input: 0.8, output: 3.2 },
212
+ // In-Region and non-US geographic ids are not built in — price them yourself.
213
+ "eu.anthropic.claude-sonnet-4-6": { input: 3.3, output: 16.5, cachedInput: 0.33 },
214
+ "amazon.nova-pro-v1:0": { input: 0.8, output: 3.2 }, // your own rates for unlisted models
162
215
  },
163
216
  });
164
217
 
165
- const { usage } = await ai.agent({ model: bedrock.model({ name: "us.amazon.nova-pro-v1:0" }) }).execute("hi");
166
- usage.cost; // per-channel USD breakdown of THIS run, computed from tokens × pricing[model]
218
+ bedrock.model({ name: "global.anthropic.claude-sonnet-4-6" }).pricing; // built-in global rate
219
+ bedrock.model({ name: "eu.anthropic.claude-sonnet-4-6" }).pricing; // registry + Claude write rates filled (cacheWrite 4.125, cacheWrite1h 6.6)
220
+ bedrockPricingFor("us.anthropic.claude-haiku-4-5-20251001-v1:0"); // US geographic rate
221
+ withClaudeCacheWriteRates("anthropic.claude-opus-4-8", { input: 5, output: 25 }); // + cacheWrite 6.25, cacheWrite1h 10
222
+ Object.keys(BEDROCK_GLOBAL_PRICING); // the priced Claude model ids
223
+
224
+ const { usage } = await ai
225
+ .agent({ model: bedrock.model({ name: "global.anthropic.claude-sonnet-4-6" }) })
226
+ .execute("hi");
227
+ console.log(usage.cost, totalCost(usage.cost)); // per-channel USD breakdown of THIS run, and its sum
167
228
  ```
168
229
 
169
- Resolution at `model()` time: per-model `pricing` (`bedrock.model({ name, pricing })`) > SDK registry > `undefined` (no cost computed). When the same model id appears under a cross-region inference profile, list each id you actually invoke (`us.`, `eu.`, `apac.` prefixes are distinct keys). See the `pick-ai-provider` topic of the `warlock-js-ai` skill.
230
+ - **Source.** https://aws.amazon.com/bedrock/pricing/ — the page's own price feed for "US East (N. Virginia)", fetched 2026-10-17; model ids from the prompt-caching guide. `BEDROCK_GLOBAL_PRICING` holds the `global.` cross-Region profile rates, `BEDROCK_US_PRICING` the `us.` geographic rates (10% above global for these models). Both are keyed by the bare model id; `bedrockPricingFor(id)` strips the profile prefix.
231
+ - **Covered:** Claude Sonnet 5.5 / 5 / 4.6 / 4.5, Opus 5.5 / 5 / 4.8 / 4.7 / 4.6 / 4.5, Fable 5.1 / 5, Haiku 4.5 — with cache read, 5-minute write (1.25x input) and 1-hour write (2x input).
232
+ - **Deliberately unpriced:** in-Region ids with no profile prefix and other geographies (`eu.`, `apac.`, …) — their rates vary by Region; Claude Haiku 5.5 (not verified); and every non-Claude model (Nova, Llama, Titan, …). Pass `pricing` for them.
233
+ - **Claude write rates are filled.** Any Claude entry (your own included) without `cacheWrite` / `cacheWrite1h` gets 1.25x / 2x the input rate (`withClaudeCacheWriteRates`). Non-Claude entries are left as given; an unpriced cache write then bills at the input rate.
170
234
 
171
235
  ### Tokens & usage — cost truth
172
236
 
173
- Both `complete()` and `stream()` normalize Converse's `TokenUsage` into the neutral `Usage` shape:
237
+ Both `complete()` and `stream()` normalize Converse's `TokenUsage` into the neutral `Usage` shape. With caching, `inputTokens` "represents only the non-cached input tokens" — total input is `inputTokens + cacheReadInputTokens + cacheWriteInputTokens` (AWS prompt-caching guide) — so the adapter marks the usage `inputIncludesCache: false` and cost bills each channel once:
238
+
239
+ | Converse `TokenUsage` field | Neutral `Usage` field | Notes |
240
+ | --------------------------- | -------------------------- | -------------------------------------------------------------------- |
241
+ | `inputTokens` | `usage.input` | **Excludes** cache reads and writes. Bills at `pricing.input`. |
242
+ | — | `usage.inputIncludesCache` | Always `false` on Bedrock. |
243
+ | `outputTokens` | `usage.output` | |
244
+ | `totalTokens` | `usage.total` | Falls back to `input + output` when absent. |
245
+ | `cacheReadInputTokens` | `usage.cachedTokens` | Only when `> 0`. Cache-hit reads — bills at `pricing.cachedInput`. |
246
+ | `cacheWriteInputTokens` | `usage.cacheWriteTokens` | Only when `> 0`. Cache writes — bills at `pricing.cacheWrite`. |
247
+ | `cacheDetails[ttl: "1h"]` | `usage.cacheWrite1hTokens` | The 1-hour share of the writes — bills at `pricing.cacheWrite1h`. |
248
+ | — | `usage.reasoningTokens` | **Never set** — Converse exposes no reasoning-token count. |
249
+ | — | `usage.cost` | Attached on `complete()` and the streaming `done` chunk when priced. |
250
+
251
+ Read-side accounting is populated whenever Bedrock reports it, independent of whether you sent a `cacheControl` breakpoint.
252
+
253
+ ## Provider body options and input-token counts
254
+
255
+ Native body fields merge after neutral options. When both values are plain
256
+ objects, their immediate keys merge and caller-provided keys win; untouched
257
+ neutral fields remain. Arrays, scalars and `null` replace the field. This merge
258
+ is one level deep: a nested object explicitly supplied by the caller replaces
259
+ that nested value.
260
+
261
+ `providerOptions.bedrock.body` adds native Converse request fields after the
262
+ neutral options, for completion and streaming:
263
+
264
+ ```ts
265
+ const options = {
266
+ reasoning: { effort: "high" as const }, // preserved alongside top_k
267
+ providerOptions: { bedrock: { body: { additionalModelRequestFields: { top_k: 4 } } } },
268
+ };
269
+ await model.complete(messages, options);
270
+ ```
174
271
 
175
- | Converse `TokenUsage` field | Neutral `Usage` field | Notes |
176
- | --- | --- | --- |
177
- | `inputTokens` | `usage.input` | Includes cached read tokens. |
178
- | `outputTokens` | `usage.output` | |
179
- | `totalTokens` | `usage.total` | Falls back to `input + output` when absent. |
180
- | `cacheReadInputTokens` | `usage.cachedTokens` | Only when `> 0`. Cache-hit reads — price at `pricing.cachedInput`. |
181
- | `cacheWriteInputTokens` | `usage.cacheWriteTokens` | Only when `> 0`. One-time cache write — price at `pricing.cachedOutput`. |
182
- | — | `usage.reasoningTokens` | **Never set** — Converse exposes no reasoning-token count. |
272
+ The key stays `bedrock` for custom provider labels. Other provider keys are
273
+ ignored. `BedrockCallOptions` is exported. `modelId`, `messages`, `system` and
274
+ `toolConfig` are protected; attempting to replace them throws
275
+ `InvalidRequestError`. Provider fields are otherwise forwarded without validation.
183
276
 
184
- Set `cachedInput` / `cachedOutput` in `ModelPricing` to have cache reads/writes billed at their distinct rates. Read-side accounting (`cachedTokens` / `cacheWriteTokens`) is populated whenever Bedrock reports it, independent of whether you sent a `cacheControl` breakpoint.
277
+ `model.countTokens(messages, options?)` calls Bedrock `CountTokens` using the same
278
+ Converse messages, system blocks, tool configuration and
279
+ `additionalModelRequestFields` used for generation. Cancellation is forwarded.
280
+ Availability depends on the model and region; provider failures use the adapter's
281
+ normal typed errors. `sdk.count(text)` remains a separate offline estimate.
282
+
283
+ ```ts
284
+ if (model.countTokens) {
285
+ const inputTokens = await model.countTokens(messages, options);
286
+ }
287
+ ```
185
288
 
186
289
  ## When NOT to use this skill
187
290
 
@@ -195,4 +298,15 @@ Set `cachedInput` / `cachedOutput` in `ModelPricing` to have cache reads/writes
195
298
  - The `pick-ai-provider` topic of the `warlock-js-ai` skill
196
299
  - The `embed-text` topic of the `warlock-js-ai` skill
197
300
 
301
+ ## Adjacent conversation turns
302
+
303
+ Converse messages combine adjacent turns of the same role, retaining every
304
+ content block in order. This keeps parallel tool results in one user turn and
305
+ allows an automatic compaction summary beside an assistant tool-call turn
306
+ without creating consecutive assistant turns. Agent compaction frames a summary
307
+ as user context when it replaces the first non-system turn.
308
+
309
+ Adjacent-turn normalization preserves empty histories and separate system prompts
310
+ without inserting synthetic conversation turns.
311
+
198
312
 
package/llms.txt CHANGED
@@ -6,4 +6,4 @@
6
6
 
7
7
  ## Skills
8
8
 
9
- - [setup-bedrock](@warlock.js/ai-bedrock/setup-bedrock/SKILL.md): Wire @warlock.js/ai-bedrock — new BedrockSDK({region, credentials?, provider?}) for AWS Bedrock Converse API + Titan embeddings. AWS credential chain (no apiKey). .model({name, vision?, reasoning?, promptCaching?, pdf?, audio?}) with cost-truth capabilities inferred per family, extended thinking via options.reasoning → Converse thinking budget_tokens, prompt caching via options.cacheControl → cachePoint, usage cachedTokens/cacheWriteTokens. Triggers: `BedrockSDK`, `bedrock.model`, `bedrock.embedder`, `bedrock.count`, `BedrockRuntimeClient`, `reasoning`, `thinking`, `budget_tokens`, `cachePoint`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`; "how do I use AWS Bedrock", "wire Claude/Nova/Llama on Bedrock", "Titan embeddings", "Bedrock Converse API", "Claude 3.7/4 extended thinking on Bedrock", "Bedrock prompt caching cost"; typical import `import { BedrockSDK } from "@warlock.js/ai-bedrock"`. Skip: agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; provider choice — the `pick-ai-provider` topic of the `warlock-js-ai` skill; embeddings concepts — the `embed-text` topic of the `warlock-js-ai` skill; competing libs `@aws-sdk/client-bedrock-runtime`, `@ai-sdk/amazon-bedrock`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`.
9
+ - [setup-bedrock](@warlock.js/ai-bedrock/setup-bedrock/SKILL.md): Wire @warlock.js/ai-bedrock — new BedrockSDK({region, apiKey?, credentials?, provider?}) for AWS Bedrock Converse API + Titan embeddings. AWS credential chain or an Amazon Bedrock API key (bearer). .model({name, vision?, reasoning?, promptCaching?, pdf?, audio?}) with cost-truth capabilities inferred per family, extended thinking via options.reasoning → Converse thinking budget_tokens, prompt caching via options.cacheControl → cachePoint on tools (Claude), system and the last message, 1h TTL via promptCacheTtl, usage inputIncludesCache: false + cachedTokens/cacheWriteTokens/cacheWrite1hTokens, built-in Claude prices for global./us. profiles (BEDROCK_GLOBAL_PRICING, BEDROCK_US_PRICING, bedrockPricingFor), ServiceUnavailableException → ProviderOverloadedError. Triggers: `apiKey`, `AWS_BEARER_TOKEN_BEDROCK`, `providerOptions`, `BedrockCallOptions`, `countTokens`, `BedrockSDK`, `bedrock.model`, `bedrock.embedder`, `bedrock.count`, `BedrockRuntimeClient`, `reasoning`, `thinking`, `budget_tokens`, `cachePoint`, `cacheControl`, `ai.providerTool`, `systemTool`, `nova_grounding`, `server_tool_use`, `streamChunks`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`; "how do I use AWS Bedrock", "wire Claude/Nova/Llama on Bedrock", "Titan embeddings", "Bedrock Converse API", "Claude 3.7/4 extended thinking on Bedrock", "Bedrock prompt caching cost"; typical import `import { BedrockSDK } from "@warlock.js/ai-bedrock"`. Skip: agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; provider choice — the `pick-ai-provider` topic of the `warlock-js-ai` skill; embeddings concepts — the `embed-text` topic of the `warlock-js-ai` skill; competing libs `@aws-sdk/client-bedrock-runtime`, `@ai-sdk/amazon-bedrock`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`.
package/package.json CHANGED
@@ -11,16 +11,17 @@
11
11
  "license": "MIT",
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "https://github.com/warlockjs/ai-bedrock"
14
+ "url": "git+https://github.com/warlockjs/framework.git",
15
+ "directory": "packages/ai-bedrock"
15
16
  },
16
17
  "peerDependencies": {
17
- "@warlock.js/ai": "5.29.0"
18
+ "@warlock.js/ai": "5.31.0"
18
19
  },
19
20
  "dependencies": {
20
21
  "@aws-sdk/client-bedrock-runtime": "^3.1048.0",
21
- "@warlock.js/logger": "5.29.0"
22
+ "@warlock.js/logger": "5.31.0"
22
23
  },
23
- "version": "5.29.0",
24
+ "version": "5.31.0",
24
25
  "main": "./cjs/index.cjs",
25
26
  "module": "./esm/index.mjs",
26
27
  "types": "./esm/index.d.mts",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: setup-bedrock
3
- description: 'Wire @warlock.js/ai-bedrock — new BedrockSDK({region, credentials?, provider?}) for AWS Bedrock Converse API + Titan embeddings. AWS credential chain (no apiKey). .model({name, vision?, reasoning?, promptCaching?, pdf?, audio?}) with cost-truth capabilities inferred per family, extended thinking via options.reasoning → Converse thinking budget_tokens, prompt caching via options.cacheControl → cachePoint, usage cachedTokens/cacheWriteTokens. Triggers: `BedrockSDK`, `bedrock.model`, `bedrock.embedder`, `bedrock.count`, `BedrockRuntimeClient`, `reasoning`, `thinking`, `budget_tokens`, `cachePoint`, `cacheControl`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`; "how do I use AWS Bedrock", "wire Claude/Nova/Llama on Bedrock", "Titan embeddings", "Bedrock Converse API", "Claude 3.7/4 extended thinking on Bedrock", "Bedrock prompt caching cost"; typical import `import { BedrockSDK } from "@warlock.js/ai-bedrock"`. Skip: agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; provider choice — the `pick-ai-provider` topic of the `warlock-js-ai` skill; embeddings concepts — the `embed-text` topic of the `warlock-js-ai` skill; competing libs `@aws-sdk/client-bedrock-runtime`, `@ai-sdk/amazon-bedrock`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`.'
3
+ description: 'Wire @warlock.js/ai-bedrock — new BedrockSDK({region, apiKey?, credentials?, provider?}) for AWS Bedrock Converse API + Titan embeddings. AWS credential chain or an Amazon Bedrock API key (bearer). .model({name, vision?, reasoning?, promptCaching?, pdf?, audio?}) with cost-truth capabilities inferred per family, extended thinking via options.reasoning → Converse thinking budget_tokens, prompt caching via options.cacheControl → cachePoint on tools (Claude), system and the last message, 1h TTL via promptCacheTtl, usage inputIncludesCache: false + cachedTokens/cacheWriteTokens/cacheWrite1hTokens, built-in Claude prices for global./us. profiles (BEDROCK_GLOBAL_PRICING, BEDROCK_US_PRICING, bedrockPricingFor), ServiceUnavailableException → ProviderOverloadedError. Triggers: `apiKey`, `AWS_BEARER_TOKEN_BEDROCK`, `providerOptions`, `BedrockCallOptions`, `countTokens`, `BedrockSDK`, `bedrock.model`, `bedrock.embedder`, `bedrock.count`, `BedrockRuntimeClient`, `reasoning`, `thinking`, `budget_tokens`, `cachePoint`, `cacheControl`, `ai.providerTool`, `systemTool`, `nova_grounding`, `server_tool_use`, `streamChunks`, `promptCaching`, `cachedTokens`, `cacheWriteTokens`; "how do I use AWS Bedrock", "wire Claude/Nova/Llama on Bedrock", "Titan embeddings", "Bedrock Converse API", "Claude 3.7/4 extended thinking on Bedrock", "Bedrock prompt caching cost"; typical import `import { BedrockSDK } from "@warlock.js/ai-bedrock"`. Skip: agent wiring — the `run-ai-agent` topic of the `warlock-js-ai` skill; provider choice — the `pick-ai-provider` topic of the `warlock-js-ai` skill; embeddings concepts — the `embed-text` topic of the `warlock-js-ai` skill; competing libs `@aws-sdk/client-bedrock-runtime`, `@ai-sdk/amazon-bedrock`; sibling adapters `@warlock.js/ai-openai`, `@warlock.js/ai-anthropic`, `@warlock.js/ai-google`, `@warlock.js/ai-ollama`.'
4
4
  ---
5
5
 
6
6
  # `@warlock.js/ai-bedrock`
@@ -21,32 +21,38 @@ const bedrock2 = new BedrockSDK({
21
21
  credentials: { accessKeyId: "AKIA...", secretAccessKey: "..." },
22
22
  provider: "bedrock-eu",
23
23
  });
24
+
25
+ // Amazon Bedrock API key (bearer auth, no AWS credentials):
26
+ const bedrock3 = new BedrockSDK({
27
+ apiKey: process.env.BEDROCK_API_KEY!,
28
+ region: "us-east-1",
29
+ });
24
30
  ```
25
31
 
26
- Bedrock authenticates via the **AWS credential chain, not an API key** — there is no `apiKey`. `region` is required; `credentials` is optional (omit to use the standard provider chain). The whole config object is forwarded to `BedrockRuntimeClient`, so any AWS client option (`endpoint`, `requestHandler`, retry config) is accepted. `provider` defaults to `"bedrock"`.
32
+ Bedrock authenticates via the **AWS credential chain** or an **Amazon Bedrock API key**. `apiKey` sends `Authorization: Bearer <key>` instead of a SigV4 signature (the adapter sets the AWS SDK's `token` + `authSchemePreference: ["httpBearerAuth"]`); combining it with `credentials` or `token`, or passing an empty key, throws `InvalidRequestError`. Without `apiKey`, the AWS SDK still reads a key from `AWS_BEARER_TOKEN_BEDROCK` on its own. `region` is required; `credentials` is optional (omit to use the standard provider chain). The whole config object is forwarded to `BedrockRuntimeClient`, so any AWS client option (`endpoint`, `requestHandler`, retry config) is accepted. `provider` defaults to `"bedrock"`.
27
33
 
28
34
  ## Producing a model
29
35
 
30
36
  `name` is the Bedrock **model id or inference-profile id**:
31
37
 
32
38
  ```ts
33
- bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" })
34
- bedrock.model({ name: "us.amazon.nova-pro-v1:0", temperature: 0.2 })
35
- bedrock.model({ name: "meta.llama3-1-8b-instruct-v1:0", maxTokens: 1024 })
39
+ bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" });
40
+ bedrock.model({ name: "us.amazon.nova-pro-v1:0", temperature: 0.2 });
41
+ bedrock.model({ name: "meta.llama3-1-8b-instruct-v1:0", maxTokens: 1024 });
36
42
  ```
37
43
 
38
44
  Uses the model-agnostic **Converse / ConverseStream** API — one wire mapping covers every Bedrock family (Claude, Nova, Llama, Mistral, Cohere) instead of per-family `InvokeModel` body shapes.
39
45
 
40
46
  ## Capabilities — what's auto-set
41
47
 
42
- | Flag | Default |
43
- | --- | --- |
44
- | `structuredOutput` | `true` (via Converse `outputConfig.textFormat`) |
45
- | `vision` | Inferred from model id substring. `true` for Claude 3/3.5/3.7/4, Nova Lite/Pro/Premier, Llama 3.2-11B/90B, Llama 4. |
46
- | `reasoning` | Inferred from model id. `true` for Claude 3.7 + Claude 4 (Sonnet/Opus/Haiku) — they expose a configurable thinking budget on Converse. |
47
- | `promptCaching` | Inferred from model id. `true` for Claude 3.5+/3.7/4 and Nova Micro/Lite/Pro/Premier — the families that honor Converse `cachePoint`. |
48
- | `pdf` | Inferred from model id. `true` for Claude 3+ and Nova Lite/Pro/Premier (Converse `document` blocks). |
49
- | `audio` | `false` by default — Converse does not accept audio blocks for the families this adapter targets. Pass `audio: true` for a confirmed audio model. |
48
+ | Flag | Default |
49
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
50
+ | `structuredOutput` | `true` (via Converse `outputConfig.textFormat`) |
51
+ | `vision` | Inferred from model id substring. `true` for Claude 3/3.5/3.7/4, Nova Lite/Pro/Premier, Llama 3.2-11B/90B, Llama 4. |
52
+ | `reasoning` | Inferred from model id. `true` for Claude 3.7 + Claude 4 (Sonnet/Opus/Haiku) — they expose a configurable thinking budget on Converse. |
53
+ | `promptCaching` | Inferred from model id. `true` for Claude 3.5+/3.7/4/5 and Nova 2 Lite / Micro / Lite / Pro / Premier — the families that honor Converse `cachePoint`. |
54
+ | `pdf` | Inferred from model id. `true` for Claude 3+ and Nova Lite/Pro/Premier (Converse `document` blocks). |
55
+ | `audio` | `false` by default — Converse does not accept audio blocks for the families this adapter targets. Pass `audio: true` for a confirmed audio model. |
50
56
 
51
57
  Explicit config always wins — every flag is overridable per model, e.g. `bedrock.model({ name, reasoning: false, promptCaching: true, pdf: false, audio: true })`.
52
58
 
@@ -68,13 +74,38 @@ A thinking budget below `1024` throws before the request. When thinking is enabl
68
74
 
69
75
  ## Prompt caching
70
76
 
71
- For a `promptCaching`-capable model, `ModelCallOptions.cacheControl.breakpoints` (any positive value) appends one Converse `cachePoint: { type: "default" }` block to the LAST message — Bedrock then caches the whole prefix up to that point:
77
+ For a `promptCaching`-capable model, `ModelCallOptions.cacheControl.breakpoints` (any positive value) places a Converse `cachePoint` on every stable segment of the prefix, in the order Bedrock processes them (`tools` → `system` → `messages`, three of the four checkpoints it allows):
78
+
79
+ | Segment | Where the `cachePoint` goes | Models |
80
+ | -------- | --------------------------------------- | ---------------------------------------------------- |
81
+ | tools | after the last `toolConfig.tools` entry | Claude only — Nova accepts no cache point in `tools` |
82
+ | system | after the system blocks | every caching-capable model |
83
+ | messages | appended to the LAST message | every caching-capable model |
84
+
85
+ The lifetime is the per-call `cacheControl.ttl`, else the model's `promptCacheTtl`. `ttl: "1h"` is sent only to models that accept it — Claude Haiku 4.5, Sonnet / Opus 4.5+ and every Claude 5 family; any other model gets the 5-minute default, because AWS warns that the field "can result in a ValidationException" on 5-minute-only models.
72
86
 
73
87
  ```ts
74
- await model.complete(messages, { cacheControl: { breakpoints: 1 } });
88
+ import { BedrockSDK } from "@warlock.js/ai-bedrock";
89
+
90
+ const bedrock = new BedrockSDK({ region: "us-east-1" });
91
+ const model = bedrock.model({ name: "global.anthropic.claude-sonnet-4-6", promptCacheTtl: "1h" });
92
+
93
+ // tools + system + last message are cached for an hour:
94
+ await model.complete(
95
+ [
96
+ { role: "system", content: "Long, stable instructions…" },
97
+ { role: "user", content: "First question" },
98
+ ],
99
+ { cacheControl: { breakpoints: 1 } },
100
+ );
101
+
102
+ // Per call, the TTL can be overridden:
103
+ await model.complete([{ role: "user", content: "hi" }], {
104
+ cacheControl: { breakpoints: 1, ttl: "5m" },
105
+ });
75
106
  ```
76
107
 
77
- Earlier turns are untouched; only the trailing message is marked. No-ops gracefully when the model is not caching-capable or no breakpoint was requested. Read-side accounting works without any breakpoint — see the usage section below.
108
+ Source for the placement rules and TTL support: https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html (checked 2026-10-17). No-ops gracefully when the model is not caching-capable or no breakpoint was requested. Read-side accounting works without any breakpoint — see the usage section below.
78
109
 
79
110
  ## System prompt
80
111
 
@@ -104,6 +135,17 @@ Object-root `responseSchema` + `structuredOutput`-capable → `outputConfig.text
104
135
  - `contentBlockDelta.delta.text` → `{ type: "delta", content }`
105
136
  - `toolUse`: `contentBlockStart` opens an accumulator, `contentBlockDelta.delta.toolUse.input` fragments append, `contentBlockStop` emits one consolidated `{ type: "tool-call", ... }`
106
137
  - terminal `{ type: "done", finishReason, usage }` — usage from `metadata.usage` (a `TokenUsage`), including `cachedTokens` / `cacheWriteTokens` when reported (see the usage table below)
138
+ - opt-in (`options.streamChunks`, set by `agent.stream()`): `{ type: "reasoning", delta }` per `reasoningContent.text` delta, `{ type: "tool-args", callId, name, delta }` per client `toolUse.input` delta; no `usage` chunk (usage arrives once at the end)
139
+
140
+ **System tools:** `ai.providerTool({ provider: "bedrock", definition: { systemTool: { name: "nova_grounding" } } })` is appended to `toolConfig.tools` verbatim. `toolUse` blocks with `type: "server_tool_use"` are never dispatched as tool calls.
141
+
142
+ ```ts
143
+ const grounding = ai.providerTool({
144
+ provider: "bedrock",
145
+ definition: { systemTool: { name: "nova_grounding" } },
146
+ });
147
+ ai.agent({ model: bedrock.model({ name: "us.amazon.nova-premier-v1:0" }), tools: [grounding] });
148
+ ```
107
149
 
108
150
  ## Finish-reason mapping
109
151
 
@@ -114,7 +156,7 @@ Object-root `responseSchema` + `structuredOutput`-capable → `outputConfig.text
114
156
  ```ts
115
157
  const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });
116
158
  const { vector } = await embedder.embed("Hello world");
117
- const { vectors } = await embedder.embedMany(["a", "b"]); // N sequential calls
159
+ const { vectors } = await embedder.embedMany(["a", "b"]); // N sequential calls
118
160
  ```
119
161
 
120
162
  Targets the **Titan Text Embeddings** family via `InvokeModel`. **Titan has no batch endpoint** — `embedMany` issues one request per input sequentially and aggregates token usage. Cohere-on-Bedrock batch embeddings use an incompatible body shape and are out of scope — use the OpenAI adapter when batch throughput matters.
@@ -130,50 +172,111 @@ Raw AWS SDK exceptions are wrapped into the typed `@warlock.js/ai` `AIError` hie
130
172
  - `ServiceQuotaExceededException` → `QuotaExceededError`
131
173
  - `ValidationException` with "too long / context window" → `ContextLengthExceededError`, else `InvalidRequestError`
132
174
  - `ModelTimeoutException` / `ETIMEDOUT` → `ProviderTimeoutError`
133
- - 5xx / `ModelError` / `ServiceUnavailable` → `ProviderError`
175
+ - `ServiceUnavailableException` / 503 → `ProviderOverloadedError` (`code: "PROVIDER_OVERLOADED"`, `retryable: true`) — the service is temporarily unable to serve the request
176
+ - other 5xx / `ModelError` → `ProviderError`
177
+
178
+ Every wrapped error carries `status`, `retryable` and `billed` (`false` for 401 / 402 / 403, otherwise `"unknown"` — AWS publishes no rule for failed requests). `fallbackModel` fails over on `PROVIDER_OVERLOADED` by default.
134
179
 
135
180
  ## Token counting
136
181
 
137
182
  ```ts
138
- await bedrock.count("some text") // approximate heuristic, offline
183
+ await bedrock.count("some text"); // approximate heuristic, offline
139
184
  ```
140
185
 
141
186
  Per-model tokenizers differ across Bedrock families.
142
187
 
143
- ## Pricing — per-model registry
188
+ ## Pricing — built-in Claude prices and registry
144
189
 
145
- `pricing` is an optional **registry keyed by Bedrock model id** — one entry per model, all rates in **USD per 1,000,000 tokens** (`ModelPricing`: `input`, `output`, optional `cachedInput` / `cachedOutput`). Set it on the SDK so every model it produces inherits the rates:
190
+ All rates are **USD per 1,000,000 tokens** (`ModelPricing`: `input`, `output`, optional `cachedInput`, `cacheWrite`, `cacheWrite1h`). Resolution at `model()` time: per-model `pricing` (`bedrock.model({ name, pricing })`) > SDK registry (exact id) > built-in profile price > `undefined` (no cost computed).
146
191
 
147
192
  ```ts
193
+ import { ai, totalCost } from "@warlock.js/ai";
194
+ import {
195
+ BEDROCK_GLOBAL_PRICING,
196
+ BedrockSDK,
197
+ bedrockPricingFor,
198
+ withClaudeCacheWriteRates,
199
+ } from "@warlock.js/ai-bedrock";
200
+
148
201
  const bedrock = new BedrockSDK({
149
- region: "us-east-1",
202
+ region: "eu-west-1",
150
203
  pricing: {
151
- // USD per 1M tokens. Key = exact Bedrock model id / inference-profile id.
152
- "anthropic.claude-sonnet-4-5-20250929-v1:0": { input: 3, output: 15 },
153
- "us.amazon.nova-pro-v1:0": { input: 0.8, output: 3.2 },
204
+ // In-Region and non-US geographic ids are not built in — price them yourself.
205
+ "eu.anthropic.claude-sonnet-4-6": { input: 3.3, output: 16.5, cachedInput: 0.33 },
206
+ "amazon.nova-pro-v1:0": { input: 0.8, output: 3.2 }, // your own rates for unlisted models
154
207
  },
155
208
  });
156
209
 
157
- const { usage } = await ai.agent({ model: bedrock.model({ name: "us.amazon.nova-pro-v1:0" }) }).execute("hi");
158
- usage.cost; // per-channel USD breakdown of THIS run, computed from tokens × pricing[model]
210
+ bedrock.model({ name: "global.anthropic.claude-sonnet-4-6" }).pricing; // built-in global rate
211
+ bedrock.model({ name: "eu.anthropic.claude-sonnet-4-6" }).pricing; // registry + Claude write rates filled (cacheWrite 4.125, cacheWrite1h 6.6)
212
+ bedrockPricingFor("us.anthropic.claude-haiku-4-5-20251001-v1:0"); // US geographic rate
213
+ withClaudeCacheWriteRates("anthropic.claude-opus-4-8", { input: 5, output: 25 }); // + cacheWrite 6.25, cacheWrite1h 10
214
+ Object.keys(BEDROCK_GLOBAL_PRICING); // the priced Claude model ids
215
+
216
+ const { usage } = await ai
217
+ .agent({ model: bedrock.model({ name: "global.anthropic.claude-sonnet-4-6" }) })
218
+ .execute("hi");
219
+ console.log(usage.cost, totalCost(usage.cost)); // per-channel USD breakdown of THIS run, and its sum
159
220
  ```
160
221
 
161
- Resolution at `model()` time: per-model `pricing` (`bedrock.model({ name, pricing })`) > SDK registry > `undefined` (no cost computed). When the same model id appears under a cross-region inference profile, list each id you actually invoke (`us.`, `eu.`, `apac.` prefixes are distinct keys). See the `pick-ai-provider` topic of the `warlock-js-ai` skill.
222
+ - **Source.** https://aws.amazon.com/bedrock/pricing/ — the page's own price feed for "US East (N. Virginia)", fetched 2026-10-17; model ids from the prompt-caching guide. `BEDROCK_GLOBAL_PRICING` holds the `global.` cross-Region profile rates, `BEDROCK_US_PRICING` the `us.` geographic rates (10% above global for these models). Both are keyed by the bare model id; `bedrockPricingFor(id)` strips the profile prefix.
223
+ - **Covered:** Claude Sonnet 5.5 / 5 / 4.6 / 4.5, Opus 5.5 / 5 / 4.8 / 4.7 / 4.6 / 4.5, Fable 5.1 / 5, Haiku 4.5 — with cache read, 5-minute write (1.25x input) and 1-hour write (2x input).
224
+ - **Deliberately unpriced:** in-Region ids with no profile prefix and other geographies (`eu.`, `apac.`, …) — their rates vary by Region; Claude Haiku 5.5 (not verified); and every non-Claude model (Nova, Llama, Titan, …). Pass `pricing` for them.
225
+ - **Claude write rates are filled.** Any Claude entry (your own included) without `cacheWrite` / `cacheWrite1h` gets 1.25x / 2x the input rate (`withClaudeCacheWriteRates`). Non-Claude entries are left as given; an unpriced cache write then bills at the input rate.
162
226
 
163
227
  ### Tokens & usage — cost truth
164
228
 
165
- Both `complete()` and `stream()` normalize Converse's `TokenUsage` into the neutral `Usage` shape:
229
+ Both `complete()` and `stream()` normalize Converse's `TokenUsage` into the neutral `Usage` shape. With caching, `inputTokens` "represents only the non-cached input tokens" — total input is `inputTokens + cacheReadInputTokens + cacheWriteInputTokens` (AWS prompt-caching guide) — so the adapter marks the usage `inputIncludesCache: false` and cost bills each channel once:
230
+
231
+ | Converse `TokenUsage` field | Neutral `Usage` field | Notes |
232
+ | --------------------------- | -------------------------- | -------------------------------------------------------------------- |
233
+ | `inputTokens` | `usage.input` | **Excludes** cache reads and writes. Bills at `pricing.input`. |
234
+ | — | `usage.inputIncludesCache` | Always `false` on Bedrock. |
235
+ | `outputTokens` | `usage.output` | |
236
+ | `totalTokens` | `usage.total` | Falls back to `input + output` when absent. |
237
+ | `cacheReadInputTokens` | `usage.cachedTokens` | Only when `> 0`. Cache-hit reads — bills at `pricing.cachedInput`. |
238
+ | `cacheWriteInputTokens` | `usage.cacheWriteTokens` | Only when `> 0`. Cache writes — bills at `pricing.cacheWrite`. |
239
+ | `cacheDetails[ttl: "1h"]` | `usage.cacheWrite1hTokens` | The 1-hour share of the writes — bills at `pricing.cacheWrite1h`. |
240
+ | — | `usage.reasoningTokens` | **Never set** — Converse exposes no reasoning-token count. |
241
+ | — | `usage.cost` | Attached on `complete()` and the streaming `done` chunk when priced. |
242
+
243
+ Read-side accounting is populated whenever Bedrock reports it, independent of whether you sent a `cacheControl` breakpoint.
244
+
245
+ ## Provider body options and input-token counts
246
+
247
+ Native body fields merge after neutral options. When both values are plain
248
+ objects, their immediate keys merge and caller-provided keys win; untouched
249
+ neutral fields remain. Arrays, scalars and `null` replace the field. This merge
250
+ is one level deep: a nested object explicitly supplied by the caller replaces
251
+ that nested value.
252
+
253
+ `providerOptions.bedrock.body` adds native Converse request fields after the
254
+ neutral options, for completion and streaming:
255
+
256
+ ```ts
257
+ const options = {
258
+ reasoning: { effort: "high" as const }, // preserved alongside top_k
259
+ providerOptions: { bedrock: { body: { additionalModelRequestFields: { top_k: 4 } } } },
260
+ };
261
+ await model.complete(messages, options);
262
+ ```
166
263
 
167
- | Converse `TokenUsage` field | Neutral `Usage` field | Notes |
168
- | --- | --- | --- |
169
- | `inputTokens` | `usage.input` | Includes cached read tokens. |
170
- | `outputTokens` | `usage.output` | |
171
- | `totalTokens` | `usage.total` | Falls back to `input + output` when absent. |
172
- | `cacheReadInputTokens` | `usage.cachedTokens` | Only when `> 0`. Cache-hit reads — price at `pricing.cachedInput`. |
173
- | `cacheWriteInputTokens` | `usage.cacheWriteTokens` | Only when `> 0`. One-time cache write — price at `pricing.cachedOutput`. |
174
- | — | `usage.reasoningTokens` | **Never set** — Converse exposes no reasoning-token count. |
264
+ The key stays `bedrock` for custom provider labels. Other provider keys are
265
+ ignored. `BedrockCallOptions` is exported. `modelId`, `messages`, `system` and
266
+ `toolConfig` are protected; attempting to replace them throws
267
+ `InvalidRequestError`. Provider fields are otherwise forwarded without validation.
175
268
 
176
- Set `cachedInput` / `cachedOutput` in `ModelPricing` to have cache reads/writes billed at their distinct rates. Read-side accounting (`cachedTokens` / `cacheWriteTokens`) is populated whenever Bedrock reports it, independent of whether you sent a `cacheControl` breakpoint.
269
+ `model.countTokens(messages, options?)` calls Bedrock `CountTokens` using the same
270
+ Converse messages, system blocks, tool configuration and
271
+ `additionalModelRequestFields` used for generation. Cancellation is forwarded.
272
+ Availability depends on the model and region; provider failures use the adapter's
273
+ normal typed errors. `sdk.count(text)` remains a separate offline estimate.
274
+
275
+ ```ts
276
+ if (model.countTokens) {
277
+ const inputTokens = await model.countTokens(messages, options);
278
+ }
279
+ ```
177
280
 
178
281
  ## When NOT to use this skill
179
282
 
@@ -186,3 +289,14 @@ Set `cachedInput` / `cachedOutput` in `ModelPricing` to have cache reads/writes
186
289
  - The `run-ai-agent` topic of the `warlock-js-ai` skill
187
290
  - The `pick-ai-provider` topic of the `warlock-js-ai` skill
188
291
  - The `embed-text` topic of the `warlock-js-ai` skill
292
+
293
+ ## Adjacent conversation turns
294
+
295
+ Converse messages combine adjacent turns of the same role, retaining every
296
+ content block in order. This keeps parallel tool results in one user turn and
297
+ allows an automatic compaction summary beside an assistant tool-call turn
298
+ without creating consecutive assistant turns. Agent compaction frames a summary
299
+ as user context when it replaces the first non-system turn.
300
+
301
+ Adjacent-turn normalization preserves empty histories and separate system prompts
302
+ without inserting synthetic conversation turns.