From af5594e0d8c48f69bbce1474c80594f843f0399f Mon Sep 17 00:00:00 2001 From: MarkXian Date: Thu, 9 Jul 2026 14:30:28 +0800 Subject: [PATCH] fix(amazon-bedrock): support inference profile ARNs as model ids (#14117) The provider built REST URLs with `encodeURIComponent(modelId)`, which encodes the `/` in an application inference profile ARN (e.g. `arn:aws:bedrock:...:application-inference-profile/abc123`) to `%2F`, making Bedrock reject it with `400 The provided model identifier is invalid`. Add a shared `encodeModelId` helper that encodes each `/`-separated segment individually, keeping slashes literal while still percent- encoding other characters (e.g. the `:` in an ARN). Use it in the chat, embedding, image, and Bedrock-Anthropic URL builders. Standard slash-free model ids encode identically to before, so existing behavior is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/bedrock-inference-profile-arn.md | 5 ++++ .../src/amazon-bedrock-chat-language-model.ts | 3 +- .../src/amazon-bedrock-embedding-model.ts | 3 +- .../amazon-bedrock-encode-model-id.test.ts | 30 +++++++++++++++++++ .../src/amazon-bedrock-encode-model-id.ts | 19 ++++++++++++ .../src/amazon-bedrock-image-model.ts | 3 +- .../amazon-bedrock-anthropic-provider.ts | 3 +- 7 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 .changeset/bedrock-inference-profile-arn.md create mode 100644 packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.test.ts create mode 100644 packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.ts diff --git a/.changeset/bedrock-inference-profile-arn.md b/.changeset/bedrock-inference-profile-arn.md new file mode 100644 index 000000000000..c063891537fc --- /dev/null +++ b/.changeset/bedrock-inference-profile-arn.md @@ -0,0 +1,5 @@ +--- +"@ai-sdk/amazon-bedrock": patch +--- + +fix(amazon-bedrock): support inference profile ARNs as model ids. The provider built request URLs with `encodeURIComponent(modelId)`, which encodes the `/` in an application inference profile ARN (e.g. `arn:aws:bedrock:...:application-inference-profile/abc123`) to `%2F` and made Bedrock reject it with `400 The provided model identifier is invalid`. Model ids are now encoded per path segment so slashes stay literal, matching the path format Bedrock expects. diff --git a/packages/amazon-bedrock/src/amazon-bedrock-chat-language-model.ts b/packages/amazon-bedrock/src/amazon-bedrock-chat-language-model.ts index 9f340378c43a..a8a9aaafeaf0 100644 --- a/packages/amazon-bedrock/src/amazon-bedrock-chat-language-model.ts +++ b/packages/amazon-bedrock/src/amazon-bedrock-chat-language-model.ts @@ -41,6 +41,7 @@ import { type AmazonBedrockLanguageModelChatOptions, type AmazonBedrockChatModelId, } from './amazon-bedrock-chat-language-model-options'; +import { encodeModelId } from './amazon-bedrock-encode-model-id'; import { AmazonBedrockErrorSchema } from './amazon-bedrock-error'; import { createAmazonBedrockEventStreamResponseHandler } from './amazon-bedrock-event-stream-response-handler'; import { prepareTools } from './amazon-bedrock-prepare-tools'; @@ -1049,7 +1050,7 @@ export class AmazonBedrockChatLanguageModel implements LanguageModelV4 { } private getUrl(modelId: string) { - const encodedModelId = encodeURIComponent(modelId); + const encodedModelId = encodeModelId(modelId); return `${this.config.baseUrl()}/model/${encodedModelId}`; } } diff --git a/packages/amazon-bedrock/src/amazon-bedrock-embedding-model.ts b/packages/amazon-bedrock/src/amazon-bedrock-embedding-model.ts index 1e7642826293..7281765f5714 100644 --- a/packages/amazon-bedrock/src/amazon-bedrock-embedding-model.ts +++ b/packages/amazon-bedrock/src/amazon-bedrock-embedding-model.ts @@ -19,6 +19,7 @@ import { amazonBedrockEmbeddingModelOptionsSchema, type AmazonBedrockEmbeddingModelId, } from './amazon-bedrock-embedding-model-options'; +import { encodeModelId } from './amazon-bedrock-encode-model-id'; import { AmazonBedrockErrorSchema } from './amazon-bedrock-error'; import { z } from 'zod/v4'; @@ -59,7 +60,7 @@ export class AmazonBedrockEmbeddingModel implements EmbeddingModelV4 { ) {} private getUrl(modelId: string): string { - const encodedModelId = encodeURIComponent(modelId); + const encodedModelId = encodeModelId(modelId); return `${this.config.baseUrl()}/model/${encodedModelId}/invoke`; } diff --git a/packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.test.ts b/packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.test.ts new file mode 100644 index 000000000000..d3dd3f56050c --- /dev/null +++ b/packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import { encodeModelId } from './amazon-bedrock-encode-model-id'; + +describe('encodeModelId', () => { + it('encodes a standard model id (colon percent-encoded, no slashes)', () => { + expect(encodeModelId('us.amazon.nova-2-lite-v1:0')).toBe( + 'us.amazon.nova-2-lite-v1%3A0', + ); + }); + + it('leaves a plain model id without special characters unchanged', () => { + expect(encodeModelId('anthropic.claude-3-haiku-20240307-v1:0')).toBe( + 'anthropic.claude-3-haiku-20240307-v1%3A0', + ); + }); + + it('preserves the slash in an application inference profile ARN', () => { + expect( + encodeModelId( + 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123xyz', + ), + ).toBe( + 'arn%3Aaws%3Abedrock%3Aus-east-1%3A123456789012%3Aapplication-inference-profile/abc123xyz', + ); + }); + + it('encodes each segment of a multi-slash path individually', () => { + expect(encodeModelId('a b/c:d/e')).toBe('a%20b/c%3Ad/e'); + }); +}); diff --git a/packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.ts b/packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.ts new file mode 100644 index 000000000000..7015b6be7ea6 --- /dev/null +++ b/packages/amazon-bedrock/src/amazon-bedrock-encode-model-id.ts @@ -0,0 +1,19 @@ +/** + * Encodes a Bedrock model identifier for use in a REST API URL path. + * + * Standard model ids (e.g. `us.amazon.nova-2-lite-v1:0`) contain no slashes and + * are encoded as a single segment. Inference profile ARNs, however, contain a + * slash (e.g. + * `arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123`). + * Bedrock's REST API treats the model id as a greedy path label and expects the + * slash to stay literal — encoding it to `%2F` (as a plain `encodeURIComponent` + * would) makes Bedrock reject the request with + * `400 The provided model identifier is invalid`. + * + * Encoding each `/`-separated segment individually keeps slashes literal while + * still percent-encoding every other special character (such as the `:` in an + * ARN), matching the path format Bedrock documents for inference profiles. + */ +export function encodeModelId(modelId: string): string { + return modelId.split('/').map(encodeURIComponent).join('/'); +} diff --git a/packages/amazon-bedrock/src/amazon-bedrock-image-model.ts b/packages/amazon-bedrock/src/amazon-bedrock-image-model.ts index 75f3b19ccbde..5f0e5f22950c 100644 --- a/packages/amazon-bedrock/src/amazon-bedrock-image-model.ts +++ b/packages/amazon-bedrock/src/amazon-bedrock-image-model.ts @@ -22,6 +22,7 @@ import { type AmazonBedrockImageModelId, } from './amazon-bedrock-image-settings'; import { amazonBedrockImageModelOptionsSchema } from './amazon-bedrock-image-model-options'; +import { encodeModelId } from './amazon-bedrock-encode-model-id'; import { AmazonBedrockErrorSchema } from './amazon-bedrock-error'; import { z } from 'zod/v4'; @@ -57,7 +58,7 @@ export class AmazonBedrockImageModel implements ImageModelV4 { } private getUrl(modelId: string): string { - const encodedModelId = encodeURIComponent(modelId); + const encodedModelId = encodeModelId(modelId); return `${this.config.baseUrl()}/model/${encodedModelId}/invoke`; } diff --git a/packages/amazon-bedrock/src/anthropic/amazon-bedrock-anthropic-provider.ts b/packages/amazon-bedrock/src/anthropic/amazon-bedrock-anthropic-provider.ts index d799e17cdb74..f2165566717c 100644 --- a/packages/amazon-bedrock/src/anthropic/amazon-bedrock-anthropic-provider.ts +++ b/packages/amazon-bedrock/src/anthropic/amazon-bedrock-anthropic-provider.ts @@ -21,6 +21,7 @@ import { createSigV4FetchFunction, type AmazonBedrockCredentials, } from '../amazon-bedrock-sigv4-fetch'; +import { encodeModelId } from '../amazon-bedrock-encode-model-id'; import { createAmazonBedrockAnthropicFetch } from './amazon-bedrock-anthropic-fetch'; import type { AmazonBedrockAnthropicModelId } from './amazon-bedrock-anthropic-options'; import { VERSION } from '../version'; @@ -258,7 +259,7 @@ export function createAmazonBedrockAnthropic( fetch: fetchFunction, buildRequestUrl: (baseURL, isStreaming) => - `${baseURL}/model/${encodeURIComponent(modelId)}/${ + `${baseURL}/model/${encodeModelId(modelId)}/${ isStreaming ? 'invoke-with-response-stream' : 'invoke' }`,