twenty/packages/twenty-front/src/modules/ai/hooks/useAiModelOptions.ts
Félix Malfait 41f09c5a4c
Add AI model pricing, Bedrock provider, and InferenceProvider/ModelFamily split (#18155)
## Summary

- **Model pricing overhaul**: All model constants updated with accurate
pricing in dollars per 1M tokens, including cached input rates, cache
creation rates, and tiered >200k context pricing
- **New providers**: Added Google (Gemini 3.x), Mistral, and AWS Bedrock
as inference providers. Bedrock serves Claude Opus 4.6 and Sonnet 4.6
via AWS, with proper credential handling following the existing S3/SES
pattern
- **InferenceProvider/ModelFamily split**: Refactored `ModelProvider`
into two orthogonal enums — `InferenceProvider` (who serves the model:
auth, SDK, metadata format) and `ModelFamily` (who created it: token
counting semantics). This eliminates growing `||` chains for token
normalization checks like `excludesCachedTokens`
- **Billing improvements**: Reasoning tokens charged at output rate,
cache token discounts applied accurately, real errors thrown to Sentry
on billing failures

## Test plan

- [x] All existing unit tests updated and passing (23 tests across 3
test files)
- [x] Lint passes for both twenty-server and twenty-front
- [ ] CI checks pass


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-22 14:39:04 +01:00

62 lines
1.8 KiB
TypeScript

import { aiModelsState } from '@/client-config/states/aiModelsState';
import { type SelectOption } from 'twenty-ui/input';
import { DEFAULT_FAST_MODEL } from '@/ai/constants/DefaultFastModel';
import { DEFAULT_SMART_MODEL } from '@/ai/constants/DefaultSmartModel';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { MODEL_FAMILY_CONFIG } from '~/pages/settings/ai/constants/SettingsAiModelProviders';
export const useAiModelOptions = (
includeDeprecated = false,
): SelectOption<string>[] => {
const aiModels = useRecoilValueV2(aiModelsState);
return aiModels
.filter((model) => includeDeprecated || !model.deprecated)
.map((model) => ({
value: model.modelId,
label:
model.modelId === DEFAULT_FAST_MODEL ||
model.modelId === DEFAULT_SMART_MODEL
? model.label
: `${model.label} (${getModelFamilyLabel(model.modelFamily) ?? model.inferenceProvider})`,
}))
.sort((a, b) => a.label.localeCompare(b.label));
};
const getModelFamilyLabel = (
modelFamily: string | null | undefined,
): string | undefined => {
if (!modelFamily) {
return undefined;
}
return MODEL_FAMILY_CONFIG[modelFamily]?.label || modelFamily;
};
export const useAiModelLabel = (
modelId: string | undefined,
includeProvider = true,
): string => {
const aiModels = useRecoilValueV2(aiModelsState);
if (!modelId) {
return '';
}
const model = aiModels.find((m) => m.modelId === modelId);
if (!model) {
return modelId;
}
if (
model.modelId === DEFAULT_FAST_MODEL ||
model.modelId === DEFAULT_SMART_MODEL ||
!includeProvider
) {
return model.label;
}
return `${model.label} (${getModelFamilyLabel(model.modelFamily) ?? model.inferenceProvider})`;
};