Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5accc1a
feat(bedrock): sign with a saved AWS credential's session when one is…
waleedlatif1 Nov 12, 2026
c1b8744
feat(bedrock): authorize and resolve a saved AWS credential for Bedro…
waleedlatif1 Nov 12, 2026
a75aaae
fix(bedrock): bind native conversation continuation to the saved AWS …
waleedlatif1 Nov 12, 2026
8086fd1
feat(bedrock): sign agent, router, and evaluator Bedrock calls with a…
waleedlatif1 Nov 12, 2026
aa2e4ff
feat(bedrock): accept a saved AWS credential in the LLM chat and guar…
waleedlatif1 Nov 12, 2026
16c1218
feat(bedrock): offer a saved AWS credential in the shared LLM model c…
waleedlatif1 Nov 12, 2026
ae0d704
test(bedrock): keep the LLM operation credential test free of type er…
waleedlatif1 Nov 12, 2026
e1d8bae
fix(bedrock): refuse a block set to a saved AWS credential whose ID r…
waleedlatif1 Nov 12, 2026
3cf2717
docs(bedrock): document signing Bedrock model calls with a saved AWS …
waleedlatif1 Nov 12, 2026
dfa8567
fix(bedrock): build role-session Bedrock clients per request instead …
waleedlatif1 Nov 12, 2026
06ed398
fix(bedrock): decide the AWS auth mode with one rule for fields and s…
waleedlatif1 Nov 12, 2026
6629800
fix(bedrock): refuse an unauthorized saved AWS credential with 401 li…
waleedlatif1 Nov 12, 2026
626cdac
revert(bedrock): drop the unreachable guardrails saved-credential plu…
waleedlatif1 Nov 12, 2026
1a76159
docs(bedrock): cover inference-profile IAM scope and role session lif…
waleedlatif1 Nov 12, 2026
cc48a4f
fix(bedrock): offer the saved AWS credential only on blocks that forw…
waleedlatif1 Nov 12, 2026
6da9081
fix(bedrock): address review on saved AWS credential signing
waleedlatif1 Nov 12, 2026
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
2 changes: 1 addition & 1 deletion apps/docs/content/docs/integrations/aws-credentials.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Save an AWS identity once and use it across every AWS block, with a
import { Callout } from '@/components/ui/callout'
import { Step, Steps } from '@/components/ui/steps'

An AWS credential holds one AWS identity for the whole workspace. Every AWS block (S3, DynamoDB, Lambda, SQS, Athena, CloudWatch, and the rest) signs in with a saved credential, so keys live in one place instead of in each block.
An AWS credential holds one AWS identity for the whole workspace. Every AWS block (S3, DynamoDB, Lambda, SQS, Athena, CloudWatch, and the rest) signs in with a saved credential, so keys live in one place instead of in each block. The Agent, Router, and Evaluator blocks can also run AWS Bedrock models with a saved credential: choose **Saved AWS credential** under **AWS Authentication**.

<Callout type="info">
Blocks added before saved credentials, which take access keys typed into the block, are marked legacy and keep working exactly as they are. Upgrade one by replacing it with the current block and selecting a credential.
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/content/docs/workflows/blocks/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The model that runs the step. Defaults to `claude-sonnet-5-5`. Type or pick any

For a custom cloud deployment, enter its provider prefix and model ID: `azure/my-deployment`, `azure-anthropic/my-deployment`, `bedrock/my-inference-profile`, or `vertex/my-gemini-model`. The prefix selects the provider and shows its credential fields even when the ID is absent from the catalog. Bedrock accepts full inference profile ARNs after `bedrock/`; Vertex uses the Gemini API and accepts Google model resource names. The deployment must support the selected provider's API. Custom IDs have no catalog pricing or token limits.

