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 oneLLMProvider. No format-specific code per model family.
Install
npm install @aws-sdk/client-bedrock-runtimePeer 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 IDs | Minimum prefix | TTLs |
|---|---|---|
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-5 | 512 | 5 minutes, 1 hour |
anthropic.claude-opus-4-8, anthropic.claude-sonnet-5, anthropic.claude-sonnet-4-6, anthropic.claude-sonnet-4-5-20250929-v1:0 | 1,024 | 5 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:0 | 4,096 | 5 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:0 | 1,024 | 5 minutes |
amazon.nova-2-lite-v1:0, amazon.nova-pro-v1:0, amazon.nova-lite-v1:0, amazon.nova-micro-v1:0 | 1K, as AWS states it; maximum 20K | 5 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(fromagentfootprint/resilience) for transient throttling. The provider rethrows aBedrockProviderError; check.codeto tell the failure classes apart:
.code | Meaning | Extra fields |
|---|---|---|
| (absent) | Generic AWS transport / service error | .status — the AWS HTTP status code (err.$metadata.httpStatusCode) |
BEDROCK_STREAM_TOOLUSE_LOST | stopReason 'tool_use' with zero parsed tool calls — a tripwire for future ConverseStream shape drift | — |
BEDROCK_MALFORMED_TOOL_ARGS | The 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
- Resilience — wrap with
withRetry/withFallback - AgentCore Memory — paired Bedrock memory adapter
OpenAI
openai() — GPT provider via the openai SDK. Also covers OpenAI-compatible endpoints (Ollama, llama.cpp, vLLM, Together, Groq, LM Studio) via baseURL, the legacyEndpoint dial that declares which dialect such a URL speaks, and azureOpenai() with either an API key or a keyless Entra credential.
Your gateway (InvokeModel)
invokeModelGateway() — Anthropic models behind your organisation's gateway that forwards the Bedrock InvokeModel operation over plain HTTPS with an API-key header. Streaming, tool calls, a key per model, typed errors.
