Skip to content

Commit cb5fa8a

Browse files
committed
fix(bedrock): support model names with slashes
1 parent f0378ec commit cb5fa8a

File tree

6 files changed

+287
-10
lines changed

6 files changed

+287
-10
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env -S npm run tsn -T
2+
/**
3+
* This example demonstrates using ARNs with slashes in the Bedrock SDK
4+
*
5+
* Usage:
6+
* - Set your AWS credentials and region via environment variables
7+
* - Run with: ts-node arn-demo.ts
8+
*/
9+
10+
import { AnthropicBedrock } from '../src';
11+
12+
async function main() {
13+
// Create an AnthropicBedrock client
14+
const anthropic = new AnthropicBedrock({
15+
// AWS credentials can be provided directly or via environment variables
16+
// awsAccessKey: 'YOUR_AWS_ACCESS_KEY',
17+
// awsSecretKey: 'YOUR_AWS_SECRET_KEY',
18+
// awsRegion: 'us-east-1', // Set your AWS region
19+
});
20+
21+
// Example ARN with slashes - this now works correctly with URL encoding
22+
const model =
23+
'arn:aws:bedrock:us-east-2:1234:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0';
24+
25+
console.log(`Using model: ${model}`);
26+
27+
try {
28+
const response = await anthropic.messages['create']({
29+
model,
30+
max_tokens: 1000,
31+
messages: [
32+
{
33+
role: 'user',
34+
content: 'Hello! How does the Bedrock SDK handle model ARNs?',
35+
},
36+
],
37+
});
38+
39+
console.log('Response:', JSON.stringify(response, null, 2));
40+
} catch (error) {
41+
console.error('Error:', error);
42+
}
43+
}
44+
45+
main().catch(console.error);

packages/bedrock-sdk/jest.config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { JestConfigWithTsJest } from 'ts-jest';
2+
3+
const config: JestConfigWithTsJest = {
4+
preset: 'ts-jest/presets/default-esm',
5+
testEnvironment: 'node',
6+
transform: {
7+
'^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }],
8+
},
9+
moduleNameMapper: {
10+
'^@anthropic-ai/bedrock-sdk$': '<rootDir>/src/index.ts',
11+
'^@anthropic-ai/bedrock-sdk/(.*)$': '<rootDir>/src/$1',
12+
},
13+
modulePathIgnorePatterns: ['<rootDir>/dist/', '<rootDir>/deno/'],
14+
testPathIgnorePatterns: ['scripts'],
15+
};
16+
17+
export default config;

