Skip to content

Commit 7ba8e33

Browse files
Deprecate free grok 4 fast (#8481)
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent 8622d93 commit 7ba8e33

File tree

23 files changed

+271
-18
lines changed

23 files changed

+271
-18
lines changed

packages/types/src/model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ export const modelInfoSchema = z.object({
7676
minTokensPerCachePoint: z.number().optional(),
7777
maxCachePoints: z.number().optional(),
7878
cachableFields: z.array(z.string()).optional(),
79+
// Flag to indicate if the model is deprecated and should not be used
80+
deprecated: z.boolean().optional(),
7981
/**
8082
* Service tiers with pricing information.
8183
* Each tier can have a name (for OpenAI service tiers) and pricing overrides.

packages/types/src/providers/roo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const rooModels = {
3838
outputPrice: 0,
3939
description:
4040
"Grok 4 Fast is xAI's latest multimodal model with SOTA cost-efficiency and a 2M token context window. (Note: prompts and completions are logged by xAI and used to improve the model.)",
41+
deprecated: true,
4142
},
4243
"deepseek/deepseek-chat-v3.1": {
4344
maxTokens: 16_384,

webview-ui/src/components/settings/ApiOptions.tsx

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -263,15 +263,24 @@ const ApiOptions = ({
263263

264264
const filteredModels = filterModels(models, selectedProvider, organizationAllowList)
265265

266-
const modelOptions = filteredModels
267-
? Object.keys(filteredModels).map((modelId) => ({
268-
value: modelId,
269-
label: modelId,
270-
}))
266+
// Include the currently selected model even if deprecated (so users can see what they have selected)
267+
// But filter out other deprecated models from being newly selectable
268+
const availableModels = filteredModels
269+
? Object.entries(filteredModels)
270+
.filter(([modelId, modelInfo]) => {
271+
// Always include the currently selected model
272+
if (modelId === selectedModelId) return true
273+
// Filter out deprecated models that aren't currently selected
274+
return !modelInfo.deprecated
275+
})
276+
.map(([modelId]) => ({
277+
value: modelId,
278+
label: modelId,
279+
}))
271280
: []
272281

273-
return modelOptions
274-
}, [selectedProvider, organizationAllowList])
282+
return availableModels
283+
}, [selectedProvider, organizationAllowList, selectedModelId])
275284

276285
const onProviderChange = useCallback(
277286
(value: ProviderName) => {
@@ -716,20 +725,28 @@ const ApiOptions = ({
716725
</Select>
717726
</div>
718727

728+
{/* Show error if a deprecated model is selected */}
729+
{selectedModelInfo?.deprecated && (
730+
<ApiErrorMessage errorMessage={t("settings:validation.modelDeprecated")} />
731+
)}
732+
719733
{selectedProvider === "bedrock" && selectedModelId === "custom-arn" && (
720734
<BedrockCustomArn
721735
apiConfiguration={apiConfiguration}
722736
setApiConfigurationField={setApiConfigurationField}
723737
/>
724738
)}
725739

726-
<ModelInfoView
727-
apiProvider={selectedProvider}
728-
selectedModelId={selectedModelId}
729-
modelInfo={selectedModelInfo}
730-
isDescriptionExpanded={isDescriptionExpanded}
731-
setIsDescriptionExpanded={setIsDescriptionExpanded}
732-
/>
740+
{/* Only show model info if not deprecated */}
741+
{!selectedModelInfo?.deprecated && (
742+
<ModelInfoView
743+
apiProvider={selectedProvider}
744+
selectedModelId={selectedModelId}
745+
modelInfo={selectedModelInfo}
746+
isDescriptionExpanded={isDescriptionExpanded}
747+
setIsDescriptionExpanded={setIsDescriptionExpanded}
748+
/>
749+
)}
733750
</>
734751
)}
735752

webview-ui/src/components/settings/ModelPicker.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,30 @@ export const ModelPicker = ({
7575
const selectTimeoutRef = useRef<NodeJS.Timeout | null>(null)
7676
const closeTimeoutRef = useRef<NodeJS.Timeout | null>(null)
7777

78+
const { id: selectedModelId, info: selectedModelInfo } = useSelectedModel(apiConfiguration)
79+
7880
const modelIds = useMemo(() => {
7981
const filteredModels = filterModels(models, apiConfiguration.apiProvider, organizationAllowList)
8082

81-
return Object.keys(filteredModels ?? {}).sort((a, b) => a.localeCompare(b))
82-
}, [models, apiConfiguration.apiProvider, organizationAllowList])
83+
// Include the currently selected model even if deprecated (so users can see what they have selected)
84+
// But filter out other deprecated models from being newly selectable
85+
const availableModels = Object.entries(filteredModels ?? {})
86+
.filter(([modelId, modelInfo]) => {
87+
// Always include the currently selected model
88+
if (modelId === selectedModelId) return true
89+
// Filter out deprecated models that aren't currently selected
90+
return !modelInfo.deprecated
91+
})
92+
.reduce(
93+
(acc, [modelId, modelInfo]) => {
94+
acc[modelId] = modelInfo
95+
return acc
96+
},
97+
{} as Record<string, ModelInfo>,
98+
)
8399

84-
const { id: selectedModelId, info: selectedModelInfo } = useSelectedModel(apiConfiguration)
100+
return Object.keys(availableModels).sort((a, b) => a.localeCompare(b))
101+
}, [models, apiConfiguration.apiProvider, organizationAllowList, selectedModelId])
85102

