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
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
115 changes: 115 additions & 0 deletions apps/sim/providers/bedrock/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* @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().mockReturnValue({
tools: [],
toolChoice: 'auto',
forcedTools: [],
}),
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',
})
})
})
34 changes: 19 additions & 15 deletions apps/sim/providers/bedrock/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type Message as BedrockMessage,
BedrockRuntimeClient,
type BedrockRuntimeClientConfig,
type ContentBlock,
type ConversationRole,
ConverseCommand,
Expand Down Expand Up @@ -50,14 +51,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 +60,24 @@ 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: BedrockRuntimeClientConfig = { 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