@ai-sdk/amazon-bedrock 4.0.23 → 4.0.25

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/anthropic/index.js +1 -1
  3. package/dist/anthropic/index.mjs +1 -1
  4. package/dist/index.js +33 -8
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +33 -8
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +4 -3
  9. package/src/__fixtures__/bedrock-json-only-text-first.1.chunks.txt +7 -0
  10. package/src/__fixtures__/bedrock-json-other-tool.1.chunks.txt +6 -0
  11. package/src/__fixtures__/bedrock-json-other-tool.1.json +24 -0
  12. package/src/__fixtures__/bedrock-json-tool-text-then-weather-then-json.1.chunks.txt +12 -0
  13. package/src/__fixtures__/bedrock-json-tool-with-answer.1.json +29 -0
  14. package/src/__fixtures__/bedrock-json-tool.1.chunks.txt +4 -0
  15. package/src/__fixtures__/bedrock-json-tool.1.json +35 -0
  16. package/src/__fixtures__/bedrock-json-tool.2.chunks.txt +6 -0
  17. package/src/__fixtures__/bedrock-json-tool.2.json +28 -0
  18. package/src/__fixtures__/bedrock-json-tool.3.chunks.txt +7 -0
  19. package/src/__fixtures__/bedrock-json-tool.3.json +36 -0
  20. package/src/__fixtures__/bedrock-json-with-tool.1.chunks.txt +9 -0
  21. package/src/__fixtures__/bedrock-json-with-tool.1.json +41 -0
  22. package/src/__fixtures__/bedrock-json-with-tools.1.chunks.txt +12 -0
  23. package/src/__fixtures__/bedrock-json-with-tools.1.json +50 -0
  24. package/src/__fixtures__/bedrock-tool-call.1.chunks.txt +6 -0
  25. package/src/__fixtures__/bedrock-tool-call.1.json +24 -0
  26. package/src/__fixtures__/bedrock-tool-no-args.chunks.txt +8 -0
  27. package/src/__fixtures__/bedrock-tool-no-args.json +25 -0
  28. package/src/anthropic/bedrock-anthropic-fetch.test.ts +344 -0
  29. package/src/anthropic/bedrock-anthropic-fetch.ts +62 -0
  30. package/src/anthropic/bedrock-anthropic-options.ts +28 -0
  31. package/src/anthropic/bedrock-anthropic-provider.test.ts +456 -0
  32. package/src/anthropic/bedrock-anthropic-provider.ts +357 -0
  33. package/src/anthropic/index.ts +9 -0
  34. package/src/bedrock-api-types.ts +195 -0
  35. package/src/bedrock-chat-language-model.test.ts +4569 -0
  36. package/src/bedrock-chat-language-model.ts +1019 -0
  37. package/src/bedrock-chat-options.ts +114 -0
  38. package/src/bedrock-embedding-model.test.ts +148 -0
  39. package/src/bedrock-embedding-model.ts +104 -0
  40. package/src/bedrock-embedding-options.ts +24 -0
  41. package/src/bedrock-error.ts +6 -0
  42. package/src/bedrock-event-stream-decoder.ts +59 -0
  43. package/src/bedrock-event-stream-response-handler.test.ts +233 -0
  44. package/src/bedrock-event-stream-response-handler.ts +57 -0
  45. package/src/bedrock-image-model.test.ts +866 -0
  46. package/src/bedrock-image-model.ts +297 -0
  47. package/src/bedrock-image-settings.ts +6 -0
  48. package/src/bedrock-prepare-tools.ts +190 -0
  49. package/src/bedrock-provider.test.ts +457 -0
  50. package/src/bedrock-provider.ts +351 -0
  51. package/src/bedrock-sigv4-fetch.test.ts +675 -0
  52. package/src/bedrock-sigv4-fetch.ts +138 -0
  53. package/src/convert-bedrock-usage.test.ts +207 -0
  54. package/src/convert-bedrock-usage.ts +50 -0
  55. package/src/convert-to-bedrock-chat-messages.test.ts +1175 -0
  56. package/src/convert-to-bedrock-chat-messages.ts +452 -0
  57. package/src/index.ts +10 -0
  58. package/src/inject-fetch-headers.test.ts +135 -0
  59. package/src/inject-fetch-headers.ts +32 -0
  60. package/src/map-bedrock-finish-reason.ts +22 -0
  61. package/src/normalize-tool-call-id.test.ts +72 -0
  62. package/src/normalize-tool-call-id.ts +36 -0
  63. package/src/reranking/__fixtures__/bedrock-reranking.1.json +12 -0
  64. package/src/reranking/bedrock-reranking-api.ts +44 -0
  65. package/src/reranking/bedrock-reranking-model.test.ts +299 -0
  66. package/src/reranking/bedrock-reranking-model.ts +115 -0
  67. package/src/reranking/bedrock-reranking-options.ts +36 -0
  68. package/src/version.ts +6 -0
