Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
8 changes: 4 additions & 4 deletions apps/sim/blocks/blocks/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,9 @@ Return ONLY the JSON array.`,
title: 'AWS Access Key ID',
type: 'short-input',
password: true,
placeholder: 'Enter your AWS Access Key ID',
placeholder: 'Optional - uses AWS default credential chain if empty',
connectionDroppable: false,
required: true,
required: false,
condition: {
field: 'model',
value: providers.bedrock.models,
Expand All @@ -380,9 +380,9 @@ Return ONLY the JSON array.`,
title: 'AWS Secret Access Key',
type: 'short-input',
password: true,
placeholder: 'Enter your AWS Secret Access Key',
placeholder: 'Optional - uses AWS default credential chain if empty',
connectionDroppable: false,
required: true,
required: false,
condition: {
field: 'model',
value: providers.bedrock.models,
Expand Down
111 changes: 111 additions & 0 deletions apps/sim/providers/bedrock/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* @vitest-environment node
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'

const mockSend = vi.fn()

vi.mock('@aws-sdk/client-bedrock-runtime', () => ({
BedrockRuntimeClient: vi.fn().mockImplementation(() => {
return { send: mockSend }
}),
ConverseCommand: vi.fn(),
ConverseStreamCommand: vi.fn(),
}))

vi.mock('@/providers/bedrock/utils', () => ({
getBedrockInferenceProfileId: vi
.fn()
.mockReturnValue('us.anthropic.claude-3-5-sonnet-20241022-v2:0'),
checkForForcedToolUsage: vi.fn(),
createReadableStreamFromBedrockStream: vi.fn(),
generateToolUseId: vi.fn().mockReturnValue('tool-1'),
}))

vi.mock('@/providers/models', () => ({
getProviderModels: vi.fn().mockReturnValue([]),
getProviderDefaultModel: vi.fn().mockReturnValue('us.anthropic.claude-3-5-sonnet-20241022-v2:0'),
}))

vi.mock('@/providers/utils', () => ({
calculateCost: vi.fn().mockReturnValue({ input: 0, output: 0, total: 0, pricing: null }),
prepareToolExecution: vi.fn(),
prepareToolsWithUsageControl: vi.fn(),
sumToolCosts: vi.fn().mockReturnValue(0),
}))

vi.mock('@/tools', () => ({
executeTool: vi.fn(),
}))

import { BedrockRuntimeClient } from '@aws-sdk/client-bedrock-runtime'
import { bedrockProvider } from '@/providers/bedrock/index'

describe('bedrockProvider credential handling', () => {
beforeEach(() => {
vi.clearAllMocks()
mockSend.mockResolvedValue({
output: { message: { content: [{ text: 'response' }] } },
usage: { inputTokens: 10, outputTokens: 5 },
})
})

const baseRequest = {
model: 'us.anthropic.claude-3-5-sonnet-20241022-v2:0',
systemPrompt: 'You are helpful.',
messages: [{ role: 'user' as const, content: 'Hello' }],
}

it('throws when only bedrockAccessKeyId is provided', async () => {
await expect(
bedrockProvider.executeRequest({
...baseRequest,
bedrockAccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
})
).rejects.toThrow('Both bedrockAccessKeyId and bedrockSecretKey must be provided together')
})

it('throws when only bedrockSecretKey is provided', async () => {
await expect(
bedrockProvider.executeRequest({
...baseRequest,
bedrockSecretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
})
).rejects.toThrow('Both bedrockAccessKeyId and bedrockSecretKey must be provided together')
})

it('creates client with explicit credentials when both are provided', async () => {
await bedrockProvider.executeRequest({
...baseRequest,
bedrockAccessKeyId: 'AKIAIOSFODNN7EXAMPLE',
bedrockSecretKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
})

expect(BedrockRuntimeClient).toHaveBeenCalledWith({
region: 'us-east-1',
credentials: {
accessKeyId: 'AKIAIOSFODNN7EXAMPLE',
secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
},
})
})

it('creates client without credentials when neither is provided', async () => {
await bedrockProvider.executeRequest(baseRequest)

expect(BedrockRuntimeClient).toHaveBeenCalledWith({
region: 'us-east-1',
})
})

it('uses custom region when provided', async () => {
await bedrockProvider.executeRequest({
...baseRequest,
bedrockRegion: 'eu-west-1',
})

expect(BedrockRuntimeClient).toHaveBeenCalledWith({
region: 'eu-west-1',
})
})
})
36 changes: 21 additions & 15 deletions apps/sim/providers/bedrock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,6 @@ export const bedrockProvider: ProviderConfig = {
executeRequest: async (
request: ProviderRequest
): Promise<ProviderResponse | StreamingExecution> => {
if (!request.bedrockAccessKeyId) {
throw new Error('AWS Access Key ID is required for Bedrock')
}

if (!request.bedrockSecretKey) {
throw new Error('AWS Secret Access Key is required for Bedrock')
}

const region = request.bedrockRegion || 'us-east-1'
const bedrockModelId = getBedrockInferenceProfileId(request.model, region)

Expand All @@ -67,13 +59,27 @@ export const bedrockProvider: ProviderConfig = {
region,
})

const client = new BedrockRuntimeClient({
region,
credentials: {
accessKeyId: request.bedrockAccessKeyId || '',
secretAccessKey: request.bedrockSecretKey || '',
},
})
const hasAccessKey = Boolean(request.bedrockAccessKeyId)
const hasSecretKey = Boolean(request.bedrockSecretKey)
if (hasAccessKey !== hasSecretKey) {
throw new Error(
'Both bedrockAccessKeyId and bedrockSecretKey must be provided together. ' +
'Provide both for explicit credentials, or omit both to use the AWS default credential chain.'
)
}

const clientConfig: {
region: string
credentials?: { accessKeyId: string; secretAccessKey: string }
} = { region }
if (request.bedrockAccessKeyId && request.bedrockSecretKey) {
clientConfig.credentials = {
accessKeyId: request.bedrockAccessKeyId,
secretAccessKey: request.bedrockSecretKey,
}
}

const client = new BedrockRuntimeClient(clientConfig)

const messages: BedrockMessage[] = []
const systemContent: SystemContentBlock[] = []
Expand Down
Loading