@ai-sdk/amazon-bedrock 4.0.24 → 4.0.26
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/CHANGELOG.md +16 -0
- package/dist/anthropic/index.js +1 -1
- package/dist/anthropic/index.mjs +1 -1
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/docs/08-amazon-bedrock.mdx +1453 -0
- package/package.json +11 -6
- package/src/__fixtures__/bedrock-json-only-text-first.1.chunks.txt +7 -0
- package/src/__fixtures__/bedrock-json-other-tool.1.chunks.txt +6 -0
- package/src/__fixtures__/bedrock-json-other-tool.1.json +24 -0
- package/src/__fixtures__/bedrock-json-tool-text-then-weather-then-json.1.chunks.txt +12 -0
- package/src/__fixtures__/bedrock-json-tool-with-answer.1.json +29 -0
- package/src/__fixtures__/bedrock-json-tool.1.chunks.txt +4 -0
- package/src/__fixtures__/bedrock-json-tool.1.json +35 -0
- package/src/__fixtures__/bedrock-json-tool.2.chunks.txt +6 -0
- package/src/__fixtures__/bedrock-json-tool.2.json +28 -0
- package/src/__fixtures__/bedrock-json-tool.3.chunks.txt +7 -0
- package/src/__fixtures__/bedrock-json-tool.3.json +36 -0
- package/src/__fixtures__/bedrock-json-with-tool.1.chunks.txt +9 -0
- package/src/__fixtures__/bedrock-json-with-tool.1.json +41 -0
- package/src/__fixtures__/bedrock-json-with-tools.1.chunks.txt +12 -0
- package/src/__fixtures__/bedrock-json-with-tools.1.json +50 -0
- package/src/__fixtures__/bedrock-tool-call.1.chunks.txt +6 -0
- package/src/__fixtures__/bedrock-tool-call.1.json +24 -0
- package/src/__fixtures__/bedrock-tool-no-args.chunks.txt +8 -0
- package/src/__fixtures__/bedrock-tool-no-args.json +25 -0
- package/src/anthropic/bedrock-anthropic-fetch.test.ts +344 -0
- package/src/anthropic/bedrock-anthropic-fetch.ts +62 -0
- package/src/anthropic/bedrock-anthropic-options.ts +28 -0
- package/src/anthropic/bedrock-anthropic-provider.test.ts +456 -0
- package/src/anthropic/bedrock-anthropic-provider.ts +357 -0
- package/src/anthropic/index.ts +9 -0
- package/src/bedrock-api-types.ts +195 -0
- package/src/bedrock-chat-language-model.test.ts +4569 -0
- package/src/bedrock-chat-language-model.ts +1019 -0
- package/src/bedrock-chat-options.ts +114 -0
- package/src/bedrock-embedding-model.test.ts +148 -0
- package/src/bedrock-embedding-model.ts +104 -0
- package/src/bedrock-embedding-options.ts +24 -0
- package/src/bedrock-error.ts +6 -0
- package/src/bedrock-event-stream-decoder.ts +59 -0
- package/src/bedrock-event-stream-response-handler.test.ts +233 -0
- package/src/bedrock-event-stream-response-handler.ts +57 -0
- package/src/bedrock-image-model.test.ts +866 -0
- package/src/bedrock-image-model.ts +297 -0
- package/src/bedrock-image-settings.ts +6 -0
- package/src/bedrock-prepare-tools.ts +190 -0
- package/src/bedrock-provider.test.ts +457 -0
- package/src/bedrock-provider.ts +351 -0
- package/src/bedrock-sigv4-fetch.test.ts +675 -0
- package/src/bedrock-sigv4-fetch.ts +138 -0
- package/src/convert-bedrock-usage.test.ts +207 -0
- package/src/convert-bedrock-usage.ts +50 -0
- package/src/convert-to-bedrock-chat-messages.test.ts +1175 -0
- package/src/convert-to-bedrock-chat-messages.ts +452 -0
- package/src/index.ts +10 -0
- package/src/inject-fetch-headers.test.ts +135 -0
- package/src/inject-fetch-headers.ts +32 -0
- package/src/map-bedrock-finish-reason.ts +22 -0
- package/src/normalize-tool-call-id.test.ts +72 -0
- package/src/normalize-tool-call-id.ts +36 -0
- package/src/reranking/__fixtures__/bedrock-reranking.1.json +12 -0
- package/src/reranking/bedrock-reranking-api.ts +44 -0
- package/src/reranking/bedrock-reranking-model.test.ts +299 -0
- package/src/reranking/bedrock-reranking-model.ts +115 -0
- package/src/reranking/bedrock-reranking-options.ts +36 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JSONObject,
|
|
3
|
+
LanguageModelV3Message,
|
|
4
|
+
LanguageModelV3Prompt,
|
|
5
|
+
SharedV3ProviderMetadata,
|
|
6
|
+
UnsupportedFunctionalityError,
|
|
7
|
+
} from '@ai-sdk/provider';
|
|
8
|
+
import { convertToBase64, parseProviderOptions } from '@ai-sdk/provider-utils';
|
|
9
|
+
import {
|
|
10
|
+
BEDROCK_CACHE_POINT,
|
|
11
|
+
BEDROCK_DOCUMENT_MIME_TYPES,
|
|
12
|
+
BEDROCK_IMAGE_MIME_TYPES,
|
|
13
|
+
BedrockAssistantMessage,
|
|
14
|
+
BedrockCachePoint,
|
|
15
|
+
BedrockDocumentFormat,
|
|
16
|
+
BedrockDocumentMimeType,
|
|
17
|
+
BedrockImageFormat,
|
|
18
|
+
BedrockImageMimeType,
|
|
19
|
+
BedrockMessages,
|
|
20
|
+
BedrockSystemMessages,
|
|
21
|
+
BedrockUserMessage,
|
|
22
|
+
} from './bedrock-api-types';
|
|
23
|
+
import { bedrockReasoningMetadataSchema } from './bedrock-chat-language-model';
|
|
24
|
+
import { bedrockFilePartProviderOptions } from './bedrock-chat-options';
|
|
25
|
+
import { normalizeToolCallId } from './normalize-tool-call-id';
|
|
26
|
+
|
|
27
|
+
function getCachePoint(
|
|
28
|
+
providerMetadata: SharedV3ProviderMetadata | undefined,
|
|
29
|
+
): BedrockCachePoint | undefined {
|
|
30
|
+
return providerMetadata?.bedrock?.cachePoint as BedrockCachePoint | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function shouldEnableCitations(
|
|
34
|
+
providerMetadata: SharedV3ProviderMetadata | undefined,
|
|
35
|
+
): Promise<boolean> {
|
|
36
|
+
const bedrockOptions = await parseProviderOptions({
|
|
37
|
+
provider: 'bedrock',
|
|
38
|
+
providerOptions: providerMetadata,
|
|
39
|
+
schema: bedrockFilePartProviderOptions,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return bedrockOptions?.citations?.enabled ?? false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function convertToBedrockChatMessages(
|
|
46
|
+
prompt: LanguageModelV3Prompt,
|
|
47
|
+
isMistral: boolean = false,
|
|
48
|
+
): Promise<{
|
|
49
|
+
system: BedrockSystemMessages;
|
|
50
|
+
messages: BedrockMessages;
|
|
51
|
+
}> {
|
|
52
|
+
const blocks = groupIntoBlocks(prompt);
|
|
53
|
+
|
|
54
|
+
let system: BedrockSystemMessages = [];
|
|
55
|
+
const messages: BedrockMessages = [];
|
|
56
|
+
|
|
57
|
+
let documentCounter = 0;
|
|
58
|
+
const generateDocumentName = () => `document-${++documentCounter}`;
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
61
|
+
const block = blocks[i];
|
|
62
|
+
const isLastBlock = i === blocks.length - 1;
|
|
63
|
+
const type = block.type;
|
|
64
|
+
|
|
65
|
+
switch (type) {
|
|
66
|
+
case 'system': {
|
|
67
|
+
if (messages.length > 0) {
|
|
68
|
+
throw new UnsupportedFunctionalityError({
|
|
69
|
+
functionality:
|
|
70
|
+
'Multiple system messages that are separated by user/assistant messages',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const message of block.messages) {
|
|
75
|
+
system.push({ text: message.content });
|
|
76
|
+
if (getCachePoint(message.providerOptions)) {
|
|
77
|
+
system.push(BEDROCK_CACHE_POINT);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case 'user': {
|
|
84
|
+
// combines all user and tool messages in this block into a single message:
|
|
85
|
+
const bedrockContent: BedrockUserMessage['content'] = [];
|
|
86
|
+
|
|
87
|
+
for (const message of block.messages) {
|
|
88
|
+
const { role, content, providerOptions } = message;
|
|
89
|
+
switch (role) {
|
|
90
|
+
case 'user': {
|
|
91
|
+
for (let j = 0; j < content.length; j++) {
|
|
92
|
+
const part = content[j];
|
|
93
|
+
|
|
94
|
+
switch (part.type) {
|
|
95
|
+
case 'text': {
|
|
96
|
+
bedrockContent.push({
|
|
97
|
+
text: part.text,
|
|
98
|
+
});
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
case 'file': {
|
|
103
|
+
if (part.data instanceof URL) {
|
|
104
|
+
// The AI SDK automatically downloads files for user file parts with URLs
|
|
105
|
+
throw new UnsupportedFunctionalityError({
|
|
106
|
+
functionality: 'File URL data',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (part.mediaType.startsWith('image/')) {
|
|
111
|
+
bedrockContent.push({
|
|
112
|
+
image: {
|
|
113
|
+
format: getBedrockImageFormat(part.mediaType),
|
|
114
|
+
source: { bytes: convertToBase64(part.data) },
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
if (!part.mediaType) {
|
|
119
|
+
throw new UnsupportedFunctionalityError({
|
|
120
|
+
functionality: 'file without mime type',
|
|
121
|
+
message:
|
|
122
|
+
'File mime type is required in user message part content',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const enableCitations = await shouldEnableCitations(
|
|
127
|
+
part.providerOptions,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
bedrockContent.push({
|
|
131
|
+
document: {
|
|
132
|
+
format: getBedrockDocumentFormat(part.mediaType),
|
|
133
|
+
name: part.filename ?? generateDocumentName(),
|
|
134
|
+
source: { bytes: convertToBase64(part.data) },
|
|
135
|
+
...(enableCitations && {
|
|
136
|
+
citations: { enabled: true },
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'tool': {
|
|
150
|
+
for (const part of content) {
|
|
151
|
+
if (part.type === 'tool-approval-response') {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
let toolResultContent;
|
|
155
|
+
|
|
156
|
+
const output = part.output;
|
|
157
|
+
switch (output.type) {
|
|
158
|
+
case 'content': {
|
|
159
|
+
toolResultContent = output.value.map(contentPart => {
|
|
160
|
+
switch (contentPart.type) {
|
|
161
|
+
case 'text':
|
|
162
|
+
return { text: contentPart.text };
|
|
163
|
+
case 'image-data':
|
|
164
|
+
if (!contentPart.mediaType.startsWith('image/')) {
|
|
165
|
+
throw new UnsupportedFunctionalityError({
|
|
166
|
+
functionality: `media type: ${contentPart.mediaType}`,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const format = getBedrockImageFormat(
|
|
171
|
+
contentPart.mediaType,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
image: {
|
|
176
|
+
format,
|
|
177
|
+
source: { bytes: contentPart.data },
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
default: {
|
|
181
|
+
throw new UnsupportedFunctionalityError({
|
|
182
|
+
functionality: `unsupported tool content part type: ${contentPart.type}`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case 'text':
|
|
190
|
+
case 'error-text':
|
|
191
|
+
toolResultContent = [{ text: output.value }];
|
|
192
|
+
break;
|
|
193
|
+
case 'execution-denied':
|
|
194
|
+
toolResultContent = [
|
|
195
|
+
{ text: output.reason ?? 'Tool execution denied.' },
|
|
196
|
+
];
|
|
197
|
+
break;
|
|
198
|
+
case 'json':
|
|
199
|
+
case 'error-json':
|
|
200
|
+
default:
|
|
201
|
+
toolResultContent = [
|
|
202
|
+
{ text: JSON.stringify(output.value) },
|
|
203
|
+
];
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
bedrockContent.push({
|
|
208
|
+
toolResult: {
|
|
209
|
+
toolUseId: normalizeToolCallId(part.toolCallId, isMistral),
|
|
210
|
+
content: toolResultContent,
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
default: {
|
|
218
|
+
const _exhaustiveCheck: never = role;
|
|
219
|
+
throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (getCachePoint(providerOptions)) {
|
|
224
|
+
bedrockContent.push(BEDROCK_CACHE_POINT);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
messages.push({ role: 'user', content: bedrockContent });
|
|
229
|
+
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
case 'assistant': {
|
|
234
|
+
// combines multiple assistant messages in this block into a single message:
|
|
235
|
+
const bedrockContent: BedrockAssistantMessage['content'] = [];
|
|
236
|
+
|
|
237
|
+
for (let j = 0; j < block.messages.length; j++) {
|
|
238
|
+
const message = block.messages[j];
|
|
239
|
+
const isLastMessage = j === block.messages.length - 1;
|
|
240
|
+
const { content } = message;
|
|
241
|
+
|
|
242
|
+
for (let k = 0; k < content.length; k++) {
|
|
243
|
+
const part = content[k];
|
|
244
|
+
const isLastContentPart = k === content.length - 1;
|
|
245
|
+
|
|
246
|
+
switch (part.type) {
|
|
247
|
+
case 'text': {
|
|
248
|
+
// Skip empty text blocks
|
|
249
|
+
if (!part.text.trim()) {
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
bedrockContent.push({
|
|
254
|
+
text:
|
|
255
|
+
// trim the last text part if it's the last message in the block
|
|
256
|
+
// because Bedrock does not allow trailing whitespace
|
|
257
|
+
// in pre-filled assistant responses
|
|
258
|
+
trimIfLast(
|
|
259
|
+
isLastBlock,
|
|
260
|
+
isLastMessage,
|
|
261
|
+
isLastContentPart,
|
|
262
|
+
part.text,
|
|
263
|
+
),
|
|
264
|
+
});
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case 'reasoning': {
|
|
269
|
+
const reasoningMetadata = await parseProviderOptions({
|
|
270
|
+
provider: 'bedrock',
|
|
271
|
+
providerOptions: part.providerOptions,
|
|
272
|
+
schema: bedrockReasoningMetadataSchema,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (reasoningMetadata != null) {
|
|
276
|
+
if (reasoningMetadata.signature != null) {
|
|
277
|
+
bedrockContent.push({
|
|
278
|
+
reasoningContent: {
|
|
279
|
+
reasoningText: {
|
|
280
|
+
// trim the last text part if it's the last message in the block
|
|
281
|
+
// because Bedrock does not allow trailing whitespace
|
|
282
|
+
// in pre-filled assistant responses
|
|
283
|
+
text: trimIfLast(
|
|
284
|
+
isLastBlock,
|
|
285
|
+
isLastMessage,
|
|
286
|
+
isLastContentPart,
|
|
287
|
+
part.text,
|
|
288
|
+
),
|
|
289
|
+
signature: reasoningMetadata.signature,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
} else if (reasoningMetadata.redactedData != null) {
|
|
294
|
+
bedrockContent.push({
|
|
295
|
+
reasoningContent: {
|
|
296
|
+
redactedReasoning: {
|
|
297
|
+
data: reasoningMetadata.redactedData,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
case 'tool-call': {
|
|
308
|
+
bedrockContent.push({
|
|
309
|
+
toolUse: {
|
|
310
|
+
toolUseId: normalizeToolCallId(part.toolCallId, isMistral),
|
|
311
|
+
name: part.toolName,
|
|
312
|
+
input: part.input as JSONObject,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (getCachePoint(message.providerOptions)) {
|
|
320
|
+
bedrockContent.push(BEDROCK_CACHE_POINT);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
messages.push({ role: 'assistant', content: bedrockContent });
|
|
325
|
+
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
default: {
|
|
330
|
+
const _exhaustiveCheck: never = type;
|
|
331
|
+
throw new Error(`Unsupported type: ${_exhaustiveCheck}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return { system, messages };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function isBedrockImageFormat(format: string): format is BedrockImageFormat {
|
|
340
|
+
return Object.values(BEDROCK_IMAGE_MIME_TYPES).includes(
|
|
341
|
+
format as BedrockImageFormat,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function getBedrockImageFormat(mimeType?: string): BedrockImageFormat {
|
|
346
|
+
if (!mimeType) {
|
|
347
|
+
throw new UnsupportedFunctionalityError({
|
|
348
|
+
functionality: 'image without mime type',
|
|
349
|
+
message: 'Image mime type is required in user message part content',
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const format = BEDROCK_IMAGE_MIME_TYPES[mimeType as BedrockImageMimeType];
|
|
354
|
+
if (!format) {
|
|
355
|
+
throw new UnsupportedFunctionalityError({
|
|
356
|
+
functionality: `image mime type: ${mimeType}`,
|
|
357
|
+
message: `Unsupported image mime type: ${mimeType}, expected one of: ${Object.keys(BEDROCK_IMAGE_MIME_TYPES).join(', ')}`,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return format;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function getBedrockDocumentFormat(mimeType: string): BedrockDocumentFormat {
|
|
365
|
+
const format =
|
|
366
|
+
BEDROCK_DOCUMENT_MIME_TYPES[mimeType as BedrockDocumentMimeType];
|
|
367
|
+
if (!format) {
|
|
368
|
+
throw new UnsupportedFunctionalityError({
|
|
369
|
+
functionality: `file mime type: ${mimeType}`,
|
|
370
|
+
message: `Unsupported file mime type: ${mimeType}, expected one of: ${Object.keys(BEDROCK_DOCUMENT_MIME_TYPES).join(', ')}`,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
return format;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function trimIfLast(
|
|
377
|
+
isLastBlock: boolean,
|
|
378
|
+
isLastMessage: boolean,
|
|
379
|
+
isLastContentPart: boolean,
|
|
380
|
+
text: string,
|
|
381
|
+
) {
|
|
382
|
+
return isLastBlock && isLastMessage && isLastContentPart ? text.trim() : text;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
type SystemBlock = {
|
|
386
|
+
type: 'system';
|
|
387
|
+
messages: Array<LanguageModelV3Message & { role: 'system' }>;
|
|
388
|
+
};
|
|
389
|
+
type AssistantBlock = {
|
|
390
|
+
type: 'assistant';
|
|
391
|
+
messages: Array<LanguageModelV3Message & { role: 'assistant' }>;
|
|
392
|
+
};
|
|
393
|
+
type UserBlock = {
|
|
394
|
+
type: 'user';
|
|
395
|
+
messages: Array<LanguageModelV3Message & { role: 'user' | 'tool' }>;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
function groupIntoBlocks(
|
|
399
|
+
prompt: LanguageModelV3Prompt,
|
|
400
|
+
): Array<SystemBlock | AssistantBlock | UserBlock> {
|
|
401
|
+
const blocks: Array<SystemBlock | AssistantBlock | UserBlock> = [];
|
|
402
|
+
let currentBlock: SystemBlock | AssistantBlock | UserBlock | undefined =
|
|
403
|
+
undefined;
|
|
404
|
+
|
|
405
|
+
for (const message of prompt) {
|
|
406
|
+
const { role } = message;
|
|
407
|
+
switch (role) {
|
|
408
|
+
case 'system': {
|
|
409
|
+
if (currentBlock?.type !== 'system') {
|
|
410
|
+
currentBlock = { type: 'system', messages: [] };
|
|
411
|
+
blocks.push(currentBlock);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
currentBlock.messages.push(message);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
case 'assistant': {
|
|
418
|
+
if (currentBlock?.type !== 'assistant') {
|
|
419
|
+
currentBlock = { type: 'assistant', messages: [] };
|
|
420
|
+
blocks.push(currentBlock);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
currentBlock.messages.push(message);
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case 'user': {
|
|
427
|
+
if (currentBlock?.type !== 'user') {
|
|
428
|
+
currentBlock = { type: 'user', messages: [] };
|
|
429
|
+
blocks.push(currentBlock);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
currentBlock.messages.push(message);
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
case 'tool': {
|
|
436
|
+
if (currentBlock?.type !== 'user') {
|
|
437
|
+
currentBlock = { type: 'user', messages: [] };
|
|
438
|
+
blocks.push(currentBlock);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
currentBlock.messages.push(message);
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
default: {
|
|
445
|
+
const _exhaustiveCheck: never = role;
|
|
446
|
+
throw new Error(`Unsupported role: ${_exhaustiveCheck}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return blocks;
|
|
452
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { AnthropicProviderOptions } from '@ai-sdk/anthropic';
|
|
2
|
+
|
|
3
|
+
export type { BedrockProviderOptions } from './bedrock-chat-options';
|
|
4
|
+
export { bedrock, createAmazonBedrock } from './bedrock-provider';
|
|
5
|
+
export type {
|
|
6
|
+
AmazonBedrockProvider,
|
|
7
|
+
AmazonBedrockProviderSettings,
|
|
8
|
+
} from './bedrock-provider';
|
|
9
|
+
export type { BedrockRerankingOptions } from './reranking/bedrock-reranking-options';
|
|
10
|
+
export { VERSION } from './version';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { injectFetchHeaders } from './inject-fetch-headers';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// Mock the version module
|
|
5
|
+
vi.mock('./version', () => ({
|
|
6
|
+
VERSION: '0.0.0-test',
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
// // Mock provider-utils to control runtime environment detection
|
|
10
|
+
vi.mock('@ai-sdk/provider-utils', async () => {
|
|
11
|
+
const actual = await vi.importActual('@ai-sdk/provider-utils');
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
getRuntimeEnvironmentUserAgent: vi.fn(() => 'runtime/testenv'),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('injectFetchHeaders', () => {
|
|
19
|
+
const originalFetch = globalThis.fetch;
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
globalThis.fetch = vi.fn();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
globalThis.fetch = originalFetch;
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should inject custom headers into fetch request', async () => {
|
|
31
|
+
const mockFetch = vi.fn().mockResolvedValue('response');
|
|
32
|
+
globalThis.fetch = mockFetch;
|
|
33
|
+
|
|
34
|
+
const customHeaders = {
|
|
35
|
+
'x-custom-header': 'custom-value',
|
|
36
|
+
authorization: 'Bearer token',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const enhancedFetch = injectFetchHeaders(customHeaders);
|
|
40
|
+
await enhancedFetch('https://api.example.com');
|
|
41
|
+
|
|
42
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
43
|
+
'https://api.example.com',
|
|
44
|
+
expect.objectContaining({
|
|
45
|
+
headers: expect.objectContaining({
|
|
46
|
+
'x-custom-header': 'custom-value',
|
|
47
|
+
authorization: 'Bearer token',
|
|
48
|
+
'user-agent': 'ai-sdk/amazon-bedrock/0.0.0-test runtime/testenv',
|
|
49
|
+
}),
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should merge custom headers with existing headers', async () => {
|
|
55
|
+
const mockFetch = vi.fn().mockResolvedValue('response');
|
|
56
|
+
globalThis.fetch = mockFetch;
|
|
57
|
+
|
|
58
|
+
const customHeaders = {
|
|
59
|
+
'x-custom-header': 'custom-value',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const existingHeaders = {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const enhancedFetch = injectFetchHeaders(customHeaders);
|
|
67
|
+
await enhancedFetch('https://api.example.com', {
|
|
68
|
+
headers: existingHeaders,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
72
|
+
'https://api.example.com',
|
|
73
|
+
expect.objectContaining({
|
|
74
|
+
headers: expect.objectContaining({
|
|
75
|
+
'content-type': 'application/json',
|
|
76
|
+
'x-custom-header': 'custom-value',
|
|
77
|
+
'user-agent': 'ai-sdk/amazon-bedrock/0.0.0-test runtime/testenv',
|
|
78
|
+
}),
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should handle undefined headers in init', async () => {
|
|
84
|
+
const mockFetch = vi.fn().mockResolvedValue('response');
|
|
85
|
+
globalThis.fetch = mockFetch;
|
|
86
|
+
|
|
87
|
+
const customHeaders = {
|
|
88
|
+
'x-custom-header': 'custom-value',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const enhancedFetch = injectFetchHeaders(customHeaders);
|
|
92
|
+
await enhancedFetch('https://api.example.com', {
|
|
93
|
+
headers: undefined,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
97
|
+
'https://api.example.com',
|
|
98
|
+
expect.objectContaining({
|
|
99
|
+
headers: expect.objectContaining({
|
|
100
|
+
'x-custom-header': 'custom-value',
|
|
101
|
+
'user-agent': 'ai-sdk/amazon-bedrock/0.0.0-test runtime/testenv',
|
|
102
|
+
}),
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should handle Headers instance in init', async () => {
|
|
108
|
+
const mockFetch = vi.fn().mockResolvedValue('response');
|
|
109
|
+
globalThis.fetch = mockFetch;
|
|
110
|
+
|
|
111
|
+
const customHeaders = {
|
|
112
|
+
'x-custom-header': 'custom-value',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const existingHeaders = new Headers({
|
|
116
|
+
'Content-Type': 'application/json',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const enhancedFetch = injectFetchHeaders(customHeaders);
|
|
120
|
+
await enhancedFetch('https://api.example.com', {
|
|
121
|
+
headers: existingHeaders,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
125
|
+
'https://api.example.com',
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
headers: expect.objectContaining({
|
|
128
|
+
'content-type': 'application/json',
|
|
129
|
+
'x-custom-header': 'custom-value',
|
|
130
|
+
'user-agent': 'ai-sdk/amazon-bedrock/0.0.0-test runtime/testenv',
|
|
131
|
+
}),
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type FetchFunction,
|
|
3
|
+
getRuntimeEnvironmentUserAgent,
|
|
4
|
+
normalizeHeaders,
|
|
5
|
+
withUserAgentSuffix,
|
|
6
|
+
} from '@ai-sdk/provider-utils';
|
|
7
|
+
import { VERSION } from './version';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Test helper to inject custom headers into a fetch request.
|
|
11
|
+
* @param customHeaders - The headers to inject.
|
|
12
|
+
* @returns A fetch function that injects the custom headers.
|
|
13
|
+
*/
|
|
14
|
+
export function injectFetchHeaders(
|
|
15
|
+
customHeaders: Record<string, string>,
|
|
16
|
+
): FetchFunction {
|
|
17
|
+
return async (input, init = {}) => {
|
|
18
|
+
const headers = withUserAgentSuffix(
|
|
19
|
+
{
|
|
20
|
+
...normalizeHeaders(init.headers),
|
|
21
|
+
...customHeaders,
|
|
22
|
+
},
|
|
23
|
+
`ai-sdk/amazon-bedrock/${VERSION}`,
|
|
24
|
+
getRuntimeEnvironmentUserAgent(),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return await globalThis.fetch(input, {
|
|
28
|
+
...init,
|
|
29
|
+
headers,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { LanguageModelV3FinishReason } from '@ai-sdk/provider';
|
|
2
|
+
import { BedrockStopReason } from './bedrock-api-types';
|
|
3
|
+
|
|
4
|
+
export function mapBedrockFinishReason(
|
|
5
|
+
finishReason: BedrockStopReason,
|
|
6
|
+
isJsonResponseFromTool?: boolean,
|
|
7
|
+
): LanguageModelV3FinishReason['unified'] {
|
|
8
|
+
switch (finishReason) {
|
|
9
|
+
case 'stop_sequence':
|
|
10
|
+
case 'end_turn':
|
|
11
|
+
return 'stop';
|
|
12
|
+
case 'max_tokens':
|
|
13
|
+
return 'length';
|
|
14
|
+
case 'content_filtered':
|
|
15
|
+
case 'guardrail_intervened':
|
|
16
|
+
return 'content-filter';
|
|
17
|
+
case 'tool_use':
|
|
18
|
+
return isJsonResponseFromTool ? 'stop' : 'tool-calls';
|
|
19
|
+
default:
|
|
20
|
+
return 'other';
|
|
21
|
+
}
|
|
22
|
+
}
|