Skip to content

Commit 80a69de

Browse files
committed
fix(api): provider endpoint mismatches preventing Copilot save, test, and remove
Fix #415 Three endpoint mismatches between frontend and backend caused the GitHub Copilot provider's save, test, and remove buttons to fail with 405 Method Not Allowed. These affected all providers for save. - change update_provider annotation from post to put to match frontend - fix test button URL from /providers/test to /providers/test-model - add github-copilot entry in build_test_llm_config since default_provider_config returns None for providers that require token exchange - widen GITHUB_COPILOT_DEFAULT_BASE_URL visibility to pub(crate) - add unit test for build_test_llm_config with github-copilot fix(api): Copilot provider shows as available after remove when env var is set get_providers fell back to the GITHUB_COPILOT_API_KEY env var when the TOML key was absent, so the provider stayed visible in settings after a remove — the env var can't be unset from a running process. Only check the TOML key for Copilot status in the config-exists path. The env var fallback remains for the no-config-file case (fresh install).
1 parent 5c85557 commit 80a69de

3 files changed

Lines changed: 48 additions & 3 deletions

File tree

src/api/providers.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,21 @@ fn build_test_llm_config(provider: &str, credential: &str) -> crate::config::Llm
207207
let mut providers = HashMap::new();
208208
if let Some(provider_config) = crate::config::default_provider_config(provider, credential) {
209209
providers.insert(provider.to_string(), provider_config);
210+
} else if provider == "github-copilot" {
211+
// GitHub Copilot uses token exchange, so default_provider_config returns None.
212+
// For testing, add a provider entry with the PAT as the api_key —
213+
// LlmManager::get_copilot_token() will exchange it for a real Copilot token.
214+
providers.insert(
215+
provider.to_string(),
216+
crate::config::ProviderConfig {
217+
api_type: crate::config::ApiType::OpenAiChatCompletions,
218+
base_url: crate::config::GITHUB_COPILOT_DEFAULT_BASE_URL.to_string(),
219+
api_key: credential.to_string(),
220+
name: Some("GitHub Copilot".to_string()),
221+
use_bearer_auth: true,
222+
extra_headers: vec![],
223+
},
224+
);
210225
}
211226

212227
crate::config::LlmConfig {
@@ -447,6 +462,15 @@ pub(super) async fn get_providers(
447462
env_set(env_var)
448463
};
449464

465+
// Copilot: only check TOML key, not env var. The env var GITHUB_COPILOT_API_KEY
466+
// is a fallback for config loading but shouldn't keep the provider visible in
467+
// the settings UI after a remove — the env var can't be unset from the process.
468+
let copilot_configured = doc
469+
.get("llm")
470+
.and_then(|llm| llm.get("github_copilot_key"))
471+
.and_then(|val| val.as_str())
472+
.is_some_and(|s| resolve_value(s).is_some());
473+
450474
(
451475
has_value("anthropic_key", "ANTHROPIC_API_KEY"),
452476
has_value("openai_key", "OPENAI_API_KEY"),
@@ -470,13 +494,13 @@ pub(super) async fn get_providers(
470494
has_value("minimax_cn_key", "MINIMAX_CN_API_KEY"),
471495
has_value("moonshot_key", "MOONSHOT_API_KEY"),
472496
has_value("zai_coding_plan_key", "ZAI_CODING_PLAN_API_KEY"),
473-
has_value("github_copilot_key", "GITHUB_COPILOT_API_KEY"),
474497
doc.get("llm")
475498
.and_then(|llm| llm.get("provider"))
476499
.and_then(|provider| provider.get("azure"))
477500
.and_then(|azure| azure.get("base_url"))
478501
.and_then(|base_url| base_url.as_str())
479502
.is_some_and(|url| !url.trim().is_empty()),
503+
copilot_configured,
480504
)
481505
} else {
482506
(
@@ -819,7 +843,7 @@ pub(super) async fn openai_browser_oauth_status(
819843
}
820844

821845
#[utoipa::path(
822-
post,
846+
put,
823847
path = "/providers",
824848
request_body = ProviderUpdateRequest,
825849
responses(
@@ -1607,4 +1631,24 @@ mod tests {
16071631
assert_eq!(provider.base_url, "http://remote-ollama.local:11434");
16081632
assert_eq!(provider.api_key, "");
16091633
}
1634+
1635+
#[test]
1636+
fn build_test_llm_config_registers_github_copilot_provider() {
1637+
let config = build_test_llm_config("github-copilot", "ghp_test_pat_token");
1638+
let provider = config
1639+
.providers
1640+
.get("github-copilot")
1641+
.expect("github-copilot provider should be registered");
1642+
1643+
assert_eq!(
1644+
provider.base_url,
1645+
crate::config::GITHUB_COPILOT_DEFAULT_BASE_URL
1646+
);
1647+
assert_eq!(provider.api_key, "ghp_test_pat_token");
1648+
assert!(provider.use_bearer_auth);
1649+
assert_eq!(
1650+
config.github_copilot_key.as_deref(),
1651+
Some("ghp_test_pat_token")
1652+
);
1653+
}
16101654
}

src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub use permissions::{
1818
DiscordPermissions, MattermostPermissions, SignalPermissions, SlackPermissions,
1919
TelegramPermissions, TwitchPermissions,
2020
};
21+
pub(crate) use providers::GITHUB_COPILOT_DEFAULT_BASE_URL;
2122
pub(crate) use providers::default_provider_config;
2223
pub use runtime::RuntimeConfig;
2324
pub use types::*;

src/config/providers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub(super) const NVIDIA_PROVIDER_BASE_URL: &str = "https://integrate.api.nvidia.
2626
pub(super) const FIREWORKS_PROVIDER_BASE_URL: &str = "https://api.fireworks.ai/inference";
2727
pub(crate) const GEMINI_PROVIDER_BASE_URL: &str =
2828
"https://generativelanguage.googleapis.com/v1beta/openai";
29-
pub(super) const GITHUB_COPILOT_DEFAULT_BASE_URL: &str = "https://api.individual.githubcopilot.com";
29+
pub(crate) const GITHUB_COPILOT_DEFAULT_BASE_URL: &str = "https://api.individual.githubcopilot.com";
3030

3131
/// App attribution headers sent with every OpenRouter API request.
3232
/// See <https://openrouter.ai/docs/app-attribution>.

0 commit comments

Comments
 (0)