litellm_rs/core/providers/github_copilot/
config.rs1use crate::core::traits::provider::ProviderConfig;
6use serde::{Deserialize, Serialize};
7
8pub const GITHUB_COPILOT_API_BASE: &str = "https://api.githubcopilot.com";
10
11pub const EDITOR_PLUGIN_VERSION: &str = "copilot-chat/0.26.7";
13
14pub const USER_AGENT: &str = "GitHubCopilotChat/0.26.7";
16
17pub const API_VERSION: &str = "2025-04-01";
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct GitHubCopilotConfig {
23 pub token_dir: Option<String>,
25
26 pub access_token_file: Option<String>,
28
29 pub api_key_file: Option<String>,
31
32 pub api_base: Option<String>,
34
35 #[serde(default = "default_timeout")]
37 pub timeout: u64,
38
39 #[serde(default = "default_max_retries")]
41 pub max_retries: u32,
42
43 #[serde(default)]
45 pub disable_system_to_assistant: bool,
46
47 #[serde(default)]
49 pub debug: bool,
50}
51
52impl Default for GitHubCopilotConfig {
53 fn default() -> Self {
54 Self {
55 token_dir: None,
56 access_token_file: None,
57 api_key_file: None,
58 api_base: None,
59 timeout: default_timeout(),
60 max_retries: default_max_retries(),
61 disable_system_to_assistant: false,
62 debug: false,
63 }
64 }
65}
66
67impl ProviderConfig for GitHubCopilotConfig {
68 fn validate(&self) -> Result<(), String> {
69 if self.timeout == 0 {
74 return Err("Timeout must be greater than 0".to_string());
75 }
76
77 Ok(())
78 }
79
80 fn api_key(&self) -> Option<&str> {
81 None
83 }
84
85 fn api_base(&self) -> Option<&str> {
86 self.api_base.as_deref()
87 }
88
89 fn timeout(&self) -> std::time::Duration {
90 std::time::Duration::from_secs(self.timeout)
91 }
92
93 fn max_retries(&self) -> u32 {
94 self.max_retries
95 }
96}
97
98impl GitHubCopilotConfig {
99 pub fn get_token_dir(&self) -> String {
101 self.token_dir
102 .clone()
103 .or_else(|| std::env::var("GITHUB_COPILOT_TOKEN_DIR").ok())
104 .unwrap_or_else(|| {
105 let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
106 format!("{}/.config/litellm/github_copilot", home)
107 })
108 }
109
110 pub fn get_access_token_file(&self) -> String {
112 self.access_token_file
113 .clone()
114 .or_else(|| std::env::var("GITHUB_COPILOT_ACCESS_TOKEN_FILE").ok())
115 .unwrap_or_else(|| "access-token".to_string())
116 }
117
118 pub fn get_api_key_file(&self) -> String {
120 self.api_key_file
121 .clone()
122 .or_else(|| std::env::var("GITHUB_COPILOT_API_KEY_FILE").ok())
123 .unwrap_or_else(|| "api-key.json".to_string())
124 }
125
126 pub fn get_api_base(&self) -> String {
128 self.api_base
129 .clone()
130 .or_else(|| std::env::var("GITHUB_COPILOT_API_BASE").ok())
131 .unwrap_or_else(|| GITHUB_COPILOT_API_BASE.to_string())
132 }
133}
134
135pub fn get_copilot_default_headers(api_key: &str) -> std::collections::HashMap<String, String> {
137 let mut headers = std::collections::HashMap::new();
138 headers.insert("Authorization".to_string(), format!("Bearer {}", api_key));
139 headers.insert("Content-Type".to_string(), "application/json".to_string());
140 headers.insert(
141 "copilot-integration-id".to_string(),
142 "vscode-chat".to_string(),
143 );
144 headers.insert("editor-version".to_string(), "vscode/1.95.0".to_string());
145 headers.insert(
146 "editor-plugin-version".to_string(),
147 EDITOR_PLUGIN_VERSION.to_string(),
148 );
149 headers.insert("user-agent".to_string(), USER_AGENT.to_string());
150 headers.insert(
151 "openai-intent".to_string(),
152 "conversation-panel".to_string(),
153 );
154 headers.insert("x-github-api-version".to_string(), API_VERSION.to_string());
155 headers.insert("x-request-id".to_string(), uuid::Uuid::new_v4().to_string());
156 headers.insert(
157 "x-vscode-user-agent-library-version".to_string(),
158 "electron-fetch".to_string(),
159 );
160 headers
161}
162
163fn default_timeout() -> u64 {
164 60
165}
166
167fn default_max_retries() -> u32 {
168 3
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_github_copilot_config_default() {
177 let config = GitHubCopilotConfig::default();
178 assert!(config.token_dir.is_none());
179 assert!(config.access_token_file.is_none());
180 assert!(config.api_key_file.is_none());
181 assert!(config.api_base.is_none());
182 assert_eq!(config.timeout, 60);
183 assert_eq!(config.max_retries, 3);
184 assert!(!config.disable_system_to_assistant);
185 assert!(!config.debug);
186 }
187
188 #[test]
189 fn test_github_copilot_config_get_api_base_default() {
190 let config = GitHubCopilotConfig::default();
191 assert_eq!(config.get_api_base(), "https://api.githubcopilot.com");
192 }
193
194 #[test]
195 fn test_github_copilot_config_get_api_base_custom() {
196 let config = GitHubCopilotConfig {
197 api_base: Some("https://custom.copilot.com".to_string()),
198 ..Default::default()
199 };
200 assert_eq!(config.get_api_base(), "https://custom.copilot.com");
201 }
202
203 #[test]
204 fn test_github_copilot_config_provider_config_trait() {
205 let config = GitHubCopilotConfig {
206 api_base: Some("https://custom.copilot.com".to_string()),
207 timeout: 60,
208 max_retries: 5,
209 ..Default::default()
210 };
211
212 assert!(config.api_key().is_none()); assert_eq!(config.api_base(), Some("https://custom.copilot.com"));
214 assert_eq!(config.timeout(), std::time::Duration::from_secs(60));
215 assert_eq!(config.max_retries(), 5);
216 }
217
218 #[test]
219 fn test_github_copilot_config_validation() {
220 let config = GitHubCopilotConfig::default();
221 assert!(config.validate().is_ok());
222 }
223
224 #[test]
225 fn test_github_copilot_config_validation_zero_timeout() {
226 let config = GitHubCopilotConfig {
227 timeout: 0,
228 ..Default::default()
229 };
230 assert!(config.validate().is_err());
231 }
232
233 #[test]
234 fn test_github_copilot_config_serialization() {
235 let config = GitHubCopilotConfig {
236 token_dir: Some("/custom/path".to_string()),
237 api_base: Some("https://custom.copilot.com".to_string()),
238 timeout: 45,
239 max_retries: 2,
240 disable_system_to_assistant: true,
241 debug: true,
242 ..Default::default()
243 };
244
245 let json = serde_json::to_value(&config).unwrap();
246 assert_eq!(json["token_dir"], "/custom/path");
247 assert_eq!(json["api_base"], "https://custom.copilot.com");
248 assert_eq!(json["timeout"], 45);
249 assert_eq!(json["max_retries"], 2);
250 assert!(json["disable_system_to_assistant"].as_bool().unwrap());
251 assert!(json["debug"].as_bool().unwrap());
252 }
253
254 #[test]
255 fn test_github_copilot_config_deserialization() {
256 let json = r#"{
257 "api_base": "https://custom.copilot.com",
258 "timeout": 60,
259 "debug": true
260 }"#;
261
262 let config: GitHubCopilotConfig = serde_json::from_str(json).unwrap();
263 assert_eq!(
264 config.api_base,
265 Some("https://custom.copilot.com".to_string())
266 );
267 assert_eq!(config.timeout, 60);
268 assert!(config.debug);
269 }
270
271 #[test]
272 fn test_get_copilot_default_headers() {
273 let headers = get_copilot_default_headers("test-api-key");
274
275 assert!(
276 headers
277 .get("Authorization")
278 .unwrap()
279 .contains("Bearer test-api-key")
280 );
281 assert_eq!(headers.get("Content-Type").unwrap(), "application/json");
282 assert_eq!(
283 headers.get("copilot-integration-id").unwrap(),
284 "vscode-chat"
285 );
286 assert!(headers.contains_key("x-request-id"));
287 }
288}