Build

AWS Bedrock

bedrock() — model-agnostic provider via AWS Bedrock's Converse API. One adapter covers Claude, Llama, Mistral, Titan, Mixtral on Bedrock.

AWS Bedrock's Converse API is model-agnostic — same wire format whether you're calling Claude, Llama, Mistral, Titan, or Mixtral. agentfootprint's bedrock() factory wraps it as one LLMProvider. No format-specific code per model family.

Install

npm install @aws-sdk/client-bedrock-runtime

Peer dep, lazy-required, optional. AWS credentials resolved via the SDK's normal chain (env, profile, IAM role).

Reaching the models through your organisation's gateway instead — an API-key header and the InvokeModel operation rather than AWS credentials and Converse? Use invokeModelGateway().

Use

import { Agent } from 'agentfootprint';
import { bedrock } from 'agentfootprint/providers';

const provider = bedrock({
  region: 'us-east-1',
});

const agent = Agent.create({
  provider,
  model: 'anthropic.claude-sonnet-4-5-20250929-v1:0',  // Bedrock-namespaced model id
}).build();

The model id is Bedrock's, not the upstream provider's. Cross-region inference profiles work as-is — just pass the profile id.

Tools (Converse tool_use blocks)

Converse API's tool_use / tool_result blocks map cleanly to agentfootprint's Tool[] + LLMMessage.toolCalls. ReAct correctness preserved.

The mapping holds on both paths — complete() and stream() produce the same toolCalls for the same logical response, and a parity test pins them together so they cannot drift apart.

Prompt caching

bedrock() declares caching per model. Pass the model to cacheStrategyFor(provider, model) when attaching a cache meter; the Agent resolves it automatically, including a model chosen by .configure() or a skill brain. withRetry, withCircuitBreaker and withFallback preserve this capability. Unknown IDs, custom aliases and profile ARNs have no declared caching; the adapter sends no cachePoint for them. This is a verified subset, not a statement that AWS cannot cache every omitted model.

// In an application: bedrock({ region: 'us-east-1' }); the example's// injected SDK client below is entirely local and returns fixture counts.const provider = scriptedBedrock();const model = 'us.anthropic.claude-sonnet-4-5-20250929-v1:0';const meter = cacheRecorder({ strategy: cacheStrategyFor(provider, model), model });const agent = Agent.create({ provider, model })  .system('Stable instructions. '.repeat(100), { cache: 'always' })  .watch(meter)  .build();await agent.run({ message: input });await agent.run({ message: 'A second question with the same prefix.' });const report = meter.report();const reads = isKnown(report.cacheReadTokensTotal)  ? report.cacheReadTokensTotal.value  : 'unknown';

The executable example uses a fake SDK, including a cache write on the first call and a read on the second. These fixture counts prove the wiring; they do not measure AWS latency or savings. A real request uses the normal bedrock({ region }) factory and your AWS credentials.

All listed models allow at most four checkpoints across the request. Claude models accept them in tools, system and messages; Nova accepts system and messages only. A marker becomes a separate union block after its exact message or tool boundary. The port currently carries the composed system prompt as one string, so a system marker covers that whole string. Skipped messages never move a checkpoint onto a neighboring message.

short uses the default five-minute TTL. long becomes ttl: '1h' only where the model documents it. Otherwise it is shortened to the default, and a long checkpoint following a short one in wire order is shortened too; prompt content is never reordered. Prefixes below the model's minimum may complete without being cached. Cache writes can cost more than fresh input, and repeated prompts do not guarantee a hit.

minimumTokens documents AWS's threshold; the adapter does not suppress checkpoints using a token estimate. A below-minimum prefix does not cache: the cache meter reports measured zero writes when AWS returns zero, rather than pretending the provider measured nothing.

Verified model facts

Checked against AWS's caching guide and its model cards. bedrockModels.ts is the single support table. Exact geography profiles are allowed only where verified for that model; support does not guarantee account access, region availability or a working bare model endpoint. In particular, Haiku 4.5 on-demand calls require a documented geography/global inference profile.

Base model IDsMinimum prefixTTLs
anthropic.claude-haiku-5-5, anthropic.claude-sonnet-5-5, anthropic.claude-opus-5-5, anthropic.claude-fable-5-1, anthropic.claude-mythos-5-1, anthropic.claude-fable-5, anthropic.claude-mythos-5, anthropic.claude-opus-55125 minutes, 1 hour
anthropic.claude-opus-4-8, anthropic.claude-sonnet-5, anthropic.claude-sonnet-4-6, anthropic.claude-sonnet-4-5-20250929-v1:01,0245 minutes, 1 hour
anthropic.claude-opus-4-7, anthropic.claude-opus-4-6-v1, anthropic.claude-opus-4-5-20251101-v1:0, anthropic.claude-haiku-4-5-20251001-v1:04,0965 minutes, 1 hour
anthropic.claude-3-7-sonnet-20250219-v1:0, anthropic.claude-3-5-sonnet-20241022-v2:0, anthropic.claude-sonnet-4-20250514-v1:0, anthropic.claude-opus-4-1-20250805-v1:01,0245 minutes
amazon.nova-2-lite-v1:0, amazon.nova-pro-v1:0, amazon.nova-lite-v1:0, amazon.nova-micro-v1:01K, as AWS states it; maximum 20K5 minutes

