vct_core/models/
provider.rs1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum Provider {
19 ClaudeCode,
21 Codex,
23 Copilot,
25 Gemini,
27 OpenCode,
29 Cursor,
31 Hermes,
33 Grok,
35 Unknown,
37}
38
39impl Provider {
40 pub const fn from_model_name(model: &str) -> Self {
59 let bytes = model.as_bytes();
61
62 if bytes.len() >= 6 {
63 if bytes[0] == b'c'
65 && bytes[1] == b'l'
66 && bytes[2] == b'a'
67 && bytes[3] == b'u'
68 && bytes[4] == b'd'
69 && bytes[5] == b'e'
70 {
71 return Self::ClaudeCode;
72 }
73 }
74
75 if bytes.len() >= 7
77 && bytes[0] == b'c'
78 && bytes[1] == b'o'
79 && bytes[2] == b'p'
80 && bytes[3] == b'i'
81 && bytes[4] == b'l'
82 && bytes[5] == b'o'
83 && bytes[6] == b't'
84 {
85 return Self::Copilot;
86 }
87
88 if bytes.len() >= 6
89 && bytes[0] == b'g'
90 && bytes[1] == b'e'
91 && bytes[2] == b'm'
92 && bytes[3] == b'i'
93 && bytes[4] == b'n'
94 && bytes[5] == b'i'
95 {
96 return Self::Gemini;
97 }
98
99 if bytes.len() >= 4
100 && bytes[0] == b'g'
101 && bytes[1] == b'r'
102 && bytes[2] == b'o'
103 && bytes[3] == b'k'
104 {
105 return Self::Grok;
106 }
107
108 if bytes.len() >= 3 && bytes[0] == b'g' && bytes[1] == b'p' && bytes[2] == b't' {
110 return Self::Codex;
111 }
112
113 if bytes.len() >= 2 && bytes[0] == b'o' && (bytes[1] == b'1' || bytes[1] == b'3') {
114 return Self::Codex;
115 }
116
117 Self::Unknown
118 }
119
120 pub const fn display_name(&self) -> &'static str {
124 match self {
125 Self::ClaudeCode => "Claude",
126 Self::Codex => "Codex",
127 Self::Copilot => "Copilot",
128 Self::Gemini => "Gemini",
129 Self::OpenCode => "OpenCode",
130 Self::Cursor => "Cursor",
131 Self::Hermes => "Hermes",
132 Self::Grok => "Grok",
133 Self::Unknown => "Unknown",
134 }
135 }
136}
137
138impl fmt::Display for Provider {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 write!(f, "{}", self.display_name())
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_provider_detection() {
150 assert_eq!(
151 Provider::from_model_name("claude-sonnet-4"),
152 Provider::ClaudeCode
153 );
154 assert_eq!(
155 Provider::from_model_name("claude-3-opus"),
156 Provider::ClaudeCode
157 );
158 assert_eq!(Provider::from_model_name("gpt-4-turbo"), Provider::Codex);
159 assert_eq!(Provider::from_model_name("gpt-3.5"), Provider::Codex);
160 assert_eq!(Provider::from_model_name("o1-preview"), Provider::Codex);
161 assert_eq!(Provider::from_model_name("o3-mini"), Provider::Codex);
162 assert_eq!(Provider::from_model_name("copilot"), Provider::Copilot);
163 assert_eq!(
164 Provider::from_model_name("copilot-gpt-4"),
165 Provider::Copilot
166 );
167 assert_eq!(Provider::from_model_name("gemini-pro"), Provider::Gemini);
168 assert_eq!(
169 Provider::from_model_name("gemini-2.0-flash"),
170 Provider::Gemini
171 );
172 assert_eq!(Provider::from_model_name("grok-4.5"), Provider::Grok);
173 assert_eq!(
174 Provider::from_model_name("unknown-model"),
175 Provider::Unknown
176 );
177 }
178
179 #[test]
180 fn test_provider_display() {
181 assert_eq!(Provider::ClaudeCode.display_name(), "Claude");
182 assert_eq!(Provider::Codex.display_name(), "Codex");
183 assert_eq!(Provider::Copilot.display_name(), "Copilot");
184 assert_eq!(Provider::Gemini.display_name(), "Gemini");
185 assert_eq!(Provider::OpenCode.display_name(), "OpenCode");
186 assert_eq!(Provider::Cursor.display_name(), "Cursor");
187 assert_eq!(Provider::Hermes.display_name(), "Hermes");
188 assert_eq!(Provider::Grok.display_name(), "Grok");
189 assert_eq!(Provider::Unknown.display_name(), "Unknown");
190 }
191}