86103
const [searchValue, setSearchValue] = useState("")
87104

@@ -225,7 +242,10 @@ export const ModelPicker = ({
225242
</Popover>
226243
</div>
227244
{errorMessage && <ApiErrorMessage errorMessage={errorMessage} />}
228-
{selectedModelId && selectedModelInfo && (
245+
{selectedModelInfo?.deprecated && (
246+
<ApiErrorMessage errorMessage={t("settings:validation.modelDeprecated")} />
247+
)}
248+
{selectedModelId && selectedModelInfo && !selectedModelInfo.deprecated && (
229249
<ModelInfoView
230250
apiProvider={apiConfiguration.apiProvider}
231251
selectedModelId={selectedModelId}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// npx vitest src/components/settings/__tests__/ModelPicker.deprecated.spec.tsx
2+
3+
import { render, screen } from "@testing-library/react"
4+
import userEvent from "@testing-library/user-event"
5+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
6+
import { describe, it, expect, vi, beforeEach } from "vitest"
7+
8+
import { ModelPicker } from "../ModelPicker"
9+
import type { ModelInfo } from "@roo-code/types"
10+
11+
// Mock the i18n module
12+
vi.mock("@src/i18n/TranslationContext", () => ({
13+
useAppTranslation: () => ({
14+
t: (key: string, options?: any) => {
15+
// Handle specific translation keys
16+
if (key === "settings:validation.modelDeprecated") {
17+
return "This model is no longer available. Please select a different model."
18+
}
19+
if (options) return `${key} ${JSON.stringify(options)}`
20+
return key
21+
},
22+
}),
23+
}))
24+
25+
// Mock the useSelectedModel hook
26+
vi.mock("@/components/ui/hooks/useSelectedModel", () => ({
27+
useSelectedModel: (apiConfiguration: any) => {
28+
const modelId = apiConfiguration?.openRouterModelId || "model-1"
29+
const models: Record<string, ModelInfo> = {
30+
"model-1": {
31+
maxTokens: 1000,
32+
contextWindow: 4000,
33+
supportsPromptCache: true,
34+
},
35+
"model-2": {
36+
maxTokens: 2000,
37+
contextWindow: 8000,
38+
supportsPromptCache: false,
39+
},
40+
"deprecated-model": {
41+
maxTokens: 1500,
42+
contextWindow: 6000,
43+
supportsPromptCache: true,
44+
deprecated: true,
45+
},
46+
}
47+
return {
48+
id: modelId,
49+
info: models[modelId],
50+
provider: "openrouter",
51+
isLoading: false,
52+
isError: false,
53+
}
54+
},
55+
}))
56+
57+
describe("ModelPicker - Deprecated Models", () => {
58+
const mockSetApiConfigurationField = vi.fn()
59+
const queryClient = new QueryClient({
60+
defaultOptions: {
61+
queries: { retry: false },
62+
},
63+
})
64+
65+
const regularModels: Record<string, ModelInfo> = {
66+
"model-1": {
67+
maxTokens: 1000,
68+
contextWindow: 4000,
69+
supportsPromptCache: true,
70+
},
71+
"model-2": {
72+
maxTokens: 2000,
73+
contextWindow: 8000,
74+
supportsPromptCache: false,
75+
},
76+
"deprecated-model": {
77+
maxTokens: 1500,
78+
contextWindow: 6000,
79+
supportsPromptCache: true,
80+
deprecated: true,
81+
},
82+
}
83+
84+
beforeEach(() => {
85+
vi.clearAllMocks()
86+
})
87+
88+
it("should filter out deprecated models from the dropdown", async () => {
89+
const user = userEvent.setup()
90+
91+
render(
92+
<QueryClientProvider client={queryClient}>
93+
<ModelPicker
94+
defaultModelId="model-1"
95+
models={regularModels}
96+
modelIdKey="openRouterModelId"
97+
serviceName="Test Service"
98+
serviceUrl="https://test.com"
99+
apiConfiguration={{ apiProvider: "openrouter" }}
100+
setApiConfigurationField={mockSetApiConfigurationField}
101+
organizationAllowList={{ allowAll: true, providers: {} }}
102+
/>
103+
</QueryClientProvider>,
104+
)
105+
106+
// Open the dropdown
107+
const button = screen.getByTestId("model-picker-button")
108+
await user.click(button)
109+
110+
// Check that non-deprecated models are shown
111+
expect(screen.getByTestId("model-option-model-1")).toBeInTheDocument()
112+
expect(screen.getByTestId("model-option-model-2")).toBeInTheDocument()
113+
114+
// Check that deprecated model is NOT shown
115+
expect(screen.queryByTestId("model-option-deprecated-model")).not.toBeInTheDocument()
116+
})
117+
118+
it("should show error when a deprecated model is currently selected", () => {
119+
render(
120+
<QueryClientProvider client={queryClient}>
121+
<ModelPicker
122+
defaultModelId="deprecated-model"
123+
models={regularModels}
124+
modelIdKey="openRouterModelId"
125+
serviceName="Test Service"
126+
serviceUrl="https://test.com"
127+
apiConfiguration={{
128+
apiProvider: "openrouter",
129+
openRouterModelId: "deprecated-model",
130+
}}
131+
setApiConfigurationField={mockSetApiConfigurationField}
132+
organizationAllowList={{ allowAll: true, providers: {} }}
133+
/>
134+
</QueryClientProvider>,
135+
)
136+
137+
// Check that the error message is displayed
138+
expect(
139+
screen.getByText("This model is no longer available. Please select a different model."),
140+
).toBeInTheDocument()
141+
})
142+
143+
it("should allow selecting non-deprecated models", async () => {
144+
const user = userEvent.setup()
145+
146+
render(
147+
<QueryClientProvider client={queryClient}>
148+
<ModelPicker
149+
defaultModelId="model-1"
150+
models={regularModels}
151+
modelIdKey="openRouterModelId"
152+
serviceName="Test Service"
153+
serviceUrl="https://test.com"
154+
apiConfiguration={{ apiProvider: "openrouter" }}
155+
setApiConfigurationField={mockSetApiConfigurationField}
156+
organizationAllowList={{ allowAll: true, providers: {} }}
157+
/>
158+
</QueryClientProvider>,
159+
)
160+
161+
// Open the dropdown
162+
const button = screen.getByTestId("model-picker-button")
163+
await user.click(button)
164+
165+
// Select a non-deprecated model
166+
const model2Option = screen.getByTestId("model-option-model-2")
167+
await user.click(model2Option)
168+
169+
// Verify the selection was made
170+
expect(mockSetApiConfigurationField).toHaveBeenCalledWith("openRouterModelId", "model-2")
171+
})
172+
173+
it("should not display model info for deprecated models", () => {
174+
render(
175+
<QueryClientProvider client={queryClient}>
176+
<ModelPicker
177+
defaultModelId="deprecated-model"
178+
models={regularModels}
179+
modelIdKey="openRouterModelId"
180+
serviceName="Test Service"
181+
serviceUrl="https://test.com"
182+
apiConfiguration={{
183+
apiProvider: "openrouter",
184+
openRouterModelId: "deprecated-model",
185+
}}
186+
setApiConfigurationField={mockSetApiConfigurationField}
187+
organizationAllowList={{ allowAll: true, providers: {} }}
188+
/>
189+
</QueryClientProvider>,
190+
)
191+
192+
// Model info should not be displayed for deprecated models
193+
expect(screen.queryByText("This is a deprecated model")).not.toBeInTheDocument()
194+
})
195+
})

webview-ui/src/i18n/locales/ca/settings.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/de/settings.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/en/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,7 @@
829829
"regionMismatch": "Warning: The region in your ARN ({{arnRegion}}) does not match your selected region ({{region}}). This may cause access issues. The provider will use the region from the ARN."
830830
},
831831
"modelAvailability": "The model ID ({{modelId}}) you provided is not available. Please choose a different model.",
832+
"modelDeprecated": "This model is no longer available. Please select a different model.",
832833
"providerNotAllowed": "Provider '{{provider}}' is not allowed by your organization",
833834
"modelNotAllowed": "Model '{{model}}' is not allowed for provider '{{provider}}' by your organization",
834835
"profileInvalid": "This profile contains a provider or model that is not allowed by your organization",

webview-ui/src/i18n/locales/es/settings.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webview-ui/src/i18n/locales/fr/settings.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)