packages/bedrock-sdk/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"packageManager": "[email protected]",
1212
"private": false,
1313
"scripts": {
14-
"test": "echo 'no tests defined yet' && exit 1",
14+
"test": "jest",
1515
"build": "bash ./build",
1616
"prepack": "echo 'to pack, run yarn build && (cd dist; yarn pack)' && exit 1",
1717
"prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1",
@@ -35,6 +35,8 @@
3535
"@smithy/util-base64": "^2.0.0"
3636
},
3737
"devDependencies": {
38+
"@swc/core": "^1.3.101",
39+
"@swc/jest": "^0.2.29",
3840
"@types/node": "^20.17.6",
3941
"@types/jest": "^29.4.0",
4042
"@typescript-eslint/eslint-plugin": "^6.7.0",

packages/bedrock-sdk/src/client.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,19 @@ export class AnthropicBedrock extends BaseAnthropic {
133133
throw new Error('Expected request body to be an object for post /v1/messages');
134134
}
135135

136-
const model = options.body['model'];
136+
const model = options.body['model'] as string;
137137
options.body['model'] = undefined;
138138

139139
const stream = options.body['stream'];
140140
options.body['stream'] = undefined;
141141

142+
// Encode model name to handle ARNs with slashes (e.g., inference-profile/name)
143+
const encodedModel = encodeURIComponent(model);
144+
142145
if (stream) {
143-
options.path = `/model/${model}/invoke-with-response-stream`;
146+
options.path = `/model/${encodedModel}/invoke-with-response-stream`;
144147
} else {
145-
options.path = `/model/${model}/invoke`;
148+
options.path = `/model/${encodedModel}/invoke`;
146149
}
147150
}
148151

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Mock the client to allow for a more integration-style test
2+
// We're mocking specific parts of the AnthropicBedrock client to avoid
3+
// dependencies while still testing the integration behavior
4+
5+
// Mock specific parts of the client
6+
jest.mock('../src/core/auth', () => ({
7+
getAuthHeaders: jest.fn().mockResolvedValue({}),
8+
}));
9+
10+
// Create a mock fetch function
11+
const mockFetch = jest.fn().mockImplementation(() => {
12+
return Promise.resolve({
13+
ok: true,
14+
status: 200,
15+
statusText: 'OK',
16+
headers: new Headers({ 'content-type': 'application/json' }),
17+
json: () => Promise.resolve({}),
18+
text: () => Promise.resolve('{}'),
19+
});
20+
});
21+
22+
// Store original fetch function
23+
const originalFetch = global.fetch;
24+
25+
describe('Bedrock model ARN URL encoding integration test', () => {
26+
beforeEach(() => {
27+
// Replace global fetch with our mock
28+
global.fetch = mockFetch;
29+
// Clear mock history
30+
mockFetch.mockClear();
31+
});
32+
33+
afterEach(() => {
34+
// Restore original fetch
35+
global.fetch = originalFetch;
36+
});
37+
38+
test('properly encodes model ARNs with slashes in URL path', async () => {
39+
// Import the client - do this inside the test to ensure mocks are set up first
40+
const { AnthropicBedrock } = require('../src');
41+
42+
// Create client instance
43+
const client = new AnthropicBedrock({
44+
awsRegion: 'us-east-1',
45+
baseURL: 'http://localhost:4010',
46+
});
47+
48+
// Model ARN with slashes that needs encoding
49+
const modelArn =
50+
'arn:aws:bedrock:us-east-2:1234:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0';
51+
52+
// Make a request to trigger the URL construction with the ARN
53+
try {
54+
await client.messages.create({
55+
model: modelArn,
56+
max_tokens: 1024,
57+
messages: [{ content: 'Test message', role: 'user' }],
58+
});
59+
} catch (e) {
60+
// We expect errors due to mocking - we're just interested in the URL construction
61+
}
62+
63+
// Verify that fetch was called
64+
expect(mockFetch).toHaveBeenCalled();
65+
66+
// Get the URL that was passed to fetch
67+
const fetchUrl = mockFetch.mock.calls[0][0];
68+
69+
// Expected URL with properly encoded ARN (slash encoded as %2F)
70+
const expectedUrl =
71+
'http://localhost:4010/model/arn%3Aaws%3Abedrock%3Aus-east-2%3A1234%3Ainference-profile%2Fus.anthropic.claude-3-7-sonnet-20250219-v1%3A0/invoke';
72+
73+
// Verify the exact URL matches what we expect
74+
expect(fetchUrl).toBe(expectedUrl);
75+
});
76+
77+
test('properly constructs URL path for normal model names', async () => {
78+
// Import the client - do this inside the test to ensure mocks are set up first
79+
const { AnthropicBedrock } = require('../src');
80+
81+
// Create client instance
82+
const client = new AnthropicBedrock({
83+
awsRegion: 'us-east-1',
84+
baseURL: 'http://localhost:4010',
85+
});
86+
87+
// Regular model name (still contains characters that need encoding)
88+
const modelName = 'anthropic.claude-3-sonnet-20240229-v1:0';
89+
90+
// Make a request to trigger the URL construction
91+
try {
92+
await client.messages.create({
93+
model: modelName,
94+
max_tokens: 1024,
95+
messages: [{ content: 'Test message', role: 'user' }],
96+
});
97+
} catch (e) {
98+
// We expect errors due to mocking - we're just interested in the URL construction
99+
}
100+
101+
// Verify that fetch was called
102+
expect(mockFetch).toHaveBeenCalled();
103+
104+
// Get the URL that was passed to fetch
105+
const fetchUrl = mockFetch.mock.calls[0][0];
106+
107+
// Expected URL with properly encoded model name
108+
const expectedUrl = 'http://localhost:4010/model/anthropic.claude-3-sonnet-20240229-v1%3A0/invoke';
109+
110+
// Verify the exact URL matches what we expect
111+
expect(fetchUrl).toBe(expectedUrl);
112+
});
113+
});

packages/bedrock-sdk/yarn.lock

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,13 @@
990990
slash "^3.0.0"
991991
strip-ansi "^6.0.0"
992992

993+
"@jest/create-cache-key-function@^29.7.0":
994+
version "29.7.0"
995+
resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0"
996+
integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==
997+
dependencies:
998+
"@jest/types" "^29.6.3"
999+
9931000
"@jest/environment@^29.7.0":
9941001
version "29.7.0"
9951002
resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7"
@@ -1954,6 +1961,96 @@
19541961
"@smithy/util-buffer-from" "^4.0.0"
19551962
tslib "^2.6.2"
19561963

