Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bedrock-image-model-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ai-sdk/amazon-bedrock': patch
---

Add `AmazonBedrockImageModelOptions` type and validate image generation provider options using Zod schema with `parseProviderOptions`.
3 changes: 2 additions & 1 deletion examples/ai-functions/src/generate-image/amazon-bedrock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { bedrock } from '@ai-sdk/amazon-bedrock';
import type { AmazonBedrockImageModelOptions } from '@ai-sdk/amazon-bedrock';
import { generateImage } from 'ai';
import { presentImages } from '../lib/present-image';
import { run } from '../lib/run';
Expand All @@ -13,7 +14,7 @@ run(async () => {
providerOptions: {
bedrock: {
quality: 'premium',
},
} satisfies AmazonBedrockImageModelOptions,
},
});

Expand Down
88 changes: 46 additions & 42 deletions packages/amazon-bedrock/src/bedrock-image-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {
convertUint8ArrayToBase64,
createJsonErrorResponseHandler,
createJsonResponseHandler,
parseProviderOptions,
postJsonToApi,
resolve,
} from '@ai-sdk/provider-utils';
import {
BedrockImageModelId,
amazonBedrockImageModelOptions,
modelMaxImagesPerCall,
} from './bedrock-image-settings';
import { BedrockErrorSchema } from './bedrock-error';
Expand Down Expand Up @@ -64,32 +66,38 @@ export class BedrockImageModel implements ImageModelV3 {
const warnings: Array<SharedV3Warning> = [];
const [width, height] = size ? size.split('x').map(Number) : [];

const bedrockOptions = await parseProviderOptions({
provider: 'bedrock',
providerOptions,
schema: amazonBedrockImageModelOptions,
});

const hasFiles = files != null && files.length > 0;

// Build image generation config (common to most modes)
const imageGenerationConfig = {
const imageGenerationConfig: Record<string, unknown> = {
...(width ? { width } : {}),
...(height ? { height } : {}),
...(seed ? { seed } : {}),
...(n ? { numberOfImages: n } : {}),
...(providerOptions?.bedrock?.quality
? { quality: providerOptions.bedrock.quality }
: {}),
...(providerOptions?.bedrock?.cfgScale
? { cfgScale: providerOptions.bedrock.cfgScale }
: {}),
};
if (bedrockOptions?.quality != null) {
imageGenerationConfig.quality = bedrockOptions.quality;
}
if (bedrockOptions?.cfgScale != null) {
imageGenerationConfig.cfgScale = bedrockOptions.cfgScale;
}

let args: Record<string, unknown>;

if (hasFiles) {
// Check if mask is actually provided (has valid data, not just an empty object)
const hasMask = mask?.type != null;
const hasMaskPrompt = providerOptions?.bedrock?.maskPrompt != null;
const hasMaskPrompt = bedrockOptions?.maskPrompt != null;

// Determine task type from provider options, or infer from mask presence
const taskType =
providerOptions?.bedrock?.taskType ??
bedrockOptions?.taskType ??
(hasMask || hasMaskPrompt ? 'INPAINTING' : 'IMAGE_VARIATION');

const sourceImageBase64 = getBase64Data(files[0]);
Expand All @@ -99,16 +107,16 @@ export class BedrockImageModel implements ImageModelV3 {
const inPaintingParams: Record<string, unknown> = {
image: sourceImageBase64,
...(prompt ? { text: prompt } : {}),
...(providerOptions?.bedrock?.negativeText
? { negativeText: providerOptions.bedrock.negativeText }
: {}),
};
if (bedrockOptions?.negativeText != null) {
inPaintingParams.negativeText = bedrockOptions.negativeText;
}

// Handle mask - can be either a maskImage or maskPrompt
if (hasMask) {
inPaintingParams.maskImage = getBase64Data(mask);
} else if (hasMaskPrompt) {
inPaintingParams.maskPrompt = providerOptions.bedrock.maskPrompt;
inPaintingParams.maskPrompt = bedrockOptions.maskPrompt;
}

args = {
Expand All @@ -123,19 +131,19 @@ export class BedrockImageModel implements ImageModelV3 {
const outPaintingParams: Record<string, unknown> = {
image: sourceImageBase64,
...(prompt ? { text: prompt } : {}),
...(providerOptions?.bedrock?.negativeText
? { negativeText: providerOptions.bedrock.negativeText }
: {}),
...(providerOptions?.bedrock?.outPaintingMode
? { outPaintingMode: providerOptions.bedrock.outPaintingMode }
: {}),
};
if (bedrockOptions?.negativeText != null) {
outPaintingParams.negativeText = bedrockOptions.negativeText;
}
if (bedrockOptions?.outPaintingMode != null) {
outPaintingParams.outPaintingMode = bedrockOptions.outPaintingMode;
}

// Outpainting requires a maskImage (white pixels = area to change)
if (hasMask) {
outPaintingParams.maskImage = getBase64Data(mask);
} else if (hasMaskPrompt) {
outPaintingParams.maskPrompt = providerOptions.bedrock.maskPrompt;
outPaintingParams.maskPrompt = bedrockOptions.maskPrompt;
}

args = {
Expand Down Expand Up @@ -164,16 +172,14 @@ export class BedrockImageModel implements ImageModelV3 {
const imageVariationParams: Record<string, unknown> = {
images,
...(prompt ? { text: prompt } : {}),
...(providerOptions?.bedrock?.negativeText
? { negativeText: providerOptions.bedrock.negativeText }
: {}),
...(providerOptions?.bedrock?.similarityStrength != null
? {
similarityStrength:
providerOptions.bedrock.similarityStrength,
}
: {}),
};
if (bedrockOptions?.negativeText != null) {
imageVariationParams.negativeText = bedrockOptions.negativeText;
}
if (bedrockOptions?.similarityStrength != null) {
imageVariationParams.similarityStrength =
bedrockOptions.similarityStrength;
}

args = {
taskType: 'IMAGE_VARIATION',
Expand All @@ -188,21 +194,19 @@ export class BedrockImageModel implements ImageModelV3 {
}
} else {
// Standard image generation mode
const textToImageParams: Record<string, unknown> = {
text: prompt,
};
if (bedrockOptions?.negativeText != null) {
textToImageParams.negativeText = bedrockOptions.negativeText;
}
if (bedrockOptions?.style != null) {
textToImageParams.style = bedrockOptions.style;
}

args = {
taskType: 'TEXT_IMAGE',
textToImageParams: {
text: prompt,
...(providerOptions?.bedrock?.negativeText
? {
negativeText: providerOptions.bedrock.negativeText,
}
: {}),
...(providerOptions?.bedrock?.style
? {
style: providerOptions.bedrock.style,
}
: {}),
},
textToImageParams,
imageGenerationConfig,
};
}
Expand Down
18 changes: 18 additions & 0 deletions packages/amazon-bedrock/src/bedrock-image-settings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import { z } from 'zod/v4';

export type BedrockImageModelId = 'amazon.nova-canvas-v1:0' | (string & {});

// https://docs.aws.amazon.com/nova/latest/userguide/image-gen-req-resp-structure.html
export const modelMaxImagesPerCall: Record<BedrockImageModelId, number> = {
'amazon.nova-canvas-v1:0': 5,
};

// https://docs.aws.amazon.com/nova/latest/userguide/image-gen-req-resp-structure.html
export const amazonBedrockImageModelOptions = z.object({
quality: z.string().optional(),
cfgScale: z.number().optional(),
negativeText: z.string().optional(),
style: z.string().optional(),
taskType: z.string().optional(),
maskPrompt: z.string().optional(),
outPaintingMode: z.enum(['DEFAULT', 'PRECISE']).optional(),
similarityStrength: z.number().optional(),
});

export type AmazonBedrockImageModelOptions = z.infer<
typeof amazonBedrockImageModelOptions
>;
1 change: 1 addition & 0 deletions packages/amazon-bedrock/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export type {
/** @deprecated Use `AmazonBedrockRerankingModelOptions` instead. */
AmazonBedrockRerankingModelOptions as BedrockRerankingOptions,
} from './reranking/bedrock-reranking-options';
export type { AmazonBedrockImageModelOptions } from './bedrock-image-settings';
export { VERSION } from './version';