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>
This commit is contained in:
+3
-1
@@ -15,7 +15,9 @@ import { Paragraph } from '@tiptap/extension-paragraph';
|
||||
import { Strike } from '@tiptap/extension-strike';
|
||||
import { Text } from '@tiptap/extension-text';
|
||||
import { Underline } from '@tiptap/extension-underline';
|
||||
import { Dropcursor, Placeholder, UndoRedo } from '@tiptap/extensions';
|
||||
import { Dropcursor } from '@tiptap/extensions/drop-cursor';
|
||||
import { Placeholder } from '@tiptap/extensions/placeholder';
|
||||
import { UndoRedo } from '@tiptap/extensions/undo-redo';
|
||||
import { type Editor, useEditor } from '@tiptap/react';
|
||||
import { marked } from 'marked';
|
||||
import { type DependencyList, useMemo } from 'react';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Document } from '@tiptap/extension-document';
|
||||
import { HardBreak } from '@tiptap/extension-hard-break';
|
||||
import { Paragraph } from '@tiptap/extension-paragraph';
|
||||
import { Text } from '@tiptap/extension-text';
|
||||
import { Placeholder } from '@tiptap/extensions';
|
||||
import { Placeholder } from '@tiptap/extensions/placeholder';
|
||||
import { useEditor } from '@tiptap/react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -4,6 +4,7 @@ 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,
|
||||
@@ -18,11 +19,21 @@ export const useAiModelOptions = (
|
||||
model.modelId === DEFAULT_FAST_MODEL ||
|
||||
model.modelId === DEFAULT_SMART_MODEL
|
||||
? model.label
|
||||
: `${model.label} (${model.provider})`,
|
||||
: `${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,
|
||||
@@ -47,5 +58,5 @@ export const useAiModelLabel = (
|
||||
return model.label;
|
||||
}
|
||||
|
||||
return `${model.label} (${model.provider})`;
|
||||
return `${model.label} (${getModelFamilyLabel(model.modelFamily) ?? model.inferenceProvider})`;
|
||||
};
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@ import { Extension } from '@tiptap/core';
|
||||
import Document from '@tiptap/extension-document';
|
||||
import Paragraph from '@tiptap/extension-paragraph';
|
||||
import Text from '@tiptap/extension-text';
|
||||
import { Placeholder, UndoRedo } from '@tiptap/extensions';
|
||||
import { Placeholder } from '@tiptap/extensions/placeholder';
|
||||
import { UndoRedo } from '@tiptap/extensions/undo-redo';
|
||||
import { Plugin, PluginKey } from '@tiptap/pm/state';
|
||||
import { type Editor, useEditor } from '@tiptap/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
+2
-1
@@ -4,7 +4,8 @@ import Document from '@tiptap/extension-document';
|
||||
import HardBreak from '@tiptap/extension-hard-break';
|
||||
import Paragraph from '@tiptap/extension-paragraph';
|
||||
import Text from '@tiptap/extension-text';
|
||||
import { Placeholder, UndoRedo } from '@tiptap/extensions';
|
||||
import { Placeholder } from '@tiptap/extensions/placeholder';
|
||||
import { UndoRedo } from '@tiptap/extensions/undo-redo';
|
||||
import { AllSelection, TextSelection } from '@tiptap/pm/state';
|
||||
import { type Editor, useEditor } from '@tiptap/react';
|
||||
import { isDefined, parseJson } from 'twenty-shared/utils';
|
||||
|
||||
Reference in New Issue
Block a user