Bedrock provider
Standalone — usable in any Node project, no
@warlock.js/corerequired.
@warlock.js/ai-bedrock is the AWS Bedrock provider adapter for
@warlock.js/ai. It speaks Bedrock’s Converse API —
the model-agnostic surface — so one wire mapping covers every
Bedrock-hosted family (Anthropic Claude, Amazon Nova, Meta Llama,
Mistral, Cohere) and hands it all back as a vendor-neutral
ModelContract. Auth uses your AWS credentials, exactly like any other
AWS SDK call, or an Amazon Bedrock API key.
Install
Section titled “Install”npm install @warlock.js/ai @warlock.js/ai-bedrockyarn add @warlock.js/ai @warlock.js/ai-bedrockpnpm add @warlock.js/ai @warlock.js/ai-bedrockConstruct
Section titled “Construct”One BedrockSDK holds one live BedrockRuntimeClient. Build it once
per account/region and reuse it everywhere — every model and embedder
it produces shares the same client, credential refresh, and retry
config.
import { BedrockSDK } from "@warlock.js/ai-bedrock";
const bedrock = new BedrockSDK({ region: "us-east-1", // Standard AWS SDK credential resolution — env vars, ~/.aws/credentials, // IAM role on EC2/ECS, SSO. Pass explicit credentials only if needed.});region is the one required field. The whole config object is forwarded
straight to BedrockRuntimeClient, so any client option works — explicit
credentials, a custom endpoint, a requestHandler, retry settings.
provider relabels the upstream (defaults to "bedrock").
const bedrock = new BedrockSDK({ region: "us-east-1", credentials: { accessKeyId: "AKIA...", secretAccessKey: "..." },});Bedrock API keys
Section titled “Bedrock API keys”Bedrock also accepts Amazon Bedrock API keys,
which are bearer tokens rather than AWS credentials. Pass one as apiKey:
const bedrock = new BedrockSDK({ apiKey: process.env.BEDROCK_API_KEY!, region: "us-east-1",});Every request then carries Authorization: Bearer <apiKey> instead of a SigV4
signature, and the AWS credential chain is never consulted. The adapter sets
the AWS SDK’s token and authSchemePreference: ["httpBearerAuth"] for you.
apiKeycannot be combined withcredentialsortoken— the constructor throwsInvalidRequestErrorrather than guess which one should sign. An empty key throws too.- Without
apiKeyorcredentials, the AWS SDK itself still picks up a key from theAWS_BEARER_TOKEN_BEDROCKenvironment variable. - API keys cover the Bedrock and Bedrock Runtime APIs this adapter calls (Converse, ConverseStream, InvokeModel for embeddings, CountTokens).
First call
Section titled “First call”Build a model, hand it to an agent, run it. execute() never throws —
failures land in error as a typed AIError.
import { ai } from "@warlock.js/ai";import { BedrockSDK } from "@warlock.js/ai-bedrock";
const bedrock = new BedrockSDK({ region: "us-east-1" });
const assistant = ai.agent({ model: bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" }), systemPrompt: "You are a concise senior TypeScript engineer.",});
const { text, usage, error } = await assistant.execute("Why use generics?");
if (error) { console.warn(error.code, error.category);} else { console.log(text, usage.total);}Use as a model
Section titled “Use as a model”name is the Bedrock model id or inference-profile id — Claude, Nova,
Llama, and friends all go through the same call.
bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" });bedrock.model({ name: "us.amazon.nova-pro-v1:0" });bedrock.model({ name: "meta.llama3-1-8b-instruct-v1:0" });Use as an embedder
Section titled “Use as an embedder”The embedder targets the Amazon Titan Text Embeddings family
(amazon.titan-embed-text-v2:0 and v1) via Bedrock’s InvokeModel.
const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });const { vector } = await embedder.embed("Hello world");const { vectors } = await embedder.embedMany(["doc 1", "doc 2"]);Titan v2 supports output truncation — pass dimensions (256 / 512 / 1024) and the adapter forwards it:
bedrock.embedder({ name: "amazon.titan-embed-text-v2:0", dimensions: 256 });Titan has no batch endpoint. Its
InvokeModelbody accepts oneinputTextper call, soembedManyissues one request per input sequentially and aggregates token usage. For high-throughput batch embedding, reach for the OpenAI or Google adapter, which batch natively.
Capabilities
Section titled “Capabilities”- Tool calling — vendor-neutral
ToolConfigs map to ConversetoolUseblocks; results round-trip back astoolResultblocks on auserturn. - Streaming —
stream()runs ConverseStream and yields textdeltas, atool-callonce atoolUseblock’s input JSON is complete, and a terminaldonewith the final finish reason and usage. - Structured output — on by default. A root-object JSON Schema is
forwarded to Converse’s native
outputConfig.textFormat(Bedrock wants it as a stringified schema; the adapter handles that). Otherwise it degrades to the agent’s soft system-prompt hint plus client-side validation. - Vision — auto-detected from the model id substring. Multimodal
families covered: Anthropic Claude 3 / 3.5 / 3.7 / 4, Amazon Nova
Lite/Pro/Premier, Meta Llama 3.2 (11B/90B) and Llama 4. Text-only
families (Llama 3/3.1, Titan Text, Mistral 7B, Cohere Command) stay
off. Override either way with the
visionflag. - PDF / document input — auto-detected from the model id, mirroring
vision minus the image-only Llama families. Inferred on for
Anthropic Claude 3 / 3.5 / 3.7 / 4 and Amazon Nova Lite/Pro/Premier
(they accept Converse
documentcontent blocks); off for text-only and image-only families. When off, a PDF attachment raises a typed capability error up front. Override with thepdfflag. - Audio input — off by default and never auto-inferred: Bedrock
Converse takes no audio content blocks for the families this adapter
targets, so every model reports
audio: falseunless you setaudio: truefor a model id you’ve confirmed accepts audio. - Embeddings — Amazon Titan Text Embeddings, single-input (see above).
Images must be base64 bytes. Bedrock Converse has no remote-URL image source — supply inlined
base64(image/jpeg,image/png,image/gif,image/webp). Passing a{ url }image raises a typedInvalidRequestErrorupfront rather than a downstream Bedrock fault.
Prompt caching
Section titled “Prompt caching”For a caching-capable model (Claude 3.5+ / 4 / 5 and the Nova family),
a per-call cacheControl: { breakpoints: 1 } places Converse
cachePoint blocks on each stable part of the prefix, in the order
Bedrock processes them: after the tools (Claude only — Nova accepts
cache points in system and messages only), after the system
prompt, and on the last message.
ttl: "1h" — from the per-call cacheControl.ttl or the model’s
promptCacheTtl — is sent only to models that accept it: Claude Haiku
4.5, Claude Sonnet / Opus 4.5 and later, and every Claude 5 family. Other
models keep the 5-minute default, since AWS rejects the field there.
Placement and TTL rules follow
Bedrock’s prompt-caching guide.
import { BedrockSDK } from "@warlock.js/ai-bedrock";
const bedrock = new BedrockSDK({ region: "us-east-1" });const model = bedrock.model({ name: "global.anthropic.claude-sonnet-4-6", promptCacheTtl: "1h", // default lifetime for this model's cache points});
await model.complete( [ { role: "system", content: "Long, stable instructions…" }, { role: "user", content: "First question" }, ], { cacheControl: { breakpoints: 1 } },);Pricing and usage
Section titled “Pricing and usage”Every response reports token usage (input, output, total), on
model.complete() and the streaming done chunk as well as on agent
reports. With caching, Bedrock’s inputTokens covers only the
non-cached input — cache reads (cachedTokens) and writes
(cacheWriteTokens, with the 1-hour share from cacheDetails in
cacheWrite1hTokens) come on top of it — so the usage is marked
inputIncludesCache: false and cost bills each channel exactly once.
Built-in Claude prices. Claude models invoked through a global.
or us. inference profile are priced out of the box from
BEDROCK_GLOBAL_PRICING / BEDROCK_US_PRICING (lookup:
bedrockPricingFor(id)), taken from
Amazon Bedrock pricing (US
East, N. Virginia), fetched 2026-10-17: input, output, cache read,
5-minute write (1.25× input) and 1-hour write (2× input). In-Region ids
with no profile prefix, other geographies (eu., apac., …), Claude
Haiku 5.5 and non-Claude models are not built in — their rates vary by
Region or were not verified — so pass pricing for them. Any Claude
entry you supply without write rates gets the 1.25× / 2× rates filled in.
Resolution: per-model pricing > SDK pricing registry (exact id) >
built-in profile price > unpriced (Usage.cost stays undefined).
import { totalCost } from "@warlock.js/ai";import { BedrockSDK } from "@warlock.js/ai-bedrock";
const bedrock = new BedrockSDK({ region: "us-east-1", pricing: { "anthropic.claude-sonnet-4-5-20250929-v1:0": { input: 3, output: 15, cachedInput: 0.3 }, "amazon.nova-pro-v1:0": { input: 0.8, output: 3.2 }, // your own rates for unlisted models },});
const { usage } = await bedrock .model({ name: "global.anthropic.claude-sonnet-4-6" }) // built-in global rate .complete([{ role: "user", content: "hi" }]);console.log(usage.inputIncludesCache, totalCost(usage.cost));A ServiceUnavailableException surfaces as ProviderOverloadedError
(code: "PROVIDER_OVERLOADED", retryable: true). Every provider error
carries status, retryable and billed (false for 401 / 402 / 403,
otherwise "unknown").
Need an offline token estimate? bedrock.count(text) returns a fast
character-heuristic approximation — Bedrock has no offline tokenizer and
per-model tokenizers differ, so this is for budgeting, not billing.
For the agent/workflow surface these models plug into, see
@warlock.js/ai. For provider-specific notes and the
latest model ids, see the warlock-js-ai-bedrock skill.
Provider body options and input-token counts
Section titled “Provider body options and input-token counts”Native body fields merge after neutral options. When both values are plain
objects, their immediate keys merge and caller-provided keys win; untouched
neutral fields remain. Arrays, scalars and null replace the field. This merge
is one level deep: a nested object explicitly supplied by the caller replaces
that nested value.
providerOptions.bedrock.body adds native Converse request fields after the
neutral options, for completion and streaming:
const options = { reasoning: { effort: "high" as const }, // preserved alongside top_k providerOptions: { bedrock: { body: { additionalModelRequestFields: { top_k: 4 } } } },};await model.complete(messages, options);The key stays bedrock for custom provider labels. Other provider keys are
ignored. BedrockCallOptions is exported. modelId, messages, system and
toolConfig are protected; attempting to replace them throws
InvalidRequestError. Provider fields are otherwise forwarded without validation.
model.countTokens(messages, options?) calls Bedrock CountTokens using the same
Converse messages, system blocks, tool configuration and
additionalModelRequestFields used for generation. Cancellation is forwarded.
Availability depends on the model and region; provider failures use the adapter’s
normal typed errors. sdk.count(text) remains a separate offline estimate.
For budget preflight, countInputTokens(model, messages, { tools }) from
@warlock.js/ai returns { tokens, source }. It estimates only when native
counting is absent or explicitly throws InvalidRequestError with
reason: "count-unsupported"; this adapter preserves that signal. System and
tools are supported natively. Ordinary validation, auth, network and rate-limit
errors propagate. See the cost/budget guide for reserve mode.
if (model.countTokens) { const inputTokens = await model.countTokens(messages, options);}Adjacent conversation turns
Section titled “Adjacent conversation turns”Converse messages combine adjacent turns of the same role, retaining every content block in order. This keeps parallel tool results in one user turn and allows an automatic compaction summary beside an assistant tool-call turn without creating consecutive assistant turns. Agent compaction frames a summary as user context when it replaces the first non-system turn.
Adjacent-turn normalization preserves empty histories and separate system prompts without inserting synthetic conversation turns.
System tools and streamed reasoning
Section titled “System tools and streamed reasoning”Bedrock system tools go in tools through ai.providerTool({ provider: "bedrock", definition }); the definition is a whole Converse Tool member, appended to toolConfig.tools after the function tool specs. A server_tool_use block in the response is never dispatched as a tool call. See Provider-defined tools.
// Nova web grounding (US cross-region profiles only).const grounding = ai.providerTool({ provider: "bedrock", definition: { systemTool: { name: "nova_grounding" } },});With streamChunks (on by default under agent.stream()), stream() also yields reasoning for reasoningContent.text deltas and tool-args for client toolUse.input deltas. See Stream reasoning and tool arguments.
Hosted-search accounting limit
Section titled “Hosted-search accounting limit”Bedrock Converse’s TokenUsage schema
and the installed SDK expose token/cache counts, with no hosted-tool request
counter (verified 2026-10-18). usage.serverToolUse therefore remains absent;
no count or search fee is inferred from server_tool_use system-tool blocks.
Hosted system tools continue to stay outside function dispatch. Direct
Anthropic counters are not assumed to exist on Converse. Supply fees only when
your own verified metering establishes the billing unit.