The API cache-point schema defines type: 'default' and optional TTL. Both Converse and ConverseStream report optional cacheReadInputTokens / cacheWriteInputTokens; the adapter lifts these to usage.cacheRead / cacheWrite. Missing counts remain unknown; a reported zero is measured zero. usage.input remains fresh input, so the cache meter adds the three buckets once when calculating totals.

When an explicit cache policy cannot be honored, development mode warns once per Agent build. A default policy warns only when the provider declares model-aware caching but cannot cache the selected model. Ordinary Mock and Ollama defaults are silent. Each run's existing agentfootprint.agent.run_configured event includes only fixed cache metadata with its source: { requested: 'explicit' | 'default', strategy: 'none', status: 'not-applicable' }. No prompt, policy function or provider configuration enters that field. The manifest describes the default model; it does not claim every runtime model override has the same support. caching: 'off' or all policies set to never produces no warning or cache field.

Code that reads provider.promptCaching.mode directly must instead call promptCachingFor(provider, model) from agentfootprint/cache, then inspect the resolved declaration. The field can now be a model function; this is a breaking TypeScript shape change. Existing static declarations still work.

Thinking support

The current bedrock() Converse adapter does not implement the request and response round-trip for thinking. It declares thinkingMode: () => 'none': .thinking() is refused at agent build, and direct complete() and stream() calls with req.thinking fail before constructing or sending an AWS command. The error is UnsupportedThinkingError with reason: 'no-thinking', not a successful call with the thinking request lost.

This is a limitation of this adapter, not of Amazon Bedrock itself. For a supported Claude Messages route behind an InvokeModel gateway, use invokeModelGateway(). Native Converse reasoning support needs its own complete request/response implementation.

Forced tool choice is checked separately. The Converse adapter shares the Claude model restrictions; normal requests without thinking are unchanged.

Streaming

provider.stream(req) uses ConverseStream — text chunks land as they arrive, and the final chunk carries the authoritative LLMResponse.

Streamed tool calls are parsed. bedrock() accumulates the ConverseStream tool-use events — contentBlockStart (id + name), contentBlockDelta (JSON argument fragments), contentBlockStop — keyed by contentBlockIndex, so parallel tool calls whose fragments interleave are reassembled correctly. The completed calls arrive on the terminal chunk's response.toolCalls. Tools that take no arguments stream as args: {}.

Argument fragments are not yielded as chunks (LLMChunk has no field for them); they surface only on the terminal response.

Honest by construction: a response with stopReason: 'tool_use' and zero parsed tool calls is never returned quietly. The provider throws a BedrockProviderError with code: 'BEDROCK_STREAM_TOOLUSE_LOST' so a reliability rule can retry, instead of the agent confidently answering without running its tools.

Fixed in 7.6.1. Before this, stream() ignored the tool-use events entirely and returned toolCalls: [] — an agent on bedrock() with streaming enabled silently produced a final answer instead of calling its tools. The non-streaming path was always correct.

Production patterns

  • AWS SDK handles credential refresh + region routing automatically.
  • Wrap with withRetry (from agentfootprint/resilience) for transient throttling. The provider rethrows a BedrockProviderError; check .code to tell the failure classes apart:
.codeMeaningExtra fields
(absent)Generic AWS transport / service error.status — the AWS HTTP status code (err.$metadata.httpStatusCode)
BEDROCK_STREAM_TOOLUSE_LOSTstopReason 'tool_use' with zero parsed tool calls — a tripwire for future ConverseStream shape drift—
BEDROCK_MALFORMED_TOOL_ARGSThe model emitted tool-argument JSON that does not parse. Thrown rather than swallowed to {}, so a tool is never executed with its arguments dropped.toolName, .toolUseId, .contentBlockIndex, .rawLength, .cause

All three are retryable through withRetry. A retry usually recovers malformed tool-args JSON (the model re-emits it cleanly); BEDROCK_STREAM_TOOLUSE_LOST is a tripwire for persistent stream-shape drift, so if it repeats, report it rather than retrying harder.

  • Tune the default model and token budget on the factory:
import { bedrock } from 'agentfootprint/providers';

const provider = bedrock({
  region: 'us-east-1',
  defaultModel: 'anthropic.claude-sonnet-4-5-20250929-v1:0', // used when LLMRequest.model is the shorthand 'bedrock'
  defaultMaxTokens: 4096,
});

Limitations

  • Multi-modal not exposed yet (text content only).
  • Bedrock Guardrails not exposed in the wrapper — pass via the SDK client directly if you need them.

Next steps

On this page