@@ -66,6 +66,9 @@ pub enum CortexError {
6666 #[ error( "Model not found: {model}" ) ]
6767 ModelNotFound { model : String } ,
6868
69+ #[ error( "Model deprecated: {model}. {suggestion}" ) ]
70+ ModelDeprecated { model : String , suggestion : String } ,
71+
6972 #[ error( "Provider not found: {provider}" ) ]
7073 ProviderNotFound { provider : String } ,
7174
@@ -105,6 +108,9 @@ pub enum CortexError {
105108 #[ error( "Permission denied: {path}" ) ]
106109 PermissionDenied { path : PathBuf } ,
107110
111+ #[ error( "Permission denied (possible SELinux denial): {path}. {hint}" ) ]
112+ PermissionDeniedSelinux { path : PathBuf , hint : String } ,
113+
108114 // Serialization errors
109115 #[ error( "JSON error: {0}" ) ]
110116 Json ( #[ from] serde_json:: Error ) ,
@@ -210,6 +216,13 @@ impl CortexError {
210216 Self :: MdnsError ( message. into ( ) )
211217 }
212218
219+ /// Create a deprecated model error with a helpful suggestion.
220+ pub fn model_deprecated ( model : impl Into < String > , raw_error : & str ) -> Self {
221+ let model = model. into ( ) ;
222+ let suggestion = suggest_model_replacement ( & model, raw_error) ;
223+ Self :: ModelDeprecated { model, suggestion }
224+ }
225+
213226 /// Check if this error is retriable.
214227 pub fn is_retriable ( & self ) -> bool {
215228 matches ! (
@@ -232,6 +245,80 @@ impl CortexError {
232245 }
233246}
234247
248+ /// Suggest a replacement model for deprecated models.
249+ fn suggest_model_replacement ( model : & str , _raw_error : & str ) -> String {
250+ // Common deprecated model replacements
251+ let suggestion = match model {
252+ // OpenAI deprecated models
253+ m if m. contains ( "gpt-3.5-turbo-0301" ) => "Try 'gpt-3.5-turbo' or 'gpt-4o-mini' instead." ,
254+ m if m. contains ( "gpt-3.5-turbo-0613" ) => "Try 'gpt-3.5-turbo' or 'gpt-4o-mini' instead." ,
255+ m if m. contains ( "gpt-4-0314" ) => "Try 'gpt-4' or 'gpt-4-turbo' instead." ,
256+ m if m. contains ( "gpt-4-0613" ) => "Try 'gpt-4' or 'gpt-4-turbo' instead." ,
257+ m if m. contains ( "gpt-4-32k" ) => "Try 'gpt-4-turbo' (128K context) instead." ,
258+ m if m. contains ( "text-davinci" ) => "Try 'gpt-3.5-turbo' or 'gpt-4o-mini' instead." ,
259+ m if m. contains ( "code-davinci" ) => "Try 'gpt-4' or 'gpt-4-turbo' instead." ,
260+ // Anthropic deprecated models
261+ m if m. contains ( "claude-instant" ) => "Try 'claude-3-haiku' instead." ,
262+ m if m. contains ( "claude-2.0" ) => "Try 'claude-3-sonnet' or 'claude-3-opus' instead." ,
263+ m if m. contains ( "claude-2.1" ) => "Try 'claude-3-sonnet' or 'claude-3-opus' instead." ,
264+ // Generic suggestion
265+ _ => "Run 'cortex models list' to see available models." ,
266+ } ;
267+
268+ format ! (
269+ "{} {}" ,
270+ suggestion, "Run 'cortex models list' for all available models."
271+ )
272+ }
273+
274+ /// Check if an API error message indicates a deprecated model.
275+ pub fn is_deprecated_model_error ( error_message : & str ) -> bool {
276+ let lower = error_message. to_lowercase ( ) ;
277+ lower. contains ( "deprecated" )
278+ || lower. contains ( "decommissioned" )
279+ || lower. contains ( "no longer available" )
280+ || lower. contains ( "model has been removed" )
281+ || lower. contains ( "model is retired" )
282+ || ( lower. contains ( "model" ) && lower. contains ( "not found" ) && lower. contains ( "has been" ) )
283+ }
284+
285+ /// Check if SELinux is enabled and enforcing on the system.
286+ #[ cfg( target_os = "linux" ) ]
287+ pub fn is_selinux_enforcing ( ) -> bool {
288+ // Check /sys/fs/selinux/enforce (reads "1" if enforcing)
289+ if let Ok ( content) = std:: fs:: read_to_string ( "/sys/fs/selinux/enforce" ) {
290+ return content. trim ( ) == "1" ;
291+ }
292+ // Alternative: check getenforce command
293+ if let Ok ( output) = std:: process:: Command :: new ( "getenforce" ) . output ( ) {
294+ if let Ok ( status) = String :: from_utf8 ( output. stdout ) {
295+ return status. trim ( ) . eq_ignore_ascii_case ( "enforcing" ) ;
296+ }
297+ }
298+ false
299+ }
300+
301+ #[ cfg( not( target_os = "linux" ) ) ]
302+ pub fn is_selinux_enforcing ( ) -> bool {
303+ false
304+ }
305+
306+ /// Create a permission denied error with SELinux hint if applicable.
307+ pub fn permission_denied_with_selinux_check ( path : PathBuf ) -> CortexError {
308+ if is_selinux_enforcing ( ) {
309+ CortexError :: PermissionDeniedSelinux {
310+ path : path. clone ( ) ,
311+ hint : format ! (
312+ "SELinux may be blocking access. Check 'ausearch -m avc -ts recent' for denials. \
313+ Try 'restorecon -Rv {}' to fix SELinux contexts.",
314+ path. display( )
315+ ) ,
316+ }
317+ } else {
318+ CortexError :: PermissionDenied { path }
319+ }
320+ }
321+
235322/// Convert protocol error info to CortexError.
236323impl From < cortex_protocol:: CortexErrorInfo > for CortexError {
237324 fn from ( info : cortex_protocol:: CortexErrorInfo ) -> Self {
0 commit comments