- OpenAIUpdated
- OpenAI: AdvancedNew
- AnthropicUpdated
- Anthropic: AdvancedNew
- Google GeminiUpdated
- Google Gemini: AdvancedNew
- Google Vertex AIUpdated
- OllamaUpdated
- Grok (xAI)Updated
- GroqUpdated
- MistralUpdated
- CohereUpdated
- TypeSafeUpdated
- OllayaUpdated
- ElevenLabsUpdated
- fal.aiUpdated
- ReactorUpdated
- World LabsUpdated
- OpenRouter AdapterUpdated
- OpenRouter: AdvancedNew
- Perplexity SearchUpdated
- Vercel AI GatewayUpdated
- Lovable AI GatewayUpdated
- OpenAI-CompatibleUpdated
- OpenAI-Compatible: AdvancedNew
- LLM GatewayUpdated
- CloudflareUpdated
- Claude CodeUpdated
- CodexUpdated
- OpenCodeUpdated
- Grok BuildUpdated
- ACP-CompatibleUpdated
- Amazon BedrockUpdated
- BytePlusUpdated
- BytePlus: AdvancedNew
Amazon Bedrock
The Bedrock adapter calls Claude, Nova, Llama, Mistral, DeepSeek, and gpt-oss in your AWS account. It supports streaming, tools, reasoning, and embeddings.
1. Install#
npm i @tanstack/ai-bedrockpnpm add @tanstack/ai-bedrockyarn add @tanstack/ai-bedrockbun add @tanstack/ai-bedrockYou need no other package. @aws-sdk/client-bedrock-runtime is a direct dependency, and it does the SigV4 signing.
2. Set the credentials#
Bedrock takes an API key or your AWS credentials. Pick one.
API key. Create a key in the AWS Console. See the Bedrock API keys guide. Then set one of these variables:
BEDROCK_API_KEY=your-bedrock-api-key
# or the legacy name:
AWS_BEARER_TOKEN_BEDROCK=your-bedrock-api-keySigV4 (AWS credential chain). Use this for IAM roles, instance profiles, or ~/.aws/credentials. With no API key in the environment, the adapter uses SigV4:
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_SESSION_TOKEN=... # optional, for temporary credentialsWith auth: 'auto' (the default), the adapter uses the first of these that it finds:
- An explicit apiKey passed to the factory.
- The BEDROCK_API_KEY environment variable.
- The AWS_BEARER_TOKEN_BEDROCK environment variable.
- SigV4 through the standard AWS credential chain.
Also enable access to the model in the Amazon Bedrock console. The models that you can use depend on your account and your region.
3. Stream a chat from the server#
import {
chat,
chatParamsFromRequest,
toServerSentEventsResponse,
} from "@tanstack/ai";
import { bedrockText } from "@tanstack/ai-bedrock";
export async function POST(request: Request) {
const { messages, threadId, runId } = await chatParamsFromRequest(request);
const stream = chat({
adapter: bedrockText("us.anthropic.claude-sonnet-4-5-20250929-v1:0", {
region: "us-east-1",
}),
messages,
threadId,
runId,
});
return toServerSentEventsResponse(stream);
}bedrockText uses the Converse API by default. Converse reaches the full chat catalog of Bedrock. For the other two APIs, see Choose the API.
4. Render the chat#
The client is the same for every provider. Point useChat at your route:
import { useState } from "react";
import { useChat, fetchServerSentEvents } from "@tanstack/ai-react";
export function Chat() {
const [input, setInput] = useState("");
const { messages, sendMessage } = useChat({
connection: fetchServerSentEvents("/api/chat"),
threadId: "support-chat",
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
sendMessage(input);
setInput("");
}}
>
{messages.map((message) => (
<p key={message.id}>
{message.parts
.map((part) => (part.type === "text" ? part.content : ""))
.join("")}
</p>
))}
<input value={input} onChange={(event) => setInput(event.target.value)} />
</form>
);
}For Vue, Solid, Svelte, Angular, and the other frameworks, see Quick Start.
You now have a streaming chat on Bedrock. Client-side tools work on all three APIs. Structured output and multimodal input (text, images, and documents) depend on the model.
Embeddings#
bedrockEmbedding calls the Titan and Cohere embedding models through InvokeModel:
import { embed } from "@tanstack/ai";
import { bedrockEmbedding } from "@tanstack/ai-bedrock";
const result = await embed({
adapter: bedrockEmbedding("amazon.titan-embed-text-v2:0"),
input: ["a red guitar", "a blue drum kit"],
dimensions: 512, // 256 | 512 | 1024
});
console.log(result.embeddings[0]?.vector);Titan Multimodal embeds text and images, alone or joined into one vector. To join parts, nest them in an array (the same Array<ContentPart> shape as the content of a chat message). The outer array is the list of items, so this embeds one joined item:
import { embed } from "@tanstack/ai";
import { bedrockEmbedding } from "@tanstack/ai-bedrock";
const productPhoto = "iVBORw0KGgo..."; // base64 image data
const result = await embed({
adapter: bedrockEmbedding("amazon.titan-embed-image-v1"),
input: [
[
{ type: "text", content: "a red guitar" },
{
type: "image",
source: {
type: "data",
value: productPhoto,
mimeType: "image/png",
},
},
],
],
dimensions: 1024, // 256 | 384 | 1024
});Cohere Embed v3 on Bedrock is text only. It sends items in batches and requires inputType:
import { embed } from "@tanstack/ai";
import { bedrockEmbedding } from "@tanstack/ai-bedrock";
const result = await embed({
adapter: bedrockEmbedding("cohere.embed-english-v3"),
input: ["a red guitar", "a blue drum kit"],
modelOptions: { inputType: "search_document" },
});- Titan models have no batch API. A batch of N items runs as N InvokeModel calls, with a small limit on calls at the same time.
- Titan Multimodal does not fetch remote image URLs. Pass base64 data (or a data: URI).
See the Embeddings guide.
Models and options#
Choose the API#
Bedrock has three chat APIs. Set api on the factory:
| api | Adapter name | Models | SDK |
|---|---|---|---|
| 'converse' (default) | bedrock-converse | The full catalog: Claude, Nova, Llama, Mistral, DeepSeek, Cohere, AI21, gpt-oss, and more | @aws-sdk/client-bedrock-runtime |
| 'chat' | bedrock | Open-weight models only: gpt-oss, DeepSeek V3.x, Gemma, Qwen, Mistral open models, GLM, and similar. No Claude, Nova, or Llama | openai (OpenAI-compatible Chat Completions) |
| 'responses' | bedrock-responses | gpt-oss and Gemma 4. Mantle only | openai (OpenAI-compatible Responses) |
Use Converse unless you need the OpenAI wire format. For which models work with which API, see the AWS API compatibility matrix. Samples for 'chat' and 'responses' are in Advanced.
Reasoning#
Set reasoning on chat(). What goes to Bedrock depends on the API:
- Converse (the default): only Claude gets thinking. A budget model gets thinking with a budget in additionalModelRequestFields, and maxTokens grows past the budget. off sends nothing.
- Chat Completions (api: 'chat'): reasoning_effort.
- Responses (api: 'responses'): reasoning.effort.
import { chat } from "@tanstack/ai";
import { bedrockText } from "@tanstack/ai-bedrock";
const stream = chat({
adapter: bedrockText("us.anthropic.claude-haiku-4-5-20251001-v1:0", {
region: "us-east-1",
}),
messages: [{ role: "user", content: "Check this proof." }],
threadId: "thread-1",
reasoning: { level: "high", budgetTokens: 8000 },
});The levels of each model:
- us.anthropic.claude-haiku-4-5-*, us.anthropic.claude-sonnet-4-5-*: off, high, with budgetTokens.
- openai.gpt-oss-120b-1:0, openai.gpt-oss-20b-1:0: low, medium, high.
- The google.gemma-4 models: off, high.
The other models take no reasoning. us.deepseek.r1-v1:0 always thinks, and has no levels. When a model thinks by default, such as DeepSeek R1 or gpt-oss, the adapter streams its thinking.
On Converse, structured output forces a tool, and Claude cannot think while a tool is forced. So the structured output call sends no thinking.
Models#
The package ships a catalog of confirmed model ids (src/model-catalog.generated.ts). The maintainer script scripts/fetch-bedrock-models.ts refreshes it with ListFoundationModels.
| Group | Model ids |
|---|---|
| Anthropic Claude | us.anthropic.claude-sonnet-4-5-20250929-v1:0, us.anthropic.claude-haiku-4-5-20251001-v1:0, us.anthropic.claude-3-7-sonnet-20250219-v1:0, us.anthropic.claude-3-5-sonnet-20241022-v2:0, us.anthropic.claude-3-5-haiku-20241022-v1:0 |
| Amazon Nova | us.amazon.nova-pro-v1:0, us.amazon.nova-lite-v1:0, us.amazon.nova-micro-v1:0 |
| Meta Llama | us.meta.llama4-maverick-17b-instruct-v1:0, us.meta.llama3-3-70b-instruct-v1:0 |
| Other chat | openai.gpt-oss-120b-1:0, openai.gpt-oss-20b-1:0, us.mistral.pixtral-large-2502-v1:0, us.deepseek.r1-v1:0, google.gemma-4-31b, google.gemma-4-26b-a4b, google.gemma-4-e2b |
| Embeddings | amazon.titan-embed-text-v2:0, amazon.titan-embed-image-v1, cohere.embed-english-v3, cohere.embed-multilingual-v3 |
adapter.inputModalities lists the input kinds of a model in the catalog. If this package does not know the model id, bedrockText and createBedrockText throw. createBedrockChat, createBedrockResponsesText, and createBedrockConverse return undefined for that id. See Check what a model accepts.
Advanced#
Configuration#
BedrockClientConfig takes these options:
| Option | Type | Default | Description |
|---|---|---|---|
| api | 'converse' | 'chat' | 'responses' | 'converse' | The Bedrock API to use |
| region | string | 'us-east-1' | AWS region (for example 'us-west-2') |
| auth | 'apikey' | 'sigv4' | 'auto' | 'auto' | Authentication mode |
| apiKey | string | none | Explicit API key (wins over the environment variables) |
| baseURL | string | none | Replaces the computed base URL |
| defaultHeaders | Record<string, string> | none | Headers sent with every request |
| endpoint | 'runtime' | 'mantle' | 'runtime' | The Bedrock endpoint (Chat Completions only) |
About endpoint:
- It applies only when api: 'chat'.
- runtime (bedrock-runtime) hosts the broad open-weight catalog. mantle is the other choice.
- On mantle, the adapter reads the path from the Bedrock API compatibility config: /openai/v1 for Gemma 4, /v1 for other models. An explicit baseURL still wins.
- The Responses API always uses mantle.
With an explicit API key:
import { createBedrockText } from "@tanstack/ai-bedrock";
const adapter = createBedrockText(
"us.anthropic.claude-haiku-4-5-20251001-v1:0",
"your-bedrock-api-key",
{ region: "us-west-2" },
);Behind a proxy#
Send every request through a gateway, such as Cloudflare AI Gateway or a corporate proxy, with baseURL and defaultHeaders. These two option names are the same on every TanStack AI adapter, so one gateway config works for all of them.
import { createBedrockText } from "@tanstack/ai-bedrock";
const adapter = createBedrockText("us.amazon.nova-pro-v1:0", process.env.BEDROCK_API_KEY!, {
baseURL: "https://gateway.example.com/aws-bedrock",
defaultHeaders: { "cf-aig-authorization": `Bearer ${process.env.GATEWAY_TOKEN}` },
});- All three Bedrock APIs accept these options. On the Converse API, the adapter adds the headers before SigV4 signing, so a signed request still includes them.
- Need more than a fixed header, such as a log of each model call? The Chat Completions API (api: 'chat') and the Responses API (api: 'responses') support wrapFetch. The Converse API ignores it, because the AWS SDK does not send its requests through fetch.
Prompt caching#
chat() adds cache points for Claude models by default: one after the system prompt, and one at the end of the last user message. Pass promptCache: 'long' for the 1-hour cache, or promptCache: 'none' to turn it off. Other models get no automatic cache point. For the cost, see Prompt Caching.
To pick the places yourself, add a cachePoint. One cache point of your own turns the automatic ones off for that request. Later requests can read matching tokens at the lower cache rate. Bedrock bills cache misses at the standard input rate.
Explicit prompt caching depends on the model. Use cachePoint only with a model that AWS lists as supporting it. The minimum checkpoint size and the TTL options also change by model.
Set metadata.cachePoint on the item that ends the stable part of your prompt. The adapter puts a cachePoint block right after it:
import { bedrockText } from "@tanstack/ai-bedrock";
import { chat } from "@tanstack/ai";
const stream = chat({
adapter: bedrockText("us.anthropic.claude-sonnet-4-5-20250929-v1:0", {
region: "us-east-1",
}),
systemPrompts: [
{
content: "Long, stable instructions...",
metadata: { cachePoint: { type: "default" } },
},
],
messages: [
// An older turn. Its cachePoint was removed when the new turn was added.
{ role: "user", content: "Summarize the report." },
{ role: "assistant", content: "The report says..." },
{
role: "user",
content: [
{
type: "text",
content: "What changed since yesterday?",
// Only the newest message has a checkpoint.
// It caches the conversation up to here for the next round.
metadata: { cachePoint: { type: "default" } },
},
],
},
],
threadId: "thread-1",
});Keep one message checkpoint, on the newest message. Before you send the next turn, remove cachePoint from the older message and add it to the new message. The metadata stays in the message history. If you do not remove it, the checkpoints add up, and the request can go over the Bedrock limit of four.
Tools take the same metadata. To cache the tool definitions, pass metadata: { cachePoint: { type: 'default' } } to toolDefinition() for the last tool in the list.
- Bedrock accepts up to four checkpoints in one request.
- Add ttl: '1h' to keep an entry for one hour. The default is 5 minutes, and a read starts the timer again.
- Bedrock reads tool checkpoints first, then system prompt checkpoints, then message checkpoints. If a request mixes both TTLs, put every 1h checkpoint before the first 5m checkpoint in that order.
- Bedrock ignores a checkpoint below the minimum size of the model. The request still succeeds, and nothing goes into the cache.
Token usage#
onUsage and RUN_FINISHED.usage report the counts of Bedrock as promptTokens, completionTokens, and totalTokens.
- promptTokens is the full input, cached tokens included.
- When a request reads or writes a prompt cache, the cache parts are also on promptTokensDetails.cachedTokens and promptTokensDetails.cacheWriteTokens.
To read these counts, see Token usage.
Chat Completions API#
Set api: 'chat' to use the OpenAI-compatible Chat Completions endpoint of Bedrock. It returns a bedrock adapter. Only open-weight models are on this endpoint.
// ignore: iterating a chat() stream and reading chunk.type/chunk.delta needs the
// AG-UI base event fields, which come from @ag-ui/core. It's a transitive dep of
// @tanstack/ai, so kiira (resolving @tanstack/ai from source under the dist->src
// heuristic) can't follow it and those base fields drop off StreamChunk. The code
// is correct (the same pattern is used throughout ai-client); see
// getting-started/quick-start-server for the type-checked consumption shape.
import { bedrockText } from '@tanstack/ai-bedrock'
import { chat } from '@tanstack/ai'
const adapter = bedrockText('openai.gpt-oss-20b-1:0', {
region: 'us-east-1',
api: 'chat',
})
for await (const chunk of chat({
adapter,
messages: [{ role: 'user', content: 'What is the capital of France?' }],
threadId: 'thread-1',
})) {
if (chunk.type === 'TEXT_MESSAGE_CONTENT') process.stdout.write(chunk.delta ?? '')
}Responses API#
Set api: 'responses' to use the OpenAI-compatible Responses API of Bedrock. It returns a bedrock-responses adapter. This API is mantle only.
- The Responses API is stateful. To continue a conversation on the server, pass previous_response_id and store in modelOptions.
- Reasoning deltas stream as thinking content. Models on mantle can still send the old event name response.reasoning.delta, which the OpenAI spec replaced with response.reasoning_text.delta. The adapter reads both names the same way.
// ignore: see the Chat Completions sample above. Iterating a chat() stream and
// reading chunk.type/chunk.delta needs @ag-ui/core base fields kiira can't
// resolve transitively through @tanstack/ai source.
import { bedrockText } from '@tanstack/ai-bedrock'
import { chat } from '@tanstack/ai'
const adapter = bedrockText('openai.gpt-oss-120b-1:0', {
region: 'us-east-1',
api: 'responses',
})
for await (const chunk of chat({
adapter,
messages: [{ role: 'user', content: 'Summarize the Bedrock pricing page.' }],
threadId: 'thread-1',
})) {
if (chunk.type === 'TEXT_MESSAGE_CONTENT') process.stdout.write(chunk.delta ?? '')
}API reference#
| Factory | Creates |
|---|---|
| bedrockText(model, config?) | Chat adapter with auth from the environment. Returns the adapter of the api that you pick |
| createBedrockText(model, apiKey, config?) | Chat adapter with an explicit API key. Skips the environment lookup |
| bedrockEmbedding(model, config?) / createBedrockEmbedding(model, apiKey, config?) | Embedding adapter for Titan and Cohere models |
config takes api, region, auth, apiKey, baseURL, defaultHeaders, and endpoint. See Configuration.
More from AWS:
- Amazon Bedrock API keys: create and manage API keys.
- Amazon Bedrock model access: enable models in your account.
- Converse API reference: the native Converse API docs.