@@ -0,0 +1,297 @@
1
+ import {
2
+ ImageModelV3,
3
+ ImageModelV3File,
4
+ SharedV3Warning,
5
+ } from '@ai-sdk/provider';
6
+ import {
7
+ FetchFunction,
8
+ Resolvable,
9
+ combineHeaders,
10
+ convertUint8ArrayToBase64,
11
+ createJsonErrorResponseHandler,
12
+ createJsonResponseHandler,
13
+ postJsonToApi,
14
+ resolve,
15
+ } from '@ai-sdk/provider-utils';
16
+ import {
17
+ BedrockImageModelId,
18
+ modelMaxImagesPerCall,
19
+ } from './bedrock-image-settings';
20
+ import { BedrockErrorSchema } from './bedrock-error';
21
+ import { z } from 'zod/v4';
22
+
23
+ type BedrockImageModelConfig = {
24
+ baseUrl: () => string;
25
+ headers: Resolvable<Record<string, string | undefined>>;
26
+ fetch?: FetchFunction;
27
+ _internal?: {
28
+ currentDate?: () => Date;
29
+ };
30
+ };
31
+
32
+ export class BedrockImageModel implements ImageModelV3 {
33
+ readonly specificationVersion = 'v3';
34
+ readonly provider = 'amazon-bedrock';
35
+
36
+ get maxImagesPerCall(): number {
37
+ return modelMaxImagesPerCall[this.modelId] ?? 1;
38
+ }
39
+
40
+ private getUrl(modelId: string): string {
41
+ const encodedModelId = encodeURIComponent(modelId);
42
+ return `${this.config.baseUrl()}/model/${encodedModelId}/invoke`;
43
+ }
44
+
45
+ constructor(
46
+ readonly modelId: BedrockImageModelId,
47
+ private readonly config: BedrockImageModelConfig,
48
+ ) {}
49
+
50
+ async doGenerate({
51
+ prompt,
52
+ n,
53
+ size,
54
+ aspectRatio,
55
+ seed,
56
+ providerOptions,
57
+ headers,
58
+ abortSignal,
59
+ files,
60
+ mask,
61
+ }: Parameters<ImageModelV3['doGenerate']>[0]): Promise<
62
+ Awaited<ReturnType<ImageModelV3['doGenerate']>>
63
+ > {
64
+ const warnings: Array<SharedV3Warning> = [];
65
+ const [width, height] = size ? size.split('x').map(Number) : [];
66
+
67
+ const hasFiles = files != null && files.length > 0;
68
+
69
+ // Build image generation config (common to most modes)
70
+ const imageGenerationConfig = {
71
+ ...(width ? { width } : {}),
72
+ ...(height ? { height } : {}),
73
+ ...(seed ? { seed } : {}),
74
+ ...(n ? { numberOfImages: n } : {}),
75
+ ...(providerOptions?.bedrock?.quality
76
+ ? { quality: providerOptions.bedrock.quality }
77
+ : {}),
78
+ ...(providerOptions?.bedrock?.cfgScale
79
+ ? { cfgScale: providerOptions.bedrock.cfgScale }
80
+ : {}),
81
+ };
82
+
83
+ let args: Record<string, unknown>;
84
+
85
+ if (hasFiles) {
86
+ // Check if mask is actually provided (has valid data, not just an empty object)
87
+ const hasMask = mask?.type != null;
88
+ const hasMaskPrompt = providerOptions?.bedrock?.maskPrompt != null;
89
+
90
+ // Determine task type from provider options, or infer from mask presence
91
+ const taskType =
92
+ providerOptions?.bedrock?.taskType ??
93
+ (hasMask || hasMaskPrompt ? 'INPAINTING' : 'IMAGE_VARIATION');
94
+
95
+ const sourceImageBase64 = getBase64Data(files[0]);
96
+
97
+ switch (taskType) {
98
+ case 'INPAINTING': {
99
+ const inPaintingParams: Record<string, unknown> = {
100
+ image: sourceImageBase64,
101
+ ...(prompt ? { text: prompt } : {}),
102
+ ...(providerOptions?.bedrock?.negativeText
103
+ ? { negativeText: providerOptions.bedrock.negativeText }
104
+ : {}),
105
+ };
106
+
107
+ // Handle mask - can be either a maskImage or maskPrompt
108
+ if (hasMask) {
109
+ inPaintingParams.maskImage = getBase64Data(mask);
110
+ } else if (hasMaskPrompt) {
111
+ inPaintingParams.maskPrompt = providerOptions.bedrock.maskPrompt;
112
+ }
113
+
114
+ args = {
115
+ taskType: 'INPAINTING',
116
+ inPaintingParams,
117
+ imageGenerationConfig,
118
+ };
119
+ break;
120
+ }
121
+
122
+ case 'OUTPAINTING': {
123
+ const outPaintingParams: Record<string, unknown> = {
124
+ image: sourceImageBase64,
125
+ ...(prompt ? { text: prompt } : {}),
126
+ ...(providerOptions?.bedrock?.negativeText
127
+ ? { negativeText: providerOptions.bedrock.negativeText }
128
+ : {}),
129
+ ...(providerOptions?.bedrock?.outPaintingMode
130
+ ? { outPaintingMode: providerOptions.bedrock.outPaintingMode }
131
+ : {}),
132
+ };
133
+
134
+ // Outpainting requires a maskImage (white pixels = area to change)
135
+ if (hasMask) {
136
+ outPaintingParams.maskImage = getBase64Data(mask);
137
+ } else if (hasMaskPrompt) {
138
+ outPaintingParams.maskPrompt = providerOptions.bedrock.maskPrompt;
139
+ }
140
+
141
+ args = {
142
+ taskType: 'OUTPAINTING',
143
+ outPaintingParams,
144
+ imageGenerationConfig,
145
+ };
146
+ break;
147
+ }
148
+
149
+ case 'BACKGROUND_REMOVAL': {
150
+ // Background removal only needs the image, no other params
151
+ args = {
152
+ taskType: 'BACKGROUND_REMOVAL',
153
+ backgroundRemovalParams: {
154
+ image: sourceImageBase64,
155
+ },
156
+ };
157
+ break;
158
+ }
159
+
160
+ case 'IMAGE_VARIATION': {
161
+ // Image variation can use multiple images
162
+ const images = files.map(file => getBase64Data(file));
163
+
164
+ const imageVariationParams: Record<string, unknown> = {
165
+ images,
166
+ ...(prompt ? { text: prompt } : {}),
167
+ ...(providerOptions?.bedrock?.negativeText
168
+ ? { negativeText: providerOptions.bedrock.negativeText }
169
+ : {}),
170
+ ...(providerOptions?.bedrock?.similarityStrength != null
171
+ ? {
172
+ similarityStrength:
173
+ providerOptions.bedrock.similarityStrength,
174
+ }
175
+ : {}),
176
+ };
177
+
178
+ args = {
179
+ taskType: 'IMAGE_VARIATION',
180
+ imageVariationParams,
181
+ imageGenerationConfig,
182
+ };
183
+ break;
184
+ }
185
+
186
+ default:
187
+ throw new Error(`Unsupported task type: ${taskType}`);
188
+ }
189
+ } else {
190
+ // Standard image generation mode
191
+ args = {
192
+ taskType: 'TEXT_IMAGE',
193
+ textToImageParams: {
194
+ text: prompt,
195
+ ...(providerOptions?.bedrock?.negativeText
196
+ ? {
197
+ negativeText: providerOptions.bedrock.negativeText,
198
+ }
199
+ : {}),
200
+ ...(providerOptions?.bedrock?.style
201
+ ? {
202
+ style: providerOptions.bedrock.style,
203
+ }
204
+ : {}),
205
+ },
206
+ imageGenerationConfig,
207
+ };
208
+ }
209
+
210
+ if (aspectRatio != undefined) {
211
+ warnings.push({
212
+ type: 'unsupported',
213
+ feature: 'aspectRatio',
214
+ details:
215
+ 'This model does not support aspect ratio. Use `size` instead.',
216
+ });
217
+ }
218
+
219
+ const currentDate = this.config._internal?.currentDate?.() ?? new Date();
220
+ const { value: response, responseHeaders } = await postJsonToApi({
221
+ url: this.getUrl(this.modelId),
222
+ headers: await resolve(
223
+ combineHeaders(await resolve(this.config.headers), headers),
224
+ ),
225
+ body: args,
226
+ failedResponseHandler: createJsonErrorResponseHandler({
227
+ errorSchema: BedrockErrorSchema,
228
+ errorToMessage: error => `${error.type}: ${error.message}`,
229
+ }),
230
+ successfulResponseHandler: createJsonResponseHandler(
231
+ bedrockImageResponseSchema,
232
+ ),
233
+ abortSignal,
234
+ fetch: this.config.fetch,
235
+ });
236
+
237
+ // Handle moderated/blocked requests
238
+ if (response.status === 'Request Moderated') {
239
+ const moderationReasons = response.details?.['Moderation Reasons'];
240
+ const reasons = Array.isArray(moderationReasons)
241
+ ? moderationReasons
242
+ : ['Unknown'];
243
+ throw new Error(
244
+ `Amazon Bedrock request was moderated: ${reasons.join(', ')}`,
245
+ );
246
+ }
247
+
248
+ // Check if images are present
249
+ if (!response.images || response.images.length === 0) {
250
+ throw new Error(
251
+ 'Amazon Bedrock returned no images. ' +
252
+ (response.status ? `Status: ${response.status}` : ''),
253
+ );
254
+ }
255
+
256
+ return {
257
+ images: response.images,
258
+ warnings,
259
+ response: {
260
+ timestamp: currentDate,
261
+ modelId: this.modelId,
262
+ headers: responseHeaders,
263
+ },
264
+ };
265
+ }
266
+ }
267
+
268
+ function getBase64Data(file: ImageModelV3File): string {
269
+ if (file.type === 'url') {
270
+ throw new Error(
271
+ 'URL-based images are not supported for Amazon Bedrock image editing. ' +
272
+ 'Please provide the image data directly.',
273
+ );
274
+ }
275
+
276
+ if (file.data instanceof Uint8Array) {
277
+ return convertUint8ArrayToBase64(file.data);
278
+ }
279
+
280
+ // Already base64 string
281
+ return file.data;
282
+ }
283
+
284
+ // minimal version of the schema, focussed on what is needed for the implementation
285
+ // this approach limits breakages when the API changes and increases efficiency
286
+ const bedrockImageResponseSchema = z.object({
287
+ // Normal successful response
288
+ images: z.array(z.string()).optional(),
289
+
290
+ // Moderation response fields
291
+ id: z.string().optional(),
292
+ status: z.string().optional(),
293
+ result: z.unknown().optional(),
294
+ progress: z.unknown().optional(),
295
+ details: z.record(z.string(), z.unknown()).optional(),
296
+ preview: z.unknown().optional(),
297
+ });
@@ -0,0 +1,6 @@
1
+ export type BedrockImageModelId = 'amazon.nova-canvas-v1:0' | (string & {});
2
+
3
+ // https://docs.aws.amazon.com/nova/latest/userguide/image-gen-req-resp-structure.html
4
+ export const modelMaxImagesPerCall: Record<BedrockImageModelId, number> = {
5
+ 'amazon.nova-canvas-v1:0': 5,
6
+ };
@@ -0,0 +1,190 @@
1
+ import {
2
+ JSONObject,
3
+ LanguageModelV3CallOptions,
4
+ SharedV3Warning,
5
+ UnsupportedFunctionalityError,
6
+ } from '@ai-sdk/provider';
7
+ import { asSchema } from '@ai-sdk/provider-utils';
8
+ import {
9
+ anthropicTools,
10
+ prepareTools as prepareAnthropicTools,
11
+ } from '@ai-sdk/anthropic/internal';
12
+ import { BedrockTool, BedrockToolConfiguration } from './bedrock-api-types';
13
+
14
+ export async function prepareTools({
15
+ tools,
16
+ toolChoice,
17
+ modelId,
18
+ }: {
19
+ tools: LanguageModelV3CallOptions['tools'];
20
+ toolChoice?: LanguageModelV3CallOptions['toolChoice'];
21
+ modelId: string;
22
+ }): Promise<{
23
+ toolConfig: BedrockToolConfiguration;
24
+ additionalTools: Record<string, unknown> | undefined;
25
+ betas: Set<string>;
26
+ toolWarnings: SharedV3Warning[];
27
+ }> {
28
+ const toolWarnings: SharedV3Warning[] = [];
29
+ const betas = new Set<string>();
30
+
31
+ if (tools == null || tools.length === 0) {
32
+ return {
33
+ toolConfig: {},
34
+ additionalTools: undefined,
35
+ betas,
36
+ toolWarnings,
37
+ };
38
+ }
39
+
40
+ // Filter out unsupported web_search tool and add a warning
41
+ const supportedTools = tools.filter(tool => {
42
+ if (
43
+ tool.type === 'provider' &&
44
+ tool.id === 'anthropic.web_search_20250305'
45
+ ) {
46
+ toolWarnings.push({
47
+ type: 'unsupported',
48
+ feature: 'web_search_20250305 tool',
49
+ details:
50
+ 'The web_search_20250305 tool is not supported on Amazon Bedrock.',
51
+ });
52
+ return false; // Exclude this tool
53
+ }
54
+ return true; // Include all other tools
55
+ });
56
+
57
+ if (supportedTools.length === 0) {
58
+ return {
59
+ toolConfig: {},
60
+ additionalTools: undefined,
61
+ betas,
62
+ toolWarnings,
63
+ };
64
+ }
65
+
66
+ const isAnthropicModel = modelId.includes('anthropic.');
67
+ const ProviderTools = supportedTools.filter(t => t.type === 'provider');
68
+ const functionTools = supportedTools.filter(t => t.type === 'function');
69
+
70
+ let additionalTools: Record<string, unknown> | undefined = undefined;
71
+ const bedrockTools: BedrockTool[] = [];
72
+
73
+ const usingAnthropicTools = isAnthropicModel && ProviderTools.length > 0;
74
+
75
+ // Handle Anthropic provider-defined tools for Anthropic models on Bedrock
76
+ if (usingAnthropicTools) {
77
+ if (functionTools.length > 0) {
78
+ toolWarnings.push({
79
+ type: 'unsupported',
80
+ feature:
81
+ 'mixing Anthropic provider-defined tools and standard function tools',
82
+ details:
83
+ 'Mixed Anthropic provider-defined tools and standard function tools are not supported in a single call to Bedrock. Only Anthropic tools will be used.',
84
+ });
85
+ }
86
+
87
+ const {
88
+ toolChoice: preparedAnthropicToolChoice,
89
+ toolWarnings: anthropicToolWarnings,
90
+ betas: anthropicBetas,
91
+ } = await prepareAnthropicTools({
92
+ tools: ProviderTools,
93
+ toolChoice,
94
+ supportsStructuredOutput: false,
95
+ });
96
+
97
+ toolWarnings.push(...anthropicToolWarnings);
98
+ anthropicBetas.forEach(beta => betas.add(beta));
99
+
100
+ // For Anthropic tools on Bedrock, only the 'tool_choice' goes into additional fields.
101
+ // The tool definitions themselves are sent in the standard 'toolConfig'.
102
+ if (preparedAnthropicToolChoice) {
103
+ additionalTools = {
104
+ tool_choice: preparedAnthropicToolChoice,
105
+ };
106
+ }
107
+
108
+ // Create a standard Bedrock tool representation for validation purposes
109
+ for (const tool of ProviderTools) {
110
+ const toolFactory = Object.values(anthropicTools).find(factory => {
111
+ const instance = (factory as (args: any) => any)({});
112
+ return instance.id === tool.id;
113
+ });
114
+
115
+ if (toolFactory != null) {
116
+ const fullToolDefinition = (toolFactory as (args: any) => any)({});
117
+ bedrockTools.push({
118
+ toolSpec: {
119
+ name: tool.name,
120
+ inputSchema: {
121
+ json: (await asSchema(fullToolDefinition.inputSchema)
122
+ .jsonSchema) as JSONObject,
123
+ },
124
+ },
125
+ });
126
+ } else {
127
+ toolWarnings.push({ type: 'unsupported', feature: 'tool ${tool.id}' });
128
+ }
129
+ }
130
+ } else {
131
+ // Report unsupported provider-defined tools for non-anthropic models
132
+ for (const tool of ProviderTools) {
133
+ toolWarnings.push({ type: 'unsupported', feature: `tool ${tool.id}` });
134
+ }
135
+ }
136
+
137
+ // Handle standard function tools for all models
138
+ for (const tool of functionTools) {
139
+ bedrockTools.push({
140
+ toolSpec: {
141
+ name: tool.name,
142
+ ...(tool.description?.trim() !== ''
143
+ ? { description: tool.description }
144
+ : {}),
145
+ inputSchema: {
146
+ json: tool.inputSchema as JSONObject,
147
+ },
148
+ },
149
+ });
150
+ }
151
+
152
+ // Handle toolChoice for standard Bedrock tools, but NOT for Anthropic provider-defined tools
153
+ let bedrockToolChoice: BedrockToolConfiguration['toolChoice'] = undefined;
154
+ if (!usingAnthropicTools && bedrockTools.length > 0 && toolChoice) {
155
+ const type = toolChoice.type;
156
+ switch (type) {
157
+ case 'auto':
158
+ bedrockToolChoice = { auto: {} };
159
+ break;
160
+ case 'required':
161
+ bedrockToolChoice = { any: {} };
162
+ break;
163
+ case 'none':
164
+ bedrockTools.length = 0;
165
+ bedrockToolChoice = undefined;
166
+ break;
167
+ case 'tool':
168
+ bedrockToolChoice = { tool: { name: toolChoice.toolName } };
169
+ break;
170
+ default: {
171
+ const _exhaustiveCheck: never = type;
172
+ throw new UnsupportedFunctionalityError({
173
+ functionality: `tool choice type: ${_exhaustiveCheck}`,
174
+ });
175
+ }
176
+ }
177
+ }
178
+
179
+ const toolConfig: BedrockToolConfiguration =
180
+ bedrockTools.length > 0
181
+ ? { tools: bedrockTools, toolChoice: bedrockToolChoice }
182
+ : {};
183
+
184
+ return {
185
+ toolConfig,
186
+ additionalTools,
187
+ betas,
188
+ toolWarnings,
189
+ };
190
+ }