@ai-sdk/amazon-bedrock 5.0.74 → 5.0.75

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.
@@ -35,7 +35,7 @@ var import_provider_utils = require("@ai-sdk/provider-utils");
35
35
  var import_aws4fetch = require("aws4fetch");
36
36
 
37
37
  // src/version.ts
38
- var VERSION = true ? "5.0.74" : "0.0.0-test";
38
+ var VERSION = true ? "5.0.75" : "0.0.0-test";
39
39
 
40
40
  // src/amazon-bedrock-sigv4-fetch.ts
41
41
  function createSigV4FetchFunction(getCredentials, fetch, service = "bedrock") {
@@ -23,7 +23,7 @@ import {
23
23
  import { AwsV4Signer } from "aws4fetch";
24
24
 
25
25
  // src/version.ts
26
- var VERSION = true ? "5.0.74" : "0.0.0-test";
26
+ var VERSION = true ? "5.0.75" : "0.0.0-test";
27
27
 
28
28
  // src/amazon-bedrock-sigv4-fetch.ts
29
29
  function createSigV4FetchFunction(getCredentials, fetch, service = "bedrock") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/amazon-bedrock",
3
- "version": "5.0.74",
3
+ "version": "5.0.75",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@ai-sdk/anthropic": "4.0.49",
45
- "@ai-sdk/openai": "4.0.58",
45
+ "@ai-sdk/openai": "4.0.59",
46
46
  "@ai-sdk/provider": "4.0.10",
47
47
  "@ai-sdk/provider-utils": "5.0.36",
48
48
  "@smithy/eventstream-codec": "^4.3.3",
@@ -632,10 +632,17 @@ export class AmazonBedrockChatLanguageModel implements LanguageModelV4 {
632
632
  // map response content to content array
633
633
  for (const part of response.output.message.content) {
634
634
  // text
635
- if (part.text != null) {
635
+ const textParts =
636
+ part.text != null
637
+ ? [part.text]
638
+ : (part.citationsContent?.content?.flatMap(content =>
639
+ content.text == null ? [] : [content.text],
640
+ ) ?? []);
641
+
642
+ for (const text of textParts) {
636
643
  content.push({
637
644
  type: 'text',
638
- text: jsonObjectTextExtractor?.process(part.text) ?? part.text,
645
+ text: jsonObjectTextExtractor?.process(text) ?? text,
639
646
  });
640
647
  }
641
648
 
@@ -1381,6 +1388,17 @@ const AmazonBedrockResponseSchema = z.object({
1381
1388
  content: z.array(
1382
1389
  z.object({
1383
1390
  text: z.string().nullish(),
1391
+ citationsContent: z
1392
+ .object({
1393
+ content: z
1394
+ .array(
1395
+ z.object({
1396
+ text: z.string().nullish(),
1397
+ }),
1398
+ )
1399
+ .nullish(),
1400
+ })
1401
+ .nullish(),
1384
1402
  toolUse: AmazonBedrockToolUseSchema.nullish(),
1385
1403
  reasoningContent: z
1386
1404
  .union([
@@ -5,6 +5,7 @@ import {
5
5
  type JSONValue,
6
6
  type LanguageModelV4Message,
7
7
  type LanguageModelV4Prompt,
8
+ type LanguageModelV4ToolResultPart,
8
9
  type SharedV4ProviderMetadata,
9
10
  } from '@ai-sdk/provider';
10
11
  import {
@@ -28,6 +29,7 @@ import {
28
29
  type AmazonBedrockImageMimeType,
29
30
  type AmazonBedrockMessages,
30
31
  type AmazonBedrockSystemMessages,
32
+ type AmazonBedrockToolResultBlock,
31
33
  type AmazonBedrockUserMessage,
32
34
  type AmazonBedrockVideoBlock,
33
35
  type AmazonBedrockVideoFormat,
@@ -72,6 +74,15 @@ function sanitizeToolName(toolName: string): string {
72
74
  return toolName.replace(/[^a-zA-Z0-9_-]/g, '') || '_';
73
75
  }
74
76
 
77
+ function sanitizeDocumentName(filename: string): string {
78
+ return stripFileExtension(filename)
79
+ .replace(/\s+/g, ' ')
80
+ .replace(/[^a-zA-Z0-9 ()[\]-]/g, '')
81
+ .trim()
82
+ .slice(0, 200)
83
+ .trim();
84
+ }
85
+
75
86
  function getAmazonBedrockMediaSource({
76
87
  data,
77
88
  functionality,
@@ -162,6 +173,8 @@ export async function convertToAmazonBedrockChatMessages(
162
173
 
163
174
  let documentCounter = 0;
164
175
  const generateDocumentName = () => `document-${++documentCounter}`;
176
+ const getDocumentName = (filename: string | undefined) =>
177
+ (filename && sanitizeDocumentName(filename)) || generateDocumentName();
165
178
 
166
179
  for (let i = 0; i < blocks.length; i++) {
167
180
  const block = blocks[i];
@@ -284,9 +297,7 @@ export async function convertToAmazonBedrockChatMessages(
284
297
  document: {
285
298
  format:
286
299
  getAmazonBedrockDocumentFormat(textMediaType),
287
- name: part.filename
288
- ? stripFileExtension(part.filename)
289
- : generateDocumentName(),
300
+ name: getDocumentName(part.filename),
290
301
  source: {
291
302
  bytes: convertToBase64(
292
303
  new TextEncoder().encode(part.data.text),
@@ -350,9 +361,7 @@ export async function convertToAmazonBedrockChatMessages(
350
361
  document: {
351
362
  format:
352
363
  getAmazonBedrockDocumentFormat(fullMediaType),
353
- name: part.filename
354
- ? stripFileExtension(part.filename)
355
- : generateDocumentName(),
364
+ name: getDocumentName(part.filename),
356
365
  source: {
357
366
  bytes: convertToBase64(part.data.data),
358
367
  },
@@ -381,126 +390,14 @@ export async function convertToAmazonBedrockChatMessages(
381
390
  if (part.type === 'tool-approval-response') {
382
391
  continue;
383
392
  }
384
- let toolResultContent;
385
-
386
- const output = part.output;
387
- switch (output.type) {
388
- case 'content': {
389
- toolResultContent = await Promise.all(
390
- output.value.map(async contentPart => {
391
- switch (contentPart.type) {
392
- case 'text':
393
- return { text: contentPart.text };
394
- case 'file': {
395
- if (
396
- contentPart.data.type !== 'data' &&
397
- (contentPart.data.type !== 'url' ||
398
- contentPart.data.url.protocol !== 's3:')
399
- ) {
400
- throw new UnsupportedFunctionalityError({
401
- functionality: `tool result file data of type "${contentPart.data.type}"`,
402
- });
403
- }
404
-
405
- const fullMediaType = resolveFullMediaType({
406
- part: contentPart,
407
- });
408
-
409
- switch (getTopLevelMediaType(fullMediaType)) {
410
- case 'image': {
411
- return {
412
- image: {
413
- format:
414
- getAmazonBedrockImageFormat(
415
- fullMediaType,
416
- ),
417
- source: getAmazonBedrockMediaSource({
418
- data: contentPart.data,
419
- functionality: `tool result file data of type "${contentPart.data.type}"`,
420
- }),
421
- },
422
- };
423
- }
424
- case 'video': {
425
- return {
426
- video: {
427
- format:
428
- getAmazonBedrockVideoFormat(
429
- fullMediaType,
430
- ),
431
- source: getAmazonBedrockMediaSource({
432
- data: contentPart.data,
433
- functionality: `tool result file data of type "${contentPart.data.type}"`,
434
- }),
435
- },
436
- };
437
- }
438
- default: {
439
- if (contentPart.data.type !== 'data') {
440
- throw new UnsupportedFunctionalityError({
441
- functionality: `tool result file data of type "${contentPart.data.type}"`,
442
- });
443
- }
444
-
445
- const enableCitations =
446
- await shouldEnableCitations(
447
- contentPart.providerOptions,
448
- );
449
-
450
- return {
451
- document: {
452
- format:
453
- getAmazonBedrockDocumentFormat(
454
- fullMediaType,
455
- ),
456
- name: contentPart.filename
457
- ? stripFileExtension(contentPart.filename)
458
- : generateDocumentName(),
459
- source: {
460
- bytes: convertToBase64(
461
- contentPart.data.data,
462
- ),
463
- },
464
- ...(enableCitations && {
465
- citations: { enabled: true },
466
- }),
467
- },
468
- };
469
- }
470
- }
471
- }
472
- default: {
473
- throw new UnsupportedFunctionalityError({
474
- functionality: `unsupported tool content part type: ${contentPart.type}`,
475
- });
476
- }
477
- }
478
- }),
479
- );
480
- break;
481
- }
482
- case 'text':
483
- case 'error-text':
484
- toolResultContent = [{ text: output.value }];
485
- break;
486
- case 'execution-denied':
487
- toolResultContent = [
488
- { text: output.reason ?? 'Tool call execution denied.' },
489
- ];
490
- break;
491
- case 'json':
492
- case 'error-json':
493
- default:
494
- toolResultContent = [
495
- { text: JSON.stringify(output.value) },
496
- ];
497
- break;
498
- }
499
393
 
500
394
  amazonBedrockContent.push({
501
395
  toolResult: {
502
396
  toolUseId: normalizeToolCallId(part.toolCallId, isMistral),
503
- content: toolResultContent,
397
+ content: await convertToolResultOutput({
398
+ output: part.output,
399
+ getDocumentName,
400
+ }),
504
401
  },
505
402
  });
506
403
  pushCachePoint(amazonBedrockContent, part.providerOptions);
@@ -517,15 +414,28 @@ export async function convertToAmazonBedrockChatMessages(
517
414
  pushCachePoint(amazonBedrockContent, providerOptions);
518
415
  }
519
416
 
520
- messages.push({ role: 'user', content: amazonBedrockContent });
417
+ appendToUserMessage(messages, amazonBedrockContent);
521
418
 
522
419
  break;
523
420
  }
524
421
 
525
422
  case 'assistant': {
526
- // combines multiple assistant messages in this block into a single message:
527
- const amazonBedrockContent: AmazonBedrockAssistantMessage['content'] =
528
- [];
423
+ let assistantContent: AmazonBedrockAssistantMessage['content'] = [];
424
+ let toolResultContent: AmazonBedrockUserMessage['content'] = [];
425
+
426
+ const flushAssistantContent = () => {
427
+ if (assistantContent.some(block => !('cachePoint' in block))) {
428
+ messages.push({ role: 'assistant', content: assistantContent });
429
+ }
430
+ assistantContent = [];
431
+ };
432
+
433
+ const flushToolResultContent = () => {
434
+ if (toolResultContent.length > 0) {
435
+ appendToUserMessage(messages, toolResultContent);
436
+ toolResultContent = [];
437
+ }
438
+ };
529
439
 
530
440
  for (let j = 0; j < block.messages.length; j++) {
531
441
  const message = block.messages[j];
@@ -539,6 +449,10 @@ export async function convertToAmazonBedrockChatMessages(
539
449
  const part = content[k];
540
450
  const isLastContentPart = k === content.length - 1;
541
451
 
452
+ if (part.type !== 'tool-result') {
453
+ flushToolResultContent();
454
+ }
455
+
542
456
  switch (part.type) {
543
457
  case 'text': {
544
458
  // Skip empty text blocks unless reasoning blocks are present
@@ -546,7 +460,7 @@ export async function convertToAmazonBedrockChatMessages(
546
460
  break;
547
461
  }
548
462
 
549
- amazonBedrockContent.push({
463
+ assistantContent.push({
550
464
  text:
551
465
  // trim the last text part if it's the last message in the block
552
466
  // because Bedrock does not allow trailing whitespace
@@ -577,7 +491,7 @@ export async function convertToAmazonBedrockChatMessages(
577
491
  if (reasoningMetadata?.signature != null) {
578
492
  // do not trim reasoning text when a signature is present:
579
493
  // the signature validates the exact original bytes
580
- amazonBedrockContent.push({
494
+ assistantContent.push({
581
495
  reasoningContent: {
582
496
  reasoningText: {
583
497
  text: part.text,
@@ -586,13 +500,13 @@ export async function convertToAmazonBedrockChatMessages(
586
500
  },
587
501
  });
588
502
  } else if (reasoningMetadata?.redactedContent != null) {
589
- amazonBedrockContent.push({
503
+ assistantContent.push({
590
504
  reasoningContent: {
591
505
  redactedContent: reasoningMetadata.redactedContent,
592
506
  },
593
507
  });
594
508
  } else if (reasoningMetadata?.redactedData != null) {
595
- amazonBedrockContent.push({
509
+ assistantContent.push({
596
510
  reasoningContent: {
597
511
  redactedReasoning: {
598
512
  data: reasoningMetadata.redactedData,
@@ -608,7 +522,7 @@ export async function convertToAmazonBedrockChatMessages(
608
522
  }
609
523
 
610
524
  case 'tool-call': {
611
- amazonBedrockContent.push({
525
+ assistantContent.push({
612
526
  toolUse: {
613
527
  toolUseId: normalizeToolCallId(part.toolCallId, isMistral),
614
528
  name: sanitizeToolName(part.toolName),
@@ -617,17 +531,41 @@ export async function convertToAmazonBedrockChatMessages(
617
531
  });
618
532
  break;
619
533
  }
534
+
535
+ case 'tool-result': {
536
+ flushAssistantContent();
537
+ toolResultContent.push({
538
+ toolResult: {
539
+ toolUseId: normalizeToolCallId(part.toolCallId, isMistral),
540
+ content: await convertToolResultOutput({
541
+ output: part.output,
542
+ getDocumentName,
543
+ }),
544
+ },
545
+ });
546
+ break;
547
+ }
620
548
  }
621
549
 
622
- pushCachePoint(amazonBedrockContent, part.providerOptions);
550
+ pushCachePoint(
551
+ part.type === 'tool-result'
552
+ ? toolResultContent
553
+ : assistantContent,
554
+ part.providerOptions,
555
+ );
623
556
  }
624
- pushCachePoint(amazonBedrockContent, message.providerOptions);
625
- }
626
557
 
627
- if (amazonBedrockContent.some(block => !('cachePoint' in block))) {
628
- messages.push({ role: 'assistant', content: amazonBedrockContent });
558
+ pushCachePoint(
559
+ content[content.length - 1]?.type === 'tool-result'
560
+ ? toolResultContent
561
+ : assistantContent,
562
+ message.providerOptions,
563
+ );
629
564
  }
630
565
 
566
+ flushToolResultContent();
567
+ flushAssistantContent();
568
+
631
569
  break;
632
570
  }
633
571
 
@@ -641,6 +579,118 @@ export async function convertToAmazonBedrockChatMessages(
641
579
  return { system, messages };
642
580
  }
643
581
 
582
+ // A trailing assistant tool result and the following user content must share a
583
+ // single Bedrock user message so that conversation roles continue to alternate.
584
+ function appendToUserMessage(
585
+ messages: AmazonBedrockMessages,
586
+ content: AmazonBedrockUserMessage['content'],
587
+ ) {
588
+ const lastMessage = messages[messages.length - 1];
589
+
590
+ if (
591
+ lastMessage?.role === 'user' &&
592
+ lastMessage.content.some(block => 'toolResult' in block)
593
+ ) {
594
+ lastMessage.content.push(...content);
595
+ } else {
596
+ messages.push({ role: 'user', content });
597
+ }
598
+ }
599
+
600
+ async function convertToolResultOutput({
601
+ output,
602
+ getDocumentName,
603
+ }: {
604
+ output: LanguageModelV4ToolResultPart['output'];
605
+ getDocumentName: (filename: string | undefined) => string;
606
+ }): Promise<AmazonBedrockToolResultBlock['toolResult']['content']> {
607
+ switch (output.type) {
608
+ case 'content': {
609
+ return Promise.all(
610
+ output.value.map(async contentPart => {
611
+ switch (contentPart.type) {
612
+ case 'text':
613
+ return { text: contentPart.text };
614
+ case 'file': {
615
+ if (
616
+ contentPart.data.type !== 'data' &&
617
+ (contentPart.data.type !== 'url' ||
618
+ contentPart.data.url.protocol !== 's3:')
619
+ ) {
620
+ throw new UnsupportedFunctionalityError({
621
+ functionality: `tool result file data of type "${contentPart.data.type}"`,
622
+ });
623
+ }
624
+
625
+ const fullMediaType = resolveFullMediaType({ part: contentPart });
626
+
627
+ switch (getTopLevelMediaType(fullMediaType)) {
628
+ case 'image':
629
+ return {
630
+ image: {
631
+ format: getAmazonBedrockImageFormat(fullMediaType),
632
+ source: getAmazonBedrockMediaSource({
633
+ data: contentPart.data,
634
+ functionality: `tool result file data of type "${contentPart.data.type}"`,
635
+ }),
636
+ },
637
+ };
638
+ case 'video':
639
+ return {
640
+ video: {
641
+ format: getAmazonBedrockVideoFormat(fullMediaType),
642
+ source: getAmazonBedrockMediaSource({
643
+ data: contentPart.data,
644
+ functionality: `tool result file data of type "${contentPart.data.type}"`,
645
+ }),
646
+ },
647
+ };
648
+ default: {
649
+ if (contentPart.data.type !== 'data') {
650
+ throw new UnsupportedFunctionalityError({
651
+ functionality: `tool result file data of type "${contentPart.data.type}"`,
652
+ });
653
+ }
654
+
655
+ const enableCitations = await shouldEnableCitations(
656
+ contentPart.providerOptions,
657
+ );
658
+
659
+ return {
660
+ document: {
661
+ format: getAmazonBedrockDocumentFormat(fullMediaType),
662
+ name: getDocumentName(contentPart.filename),
663
+ source: {
664
+ bytes: convertToBase64(contentPart.data.data),
665
+ },
666
+ ...(enableCitations && {
667
+ citations: { enabled: true },
668
+ }),
669
+ },
670
+ };
671
+ }
672
+ }
673
+ }
674
+ default:
675
+ throw new UnsupportedFunctionalityError({
676
+ functionality: `unsupported tool content part type: ${contentPart.type}`,
677
+ });
678
+ }
679
+ }),
680
+ );
681
+ }
682
+ case 'text':
683
+ case 'error-text':
684
+ return [{ text: output.value }];
685
+ case 'execution-denied':
686
+ return [{ text: output.reason ?? 'Tool call execution denied.' }];
687
+ case 'json':
688
+ case 'error-json':
689
+ default:
690
+ return [{ text: JSON.stringify(output.value) }];
691
+ }
692
+ }
693
+
644
694
  // wrap invalid tool call input because Bedrock requires it to be an object
645
695
  function toBedrockToolInput(input: unknown): JSONObject {
646
696
  return typeof input === 'object' && input !== null && !Array.isArray(input)