From 40e9940752771f5d977ca721a17898f21181d6af Mon Sep 17 00:00:00 2001 From: ehconitin Date: Sat, 21 Mar 2026 01:31:24 +0530 Subject: [PATCH 01/13] feat: add hover on rounded icon button --- .../src/input/button/components/RoundedIconButton.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/twenty-ui/src/input/button/components/RoundedIconButton.tsx b/packages/twenty-ui/src/input/button/components/RoundedIconButton.tsx index 07e86e1e656a8..a057afca7aefe 100644 --- a/packages/twenty-ui/src/input/button/components/RoundedIconButton.tsx +++ b/packages/twenty-ui/src/input/button/components/RoundedIconButton.tsx @@ -23,6 +23,10 @@ const StyledIconButton = styled.button<{ color 0.1s ease-in-out, background 0.1s ease-in-out; + &:hover:not(:disabled) { + background: ${themeCssVariables.color.blue10}; + } + &:disabled { background: ${themeCssVariables.background.quaternary}; color: ${themeCssVariables.font.color.tertiary}; From 681fb569aab6bb88f5bea7269e7964a42f2b8722 Mon Sep 17 00:00:00 2001 From: ehconitin Date: Tue, 24 Mar 2026 02:09:10 +0530 Subject: [PATCH 02/13] Add model selector dropdown to chat composer and fix ModelFamily enum casing --- .../ai/components/AIChatEditorSection.tsx | 104 +++++-- .../ai/ai-models/ai-providers.json | 270 +++++++++--------- .../ai/ai-models/types/model-family.enum.ts | 10 +- 3 files changed, 226 insertions(+), 158 deletions(-) diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx index c04638e3bdef4..7bc9326b106e1 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx @@ -1,6 +1,8 @@ import { styled } from '@linaria/react'; +import { useMutation } from '@apollo/client/react'; import { EditorContent } from '@tiptap/react'; -import { LightButton } from 'twenty-ui/input'; +import { t } from '@lingui/core/macro'; +import { IconTwentyStar } from 'twenty-ui/display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; import { AIChatEmptyState } from '@/ai/components/AIChatEmptyState'; @@ -11,11 +13,19 @@ import { AIChatContextUsageButton } from '@/ai/components/internal/AIChatContext import { AIChatEditorFocusEffect } from '@/ai/components/internal/AIChatEditorFocusEffect'; import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoader'; import { SendMessageButton } from '@/ai/components/internal/SendMessageButton'; +import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; import { useAIChatEditor } from '@/ai/hooks/useAIChatEditor'; -import { useAiModelLabel } from '@/ai/hooks/useAiModelOptions'; +import { useWorkspaceAiModelAvailability } from '@/ai/hooks/useWorkspaceAiModelAvailability'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; +import { aiModelsState } from '@/client-config/states/aiModelsState'; +import { getModelIcon } from '@/settings/admin-panel/ai/utils/getModelIcon'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { Select } from '@/ui/input/components/Select'; +import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { UpdateWorkspaceDocument } from '~/generated-metadata/graphql'; const StyledInputArea = styled.div<{ isMobile: boolean }>` align-items: flex-end; @@ -102,24 +112,77 @@ const StyledRightButtonsContainer = styled.div` gap: ${themeCssVariables.spacing[1]}; `; -const StyledReadOnlyModelButtonContainer = styled.div` - > * { - cursor: default; - - &:hover, - &:active { - background: transparent; - } - } -`; - export const AIChatEditorSection = () => { const isMobile = useIsMobile(); - const currentWorkspace = useAtomStateValue(currentWorkspaceState); - const smartModelLabel = useAiModelLabel(currentWorkspace?.smartModel, false); + const { enqueueErrorSnackBar } = useSnackBar(); + const [currentWorkspace, setCurrentWorkspace] = + useAtomState(currentWorkspaceState); + const [updateWorkspace] = useMutation(UpdateWorkspaceDocument); + const aiModels = useAtomStateValue(aiModelsState); + const { enabledModels } = useWorkspaceAiModelAvailability(); const { editor, handleSendAndClear } = useAIChatEditor(); + const currentSmartModel = currentWorkspace?.smartModel; + + const buildVirtualModelOption = (virtualModelId: string) => { + const virtualModel = aiModels.find( + (model) => model.modelId === virtualModelId, + ); + + return virtualModel + ? { + value: virtualModelId, + label: virtualModel.label, + Icon: IconTwentyStar, + } + : null; + }; + + const smartAutoOption = buildVirtualModelOption(DEFAULT_SMART_MODEL); + + const smartModelOptions = enabledModels.map((model) => ({ + value: model.modelId, + label: model.label, + Icon: getModelIcon(model.modelFamily, model.providerName), + })); + + if (smartAutoOption !== null) { + smartModelOptions.unshift(smartAutoOption); + } + + const handleSmartModelChange = async (value: string) => { + if (!currentWorkspace?.id) { + return; + } + + const previousValue = currentWorkspace.smartModel; + + try { + setCurrentWorkspace({ + ...currentWorkspace, + smartModel: value, + }); + + await updateWorkspace({ + variables: { + input: { + smartModel: value, + }, + }, + }); + } catch { + setCurrentWorkspace({ + ...currentWorkspace, + smartModel: previousValue, + }); + + enqueueErrorSnackBar({ + message: t`Failed to update model`, + }); + } + }; + return ( <> @@ -139,9 +202,14 @@ export const AIChatEditorSection = () => { - - - + diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts index 9bd1d07701830..1d8a8067ce0c0 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts @@ -15,6 +15,7 @@ import { agentChatDraftsByThreadIdState, } from '@/ai/states/agentChatDraftsByThreadIdState'; import { agentChatInputState } from '@/ai/states/agentChatInputState'; +import { agentChatUserSelectedModelState } from '@/ai/states/agentChatUserSelectedModelState'; import { REST_API_BASE_URL } from '@/apollo/constant/rest-api-base-url'; import { getTokenPair } from '@/apollo/utils/getTokenPair'; import { renewToken } from '@/auth/services/AuthService'; @@ -248,6 +249,9 @@ export const useAgentChat = ( })); const browsingContext = getBrowsingContext(); + const userSelectedModel = store.get( + agentChatUserSelectedModelState.atom, + ); sendMessage( { @@ -258,6 +262,9 @@ export const useAgentChat = ( body: { threadId, browsingContext, + ...(isDefined(userSelectedModel) && { + modelId: userSelectedModel, + }), }, }, ); diff --git a/packages/twenty-front/src/modules/ai/states/agentChatUserSelectedModelState.ts b/packages/twenty-front/src/modules/ai/states/agentChatUserSelectedModelState.ts new file mode 100644 index 0000000000000..0383c09b14ec8 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/states/agentChatUserSelectedModelState.ts @@ -0,0 +1,8 @@ +import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState'; + +export const agentChatUserSelectedModelState = createAtomState({ + key: 'ai/agentChatUserSelectedModel', + defaultValue: null, + useLocalStorage: true, + localStorageOptions: { getOnInit: true }, +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts index 8dfb98ccc235f..3181639df65ea 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts @@ -59,6 +59,7 @@ export class AgentChatController { threadId: string; messages: ExtendedUIMessage[]; browsingContext?: BrowsingContextType | null; + modelId?: string; }, @AuthUserWorkspaceId() userWorkspaceId: string, @AuthWorkspace() workspace: WorkspaceEntity, @@ -71,7 +72,7 @@ export class AgentChatController { ); } - const resolvedModelId = workspace.smartModel; + const resolvedModelId = body.modelId ?? workspace.smartModel; this.aiModelRegistryService.validateModelAvailability( resolvedModelId, @@ -96,6 +97,7 @@ export class AgentChatController { threadId: body.threadId, messages: body.messages, browsingContext: body.browsingContext ?? null, + modelId: body.modelId, userWorkspaceId, workspace, response, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts index 34f2647319f8e..ffb6905406f2d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts @@ -33,6 +33,7 @@ export type StreamAgentChatOptions = { response: Response; messages: ExtendedUIMessage[]; browsingContext: BrowsingContextType | null; + modelId?: string; }; @Injectable() @@ -51,6 +52,7 @@ export class AgentChatStreamingService { messages, browsingContext, response, + modelId, }: StreamAgentChatOptions) { const thread = await this.threadRepository.findOne({ where: { @@ -114,6 +116,7 @@ export class AgentChatStreamingService { messages, browsingContext, onCodeExecutionUpdate, + modelId, }); let streamUsage = { diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts index 18615417e583e..bbcdfce08824a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/chat-execution.service.ts @@ -60,6 +60,7 @@ export type ChatExecutionOptions = { messages: UIMessage[]; browsingContext: BrowsingContextType | null; onCodeExecutionUpdate?: CodeExecutionStreamEmitter; + modelId?: string; }; export type ChatExecutionResult = { @@ -89,6 +90,7 @@ export class ChatExecutionService { messages, browsingContext, onCodeExecutionUpdate, + modelId, }: ChatExecutionOptions): Promise { const { actorContext, roleId, userId, userContext } = await this.agentActorContextService.buildUserAndAgentActorContext( @@ -128,13 +130,16 @@ export class ChatExecutionService { toolContext, ); - const modelId = workspace.smartModel; + const resolvedModelId = modelId ?? workspace.smartModel; - this.aiModelRegistryService.validateModelAvailability(modelId, workspace); + this.aiModelRegistryService.validateModelAvailability( + resolvedModelId, + workspace, + ); const registeredModel = await this.aiModelRegistryService.resolveModelForAgent({ - modelId, + modelId: resolvedModelId, }); const modelConfig = this.aiModelRegistryService.getEffectiveModelConfig( From bca4d8113e46c43563b8cf493496846ca8fe1a83 Mon Sep 17 00:00:00 2001 From: ehconitin Date: Tue, 24 Mar 2026 13:34:34 +0530 Subject: [PATCH 05/13] Validate stored model against current workspace before using it --- .../ai/components/AIChatEditorSection.tsx | 16 +++++++++++++--- .../src/modules/ai/hooks/useAgentChat.ts | 4 +--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx index d3af28c8642e0..4649da3e19140 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx @@ -1,5 +1,6 @@ import { styled } from '@linaria/react'; import { EditorContent } from '@tiptap/react'; +import { isDefined } from 'twenty-shared/utils'; import { IconTwentyStar } from 'twenty-ui/display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; @@ -118,9 +119,6 @@ export const AIChatEditorSection = () => { const { editor, handleSendAndClear } = useAIChatEditor(); - const selectedModel = - agentChatUserSelectedModel ?? currentWorkspace?.smartModel; - const buildVirtualModelOption = (virtualModelId: string) => { const virtualModel = aiModels.find( (model) => model.modelId === virtualModelId, @@ -146,6 +144,16 @@ export const AIChatEditorSection = () => { })), ]; + const isUserModelAvailable = + isDefined(agentChatUserSelectedModel) && + smartModelOptions.some( + (option) => option.value === agentChatUserSelectedModel, + ); + + const selectedModel = isUserModelAvailable + ? agentChatUserSelectedModel + : currentWorkspace?.smartModel; + const handleModelChange = (value: string) => { setAgentChatUserSelectedModel(value); }; @@ -175,6 +183,8 @@ export const AIChatEditorSection = () => { onChange={handleModelChange} options={smartModelOptions} selectSizeVariant="small" + withSearchInput + dropdownOffset={{ x: 0, y: 8 }} /> diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts index 1d8a8067ce0c0..472b28cedad5d 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts @@ -249,9 +249,7 @@ export const useAgentChat = ( })); const browsingContext = getBrowsingContext(); - const userSelectedModel = store.get( - agentChatUserSelectedModelState.atom, - ); + const userSelectedModel = store.get(agentChatUserSelectedModelState.atom); sendMessage( { From 368a05b6ae8322c85d9bf86daff6dc326dd5a3d9 Mon Sep 17 00:00:00 2001 From: ehconitin Date: Tue, 24 Mar 2026 13:37:07 +0530 Subject: [PATCH 06/13] chore: lint --- .../api/graphql/direct-execution/direct-execution.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/twenty-server/src/engine/api/graphql/direct-execution/direct-execution.service.ts b/packages/twenty-server/src/engine/api/graphql/direct-execution/direct-execution.service.ts index 0007fdc18bdaa..59ae12984a6b2 100644 --- a/packages/twenty-server/src/engine/api/graphql/direct-execution/direct-execution.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/direct-execution/direct-execution.service.ts @@ -280,6 +280,7 @@ export class DirectExecutionService { ); } + // oxlint-disable-next-line no-explicit-any private formatError(error: any, req: Request): GraphQLFormattedError { try { workspaceQueryRunnerGraphqlApiExceptionHandler(error); From a74dad5e42c4dac4d4fd32941372368bf952af51 Mon Sep 17 00:00:00 2001 From: ehconitin Date: Tue, 24 Mar 2026 13:55:26 +0530 Subject: [PATCH 07/13] Refactor AIChatEditorSection to use resolved model ID and update state management for agent chat model selection --- .../ai/components/AIChatEditorSection.tsx | 24 +++++------------ .../src/modules/ai/hooks/useAgentChat.ts | 9 ++++--- .../modules/ai/hooks/useAgentChatModelId.ts | 26 +++++++++++++++++++ 3 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 packages/twenty-front/src/modules/ai/hooks/useAgentChatModelId.ts diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx index 4649da3e19140..4bb712cb2ced0 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx @@ -1,6 +1,5 @@ import { styled } from '@linaria/react'; import { EditorContent } from '@tiptap/react'; -import { isDefined } from 'twenty-shared/utils'; import { IconTwentyStar } from 'twenty-ui/display'; import { themeCssVariables } from 'twenty-ui/theme-constants'; @@ -14,14 +13,14 @@ import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoa import { SendMessageButton } from '@/ai/components/internal/SendMessageButton'; import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; import { useAIChatEditor } from '@/ai/hooks/useAIChatEditor'; +import { useAgentChatModelId } from '@/ai/hooks/useAgentChatModelId'; import { useWorkspaceAiModelAvailability } from '@/ai/hooks/useWorkspaceAiModelAvailability'; import { agentChatUserSelectedModelState } from '@/ai/states/agentChatUserSelectedModelState'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { aiModelsState } from '@/client-config/states/aiModelsState'; import { getModelIcon } from '@/settings/admin-panel/ai/utils/getModelIcon'; import { Select } from '@/ui/input/components/Select'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; -import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState'; +import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; const StyledInputArea = styled.div<{ isMobile: boolean }>` @@ -111,11 +110,12 @@ const StyledRightButtonsContainer = styled.div` export const AIChatEditorSection = () => { const isMobile = useIsMobile(); - const currentWorkspace = useAtomStateValue(currentWorkspaceState); const aiModels = useAtomStateValue(aiModelsState); const { enabledModels } = useWorkspaceAiModelAvailability(); - const [agentChatUserSelectedModel, setAgentChatUserSelectedModel] = - useAtomState(agentChatUserSelectedModelState); + const setAgentChatUserSelectedModel = useSetAtomState( + agentChatUserSelectedModelState, + ); + const { resolvedModelId } = useAgentChatModelId(); const { editor, handleSendAndClear } = useAIChatEditor(); @@ -144,16 +144,6 @@ export const AIChatEditorSection = () => { })), ]; - const isUserModelAvailable = - isDefined(agentChatUserSelectedModel) && - smartModelOptions.some( - (option) => option.value === agentChatUserSelectedModel, - ); - - const selectedModel = isUserModelAvailable - ? agentChatUserSelectedModel - : currentWorkspace?.smartModel; - const handleModelChange = (value: string) => { setAgentChatUserSelectedModel(value); }; @@ -179,7 +169,7 @@ export const AIChatEditorSection = () => { { - const currentWorkspace = useAtomStateValue(currentWorkspaceState); const { enabledModels } = useWorkspaceAiModelAvailability(); const agentChatUserSelectedModel = useAtomStateValue( agentChatUserSelectedModelState, ); const isUserModelAvailable = - isDefined(agentChatUserSelectedModel) && - enabledModels.some( - (model) => model.modelId === agentChatUserSelectedModel, - ); + !isDefined(agentChatUserSelectedModel) || + enabledModels.some((model) => model.modelId === agentChatUserSelectedModel); - const resolvedModelId = isUserModelAvailable + const selectedModelId = isUserModelAvailable ? agentChatUserSelectedModel - : currentWorkspace?.smartModel; + : null; - return { resolvedModelId }; + const modelIdForRequest = isDefined(selectedModelId) + ? selectedModelId + : undefined; + + return { selectedModelId, modelIdForRequest }; }; diff --git a/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts b/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts index 4680a1567ce98..643506a091091 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts @@ -5,12 +5,13 @@ import { aiModelsState } from '@/client-config/states/aiModelsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { type ClientAiModelConfig } from '~/generated-metadata/graphql'; -const VIRTUAL_MODEL_IDS: Set = new Set([ +const DEFAULT_MODEL_SENTINEL_IDS: Set = new Set([ DEFAULT_SMART_MODEL, DEFAULT_FAST_MODEL, ]); -const isVirtualModel = (modelId: string) => VIRTUAL_MODEL_IDS.has(modelId); +const isDefaultModelSentinel = (modelId: string) => + DEFAULT_MODEL_SENTINEL_IDS.has(modelId); export const useWorkspaceAiModelAvailability = () => { const aiModels = useAtomStateValue(aiModelsState); @@ -23,7 +24,7 @@ export const useWorkspaceAiModelAvailability = () => { modelId: string, model?: ClientAiModelConfig, ): boolean => { - if (isVirtualModel(modelId)) { + if (isDefaultModelSentinel(modelId)) { return true; } @@ -35,7 +36,7 @@ export const useWorkspaceAiModelAvailability = () => { }; const realModels = aiModels.filter( - (model) => !isVirtualModel(model.modelId) && !model.isDeprecated, + (model) => !isDefaultModelSentinel(model.modelId) && !model.isDeprecated, ); const enabledModels = realModels.filter((model) => diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx index f79ff8e176b45..f2990ec00658a 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx @@ -51,22 +51,24 @@ export const SettingsAIModelsTab = () => { const currentSmartModel = currentWorkspace?.smartModel; const currentFastModel = currentWorkspace?.fastModel; - const buildVirtualModelOption = (virtualModelId: string) => { - const virtualModel = aiModels.find( - (model) => model.modelId === virtualModelId, + const buildDefaultModelOption = (defaultModelId: string) => { + const defaultModel = aiModels.find( + (model) => model.modelId === defaultModelId, ); - return virtualModel + return defaultModel ? { - value: virtualModelId, - label: virtualModel.label, + value: defaultModelId, + label: defaultModel.label, Icon: IconTwentyStar, } : null; }; - const smartAutoOption = buildVirtualModelOption(DEFAULT_SMART_MODEL); - const fastAutoOption = buildVirtualModelOption(DEFAULT_FAST_MODEL); + const serverDefaultSmartModelOption = + buildDefaultModelOption(DEFAULT_SMART_MODEL); + const serverDefaultFastModelOption = + buildDefaultModelOption(DEFAULT_FAST_MODEL); const modelOptions = enabledModels.map((model) => { const residencyFlag = model.dataResidency @@ -82,14 +84,14 @@ export const SettingsAIModelsTab = () => { const smartModelOptions = [...modelOptions]; - if (smartAutoOption !== null) { - smartModelOptions.unshift(smartAutoOption); + if (serverDefaultSmartModelOption !== null) { + smartModelOptions.unshift(serverDefaultSmartModelOption); } const fastModelOptions = [...modelOptions]; - if (fastAutoOption !== null) { - fastModelOptions.unshift(fastAutoOption); + if (serverDefaultFastModelOption !== null) { + fastModelOptions.unshift(serverDefaultFastModelOption); } const handleModelFieldChange = async ( From cac7686674a8e6220d652bf59531293fa145f1a8 Mon Sep 17 00:00:00 2001 From: ehconitin Date: Tue, 24 Mar 2026 17:23:42 +0530 Subject: [PATCH 09/13] revert: auto generated file --- .../ai/ai-models/ai-providers.json | 775 ++++++------------ 1 file changed, 238 insertions(+), 537 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json index 3aac89cff3910..4aaf0d291b377 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json @@ -7,75 +7,64 @@ { "name": "gpt-4o-2024-11-20", "label": "GPT-4o (2024-11-20)", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 1.25, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "gpt-5.3-codex", "label": "GPT-5.3 Codex", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "gpt-5-codex", "label": "GPT-5-Codex", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5-pro", "label": "GPT-5 Pro", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 120, "contextWindowTokens": 400000, "maxOutputTokens": 272000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4o-mini", "label": "GPT-4o mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.6, "cachedInputCostPerMillionTokens": 0.08, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "codex-mini-latest", "label": "Codex Mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.5, "outputCostPerMillionTokens": 6, "cachedInputCostPerMillionTokens": 0.375, @@ -86,267 +75,227 @@ { "name": "gpt-5.1-codex-max", "label": "GPT-5.1 Codex Max", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4o-2024-05-13", "label": "GPT-4o (2024-05-13)", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 15, "contextWindowTokens": 128000, "maxOutputTokens": 4096, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "gpt-5.2-chat-latest", "label": "GPT-5.2 Chat", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.2-codex", "label": "GPT-5.2 Codex", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "o3-deep-research", "label": "o3-deep-research", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 10, "outputCostPerMillionTokens": 40, "cachedInputCostPerMillionTokens": 2.5, "contextWindowTokens": 200000, "maxOutputTokens": 100000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "o1", "label": "o1", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 60, "cachedInputCostPerMillionTokens": 7.5, "contextWindowTokens": 200000, "maxOutputTokens": 100000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.1", "label": "GPT-5.1", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.13, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "o4-mini-deep-research", "label": "o4-mini-deep-research", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 8, "cachedInputCostPerMillionTokens": 0.5, "contextWindowTokens": 200000, "maxOutputTokens": 100000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.3-codex-spark", "label": "GPT-5.3 Codex Spark", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, "contextWindowTokens": 128000, "maxOutputTokens": 32000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "o3", "label": "o3", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 8, "cachedInputCostPerMillionTokens": 0.5, "contextWindowTokens": 200000, "maxOutputTokens": 100000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4.1-nano", "label": "GPT-4.1 nano", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.03, "contextWindowTokens": 1047576, "maxOutputTokens": 32768, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "gpt-5.1-codex-mini", "label": "GPT-5.1 Codex mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 2, "cachedInputCostPerMillionTokens": 0.025, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.2", "label": "GPT-5.2", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4.1", "label": "GPT-4.1", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 8, "cachedInputCostPerMillionTokens": 0.5, "contextWindowTokens": 1047576, "maxOutputTokens": 32768, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "o3-pro", "label": "o3-pro", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 20, "outputCostPerMillionTokens": 80, "contextWindowTokens": 200000, "maxOutputTokens": 100000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4-turbo", "label": "GPT-4 Turbo", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 10, "outputCostPerMillionTokens": 30, "contextWindowTokens": 128000, "maxOutputTokens": 4096, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "gpt-5", "label": "GPT-5", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "o4-mini", "label": "o4-mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.1, "outputCostPerMillionTokens": 4.4, "cachedInputCostPerMillionTokens": 0.28, "contextWindowTokens": 200000, "maxOutputTokens": 100000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4.1-mini", "label": "GPT-4.1 mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 1.6, "cachedInputCostPerMillionTokens": 0.1, "contextWindowTokens": 1047576, "maxOutputTokens": 32768, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "gpt-5.4", "label": "GPT-5.4", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.25, @@ -358,16 +307,13 @@ }, "contextWindowTokens": 1050000, "maxOutputTokens": 128000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "gpt-5.4-pro", "label": "GPT-5.4 Pro", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 30, "outputCostPerMillionTokens": 180, "longContextCost": { @@ -377,55 +323,47 @@ }, "contextWindowTokens": 1050000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "o1-pro", "label": "o1-pro", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 150, "outputCostPerMillionTokens": 600, "contextWindowTokens": 200000, "maxOutputTokens": 100000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.1-codex", "label": "GPT-5.1 Codex", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.2-pro", "label": "GPT-5.2 Pro", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 21, "outputCostPerMillionTokens": 168, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "o3-mini", "label": "o3-mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.1, "outputCostPerMillionTokens": 4.4, "cachedInputCostPerMillionTokens": 0.55, @@ -436,62 +374,54 @@ { "name": "gpt-4o-2024-08-06", "label": "GPT-4o (2024-08-06)", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 1.25, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "gpt-5-mini", "label": "GPT-5 Mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 2, "cachedInputCostPerMillionTokens": 0.025, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.4-nano", "label": "GPT-5.4 nano", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 1.25, "cachedInputCostPerMillionTokens": 0.02, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-5.1-chat-latest", "label": "GPT-5.1 Chat", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4", "label": "GPT-4", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 30, "outputCostPerMillionTokens": 60, "contextWindowTokens": 8192, @@ -500,42 +430,36 @@ { "name": "gpt-5-nano", "label": "GPT-5 Nano", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.05, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.005, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "gpt-4o", "label": "GPT-4o", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 1.25, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "gpt-5.4-mini", "label": "GPT-5.4 mini", - "modelFamily": "GPT", + "modelFamily": "gpt", "inputCostPerMillionTokens": 0.75, "outputCostPerMillionTokens": 4.5, "cachedInputCostPerMillionTokens": 0.075, "contextWindowTokens": 400000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true } ] @@ -548,362 +472,293 @@ { "name": "claude-opus-4-5-20251101", "label": "Claude Opus 4.5", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 0.5, "cacheCreationCostPerMillionTokens": 6.25, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-3-5-haiku-latest", "label": "Claude Haiku 3.5 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 0.8, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.08, "cacheCreationCostPerMillionTokens": 1, "contextWindowTokens": 200000, "maxOutputTokens": 8192, - "modalities": [ - "image", - "pdf" - ] + "modalities": ["image", "pdf"] }, { "name": "claude-opus-4-1", "label": "Claude Opus 4.1 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, "cacheCreationCostPerMillionTokens": 18.75, "contextWindowTokens": 200000, "maxOutputTokens": 32000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-3-5-sonnet-20241022", "label": "Claude Sonnet 3.5 v2", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 8192, - "modalities": [ - "image", - "pdf" - ] + "modalities": ["image", "pdf"] }, { "name": "claude-3-sonnet-20240229", "label": "Claude Sonnet 3", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 0.3, "contextWindowTokens": 200000, "maxOutputTokens": 4096, - "modalities": [ - "image", - "pdf" - ] + "modalities": ["image", "pdf"] }, { "name": "claude-opus-4-6", "label": "Claude Opus 4.6", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 0.5, "cacheCreationCostPerMillionTokens": 6.25, "contextWindowTokens": 1000000, "maxOutputTokens": 128000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-sonnet-4-6", "label": "Claude Sonnet 4.6", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 1000000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-sonnet-4-0", "label": "Claude Sonnet 4 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-opus-4-20250514", "label": "Claude Opus 4", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, "cacheCreationCostPerMillionTokens": 18.75, "contextWindowTokens": 200000, "maxOutputTokens": 32000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-sonnet-4-5-20250929", "label": "Claude Sonnet 4.5", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-opus-4-0", "label": "Claude Opus 4 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, "cacheCreationCostPerMillionTokens": 18.75, "contextWindowTokens": 200000, "maxOutputTokens": 32000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-3-5-haiku-20241022", "label": "Claude Haiku 3.5", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 0.8, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.08, "cacheCreationCostPerMillionTokens": 1, "contextWindowTokens": 200000, "maxOutputTokens": 8192, - "modalities": [ - "image", - "pdf" - ] + "modalities": ["image", "pdf"] }, { "name": "claude-3-5-sonnet-20240620", "label": "Claude Sonnet 3.5", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 8192, - "modalities": [ - "image", - "pdf" - ] + "modalities": ["image", "pdf"] }, { "name": "claude-3-7-sonnet-latest", "label": "Claude Sonnet 3.7 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-3-7-sonnet-20250219", "label": "Claude Sonnet 3.7", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-3-haiku-20240307", "label": "Claude Haiku 3", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 1.25, "cachedInputCostPerMillionTokens": 0.03, "cacheCreationCostPerMillionTokens": 0.3, "contextWindowTokens": 200000, "maxOutputTokens": 4096, - "modalities": [ - "image", - "pdf" - ] + "modalities": ["image", "pdf"] }, { "name": "claude-haiku-4-5-20251001", "label": "Claude Haiku 4.5", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 1, "outputCostPerMillionTokens": 5, "cachedInputCostPerMillionTokens": 0.1, "cacheCreationCostPerMillionTokens": 1.25, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-haiku-4-5", "label": "Claude Haiku 4.5 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 1, "outputCostPerMillionTokens": 5, "cachedInputCostPerMillionTokens": 0.1, "cacheCreationCostPerMillionTokens": 1.25, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-opus-4-5", "label": "Claude Opus 4.5 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 0.5, "cacheCreationCostPerMillionTokens": 6.25, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-3-opus-20240229", "label": "Claude Opus 3", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, "cacheCreationCostPerMillionTokens": 18.75, "contextWindowTokens": 200000, "maxOutputTokens": 4096, - "modalities": [ - "image", - "pdf" - ] + "modalities": ["image", "pdf"] }, { "name": "claude-sonnet-4-5", "label": "Claude Sonnet 4.5 (latest)", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-sonnet-4-20250514", "label": "Claude Sonnet 4", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, "cacheCreationCostPerMillionTokens": 3.75, "contextWindowTokens": 200000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true }, { "name": "claude-opus-4-1-20250805", "label": "Claude Opus 4.1", - "modelFamily": "CLAUDE", + "modelFamily": "claude", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, "cacheCreationCostPerMillionTokens": 18.75, "contextWindowTokens": 200000, "maxOutputTokens": 32000, - "modalities": [ - "image", - "pdf" - ], + "modalities": ["image", "pdf"], "supportsReasoning": true } ] @@ -916,24 +771,19 @@ { "name": "gemini-2.5-flash-lite-preview-09-2025", "label": "Gemini 2.5 Flash Lite Preview 09-25", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-3.1-pro-preview-customtools", "label": "Gemini 3.1 Pro Preview Custom Tools", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 12, "cachedInputCostPerMillionTokens": 0.2, @@ -945,153 +795,109 @@ }, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "video", - "audio", - "pdf" - ], + "modalities": ["image", "video", "audio", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-pro-preview-06-05", "label": "Gemini 2.5 Pro Preview 06-05", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.31, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-flash-preview-04-17", "label": "Gemini 2.5 Flash Preview 04-17", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.6, "cachedInputCostPerMillionTokens": 0.0375, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-flash-preview-09-2025", "label": "Gemini 2.5 Flash Preview 09-25", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 2.5, "cachedInputCostPerMillionTokens": 0.075, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-pro-preview-05-06", "label": "Gemini 2.5 Pro Preview 05-06", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.31, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-flash-preview-05-20", "label": "Gemini 2.5 Flash Preview 05-20", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.6, "cachedInputCostPerMillionTokens": 0.0375, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-flash", "label": "Gemini 2.5 Flash", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 2.5, "cachedInputCostPerMillionTokens": 0.075, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-3.1-flash-lite-preview", "label": "Gemini 3.1 Flash Lite Preview", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 1.5, "cachedInputCostPerMillionTokens": 0.025, "cacheCreationCostPerMillionTokens": 1, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "video", - "audio", - "pdf" - ], + "modalities": ["image", "video", "audio", "pdf"], "supportsReasoning": true }, { "name": "gemini-live-2.5-flash", "label": "Gemini Live 2.5 Flash", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 2, "contextWindowTokens": 128000, "maxOutputTokens": 8000, - "modalities": [ - "image", - "audio", - "video" - ], + "modalities": ["image", "audio", "video"], "supportsReasoning": true }, { "name": "gemini-3-flash-preview", "label": "Gemini 3 Flash Preview", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 3, "cachedInputCostPerMillionTokens": 0.05, @@ -1103,49 +909,36 @@ }, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "video", - "audio", - "pdf" - ], + "modalities": ["image", "video", "audio", "pdf"], "supportsReasoning": true }, { "name": "gemini-live-2.5-flash-preview-native-audio", "label": "Gemini Live 2.5 Flash Preview Native Audio", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 2, "contextWindowTokens": 131072, "maxOutputTokens": 65536, - "modalities": [ - "audio", - "video" - ], + "modalities": ["audio", "video"], "supportsReasoning": true }, { "name": "gemini-2.5-flash-lite", "label": "Gemini 2.5 Flash Lite", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-3.1-pro-preview", "label": "Gemini 3.1 Pro Preview", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 12, "cachedInputCostPerMillionTokens": 0.2, @@ -1157,67 +950,48 @@ }, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "video", - "audio", - "pdf" - ], + "modalities": ["image", "video", "audio", "pdf"], "supportsReasoning": true }, { "name": "gemini-flash-latest", "label": "Gemini Flash Latest", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 2.5, "cachedInputCostPerMillionTokens": 0.075, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-flash-lite-preview-06-17", "label": "Gemini 2.5 Flash Lite Preview 06-17", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-1.5-flash-8b", "label": "Gemini 1.5 Flash-8B", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.0375, "outputCostPerMillionTokens": 0.15, "cachedInputCostPerMillionTokens": 0.01, "contextWindowTokens": 1000000, "maxOutputTokens": 8192, - "modalities": [ - "image", - "audio", - "video" - ] + "modalities": ["image", "audio", "video"] }, { "name": "gemini-3-pro-preview", "label": "Gemini 3 Pro Preview", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 12, "cachedInputCostPerMillionTokens": 0.2, @@ -1229,108 +1003,75 @@ }, "contextWindowTokens": 1000000, "maxOutputTokens": 64000, - "modalities": [ - "image", - "video", - "audio", - "pdf" - ], + "modalities": ["image", "video", "audio", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.0-flash-lite", "label": "Gemini 2.0 Flash Lite", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.075, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 1048576, "maxOutputTokens": 8192, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ] + "modalities": ["image", "audio", "video", "pdf"] }, { "name": "gemini-1.5-flash", "label": "Gemini 1.5 Flash", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.075, "outputCostPerMillionTokens": 0.3, "cachedInputCostPerMillionTokens": 0.01875, "contextWindowTokens": 1000000, "maxOutputTokens": 8192, - "modalities": [ - "image", - "audio", - "video" - ] + "modalities": ["image", "audio", "video"] }, { "name": "gemini-flash-lite-latest", "label": "Gemini Flash-Lite Latest", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.5-pro", "label": "Gemini 2.5 Pro", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.31, "contextWindowTokens": 1048576, "maxOutputTokens": 65536, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ], + "modalities": ["image", "audio", "video", "pdf"], "supportsReasoning": true }, { "name": "gemini-2.0-flash", "label": "Gemini 2.0 Flash", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, "contextWindowTokens": 1048576, "maxOutputTokens": 8192, - "modalities": [ - "image", - "audio", - "video", - "pdf" - ] + "modalities": ["image", "audio", "video", "pdf"] }, { "name": "gemini-1.5-pro", "label": "Gemini 1.5 Pro", - "modelFamily": "GEMINI", + "modelFamily": "gemini", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 5, "cachedInputCostPerMillionTokens": 0.3125, "contextWindowTokens": 1000000, "maxOutputTokens": 8192, - "modalities": [ - "image", - "audio", - "video" - ] + "modalities": ["image", "audio", "video"] } ] }, @@ -1342,7 +1083,7 @@ { "name": "devstral-medium-2507", "label": "Devstral Medium", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 128000, @@ -1351,19 +1092,17 @@ { "name": "labs-devstral-small-2512", "label": "Devstral Small 2", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0, "outputCostPerMillionTokens": 0, "contextWindowTokens": 256000, "maxOutputTokens": 256000, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "devstral-medium-latest", "label": "Devstral 2 (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 262144, @@ -1372,7 +1111,7 @@ { "name": "open-mistral-7b", "label": "Mistral 7B", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 0.25, "contextWindowTokens": 8000, @@ -1381,31 +1120,27 @@ { "name": "mistral-small-2506", "label": "Mistral Small 3.2", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "mistral-medium-2505", "label": "Mistral Medium 3", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 131072, "maxOutputTokens": 131072, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "codestral-latest", "label": "Codestral (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 0.9, "contextWindowTokens": 256000, @@ -1414,7 +1149,7 @@ { "name": "ministral-8b-latest", "label": "Ministral 8B (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.1, "contextWindowTokens": 128000, @@ -1423,7 +1158,7 @@ { "name": "magistral-small", "label": "Magistral Small", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 1.5, "contextWindowTokens": 128000, @@ -1433,19 +1168,17 @@ { "name": "mistral-large-2512", "label": "Mistral Large 3", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 1.5, "contextWindowTokens": 262144, "maxOutputTokens": 262144, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "ministral-3b-latest", "label": "Ministral 3B (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.04, "outputCostPerMillionTokens": 0.04, "contextWindowTokens": 128000, @@ -1454,7 +1187,7 @@ { "name": "devstral-small-2505", "label": "Devstral Small 2505", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, @@ -1463,19 +1196,17 @@ { "name": "pixtral-12b", "label": "Pixtral 12B", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.15, "contextWindowTokens": 128000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "open-mixtral-8x7b", "label": "Mixtral 8x7B", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.7, "outputCostPerMillionTokens": 0.7, "contextWindowTokens": 32000, @@ -1484,19 +1215,17 @@ { "name": "pixtral-large-latest", "label": "Pixtral Large (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "contextWindowTokens": 128000, "maxOutputTokens": 128000, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "mistral-nemo", "label": "Mistral Nemo", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.15, "contextWindowTokens": 128000, @@ -1505,7 +1234,7 @@ { "name": "devstral-2512", "label": "Devstral 2", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 262144, @@ -1514,31 +1243,27 @@ { "name": "mistral-large-latest", "label": "Mistral Large (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 1.5, "contextWindowTokens": 262144, "maxOutputTokens": 262144, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "mistral-medium-2508", "label": "Mistral Medium 3.1", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 262144, "maxOutputTokens": 262144, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "mistral-large-2411", "label": "Mistral Large 2.1", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "contextWindowTokens": 131072, @@ -1547,19 +1272,17 @@ { "name": "mistral-small-latest", "label": "Mistral Small (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "open-mixtral-8x22b", "label": "Mixtral 8x22B", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "contextWindowTokens": 64000, @@ -1568,19 +1291,17 @@ { "name": "mistral-medium-latest", "label": "Mistral Medium (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 128000, "maxOutputTokens": 16384, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "devstral-small-2507", "label": "Devstral Small", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, @@ -1589,7 +1310,7 @@ { "name": "magistral-medium-latest", "label": "Magistral Medium (latest)", - "modelFamily": "MISTRAL", + "modelFamily": "mistral", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 5, "contextWindowTokens": 128000, @@ -1606,7 +1327,7 @@ { "name": "grok-2-1212", "label": "Grok 2 (1212)", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1616,7 +1337,7 @@ { "name": "grok-2", "label": "Grok 2", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1626,7 +1347,7 @@ { "name": "grok-3-fast-latest", "label": "Grok 3 Fast Latest", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 1.25, @@ -1636,20 +1357,18 @@ { "name": "grok-2-vision", "label": "Grok 2 Vision", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, "contextWindowTokens": 8192, "maxOutputTokens": 4096, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "grok-3", "label": "Grok 3", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.75, @@ -1659,7 +1378,7 @@ { "name": "grok-code-fast-1", "label": "Grok Code Fast 1", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 1.5, "cachedInputCostPerMillionTokens": 0.02, @@ -1670,33 +1389,29 @@ { "name": "grok-2-vision-1212", "label": "Grok 2 Vision (1212)", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, "contextWindowTokens": 8192, "maxOutputTokens": 4096, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "grok-4-1-fast-non-reasoning", "label": "Grok 4.1 Fast (Non-Reasoning)", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, "contextWindowTokens": 2000000, "maxOutputTokens": 30000, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "grok-beta", "label": "Grok Beta", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 5, @@ -1706,7 +1421,7 @@ { "name": "grok-3-mini-fast", "label": "Grok 3 Mini Fast", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.6, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.15, @@ -1717,21 +1432,19 @@ { "name": "grok-4-fast", "label": "Grok 4 Fast", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, "contextWindowTokens": 2000000, "maxOutputTokens": 30000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "grok-4", "label": "Grok 4", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.75, @@ -1742,7 +1455,7 @@ { "name": "grok-3-latest", "label": "Grok 3 Latest", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.75, @@ -1752,34 +1465,30 @@ { "name": "grok-4-1-fast", "label": "Grok 4.1 Fast", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, "contextWindowTokens": 2000000, "maxOutputTokens": 30000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "grok-2-vision-latest", "label": "Grok 2 Vision Latest", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, "contextWindowTokens": 8192, "maxOutputTokens": 4096, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "grok-3-mini-latest", "label": "Grok 3 Mini Latest", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.075, @@ -1790,7 +1499,7 @@ { "name": "grok-3-mini", "label": "Grok 3 Mini", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.075, @@ -1801,7 +1510,7 @@ { "name": "grok-3-mini-fast-latest", "label": "Grok 3 Mini Fast Latest", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.6, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.15, @@ -1812,7 +1521,7 @@ { "name": "grok-4.20-0309-reasoning", "label": "Grok 4.20 (Reasoning)", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "cachedInputCostPerMillionTokens": 0.2, @@ -1824,15 +1533,13 @@ }, "contextWindowTokens": 2000000, "maxOutputTokens": 30000, - "modalities": [ - "image" - ], + "modalities": ["image"], "supportsReasoning": true }, { "name": "grok-2-latest", "label": "Grok 2 Latest", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1842,33 +1549,29 @@ { "name": "grok-4-fast-non-reasoning", "label": "Grok 4 Fast (Non-Reasoning)", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, "contextWindowTokens": 2000000, "maxOutputTokens": 30000, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "grok-vision-beta", "label": "Grok Vision Beta", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 5, "contextWindowTokens": 8192, "maxOutputTokens": 4096, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "grok-4.20-0309-non-reasoning", "label": "Grok 4.20 (Non-Reasoning)", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "cachedInputCostPerMillionTokens": 0.2, @@ -1880,14 +1583,12 @@ }, "contextWindowTokens": 2000000, "maxOutputTokens": 30000, - "modalities": [ - "image" - ] + "modalities": ["image"] }, { "name": "grok-3-fast", "label": "Grok 3 Fast", - "modelFamily": "GROK", + "modelFamily": "grok", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 1.25, From ca7d7b1e4efa29b8cef0a7f596a79fc36f3b6558 Mon Sep 17 00:00:00 2001 From: ehconitin Date: Wed, 25 Mar 2026 20:45:40 +0530 Subject: [PATCH 10/13] reviuew --- .../modules/ai/hooks/useAgentChatModelId.ts | 5 +- .../hooks/useWorkspaceAiModelAvailability.ts | 9 +- .../ai/ai-models/ai-providers.json | 270 +++++++++--------- 3 files changed, 140 insertions(+), 144 deletions(-) diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChatModelId.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChatModelId.ts index 4620b196c2782..661965d63cf66 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChatModelId.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChatModelId.ts @@ -17,10 +17,7 @@ export const useAgentChatModelId = () => { const selectedModelId = isUserModelAvailable ? agentChatUserSelectedModel : null; - - const modelIdForRequest = isDefined(selectedModelId) - ? selectedModelId - : undefined; + const modelIdForRequest = selectedModelId ?? undefined; return { selectedModelId, modelIdForRequest }; }; diff --git a/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts b/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts index 643506a091091..2b0c7f39e8d5b 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts @@ -5,13 +5,12 @@ import { aiModelsState } from '@/client-config/states/aiModelsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { type ClientAiModelConfig } from '~/generated-metadata/graphql'; -const DEFAULT_MODEL_SENTINEL_IDS: Set = new Set([ +const DEFAULT_MODEL_IDS: Set = new Set([ DEFAULT_SMART_MODEL, DEFAULT_FAST_MODEL, ]); -const isDefaultModelSentinel = (modelId: string) => - DEFAULT_MODEL_SENTINEL_IDS.has(modelId); +const isDefaultModelId = (modelId: string) => DEFAULT_MODEL_IDS.has(modelId); export const useWorkspaceAiModelAvailability = () => { const aiModels = useAtomStateValue(aiModelsState); @@ -24,7 +23,7 @@ export const useWorkspaceAiModelAvailability = () => { modelId: string, model?: ClientAiModelConfig, ): boolean => { - if (isDefaultModelSentinel(modelId)) { + if (isDefaultModelId(modelId)) { return true; } @@ -36,7 +35,7 @@ export const useWorkspaceAiModelAvailability = () => { }; const realModels = aiModels.filter( - (model) => !isDefaultModelSentinel(model.modelId) && !model.isDeprecated, + (model) => !isDefaultModelId(model.modelId) && !model.isDeprecated, ); const enabledModels = realModels.filter((model) => diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json index 4aaf0d291b377..b42010f22ea0a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/ai-providers.json @@ -7,7 +7,7 @@ { "name": "gpt-4o-2024-11-20", "label": "GPT-4o (2024-11-20)", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 1.25, @@ -18,7 +18,7 @@ { "name": "gpt-5.3-codex", "label": "GPT-5.3 Codex", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, @@ -30,7 +30,7 @@ { "name": "gpt-5-codex", "label": "GPT-5-Codex", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, @@ -42,7 +42,7 @@ { "name": "gpt-5-pro", "label": "GPT-5 Pro", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 120, "contextWindowTokens": 400000, @@ -53,7 +53,7 @@ { "name": "gpt-4o-mini", "label": "GPT-4o mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.6, "cachedInputCostPerMillionTokens": 0.08, @@ -64,7 +64,7 @@ { "name": "codex-mini-latest", "label": "Codex Mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.5, "outputCostPerMillionTokens": 6, "cachedInputCostPerMillionTokens": 0.375, @@ -75,7 +75,7 @@ { "name": "gpt-5.1-codex-max", "label": "GPT-5.1 Codex Max", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, @@ -87,7 +87,7 @@ { "name": "gpt-4o-2024-05-13", "label": "GPT-4o (2024-05-13)", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 15, "contextWindowTokens": 128000, @@ -97,7 +97,7 @@ { "name": "gpt-5.2-chat-latest", "label": "GPT-5.2 Chat", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, @@ -109,7 +109,7 @@ { "name": "gpt-5.2-codex", "label": "GPT-5.2 Codex", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, @@ -121,7 +121,7 @@ { "name": "o3-deep-research", "label": "o3-deep-research", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 10, "outputCostPerMillionTokens": 40, "cachedInputCostPerMillionTokens": 2.5, @@ -133,7 +133,7 @@ { "name": "o1", "label": "o1", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 60, "cachedInputCostPerMillionTokens": 7.5, @@ -145,7 +145,7 @@ { "name": "gpt-5.1", "label": "GPT-5.1", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.13, @@ -157,7 +157,7 @@ { "name": "o4-mini-deep-research", "label": "o4-mini-deep-research", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 8, "cachedInputCostPerMillionTokens": 0.5, @@ -169,7 +169,7 @@ { "name": "gpt-5.3-codex-spark", "label": "GPT-5.3 Codex Spark", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, @@ -181,7 +181,7 @@ { "name": "o3", "label": "o3", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 8, "cachedInputCostPerMillionTokens": 0.5, @@ -193,7 +193,7 @@ { "name": "gpt-4.1-nano", "label": "GPT-4.1 nano", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.03, @@ -204,7 +204,7 @@ { "name": "gpt-5.1-codex-mini", "label": "GPT-5.1 Codex mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 2, "cachedInputCostPerMillionTokens": 0.025, @@ -216,7 +216,7 @@ { "name": "gpt-5.2", "label": "GPT-5.2", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.75, "outputCostPerMillionTokens": 14, "cachedInputCostPerMillionTokens": 0.175, @@ -228,7 +228,7 @@ { "name": "gpt-4.1", "label": "GPT-4.1", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 8, "cachedInputCostPerMillionTokens": 0.5, @@ -239,7 +239,7 @@ { "name": "o3-pro", "label": "o3-pro", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 20, "outputCostPerMillionTokens": 80, "contextWindowTokens": 200000, @@ -250,7 +250,7 @@ { "name": "gpt-4-turbo", "label": "GPT-4 Turbo", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 10, "outputCostPerMillionTokens": 30, "contextWindowTokens": 128000, @@ -260,7 +260,7 @@ { "name": "gpt-5", "label": "GPT-5", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, @@ -272,7 +272,7 @@ { "name": "o4-mini", "label": "o4-mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.1, "outputCostPerMillionTokens": 4.4, "cachedInputCostPerMillionTokens": 0.28, @@ -284,7 +284,7 @@ { "name": "gpt-4.1-mini", "label": "GPT-4.1 mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 1.6, "cachedInputCostPerMillionTokens": 0.1, @@ -295,7 +295,7 @@ { "name": "gpt-5.4", "label": "GPT-5.4", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.25, @@ -313,7 +313,7 @@ { "name": "gpt-5.4-pro", "label": "GPT-5.4 Pro", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 30, "outputCostPerMillionTokens": 180, "longContextCost": { @@ -329,7 +329,7 @@ { "name": "o1-pro", "label": "o1-pro", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 150, "outputCostPerMillionTokens": 600, "contextWindowTokens": 200000, @@ -340,7 +340,7 @@ { "name": "gpt-5.1-codex", "label": "GPT-5.1 Codex", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, @@ -352,7 +352,7 @@ { "name": "gpt-5.2-pro", "label": "GPT-5.2 Pro", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 21, "outputCostPerMillionTokens": 168, "contextWindowTokens": 400000, @@ -363,7 +363,7 @@ { "name": "o3-mini", "label": "o3-mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.1, "outputCostPerMillionTokens": 4.4, "cachedInputCostPerMillionTokens": 0.55, @@ -374,7 +374,7 @@ { "name": "gpt-4o-2024-08-06", "label": "GPT-4o (2024-08-06)", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 1.25, @@ -385,7 +385,7 @@ { "name": "gpt-5-mini", "label": "GPT-5 Mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 2, "cachedInputCostPerMillionTokens": 0.025, @@ -397,7 +397,7 @@ { "name": "gpt-5.4-nano", "label": "GPT-5.4 nano", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 1.25, "cachedInputCostPerMillionTokens": 0.02, @@ -409,7 +409,7 @@ { "name": "gpt-5.1-chat-latest", "label": "GPT-5.1 Chat", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.125, @@ -421,7 +421,7 @@ { "name": "gpt-4", "label": "GPT-4", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 30, "outputCostPerMillionTokens": 60, "contextWindowTokens": 8192, @@ -430,7 +430,7 @@ { "name": "gpt-5-nano", "label": "GPT-5 Nano", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.05, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.005, @@ -442,7 +442,7 @@ { "name": "gpt-4o", "label": "GPT-4o", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 2.5, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 1.25, @@ -453,7 +453,7 @@ { "name": "gpt-5.4-mini", "label": "GPT-5.4 mini", - "modelFamily": "gpt", + "modelFamily": "GPT", "inputCostPerMillionTokens": 0.75, "outputCostPerMillionTokens": 4.5, "cachedInputCostPerMillionTokens": 0.075, @@ -472,7 +472,7 @@ { "name": "claude-opus-4-5-20251101", "label": "Claude Opus 4.5", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 0.5, @@ -485,7 +485,7 @@ { "name": "claude-3-5-haiku-latest", "label": "Claude Haiku 3.5 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 0.8, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.08, @@ -497,7 +497,7 @@ { "name": "claude-opus-4-1", "label": "Claude Opus 4.1 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, @@ -510,7 +510,7 @@ { "name": "claude-3-5-sonnet-20241022", "label": "Claude Sonnet 3.5 v2", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -522,7 +522,7 @@ { "name": "claude-3-sonnet-20240229", "label": "Claude Sonnet 3", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -534,7 +534,7 @@ { "name": "claude-opus-4-6", "label": "Claude Opus 4.6", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 0.5, @@ -547,7 +547,7 @@ { "name": "claude-sonnet-4-6", "label": "Claude Sonnet 4.6", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -560,7 +560,7 @@ { "name": "claude-sonnet-4-0", "label": "Claude Sonnet 4 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -573,7 +573,7 @@ { "name": "claude-opus-4-20250514", "label": "Claude Opus 4", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, @@ -586,7 +586,7 @@ { "name": "claude-sonnet-4-5-20250929", "label": "Claude Sonnet 4.5", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -599,7 +599,7 @@ { "name": "claude-opus-4-0", "label": "Claude Opus 4 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, @@ -612,7 +612,7 @@ { "name": "claude-3-5-haiku-20241022", "label": "Claude Haiku 3.5", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 0.8, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.08, @@ -624,7 +624,7 @@ { "name": "claude-3-5-sonnet-20240620", "label": "Claude Sonnet 3.5", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -636,7 +636,7 @@ { "name": "claude-3-7-sonnet-latest", "label": "Claude Sonnet 3.7 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -649,7 +649,7 @@ { "name": "claude-3-7-sonnet-20250219", "label": "Claude Sonnet 3.7", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -662,7 +662,7 @@ { "name": "claude-3-haiku-20240307", "label": "Claude Haiku 3", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 1.25, "cachedInputCostPerMillionTokens": 0.03, @@ -674,7 +674,7 @@ { "name": "claude-haiku-4-5-20251001", "label": "Claude Haiku 4.5", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 1, "outputCostPerMillionTokens": 5, "cachedInputCostPerMillionTokens": 0.1, @@ -687,7 +687,7 @@ { "name": "claude-haiku-4-5", "label": "Claude Haiku 4.5 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 1, "outputCostPerMillionTokens": 5, "cachedInputCostPerMillionTokens": 0.1, @@ -700,7 +700,7 @@ { "name": "claude-opus-4-5", "label": "Claude Opus 4.5 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 0.5, @@ -713,7 +713,7 @@ { "name": "claude-3-opus-20240229", "label": "Claude Opus 3", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, @@ -725,7 +725,7 @@ { "name": "claude-sonnet-4-5", "label": "Claude Sonnet 4.5 (latest)", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -738,7 +738,7 @@ { "name": "claude-sonnet-4-20250514", "label": "Claude Sonnet 4", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.3, @@ -751,7 +751,7 @@ { "name": "claude-opus-4-1-20250805", "label": "Claude Opus 4.1", - "modelFamily": "claude", + "modelFamily": "CLAUDE", "inputCostPerMillionTokens": 15, "outputCostPerMillionTokens": 75, "cachedInputCostPerMillionTokens": 1.5, @@ -771,7 +771,7 @@ { "name": "gemini-2.5-flash-lite-preview-09-2025", "label": "Gemini 2.5 Flash Lite Preview 09-25", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, @@ -783,7 +783,7 @@ { "name": "gemini-3.1-pro-preview-customtools", "label": "Gemini 3.1 Pro Preview Custom Tools", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 12, "cachedInputCostPerMillionTokens": 0.2, @@ -801,7 +801,7 @@ { "name": "gemini-2.5-pro-preview-06-05", "label": "Gemini 2.5 Pro Preview 06-05", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.31, @@ -813,7 +813,7 @@ { "name": "gemini-2.5-flash-preview-04-17", "label": "Gemini 2.5 Flash Preview 04-17", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.6, "cachedInputCostPerMillionTokens": 0.0375, @@ -825,7 +825,7 @@ { "name": "gemini-2.5-flash-preview-09-2025", "label": "Gemini 2.5 Flash Preview 09-25", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 2.5, "cachedInputCostPerMillionTokens": 0.075, @@ -837,7 +837,7 @@ { "name": "gemini-2.5-pro-preview-05-06", "label": "Gemini 2.5 Pro Preview 05-06", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.31, @@ -849,7 +849,7 @@ { "name": "gemini-2.5-flash-preview-05-20", "label": "Gemini 2.5 Flash Preview 05-20", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.6, "cachedInputCostPerMillionTokens": 0.0375, @@ -861,7 +861,7 @@ { "name": "gemini-2.5-flash", "label": "Gemini 2.5 Flash", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 2.5, "cachedInputCostPerMillionTokens": 0.075, @@ -873,7 +873,7 @@ { "name": "gemini-3.1-flash-lite-preview", "label": "Gemini 3.1 Flash Lite Preview", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 1.5, "cachedInputCostPerMillionTokens": 0.025, @@ -886,7 +886,7 @@ { "name": "gemini-live-2.5-flash", "label": "Gemini Live 2.5 Flash", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 2, "contextWindowTokens": 128000, @@ -897,7 +897,7 @@ { "name": "gemini-3-flash-preview", "label": "Gemini 3 Flash Preview", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 3, "cachedInputCostPerMillionTokens": 0.05, @@ -915,7 +915,7 @@ { "name": "gemini-live-2.5-flash-preview-native-audio", "label": "Gemini Live 2.5 Flash Preview Native Audio", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 2, "contextWindowTokens": 131072, @@ -926,7 +926,7 @@ { "name": "gemini-2.5-flash-lite", "label": "Gemini 2.5 Flash Lite", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, @@ -938,7 +938,7 @@ { "name": "gemini-3.1-pro-preview", "label": "Gemini 3.1 Pro Preview", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 12, "cachedInputCostPerMillionTokens": 0.2, @@ -956,7 +956,7 @@ { "name": "gemini-flash-latest", "label": "Gemini Flash Latest", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 2.5, "cachedInputCostPerMillionTokens": 0.075, @@ -968,7 +968,7 @@ { "name": "gemini-2.5-flash-lite-preview-06-17", "label": "Gemini 2.5 Flash Lite Preview 06-17", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, @@ -980,7 +980,7 @@ { "name": "gemini-1.5-flash-8b", "label": "Gemini 1.5 Flash-8B", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.0375, "outputCostPerMillionTokens": 0.15, "cachedInputCostPerMillionTokens": 0.01, @@ -991,7 +991,7 @@ { "name": "gemini-3-pro-preview", "label": "Gemini 3 Pro Preview", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 12, "cachedInputCostPerMillionTokens": 0.2, @@ -1009,7 +1009,7 @@ { "name": "gemini-2.0-flash-lite", "label": "Gemini 2.0 Flash Lite", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.075, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 1048576, @@ -1019,7 +1019,7 @@ { "name": "gemini-1.5-flash", "label": "Gemini 1.5 Flash", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.075, "outputCostPerMillionTokens": 0.3, "cachedInputCostPerMillionTokens": 0.01875, @@ -1030,7 +1030,7 @@ { "name": "gemini-flash-lite-latest", "label": "Gemini Flash-Lite Latest", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, @@ -1042,7 +1042,7 @@ { "name": "gemini-2.5-pro", "label": "Gemini 2.5 Pro", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 0.31, @@ -1054,7 +1054,7 @@ { "name": "gemini-2.0-flash", "label": "Gemini 2.0 Flash", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.4, "cachedInputCostPerMillionTokens": 0.025, @@ -1065,7 +1065,7 @@ { "name": "gemini-1.5-pro", "label": "Gemini 1.5 Pro", - "modelFamily": "gemini", + "modelFamily": "GEMINI", "inputCostPerMillionTokens": 1.25, "outputCostPerMillionTokens": 5, "cachedInputCostPerMillionTokens": 0.3125, @@ -1083,7 +1083,7 @@ { "name": "devstral-medium-2507", "label": "Devstral Medium", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 128000, @@ -1092,7 +1092,7 @@ { "name": "labs-devstral-small-2512", "label": "Devstral Small 2", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0, "outputCostPerMillionTokens": 0, "contextWindowTokens": 256000, @@ -1102,7 +1102,7 @@ { "name": "devstral-medium-latest", "label": "Devstral 2 (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 262144, @@ -1111,7 +1111,7 @@ { "name": "open-mistral-7b", "label": "Mistral 7B", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.25, "outputCostPerMillionTokens": 0.25, "contextWindowTokens": 8000, @@ -1120,7 +1120,7 @@ { "name": "mistral-small-2506", "label": "Mistral Small 3.2", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, @@ -1130,7 +1130,7 @@ { "name": "mistral-medium-2505", "label": "Mistral Medium 3", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 131072, @@ -1140,7 +1140,7 @@ { "name": "codestral-latest", "label": "Codestral (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 0.9, "contextWindowTokens": 256000, @@ -1149,7 +1149,7 @@ { "name": "ministral-8b-latest", "label": "Ministral 8B (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.1, "contextWindowTokens": 128000, @@ -1158,7 +1158,7 @@ { "name": "magistral-small", "label": "Magistral Small", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 1.5, "contextWindowTokens": 128000, @@ -1168,7 +1168,7 @@ { "name": "mistral-large-2512", "label": "Mistral Large 3", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 1.5, "contextWindowTokens": 262144, @@ -1178,7 +1178,7 @@ { "name": "ministral-3b-latest", "label": "Ministral 3B (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.04, "outputCostPerMillionTokens": 0.04, "contextWindowTokens": 128000, @@ -1187,7 +1187,7 @@ { "name": "devstral-small-2505", "label": "Devstral Small 2505", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, @@ -1196,7 +1196,7 @@ { "name": "pixtral-12b", "label": "Pixtral 12B", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.15, "contextWindowTokens": 128000, @@ -1206,7 +1206,7 @@ { "name": "open-mixtral-8x7b", "label": "Mixtral 8x7B", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.7, "outputCostPerMillionTokens": 0.7, "contextWindowTokens": 32000, @@ -1215,7 +1215,7 @@ { "name": "pixtral-large-latest", "label": "Pixtral Large (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "contextWindowTokens": 128000, @@ -1225,7 +1225,7 @@ { "name": "mistral-nemo", "label": "Mistral Nemo", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.15, "outputCostPerMillionTokens": 0.15, "contextWindowTokens": 128000, @@ -1234,7 +1234,7 @@ { "name": "devstral-2512", "label": "Devstral 2", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 262144, @@ -1243,7 +1243,7 @@ { "name": "mistral-large-latest", "label": "Mistral Large (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.5, "outputCostPerMillionTokens": 1.5, "contextWindowTokens": 262144, @@ -1253,7 +1253,7 @@ { "name": "mistral-medium-2508", "label": "Mistral Medium 3.1", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 262144, @@ -1263,7 +1263,7 @@ { "name": "mistral-large-2411", "label": "Mistral Large 2.1", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "contextWindowTokens": 131072, @@ -1272,7 +1272,7 @@ { "name": "mistral-small-latest", "label": "Mistral Small (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, @@ -1282,7 +1282,7 @@ { "name": "open-mixtral-8x22b", "label": "Mixtral 8x22B", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "contextWindowTokens": 64000, @@ -1291,7 +1291,7 @@ { "name": "mistral-medium-latest", "label": "Mistral Medium (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.4, "outputCostPerMillionTokens": 2, "contextWindowTokens": 128000, @@ -1301,7 +1301,7 @@ { "name": "devstral-small-2507", "label": "Devstral Small", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 0.1, "outputCostPerMillionTokens": 0.3, "contextWindowTokens": 128000, @@ -1310,7 +1310,7 @@ { "name": "magistral-medium-latest", "label": "Magistral Medium (latest)", - "modelFamily": "mistral", + "modelFamily": "MISTRAL", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 5, "contextWindowTokens": 128000, @@ -1327,7 +1327,7 @@ { "name": "grok-2-1212", "label": "Grok 2 (1212)", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1337,7 +1337,7 @@ { "name": "grok-2", "label": "Grok 2", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1347,7 +1347,7 @@ { "name": "grok-3-fast-latest", "label": "Grok 3 Fast Latest", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 1.25, @@ -1357,7 +1357,7 @@ { "name": "grok-2-vision", "label": "Grok 2 Vision", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1368,7 +1368,7 @@ { "name": "grok-3", "label": "Grok 3", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.75, @@ -1378,7 +1378,7 @@ { "name": "grok-code-fast-1", "label": "Grok Code Fast 1", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 1.5, "cachedInputCostPerMillionTokens": 0.02, @@ -1389,7 +1389,7 @@ { "name": "grok-2-vision-1212", "label": "Grok 2 Vision (1212)", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1400,7 +1400,7 @@ { "name": "grok-4-1-fast-non-reasoning", "label": "Grok 4.1 Fast (Non-Reasoning)", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, @@ -1411,7 +1411,7 @@ { "name": "grok-beta", "label": "Grok Beta", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 5, @@ -1421,7 +1421,7 @@ { "name": "grok-3-mini-fast", "label": "Grok 3 Mini Fast", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.6, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.15, @@ -1432,7 +1432,7 @@ { "name": "grok-4-fast", "label": "Grok 4 Fast", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, @@ -1444,7 +1444,7 @@ { "name": "grok-4", "label": "Grok 4", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.75, @@ -1455,7 +1455,7 @@ { "name": "grok-3-latest", "label": "Grok 3 Latest", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 3, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 0.75, @@ -1465,7 +1465,7 @@ { "name": "grok-4-1-fast", "label": "Grok 4.1 Fast", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, @@ -1477,7 +1477,7 @@ { "name": "grok-2-vision-latest", "label": "Grok 2 Vision Latest", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1488,7 +1488,7 @@ { "name": "grok-3-mini-latest", "label": "Grok 3 Mini Latest", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.075, @@ -1499,7 +1499,7 @@ { "name": "grok-3-mini", "label": "Grok 3 Mini", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.3, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.075, @@ -1510,7 +1510,7 @@ { "name": "grok-3-mini-fast-latest", "label": "Grok 3 Mini Fast Latest", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.6, "outputCostPerMillionTokens": 4, "cachedInputCostPerMillionTokens": 0.15, @@ -1521,7 +1521,7 @@ { "name": "grok-4.20-0309-reasoning", "label": "Grok 4.20 (Reasoning)", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "cachedInputCostPerMillionTokens": 0.2, @@ -1539,7 +1539,7 @@ { "name": "grok-2-latest", "label": "Grok 2 Latest", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 10, "cachedInputCostPerMillionTokens": 2, @@ -1549,7 +1549,7 @@ { "name": "grok-4-fast-non-reasoning", "label": "Grok 4 Fast (Non-Reasoning)", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 0.2, "outputCostPerMillionTokens": 0.5, "cachedInputCostPerMillionTokens": 0.05, @@ -1560,7 +1560,7 @@ { "name": "grok-vision-beta", "label": "Grok Vision Beta", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 15, "cachedInputCostPerMillionTokens": 5, @@ -1571,7 +1571,7 @@ { "name": "grok-4.20-0309-non-reasoning", "label": "Grok 4.20 (Non-Reasoning)", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 2, "outputCostPerMillionTokens": 6, "cachedInputCostPerMillionTokens": 0.2, @@ -1588,7 +1588,7 @@ { "name": "grok-3-fast", "label": "Grok 3 Fast", - "modelFamily": "grok", + "modelFamily": "GROK", "inputCostPerMillionTokens": 5, "outputCostPerMillionTokens": 25, "cachedInputCostPerMillionTokens": 1.25, From 9d310907e606ac5bee19af35e5518560aec2ae37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Wed, 25 Mar 2026 16:45:27 +0100 Subject: [PATCH 11/13] Begin small refacto --- .../ai/components/AIChatEditorSection.tsx | 8 +++-- .../modules/ai/constants/DefaultFastModel.ts | 1 - .../modules/ai/constants/DefaultSmartModel.ts | 1 - .../src/modules/ai/hooks/useAiModelOptions.ts | 17 +++------ .../hooks/useWorkspaceAiModelAvailability.ts | 15 +++----- .../services/__tests__/apollo.factory.test.ts | 10 +++--- ...olumnDefinitionsFromObjectMetadata.test.ts | 10 +++--- .../ai/components/SettingsAIModelsTab.tsx | 17 +++++---- .../src/testing/mock-data/users.ts | 10 +++--- ...0-migrate-model-ids-to-composite-format.ts | 6 ++-- ...t-manifest-to-universal-flat-agent.util.ts | 4 +-- .../services/client-config.service.ts | 36 ++++++++++--------- .../workspace/workspace.entity.ts | 10 +++--- .../ai/ai-agent/entities/agent.entity.ts | 4 +-- .../constants/ai-models-types.const.spec.ts | 14 ++++---- .../services/ai-model-registry.service.ts | 17 +++++---- .../types/default-fast-model.const.ts | 1 - .../types/default-smart-model.const.ts | 1 - .../utils/is-default-model-sentinel.util.ts | 5 --- .../ai-models/utils/is-model-allowed.util.ts | 4 +-- ...reate-standard-flat-agent-metadata.util.ts | 4 +-- ...rsion-step-operations.workspace-service.ts | 4 +-- .../ai-agent/ai-agent.workflow-action.ts | 4 +-- .../src/constants/AutoSelectFastModelId.ts | 1 + .../src/constants/AutoSelectSmartModelId.ts | 1 + packages/twenty-shared/src/constants/index.ts | 2 ++ packages/twenty-shared/src/utils/index.ts | 1 + .../src/utils/isAutoSelectModelId.ts | 6 ++++ 28 files changed, 113 insertions(+), 101 deletions(-) delete mode 100644 packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts delete mode 100644 packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-default-model-sentinel.util.ts create mode 100644 packages/twenty-shared/src/constants/AutoSelectFastModelId.ts create mode 100644 packages/twenty-shared/src/constants/AutoSelectSmartModelId.ts create mode 100644 packages/twenty-shared/src/utils/isAutoSelectModelId.ts diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx index 825050f355df0..c0025d7d9093a 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx @@ -125,9 +125,13 @@ export const AIChatEditorSection = () => { ); const defaultLabel = workspaceSmartModel?.label - ? t`Default (${workspaceSmartModel.label})` + ? t`${workspaceSmartModel.label} (default)` : t`Default`; + const modelsWithoutDefault = enabledModels.filter( + (model) => model.label !== workspaceSmartModel?.label, + ); + const smartModelOptions = [ { value: null, @@ -137,7 +141,7 @@ export const AIChatEditorSection = () => { workspaceSmartModel?.providerName, ), }, - ...enabledModels.map((model) => ({ + ...modelsWithoutDefault.map((model) => ({ value: model.modelId, label: model.label, Icon: getModelIcon(model.modelFamily, model.providerName), diff --git a/packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts b/packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts deleted file mode 100644 index 84e98b83087b1..0000000000000 --- a/packages/twenty-front/src/modules/ai/constants/DefaultFastModel.ts +++ /dev/null @@ -1 +0,0 @@ -export const DEFAULT_FAST_MODEL = 'default-fast-model' as const; diff --git a/packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts b/packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts deleted file mode 100644 index f85d6f971916b..0000000000000 --- a/packages/twenty-front/src/modules/ai/constants/DefaultSmartModel.ts +++ /dev/null @@ -1 +0,0 @@ -export const DEFAULT_SMART_MODEL = 'default-smart-model' as const; diff --git a/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts b/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts index dd902fba3decc..18c1a5d976976 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts @@ -1,7 +1,6 @@ import { type SelectOption } from 'twenty-ui/input'; +import { isAutoSelectModelId } from 'twenty-shared/utils'; -import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; -import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; import { useWorkspaceAiModelAvailability } from '@/ai/hooks/useWorkspaceAiModelAvailability'; import { aiModelsState } from '@/client-config/states/aiModelsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; @@ -16,11 +15,9 @@ export const useAiModelOptions = (): SelectOption[] => { ) .map((model) => ({ value: model.modelId, - label: - model.modelId === DEFAULT_FAST_MODEL || - model.modelId === DEFAULT_SMART_MODEL - ? model.label - : model.modelFamilyLabel + label: isAutoSelectModelId(model.modelId) + ? model.label + : model.modelFamilyLabel ? `${model.label} (${model.modelFamilyLabel})` : model.label, })) @@ -43,11 +40,7 @@ export const useAiModelLabel = ( return modelId; } - if ( - model.modelId === DEFAULT_FAST_MODEL || - model.modelId === DEFAULT_SMART_MODEL || - !includeProvider - ) { + if (isAutoSelectModelId(model.modelId) || !includeProvider) { return model.label; } diff --git a/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts b/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts index 2b0c7f39e8d5b..7e844289d8afa 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useWorkspaceAiModelAvailability.ts @@ -1,17 +1,10 @@ -import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; -import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; +import { isAutoSelectModelId } from 'twenty-shared/utils'; + import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { aiModelsState } from '@/client-config/states/aiModelsState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { type ClientAiModelConfig } from '~/generated-metadata/graphql'; -const DEFAULT_MODEL_IDS: Set = new Set([ - DEFAULT_SMART_MODEL, - DEFAULT_FAST_MODEL, -]); - -const isDefaultModelId = (modelId: string) => DEFAULT_MODEL_IDS.has(modelId); - export const useWorkspaceAiModelAvailability = () => { const aiModels = useAtomStateValue(aiModelsState); const currentWorkspace = useAtomStateValue(currentWorkspaceState); @@ -23,7 +16,7 @@ export const useWorkspaceAiModelAvailability = () => { modelId: string, model?: ClientAiModelConfig, ): boolean => { - if (isDefaultModelId(modelId)) { + if (isAutoSelectModelId(modelId)) { return true; } @@ -35,7 +28,7 @@ export const useWorkspaceAiModelAvailability = () => { }; const realModels = aiModels.filter( - (model) => !isDefaultModelId(model.modelId) && !model.isDeprecated, + (model) => !isAutoSelectModelId(model.modelId) && !model.isDeprecated, ); const enabledModels = realModels.filter((model) => diff --git a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts index 1d7f029277d8f..97f75257e71d8 100644 --- a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts +++ b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts @@ -2,8 +2,10 @@ import { gql, InMemoryCache } from '@apollo/client'; import { CombinedGraphQLErrors } from '@apollo/client/errors'; import fetchMock, { enableFetchMocks } from 'jest-fetch-mock'; -import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; -import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; +import { + AUTO_SELECT_FAST_MODEL_ID, + AUTO_SELECT_SMART_MODEL_ID, +} from 'twenty-shared/constants'; import { ApolloFactory, type Options } from '@/apollo/services/apollo.factory'; import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant'; import { WorkspaceActivationStatus } from '~/generated-metadata/graphql'; @@ -71,8 +73,8 @@ const mockWorkspace = { isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, eventLogRetentionDays: 365 * 3, - fastModel: DEFAULT_FAST_MODEL, - smartModel: DEFAULT_SMART_MODEL, + fastModel: AUTO_SELECT_FAST_MODEL_ID, + smartModel: AUTO_SELECT_SMART_MODEL_ID, routerModel: 'auto', enabledAiModelIds: [], useRecommendedModels: true, diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts index e77994d0ef08f..2643bc66ee648 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromObjectMetadata.test.ts @@ -1,7 +1,9 @@ import { renderHook } from '@testing-library/react'; -import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; -import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; +import { + AUTO_SELECT_FAST_MODEL_ID, + AUTO_SELECT_SMART_MODEL_ID, +} from 'twenty-shared/constants'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant'; import { useColumnDefinitionsFromObjectMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromObjectMetadata'; @@ -68,8 +70,8 @@ describe('useColumnDefinitionsFromObjectMetadata', () => { isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, eventLogRetentionDays: 365 * 3, - fastModel: DEFAULT_FAST_MODEL, - smartModel: DEFAULT_SMART_MODEL, + fastModel: AUTO_SELECT_FAST_MODEL_ID, + smartModel: AUTO_SELECT_SMART_MODEL_ID, enabledAiModelIds: [], useRecommendedModels: true, }); diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx index f2990ec00658a..48a3d6614fdfe 100644 --- a/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAIModelsTab.tsx @@ -1,8 +1,11 @@ import { useState } from 'react'; import { styled } from '@linaria/react'; -import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; -import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; +import { + AUTO_SELECT_FAST_MODEL_ID, + AUTO_SELECT_SMART_MODEL_ID, +} from 'twenty-shared/constants'; + import { useWorkspaceAiModelAvailability } from '@/ai/hooks/useWorkspaceAiModelAvailability'; import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { aiModelsState } from '@/client-config/states/aiModelsState'; @@ -65,10 +68,12 @@ export const SettingsAIModelsTab = () => { : null; }; - const serverDefaultSmartModelOption = - buildDefaultModelOption(DEFAULT_SMART_MODEL); - const serverDefaultFastModelOption = - buildDefaultModelOption(DEFAULT_FAST_MODEL); + const serverDefaultSmartModelOption = buildDefaultModelOption( + AUTO_SELECT_SMART_MODEL_ID, + ); + const serverDefaultFastModelOption = buildDefaultModelOption( + AUTO_SELECT_FAST_MODEL_ID, + ); const modelOptions = enabledModels.map((model) => { const residencyFlag = model.dataResidency diff --git a/packages/twenty-front/src/testing/mock-data/users.ts b/packages/twenty-front/src/testing/mock-data/users.ts index bd1279262db8a..2bc2f042a5667 100644 --- a/packages/twenty-front/src/testing/mock-data/users.ts +++ b/packages/twenty-front/src/testing/mock-data/users.ts @@ -1,5 +1,7 @@ -import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel'; -import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel'; +import { + AUTO_SELECT_FAST_MODEL_ID, + AUTO_SELECT_SMART_MODEL_ID, +} from 'twenty-shared/constants'; import { type CurrentUserWorkspace } from '@/auth/states/currentUserWorkspaceState'; import { CUSTOM_WORKSPACE_APPLICATION_MOCK } from '@/object-metadata/hooks/__tests__/constants/CustomWorkspaceApplicationMock.test.constant'; import { type WorkspaceMember } from '@/workspace-member/types/WorkspaceMember'; @@ -86,8 +88,8 @@ export const mockCurrentWorkspace = { updatedAt: '2023-04-26T10:23:42.33625+00:00', metadataVersion: 1, trashRetentionDays: 14, - fastModel: DEFAULT_FAST_MODEL, - smartModel: DEFAULT_SMART_MODEL, + fastModel: AUTO_SELECT_FAST_MODEL_ID, + smartModel: AUTO_SELECT_SMART_MODEL_ID, routerModel: 'auto', enabledAiModelIds: [], useRecommendedModels: true, diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1773900000000-migrate-model-ids-to-composite-format.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1773900000000-migrate-model-ids-to-composite-format.ts index e4ae30003719f..d6f689755cfcf 100644 --- a/packages/twenty-server/src/database/typeorm/core/migrations/common/1773900000000-migrate-model-ids-to-composite-format.ts +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1773900000000-migrate-model-ids-to-composite-format.ts @@ -6,7 +6,7 @@ export class MigrateModelIdsToCompositeFormat1773900000000 name = 'MigrateModelIdsToCompositeFormat1773900000000'; public async up(queryRunner: QueryRunner): Promise { - // Reset workspace model columns to sentinel defaults. + // Reset workspace model columns to auto-select placeholders. // The runtime resolves these dynamically from admin preferences. await queryRunner.query( `UPDATE "core"."workspace" @@ -25,7 +25,7 @@ export class MigrateModelIdsToCompositeFormat1773900000000 OR array_length("enabledAiModelIds", 1) > 0`, ); - // Reset agent model IDs to the sentinel so they fall back to workspace defaults + // Reset agent model IDs to the auto-select placeholder so they fall back to workspace defaults await queryRunner.query( `UPDATE "core"."agent" SET "modelId" = 'default-smart-model' @@ -34,6 +34,6 @@ export class MigrateModelIdsToCompositeFormat1773900000000 } public async down(_queryRunner: QueryRunner): Promise { - // No reversal needed — sentinel defaults are safe to leave in place. + // No reversal needed — auto-select placeholders are safe to leave in place. } } diff --git a/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts b/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts index b9900aedb2e37..1ca6978486290 100644 --- a/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/utils/from-agent-manifest-to-universal-flat-agent.util.ts @@ -1,6 +1,6 @@ import { type AgentManifest } from 'twenty-shared/application'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { AUTO_SELECT_SMART_MODEL_ID } from 'twenty-shared/constants'; import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/types/model-id.type'; import { type UniversalFlatAgent } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-agent.type'; @@ -21,7 +21,7 @@ export const fromAgentManifestToUniversalFlatAgent = ({ icon: agentManifest.icon ?? null, description: agentManifest.description ?? null, prompt: agentManifest.prompt, - modelId: (agentManifest.modelId as ModelId) ?? DEFAULT_SMART_MODEL, + modelId: (agentManifest.modelId as ModelId) ?? AUTO_SELECT_SMART_MODEL_ID, responseFormat: { type: 'text' }, modelConfiguration: null, evaluationInputs: [], diff --git a/packages/twenty-server/src/engine/core-modules/client-config/services/client-config.service.ts b/packages/twenty-server/src/engine/core-modules/client-config/services/client-config.service.ts index fedd819dbdc31..ff1ea346b85f0 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/services/client-config.service.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/services/client-config.service.ts @@ -20,8 +20,10 @@ import { DomainServerConfigService } from 'src/engine/core-modules/domain/domain import { PUBLIC_FEATURE_FLAGS } from 'src/engine/core-modules/feature-flag/constants/public-feature-flag.const'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { convertDollarsToBillingCredits } from 'src/engine/metadata-modules/ai/ai-billing/utils/convert-dollars-to-billing-credits.util'; -import { DEFAULT_FAST_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { + AUTO_SELECT_FAST_MODEL_ID, + AUTO_SELECT_SMART_MODEL_ID, +} from 'twenty-shared/constants'; import { MODEL_FAMILY_LABELS } from 'src/engine/metadata-modules/ai/ai-models/constants/model-family-labels.const'; import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service'; @@ -107,10 +109,6 @@ export class ClientConfigService { this.aiModelRegistryService.getDefaultSpeedModel(); const defaultSpeedModelConfig = this.aiModelRegistryService.getModelConfig(defaultSpeedModel?.modelId); - const defaultSpeedModelLabel = - defaultSpeedModelConfig?.label || - defaultSpeedModel?.modelId || - 'Default'; const defaultPerformanceModel = this.aiModelRegistryService.getDefaultPerformanceModel(); @@ -118,23 +116,29 @@ export class ClientConfigService { this.aiModelRegistryService.getModelConfig( defaultPerformanceModel?.modelId, ); - const defaultPerformanceModelLabel = - defaultPerformanceModelConfig?.label || - defaultPerformanceModel?.modelId || - 'Default'; aiModels.unshift( { - modelId: DEFAULT_SMART_MODEL, - label: `Best (${defaultPerformanceModelLabel})`, - sdkPackage: null, + modelId: AUTO_SELECT_SMART_MODEL_ID, + label: + defaultPerformanceModelConfig?.label || + defaultPerformanceModel?.modelId || + 'Default', + modelFamily: defaultPerformanceModelConfig?.modelFamily, + providerName: defaultPerformanceModel?.providerName, + sdkPackage: defaultPerformanceModel?.sdkPackage ?? null, inputCostPerMillionTokensInCredits: 0, outputCostPerMillionTokensInCredits: 0, }, { - modelId: DEFAULT_FAST_MODEL, - label: `Best (${defaultSpeedModelLabel})`, - sdkPackage: null, + modelId: AUTO_SELECT_FAST_MODEL_ID, + label: + defaultSpeedModelConfig?.label || + defaultSpeedModel?.modelId || + 'Default', + modelFamily: defaultSpeedModelConfig?.modelFamily, + providerName: defaultSpeedModel?.providerName, + sdkPackage: defaultSpeedModel?.sdkPackage ?? null, inputCostPerMillionTokensInCredits: 0, outputCostPerMillionTokensInCredits: 0, }, diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts index dcd4807f0fe4a..43edf5ed68099 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts @@ -34,8 +34,10 @@ import { PublicDomainEntity } from 'src/engine/core-modules/public-domain/public import { WorkspaceSSOIdentityProviderEntity } from 'src/engine/core-modules/sso/workspace-sso-identity-provider.entity'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; -import { DEFAULT_FAST_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { + AUTO_SELECT_FAST_MODEL_ID, + AUTO_SELECT_SMART_MODEL_ID, +} from 'twenty-shared/constants'; import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/types/model-id.type'; import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto'; @@ -301,11 +303,11 @@ export class WorkspaceEntity { version: string | null; @Field(() => String, { nullable: false }) - @Column({ type: 'varchar', nullable: false, default: DEFAULT_FAST_MODEL }) + @Column({ type: 'varchar', nullable: false, default: AUTO_SELECT_FAST_MODEL_ID }) fastModel: ModelId; @Field(() => String, { nullable: false }) - @Column({ type: 'varchar', nullable: false, default: DEFAULT_SMART_MODEL }) + @Column({ type: 'varchar', nullable: false, default: AUTO_SELECT_SMART_MODEL_ID }) smartModel: ModelId; @Field(() => String, { nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts index 43336d6e3d327..9145b02874154 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts @@ -10,7 +10,7 @@ import { import { AgentResponseFormat } from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type'; import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { AUTO_SELECT_SMART_MODEL_ID } from 'twenty-shared/constants'; import { type ModelId } from 'src/engine/metadata-modules/ai/ai-models/types/model-id.type'; import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; import { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type'; @@ -43,7 +43,7 @@ export class AgentEntity @Column({ nullable: false, type: 'text' }) prompt: string; - @Column({ nullable: false, type: 'varchar', default: DEFAULT_SMART_MODEL }) + @Column({ nullable: false, type: 'varchar', default: AUTO_SELECT_SMART_MODEL_ID }) modelId: ModelId; // Should not be nullable diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts index 48348699bdb3a..73b89a0acc250 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts @@ -8,7 +8,7 @@ import { SdkProviderFactoryService } from 'src/engine/metadata-modules/ai/ai-mod import { buildCompositeModelId } from 'src/engine/metadata-modules/ai/ai-models/utils/composite-model-id.util'; import { loadDefaultAiProviders } from 'src/engine/metadata-modules/ai/ai-models/utils/load-default-ai-providers.util'; import { type AiProvidersConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-providers-config.type'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { AUTO_SELECT_SMART_MODEL_ID } from 'twenty-shared/constants'; const DEFAULT_PROVIDERS: AiProvidersConfig = loadDefaultAiProviders(); @@ -125,13 +125,13 @@ describe('AiModelRegistryService', () => { service = module.get(AiModelRegistryService); }); - it('should throw when no models are available for DEFAULT_SMART_MODEL', () => { - expect(() => service.getEffectiveModelConfig(DEFAULT_SMART_MODEL)).toThrow( + it('should throw when no models are available for AUTO_SELECT_SMART_MODEL_ID', () => { + expect(() => service.getEffectiveModelConfig(AUTO_SELECT_SMART_MODEL_ID)).toThrow( 'No AI models are available. Configure at least one AI provider.', ); }); - it('should return effective model config for DEFAULT_SMART_MODEL when models are available', () => { + it('should return effective model config for AUTO_SELECT_SMART_MODEL_ID when models are available', () => { jest.spyOn(service, 'getAvailableModels').mockReturnValue([ { modelId: 'openai/gpt-5.2', @@ -146,14 +146,14 @@ describe('AiModelRegistryService', () => { model: {} as any, }); - const result = service.getEffectiveModelConfig(DEFAULT_SMART_MODEL); + const result = service.getEffectiveModelConfig(AUTO_SELECT_SMART_MODEL_ID); expect(result).toBeDefined(); expect(result.modelId).toBe('openai/gpt-5.2'); expect(result.sdkPackage).toBe('@ai-sdk/openai'); }); - it('should return effective model config for DEFAULT_SMART_MODEL with custom model', () => { + it('should return effective model config for AUTO_SELECT_SMART_MODEL_ID with custom model', () => { jest.spyOn(service, 'getAvailableModels').mockReturnValue([ { modelId: 'custom/mistral', @@ -168,7 +168,7 @@ describe('AiModelRegistryService', () => { model: {} as any, }); - const result = service.getEffectiveModelConfig(DEFAULT_SMART_MODEL); + const result = service.getEffectiveModelConfig(AUTO_SELECT_SMART_MODEL_ID); expect(result).toBeDefined(); expect(result.modelId).toBe('custom/mistral'); 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 d3fb129e6784c..87b9fb1615d2b 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 @@ -17,12 +17,15 @@ import { type AiProviderConfig } from 'src/engine/metadata-modules/ai/ai-models/ import { type AiProviderModelConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-provider-model-config.type'; import { type AiProvidersConfig } from 'src/engine/metadata-modules/ai/ai-models/types/ai-providers-config.type'; import { DEFAULT_CONTEXT_WINDOW_TOKENS } from 'src/engine/metadata-modules/ai/ai-models/types/default-context-window-tokens.const'; -import { DEFAULT_FAST_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const'; +import { + AUTO_SELECT_FAST_MODEL_ID, + AUTO_SELECT_SMART_MODEL_ID, +} from 'twenty-shared/constants'; +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 { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.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 { isDefaultModelSentinel } from 'src/engine/metadata-modules/ai/ai-models/utils/is-default-model-sentinel.util'; import { isModelAllowedByWorkspace, type WorkspaceModelAvailabilitySettings, @@ -203,9 +206,9 @@ export class AiModelRegistryService { } getEffectiveModelConfig(modelId: string): AIModelConfig { - if (isDefaultModelSentinel(modelId)) { + if (isAutoSelectModelId(modelId)) { const defaultModel = - modelId === DEFAULT_FAST_MODEL + modelId === AUTO_SELECT_FAST_MODEL_ID ? this.getDefaultSpeedModel() : this.getDefaultPerformanceModel(); @@ -253,7 +256,7 @@ export class AiModelRegistryService { } isModelAdminAllowed(modelId: string): boolean { - if (isDefaultModelSentinel(modelId)) { + if (isAutoSelectModelId(modelId)) { return true; } @@ -360,7 +363,7 @@ export class AiModelRegistryService { resolveModelForAgent(agent: { modelId: string } | null): RegisteredAIModel { const aiModel = this.getEffectiveModelConfig( - agent?.modelId ?? DEFAULT_SMART_MODEL, + agent?.modelId ?? AUTO_SELECT_SMART_MODEL_ID, ); const registeredModel = this.getModel(aiModel.modelId); diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const.ts deleted file mode 100644 index 84e98b83087b1..0000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const.ts +++ /dev/null @@ -1 +0,0 @@ -export const DEFAULT_FAST_MODEL = 'default-fast-model' as const; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const.ts deleted file mode 100644 index f85d6f971916b..0000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const.ts +++ /dev/null @@ -1 +0,0 @@ -export const DEFAULT_SMART_MODEL = 'default-smart-model' as const; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-default-model-sentinel.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-default-model-sentinel.util.ts deleted file mode 100644 index 34bd198b263cd..0000000000000 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-default-model-sentinel.util.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { DEFAULT_FAST_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-fast-model.const'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; - -export const isDefaultModelSentinel = (modelId: string): boolean => - modelId === DEFAULT_FAST_MODEL || modelId === DEFAULT_SMART_MODEL; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-model-allowed.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-model-allowed.util.ts index 53f5b1519b0ce..3350106786e95 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-model-allowed.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/utils/is-model-allowed.util.ts @@ -1,4 +1,4 @@ -import { isDefaultModelSentinel } from 'src/engine/metadata-modules/ai/ai-models/utils/is-default-model-sentinel.util'; +import { isAutoSelectModelId } from 'twenty-shared/utils'; export type WorkspaceModelAvailabilitySettings = { useRecommendedModels: boolean; @@ -10,7 +10,7 @@ export const isModelAllowedByWorkspace = ( workspace: WorkspaceModelAvailabilitySettings, recommendedModelIds?: Set, ): boolean => { - if (isDefaultModelSentinel(modelId)) { + if (isAutoSelectModelId(modelId)) { return true; } diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-flat-agent-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-flat-agent-metadata.util.ts index cd12cc64c103c..2415607f69275 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-flat-agent-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/agent-metadata/create-standard-flat-agent-metadata.util.ts @@ -1,4 +1,4 @@ -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { AUTO_SELECT_SMART_MODEL_ID } from 'twenty-shared/constants'; import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; import { type AllStandardAgentName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-agent-name.type'; import { @@ -40,7 +40,7 @@ Response format: - Use markdown for readability Always base answers on official Twenty documentation. Be patient and helpful.`, - modelId: DEFAULT_SMART_MODEL, + modelId: AUTO_SELECT_SMART_MODEL_ID, responseFormat: { type: 'text' }, isCustom: false, modelConfiguration: {}, diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts index efcd5e9fd9e2c..4c1de0627a5d2 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service.ts @@ -19,7 +19,7 @@ import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/work import { type WorkflowStepPositionInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-step-position.input'; import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service'; import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { AUTO_SELECT_SMART_MODEL_ID } from 'twenty-shared/constants'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util'; @@ -446,7 +446,7 @@ export class WorkflowVersionStepOperationsWorkspaceService { description: '', prompt: 'You are a helpful AI assistant. Complete the task based on the workflow context.', - modelId: DEFAULT_SMART_MODEL, + modelId: AUTO_SELECT_SMART_MODEL_ID, responseFormat: { type: 'text' }, isCustom: true, }, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts index de3f41e799634..c9d76a0aee412 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts @@ -9,7 +9,7 @@ import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/inte import { AgentAsyncExecutorService } from 'src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service'; import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { AiBillingService } from 'src/engine/metadata-modules/ai/ai-billing/services/ai-billing.service'; -import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/types/default-smart-model.const'; +import { AUTO_SELECT_SMART_MODEL_ID } from 'twenty-shared/constants'; import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, @@ -90,7 +90,7 @@ export class AiAgentWorkflowAction implements WorkflowAction { }); await this.aiBillingService.calculateAndBillUsage( - agent?.modelId ?? DEFAULT_SMART_MODEL, + agent?.modelId ?? AUTO_SELECT_SMART_MODEL_ID, { usage, cacheCreationTokens }, workspaceId, agent?.id || null, diff --git a/packages/twenty-shared/src/constants/AutoSelectFastModelId.ts b/packages/twenty-shared/src/constants/AutoSelectFastModelId.ts new file mode 100644 index 0000000000000..5a18f5f460229 --- /dev/null +++ b/packages/twenty-shared/src/constants/AutoSelectFastModelId.ts @@ -0,0 +1 @@ +export const AUTO_SELECT_FAST_MODEL_ID = 'default-fast-model' as const; diff --git a/packages/twenty-shared/src/constants/AutoSelectSmartModelId.ts b/packages/twenty-shared/src/constants/AutoSelectSmartModelId.ts new file mode 100644 index 0000000000000..cc5e9ebbc4298 --- /dev/null +++ b/packages/twenty-shared/src/constants/AutoSelectSmartModelId.ts @@ -0,0 +1 @@ +export const AUTO_SELECT_SMART_MODEL_ID = 'default-smart-model' as const; diff --git a/packages/twenty-shared/src/constants/index.ts b/packages/twenty-shared/src/constants/index.ts index 3b06cd9ef7ca4..ea586dd07de17 100644 --- a/packages/twenty-shared/src/constants/index.ts +++ b/packages/twenty-shared/src/constants/index.ts @@ -7,6 +7,8 @@ * |___/ */ +export { AUTO_SELECT_FAST_MODEL_ID } from './AutoSelectFastModelId'; +export { AUTO_SELECT_SMART_MODEL_ID } from './AutoSelectSmartModelId'; export { BACKEND_BATCH_REQUEST_MAX_COUNT } from './BackendBatchRequestMaxCount'; export { CalendarStartDay } from './CalendarStartDay'; export { COMPOSITE_FIELD_TYPE_SUB_FIELDS_NAMES } from './CompositeFieldTypeSubFieldsNames'; diff --git a/packages/twenty-shared/src/utils/index.ts b/packages/twenty-shared/src/utils/index.ts index ebdf8072ed263..01517cad0b166 100644 --- a/packages/twenty-shared/src/utils/index.ts +++ b/packages/twenty-shared/src/utils/index.ts @@ -143,6 +143,7 @@ export { getLogoUrlFromDomainName, } from './image/getLogoUrlFromDomainName'; export { getUniqueConstraintsFields } from './indexMetadata/getUniqueConstraintsFields'; +export { isAutoSelectModelId } from './isAutoSelectModelId'; export { fastDeepEqual } from './json/fast-deep-equal'; export { getAppPath } from './navigation/getAppPath'; export { getSettingsPath } from './navigation/getSettingsPath'; diff --git a/packages/twenty-shared/src/utils/isAutoSelectModelId.ts b/packages/twenty-shared/src/utils/isAutoSelectModelId.ts new file mode 100644 index 0000000000000..06cbf35edbe7c --- /dev/null +++ b/packages/twenty-shared/src/utils/isAutoSelectModelId.ts @@ -0,0 +1,6 @@ +import { AUTO_SELECT_FAST_MODEL_ID } from '../constants/AutoSelectFastModelId'; +import { AUTO_SELECT_SMART_MODEL_ID } from '../constants/AutoSelectSmartModelId'; + +export const isAutoSelectModelId = (modelId: string): boolean => + modelId === AUTO_SELECT_FAST_MODEL_ID || + modelId === AUTO_SELECT_SMART_MODEL_ID; From 3d7346dfd4e7aaaed93dfb0e00c9b41be81065d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Wed, 25 Mar 2026 22:52:31 +0100 Subject: [PATCH 12/13] UI improvements --- .../AIChatCreditsExhaustedMessage.tsx | 49 ++------------ .../ai/components/AIChatEditorSection.tsx | 45 +++++++------ .../AIChatErrorUnderMessageList.tsx | 14 +++- .../components/AgentChatAiSdkStreamEffect.tsx | 3 +- .../src/modules/ai/hooks/useAiModelOptions.ts | 4 +- .../__tests__/normalizeAiSdkError.test.ts | 60 +++++++++++++++++ .../modules/ai/utils/normalizeAiSdkError.ts | 43 ++++++++++++ .../DateTimeSettingsDateFormatSelect.tsx | 24 +++---- .../DateTimeSettingsTimeFormatSelect.tsx | 15 +++-- .../DateTimeSettingsTimeZoneSelect.tsx | 18 ++--- .../components/NumberFormatSelect.tsx | 21 +++--- .../modules/ui/input/components/Select.tsx | 29 +++++++- .../ui/input/components/SelectControl.tsx | 8 ++- .../ai/components/SettingsAIModelsTab.tsx | 66 +++++++++---------- ...DateTimeSettingsCalendarStartDaySelect.tsx | 42 ++++++------ .../controllers/agent-chat.controller.ts | 2 +- .../twenty-ui/src/input/types/SelectOption.ts | 1 + 17 files changed, 275 insertions(+), 169 deletions(-) create mode 100644 packages/twenty-front/src/modules/ai/utils/__tests__/normalizeAiSdkError.test.ts create mode 100644 packages/twenty-front/src/modules/ai/utils/normalizeAiSdkError.ts diff --git a/packages/twenty-front/src/modules/ai/components/AIChatCreditsExhaustedMessage.tsx b/packages/twenty-front/src/modules/ai/components/AIChatCreditsExhaustedMessage.tsx index fa023b328ab2d..c37e771fc2bb3 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatCreditsExhaustedMessage.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatCreditsExhaustedMessage.tsx @@ -1,67 +1,28 @@ import { AIChatBanner } from '@/ai/components/AIChatBanner'; -import { useEndSubscriptionTrialPeriod } from '@/settings/billing/hooks/useEndSubscriptionTrialPeriod'; -import { useRedirect } from '@/domain-manager/hooks/useRedirect'; import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap'; import { useSubscriptionStatus } from '@/workspace/hooks/useSubscriptionStatus'; import { t } from '@lingui/core/macro'; -import { useState } from 'react'; import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { IconSparkles } from 'twenty-ui/display'; -import { useQuery } from '@apollo/client/react'; import { PermissionFlagType, SubscriptionStatus, - BillingPortalSessionDocument, } from '~/generated-metadata/graphql'; +import { useNavigateSettings } from '~/hooks/useNavigateSettings'; export const AIChatCreditsExhaustedMessage = () => { - const { redirect } = useRedirect(); + const navigateSettings = useNavigateSettings(); const subscriptionStatus = useSubscriptionStatus(); - const { endTrialPeriod, isLoading: isEndingTrial } = - useEndSubscriptionTrialPeriod(); - const [isProcessing, setIsProcessing] = useState(false); const isTrialing = subscriptionStatus === SubscriptionStatus.Trialing; const { [PermissionFlagType.WORKSPACE]: hasPermissionToManageBilling } = usePermissionFlagMap(); - const { data: billingPortalData, loading: isBillingPortalLoading } = useQuery( - BillingPortalSessionDocument, - { - variables: { - returnUrlPath: getSettingsPath(SettingsPath.Billing), - }, - }, - ); - - const openBillingPortal = () => { - if ( - isDefined(billingPortalData) && - isDefined(billingPortalData.billingPortalSession.url) - ) { - redirect(billingPortalData.billingPortalSession.url); - } + const handleUpgradeClick = () => { + navigateSettings(SettingsPath.Billing); }; - const handleUpgradeClick = async () => { - if (!isTrialing) { - openBillingPortal(); - return; - } - - setIsProcessing(true); - const result = await endTrialPeriod(); - setIsProcessing(false); - - if (!result.success) { - openBillingPortal(); - } - }; - - const isLoading = isEndingTrial || isBillingPortalLoading || isProcessing; - const message = hasPermissionToManageBilling ? isTrialing ? t`Free trial credits exhausted. Subscribe now to continue using AI features.` @@ -79,8 +40,6 @@ export const AIChatCreditsExhaustedMessage = () => { buttonOnClick={ hasPermissionToManageBilling ? handleUpgradeClick : undefined } - isButtonDisabled={isLoading} - isButtonLoading={isLoading} /> ); }; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx index c0025d7d9093a..04667ca3f057b 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx @@ -124,29 +124,31 @@ export const AIChatEditorSection = () => { (model) => model.modelId === currentWorkspace?.smartModel, ); - const defaultLabel = workspaceSmartModel?.label - ? t`${workspaceSmartModel.label} (default)` - : t`Default`; - - const modelsWithoutDefault = enabledModels.filter( - (model) => model.label !== workspaceSmartModel?.label, - ); - - const smartModelOptions = [ - { - value: null, - label: defaultLabel, - Icon: getModelIcon( - workspaceSmartModel?.modelFamily, - workspaceSmartModel?.providerName, - ), - }, - ...modelsWithoutDefault.map((model) => ({ - value: model.modelId, + const resolvedDefaultModelId = enabledModels.find( + (model) => + model.label === workspaceSmartModel?.label && + model.providerName === workspaceSmartModel?.providerName, + )?.modelId; + + const defaultPinnedOption = workspaceSmartModel + ? { + value: null as string | null, + label: workspaceSmartModel.label, + Icon: getModelIcon( + workspaceSmartModel.modelFamily, + workspaceSmartModel.providerName, + ), + contextualText: t`default`, + } + : undefined; + + const smartModelOptions = enabledModels + .filter((model) => model.modelId !== resolvedDefaultModelId) + .map((model) => ({ + value: model.modelId as string | null, label: model.label, Icon: getModelIcon(model.modelFamily, model.providerName), - })), - ]; + })); return ( <> @@ -172,6 +174,7 @@ export const AIChatEditorSection = () => { value={selectedModelId} onChange={setAgentChatUserSelectedModel} options={smartModelOptions} + pinnedOption={defaultPinnedOption} selectSizeVariant="small" withSearchInput dropdownOffset={{ x: 0, y: 8 }} diff --git a/packages/twenty-front/src/modules/ai/components/AIChatErrorUnderMessageList.tsx b/packages/twenty-front/src/modules/ai/components/AIChatErrorUnderMessageList.tsx index 1e17d8a8334a5..4e9f61f2ce072 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatErrorUnderMessageList.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatErrorUnderMessageList.tsx @@ -1,4 +1,4 @@ -import { AIChatStandaloneError } from '@/ai/components/AIChatStandaloneError'; +import { AIChatErrorRenderer } from '@/ai/components/AIChatErrorRenderer'; import { AgentMessageRole } from '@/ai/constants/AgentMessageRole'; import { agentChatErrorState } from '@/ai/states/agentChatErrorState'; import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState'; @@ -7,6 +7,12 @@ import { agentChatMessageIdsComponentSelector } from '@/ai/states/agentChatMessa import { useAtomComponentFamilySelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilySelectorValue'; import { useAtomComponentSelectorValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentSelectorValue'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; +import { styled } from '@linaria/react'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; + +const StyledErrorWrapper = styled.div` + padding-top: ${themeCssVariables.spacing[3]}; +`; export const AIChatErrorUnderMessageList = () => { const agentChatError = useAtomStateValue(agentChatErrorState); @@ -31,5 +37,9 @@ export const AIChatErrorUnderMessageList = () => { return null; } - return ; + return ( + + + + ); }; diff --git a/packages/twenty-front/src/modules/ai/components/AgentChatAiSdkStreamEffect.tsx b/packages/twenty-front/src/modules/ai/components/AgentChatAiSdkStreamEffect.tsx index b728822d6dc21..e0df67e2e551d 100644 --- a/packages/twenty-front/src/modules/ai/components/AgentChatAiSdkStreamEffect.tsx +++ b/packages/twenty-front/src/modules/ai/components/AgentChatAiSdkStreamEffect.tsx @@ -6,6 +6,7 @@ import { useEnsureAgentChatThreadIdForSend } from '@/ai/hooks/useEnsureAgentChat import { agentChatErrorState } from '@/ai/states/agentChatErrorState'; import { agentChatIsLoadingState } from '@/ai/states/agentChatIsLoadingState'; import { agentChatIsStreamingState } from '@/ai/states/agentChatIsStreamingState'; +import { normalizeAiSdkError } from '@/ai/utils/normalizeAiSdkError'; import { agentChatMessagesComponentFamilyState } from '@/ai/states/agentChatMessagesComponentFamilyState'; import { agentChatMessagesLoadingState } from '@/ai/states/agentChatMessagesLoadingState'; import { agentChatThreadsLoadingState } from '@/ai/states/agentChatThreadsLoadingState'; @@ -119,7 +120,7 @@ export const AgentChatAiSdkStreamEffect = () => { const setAgentChatError = useSetAtomState(agentChatErrorState); useEffect(() => { - setAgentChatError(chatState.error); + setAgentChatError(normalizeAiSdkError(chatState.error)); }, [chatState.error, setAgentChatError]); const setAgentChatIsStreaming = useSetAtomState(agentChatIsStreamingState); diff --git a/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts b/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts index 18c1a5d976976..27398e3543b64 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts @@ -18,8 +18,8 @@ export const useAiModelOptions = (): SelectOption[] => { label: isAutoSelectModelId(model.modelId) ? model.label : model.modelFamilyLabel - ? `${model.label} (${model.modelFamilyLabel})` - : model.label, + ? `${model.label} (${model.modelFamilyLabel})` + : model.label, })) .sort((a, b) => a.label.localeCompare(b.label)); }; diff --git a/packages/twenty-front/src/modules/ai/utils/__tests__/normalizeAiSdkError.test.ts b/packages/twenty-front/src/modules/ai/utils/__tests__/normalizeAiSdkError.test.ts new file mode 100644 index 0000000000000..50088384fc18e --- /dev/null +++ b/packages/twenty-front/src/modules/ai/utils/__tests__/normalizeAiSdkError.test.ts @@ -0,0 +1,60 @@ +import { normalizeAiSdkError } from '@/ai/utils/normalizeAiSdkError'; + +describe('normalizeAiSdkError', () => { + it('should return undefined for undefined input', () => { + expect(normalizeAiSdkError(undefined)).toBeUndefined(); + }); + + it('should return the error unchanged if it already has a code', () => { + const error = new Error('test') as Error & { code: string }; + error.code = 'EXISTING_CODE'; + + const result = normalizeAiSdkError(error); + + expect(result).toBe(error); + expect((result as Error & { code: string }).code).toBe('EXISTING_CODE'); + }); + + it('should parse JSON message and attach code from response body', () => { + const error = new Error( + '{"statusCode":402,"error":"Error","messages":["Credits exhausted"],"code":"BILLING_CREDITS_EXHAUSTED"}', + ); + + const result = normalizeAiSdkError(error); + + expect(result).not.toBe(error); + expect((result as Error & { code: string }).code).toBe( + 'BILLING_CREDITS_EXHAUSTED', + ); + expect(result?.message).toBe(error.message); + }); + + it('should return original error for non-JSON message', () => { + const error = new Error('Something went wrong'); + + const result = normalizeAiSdkError(error); + + expect(result).toBe(error); + }); + + it('should return original error for JSON message without code', () => { + const error = new Error( + '{"statusCode":500,"error":"Internal Server Error"}', + ); + + const result = normalizeAiSdkError(error); + + expect(result).toBe(error); + }); + + it('should preserve the original stack trace', () => { + const error = new Error( + '{"statusCode":402,"code":"BILLING_CREDITS_EXHAUSTED"}', + ); + const originalStack = error.stack; + + const result = normalizeAiSdkError(error); + + expect(result?.stack).toBe(originalStack); + }); +}); diff --git a/packages/twenty-front/src/modules/ai/utils/normalizeAiSdkError.ts b/packages/twenty-front/src/modules/ai/utils/normalizeAiSdkError.ts new file mode 100644 index 0000000000000..bca90d57f1ee7 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/utils/normalizeAiSdkError.ts @@ -0,0 +1,43 @@ +import { isDefined } from 'twenty-shared/utils'; + +// The Vercel AI SDK wraps non-200 responses into an Error whose message +// is the raw JSON response body, losing any custom properties (like `code`). +// This function parses that JSON and re-attaches the code so downstream +// consumers (extractErrorCode) can find it without knowing about the SDK. +export const normalizeAiSdkError = ( + error: Error | undefined, +): Error | undefined => { + if (!isDefined(error) || !(error instanceof Error)) { + return error; + } + + if ( + 'code' in error && + typeof (error as Error & { code: string }).code === 'string' + ) { + return error; + } + + try { + const parsed: unknown = JSON.parse(error.message); + + if ( + isDefined(parsed) && + typeof parsed === 'object' && + 'code' in parsed && + typeof (parsed as { code: unknown }).code === 'string' + ) { + const normalizedError = new Error(error.message) as Error & { + code: string; + }; + normalizedError.code = (parsed as { code: string }).code; + normalizedError.stack = error.stack; + + return normalizedError; + } + } catch { + // message is not JSON — nothing to normalize + } + + return error; +}; diff --git a/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx b/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx index fe349367fa3aa..5f2ced128c36f 100644 --- a/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx +++ b/packages/twenty-front/src/modules/settings/experience/components/DateTimeSettingsDateFormatSelect.tsx @@ -34,38 +34,38 @@ export const DateTimeSettingsDateFormatSelect = ({ return ( diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts index 3181639df65ea..e80a6ff49c830 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts @@ -39,9 +39,9 @@ import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models @Controller('rest/agent-chat') @UseGuards(JwtAuthGuard, WorkspaceAuthGuard) @UseFilters( + RestApiExceptionFilter, AgentRestApiExceptionFilter, BillingRestApiExceptionFilter, - RestApiExceptionFilter, ) export class AgentChatController { constructor( diff --git a/packages/twenty-ui/src/input/types/SelectOption.ts b/packages/twenty-ui/src/input/types/SelectOption.ts index 949eae87712bd..3b3f9d39f2dbf 100644 --- a/packages/twenty-ui/src/input/types/SelectOption.ts +++ b/packages/twenty-ui/src/input/types/SelectOption.ts @@ -10,4 +10,5 @@ export type SelectOption< value: Value; disabled?: boolean; color?: ThemeColor | 'transparent'; + contextualText?: string; }; From 93b2310be505353c7c631e8f988fde796422e1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Thu, 26 Mar 2026 04:00:12 +0100 Subject: [PATCH 13/13] Add showContextualTextInControl prop to Select, simplify billing redirect, normalize AI SDK errors Made-with: Cursor --- .../ai/components/AIChatEditorSection.tsx | 1 + .../modules/ui/input/components/Select.tsx | 33 +++++++++++++++---- .../workspace/workspace.entity.ts | 12 +++++-- .../ai/ai-agent/entities/agent.entity.ts | 6 +++- .../constants/ai-models-types.const.spec.ts | 4 ++- 5 files changed, 45 insertions(+), 11 deletions(-) diff --git a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx index 04667ca3f057b..ff7eff864bb1c 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatEditorSection.tsx @@ -176,6 +176,7 @@ export const AIChatEditorSection = () => { options={smartModelOptions} pinnedOption={defaultPinnedOption} selectSizeVariant="small" + showContextualTextInControl={false} withSearchInput dropdownOffset={{ x: 0, y: 8 }} /> diff --git a/packages/twenty-front/src/modules/ui/input/components/Select.tsx b/packages/twenty-front/src/modules/ui/input/components/Select.tsx index 8e7f79d899bec..96adf7ed2759c 100644 --- a/packages/twenty-front/src/modules/ui/input/components/Select.tsx +++ b/packages/twenty-front/src/modules/ui/input/components/Select.tsx @@ -53,6 +53,7 @@ export type SelectProps = { callToActionButton?: CallToActionButton; dropdownOffset?: DropdownOffset; hasRightElement?: boolean; + showContextualTextInControl?: boolean; }; const StyledContainer = styled.div<{ fullWidth?: boolean }>` @@ -93,6 +94,7 @@ export const Select = ({ callToActionButton, dropdownOffset, hasRightElement, + showContextualTextInControl = true, }: SelectProps) => { const selectContainerRef = useRef(null); @@ -155,13 +157,26 @@ export const Select = ({ const { setSelectedItemId } = useSelectableList(dropdownId); + const controlSelectedOption = useMemo(() => { + if (!isDefined(selectedOption) || showContextualTextInControl) { + return selectedOption; + } + + const { contextualText: _, ...rest } = selectedOption; + + return rest; + }, [selectedOption, showContextualTextInControl]); + const handleDropdownOpen = () => { - if (isDefined(selectedOption) && !isNonEmptyString(searchInputValue)) { - setSelectedItemId(selectedOption.label); + if ( + isDefined(controlSelectedOption) && + !isNonEmptyString(searchInputValue) + ) { + setSelectedItemId(controlSelectedOption.label); } }; - if (!isDefined(selectedOption)) { + if (!isDefined(controlSelectedOption)) { return <>; } @@ -176,7 +191,7 @@ export const Select = ({ {isNonEmptyString(label) && {label}} {isDisabled ? ( ({ onOpen={handleDropdownOpen} clickableComponent={ ({ LeftIcon={pinnedOption.Icon} text={pinnedOption.label} contextualText={pinnedOption.contextualText} - selected={selectedOption.value === pinnedOption.value} + selected={ + controlSelectedOption.value === pinnedOption.value + } needIconCheck={needIconCheck} onClick={() => { onChange?.(pinnedOption.value); @@ -247,7 +264,9 @@ export const Select = ({ LeftIcon={option.Icon} text={option.label} contextualText={option.contextualText} - selected={selectedOption.value === option.value} + selected={ + controlSelectedOption.value === option.value + } focused={selectedItemId === option.label} needIconCheck={needIconCheck} onClick={() => { diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts index 43edf5ed68099..c940095da42b2 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.entity.ts @@ -303,11 +303,19 @@ export class WorkspaceEntity { version: string | null; @Field(() => String, { nullable: false }) - @Column({ type: 'varchar', nullable: false, default: AUTO_SELECT_FAST_MODEL_ID }) + @Column({ + type: 'varchar', + nullable: false, + default: AUTO_SELECT_FAST_MODEL_ID, + }) fastModel: ModelId; @Field(() => String, { nullable: false }) - @Column({ type: 'varchar', nullable: false, default: AUTO_SELECT_SMART_MODEL_ID }) + @Column({ + type: 'varchar', + nullable: false, + default: AUTO_SELECT_SMART_MODEL_ID, + }) smartModel: ModelId; @Field(() => String, { nullable: true }) diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts index 9145b02874154..40fd8e01b50cf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts @@ -43,7 +43,11 @@ export class AgentEntity @Column({ nullable: false, type: 'text' }) prompt: string; - @Column({ nullable: false, type: 'varchar', default: AUTO_SELECT_SMART_MODEL_ID }) + @Column({ + nullable: false, + type: 'varchar', + default: AUTO_SELECT_SMART_MODEL_ID, + }) modelId: ModelId; // Should not be nullable diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts index 73b89a0acc250..3ecbf1bec993f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-models/constants/ai-models-types.const.spec.ts @@ -126,7 +126,9 @@ describe('AiModelRegistryService', () => { }); it('should throw when no models are available for AUTO_SELECT_SMART_MODEL_ID', () => { - expect(() => service.getEffectiveModelConfig(AUTO_SELECT_SMART_MODEL_ID)).toThrow( + expect(() => + service.getEffectiveModelConfig(AUTO_SELECT_SMART_MODEL_ID), + ).toThrow( 'No AI models are available. Configure at least one AI provider.', ); });