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);