@@ -14,6 +14,7 @@ import { useGateway } from "@/browser/hooks/useGatewayModels";
1414import { usePersistedState } from "@/browser/hooks/usePersistedState" ;
1515import { useProvidersConfig } from "@/browser/hooks/useProvidersConfig" ;
1616import { SearchableModelSelect } from "../components/SearchableModelSelect" ;
17+ import type { EffectivePolicy } from "@/common/orpc/types" ;
1718import { KNOWN_MODELS } from "@/common/constants/knownModels" ;
1819import { PROVIDER_DISPLAY_NAMES } from "@/common/constants/providers" ;
1920import { usePolicy } from "@/browser/contexts/PolicyContext" ;
@@ -24,6 +25,33 @@ import {
2425} from "@/common/constants/storage" ;
2526import { ModelRow } from "./ModelRow" ;
2627
28+ function isModelAllowedByPolicy ( policy : EffectivePolicy | null , modelString : string ) : boolean {
29+ const providerAccess = policy ?. providerAccess ;
30+ if ( providerAccess == null ) {
31+ return true ;
32+ }
33+
34+ const colonIndex = modelString . indexOf ( ":" ) ;
35+ if ( colonIndex <= 0 || colonIndex === modelString . length - 1 ) {
36+ return true ;
37+ }
38+
39+ const provider = modelString . slice ( 0 , colonIndex ) ;
40+ const modelId = modelString . slice ( colonIndex + 1 ) ;
41+
42+ const providerPolicy = providerAccess . find ( ( p ) => p . id === provider ) ;
43+ if ( ! providerPolicy ) {
44+ return false ;
45+ }
46+
47+ const allowedModels = providerPolicy . allowedModels ?? null ;
48+ if ( allowedModels === null ) {
49+ return true ;
50+ }
51+
52+ return allowedModels . includes ( modelId ) ;
53+ }
54+
2755// Providers to exclude from the custom models UI (handled specially or internal)
2856const HIDDEN_PROVIDERS = new Set ( [ "mux-gateway" ] ) ;
2957
@@ -80,8 +108,13 @@ export function ModelsSection() {
80108 { listener : true }
81109 ) ;
82110
83- // All models (including hidden) for the settings dropdowns
111+ // All models (including hidden) for the settings dropdowns.
112+ // PolicyService enforces model access on the backend, but we also filter here so users can't
113+ // select models that will be denied at send time.
84114 const allModels = getSuggestedModels ( config ) ;
115+ const selectableModels = effectivePolicy
116+ ? allModels . filter ( ( model ) => isModelAllowedByPolicy ( effectivePolicy , model ) )
117+ : allModels ;
85118
86119 // Check if a model already exists (for duplicate prevention)
87120 const modelExists = useCallback (
@@ -232,7 +265,7 @@ export function ModelsSection() {
232265 < SearchableModelSelect
233266 value = { defaultModel }
234267 onChange = { setDefaultModel }
235- models = { allModels }
268+ models = { selectableModels }
236269 placeholder = "Select model"
237270 />
238271 </ div >
@@ -247,7 +280,7 @@ export function ModelsSection() {
247280 < SearchableModelSelect
248281 value = { compactionModel }
249282 onChange = { setCompactionModel }
250- models = { allModels }
283+ models = { selectableModels }
251284 emptyOption = { { value : "" , label : "Use workspace model" } }
252285 />
253286 </ div >
0 commit comments