diff --git a/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAiProviderListCard.tsx b/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAiProviderListCard.tsx index 5218967b48..18ccc57d8d 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAiProviderListCard.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/ai/components/SettingsAdminAiProviderListCard.tsx @@ -33,7 +33,9 @@ const getProviderDescription = (provider: AiProviderItem): string => { parts.push(provider.baseUrl); } - if (provider.apiKey) { + if (provider.authType === 'role') { + parts.push(t`IAM role`); + } else if (provider.apiKey) { parts.push(t`API key configured`); } else if (provider.hasAccessKey) { parts.push(t`IAM credentials`); @@ -42,6 +44,9 @@ const getProviderDescription = (provider: AiProviderItem): string => { return parts.join(' ยท '); }; +const isProviderConfigured = (provider: AiProviderItem): boolean => + !!(provider.authType || provider.apiKey || provider.hasAccessKey); + export const SettingsAdminAiProviderListCard = ({ providers, showAddButton = true, @@ -70,7 +75,7 @@ export const SettingsAdminAiProviderListCard = ({ getItemLabel={(provider) => provider.label ?? provider.id} getItemDescription={getProviderDescription} RowRightComponent={({ item: provider }) => - provider.apiKey || provider.hasAccessKey ? ( + isProviderConfigured(provider) ? ( ) : ( diff --git a/packages/twenty-front/src/modules/settings/admin-panel/ai/types/AiProviderAuthType.ts b/packages/twenty-front/src/modules/settings/admin-panel/ai/types/AiProviderAuthType.ts new file mode 100644 index 0000000000..b43fe0eb90 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/admin-panel/ai/types/AiProviderAuthType.ts @@ -0,0 +1 @@ +export type AiProviderAuthType = 'key' | 'credentials' | 'role'; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/ai/types/AiProviderItem.ts b/packages/twenty-front/src/modules/settings/admin-panel/ai/types/AiProviderItem.ts index 52a6fa9b68..7c512af1f0 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/ai/types/AiProviderItem.ts +++ b/packages/twenty-front/src/modules/settings/admin-panel/ai/types/AiProviderItem.ts @@ -1,5 +1,6 @@ import { type AiSdkPackage, type DataResidency } from 'twenty-shared/ai'; +import { type AiProviderAuthType } from '@/settings/admin-panel/ai/types/AiProviderAuthType'; import { type AiProviderSource } from '@/settings/admin-panel/ai/types/AiProviderSource'; // AiProviderItem = RawAiProviderConfig (from the backend's Record { label: t`Credentials`, value: t`IAM credentials configured`, }); + } else if (provider.authType === 'role') { + items.push({ + Icon: IconKey, + label: t`Credentials`, + value: t`IAM role (instance profile)`, + }); } if (isCustomProvider && provider.dataResidency) { diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts index d97708be8e..6a212b32c4 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts @@ -357,6 +357,7 @@ export class AdminPanelResolver { npm: config.npm, label: config.label ?? key, source: isCatalog ? 'catalog' : 'custom', + ...(config.authType && { authType: config.authType }), ...(config.name && { name: config.name }), ...(config.baseUrl && { baseUrl: config.baseUrl }), ...(config.region && { region: config.region }), diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service.ts index 87b9fb1615..a3968aad4e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service.ts @@ -26,6 +26,7 @@ import { isAutoSelectModelId } from 'twenty-shared/utils'; import { DEFAULT_MAX_OUTPUT_TOKENS } from 'src/engine/metadata-modules/ai/ai-models/types/default-max-output-tokens.const'; import { buildCompositeModelId } from 'src/engine/metadata-modules/ai/ai-models/utils/composite-model-id.util'; import { inferModelFamily } from 'src/engine/metadata-modules/ai/ai-models/utils/infer-model-family.util'; +import { isProviderConfigured } from 'src/engine/metadata-modules/ai/ai-models/utils/is-provider-configured.util'; import { isModelAllowedByWorkspace, type WorkspaceModelAvailabilitySettings, @@ -84,9 +85,7 @@ export class AiModelRegistryService { continue; } - const isConfigured = !!(config.apiKey || config.accessKeyId); - - const sdkInstance = isConfigured + const sdkInstance = isProviderConfigured(config) ? this.sdkProviderFactory.createProvider(providerKey, config) : undefined; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/ai-provider-auth-type.type.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/ai-provider-auth-type.type.ts new file mode 100644 index 0000000000..b43fe0eb90 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/ai-provider-auth-type.type.ts @@ -0,0 +1 @@ +export type AiProviderAuthType = 'key' | 'credentials' | 'role'; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/ai-provider-config.type.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/ai-provider-config.type.ts index 2fb663331b..bba8af5e32 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/ai-provider-config.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/ai-provider-config.type.ts @@ -1,5 +1,6 @@ import { type AiSdkPackage, type DataResidency } from 'twenty-shared/ai'; +import { type AiProviderAuthType } from 'src/engine/metadata-modules/ai/ai-models/types/ai-provider-auth-type.type'; import { type AiProviderModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-provider-model-config.type'; export type AiProviderConfig = { @@ -7,6 +8,7 @@ export type AiProviderConfig = { // Optional provider display/catalog name (e.g. models.dev label). Not a model name; per-model names live on `models[].name`. name?: string; label?: string; + authType?: AiProviderAuthType; apiKey?: string; baseUrl?: string; region?: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-provider-configured.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-provider-configured.util.ts new file mode 100644 index 0000000000..fc57588628 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-provider-configured.util.ts @@ -0,0 +1,4 @@ +import { type AiProviderConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-provider-config.type'; + +export const isProviderConfigured = (config: AiProviderConfig): boolean => + !!(config.apiKey || config.accessKeyId || config.authType);