@warlock.js/ai-bedrock 4.1.1

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.
package/esm/model.mjs ADDED
@@ -0,0 +1,290 @@
1
+ import { mapStopReason } from "./utils/map-stop-reason.mjs";
2
+ import { toBedrockMessages } from "./utils/to-bedrock-messages.mjs";
3
+ import { toBedrockToolConfig } from "./utils/to-bedrock-tools.mjs";
4
+ import { wrapBedrockError } from "./utils/wrap-bedrock-error.mjs";
5
+ import "./utils/index.mjs";
6
+ import { inferVisionCapability } from "./known-vision-models.mjs";
7
+ import { ConverseCommand, ConverseStreamCommand } from "@aws-sdk/client-bedrock-runtime";
8
+ import { safeJsonParse } from "@warlock.js/ai";
9
+ import { log } from "@warlock.js/logger";
10
+
11
+ //#region ../../@warlock.js/ai-bedrock/src/model.ts
12
+ const LOG_MODULE = "ai.bedrock";
13
+ /**
14
+ * Bedrock-backed implementation of `ModelContract`.
15
+ *
16
+ * **Role.** The provider-facing bridge between the vendor-neutral
17
+ * `@warlock.js/ai` agent runtime and AWS Bedrock's Converse /
18
+ * ConverseStream API. Converse is the model-agnostic surface — one
19
+ * wire mapping covers every Bedrock-hosted family (Anthropic Claude,
20
+ * Amazon Nova, Meta Llama, Mistral, Cohere) instead of per-family
21
+ * `InvokeModel` body shapes.
22
+ *
23
+ * **Responsibility.**
24
+ * - Owns: a long-lived `BedrockRuntimeClient` + frozen `ModelConfig`
25
+ * (modelId, temperature, maxTokens) used as per-call defaults.
26
+ * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into
27
+ * Converse shapes (system hoisting, `toolUse` / `toolResult` blocks,
28
+ * image bytes) on the way out, and Converse's content-block response
29
+ * (text, tool calls, stop reason, token usage) back into the neutral
30
+ * shapes on the way in.
31
+ * - Does NOT own: dispatching tools, looping, history, retries — those
32
+ * are agent concerns. The model is a per-call protocol adapter.
33
+ *
34
+ * Modeled as a class (see §4.2 of code-style.md — "long-lived state
35
+ * across calls"): the AWS client is heavy to construct and reused for
36
+ * the SDK's lifetime.
37
+ *
38
+ * @example
39
+ * import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
40
+ * const client = new BedrockRuntimeClient({ region: "us-east-1" });
41
+ * const model = new BedrockModel(client, {
42
+ * name: "anthropic.claude-sonnet-4-5-20250929-v1:0",
43
+ * });
44
+ *
45
+ * const myAgent = agent({ model, tools: [searchTool] });
46
+ * const result = await myAgent.execute("Summarize today's news.");
47
+ */
48
+ var BedrockModel = class {
49
+ constructor(client, config, provider = "bedrock") {
50
+ this.logger = log;
51
+ this.client = client;
52
+ this.config = config;
53
+ this.name = config.name;
54
+ this.provider = provider;
55
+ this.pricing = config.pricing;
56
+ this.capabilities = {
57
+ structuredOutput: config.structuredOutput ?? true,
58
+ vision: config.vision ?? inferVisionCapability(config.name)
59
+ };
60
+ }
61
+ /**
62
+ * Single-shot completion via the Converse API. Sends the full
63
+ * message list, waits for the terminal response, and reshapes it
64
+ * into a vendor-neutral `ModelResponse`. Per-call `options` override
65
+ * the instance defaults for this call only.
66
+ */
67
+ async complete(messages, options) {
68
+ this.logger.debug(LOG_MODULE, "request", "Starting Converse call", {
69
+ model: this.name,
70
+ messageCount: messages.length,
71
+ streaming: false,
72
+ toolCount: options?.tools?.length ?? 0
73
+ });
74
+ let response;
75
+ try {
76
+ response = await this.client.send(new ConverseCommand(this.buildRequest(messages, options)), options?.signal ? { abortSignal: options.signal } : void 0);
77
+ } catch (thrown) {
78
+ throw this.logAndWrap(thrown);
79
+ }
80
+ const blocks = response.output?.message?.content ?? [];
81
+ const finishReason = mapStopReason(response.stopReason);
82
+ const usage = this.extractUsage(response.usage);
83
+ const toolCalls = this.extractToolCalls(blocks);
84
+ this.logger.debug(LOG_MODULE, "response", "Converse call succeeded", {
85
+ finishReason,
86
+ usage
87
+ });
88
+ return {
89
+ content: this.extractText(blocks),
90
+ finishReason,
91
+ usage,
92
+ toolCalls
93
+ };
94
+ }
95
+ /**
96
+ * Incremental streaming completion via ConverseStream. Yields neutral
97
+ * `ModelStreamChunk`s — `delta` for text, `tool-call` once a
98
+ * `toolUse` block's accumulated input JSON is complete, and a
99
+ * terminal `done` with the final finish reason + usage totals.
100
+ */
101
+ async *stream(messages, options) {
102
+ this.logger.debug(LOG_MODULE, "request", "Starting ConverseStream call", {
103
+ model: this.name,
104
+ messageCount: messages.length,
105
+ streaming: true,
106
+ toolCount: options?.tools?.length ?? 0
107
+ });
108
+ let response;
109
+ try {
110
+ response = await this.client.send(new ConverseStreamCommand(this.buildRequest(messages, options)), options?.signal ? { abortSignal: options.signal } : void 0);
111
+ } catch (thrown) {
112
+ throw this.logAndWrap(thrown);
113
+ }
114
+ let rawStopReason;
115
+ const usage = {
116
+ input: 0,
117
+ output: 0,
118
+ total: 0
119
+ };
120
+ const toolBlocks = /* @__PURE__ */ new Map();
121
+ try {
122
+ for await (const event of response.stream ?? []) {
123
+ if (event.contentBlockStart?.start?.toolUse) {
124
+ const start = event.contentBlockStart.start.toolUse;
125
+ toolBlocks.set(event.contentBlockStart.contentBlockIndex ?? 0, {
126
+ id: start.toolUseId ?? "",
127
+ name: start.name ?? "",
128
+ json: ""
129
+ });
130
+ continue;
131
+ }
132
+ if (event.contentBlockDelta?.delta) {
133
+ const delta = event.contentBlockDelta.delta;
134
+ if (delta.text) yield {
135
+ type: "delta",
136
+ content: delta.text
137
+ };
138
+ else if (delta.toolUse) {
139
+ const accumulator = toolBlocks.get(event.contentBlockDelta.contentBlockIndex ?? 0);
140
+ if (accumulator) accumulator.json += delta.toolUse.input ?? "";
141
+ }
142
+ continue;
143
+ }
144
+ if (event.contentBlockStop) {
145
+ const accumulator = toolBlocks.get(event.contentBlockStop.contentBlockIndex ?? 0);
146
+ if (accumulator) {
147
+ yield {
148
+ type: "tool-call",
149
+ id: accumulator.id,
150
+ name: accumulator.name,
151
+ input: safeJsonParse(accumulator.json, {})
152
+ };
153
+ toolBlocks.delete(event.contentBlockStop.contentBlockIndex ?? 0);
154
+ }
155
+ continue;
156
+ }
157
+ if (event.messageStop) rawStopReason = event.messageStop.stopReason;
158
+ if (event.metadata?.usage) {
159
+ const raw = event.metadata.usage;
160
+ usage.input = raw.inputTokens ?? 0;
161
+ usage.output = raw.outputTokens ?? 0;
162
+ usage.total = raw.totalTokens ?? usage.input + usage.output;
163
+ if (raw.cacheReadInputTokens && raw.cacheReadInputTokens > 0) usage.cachedTokens = raw.cacheReadInputTokens;
164
+ }
165
+ }
166
+ } catch (thrown) {
167
+ throw this.logAndWrap(thrown);
168
+ }
169
+ const finishReason = mapStopReason(rawStopReason);
170
+ this.logger.debug(LOG_MODULE, "response", "ConverseStream call succeeded", {
171
+ finishReason,
172
+ usage
173
+ });
174
+ yield {
175
+ type: "done",
176
+ finishReason,
177
+ usage
178
+ };
179
+ }
180
+ /**
181
+ * Assemble the Converse request shared by `complete()` and
182
+ * `stream()` (both command shapes take the same input). Hoists the
183
+ * system prompt, maps inference params, and conditionally attaches
184
+ * tools and native structured output.
185
+ */
186
+ buildRequest(messages, options) {
187
+ const { system, messages: bedrockMessages } = toBedrockMessages(messages);
188
+ const maxTokens = options?.maxTokens ?? this.config.maxTokens;
189
+ const temperature = options?.temperature ?? this.config.temperature;
190
+ return {
191
+ modelId: this.name,
192
+ messages: bedrockMessages,
193
+ ...system ? { system } : {},
194
+ inferenceConfig: {
195
+ ...maxTokens !== void 0 ? { maxTokens } : {},
196
+ ...temperature !== void 0 ? { temperature } : {}
197
+ },
198
+ ...this.buildToolConfig(options?.tools),
199
+ ...this.buildOutputConfig(options?.responseSchema)
200
+ };
201
+ }
202
+ /**
203
+ * Spread-friendly tool fragment. Returns an empty object when no
204
+ * tools were supplied (Bedrock rejects an empty `tools` array).
205
+ */
206
+ buildToolConfig(tools) {
207
+ const toolConfig = toBedrockToolConfig(tools);
208
+ return toolConfig ? { toolConfig } : {};
209
+ }
210
+ /**
211
+ * Translate the neutral `responseSchema` into Converse's native
212
+ * `outputConfig.textFormat` (JSON-schema structured output). Bedrock
213
+ * requires the schema as a stringified JSON document and only
214
+ * accepts an object root. Emitted only when the model is
215
+ * `structuredOutput`-capable and the schema is an object — otherwise
216
+ * the agent's soft system-prompt hint + client-side `validate()`
217
+ * carry shape (same degradation philosophy as the OpenAI adapter).
218
+ */
219
+ buildOutputConfig(responseSchema) {
220
+ if (!responseSchema || !this.capabilities.structuredOutput) return {};
221
+ if (responseSchema.type !== "object" || typeof responseSchema.properties !== "object") return {};
222
+ return { outputConfig: { textFormat: {
223
+ type: "json_schema",
224
+ structure: { jsonSchema: {
225
+ name: "response",
226
+ schema: JSON.stringify(responseSchema)
227
+ } }
228
+ } } };
229
+ }
230
+ /**
231
+ * Concatenate every `text` content block into the single neutral
232
+ * `content` string. `toolUse` and other block types are surfaced
233
+ * separately via `extractToolCalls`.
234
+ */
235
+ extractText(blocks) {
236
+ return blocks.map((block) => "text" in block && typeof block.text === "string" ? block.text : "").join("");
237
+ }
238
+ /**
239
+ * Reshape Converse `toolUse` content blocks into the neutral
240
+ * `ModelToolCallRequest[]`. Returns `undefined` when no tools were
241
+ * requested so callers can branch on presence.
242
+ */
243
+ extractToolCalls(blocks) {
244
+ const toolCalls = [];
245
+ for (const block of blocks) if ("toolUse" in block && block.toolUse) toolCalls.push({
246
+ id: block.toolUse.toolUseId ?? "",
247
+ name: block.toolUse.name ?? "",
248
+ input: block.toolUse.input ?? {}
249
+ });
250
+ return toolCalls.length > 0 ? toolCalls : void 0;
251
+ }
252
+ /**
253
+ * Normalize Converse's `TokenUsage` into the neutral `Usage` shape.
254
+ * Bedrock supplies a pre-summed `totalTokens`; cache-read tokens are
255
+ * surfaced as `cachedTokens` only when non-zero.
256
+ */
257
+ extractUsage(raw) {
258
+ if (!raw) return {
259
+ input: 0,
260
+ output: 0,
261
+ total: 0
262
+ };
263
+ const input = raw.inputTokens ?? 0;
264
+ const output = raw.outputTokens ?? 0;
265
+ const cached = raw.cacheReadInputTokens;
266
+ return {
267
+ input,
268
+ output,
269
+ total: raw.totalTokens ?? input + output,
270
+ ...cached && cached > 0 ? { cachedTokens: cached } : {}
271
+ };
272
+ }
273
+ /**
274
+ * Wrap a thrown provider error into the typed `AIError` hierarchy
275
+ * and emit the standard error log line before it propagates. Shared
276
+ * by every catch site so the log shape stays identical.
277
+ */
278
+ logAndWrap(thrown) {
279
+ const wrapped = wrapBedrockError(thrown);
280
+ this.logger.error(LOG_MODULE, "error", wrapped.message, {
281
+ code: wrapped.code,
282
+ context: wrapped.context
283
+ });
284
+ return wrapped;
285
+ }
286
+ };
287
+
288
+ //#endregion
289
+ export { BedrockModel };
290
+ //# sourceMappingURL=model.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.mjs","names":[],"sources":["../../../../../@warlock.js/ai-bedrock/src/model.ts"],"sourcesContent":["import {\n safeJsonParse,\n type Message,\n type ModelCallOptions,\n type ModelCapabilities,\n type ModelContract,\n type ModelPricing,\n type ModelResponse,\n type ModelStreamChunk,\n type ModelToolCallRequest,\n type Usage,\n} from \"@warlock.js/ai\";\nimport { log, type Logger } from \"@warlock.js/logger\";\nimport {\n ConverseCommand,\n ConverseStreamCommand,\n type BedrockRuntimeClient,\n type ContentBlock,\n type ConverseRequest,\n type TokenUsage,\n} from \"@aws-sdk/client-bedrock-runtime\";\nimport type { BedrockModelConfig } from \"./config.type\";\nimport { inferVisionCapability } from \"./known-vision-models\";\nimport { mapStopReason, toBedrockMessages, toBedrockToolConfig, wrapBedrockError } from \"./utils\";\n\nconst LOG_MODULE = \"ai.bedrock\";\n\n/**\n * Bedrock-backed implementation of `ModelContract`.\n *\n * **Role.** The provider-facing bridge between the vendor-neutral\n * `@warlock.js/ai` agent runtime and AWS Bedrock's Converse /\n * ConverseStream API. Converse is the model-agnostic surface — one\n * wire mapping covers every Bedrock-hosted family (Anthropic Claude,\n * Amazon Nova, Meta Llama, Mistral, Cohere) instead of per-family\n * `InvokeModel` body shapes.\n *\n * **Responsibility.**\n * - Owns: a long-lived `BedrockRuntimeClient` + frozen `ModelConfig`\n * (modelId, temperature, maxTokens) used as per-call defaults.\n * - Owns: translating vendor-neutral `Message[]` / `ToolConfig[]` into\n * Converse shapes (system hoisting, `toolUse` / `toolResult` blocks,\n * image bytes) on the way out, and Converse's content-block response\n * (text, tool calls, stop reason, token usage) back into the neutral\n * shapes on the way in.\n * - Does NOT own: dispatching tools, looping, history, retries — those\n * are agent concerns. The model is a per-call protocol adapter.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across calls\"): the AWS client is heavy to construct and reused for\n * the SDK's lifetime.\n *\n * @example\n * import { BedrockRuntimeClient } from \"@aws-sdk/client-bedrock-runtime\";\n * const client = new BedrockRuntimeClient({ region: \"us-east-1\" });\n * const model = new BedrockModel(client, {\n * name: \"anthropic.claude-sonnet-4-5-20250929-v1:0\",\n * });\n *\n * const myAgent = agent({ model, tools: [searchTool] });\n * const result = await myAgent.execute(\"Summarize today's news.\");\n */\nexport class BedrockModel implements ModelContract {\n public readonly name: string;\n public readonly provider: string;\n public readonly capabilities: ModelCapabilities;\n public readonly pricing?: ModelPricing;\n\n private readonly client: BedrockRuntimeClient;\n private readonly config: BedrockModelConfig;\n private readonly logger: Logger = log;\n\n public constructor(\n client: BedrockRuntimeClient,\n config: BedrockModelConfig,\n provider: string = \"bedrock\",\n ) {\n this.client = client;\n this.config = config;\n this.name = config.name;\n this.provider = provider;\n this.pricing = config.pricing;\n this.capabilities = {\n structuredOutput: config.structuredOutput ?? true,\n vision: config.vision ?? inferVisionCapability(config.name),\n };\n }\n\n /**\n * Single-shot completion via the Converse API. Sends the full\n * message list, waits for the terminal response, and reshapes it\n * into a vendor-neutral `ModelResponse`. Per-call `options` override\n * the instance defaults for this call only.\n */\n public async complete(messages: Message[], options?: ModelCallOptions): Promise<ModelResponse> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting Converse call\", {\n model: this.name,\n messageCount: messages.length,\n streaming: false,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response;\n\n try {\n response = await this.client.send(\n new ConverseCommand(this.buildRequest(messages, options)),\n options?.signal ? { abortSignal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const blocks = response.output?.message?.content ?? [];\n const finishReason = mapStopReason(response.stopReason);\n const usage = this.extractUsage(response.usage);\n const toolCalls = this.extractToolCalls(blocks);\n\n this.logger.debug(LOG_MODULE, \"response\", \"Converse call succeeded\", { finishReason, usage });\n\n return {\n content: this.extractText(blocks),\n finishReason,\n usage,\n toolCalls,\n };\n }\n\n /**\n * Incremental streaming completion via ConverseStream. Yields neutral\n * `ModelStreamChunk`s — `delta` for text, `tool-call` once a\n * `toolUse` block's accumulated input JSON is complete, and a\n * terminal `done` with the final finish reason + usage totals.\n */\n public async *stream(\n messages: Message[],\n options?: ModelCallOptions,\n ): AsyncIterable<ModelStreamChunk> {\n this.logger.debug(LOG_MODULE, \"request\", \"Starting ConverseStream call\", {\n model: this.name,\n messageCount: messages.length,\n streaming: true,\n toolCount: options?.tools?.length ?? 0,\n });\n\n let response;\n\n try {\n response = await this.client.send(\n new ConverseStreamCommand(this.buildRequest(messages, options)),\n options?.signal ? { abortSignal: options.signal } : undefined,\n );\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n let rawStopReason: string | undefined;\n const usage: Usage = { input: 0, output: 0, total: 0 };\n const toolBlocks = new Map<number, { id: string; name: string; json: string }>();\n\n try {\n for await (const event of response.stream ?? []) {\n if (event.contentBlockStart?.start?.toolUse) {\n const start = event.contentBlockStart.start.toolUse;\n\n toolBlocks.set(event.contentBlockStart.contentBlockIndex ?? 0, {\n id: start.toolUseId ?? \"\",\n name: start.name ?? \"\",\n json: \"\",\n });\n\n continue;\n }\n\n if (event.contentBlockDelta?.delta) {\n const delta = event.contentBlockDelta.delta;\n\n if (delta.text) {\n yield { type: \"delta\", content: delta.text };\n } else if (delta.toolUse) {\n const accumulator = toolBlocks.get(event.contentBlockDelta.contentBlockIndex ?? 0);\n\n if (accumulator) {\n accumulator.json += delta.toolUse.input ?? \"\";\n }\n }\n\n continue;\n }\n\n if (event.contentBlockStop) {\n const accumulator = toolBlocks.get(event.contentBlockStop.contentBlockIndex ?? 0);\n\n if (accumulator) {\n yield {\n type: \"tool-call\",\n id: accumulator.id,\n name: accumulator.name,\n input: safeJsonParse<Record<string, unknown>>(accumulator.json, {}),\n };\n\n toolBlocks.delete(event.contentBlockStop.contentBlockIndex ?? 0);\n }\n\n continue;\n }\n\n if (event.messageStop) {\n rawStopReason = event.messageStop.stopReason;\n }\n\n if (event.metadata?.usage) {\n const raw = event.metadata.usage;\n\n usage.input = raw.inputTokens ?? 0;\n usage.output = raw.outputTokens ?? 0;\n usage.total = raw.totalTokens ?? usage.input + usage.output;\n\n if (raw.cacheReadInputTokens && raw.cacheReadInputTokens > 0) {\n usage.cachedTokens = raw.cacheReadInputTokens;\n }\n }\n }\n } catch (thrown) {\n throw this.logAndWrap(thrown);\n }\n\n const finishReason = mapStopReason(rawStopReason);\n\n this.logger.debug(LOG_MODULE, \"response\", \"ConverseStream call succeeded\", {\n finishReason,\n usage,\n });\n\n yield { type: \"done\", finishReason, usage };\n }\n\n /**\n * Assemble the Converse request shared by `complete()` and\n * `stream()` (both command shapes take the same input). Hoists the\n * system prompt, maps inference params, and conditionally attaches\n * tools and native structured output.\n */\n private buildRequest(\n messages: Message[],\n options: ModelCallOptions | undefined,\n ): ConverseRequest {\n const { system, messages: bedrockMessages } = toBedrockMessages(messages);\n const maxTokens = options?.maxTokens ?? this.config.maxTokens;\n const temperature = options?.temperature ?? this.config.temperature;\n\n return {\n modelId: this.name,\n messages: bedrockMessages,\n ...(system ? { system } : {}),\n inferenceConfig: {\n ...(maxTokens !== undefined ? { maxTokens } : {}),\n ...(temperature !== undefined ? { temperature } : {}),\n },\n ...this.buildToolConfig(options?.tools),\n ...this.buildOutputConfig(options?.responseSchema),\n };\n }\n\n /**\n * Spread-friendly tool fragment. Returns an empty object when no\n * tools were supplied (Bedrock rejects an empty `tools` array).\n */\n private buildToolConfig(tools: ModelCallOptions[\"tools\"]): Pick<ConverseRequest, \"toolConfig\"> {\n const toolConfig = toBedrockToolConfig(tools);\n\n return toolConfig ? { toolConfig } : {};\n }\n\n /**\n * Translate the neutral `responseSchema` into Converse's native\n * `outputConfig.textFormat` (JSON-schema structured output). Bedrock\n * requires the schema as a stringified JSON document and only\n * accepts an object root. Emitted only when the model is\n * `structuredOutput`-capable and the schema is an object — otherwise\n * the agent's soft system-prompt hint + client-side `validate()`\n * carry shape (same degradation philosophy as the OpenAI adapter).\n */\n private buildOutputConfig(\n responseSchema: Record<string, unknown> | undefined,\n ): Pick<ConverseRequest, \"outputConfig\"> {\n if (!responseSchema || !this.capabilities.structuredOutput) {\n return {};\n }\n\n if (responseSchema.type !== \"object\" || typeof responseSchema.properties !== \"object\") {\n return {};\n }\n\n return {\n outputConfig: {\n textFormat: {\n type: \"json_schema\",\n structure: {\n jsonSchema: { name: \"response\", schema: JSON.stringify(responseSchema) },\n },\n },\n },\n };\n }\n\n /**\n * Concatenate every `text` content block into the single neutral\n * `content` string. `toolUse` and other block types are surfaced\n * separately via `extractToolCalls`.\n */\n private extractText(blocks: ContentBlock[]): string {\n return blocks\n .map((block) => (\"text\" in block && typeof block.text === \"string\" ? block.text : \"\"))\n .join(\"\");\n }\n\n /**\n * Reshape Converse `toolUse` content blocks into the neutral\n * `ModelToolCallRequest[]`. Returns `undefined` when no tools were\n * requested so callers can branch on presence.\n */\n private extractToolCalls(blocks: ContentBlock[]): ModelToolCallRequest[] | undefined {\n const toolCalls: ModelToolCallRequest[] = [];\n\n for (const block of blocks) {\n if (\"toolUse\" in block && block.toolUse) {\n toolCalls.push({\n id: block.toolUse.toolUseId ?? \"\",\n name: block.toolUse.name ?? \"\",\n input: (block.toolUse.input ?? {}) as Record<string, unknown>,\n });\n }\n }\n\n return toolCalls.length > 0 ? toolCalls : undefined;\n }\n\n /**\n * Normalize Converse's `TokenUsage` into the neutral `Usage` shape.\n * Bedrock supplies a pre-summed `totalTokens`; cache-read tokens are\n * surfaced as `cachedTokens` only when non-zero.\n */\n private extractUsage(raw: TokenUsage | undefined): Usage {\n if (!raw) {\n return { input: 0, output: 0, total: 0 };\n }\n\n const input = raw.inputTokens ?? 0;\n const output = raw.outputTokens ?? 0;\n const cached = raw.cacheReadInputTokens;\n\n return {\n input,\n output,\n total: raw.totalTokens ?? input + output,\n ...(cached && cached > 0 ? { cachedTokens: cached } : {}),\n };\n }\n\n /**\n * Wrap a thrown provider error into the typed `AIError` hierarchy\n * and emit the standard error log line before it propagates. Shared\n * by every catch site so the log shape stays identical.\n */\n private logAndWrap(thrown: unknown) {\n const wrapped = wrapBedrockError(thrown);\n\n this.logger.error(LOG_MODULE, \"error\", wrapped.message, {\n code: wrapped.code,\n context: wrapped.context,\n });\n\n return wrapped;\n }\n}\n"],"mappings":"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"}
package/esm/sdk.d.mts ADDED
@@ -0,0 +1,66 @@
1
+ import { BedrockEmbedderConfig, BedrockModelConfig, BedrockSDKConfig } from "./config.type.mjs";
2
+ import { EmbedderContract, ModelContract, SDKAdapterContract } from "@warlock.js/ai";
3
+
4
+ //#region ../../@warlock.js/ai-bedrock/src/sdk.d.ts
5
+ /**
6
+ * AWS Bedrock-backed implementation of `SDKAdapterContract`.
7
+ *
8
+ * **Role.** The package entry point for any Bedrock-hosted model via
9
+ * the Converse API. A single `BedrockSDK` holds one live
10
+ * `BedrockRuntimeClient`, shared by every `ModelContract` and
11
+ * `EmbedderContract` it produces. Construct one SDK per AWS
12
+ * account/region and reuse it everywhere.
13
+ *
14
+ * **Responsibility.**
15
+ * - Owns: a long-lived `BedrockRuntimeClient` (region, credential
16
+ * chain) and its lifetime. Factory for `BedrockModel` /
17
+ * `BedrockEmbedder` instances sharing that client.
18
+ * - Does NOT own: anything per-call — those live in `BedrockModel` /
19
+ * `BedrockEmbedder` and the agent runtime.
20
+ *
21
+ * Modeled as a class (see §4.2 of code-style.md — "long-lived state
22
+ * across many calls"): the AWS client is heavy to construct and
23
+ * designed for reuse; keeping it on `this` aligns with the
24
+ * `new BedrockRuntimeClient(...)` upstream convention.
25
+ *
26
+ * @example
27
+ * const bedrock = new BedrockSDK({ region: "us-east-1" });
28
+ * const model = bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" });
29
+ * const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });
30
+ */
31
+ declare class BedrockSDK implements SDKAdapterContract {
32
+ private readonly client;
33
+ private readonly provider;
34
+ private readonly pricing?;
35
+ constructor(config: BedrockSDKConfig);
36
+ /**
37
+ * Build a `BedrockModel` bound to this SDK's client. Each call
38
+ * returns a fresh instance; all instances share the underlying AWS
39
+ * client so connection pools, credential refresh, and retry config
40
+ * stay unified. The SDK's `provider` label is forwarded.
41
+ *
42
+ * Pricing resolution: per-model `config.pricing` wins; otherwise the
43
+ * SDK-level registry entry keyed by `config.name`; otherwise
44
+ * `undefined` (no cost computed).
45
+ */
46
+ model(config: BedrockModelConfig): ModelContract;
47
+ /**
48
+ * Rough token-count estimate. Uses the character-heuristic
49
+ * (`approximateTokenCount`) from the core package — Bedrock has no
50
+ * offline tokenizer and the per-model tokenizers differ; good enough
51
+ * for budgeting and quota guards, not for billing.
52
+ */
53
+ count(text: string, _model?: string): Promise<number>;
54
+ /**
55
+ * Build a `BedrockEmbedder` (Amazon Titan Text Embeddings) bound to
56
+ * this SDK's client.
57
+ *
58
+ * @example
59
+ * const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });
60
+ * const { vector } = await embedder.embed("Hello world");
61
+ */
62
+ embedder(config: BedrockEmbedderConfig): EmbedderContract;
63
+ }
64
+ //#endregion
65
+ export { BedrockSDK };
66
+ //# sourceMappingURL=sdk.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.d.mts","names":[],"sources":["../../../../../@warlock.js/ai-bedrock/src/sdk.ts"],"mappings":";;;;;;AA0CA;;;;;;;;;;;;;;;;;;;;;;;;cAAa,UAAA,YAAsB,kBAAA;EAAA,iBAChB,MAAA;EAAA,iBACA,QAAA;EAAA,iBACA,OAAA;cAEE,MAAA,EAAQ,gBAAA;EA4CX;;;AAAgD;;;;;;;EA1BzD,KAAA,CAAM,MAAA,EAAQ,kBAAA,GAAqB,aAAA;;;;;;;EAc7B,KAAA,CAAM,IAAA,UAAc,MAAA,YAAkB,OAAA;;;;;;;;;EAY5C,QAAA,CAAS,MAAA,EAAQ,qBAAA,GAAwB,gBAAA;AAAA"}
package/esm/sdk.mjs ADDED
@@ -0,0 +1,82 @@
1
+ import { BedrockEmbedder } from "./embedder.mjs";
2
+ import { BedrockModel } from "./model.mjs";
3
+ import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime";
4
+ import { approximateTokenCount } from "@warlock.js/ai";
5
+
6
+ //#region ../../@warlock.js/ai-bedrock/src/sdk.ts
7
+ /**
8
+ * AWS Bedrock-backed implementation of `SDKAdapterContract`.
9
+ *
10
+ * **Role.** The package entry point for any Bedrock-hosted model via
11
+ * the Converse API. A single `BedrockSDK` holds one live
12
+ * `BedrockRuntimeClient`, shared by every `ModelContract` and
13
+ * `EmbedderContract` it produces. Construct one SDK per AWS
14
+ * account/region and reuse it everywhere.
15
+ *
16
+ * **Responsibility.**
17
+ * - Owns: a long-lived `BedrockRuntimeClient` (region, credential
18
+ * chain) and its lifetime. Factory for `BedrockModel` /
19
+ * `BedrockEmbedder` instances sharing that client.
20
+ * - Does NOT own: anything per-call — those live in `BedrockModel` /
21
+ * `BedrockEmbedder` and the agent runtime.
22
+ *
23
+ * Modeled as a class (see §4.2 of code-style.md — "long-lived state
24
+ * across many calls"): the AWS client is heavy to construct and
25
+ * designed for reuse; keeping it on `this` aligns with the
26
+ * `new BedrockRuntimeClient(...)` upstream convention.
27
+ *
28
+ * @example
29
+ * const bedrock = new BedrockSDK({ region: "us-east-1" });
30
+ * const model = bedrock.model({ name: "anthropic.claude-sonnet-4-5-20250929-v1:0" });
31
+ * const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });
32
+ */
33
+ var BedrockSDK = class {
34
+ constructor(config) {
35
+ const { provider, pricing, ...clientConfig } = config;
36
+ this.client = new BedrockRuntimeClient(clientConfig);
37
+ this.provider = provider ?? "bedrock";
38
+ this.pricing = pricing;
39
+ }
40
+ /**
41
+ * Build a `BedrockModel` bound to this SDK's client. Each call
42
+ * returns a fresh instance; all instances share the underlying AWS
43
+ * client so connection pools, credential refresh, and retry config
44
+ * stay unified. The SDK's `provider` label is forwarded.
45
+ *
46
+ * Pricing resolution: per-model `config.pricing` wins; otherwise the
47
+ * SDK-level registry entry keyed by `config.name`; otherwise
48
+ * `undefined` (no cost computed).
49
+ */
50
+ model(config) {
51
+ const resolvedPricing = config.pricing ?? this.pricing?.[config.name];
52
+ const resolvedConfig = resolvedPricing === config.pricing ? config : {
53
+ ...config,
54
+ pricing: resolvedPricing
55
+ };
56
+ return new BedrockModel(this.client, resolvedConfig, this.provider);
57
+ }
58
+ /**
59
+ * Rough token-count estimate. Uses the character-heuristic
60
+ * (`approximateTokenCount`) from the core package — Bedrock has no
61
+ * offline tokenizer and the per-model tokenizers differ; good enough
62
+ * for budgeting and quota guards, not for billing.
63
+ */
64
+ async count(text, _model) {
65
+ return approximateTokenCount(text);
66
+ }
67
+ /**
68
+ * Build a `BedrockEmbedder` (Amazon Titan Text Embeddings) bound to
69
+ * this SDK's client.
70
+ *
71
+ * @example
72
+ * const embedder = bedrock.embedder({ name: "amazon.titan-embed-text-v2:0" });
73
+ * const { vector } = await embedder.embed("Hello world");
74
+ */
75
+ embedder(config) {
76
+ return new BedrockEmbedder(this.client, config, this.provider);
77
+ }
78
+ };
79
+
80
+ //#endregion
81
+ export { BedrockSDK };
82
+ //# sourceMappingURL=sdk.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.mjs","names":[],"sources":["../../../../../@warlock.js/ai-bedrock/src/sdk.ts"],"sourcesContent":["import { BedrockRuntimeClient } from \"@aws-sdk/client-bedrock-runtime\";\nimport type {\n EmbedderContract,\n ModelContract,\n ModelPricing,\n SDKAdapterContract,\n} from \"@warlock.js/ai\";\nimport { approximateTokenCount } from \"@warlock.js/ai\";\nimport type {\n BedrockEmbedderConfig,\n BedrockModelConfig,\n BedrockSDKConfig,\n} from \"./config.type\";\nimport { BedrockEmbedder } from \"./embedder\";\nimport { BedrockModel } from \"./model\";\n\n/**\n * AWS Bedrock-backed implementation of `SDKAdapterContract`.\n *\n * **Role.** The package entry point for any Bedrock-hosted model via\n * the Converse API. A single `BedrockSDK` holds one live\n * `BedrockRuntimeClient`, shared by every `ModelContract` and\n * `EmbedderContract` it produces. Construct one SDK per AWS\n * account/region and reuse it everywhere.\n *\n * **Responsibility.**\n * - Owns: a long-lived `BedrockRuntimeClient` (region, credential\n * chain) and its lifetime. Factory for `BedrockModel` /\n * `BedrockEmbedder` instances sharing that client.\n * - Does NOT own: anything per-call — those live in `BedrockModel` /\n * `BedrockEmbedder` and the agent runtime.\n *\n * Modeled as a class (see §4.2 of code-style.md — \"long-lived state\n * across many calls\"): the AWS client is heavy to construct and\n * designed for reuse; keeping it on `this` aligns with the\n * `new BedrockRuntimeClient(...)` upstream convention.\n *\n * @example\n * const bedrock = new BedrockSDK({ region: \"us-east-1\" });\n * const model = bedrock.model({ name: \"anthropic.claude-sonnet-4-5-20250929-v1:0\" });\n * const embedder = bedrock.embedder({ name: \"amazon.titan-embed-text-v2:0\" });\n */\nexport class BedrockSDK implements SDKAdapterContract {\n private readonly client: BedrockRuntimeClient;\n private readonly provider: string;\n private readonly pricing?: Record<string, ModelPricing>;\n\n public constructor(config: BedrockSDKConfig) {\n const { provider, pricing, ...clientConfig } = config;\n\n this.client = new BedrockRuntimeClient(clientConfig);\n this.provider = provider ?? \"bedrock\";\n this.pricing = pricing;\n }\n\n /**\n * Build a `BedrockModel` bound to this SDK's client. Each call\n * returns a fresh instance; all instances share the underlying AWS\n * client so connection pools, credential refresh, and retry config\n * stay unified. The SDK's `provider` label is forwarded.\n *\n * Pricing resolution: per-model `config.pricing` wins; otherwise the\n * SDK-level registry entry keyed by `config.name`; otherwise\n * `undefined` (no cost computed).\n */\n public model(config: BedrockModelConfig): ModelContract {\n const resolvedPricing = config.pricing ?? this.pricing?.[config.name];\n const resolvedConfig: BedrockModelConfig =\n resolvedPricing === config.pricing ? config : { ...config, pricing: resolvedPricing };\n\n return new BedrockModel(this.client, resolvedConfig, this.provider);\n }\n\n /**\n * Rough token-count estimate. Uses the character-heuristic\n * (`approximateTokenCount`) from the core package — Bedrock has no\n * offline tokenizer and the per-model tokenizers differ; good enough\n * for budgeting and quota guards, not for billing.\n */\n public async count(text: string, _model?: string): Promise<number> {\n return approximateTokenCount(text);\n }\n\n /**\n * Build a `BedrockEmbedder` (Amazon Titan Text Embeddings) bound to\n * this SDK's client.\n *\n * @example\n * const embedder = bedrock.embedder({ name: \"amazon.titan-embed-text-v2:0\" });\n * const { vector } = await embedder.embed(\"Hello world\");\n */\n public embedder(config: BedrockEmbedderConfig): EmbedderContract {\n return new BedrockEmbedder(this.client, config, this.provider);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,aAAb,MAAsD;CAKpD,AAAO,YAAY,QAA0B;EAC3C,MAAM,EAAE,UAAU,SAAS,GAAG,iBAAiB;EAE/C,KAAK,SAAS,IAAI,qBAAqB,YAAY;EACnD,KAAK,WAAW,YAAY;EAC5B,KAAK,UAAU;CACjB;;;;;;;;;;;CAYA,AAAO,MAAM,QAA2C;EACtD,MAAM,kBAAkB,OAAO,WAAW,KAAK,UAAU,OAAO;EAChE,MAAM,iBACJ,oBAAoB,OAAO,UAAU,SAAS;GAAE,GAAG;GAAQ,SAAS;EAAgB;EAEtF,OAAO,IAAI,aAAa,KAAK,QAAQ,gBAAgB,KAAK,QAAQ;CACpE;;;;;;;CAQA,MAAa,MAAM,MAAc,QAAkC;EACjE,OAAO,sBAAsB,IAAI;CACnC;;;;;;;;;CAUA,AAAO,SAAS,QAAiD;EAC/D,OAAO,IAAI,gBAAgB,KAAK,QAAQ,QAAQ,KAAK,QAAQ;CAC/D;AACF"}
@@ -0,0 +1,6 @@
1
+ import { mapStopReason } from "./map-stop-reason.mjs";
2
+ import { toBedrockMessages } from "./to-bedrock-messages.mjs";
3
+ import { toBedrockToolConfig } from "./to-bedrock-tools.mjs";
4
+ import { wrapBedrockError } from "./wrap-bedrock-error.mjs";
5
+
6
+ export { };
@@ -0,0 +1,31 @@
1
+ //#region ../../@warlock.js/ai-bedrock/src/utils/map-stop-reason.ts
2
+ const stopReasonMap = {
3
+ end_turn: "stop",
4
+ stop_sequence: "stop",
5
+ max_tokens: "length",
6
+ tool_use: "tool_calls"
7
+ };
8
+ /**
9
+ * Map Bedrock Converse's `stopReason` to the normalized `FinishReason`
10
+ * union.
11
+ *
12
+ * `end_turn` / `stop_sequence` are natural stops. `max_tokens` maps to
13
+ * `length`. `tool_use` maps to `tool_calls`. Everything else —
14
+ * `content_filtered`, `guardrail_intervened`, `malformed_tool_use`,
15
+ * `malformed_model_output`, `model_context_window_exceeded`, `null`,
16
+ * or any future value — falls through to `"error"`: none produced a
17
+ * clean terminal answer, so the agent must not treat them as success.
18
+ *
19
+ * @example
20
+ * mapStopReason("end_turn"); // "stop"
21
+ * mapStopReason("tool_use"); // "tool_calls"
22
+ * mapStopReason("guardrail_intervened"); // "error"
23
+ * mapStopReason(undefined); // "error"
24
+ */
25
+ function mapStopReason(raw) {
26
+ return stopReasonMap[raw ?? ""] ?? "error";
27
+ }
28
+
29
+ //#endregion
30
+ export { mapStopReason };
31
+ //# sourceMappingURL=map-stop-reason.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-stop-reason.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-bedrock/src/utils/map-stop-reason.ts"],"sourcesContent":["import type { FinishReason } from \"@warlock.js/ai\";\n\nconst stopReasonMap: Record<string, FinishReason> = {\n end_turn: \"stop\",\n stop_sequence: \"stop\",\n max_tokens: \"length\",\n tool_use: \"tool_calls\",\n};\n\n/**\n * Map Bedrock Converse's `stopReason` to the normalized `FinishReason`\n * union.\n *\n * `end_turn` / `stop_sequence` are natural stops. `max_tokens` maps to\n * `length`. `tool_use` maps to `tool_calls`. Everything else —\n * `content_filtered`, `guardrail_intervened`, `malformed_tool_use`,\n * `malformed_model_output`, `model_context_window_exceeded`, `null`,\n * or any future value — falls through to `\"error\"`: none produced a\n * clean terminal answer, so the agent must not treat them as success.\n *\n * @example\n * mapStopReason(\"end_turn\"); // \"stop\"\n * mapStopReason(\"tool_use\"); // \"tool_calls\"\n * mapStopReason(\"guardrail_intervened\"); // \"error\"\n * mapStopReason(undefined); // \"error\"\n */\nexport function mapStopReason(raw: string | null | undefined): FinishReason {\n return stopReasonMap[raw ?? \"\"] ?? \"error\";\n}\n"],"mappings":";AAEA,MAAM,gBAA8C;CAClD,UAAU;CACV,eAAe;CACf,YAAY;CACZ,UAAU;AACZ;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cAAc,KAA8C;CAC1E,OAAO,cAAc,OAAO,OAAO;AACrC"}
@@ -0,0 +1,111 @@
1
+ import { InvalidRequestError } from "@warlock.js/ai";
2
+
3
+ //#region ../../@warlock.js/ai-bedrock/src/utils/to-bedrock-messages.ts
4
+ const MEDIA_TYPE_TO_FORMAT = {
5
+ "image/jpeg": "jpeg",
6
+ "image/png": "png",
7
+ "image/gif": "gif",
8
+ "image/webp": "webp"
9
+ };
10
+ /**
11
+ * Convert vendor-neutral `Message[]` into Bedrock Converse's request
12
+ * shape.
13
+ *
14
+ * Converse differs from the OpenAI Chat protocol in three ways this
15
+ * function absorbs:
16
+ *
17
+ * 1. **No `system` role.** System messages become a separate
18
+ * `SystemContentBlock[]` (one `{ text }` block each).
19
+ * 2. **Tool results are `user` turns.** A neutral `tool` message
20
+ * becomes a `user` message carrying a single `toolResult` block.
21
+ * 3. **Tool calls are `toolUse` content blocks.** An assistant message
22
+ * with `toolCalls` becomes an `assistant` message: an optional
23
+ * leading `text` block followed by one `toolUse` block per call.
24
+ *
25
+ * @example
26
+ * const { system, messages } = toBedrockMessages([
27
+ * { role: "system", content: "Be concise." },
28
+ * { role: "user", content: "Hi" },
29
+ * ]);
30
+ */
31
+ function toBedrockMessages(messages) {
32
+ const system = [];
33
+ const mapped = [];
34
+ for (const message of messages) {
35
+ if (message.role === "system") {
36
+ system.push({ text: stringifyContent(message.content) });
37
+ continue;
38
+ }
39
+ if (message.role === "tool") {
40
+ mapped.push({
41
+ role: "user",
42
+ content: [{ toolResult: {
43
+ toolUseId: message.toolCallId ?? "",
44
+ content: [{ text: stringifyContent(message.content) }]
45
+ } }]
46
+ });
47
+ continue;
48
+ }
49
+ if (message.role === "assistant" && message.toolCalls && message.toolCalls.length > 0) {
50
+ const blocks = [];
51
+ const text = stringifyContent(message.content);
52
+ if (text) blocks.push({ text });
53
+ for (const toolCall of message.toolCalls) blocks.push({ toolUse: {
54
+ toolUseId: toolCall.id,
55
+ name: toolCall.name,
56
+ input: toolCall.input ?? {}
57
+ } });
58
+ mapped.push({
59
+ role: "assistant",
60
+ content: blocks
61
+ });
62
+ continue;
63
+ }
64
+ if (message.role === "user" && Array.isArray(message.content)) {
65
+ mapped.push({
66
+ role: "user",
67
+ content: message.content.map(toBedrockContentBlock)
68
+ });
69
+ continue;
70
+ }
71
+ mapped.push({
72
+ role: message.role === "assistant" ? "assistant" : "user",
73
+ content: [{ text: stringifyContent(message.content) }]
74
+ });
75
+ }
76
+ return {
77
+ system: system.length > 0 ? system : void 0,
78
+ messages: mapped
79
+ };
80
+ }
81
+ /**
82
+ * Multipart content is only meaningful on user messages — for any other
83
+ * role collapse a `ContentPart[]` to its concatenated text. Plain
84
+ * strings pass through unchanged.
85
+ */
86
+ function stringifyContent(content) {
87
+ if (typeof content === "string") return content;
88
+ return content.filter((part) => part.type === "text").map((part) => part.text).join("");
89
+ }
90
+ /**
91
+ * Map a resolved `ContentPart` to a Bedrock `ContentBlock`. Bedrock's
92
+ * `ImageSource` only accepts raw bytes or an S3 location — there is no
93
+ * remote-URL source. A neutral `{ url }` image therefore cannot be
94
+ * sent and surfaces a typed `InvalidRequestError` upfront rather than
95
+ * a downstream Bedrock validation fault. The agent has already
96
+ * resolved attachments, so this never fetches or reads anything.
97
+ */
98
+ function toBedrockContentBlock(part) {
99
+ if (part.type === "text") return { text: part.text };
100
+ if ("url" in part.source) throw new InvalidRequestError("Bedrock Converse does not support remote-URL image sources; supply base64 image bytes instead.");
101
+ const format = MEDIA_TYPE_TO_FORMAT[part.source.mediaType];
102
+ if (!format) throw new InvalidRequestError(`Unsupported image media type for Bedrock: "${part.source.mediaType}" (expected image/jpeg, image/png, image/gif, or image/webp).`);
103
+ return { image: {
104
+ format,
105
+ source: { bytes: Buffer.from(part.source.base64, "base64") }
106
+ } };
107
+ }
108
+
109
+ //#endregion
110
+ export { toBedrockMessages };
111
+ //# sourceMappingURL=to-bedrock-messages.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"to-bedrock-messages.mjs","names":[],"sources":["../../../../../../@warlock.js/ai-bedrock/src/utils/to-bedrock-messages.ts"],"sourcesContent":["import { InvalidRequestError, type ContentPart, type Message } from \"@warlock.js/ai\";\nimport type {\n ContentBlock,\n ImageFormat,\n Message as BedrockMessage,\n SystemContentBlock,\n} from \"@aws-sdk/client-bedrock-runtime\";\n\n/**\n * Result of splitting a vendor-neutral `Message[]` for the Bedrock\n * Converse API: system prompts hoist to a separate `SystemContentBlock[]`\n * (Converse has no `\"system\"` role inside `messages`), and the\n * remaining turns map to Bedrock `Message[]`.\n */\nexport type BedrockMessages = {\n system: SystemContentBlock[] | undefined;\n messages: BedrockMessage[];\n};\n\nconst MEDIA_TYPE_TO_FORMAT: Record<string, ImageFormat> = {\n \"image/jpeg\": \"jpeg\",\n \"image/png\": \"png\",\n \"image/gif\": \"gif\",\n \"image/webp\": \"webp\",\n};\n\n/**\n * Convert vendor-neutral `Message[]` into Bedrock Converse's request\n * shape.\n *\n * Converse differs from the OpenAI Chat protocol in three ways this\n * function absorbs:\n *\n * 1. **No `system` role.** System messages become a separate\n * `SystemContentBlock[]` (one `{ text }` block each).\n * 2. **Tool results are `user` turns.** A neutral `tool` message\n * becomes a `user` message carrying a single `toolResult` block.\n * 3. **Tool calls are `toolUse` content blocks.** An assistant message\n * with `toolCalls` becomes an `assistant` message: an optional\n * leading `text` block followed by one `toolUse` block per call.\n *\n * @example\n * const { system, messages } = toBedrockMessages([\n * { role: \"system\", content: \"Be concise.\" },\n * { role: \"user\", content: \"Hi\" },\n * ]);\n */\nexport function toBedrockMessages(messages: Message[]): BedrockMessages {\n const system: SystemContentBlock[] = [];\n const mapped: BedrockMessage[] = [];\n\n for (const message of messages) {\n if (message.role === \"system\") {\n system.push({ text: stringifyContent(message.content) });\n\n continue;\n }\n\n if (message.role === \"tool\") {\n mapped.push({\n role: \"user\",\n content: [\n {\n toolResult: {\n toolUseId: message.toolCallId ?? \"\",\n content: [{ text: stringifyContent(message.content) }],\n },\n },\n ],\n });\n\n continue;\n }\n\n if (message.role === \"assistant\" && message.toolCalls && message.toolCalls.length > 0) {\n const blocks: ContentBlock[] = [];\n const text = stringifyContent(message.content);\n\n if (text) {\n blocks.push({ text });\n }\n\n for (const toolCall of message.toolCalls) {\n blocks.push({\n toolUse: {\n toolUseId: toolCall.id,\n name: toolCall.name,\n input: toolCall.input ?? {},\n },\n } as ContentBlock);\n }\n\n mapped.push({ role: \"assistant\", content: blocks });\n\n continue;\n }\n\n if (message.role === \"user\" && Array.isArray(message.content)) {\n mapped.push({\n role: \"user\",\n content: message.content.map(toBedrockContentBlock),\n });\n\n continue;\n }\n\n mapped.push({\n role: message.role === \"assistant\" ? \"assistant\" : \"user\",\n content: [{ text: stringifyContent(message.content) }],\n });\n }\n\n return {\n system: system.length > 0 ? system : undefined,\n messages: mapped,\n };\n}\n\n/**\n * Multipart content is only meaningful on user messages — for any other\n * role collapse a `ContentPart[]` to its concatenated text. Plain\n * strings pass through unchanged.\n */\nfunction stringifyContent(content: string | ContentPart[]): string {\n if (typeof content === \"string\") {\n return content;\n }\n\n return content\n .filter((part): part is { type: \"text\"; text: string } => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\");\n}\n\n/**\n * Map a resolved `ContentPart` to a Bedrock `ContentBlock`. Bedrock's\n * `ImageSource` only accepts raw bytes or an S3 location — there is no\n * remote-URL source. A neutral `{ url }` image therefore cannot be\n * sent and surfaces a typed `InvalidRequestError` upfront rather than\n * a downstream Bedrock validation fault. The agent has already\n * resolved attachments, so this never fetches or reads anything.\n */\nfunction toBedrockContentBlock(part: ContentPart): ContentBlock {\n if (part.type === \"text\") {\n return { text: part.text };\n }\n\n if (\"url\" in part.source) {\n throw new InvalidRequestError(\n \"Bedrock Converse does not support remote-URL image sources; supply base64 image bytes instead.\",\n );\n }\n\n const format = MEDIA_TYPE_TO_FORMAT[part.source.mediaType];\n\n if (!format) {\n throw new InvalidRequestError(\n `Unsupported image media type for Bedrock: \"${part.source.mediaType}\" (expected image/jpeg, image/png, image/gif, or image/webp).`,\n );\n }\n\n return {\n image: {\n format,\n source: { bytes: Buffer.from(part.source.base64, \"base64\") },\n },\n };\n}\n"],"mappings":";;;AAmBA,MAAM,uBAAoD;CACxD,cAAc;CACd,aAAa;CACb,aAAa;CACb,cAAc;AAChB;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,kBAAkB,UAAsC;CACtE,MAAM,SAA+B,CAAC;CACtC,MAAM,SAA2B,CAAC;CAElC,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,QAAQ,SAAS,UAAU;GAC7B,OAAO,KAAK,EAAE,MAAM,iBAAiB,QAAQ,OAAO,EAAE,CAAC;GAEvD;EACF;EAEA,IAAI,QAAQ,SAAS,QAAQ;GAC3B,OAAO,KAAK;IACV,MAAM;IACN,SAAS,CACP,EACE,YAAY;KACV,WAAW,QAAQ,cAAc;KACjC,SAAS,CAAC,EAAE,MAAM,iBAAiB,QAAQ,OAAO,EAAE,CAAC;IACvD,EACF,CACF;GACF,CAAC;GAED;EACF;EAEA,IAAI,QAAQ,SAAS,eAAe,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;GACrF,MAAM,SAAyB,CAAC;GAChC,MAAM,OAAO,iBAAiB,QAAQ,OAAO;GAE7C,IAAI,MACF,OAAO,KAAK,EAAE,KAAK,CAAC;GAGtB,KAAK,MAAM,YAAY,QAAQ,WAC7B,OAAO,KAAK,EACV,SAAS;IACP,WAAW,SAAS;IACpB,MAAM,SAAS;IACf,OAAO,SAAS,SAAS,CAAC;GAC5B,EACF,CAAiB;GAGnB,OAAO,KAAK;IAAE,MAAM;IAAa,SAAS;GAAO,CAAC;GAElD;EACF;EAEA,IAAI,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,GAAG;GAC7D,OAAO,KAAK;IACV,MAAM;IACN,SAAS,QAAQ,QAAQ,IAAI,qBAAqB;GACpD,CAAC;GAED;EACF;EAEA,OAAO,KAAK;GACV,MAAM,QAAQ,SAAS,cAAc,cAAc;GACnD,SAAS,CAAC,EAAE,MAAM,iBAAiB,QAAQ,OAAO,EAAE,CAAC;EACvD,CAAC;CACH;CAEA,OAAO;EACL,QAAQ,OAAO,SAAS,IAAI,SAAS;EACrC,UAAU;CACZ;AACF;;;;;;AAOA,SAAS,iBAAiB,SAAyC;CACjE,IAAI,OAAO,YAAY,UACrB,OAAO;CAGT,OAAO,QACJ,QAAQ,SAAiD,KAAK,SAAS,MAAM,EAC7E,KAAK,SAAS,KAAK,IAAI,EACvB,KAAK,EAAE;AACZ;;;;;;;;;AAUA,SAAS,sBAAsB,MAAiC;CAC9D,IAAI,KAAK,SAAS,QAChB,OAAO,EAAE,MAAM,KAAK,KAAK;CAG3B,IAAI,SAAS,KAAK,QAChB,MAAM,IAAI,oBACR,gGACF;CAGF,MAAM,SAAS,qBAAqB,KAAK,OAAO;CAEhD,IAAI,CAAC,QACH,MAAM,IAAI,oBACR,8CAA8C,KAAK,OAAO,UAAU,8DACtE;CAGF,OAAO,EACL,OAAO;EACL;EACA,QAAQ,EAAE,OAAO,OAAO,KAAK,KAAK,OAAO,QAAQ,QAAQ,EAAE;CAC7D,EACF;AACF"}