Skip to content

Commit 3f45f99

Browse files
committed
Deprecate free grok 4 fast
1 parent 8622d93 commit 3f45f99

File tree

23 files changed

+267
-18
lines changed

23 files changed

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

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)