For an AWS Bedrock model, set **AWS Authentication** to **Access keys** to type a key pair into the block, or to **Saved AWS credential** to sign with a workspace [AWS credential](/integrations/aws-credentials) (access keys, an assumed IAM role, or a keyless role). A saved credential is used on its own: if it cannot be used, the run fails rather than signing with other keys. Its identity needs `bedrock:InvokeModel`, plus `bedrock:InvokeModelWithResponseStream` for streamed runs. Sim calls catalog Claude models through a geographic inference profile (`us.`, `eu.`, or `au.`, picked from the block's **AWS Region**), so a policy scoped to specific resources must allow the inference profile and the foundation model in every Region that profile routes to, as AWS's [inference profile prerequisites](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-prereq.html) describe. An assumed or keyless role's session starts when the block runs and is not renewed during the run, so one run must finish within the credential's session duration. The Router and Evaluator blocks offer the same choice.

Ollama Cloud, OpenRouter, Fireworks, Together AI, Baseten, Ollama, vLLM, and LiteLLM load their available models from the configured provider. New models appear through that discovery without a Sim catalog release. You can also enter a namespaced ID directly, such as `ollama-cloud/deepseek-v4.1-flash`, `openrouter/provider/model`, or `ollama/my-local-model`. Provider prefixes are case-insensitive; the model ID after the prefix keeps its original casing.

#### Nebius Token Factory
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/blocks/blocks/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getModelFallbackSubBlock, MODEL_FALLBACK_INPUTS } from '@/blocks/model-
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import {
BEDROCK_SAVED_CREDENTIAL_INPUTS,
getAgentModelOptions,
getModelCapabilityCondition,
getProviderCredentialSubBlocks,
Expand Down Expand Up @@ -136,7 +137,7 @@ Return ONLY the JSON array.`,
options: getAgentModelOptions,
commandSearchable: true,
},
...getProviderCredentialSubBlocks(),
...getProviderCredentialSubBlocks({ bedrockSavedCredential: true }),
{
id: 'evaluationState',
title: 'State',
Expand Down Expand Up @@ -559,6 +560,7 @@ Return ONLY the JSON array.`,
bedrockAccessKeyId: { type: 'string', description: 'AWS Access Key ID for Bedrock' },
bedrockSecretKey: { type: 'string', description: 'AWS Secret Access Key for Bedrock' },
bedrockRegion: { type: 'string', description: 'AWS region for Bedrock' },
...BEDROCK_SAVED_CREDENTIAL_INPUTS,
responseFormat: {
type: 'json',
description: 'JSON response format schema',
Expand Down
4 changes: 3 additions & 1 deletion apps/sim/blocks/blocks/evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ChartBarIcon } from '@/components/icons'
import { getModelFallbackSubBlock, MODEL_FALLBACK_INPUTS } from '@/blocks/model-fallbacks'
import type { BlockConfig, ParamType } from '@/blocks/types'
import {
BEDROCK_SAVED_CREDENTIAL_INPUTS,
getModelOptions,
getProviderCredentialSubBlocks,
getSerializedModelProviderId,
Expand Down Expand Up @@ -170,7 +171,7 @@ export const EvaluatorBlock: BlockConfig = {
defaultValue: 'claude-sonnet-5-5',
options: getModelOptions,
},
...getProviderCredentialSubBlocks(),
...getProviderCredentialSubBlocks({ bedrockSavedCredential: true }),
getModelFallbackSubBlock(),
{
id: 'temperature',
Expand Down Expand Up @@ -277,6 +278,7 @@ export const EvaluatorBlock: BlockConfig = {
},
model: { type: 'string' as ParamType, description: 'AI model to use' },
...PROVIDER_CREDENTIAL_INPUTS,
...BEDROCK_SAVED_CREDENTIAL_INPUTS,
...MODEL_FALLBACK_INPUTS,
temperature: {
type: 'number' as ParamType,
Expand Down
7 changes: 5 additions & 2 deletions apps/sim/blocks/blocks/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ConnectIcon } from '@/components/icons'
import { getModelFallbackSubBlock, MODEL_FALLBACK_INPUTS } from '@/blocks/model-fallbacks'
import { AuthMode, type BlockConfig } from '@/blocks/types'
import {
BEDROCK_SAVED_CREDENTIAL_INPUTS,
getModelOptions,
getProviderCredentialSubBlocks,
getSerializedModelProviderId,
Expand Down Expand Up @@ -163,7 +164,7 @@ export const RouterBlock: BlockConfig = {
defaultValue: 'claude-sonnet-5-5',
options: getModelOptions,
},
...getProviderCredentialSubBlocks(),
...getProviderCredentialSubBlocks({ bedrockSavedCredential: true }),
getModelFallbackSubBlock(),
{
id: 'temperature',
Expand Down Expand Up @@ -200,6 +201,7 @@ export const RouterBlock: BlockConfig = {
prompt: { type: 'string', description: 'Routing prompt content' },
model: { type: 'string', description: 'AI model to use' },
...PROVIDER_CREDENTIAL_INPUTS,
...BEDROCK_SAVED_CREDENTIAL_INPUTS,
...MODEL_FALLBACK_INPUTS,
temperature: {
type: 'number',
Expand Down Expand Up @@ -254,7 +256,7 @@ export const RouterV2Block: BlockConfig = {
defaultValue: 'claude-sonnet-5-5',
options: getModelOptions,
},
...getProviderCredentialSubBlocks(),
...getProviderCredentialSubBlocks({ bedrockSavedCredential: true }),
getModelFallbackSubBlock(),
],
tools: {
Expand All @@ -275,6 +277,7 @@ export const RouterV2Block: BlockConfig = {
routes: { type: 'json', description: 'Route definitions with descriptions' },
model: { type: 'string', description: 'AI model to use' },
...PROVIDER_CREDENTIAL_INPUTS,
...BEDROCK_SAVED_CREDENTIAL_INPUTS,
...MODEL_FALLBACK_INPUTS,
},
outputs: {
Expand Down
37 changes: 37 additions & 0 deletions apps/sim/blocks/blocks/translate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, expect, it } from 'vitest'
import { TranslateBlock } from '@/blocks/blocks/translate'

const toolParams = (params: Record<string, unknown>) => {
const build = TranslateBlock.tools.config?.params
if (!build) throw new Error('TranslateBlock.tools.config.params is missing')
return build({ context: 'Hello', targetLanguage: 'French', ...params })
}

describe('Translate Bedrock credential', () => {
it('sends the saved AWS credential with a Bedrock model call', () => {
expect(
toolParams({
model: 'bedrock/my-inference-profile',
bedrockAuthMode: 'credential',
bedrockCredential: 'aws-cred',
})
).toMatchObject({ model: 'bedrock/my-inference-profile', bedrockCredential: 'aws-cred' })
})

it('refuses a Bedrock model call set to a saved AWS credential with none selected', () => {
expect(() =>
toolParams({
model: 'bedrock/my-inference-profile',
bedrockAuthMode: 'credential',
bedrockCredential: '',
})
).toThrow('Select an AWS credential for Bedrock')
})

it('ignores a leftover Bedrock credential choice once the model is not on Bedrock', () => {
const params = toolParams({ model: 'gpt-4o', bedrockAuthMode: 'credential' })

expect(params).toMatchObject({ model: 'gpt-4o' })
expect(params.bedrockCredential).toBeUndefined()
})
})
11 changes: 10 additions & 1 deletion apps/sim/blocks/blocks/translate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { TranslateIcon } from '@/components/icons'
import { AuthMode, type BlockConfig, IntegrationType } from '@/blocks/types'
import {
BEDROCK_SAVED_CREDENTIAL_INPUTS,
getModelOptions,
getProviderCredentialSubBlocks,
getSelectedBedrockCredential,
PROVIDER_CREDENTIAL_INPUTS,
} from '@/blocks/utils'
import { findProviderFromModel } from '@/providers/models'

const getTranslationPrompt = (targetLanguage: string) =>
`Translate the following text into ${targetLanguage || 'English'}. Output ONLY the translated text with no additional commentary, explanations, or notes.`
Expand Down Expand Up @@ -52,7 +55,7 @@ export const TranslateBlock: BlockConfig = {
required: true,
options: getModelOptions,
},
...getProviderCredentialSubBlocks(),
...getProviderCredentialSubBlocks({ bedrockSavedCredential: true }),
{
id: 'systemPrompt',
title: 'System Prompt',
Expand Down Expand Up @@ -80,13 +83,19 @@ export const TranslateBlock: BlockConfig = {
bedrockAccessKeyId: params.bedrockAccessKeyId,
bedrockSecretKey: params.bedrockSecretKey,
bedrockRegion: params.bedrockRegion,
bedrockCredential:
typeof params.model === 'string' &&
findProviderFromModel(params.model.trim()) === 'bedrock'
? getSelectedBedrockCredential(params)
: undefined,
}),
},
},
inputs: {
context: { type: 'string', description: 'Text to translate' },
targetLanguage: { type: 'string', description: 'Target language' },
...PROVIDER_CREDENTIAL_INPUTS,
...BEDROCK_SAVED_CREDENTIAL_INPUTS,
systemPrompt: { type: 'string', description: 'Translation instructions' },
},
outputs: {
Expand Down
93 changes: 93 additions & 0 deletions apps/sim/blocks/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ vi.mock('@/stores/providers/store', () => ({
vi.mock('@/lib/oauth/utils', () => oauthUtilsMock)

import { S3Icon } from '@/components/icons'
import { evaluateSubBlockCondition } from '@/lib/workflows/subblocks/visibility'
import type { BlockConfig } from '@/blocks/types'
import {
getApiKeyCondition,
getProviderCredentialSubBlocks,
getSelectedBedrockCredential,
getSerializedModelProviderId,
parseOptionalBooleanInput,
parseOptionalJsonInput,
Expand Down Expand Up @@ -102,6 +105,96 @@ describe('requiresProviderFamilyCredentials', () => {
})
})

describe('Bedrock credential fields', () => {
const BEDROCK_FIELDS = [
'bedrockAuthMode',
'bedrockCredential',
'bedrockManualCredential',
'bedrockAccessKeyId',
'bedrockSecretKey',
'bedrockRegion',
]

const visibleBedrockFields = (
values: Record<string, unknown>,
subBlocks = getProviderCredentialSubBlocks({ bedrockSavedCredential: true })
) =>
subBlocks
.filter((subBlock) => BEDROCK_FIELDS.includes(subBlock.id))
.filter((subBlock) => evaluateSubBlockCondition(subBlock.condition, values))
.map((subBlock) => subBlock.id)

it('keeps showing access keys, and no credential picker, on a block saved before auth modes', () => {
expect(visibleBedrockFields({ model: 'bedrock/my-inference-profile' })).toEqual([
'bedrockAuthMode',
'bedrockAccessKeyId',
'bedrockSecretKey',
'bedrockRegion',
])
})

it('swaps the access keys for the AWS credential picker when a saved credential is chosen', () => {
expect(
visibleBedrockFields({ model: 'bedrock/my-inference-profile', bedrockAuthMode: 'credential' })
).toEqual(['bedrockAuthMode', 'bedrockCredential', 'bedrockManualCredential', 'bedrockRegion'])
})

it('shows no Bedrock field for a model of another provider', () => {
expect(visibleBedrockFields({ model: 'gpt-4o', bedrockAuthMode: 'credential' })).toEqual([])
})

it('offers no saved credential, and keeps the access keys, for a block that cannot forward one', () => {
expect(
visibleBedrockFields(
{ model: 'bedrock/my-inference-profile', bedrockAuthMode: 'credential' },
getProviderCredentialSubBlocks()
)
).toEqual(['bedrockAccessKeyId', 'bedrockSecretKey', 'bedrockRegion'])
})
})

describe('getSelectedBedrockCredential', () => {
it('returns the trimmed saved credential a block signs Bedrock calls with', () => {
expect(
getSelectedBedrockCredential({ bedrockAuthMode: 'credential', bedrockCredential: ' cred-1 ' })
).toBe('cred-1')
})

it('refuses a block set to a saved credential whose ID resolves blank', () => {
expect(() =>
getSelectedBedrockCredential({ bedrockAuthMode: 'credential', bedrockCredential: '' })
).toThrow('Select an AWS credential for Bedrock')
expect(() => getSelectedBedrockCredential({ bedrockAuthMode: 'credential' })).toThrow(
'Select an AWS credential for Bedrock'
)
})

it('returns nothing for a block on access keys or saved before auth modes', () => {
expect(
getSelectedBedrockCredential({ bedrockAuthMode: 'access_keys', bedrockCredential: 'cred-1' })
).toBeUndefined()
expect(getSelectedBedrockCredential({})).toBeUndefined()
})

it('signs with a credential exactly when the block shows and serializes the credential picker', () => {
const picker = getProviderCredentialSubBlocks({ bedrockSavedCredential: true }).find(
(subBlock) => subBlock.id === 'bedrockCredential'
)
const cases: Record<string, unknown>[] = [
{ bedrockCredential: 'cred-1' },
{ bedrockAuthMode: '', bedrockCredential: 'cred-1' },
{ bedrockAuthMode: 'access_keys', bedrockCredential: 'cred-1' },
{ bedrockAuthMode: 'credential', bedrockCredential: 'cred-1' },
]
for (const values of cases) {
const withModel = { model: 'bedrock/my-inference-profile', ...values }
expect(getSelectedBedrockCredential(values) !== undefined, JSON.stringify(values)).toBe(
evaluateSubBlockCondition(picker?.condition, withModel)
)
}
})
})

describe('getApiKeyCondition / shouldRequireApiKeyForModel', () => {
const evaluateCondition = (model: string): boolean => {
const conditionFn = getApiKeyCondition()
Expand Down
Loading
Loading