1964+
1965+
version "1.11.24"
1966+
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.24.tgz#c9fcc9c4bad0511fed26210449556d2b33fb2d9a"
1967+
integrity sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==
1968+
1969+
1970+
version "1.11.24"
1971+
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.11.24.tgz#048ea3ee43281264a62fccb5a944b76d1c56eb24"
1972+
integrity sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==
1973+
1974+
1975+
version "1.11.24"
1976+
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.24.tgz#f01ba657a81c67d8fb9f681712e65abf1324cec6"
1977+
integrity sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==
1978+
1979+
1980+
version "1.11.24"
1981+
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.24.tgz#9aefca7f7f87c8312c2fa714c1eb731411d8596c"
1982+
integrity sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==
1983+
1984+
1985+
version "1.11.24"
1986+
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.24.tgz#e4805484779bbc59b639eab4f8e45166f3d7a4f7"
1987+
integrity sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==
1988+
1989+
1990+
version "1.11.24"
1991+
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.24.tgz#e8d8cc50a49903880944379590b73733e150a5d4"
1992+
integrity sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==
1993+
1994+
1995+
version "1.11.24"
1996+
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.24.tgz#f3c46212eb8a793f6a42a36b2a9017a9b1462737"
1997+
integrity sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==
1998+
1999+
2000+
version "1.11.24"
2001+
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.24.tgz#b1c3327d81a5f94415ac0b1713e192df1c121fbd"
2002+
integrity sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==
2003+
2004+
2005+
version "1.11.24"
2006+
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.24.tgz#6a944dd6111ec5fae3cf5925b73701e49b109edc"
2007+
integrity sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==
2008+
2009+
2010+
version "1.11.24"
2011+
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.24.tgz#eebb5d5ece2710aeb25cc58bd7c5c4c2c046f030"
2012+
integrity sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==
2013+
2014+
"@swc/core@^1.3.101":
2015+
version "1.11.24"
2016+
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.11.24.tgz#340425648296964f815c940b8da00fcdb1ff2abd"
2017+
integrity sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==
2018+
dependencies:
2019+
"@swc/counter" "^0.1.3"
2020+
"@swc/types" "^0.1.21"
2021+
optionalDependencies:
2022+
"@swc/core-darwin-arm64" "1.11.24"
2023+
"@swc/core-darwin-x64" "1.11.24"
2024+
"@swc/core-linux-arm-gnueabihf" "1.11.24"
2025+
"@swc/core-linux-arm64-gnu" "1.11.24"
2026+
"@swc/core-linux-arm64-musl" "1.11.24"
2027+
"@swc/core-linux-x64-gnu" "1.11.24"
2028+
"@swc/core-linux-x64-musl" "1.11.24"
2029+
"@swc/core-win32-arm64-msvc" "1.11.24"
2030+
"@swc/core-win32-ia32-msvc" "1.11.24"
2031+
"@swc/core-win32-x64-msvc" "1.11.24"
2032+
2033+
"@swc/counter@^0.1.3":
2034+
version "0.1.3"
2035+
resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
2036+
integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
2037+
2038+
"@swc/jest@^0.2.29":
2039+
version "0.2.38"
2040+
resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.38.tgz#8b137e344c6c021d4e49ee2bc62b0e5e564d2c7c"
2041+
integrity sha512-HMoZgXWMqChJwffdDjvplH53g9G2ALQes3HKXDEdliB/b85OQ0CTSbxG8VSeCwiAn7cOaDVEt4mwmZvbHcS52w==
2042+
dependencies:
2043+
"@jest/create-cache-key-function" "^29.7.0"
2044+
"@swc/counter" "^0.1.3"
2045+
jsonc-parser "^3.2.0"
2046+
2047+
"@swc/types@^0.1.21":
2048+
version "0.1.21"
2049+
resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.21.tgz#6fcadbeca1d8bc89e1ab3de4948cef12344a38c0"
2050+
integrity sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==
2051+
dependencies:
2052+
"@swc/counter" "^0.1.3"
2053+
19572054
"@ts-morph/common@~0.20.0":
19582055
version "0.20.0"
19592056
resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.20.0.tgz#3f161996b085ba4519731e4d24c35f6cba5b80af"
@@ -3598,6 +3695,11 @@ json5@^2.2.2, json5@^2.2.3:
35983695
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
35993696
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
36003697

3698+
jsonc-parser@^3.2.0:
3699+
version "3.3.1"
3700+
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4"
3701+
integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==
3702+
36013703
keyv@^4.5.3:
36023704
version "4.5.4"
36033705
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -4326,12 +4428,7 @@ tslib@^1.11.1:
43264428
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
43274429
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
43284430

4329-
tslib@^2.3.1, tslib@^2.5.0, tslib@^2.6.2:
4330-
version "2.8.1"
4331-
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
4332-
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
4333-
4334-
tslib@^2.8.1:
4431+
tslib@^2.3.1, tslib@^2.5.0, tslib@^2.6.2, tslib@^2.8.1:
43354432
version "2.8.1"
43364433
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
43374434
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==

0 commit comments

Comments
 (0)