From 8527ae5d3bcb7655a9464ddf8a4fecd362b5ef47 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 1 Apr 2026 16:27:54 -0700 Subject: [PATCH] feat(providers): server-side credential hiding for Azure and Bedrock (#3884) * fix: allow Bedrock provider to use AWS SDK default credential chain Remove hard requirement for explicit AWS credentials in Bedrock provider. When access key and secret key are not provided, the AWS SDK automatically falls back to its default credential chain (env vars, instance profile, ECS task role, EKS IRSA, SSO). Closes #3694 Signed-off-by: majiayu000 <1835304752@qq.com> * fix: add partial credential guard for Bedrock provider Reject configurations where only one of bedrockAccessKeyId or bedrockSecretKey is provided, preventing silent fallback to the default credential chain with a potentially different identity. Add tests covering all credential configuration scenarios. Signed-off-by: majiayu000 <1835304752@qq.com> * fix: clean up bedrock test lint and dead code Remove unused config parameter and dead _lastConfig assignment from mock factory. Break long mockReturnValue chain to satisfy biome line-length rule. Signed-off-by: majiayu000 <1835304752@qq.com> * fix: address greptile review feedback on PR #3708 Use BedrockRuntimeClientConfig from SDK instead of inline type. Add default return value for prepareToolsWithUsageControl mock. Signed-off-by: majiayu000 <1835304752@qq.com> * feat(providers): server-side credential hiding for Azure and Bedrock * fix(providers): revert Bedrock credential fields to required with original placeholders * fix(blocks): add hideWhenEnvSet to getProviderCredentialSubBlocks for Azure and Bedrock * fix(agent): use getProviderCredentialSubBlocks() instead of duplicating credential subblocks * fix(blocks): consolidate Vertex credential into shared factory with basic/advanced mode * fix(types): resolve pre-existing TypeScript errors across auth, secrets, and copilot * lint * improvement(blocks): make Vertex AI project ID a password field * fix(blocks): preserve vertexCredential subblock ID for backwards compatibility * fix(blocks): follow canonicalParamId pattern correctly for vertex credential subblocks * fix(blocks): keep vertexCredential subblock ID stable to preserve saved workflow state * fix(blocks): add canonicalParamId to vertexCredential basic subblock to complete the swap pair * fix types * more types --------- Signed-off-by: majiayu000 <1835304752@qq.com> Co-authored-by: majiayu000 <1835304752@qq.com> Co-authored-by: Vikhyath Mondreti --- .cursor/skills/add-hosted-key/SKILL.md | 2 +- apps/sim/.env.example | 8 ++ .../app/api/tools/secrets_manager/utils.ts | 5 +- .../hooks/use-editor-subblock-layout.ts | 6 +- .../workflow-block/workflow-block.tsx | 4 +- apps/sim/blocks/blocks/agent.ts | 136 +----------------- apps/sim/blocks/blocks/function.ts | 2 +- apps/sim/blocks/types.ts | 3 +- apps/sim/blocks/utils.ts | 44 +++++- apps/sim/lib/api-key/byok.ts | 11 +- apps/sim/lib/auth/auth.ts | 5 +- .../orchestrator/tool-executor/index.ts | 4 +- apps/sim/lib/copilot/vfs/serializers.ts | 4 +- apps/sim/lib/core/config/env.ts | 4 + apps/sim/lib/core/config/feature-flags.ts | 8 ++ .../sim/lib/workflows/subblocks/visibility.ts | 18 ++- apps/sim/providers/azure-anthropic/index.ts | 17 ++- apps/sim/providers/azure-openai/index.ts | 68 +++++---- apps/sim/providers/bedrock/index.test.ts | 115 +++++++++++++++ apps/sim/providers/bedrock/index.ts | 34 +++-- apps/sim/providers/utils.ts | 9 +- apps/sim/serializer/index.ts | 4 +- apps/sim/tools/params.ts | 8 +- bun.lock | 1 - helm/sim/examples/values-aws.yaml | 4 + helm/sim/examples/values-azure.yaml | 11 ++ helm/sim/values.yaml | 25 +++- 27 files changed, 343 insertions(+), 217 deletions(-) create mode 100644 apps/sim/providers/bedrock/index.test.ts diff --git a/.cursor/skills/add-hosted-key/SKILL.md b/.cursor/skills/add-hosted-key/SKILL.md index a6e0f07052..2181910f8d 100644 --- a/.cursor/skills/add-hosted-key/SKILL.md +++ b/.cursor/skills/add-hosted-key/SKILL.md @@ -192,7 +192,7 @@ In the block config (`blocks/blocks/{service}.ts`), add `hideWhenHosted: true` t }, ``` -The visibility is controlled by `isSubBlockHiddenByHostedKey()` in `lib/workflows/subblocks/visibility.ts`, which checks the `isHosted` feature flag. +The visibility is controlled by `isSubBlockHidden()` in `lib/workflows/subblocks/visibility.ts`, which checks both the `isHosted` feature flag (`hideWhenHosted`) and optional env var conditions (`hideWhenEnvSet`). ### Excluding Specific Operations from Hosted Key Support diff --git a/apps/sim/.env.example b/apps/sim/.env.example index 8db5d82c1a..fe9514e358 100644 --- a/apps/sim/.env.example +++ b/apps/sim/.env.example @@ -29,6 +29,14 @@ API_ENCRYPTION_KEY=your_api_encryption_key # Use `openssl rand -hex 32` to gener # VLLM_BASE_URL=http://localhost:8000 # Base URL for your self-hosted vLLM (OpenAI-compatible) # VLLM_API_KEY= # Optional bearer token if your vLLM instance requires auth # FIREWORKS_API_KEY= # Optional Fireworks AI API key for model listing +# NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS=true # Set when using AWS default credential chain (IAM roles, ECS task roles, IRSA). Hides credential fields in Agent block UI. +# AZURE_OPENAI_ENDPOINT= # Azure OpenAI endpoint (hides field in UI when set alongside NEXT_PUBLIC_AZURE_CONFIGURED) +# AZURE_OPENAI_API_KEY= # Azure OpenAI API key +# AZURE_OPENAI_API_VERSION= # Azure OpenAI API version +# AZURE_ANTHROPIC_ENDPOINT= # Azure Anthropic endpoint (AI Foundry) +# AZURE_ANTHROPIC_API_KEY= # Azure Anthropic API key +# AZURE_ANTHROPIC_API_VERSION= # Azure Anthropic API version (e.g., 2023-06-01) +# NEXT_PUBLIC_AZURE_CONFIGURED=true # Set when Azure credentials are pre-configured above. Hides endpoint/key/version fields in Agent block UI. # Admin API (Optional - for self-hosted GitOps) # ADMIN_API_KEY= # Use `openssl rand -hex 32` to generate. Enables admin API for workflow export/import. diff --git a/apps/sim/app/api/tools/secrets_manager/utils.ts b/apps/sim/app/api/tools/secrets_manager/utils.ts index 331197fcec..dacbbc30be 100644 --- a/apps/sim/app/api/tools/secrets_manager/utils.ts +++ b/apps/sim/app/api/tools/secrets_manager/utils.ts @@ -1,3 +1,4 @@ +import type { SecretListEntry, Tag } from '@aws-sdk/client-secrets-manager' import { CreateSecretCommand, DeleteSecretCommand, @@ -61,7 +62,7 @@ export async function listSecrets( }) const response = await client.send(command) - const secrets = (response.SecretList ?? []).map((secret) => ({ + const secrets = (response.SecretList ?? []).map((secret: SecretListEntry) => ({ name: secret.Name ?? '', arn: secret.ARN ?? '', description: secret.Description ?? null, @@ -69,7 +70,7 @@ export async function listSecrets( lastChangedDate: secret.LastChangedDate?.toISOString() ?? null, lastAccessedDate: secret.LastAccessedDate?.toISOString() ?? null, rotationEnabled: secret.RotationEnabled ?? false, - tags: secret.Tags?.map((t) => ({ key: t.Key ?? '', value: t.Value ?? '' })) ?? [], + tags: secret.Tags?.map((t: Tag) => ({ key: t.Key ?? '', value: t.Value ?? '' })) ?? [], })) return { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts index 0cf118e428..1ebc925262 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts @@ -3,7 +3,7 @@ import { buildCanonicalIndex, evaluateSubBlockCondition, isSubBlockFeatureEnabled, - isSubBlockHiddenByHostedKey, + isSubBlockHidden, isSubBlockVisibleForMode, } from '@/lib/workflows/subblocks/visibility' import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types' @@ -109,8 +109,8 @@ export function useEditorSubblockLayout( // Check required feature if specified - declarative feature gating if (!isSubBlockFeatureEnabled(block)) return false - // Hide tool API key fields when hosted - if (isSubBlockHiddenByHostedKey(block)) return false + // Hide tool API key fields when hosted or when env var is set + if (isSubBlockHidden(block)) return false // Special handling for trigger-config type (legacy trigger configuration UI) if (block.type === ('trigger-config' as SubBlockType)) { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index 70ddefc95e..b7b04f38bb 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -17,7 +17,7 @@ import { evaluateSubBlockCondition, hasAdvancedValues, isSubBlockFeatureEnabled, - isSubBlockHiddenByHostedKey, + isSubBlockHidden, isSubBlockVisibleForMode, resolveDependencyValue, } from '@/lib/workflows/subblocks/visibility' @@ -980,7 +980,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ if (block.hidden) return false if (block.hideFromPreview) return false if (!isSubBlockFeatureEnabled(block)) return false - if (isSubBlockHiddenByHostedKey(block)) return false + if (isSubBlockHidden(block)) return false const isPureTriggerBlock = config?.triggers?.enabled && config.category === 'triggers' diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts index b44ef4658c..caad30d9e2 100644 --- a/apps/sim/blocks/blocks/agent.ts +++ b/apps/sim/blocks/blocks/agent.ts @@ -1,9 +1,12 @@ import { createLogger } from '@sim/logger' import { AgentIcon } from '@/components/icons' -import { getScopesForService } from '@/lib/oauth/utils' import type { BlockConfig } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' -import { getApiKeyCondition, getModelOptions, RESPONSE_FORMAT_WAND_CONFIG } from '@/blocks/utils' +import { + getModelOptions, + getProviderCredentialSubBlocks, + RESPONSE_FORMAT_WAND_CONFIG, +} from '@/blocks/utils' import { getBaseModelProviders, getMaxTemperature, @@ -12,7 +15,6 @@ import { getModelsWithReasoningEffort, getModelsWithThinking, getModelsWithVerbosity, - getProviderModels, getReasoningEffortValuesForModel, getThinkingLevelsForModel, getVerbosityValuesForModel, @@ -23,9 +25,6 @@ import { useSubBlockStore } from '@/stores/workflows/subblock/store' import type { ToolResponse } from '@/tools/types' const logger = createLogger('AgentBlock') -const VERTEX_MODELS = getProviderModels('vertex') -const BEDROCK_MODELS = getProviderModels('bedrock') -const AZURE_MODELS = [...getProviderModels('azure-openai'), ...getProviderModels('azure-anthropic')] const MODELS_WITH_REASONING_EFFORT = getModelsWithReasoningEffort() const MODELS_WITH_VERBOSITY = getModelsWithVerbosity() const MODELS_WITH_THINKING = getModelsWithThinking() @@ -134,34 +133,6 @@ Return ONLY the JSON array.`, defaultValue: 'claude-sonnet-4-5', options: getModelOptions, }, - { - id: 'vertexCredential', - title: 'Google Cloud Account', - type: 'oauth-input', - serviceId: 'vertex-ai', - canonicalParamId: 'oauthCredential', - mode: 'basic', - requiredScopes: getScopesForService('vertex-ai'), - placeholder: 'Select Google Cloud account', - required: true, - condition: { - field: 'model', - value: VERTEX_MODELS, - }, - }, - { - id: 'manualCredential', - title: 'Google Cloud Account', - type: 'short-input', - canonicalParamId: 'oauthCredential', - mode: 'advanced', - placeholder: 'Enter credential ID', - required: true, - condition: { - field: 'model', - value: VERTEX_MODELS, - }, - }, { id: 'reasoningEffort', title: 'Reasoning Effort', @@ -318,100 +289,7 @@ Return ONLY the JSON array.`, }, }, - { - id: 'azureEndpoint', - title: 'Azure Endpoint', - type: 'short-input', - password: true, - placeholder: 'https://your-resource.services.ai.azure.com', - connectionDroppable: false, - condition: { - field: 'model', - value: AZURE_MODELS, - }, - }, - { - id: 'azureApiVersion', - title: 'Azure API Version', - type: 'short-input', - placeholder: 'Enter API version', - connectionDroppable: false, - condition: { - field: 'model', - value: AZURE_MODELS, - }, - }, - { - id: 'vertexProject', - title: 'Vertex AI Project', - type: 'short-input', - placeholder: 'your-gcp-project-id', - connectionDroppable: false, - required: true, - condition: { - field: 'model', - value: VERTEX_MODELS, - }, - }, - { - id: 'vertexLocation', - title: 'Vertex AI Location', - type: 'short-input', - placeholder: 'us-central1', - connectionDroppable: false, - required: true, - condition: { - field: 'model', - value: VERTEX_MODELS, - }, - }, - { - id: 'bedrockAccessKeyId', - title: 'AWS Access Key ID', - type: 'short-input', - password: true, - placeholder: 'Enter your AWS Access Key ID', - connectionDroppable: false, - required: true, - condition: { - field: 'model', - value: BEDROCK_MODELS, - }, - }, - { - id: 'bedrockSecretKey', - title: 'AWS Secret Access Key', - type: 'short-input', - password: true, - placeholder: 'Enter your AWS Secret Access Key', - connectionDroppable: false, - required: true, - condition: { - field: 'model', - value: BEDROCK_MODELS, - }, - }, - { - id: 'bedrockRegion', - title: 'AWS Region', - type: 'short-input', - placeholder: 'us-east-1', - connectionDroppable: false, - condition: { - field: 'model', - value: BEDROCK_MODELS, - }, - }, - { - id: 'apiKey', - title: 'API Key', - type: 'short-input', - placeholder: 'Enter your API key', - password: true, - connectionDroppable: false, - required: true, - condition: getApiKeyCondition(), - }, + ...getProviderCredentialSubBlocks(), { id: 'tools', title: 'Tools', @@ -661,7 +539,7 @@ Return ONLY the JSON array.`, apiKey: { type: 'string', description: 'Provider API key' }, azureEndpoint: { type: 'string', description: 'Azure endpoint URL' }, azureApiVersion: { type: 'string', description: 'Azure API version' }, - oauthCredential: { type: 'string', description: 'OAuth credential for Vertex AI' }, + vertexCredential: { type: 'string', description: 'OAuth credential for Vertex AI' }, vertexProject: { type: 'string', description: 'Google Cloud project ID for Vertex AI' }, vertexLocation: { type: 'string', description: 'Google Cloud location for Vertex AI' }, bedrockAccessKeyId: { type: 'string', description: 'AWS Access Key ID for Bedrock' }, diff --git a/apps/sim/blocks/blocks/function.ts b/apps/sim/blocks/blocks/function.ts index bdfc15ab11..341f648245 100644 --- a/apps/sim/blocks/blocks/function.ts +++ b/apps/sim/blocks/blocks/function.ts @@ -29,7 +29,7 @@ export const FunctionBlock: BlockConfig = { ], placeholder: 'Select language', value: () => CodeLanguage.JavaScript, - requiresFeature: 'NEXT_PUBLIC_E2B_ENABLED', + showWhenEnvSet: 'NEXT_PUBLIC_E2B_ENABLED', }, { id: 'code', diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 614c686ec5..a471af5aad 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -327,8 +327,9 @@ export interface SubBlockConfig { connectionDroppable?: boolean hidden?: boolean hideFromPreview?: boolean // Hide this subblock from the workflow block preview - requiresFeature?: string // Environment variable name that must be truthy for this subblock to be visible + showWhenEnvSet?: string // Show this subblock only when the named NEXT_PUBLIC_ env var is truthy hideWhenHosted?: boolean // Hide this subblock when running on hosted sim + hideWhenEnvSet?: string // Hide this subblock when the named NEXT_PUBLIC_ env var is truthy description?: string tooltip?: string // Tooltip text displayed via info icon next to the title value?: (params: Record) => string diff --git a/apps/sim/blocks/utils.ts b/apps/sim/blocks/utils.ts index e06dbdf5ad..b68ac4cbbb 100644 --- a/apps/sim/blocks/utils.ts +++ b/apps/sim/blocks/utils.ts @@ -1,4 +1,5 @@ -import { isHosted } from '@/lib/core/config/feature-flags' +import { isAzureConfigured, isHosted } from '@/lib/core/config/feature-flags' +import { getScopesForService } from '@/lib/oauth/utils' import type { BlockOutput, OutputFieldDefinition, SubBlockConfig } from '@/blocks/types' import { getHostedModels, @@ -8,9 +9,12 @@ import { } from '@/providers/models' import { useProvidersStore } from '@/stores/providers/store' -const VERTEX_MODELS = getProviderModels('vertex') -const BEDROCK_MODELS = getProviderModels('bedrock') -const AZURE_MODELS = [...getProviderModels('azure-openai'), ...getProviderModels('azure-anthropic')] +export const VERTEX_MODELS = getProviderModels('vertex') +export const BEDROCK_MODELS = getProviderModels('bedrock') +export const AZURE_MODELS = [ + ...getProviderModels('azure-openai'), + ...getProviderModels('azure-anthropic'), +] /** * Returns model options for combobox subblocks, combining all provider sources. @@ -105,6 +109,16 @@ function shouldRequireApiKeyForModel(model: string): boolean { return false } + if ( + isAzureConfigured && + (normalizedModel.startsWith('azure/') || + normalizedModel.startsWith('azure-openai/') || + normalizedModel.startsWith('azure-anthropic/') || + AZURE_MODELS.some((m) => m.toLowerCase() === normalizedModel)) + ) { + return false + } + if (normalizedModel.startsWith('vllm/')) { return false } @@ -158,7 +172,9 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { title: 'Google Cloud Account', type: 'oauth-input', serviceId: 'vertex-ai', - requiredScopes: ['https://www.googleapis.com/auth/cloud-platform'], + canonicalParamId: 'vertexCredential', + mode: 'basic', + requiredScopes: getScopesForService('vertex-ai'), placeholder: 'Select Google Cloud account', required: true, condition: { @@ -166,6 +182,19 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { value: VERTEX_MODELS, }, }, + { + id: 'vertexManualCredential', + title: 'Google Cloud Account', + type: 'short-input', + canonicalParamId: 'vertexCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + condition: { + field: 'model', + value: VERTEX_MODELS, + }, + }, { id: 'apiKey', title: 'API Key', @@ -183,6 +212,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { password: true, placeholder: 'https://your-resource.services.ai.azure.com', connectionDroppable: false, + hideWhenEnvSet: 'NEXT_PUBLIC_AZURE_CONFIGURED', condition: { field: 'model', value: AZURE_MODELS, @@ -194,6 +224,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { type: 'short-input', placeholder: 'Enter API version', connectionDroppable: false, + hideWhenEnvSet: 'NEXT_PUBLIC_AZURE_CONFIGURED', condition: { field: 'model', value: AZURE_MODELS, @@ -203,6 +234,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { id: 'vertexProject', title: 'Vertex AI Project', type: 'short-input', + password: true, placeholder: 'your-gcp-project-id', connectionDroppable: false, required: true, @@ -231,6 +263,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { placeholder: 'Enter your AWS Access Key ID', connectionDroppable: false, required: true, + hideWhenEnvSet: 'NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS', condition: { field: 'model', value: BEDROCK_MODELS, @@ -244,6 +277,7 @@ export function getProviderCredentialSubBlocks(): SubBlockConfig[] { placeholder: 'Enter your AWS Secret Access Key', connectionDroppable: false, required: true, + hideWhenEnvSet: 'NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS', condition: { field: 'model', value: BEDROCK_MODELS, diff --git a/apps/sim/lib/api-key/byok.ts b/apps/sim/lib/api-key/byok.ts index 901d86af5f..a2aa198e85 100644 --- a/apps/sim/lib/api-key/byok.ts +++ b/apps/sim/lib/api-key/byok.ts @@ -8,6 +8,7 @@ import { isHosted } from '@/lib/core/config/feature-flags' import { decryptSecret } from '@/lib/core/security/encryption' import { getWorkspaceById } from '@/lib/workspaces/permissions/utils' import { getHostedModels } from '@/providers/models' +import { PROVIDER_PLACEHOLDER_KEY } from '@/providers/utils' import { useProvidersStore } from '@/stores/providers/store' import type { BYOKProviderId } from '@/tools/types' @@ -95,7 +96,15 @@ export async function getApiKeyWithBYOK( const isBedrockModel = provider === 'bedrock' || model.startsWith('bedrock/') if (isBedrockModel) { - return { apiKey: 'bedrock-uses-own-credentials', isBYOK: false } + return { apiKey: PROVIDER_PLACEHOLDER_KEY, isBYOK: false } + } + + if (provider === 'azure-openai') { + return { apiKey: userProvidedKey || env.AZURE_OPENAI_API_KEY || '', isBYOK: false } + } + + if (provider === 'azure-anthropic') { + return { apiKey: userProvidedKey || env.AZURE_ANTHROPIC_API_KEY || '', isBYOK: false } } const isOpenAIModel = provider === 'openai' diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 7bfaa64889..69a308eccc 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -559,12 +559,12 @@ export const auth = betterAuth({ github: { clientId: env.GITHUB_CLIENT_ID as string, clientSecret: env.GITHUB_CLIENT_SECRET as string, - scopes: ['user:email', 'repo'], + scope: ['user:email', 'repo'], }, google: { clientId: env.GOOGLE_CLIENT_ID as string, clientSecret: env.GOOGLE_CLIENT_SECRET as string, - scopes: [ + scope: [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', ], @@ -602,7 +602,6 @@ export const auth = betterAuth({ emailAndPassword: { enabled: true, requireEmailVerification: isEmailVerificationEnabled, - sendVerificationOnSignUp: isEmailVerificationEnabled, // Auto-send verification OTP on signup when verification is required throwOnMissingCredentials: true, throwOnInvalidCredentials: true, sendResetPassword: async ({ user, url, token }, request) => { diff --git a/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts b/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts index 95252daf8b..cf6b623717 100644 --- a/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts +++ b/apps/sim/lib/copilot/orchestrator/tool-executor/index.ts @@ -954,10 +954,10 @@ async function generateOAuthLink( const { headers: getHeaders } = await import('next/headers') const reqHeaders = await getHeaders() - const data = (await auth.api.oAuth2LinkAccount({ + const data = await auth.api.oAuth2LinkAccount({ body: { providerId, callbackURL }, headers: reqHeaders, - })) as { url?: string; redirect?: boolean } + }) if (!data?.url) { throw new Error('oAuth2LinkAccount did not return an authorization URL') diff --git a/apps/sim/lib/copilot/vfs/serializers.ts b/apps/sim/lib/copilot/vfs/serializers.ts index e1d282d0d5..524e8567e0 100644 --- a/apps/sim/lib/copilot/vfs/serializers.ts +++ b/apps/sim/lib/copilot/vfs/serializers.ts @@ -1,6 +1,6 @@ import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions' import { isHosted } from '@/lib/core/config/feature-flags' -import { isSubBlockHiddenByHostedKey } from '@/lib/workflows/subblocks/visibility' +import { isSubBlockHidden } from '@/lib/workflows/subblocks/visibility' import type { BlockConfig, SubBlockConfig } from '@/blocks/types' import { PROVIDER_DEFINITIONS } from '@/providers/models' import type { ToolConfig } from '@/tools/types' @@ -369,7 +369,7 @@ function serializeSubBlock(sb: SubBlockConfig): Record { * Serialize a block schema for VFS components/blocks/{type}.json */ export function serializeBlockSchema(block: BlockConfig): string { - const hiddenIds = new Set(block.subBlocks.filter(isSubBlockHiddenByHostedKey).map((sb) => sb.id)) + const hiddenIds = new Set(block.subBlocks.filter(isSubBlockHidden).map((sb) => sb.id)) const subBlocks = block.subBlocks .filter((sb) => !hiddenIds.has(sb.id)) diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index c6b05f076f..9db115984e 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -403,6 +403,8 @@ export const env = createEnv({ NEXT_PUBLIC_SUPPORT_EMAIL: z.string().email().optional(), // Custom support email NEXT_PUBLIC_E2B_ENABLED: z.string().optional(), + NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: z.string().optional(), // Hide Bedrock credential fields when deployment uses AWS default credential chain (IAM roles, instance profiles, ECS task roles, IRSA) + NEXT_PUBLIC_AZURE_CONFIGURED: z.string().optional(), // Hide Azure credential fields when endpoint/key/version are pre-configured server-side NEXT_PUBLIC_COPILOT_TRAINING_ENABLED: z.string().optional(), NEXT_PUBLIC_ENABLE_PLAYGROUND: z.string().optional(), // Enable component playground at /playground NEXT_PUBLIC_DOCUMENTATION_URL: z.string().url().optional(), // Custom documentation URL @@ -461,6 +463,8 @@ export const env = createEnv({ NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED: process.env.NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED, NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY, NEXT_PUBLIC_E2B_ENABLED: process.env.NEXT_PUBLIC_E2B_ENABLED, + NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: process.env.NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS, + NEXT_PUBLIC_AZURE_CONFIGURED: process.env.NEXT_PUBLIC_AZURE_CONFIGURED, NEXT_PUBLIC_COPILOT_TRAINING_ENABLED: process.env.NEXT_PUBLIC_COPILOT_TRAINING_ENABLED, NEXT_PUBLIC_ENABLE_PLAYGROUND: process.env.NEXT_PUBLIC_ENABLE_PLAYGROUND, NEXT_PUBLIC_POSTHOG_ENABLED: process.env.NEXT_PUBLIC_POSTHOG_ENABLED, diff --git a/apps/sim/lib/core/config/feature-flags.ts b/apps/sim/lib/core/config/feature-flags.ts index 012d1d5e02..b688924afe 100644 --- a/apps/sim/lib/core/config/feature-flags.ts +++ b/apps/sim/lib/core/config/feature-flags.ts @@ -122,6 +122,14 @@ export const isInboxEnabled = isTruthy(env.INBOX_ENABLED) */ export const isE2bEnabled = isTruthy(env.E2B_ENABLED) +/** + * Whether Azure OpenAI / Azure Anthropic credentials are pre-configured at the server level + * (via AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_KEY, AZURE_ANTHROPIC_ENDPOINT, etc.). + * When true, the endpoint, API key, and API version fields are hidden in the Agent block UI. + * Set NEXT_PUBLIC_AZURE_CONFIGURED=true in self-hosted deployments on Azure. + */ +export const isAzureConfigured = isTruthy(getEnv('NEXT_PUBLIC_AZURE_CONFIGURED')) + /** * Are invitations disabled globally * When true, workspace invitations are disabled for all users diff --git a/apps/sim/lib/workflows/subblocks/visibility.ts b/apps/sim/lib/workflows/subblocks/visibility.ts index 44cddf1224..356ab0507b 100644 --- a/apps/sim/lib/workflows/subblocks/visibility.ts +++ b/apps/sim/lib/workflows/subblocks/visibility.ts @@ -285,15 +285,19 @@ export function resolveDependencyValue( * Check if a subblock is gated by a feature flag. */ export function isSubBlockFeatureEnabled(subBlock: SubBlockConfig): boolean { - if (!subBlock.requiresFeature) return true - return isTruthy(getEnv(subBlock.requiresFeature)) + if (!subBlock.showWhenEnvSet) return true + return isTruthy(getEnv(subBlock.showWhenEnvSet)) } /** - * Check if a subblock should be hidden because we're running on hosted Sim. - * Used for tool API key fields that should be hidden when Sim provides hosted keys. + * Check if a subblock should be hidden based on environment conditions. + * Covers two cases: + * - `hideWhenHosted`: hidden when running on hosted Sim (tool API key fields) + * - `hideWhenEnvSet`: hidden when a specific NEXT_PUBLIC_ env var is truthy + * (credential fields hidden when the deployment provides them server-side) */ -export function isSubBlockHiddenByHostedKey(subBlock: SubBlockConfig): boolean { - if (!subBlock.hideWhenHosted) return false - return isHosted +export function isSubBlockHidden(subBlock: SubBlockConfig): boolean { + if (subBlock.hideWhenHosted && isHosted) return true + if (subBlock.hideWhenEnvSet && isTruthy(getEnv(subBlock.hideWhenEnvSet))) return true + return false } diff --git a/apps/sim/providers/azure-anthropic/index.ts b/apps/sim/providers/azure-anthropic/index.ts index 721e363394..710d1ba90e 100644 --- a/apps/sim/providers/azure-anthropic/index.ts +++ b/apps/sim/providers/azure-anthropic/index.ts @@ -1,5 +1,6 @@ import Anthropic from '@anthropic-ai/sdk' import { createLogger } from '@sim/logger' +import { env } from '@/lib/core/config/env' import type { StreamingExecution } from '@/executor/types' import { executeAnthropicProviderRequest } from '@/providers/anthropic/core' import { getProviderDefaultModel, getProviderModels } from '@/providers/models' @@ -18,14 +19,16 @@ export const azureAnthropicProvider: ProviderConfig = { executeRequest: async ( request: ProviderRequest ): Promise => { - if (!request.azureEndpoint) { + const azureEndpoint = request.azureEndpoint || env.AZURE_ANTHROPIC_ENDPOINT + if (!azureEndpoint) { throw new Error( - 'Azure endpoint is required for Azure Anthropic. Please provide it via the azureEndpoint parameter.' + 'Azure endpoint is required for Azure Anthropic. Please provide it via the azureEndpoint parameter or AZURE_ANTHROPIC_ENDPOINT environment variable.' ) } - if (!request.apiKey) { - throw new Error('API key is required for Azure Anthropic') + const apiKey = request.apiKey + if (!apiKey) { + throw new Error('API key is required for Azure Anthropic.') } // Strip the azure-anthropic/ prefix from the model name if present @@ -33,14 +36,16 @@ export const azureAnthropicProvider: ProviderConfig = { // Azure AI Foundry hosts Anthropic models at {endpoint}/anthropic // The SDK appends /v1/messages automatically - const baseURL = `${request.azureEndpoint.replace(/\/$/, '')}/anthropic` + const baseURL = `${azureEndpoint.replace(/\/$/, '')}/anthropic` - const anthropicVersion = request.azureApiVersion || '2023-06-01' + const anthropicVersion = + request.azureApiVersion || env.AZURE_ANTHROPIC_API_VERSION || '2023-06-01' return executeAnthropicProviderRequest( { ...request, model: modelName, + apiKey, }, { providerId: 'azure-anthropic', diff --git a/apps/sim/providers/azure-openai/index.ts b/apps/sim/providers/azure-openai/index.ts index 930c31035a..72ee513e80 100644 --- a/apps/sim/providers/azure-openai/index.ts +++ b/apps/sim/providers/azure-openai/index.ts @@ -65,7 +65,7 @@ async function executeChatCompletionsRequest( }) const azureOpenAI = new AzureOpenAI({ - apiKey: request.apiKey, + apiKey: request.apiKey!, apiVersion: azureApiVersion, endpoint: azureEndpoint, }) @@ -623,8 +623,9 @@ export const azureOpenAIProvider: ProviderConfig = { ) } - if (!request.apiKey) { - throw new Error('API key is required for Azure OpenAI') + const apiKey = request.apiKey + if (!apiKey) { + throw new Error('API key is required for Azure OpenAI.') } // Check if the endpoint is a full chat completions URL @@ -653,7 +654,12 @@ export const azureOpenAIProvider: ProviderConfig = { apiVersion: azureApiVersion, }) - return executeChatCompletionsRequest(request, baseUrl, azureApiVersion, deploymentName) + return executeChatCompletionsRequest( + { ...request, apiKey }, + baseUrl, + azureApiVersion, + deploymentName + ) } // Check if the endpoint is already a full responses API URL @@ -663,18 +669,21 @@ export const azureOpenAIProvider: ProviderConfig = { const deploymentName = request.model.replace('azure/', '') // Use the URL as-is since it's already complete - return executeResponsesProviderRequest(request, { - providerId: 'azure-openai', - providerLabel: 'Azure OpenAI', - modelName: deploymentName, - endpoint: azureEndpoint, - headers: { - 'Content-Type': 'application/json', - 'OpenAI-Beta': 'responses=v1', - 'api-key': request.apiKey, - }, - logger, - }) + return executeResponsesProviderRequest( + { ...request, apiKey }, + { + providerId: 'azure-openai', + providerLabel: 'Azure OpenAI', + modelName: deploymentName, + endpoint: azureEndpoint, + headers: { + 'Content-Type': 'application/json', + 'OpenAI-Beta': 'responses=v1', + 'api-key': apiKey, + }, + logger, + } + ) } // Default: base URL provided, construct the responses API URL @@ -684,17 +693,20 @@ export const azureOpenAIProvider: ProviderConfig = { const deploymentName = request.model.replace('azure/', '') const apiUrl = `${azureEndpoint.replace(/\/$/, '')}/openai/v1/responses?api-version=${azureApiVersion}` - return executeResponsesProviderRequest(request, { - providerId: 'azure-openai', - providerLabel: 'Azure OpenAI', - modelName: deploymentName, - endpoint: apiUrl, - headers: { - 'Content-Type': 'application/json', - 'OpenAI-Beta': 'responses=v1', - 'api-key': request.apiKey, - }, - logger, - }) + return executeResponsesProviderRequest( + { ...request, apiKey }, + { + providerId: 'azure-openai', + providerLabel: 'Azure OpenAI', + modelName: deploymentName, + endpoint: apiUrl, + headers: { + 'Content-Type': 'application/json', + 'OpenAI-Beta': 'responses=v1', + 'api-key': apiKey, + }, + logger, + } + ) }, } diff --git a/apps/sim/providers/bedrock/index.test.ts b/apps/sim/providers/bedrock/index.test.ts new file mode 100644 index 0000000000..8c938e01c8 --- /dev/null +++ b/apps/sim/providers/bedrock/index.test.ts @@ -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', + }) + }) +}) diff --git a/apps/sim/providers/bedrock/index.ts b/apps/sim/providers/bedrock/index.ts index ec0af6ab04..d3223cfebd 100644 --- a/apps/sim/providers/bedrock/index.ts +++ b/apps/sim/providers/bedrock/index.ts @@ -1,6 +1,7 @@ import { type Message as BedrockMessage, BedrockRuntimeClient, + type BedrockRuntimeClientConfig, type ContentBlock, type ConversationRole, ConverseCommand, @@ -50,14 +51,6 @@ export const bedrockProvider: ProviderConfig = { executeRequest: async ( request: ProviderRequest ): Promise => { - 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) @@ -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[] = [] diff --git a/apps/sim/providers/utils.ts b/apps/sim/providers/utils.ts index 30fe1467eb..40d4a0ebdf 100644 --- a/apps/sim/providers/utils.ts +++ b/apps/sim/providers/utils.ts @@ -718,6 +718,13 @@ export function shouldBillModelUsage(model: string): boolean { return hostedModels.some((hostedModel) => model.toLowerCase() === hostedModel.toLowerCase()) } +/** + * Placeholder returned for providers that use their own credential mechanism + * rather than a user-supplied API key (e.g. AWS Bedrock via IAM/instance profiles). + * Must be truthy so upstream key-presence checks don't reject it. + */ +export const PROVIDER_PLACEHOLDER_KEY = 'provider-uses-own-credentials' + /** * Get an API key for a specific provider, handling rotation and fallbacks * For use server-side only @@ -740,7 +747,7 @@ export function getApiKey(provider: string, model: string, userProvidedKey?: str // Bedrock uses its own credentials (bedrockAccessKeyId/bedrockSecretKey), not apiKey const isBedrockModel = provider === 'bedrock' || model.startsWith('bedrock/') if (isBedrockModel) { - return 'bedrock-uses-own-credentials' + return PROVIDER_PLACEHOLDER_KEY } const isOpenAIModel = provider === 'openai' diff --git a/apps/sim/serializer/index.ts b/apps/sim/serializer/index.ts index 9c21661deb..485b4e3cc2 100644 --- a/apps/sim/serializer/index.ts +++ b/apps/sim/serializer/index.ts @@ -9,7 +9,7 @@ import { isCanonicalPair, isNonEmptyValue, isSubBlockFeatureEnabled, - isSubBlockHiddenByHostedKey, + isSubBlockHidden, resolveCanonicalMode, } from '@/lib/workflows/subblocks/visibility' import { getBlock } from '@/blocks' @@ -49,7 +49,7 @@ function shouldSerializeSubBlock( canonicalModeOverrides?: CanonicalModeOverrides ): boolean { if (!isSubBlockFeatureEnabled(subBlockConfig)) return false - if (isSubBlockHiddenByHostedKey(subBlockConfig)) return false + if (isSubBlockHidden(subBlockConfig)) return false if (subBlockConfig.mode === 'trigger') { if (!isTriggerContext && !isTriggerCategory) return false diff --git a/apps/sim/tools/params.ts b/apps/sim/tools/params.ts index 925e4fe6f5..9cc8c88365 100644 --- a/apps/sim/tools/params.ts +++ b/apps/sim/tools/params.ts @@ -5,7 +5,7 @@ import { type CanonicalModeOverrides, evaluateSubBlockCondition, isCanonicalPair, - isSubBlockHiddenByHostedKey, + isSubBlockHidden, resolveCanonicalMode, type SubBlockCondition, } from '@/lib/workflows/subblocks/visibility' @@ -320,7 +320,7 @@ export function getToolParametersConfig( ) if (subBlock) { - if (isSubBlockHiddenByHostedKey(subBlock)) { + if (isSubBlockHidden(subBlock)) { toolParam.visibility = 'hidden' } @@ -946,8 +946,8 @@ export function getSubBlocksForToolInput( // Skip trigger-mode-only subblocks if (sb.mode === 'trigger') continue - // Hide tool API key fields when running on hosted Sim - if (isSubBlockHiddenByHostedKey(sb)) continue + // Hide tool API key fields when running on hosted Sim or when env var is set + if (isSubBlockHidden(sb)) continue // Determine the effective param ID (canonical or subblock id) const effectiveParamId = sb.canonicalParamId || sb.id diff --git a/bun.lock b/bun.lock index e773f35d67..98f2b87516 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "simstudio", diff --git a/helm/sim/examples/values-aws.yaml b/helm/sim/examples/values-aws.yaml index c8451efc2a..a5f21eb229 100644 --- a/helm/sim/examples/values-aws.yaml +++ b/helm/sim/examples/values-aws.yaml @@ -45,6 +45,10 @@ app: NODE_ENV: "production" NEXT_TELEMETRY_DISABLED: "1" + # AWS Bedrock - when using IRSA (see serviceAccount below), the default credential chain + # resolves automatically. Setting this hides the credential fields in the Agent block UI. + NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: "true" # Uncomment if using Bedrock with IRSA + # AWS S3 Cloud Storage Configuration (RECOMMENDED for production) # Create S3 buckets in your AWS account and configure IAM permissions AWS_REGION: "us-west-2" diff --git a/helm/sim/examples/values-azure.yaml b/helm/sim/examples/values-azure.yaml index a11b55adc9..30c97798bf 100644 --- a/helm/sim/examples/values-azure.yaml +++ b/helm/sim/examples/values-azure.yaml @@ -47,6 +47,17 @@ app: NODE_ENV: "production" NEXT_TELEMETRY_DISABLED: "1" + # Azure OpenAI / Azure Anthropic (Azure AI Foundry) — set to use server-side credentials. + # When NEXT_PUBLIC_AZURE_CONFIGURED is true, the endpoint/key/version fields are hidden in + # the Agent block UI so users just pick a model and run. + AZURE_OPENAI_ENDPOINT: "" # e.g. https://your-resource.openai.azure.com + AZURE_OPENAI_API_KEY: "" # Azure OpenAI API key + AZURE_OPENAI_API_VERSION: "" # e.g. 2024-07-01-preview + AZURE_ANTHROPIC_ENDPOINT: "" # Azure AI Foundry endpoint for Anthropic models + AZURE_ANTHROPIC_API_KEY: "" # Azure Anthropic API key + AZURE_ANTHROPIC_API_VERSION: "" # Azure Anthropic API version (e.g., 2023-06-01) + NEXT_PUBLIC_AZURE_CONFIGURED: "true" # Set to "true" once credentials are configured above + # Azure Blob Storage Configuration (RECOMMENDED for production) # Create a storage account and containers in your Azure subscription AZURE_ACCOUNT_NAME: "simstudiostorageacct" # Azure storage account name diff --git a/helm/sim/values.yaml b/helm/sim/values.yaml index 92c163b422..9fbe6195b6 100644 --- a/helm/sim/values.yaml +++ b/helm/sim/values.yaml @@ -113,13 +113,24 @@ app: # Google Vertex AI Configuration VERTEX_PROJECT: "" # Google Cloud project ID for Vertex AI VERTEX_LOCATION: "us-central1" # Google Cloud region for Vertex AI (e.g., "us-central1") - + + # Azure OpenAI Configuration (leave empty if not using Azure OpenAI) + AZURE_OPENAI_ENDPOINT: "" # Azure OpenAI service endpoint (e.g., https://your-resource.openai.azure.com) + AZURE_OPENAI_API_KEY: "" # Azure OpenAI API key + AZURE_OPENAI_API_VERSION: "" # Azure OpenAI API version (e.g., 2024-07-01-preview) + + # Azure Anthropic Configuration (leave empty if not using Azure Anthropic via AI Foundry) + AZURE_ANTHROPIC_ENDPOINT: "" # Azure AI Foundry endpoint for Anthropic models + AZURE_ANTHROPIC_API_KEY: "" # Azure Anthropic API key + AZURE_ANTHROPIC_API_VERSION: "" # Azure Anthropic API version (e.g., 2023-06-01) + # AI Provider API Keys (leave empty if not using) OPENAI_API_KEY: "" # Primary OpenAI API key OPENAI_API_KEY_1: "" # Additional OpenAI API key for load balancing OPENAI_API_KEY_2: "" # Additional OpenAI API key for load balancing OPENAI_API_KEY_3: "" # Additional OpenAI API key for load balancing MISTRAL_API_KEY: "" # Mistral AI API key + FIREWORKS_API_KEY: "" # Fireworks AI API key (for hosted model access) ANTHROPIC_API_KEY_1: "" # Primary Anthropic Claude API key ANTHROPIC_API_KEY_2: "" # Additional Anthropic API key for load balancing ANTHROPIC_API_KEY_3: "" # Additional Anthropic API key for load balancing @@ -223,6 +234,18 @@ app: SSO_ENABLED: "" # Enable SSO authentication ("true" to enable) NEXT_PUBLIC_SSO_ENABLED: "" # Show SSO login button in UI ("true" to enable) + # AWS Bedrock Credential Mode + # Set to "true" when the deployment uses AWS default credential chain (IAM roles, instance + # profiles, ECS task roles, IRSA, etc.) instead of explicit access key/secret per workflow. + # When enabled, the AWS Access Key ID and Secret fields are hidden in the Agent block UI. + NEXT_PUBLIC_BEDROCK_DEFAULT_CREDENTIALS: "" # Set to "true" to hide Bedrock credential fields + + # Azure Provider Credential Mode + # Set to "true" when AZURE_OPENAI_ENDPOINT/API_KEY (and/or AZURE_ANTHROPIC_*) are configured + # server-side. When enabled, the Azure endpoint, API key, and API version fields are hidden + # in the Agent block UI — users just pick an Azure model and run. + NEXT_PUBLIC_AZURE_CONFIGURED: "" # Set to "true" to hide Azure credential fields + # AWS S3 Cloud Storage Configuration (optional - for file storage) # If configured, files will be stored in S3 instead of local storage AWS_REGION: "" # AWS region (e.g., "us-east-1")