diff --git a/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.spec.tsx b/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.spec.tsx
index 2ab26649afd..188678a5975 100644
--- a/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.spec.tsx
+++ b/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.spec.tsx
@@ -30,8 +30,13 @@ describe('LlmProviderCreateEditModal', () => {
{
short_name: 'AWS',
regions: [
- { name: 'eu-west-1', city: 'Dublin' },
- { name: 'eu-west-2', city: 'London' },
+ { name: 'eu-west-1', city: 'Dublin', country_code: 'IE' },
+ { name: 'eu-west-2', city: 'London', country_code: 'GB' },
+ { name: 'us-east-2', city: 'Ohio', country_code: 'US' },
+ { name: 'af-south-1', city: 'Cape Town', country_code: 'ZA' },
+ { name: 'ap-east-1', city: 'Hong Kong', country_code: 'HK' },
+ { name: 'ap-south-1', city: 'Mumbai', country_code: 'IN' },
+ { name: 'ap-northeast-1', city: 'Tokyo', country_code: 'JP' },
],
},
{ short_name: 'GCP', regions: [{ name: 'europe-west1', city: 'Belgium' }] },
@@ -129,6 +134,7 @@ describe('LlmProviderCreateEditModal', () => {
const { userEvent } = renderWithProviders()
await selectEvent.select(screen.getByLabelText('Provider'), 'Amazon Bedrock')
+ expect(document.querySelector('[data-testid="selected-icon"] img[src*="/ie.svg"]')).toBeInTheDocument()
await userEvent.type(screen.getByLabelText('Name'), 'EU Bedrock')
await userEvent.type(screen.getByLabelText('Token'), 'aws-credentials')
await selectEvent.select(screen.getByLabelText('AWS region'), 'London (eu-west-2)', {
@@ -151,6 +157,17 @@ describe('LlmProviderCreateEditModal', () => {
)
})
+ it('shows the country flag for every AWS region option', async () => {
+ renderWithProviders()
+
+ await selectEvent.select(screen.getByLabelText('Provider'), 'Amazon Bedrock')
+ await selectEvent.openMenu(screen.getByLabelText('AWS region'))
+
+ for (const code of ['ie', 'gb', 'us', 'za', 'hk', 'in', 'jp']) {
+ expect(document.querySelector(`img[src$="/${code}.svg"]`)).toBeInTheDocument()
+ }
+ })
+
it('should select eu-west-1 as the default AWS region', async () => {
const { userEvent } = renderWithProviders()
diff --git a/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.tsx b/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.tsx
index 25dc02936af..41bdcad00e2 100644
--- a/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.tsx
+++ b/libs/domains/organizations/feature/src/lib/llm-provider-create-edit-modal/llm-provider-create-edit-modal.tsx
@@ -8,7 +8,8 @@ import {
} from 'qovery-typescript-axios'
import { Controller, FormProvider, useForm } from 'react-hook-form'
import { useCloudProviders } from '@qovery/domains/cloud-providers/feature'
-import { InputSelect, InputText, InputTextArea, ModalCrud, useModal } from '@qovery/shared/ui'
+import { IconFlag, InputSelect, InputText, InputTextArea, ModalCrud, useModal } from '@qovery/shared/ui'
+import { getAwsLocationFlagCode } from '@qovery/shared/util-js'
import { useCreateLlmProvider } from '../hooks/use-create-llm-provider/use-create-llm-provider'
import { useEditLlmProvider } from '../hooks/use-edit-llm-provider/use-edit-llm-provider'
@@ -55,10 +56,14 @@ const SCOPE_OPTIONS = [
function BedrockRegionSelect({ value, onChange }: { value: string; onChange: (value: string) => void }) {
const { data: cloudProviders = [], isError, isLoading } = useCloudProviders()
const awsRegions = cloudProviders.find(({ short_name }) => short_name === CloudProviderEnum.AWS)?.regions ?? []
- const options = awsRegions.map(({ city, name }) => ({ label: `${city} (${name})`, value: name }))
+ const options = awsRegions.map(({ city, country_code, name }) => {
+ const flagCode = country_code || getAwsLocationFlagCode(name)
+ return { label: `${city} (${name})`, value: name, icon: flagCode ? : undefined }
+ })
if (value && !options.some(({ value: region }) => region === value)) {
- options.push({ label: value, value })
+ const flagCode = getAwsLocationFlagCode(value)
+ options.push({ label: value, value, icon: flagCode ? : undefined })
}
return (
diff --git a/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.spec.tsx b/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.spec.tsx
index 07b94632c4f..c6a6a488c4c 100644
--- a/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.spec.tsx
+++ b/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.spec.tsx
@@ -30,6 +30,7 @@ const bedrockProvider: LlmProviderResponse = {
id: 'provider-2',
name: 'Bedrock token',
type: LlmProviderType.BEDROCK,
+ region: 'eu-west-1',
}
describe('AgenticWorkflowAiConfigurationSettings', () => {
@@ -104,13 +105,19 @@ describe('AgenticWorkflowAiConfigurationSettings', () => {
it('shows the model selector for Bedrock', () => {
useLlmProviderModelsSpy.mockReturnValue({
- data: [{ id: 'eu.anthropic.claude-opus-5', display_name: 'Claude Opus 5', created_at: null }],
+ data: [{ id: 'anthropic.claude-opus-5', display_name: 'Claude Opus 5', created_at: null }],
isError: false,
isLoading: false,
})
renderWithProviders(
-
+
{(form) => (
{
)
expect(screen.getByLabelText('Model')).toBeInTheDocument()
+ expect(document.querySelector('[data-testid="selected-icon"] img[src*="/eu.svg"]')).toBeInTheDocument()
expect(screen.queryByText('Cloud settings JSON')).not.toBeInTheDocument()
})
})
diff --git a/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.tsx b/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.tsx
index 0560aa457f1..75c5786455a 100644
--- a/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.tsx
+++ b/libs/domains/service-settings/feature/src/lib/agentic-workflow-settings/agentic-workflow-ai-configuration-settings/agentic-workflow-ai-configuration-settings.tsx
@@ -16,6 +16,7 @@ export function AgenticWorkflowAiConfigurationSettings({
}) {
const llmProviderId = form.watch('llmProviderId')
const currentModelType = form.watch('modelType')
+ const providerRegion = llmProviders.find(({ id }) => id === llmProviderId)?.region
return (
<>
@@ -47,6 +48,7 @@ export function AgenticWorkflowAiConfigurationSettings({
diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.tsx
index f52fa40219a..dfce8874fc2 100644
--- a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.tsx
+++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-configuration/agentic-workflow-configuration.tsx
@@ -1182,6 +1182,7 @@ export function AgenticWorkflowConfiguration() {
id === values.llmProviderId)?.region}
settings={field.value}
onChange={field.onChange}
/>
diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.spec.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.spec.tsx
index f08f8ceb697..2ed65fc4008 100644
--- a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.spec.tsx
+++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.spec.tsx
@@ -64,6 +64,7 @@ describe('AgenticWorkflowModelSetting', () => {
expect(screen.getByLabelText('Model')).toBeInTheDocument()
expect(screen.queryByText('Bedrock settings')).not.toBeInTheDocument()
+ expect(screen.queryByTestId('selected-icon')).not.toBeInTheDocument()
await selectEvent.select(screen.getByLabelText('Model'), 'Claude Sonnet', {
container: document.body,
})
@@ -126,7 +127,7 @@ describe('AgenticWorkflowModelSetting', () => {
it('lists Bedrock models and serializes the first model', async () => {
mockModels = [
{ id: 'eu.anthropic.claude-opus-5', display_name: 'Claude Opus 5', created_at: null },
- { id: 'eu.anthropic.claude-sonnet-4', display_name: 'Claude Sonnet 4', created_at: null },
+ { id: 'us.anthropic.claude-sonnet-4', display_name: 'Claude Sonnet 4', created_at: null },
]
const onChange = jest.fn()
@@ -134,16 +135,55 @@ describe('AgenticWorkflowModelSetting', () => {
)
expect(screen.getByLabelText('Model')).toBeInTheDocument()
+ await selectEvent.openMenu(screen.getByLabelText('Model'))
+ expect(document.querySelector('img[src*="/eu.svg"]')).toBeInTheDocument()
+ expect(document.querySelector('img[src*="/us.svg"]')).toBeInTheDocument()
await waitFor(() => expect(onChange).toHaveBeenCalledTimes(1))
expect(JSON.parse(onChange.mock.calls[0][0])).toEqual({ model: 'eu.anthropic.claude-opus-5' })
})
+ it('uses the Bedrock token region for a model without a geographic prefix', async () => {
+ mockModels = [{ id: 'anthropic.claude-sonnet-4', display_name: 'Claude Sonnet 4', created_at: null }]
+
+ renderWithProviders(
+
+ )
+
+ expect(document.querySelector('[data-testid="selected-icon"] img[src*="/in.svg"]')).toBeInTheDocument()
+ await selectEvent.openMenu(screen.getByLabelText('Model'))
+ expect(document.querySelector('img[src*="/in.svg"]')).toBeInTheDocument()
+ })
+
+ it('shows a globe for a global Bedrock model', () => {
+ mockModels = [{ id: 'global.anthropic.claude-sonnet-4', display_name: 'Claude Sonnet 4', created_at: null }]
+
+ renderWithProviders(
+
+ )
+
+ expect(document.querySelector('[data-testid="selected-icon"] .fa-globe')).toBeInTheDocument()
+ expect(document.querySelector('img[src*="/eu.svg"]')).not.toBeInTheDocument()
+ })
+
it('repairs invalid Bedrock settings with the first available model', async () => {
mockModels = [{ id: 'eu.anthropic.claude-opus-5', display_name: 'Claude Opus 5', created_at: null }]
const onChange = jest.fn()
diff --git a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.tsx b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.tsx
index 98e4d2c1f63..c8702765061 100644
--- a/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.tsx
+++ b/libs/domains/services/feature/src/lib/service-creation-flow/agentic-workflow/agentic-workflow-model-setting.tsx
@@ -2,7 +2,8 @@ import { LlmProviderType, type LlmProviderType as LlmProviderTypeValue } from 'q
import { useEffect, useRef } from 'react'
import { match } from 'ts-pattern'
import { useLlmProviderModels } from '@qovery/domains/organizations/feature'
-import { InputSelect } from '@qovery/shared/ui'
+import { Icon, IconFlag, InputSelect } from '@qovery/shared/ui'
+import { getAwsLocationFlagCode } from '@qovery/shared/util-js'
function parseModelSettings(value: string): Record | undefined {
try {
@@ -28,6 +29,7 @@ export function updateAgenticWorkflowModel(value: string, model: string) {
export interface AgenticWorkflowModelSettingProps {
llmProviderId: string
providerType?: LlmProviderTypeValue
+ providerRegion?: string | null
settings: string
onChange: (value: string) => void
}
@@ -35,6 +37,7 @@ export interface AgenticWorkflowModelSettingProps {
export function AgenticWorkflowModelSetting({
llmProviderId,
providerType,
+ providerRegion,
settings,
onChange,
}: AgenticWorkflowModelSettingProps) {
@@ -96,7 +99,19 @@ export function AgenticWorkflowModelSetting({
if (!hasModelProvider) return null
- const modelOptions = models.map(({ id, display_name }) => ({ value: id, label: display_name }))
+ const modelOptions = models.map(({ id, display_name }) => {
+ const isCrossRegionProfile = providerType === LlmProviderType.BEDROCK && /^(global|apac)\./.test(id)
+ const flagCode =
+ providerType === LlmProviderType.BEDROCK && !isCrossRegionProfile
+ ? getAwsLocationFlagCode(id) ?? getAwsLocationFlagCode(providerRegion ?? '')
+ : undefined
+ const icon = flagCode ? (
+
+ ) : isCrossRegionProfile ? (
+
+ ) : undefined
+ return { value: id, label: display_name, icon }
+ })
const hasModelsError = isError || (!isLoading && models.length === 0)
const modelsError = match([isError, providerType === LlmProviderType.BEDROCK])
.with([true, true], () => 'We couldn’t load models. Check this token’s AWS credentials and region.')
diff --git a/libs/shared/util-js/src/index.ts b/libs/shared/util-js/src/index.ts
index bed190b8758..df938c89e6a 100644
--- a/libs/shared/util-js/src/index.ts
+++ b/libs/shared/util-js/src/index.ts
@@ -1,3 +1,4 @@
+export * from './lib/aws-location-flag-code'
export * from './lib/build-git-repo-url'
export * from './lib/compute-available-environment-variable-scope'
export * from './lib/container-registry-kind-to-icon'
diff --git a/libs/shared/util-js/src/lib/aws-location-flag-code.spec.ts b/libs/shared/util-js/src/lib/aws-location-flag-code.spec.ts
new file mode 100644
index 00000000000..c32593b0831
--- /dev/null
+++ b/libs/shared/util-js/src/lib/aws-location-flag-code.spec.ts
@@ -0,0 +1,18 @@
+import { getAwsLocationFlagCode } from './aws-location-flag-code'
+
+describe('getAwsLocationFlagCode', () => {
+ it.each([
+ ['eu-west-1', 'EU'],
+ ['eu.anthropic.claude-sonnet', 'EU'],
+ ['us-east-1', 'US'],
+ ['us.anthropic.claude-sonnet', 'US'],
+ ['af-south-1', 'ZA'],
+ ['ap-east-1', 'HK'],
+ ['ap-south-2', 'IN'],
+ ['ap-northeast-1', 'JP'],
+ ['ap-southeast-1', 'SG'],
+ ['global.anthropic.claude-sonnet', undefined],
+ ])('maps %s to %s', (location, expected) => {
+ expect(getAwsLocationFlagCode(location)).toBe(expected)
+ })
+})
diff --git a/libs/shared/util-js/src/lib/aws-location-flag-code.ts b/libs/shared/util-js/src/lib/aws-location-flag-code.ts
new file mode 100644
index 00000000000..5403b2b32fa
--- /dev/null
+++ b/libs/shared/util-js/src/lib/aws-location-flag-code.ts
@@ -0,0 +1,28 @@
+const AWS_REGION_COUNTRY_CODES: Record = {
+ 'af-south-1': 'ZA',
+ 'ap-east-1': 'HK',
+ 'ap-northeast-1': 'JP',
+ 'ap-northeast-2': 'KR',
+ 'ap-northeast-3': 'JP',
+ 'ap-south-1': 'IN',
+ 'ap-south-2': 'IN',
+ 'ap-southeast-1': 'SG',
+ 'ap-southeast-2': 'AU',
+ 'ap-southeast-3': 'ID',
+ 'ap-southeast-4': 'AU',
+ 'ca-central-1': 'CA',
+ 'ca-west-1': 'CA',
+ 'cn-north-1': 'CN',
+ 'cn-northwest-1': 'CN',
+ 'il-central-1': 'IL',
+ 'me-central-1': 'AE',
+ 'me-south-1': 'BH',
+ 'mx-central-1': 'MX',
+ 'sa-east-1': 'BR',
+}
+
+export function getAwsLocationFlagCode(location: string): string | undefined {
+ if (/^eu[.-]/.test(location)) return 'EU'
+ if (/^us[.-]/.test(location)) return 'US'
+ return AWS_REGION_COUNTRY_CODES[location]
+}