@plumbus/ai-bedrock 0.1.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.
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Configuration for {@link createBedrockAdapter}.
3
+ *
4
+ * Pricing:
5
+ * - Set `pricingFilePath` to load a normalized JSON file (recommended for
6
+ * containers / Kubernetes — no egress to the AWS Price List CDN).
7
+ * - Otherwise rates are auto-downloaded for `region` and cached in memory.
8
+ */
9
+ export interface BedrockAdapterConfig {
10
+ /** AWS region for Bedrock Runtime and Price List lookup (e.g. `us-east-1`). */
11
+ region: string;
12
+ /**
13
+ * Optional credentials. When omitted, the AWS default credential provider
14
+ * chain is used (env keys, shared config, IRSA, instance role, …).
15
+ */
16
+ credentials?: import('@aws-sdk/types').AwsCredentialIdentity | import('@aws-sdk/types').AwsCredentialIdentityProvider;
17
+ /** Default Converse model id when a request omits `model`. */
18
+ defaultModel?: string;
19
+ /** Default embedding model id (default: Titan Text Embeddings V2). */
20
+ defaultEmbeddingModel?: string;
21
+ /** Request timeout in milliseconds (default: 120_000). */
22
+ requestTimeout?: number;
23
+ /**
24
+ * Provider-side structured outputs via Converse `outputConfig.textFormat`.
25
+ *
26
+ * - `'off'` (default) — schemas are enforced by core's validate-and-repair
27
+ * loop, exactly as before. Safe on every model.
28
+ * - `'native'` — forward `responseSchema` to Bedrock. Support is
29
+ * model-dependent; a model that does not accept `outputConfig` rejects the
30
+ * whole request, so enable it per deployment once you have verified your
31
+ * model supports it.
32
+ */
33
+ structuredOutputs?: 'off' | 'native';
34
+ /**
35
+ * Max concurrent InvokeModel embedding calls (default: 4). Titan embeds one
36
+ * text per request, so this bounds the fan-out for a corpus ingest.
37
+ */
38
+ embedConcurrency?: number;
39
+ /**
40
+ * `input_type` sent to Cohere embedding models (default: `search_document`).
41
+ * Ignored by Titan. Use `search_query` for query-side embeddings if you keep
42
+ * a separate adapter for retrieval.
43
+ */
44
+ embeddingInputType?: string;
45
+ /** Optional Bedrock Runtime endpoint override (VPC / GovCloud). */
46
+ endpoint?: string;
47
+ /**
48
+ * Path to a normalized pricing JSON file. When set, rates load from this
49
+ * file and auto-download is skipped. Recommended for container deployments.
50
+ */
51
+ pricingFilePath?: string;
52
+ /**
53
+ * TTL for auto-downloaded rates in memory (ignored when `pricingFilePath`
54
+ * is set). Default: 24 hours.
55
+ */
56
+ pricingCacheTtlMs?: number;
57
+ /**
58
+ * When true (default), warm pricing during the first AI call (file load or
59
+ * auto-download). File mode fails clearly if the path is missing/invalid;
60
+ * auto-download failures warn and leave rates empty (cost $0).
61
+ */
62
+ warmPricingOnCreate?: boolean;
63
+ /** Timeout for auto-download warm/refresh (default: 15_000). */
64
+ pricingRefreshTimeoutMs?: number;
65
+ /**
66
+ * Optional BedrockRuntimeClient override (tests). When omitted a client is
67
+ * constructed from region / credentials / endpoint.
68
+ */
69
+ runtimeClient?: import('@aws-sdk/client-bedrock-runtime').BedrockRuntimeClient;
70
+ /** Optional pricing store override (tests). */
71
+ pricingStore?: import('./pricing.js').BedrockPricingStore;
72
+ }
73
+ /** Default Titan Text Embeddings V2 model id. */
74
+ export declare const BEDROCK_DEFAULT_EMBEDDING_MODEL = "amazon.titan-embed-text-v2:0";
75
+ /**
76
+ * Normalized pricing file schema (version 1).
77
+ *
78
+ * Operators generate this in CI from the AWS Price List and mount it
79
+ * (ConfigMap / volume). Keys should be Bedrock model ids (or family keys
80
+ * such as `anthropic.claude-haiku-4-5`).
81
+ */
82
+ export interface BedrockPricingFileV1 {
83
+ version: 1;
84
+ /** ISO region the rates apply to (informational). */
85
+ region?: string;
86
+ /** Optional ISO timestamp when the file was generated. */
87
+ generatedAt?: string;
88
+ models: Record<string, BedrockModelRate>;
89
+ }
90
+ export interface BedrockModelRate {
91
+ /** USD per 1M input tokens. */
92
+ inputPerMTok: number;
93
+ /** USD per 1M output tokens (0 for embeddings). */
94
+ outputPerMTok: number;
95
+ kind?: 'text' | 'embedding' | 'moderation' | 'image' | 'audio';
96
+ /**
97
+ * USD per 1M cache-read input tokens. When absent, cost falls back to an
98
+ * approximate 0.1× of `inputPerMTok`.
99
+ */
100
+ cacheReadPerMTok?: number;
101
+ /**
102
+ * USD per 1M cache-write input tokens (default 5-minute TTL). When absent,
103
+ * cost falls back to an approximate 1.25× of `inputPerMTok`. Longer TTLs are
104
+ * billed higher by AWS and are not modelled — mount explicit rates if you
105
+ * rely on 1-hour caching.
106
+ */
107
+ cacheWritePerMTok?: number;
108
+ /**
109
+ * USD per 1M tokens when called through a **global** cross-region inference
110
+ * profile (`global.` prefix), which AWS bills below the regional rate. When
111
+ * absent, `global.` ids fall back to the regional rate.
112
+ */
113
+ globalInputPerMTok?: number;
114
+ /** USD per 1M output tokens for global inference profiles. */
115
+ globalOutputPerMTok?: number;
116
+ }
117
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,CAAC,EACR,OAAO,gBAAgB,EAAE,qBAAqB,GAC9C,OAAO,gBAAgB,EAAE,6BAA6B,CAAC;IAC3D,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;;;;OASG;IACH,iBAAiB,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IACrC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gEAAgE;IAChE,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,iCAAiC,EAAE,oBAAoB,CAAC;IAC/E,+CAA+C;IAC/C,YAAY,CAAC,EAAE,OAAO,cAAc,EAAE,mBAAmB,CAAC;CAC3D;AAED,iDAAiD;AACjD,eAAO,MAAM,+BAA+B,iCAAiC,CAAC;AAE9E;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,CAAC,CAAC;IACX,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,mDAAmD;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/D;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8DAA8D;IAC9D,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Default Titan Text Embeddings V2 model id. */
2
+ export const BEDROCK_DEFAULT_EMBEDDING_MODEL = 'amazon.titan-embed-text-v2:0';
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA2EA,iDAAiD;AACjD,MAAM,CAAC,MAAM,+BAA+B,GAAG,8BAA8B,CAAC"}
@@ -0,0 +1,37 @@
1
+ # @plumbus/ai-bedrock — agent instructions
2
+
3
+ **Exact path in a consumer app:** `node_modules/@plumbus/ai-bedrock/instructions/README.md`
4
+
5
+ | File | When to read |
6
+ |------|----------------|
7
+ | [framework.md](./framework.md) | Package boundary, install, env table, IAM sketch, model ids, wiring |
8
+ | [pricing.md](./pricing.md) | **Where to pull rates (URLs)**, curl, one-shot normalize, ConfigMap, `$0` cost debug |
9
+
10
+ ## Reading order
11
+
12
+ 1. **framework.md** — install + `AI_BEDROCK_*` + IAM + prompt example.
13
+ 2. **pricing.md** — if you need cost recording in containers, or `cost` is always `$0`.
14
+
15
+ ## Critical rules
16
+
17
+ 1. Install `@plumbus/ai-bedrock` explicitly — core will not bundle the AWS SDK.
18
+ 2. Use Bedrock **model ids** in prompts (`anthropic.claude-…-v1:0`, not Anthropic API short names alone).
19
+ 3. For Kubernetes / containers / reliable cost, set **`pricingFilePath` / `AI_BEDROCK_PRICING_FILE`** with explicit family keys — do not rely on Price List auto-download completeness. Read [pricing.md](./pricing.md).
20
+ 4. Business logic stays in Plumbus primitives (`definePrompt`, `ctx.ai.*`); this package is only the Bedrock adapter.
21
+ 5. Cost USD comes from this package’s rates (file or auto-download), **not** from core’s OpenAI/Anthropic `MODEL_PRICING` table. Never alias Bedrock rates to Anthropic catalog rows.
22
+ 6. Bedrock Converse responses return **token usage only** — never dollar amounts. Pricing is always usage × rates from the Price List (or your mounted file).
23
+ 7. Do not confuse **Bedrock Runtime** (this package) with **Bedrock Mantle** (OpenAI-compatible HTTP → `createOpenAIAdapter`). Full guide in the monorepo: `docs/ai/bedrock.md` (also linked from the package README).
24
+ 8. After install on an existing app, run `plumbus init --patch` (wiring version 13+) so agents see these instruction paths; `plumbus doctor` reports stale wiring.
25
+ 9. **Tools** — `runToolLoop` / `generateWithUsage({ tools })` (see [framework.md](./framework.md)); `toolChoice: 'none'` omits `toolConfig`.
26
+ 10. **Embeddings** — Titan via InvokeModel through `createRAGPipeline({ provider })` + `ctx.ai.retrieve` / `ragPipeline.ingest`; there is no `ctx.ai.embed`. `plumbus rag ingest` does not auto-pick Bedrock. Mantle usually cannot embed.
27
+
28
+ ## Price List (bookmark)
29
+
30
+ ```text
31
+ https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrock/current/{region}/index.json
32
+ https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/{region}/index.json
33
+ ```
34
+
35
+ Host stays `pricing.us-east-1.amazonaws.com`; swap `{region}` for `AI_BEDROCK_REGION`. Public HTTPS — no IAM.
36
+
37
+ Package README: [../README.md](../README.md)
@@ -0,0 +1,293 @@
1
+ # @plumbus/ai-bedrock — framework boundary
2
+
3
+ **Exact path in a consumer app:** `node_modules/@plumbus/ai-bedrock/instructions/framework.md`
4
+
5
+ Index: `node_modules/@plumbus/ai-bedrock/instructions/README.md`
6
+ Pricing pull / k8s: `node_modules/@plumbus/ai-bedrock/instructions/pricing.md`
7
+
8
+ `@plumbus/ai-bedrock` is the **Amazon Bedrock AI provider** for Plumbus. It implements `AIProviderAdapter` (Converse chat, Titan embeddings, package-owned pricing). It is an **optional peer** of `@plumbus/core` (version-locked **`0.6.x`**).
9
+
10
+ **`package.json` peer:** `"@plumbus/core": "0.6.x"` — copy literally; see `packages/plumbus-core/instructions/peer-dependencies.md`.
11
+
12
+ ## When to use / when not
13
+
14
+ | Use this package | Do not use |
15
+ |------------------|------------|
16
+ | App needs Bedrock **Runtime** Converse via `ctx.ai` / Titan embed via RAG | OpenAI-only or Anthropic Messages API-only stacks |
17
+ | AWS IAM / IRSA (or SDK bearer) for Bedrock Runtime | Bedrock **Mantle** OpenAI-compatible URL → use `createOpenAIAdapter` instead |
18
+ | You want cost recording from **AWS Bedrock** rates | Expecting dollars inside the Converse response (Bedrock never returns USD) |
19
+ | Regional Bedrock model ids (`anthropic.claude-…-v1:0`) | Anthropic API short names alone (`claude-sonnet-4-…`) without Bedrock ids |
20
+
21
+ ## Package boundary
22
+
23
+ | Concern | Owned by |
24
+ |---------|----------|
25
+ | `AIProviderAdapter`, `createAIService`, `ctx.ai.*`, ledger hooks | `@plumbus/core` |
26
+ | Optional `ProviderResponse.cost` preference | `@plumbus/core` |
27
+ | Env discovery `AI_BEDROCK_*`, `createProviderAdapter('bedrock')` | `@plumbus/core` (dynamic `createRequire`) |
28
+ | AWS SDK client, Converse / ConverseStream / InvokeModel | `@plumbus/ai-bedrock` |
29
+ | Price List fetch, pricing file parse, USD from usage × rates | `@plumbus/ai-bedrock` |
30
+ | App prompts / capabilities / chat | Consumer app (`definePrompt`, `defineCapability`, `@plumbus/chat`) |
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pnpm add @plumbus/ai-bedrock
36
+ ```
37
+
38
+ Requires `@plumbus/core` **≥ 0.6.16**. After install on an existing app, refresh agent wiring so coding agents discover these files:
39
+
40
+ ```bash
41
+ plumbus init --patch --agent agents-md
42
+ plumbus doctor
43
+ ```
44
+
45
+ Missing package + `createProviderAdapter('bedrock')` → clear error: run `pnpm add @plumbus/ai-bedrock`.
46
+
47
+ Core that loads this package must prefer adapter-supplied `cost` (**`@plumbus/core` ≥ 0.6.16**). Older cores ignore package rates and under-report spend; they also lack `createProviderAdapter('bedrock')` / `AI_BEDROCK_*` env discovery.
48
+
49
+ ## Public exports
50
+
51
+ | Export | Role |
52
+ |--------|------|
53
+ | `createBedrockAdapter(config)` | Sync factory → `AIProviderAdapter` (`name: 'bedrock'`) |
54
+ | `BEDROCK_DEFAULT_EMBEDDING_MODEL` | `amazon.titan-embed-text-v2:0` |
55
+ | `BedrockAdapterConfig` | Config type (region, pricing file, TTL, credentials, …) |
56
+ | `BedrockPricingFileV1` / `BedrockModelRate` | Normalized pricing file types |
57
+ | `parseAwsOfferRates` / `parsePricingFile` / `normalizeBedrockModelId` / `extractModelIdFromOfferAttrs` | CI helpers to build / validate the pricing file (auto-download is best-effort) |
58
+ | `createPricingStore` | Advanced / tests |
59
+
60
+ ## Wiring
61
+
62
+ ### Env + bootstrap (same path as OpenAI/Anthropic)
63
+
64
+ ```bash
65
+ AI_DEFAULT_PROVIDER=bedrock
66
+ AI_BEDROCK_REGION=us-east-1
67
+ AI_BEDROCK_MODEL=anthropic.claude-sonnet-4-5-20250929-v1:0
68
+ AI_BEDROCK_EMBEDDING_MODEL=amazon.titan-embed-text-v2:0
69
+ # containers / k8s (recommended):
70
+ AI_BEDROCK_PRICING_FILE=/config/bedrock-pricing.json
71
+ # optional:
72
+ # AI_BEDROCK_PRICING_TTL_MS=86400000
73
+ # AI_BEDROCK_REQUEST_TIMEOUT=120000
74
+ ```
75
+
76
+ | Variable | Required | Purpose |
77
+ |----------|----------|---------|
78
+ | `AI_BEDROCK_REGION` | yes\* | Bedrock Runtime region + Price List path region |
79
+ | `AI_BEDROCK_ENABLED` | alt | With `AWS_REGION` / `AWS_DEFAULT_REGION`, enables discovery without `AI_BEDROCK_REGION` |
80
+ | `AI_BEDROCK_MODEL` | no | Default chat model id |
81
+ | `AI_BEDROCK_EMBEDDING_MODEL` | no | Default embed model (else Titan V2) |
82
+ | `AI_BEDROCK_PRICING_FILE` | **yes in k8s** | Absolute path to normalized pricing JSON |
83
+ | `AI_BEDROCK_PRICING_TTL_MS` | no | Auto-download cache TTL (default ~24h) |
84
+ | `AI_BEDROCK_REQUEST_TIMEOUT` | no | Client timeout ms |
85
+ | `AI_BEDROCK_MAX_TOKENS` | no | Core per-request max-tokens guard |
86
+ | `AI_BEDROCK_DAILY_COST_LIMIT` | no | Core daily USD cost guard |
87
+
88
+ \*Discovery enables Bedrock when `AI_BEDROCK_REGION` is set, or `AI_BEDROCK_ENABLED=1`/`true` + `AWS_REGION` / `AWS_DEFAULT_REGION`. There is **no** `AI_BEDROCK_API_KEY` — inference uses the AWS default credential chain (env keys, shared config, IRSA, instance role). Optional `AWS_BEARER_TOKEN_BEDROCK` is honored by the AWS SDK when set. Optional `AI_BEDROCK_MAX_TOKENS` / `AI_BEDROCK_DAILY_COST_LIMIT` are core AI guards (same pattern as OpenAI/Anthropic), not Converse request fields by themselves.
89
+
90
+ ### Programmatic
91
+
92
+ ```typescript
93
+ import { createAIService, createOpenAIAdapter } from '@plumbus/core';
94
+ import { createBedrockAdapter } from '@plumbus/ai-bedrock';
95
+
96
+ createAIService({
97
+ defaultProvider: 'bedrock',
98
+ providers: {
99
+ bedrock: createBedrockAdapter({
100
+ region: 'us-east-1',
101
+ pricingFilePath: process.env.AI_BEDROCK_PRICING_FILE,
102
+ }),
103
+ openai: createOpenAIAdapter({ apiKey: process.env.AI_OPENAI_API_KEY ?? '' }),
104
+ },
105
+ });
106
+ ```
107
+
108
+ `createBedrockAdapter` is **sync**. Pricing warms on first AI call by default (`warmPricingOnCreate`).
109
+
110
+ ### Prompts (unchanged `ctx.ai` API)
111
+
112
+ ```typescript
113
+ import { definePrompt } from '@plumbus/core';
114
+ import { z } from '@plumbus/core/zod';
115
+
116
+ export const classifyTicket = definePrompt({
117
+ name: 'classifyTicket',
118
+ system: 'You classify support tickets.',
119
+ description: 'Classify a support ticket',
120
+ domain: 'support',
121
+ input: z.object({ ticketText: z.string() }),
122
+ output: z.object({
123
+ department: z.enum(['billing', 'technical', 'general']),
124
+ urgency: z.enum(['low', 'medium', 'high']),
125
+ }),
126
+ model: {
127
+ provider: 'bedrock',
128
+ name: 'anthropic.claude-sonnet-4-5-20250929-v1:0',
129
+ temperature: 0.2,
130
+ maxTokens: 256,
131
+ },
132
+ });
133
+ ```
134
+
135
+ ```typescript
136
+ const { data, usage, provider, cost } = await ctx.ai.generateWithUsage({
137
+ prompt: 'classifyTicket',
138
+ input: { ticketText: input.body },
139
+ });
140
+ // provider === 'bedrock'
141
+ // cost === USD from pricing file or auto-downloaded regional rates
142
+ ```
143
+
144
+ Chat (`@plumbus/chat`) and RAG `retrieve` work the same way once the provider is `bedrock` (RAG needs `ragPipeline` wired with this adapter — see below).
145
+
146
+ ### Tool calling
147
+
148
+ Same core API as OpenAI/Anthropic. Prompt `model.provider` must be `'bedrock'` (or `defaultProvider: 'bedrock'`):
149
+
150
+ ```typescript
151
+ import { runToolLoop, zodToProviderJsonSchema, type AITool } from '@plumbus/core';
152
+ import { z } from '@plumbus/core/zod';
153
+
154
+ const tools: AITool[] = [
155
+ {
156
+ name: 'lookupOrder',
157
+ description: 'Look up an order by id',
158
+ parameters: zodToProviderJsonSchema(z.object({ orderId: z.string() })).schema,
159
+ },
160
+ ];
161
+
162
+ const { final, aggregatedCost } = await runToolLoop(ctx.ai, {
163
+ prompt: 'assistant.turn',
164
+ input: { userMessage },
165
+ tools,
166
+ execute: async (call) => lookupOrder(call.arguments as { orderId: string }),
167
+ });
168
+ // final.data — answer after tool rounds
169
+ // aggregatedCost — sum of adapter `cost` across Converse rounds
170
+ ```
171
+
172
+ **Gotchas:** `toolChoice: 'none'` omits `toolConfig` (Bedrock has no true none); `'auto'` omits just the `toolChoice` field. Only execute `argumentsStatus === 'parsed'`. Full protocol: `node_modules/@plumbus/core/instructions/ai.md`.
173
+
174
+ ### Structured outputs
175
+
176
+ Default is `structuredOutputs: 'off'` — prompt `output` schemas are enforced by core's validate-and-repair loop, which works on every model. Set `structuredOutputs: 'native'` on `createBedrockAdapter` to forward the schema as Converse `outputConfig.textFormat`. It is opt-in because model support varies and an unsupported model rejects the whole request.
177
+
178
+ ### Embeddings (RAG)
179
+
180
+ There is **no** `ctx.ai.embed`. Pass this adapter into `createRAGPipeline({ provider })`:
181
+
182
+ ```typescript
183
+ import { createRAGPipeline, createInMemoryVectorStore } from '@plumbus/core';
184
+ import { createBedrockAdapter } from '@plumbus/ai-bedrock';
185
+
186
+ const bedrock = createBedrockAdapter({ region: 'us-east-1' });
187
+ const ragPipeline = createRAGPipeline({
188
+ provider: bedrock,
189
+ vectorStore: createInMemoryVectorStore(),
190
+ embeddingModel: 'amazon.titan-embed-text-v2:0', // keep ingest + query identical
191
+ });
192
+ // Pass ragPipeline into createAIService({ …, ragPipeline }) → ctx.ai.retrieve
193
+ // Ingest with ragPipeline.ingest(…). `plumbus rag ingest` is OpenAI-oriented today
194
+ // (uses ai.apiKey); do not expect it to pick Titan automatically.
195
+ ```
196
+
197
+ **Gotchas:** Mantle has no embeddings. Titan must be enabled in-console. Unkeyed chat models still chat but carry **no `cost` field** until the pricing file has a row.
198
+
199
+ Cohere embedding models (`cohere.*`) are supported too — the adapter sends `{ texts, input_type }` and reads `embeddings[0]`. `input_type` defaults to `search_document` (`embeddingInputType` to override). Titan takes one text per call, so ingests fan out up to `embedConcurrency` (default 4) with output order preserved.
200
+
201
+ ## Auth (IAM)
202
+
203
+ Inference uses the **AWS default credential chain** (env keys, shared config, IRSA, instance role). Optional `AWS_BEARER_TOKEN_BEDROCK` is honored by the AWS SDK when set. There is no `apiKey` on `createBedrockAdapter`.
204
+
205
+ **Not this package:** Bedrock Mantle OpenAI-compatible URLs (`bedrock-mantle.*.api.aws` + console `OPENAI_API_KEY`) use core’s `createOpenAIAdapter` — see `docs/ai/bedrock.md`.
206
+
207
+ Minimal IAM sketch for Runtime (tighten resource ARNs in production). This package calls **Converse**, **ConverseStream**, and **InvokeModel**. Note that AWS documents Converse itself as requiring `bedrock:InvokeModel` — so when narrowing `Resource`, keep `InvokeModel` on the **chat** model ARNs too, not just the embedding model. `InvokeModelWithResponseStream` is unused by the adapter today:
208
+
209
+ ```json
210
+ {
211
+ "Effect": "Allow",
212
+ "Action": [
213
+ "bedrock:InvokeModel",
214
+ "bedrock:Converse",
215
+ "bedrock:ConverseStream"
216
+ ],
217
+ "Resource": "*"
218
+ }
219
+ ```
220
+
221
+ Also ensure the model / inference profile is **enabled** in the Bedrock console for that account/region (Anthropic models may require a use-case form).
222
+
223
+ Price List downloads do **not** use IAM — only outbound HTTPS to `pricing.us-east-1.amazonaws.com`.
224
+
225
+ ## Model ids
226
+
227
+ Use **Bedrock** model ids (or inference-profile ids), not Anthropic Messages API short names alone:
228
+
229
+ - Chat example: `anthropic.claude-sonnet-4-5-20250929-v1:0` or `us.anthropic.claude-…`
230
+ - Embed default: `amazon.titan-embed-text-v2:0`
231
+
232
+ Rate lookup strips geo prefixes (`us.`, `eu.`, …) and dated `-YYYYMMDD-vN:M` suffixes to a family key.
233
+
234
+ ## Pricing (summary)
235
+
236
+ Full pull URLs, curl, normalize script, and k8s ConfigMap recipe: **[pricing.md](./pricing.md)**.
237
+
238
+ | Mode | Behavior |
239
+ |------|----------|
240
+ | `pricingFilePath` / `AI_BEDROCK_PRICING_FILE` set | **Reliable path** — load normalized JSON keyed by family ids; **no** Price List fetch |
241
+ | Else (auto-download) | **Best-effort** — fetch offers; key rows via extracted SDK ids + limited generative display inference; unknowns stay `$0` |
242
+
243
+ **Do not hardcode / expect a forever-complete AWS model catalog in this package.** Put the models you call in the pricing file.
244
+
245
+ Example Price List URL (us-east-1 FoundationModels):
246
+
247
+ ```text
248
+ https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/us-east-1/index.json
249
+ ```
250
+
251
+ Bedrock does **not** return dollars on chat responses — only token usage. This package multiplies usage × rates and sets `cost` on the adapter response; core prefers that over OpenAI/Anthropic `MODEL_PRICING`.
252
+
253
+ ### IAM vs CDN
254
+
255
+ | Concern | Need |
256
+ |---------|------|
257
+ | Converse / InvokeModel | IAM / IRSA / instance role |
258
+ | Auto-download Price List | Outbound HTTPS to `pricing.us-east-1.amazonaws.com` (no IAM) |
259
+ | Pricing file mode | No pricing CDN egress |
260
+
261
+ ## Capabilities
262
+
263
+ Declared: `tools`, `streamingTools`, `parallelToolCalls`, `namedToolChoice` (Converse tool-use). Model-dependent limits still apply on the AWS side.
264
+
265
+ ## Troubleshooting (quick)
266
+
267
+ | Symptom | Likely fix |
268
+ |---------|------------|
269
+ | Install / load error for `bedrock` | `pnpm add @plumbus/ai-bedrock`; core **≥ 0.6.16** |
270
+ | AccessDenied / model not found | IAM + Bedrock console model access for region (Anthropic use-case form) |
271
+ | `cost` missing from responses | No rate for that model family — see [pricing.md](./pricing.md). Unkeyed models (Llama, Mistral, newer Nova generations) need a manual pricing-file row. The field is omitted rather than set to `0`, so core falls back to its own catalog instead of recording spend as free |
272
+ | Auto-download fails in cluster | Mount `AI_BEDROCK_PRICING_FILE` |
273
+ | Mantle `OPENAI_API_KEY` does not work here | Correct — Mantle uses `createOpenAIAdapter`; this package is Runtime + IAM |
274
+ | Tools never fire | Model must support Converse tools; check `finishReason === 'tool_calls'` / use `runToolLoop` |
275
+ | RAG retrieve fails / dim mismatch | Wire `ragPipeline` with this adapter; same Titan model id for ingest + query |
276
+
277
+ ## Critical rules
278
+
279
+ 1. **Framework-first** — business logic in `definePrompt` / capabilities / `ctx.ai`; never call Bedrock SDK from app code for product features.
280
+ 2. **Do not** copy Anthropic `MODEL_PRICING` rows for Bedrock — rates diverge (e.g. Haiku 4.5 regional Bedrock ≠ Anthropic API).
281
+ 3. **Containers:** prefer `AI_BEDROCK_PRICING_FILE` ([pricing.md](./pricing.md)).
282
+ 4. **Peer literal** is `"0.6.x"` — never `^0.6.0`. Runtime floor **≥ 0.6.16**.
283
+ 5. Install the package explicitly; core only `createRequire`s it when the provider name is `bedrock`.
284
+ 6. **Tools** — use `runToolLoop` / `generateWithUsage({ tools })`; never invent a Bedrock-only tool API. `toolChoice: 'none'` omits tools.
285
+ 7. **Embeddings** — via `createRAGPipeline({ provider: bedrockAdapter })` + `ctx.ai.retrieve` / rag ingest — not Converse, not Mantle.
286
+
287
+ ## Where to look for more
288
+
289
+ - [pricing.md](./pricing.md) — Price List URLs, generate normalized file, k8s
290
+ - [README.md](../README.md) — quick start
291
+ - Monorepo concept guide: `docs/ai/bedrock.md` (Runtime vs Mantle, tools/stream, checklist)
292
+ - Broader AI stack: `docs/ai/ai-integration.md`
293
+ - Core peer rules: `packages/plumbus-core/instructions/peer-dependencies.md`