Skip to content

Commit 0904a72

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 66846e5 commit 0904a72

File tree

4 files changed

+49
-4
lines changed

4 files changed

+49
-4
lines changed

interface/src/api/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1596,7 +1596,7 @@ export const api = {
15961596
return response.json() as Promise<Types.ProviderUpdateResponse>;
15971597
},
15981598
testProviderModel: async (provider: string, apiKey: string, model: string) => {
1599-
const response = await fetch(`${getApiBase()}/providers/test`, {
1599+
const response = await fetch(`${getApiBase()}/providers/test-model`, {
16001600
method: "POST",
16011601
headers: { "Content-Type": "application/json" },
16021602
body: JSON.stringify({ provider, api_key: apiKey, model }),

src/api/providers.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,21 @@ fn build_test_llm_config(provider: &str, credential: &str) -> crate::config::Llm
192192
let mut providers = HashMap::new();
193193
if let Some(provider_config) = crate::config::default_provider_config(provider, credential) {
194194
providers.insert(provider.to_string(), provider_config);
195+
} else if provider == "github-copilot" {
196+
// GitHub Copilot uses token exchange, so default_provider_config returns None.
197+
// For testing, add a provider entry with the PAT as the api_key —
198+
// LlmManager::get_copilot_token() will exchange it for a real Copilot token.
199+
providers.insert(
200+
provider.to_string(),
201+
crate::config::ProviderConfig {
202+
api_type: crate::config::ApiType::OpenAiChatCompletions,
203+
base_url: crate::config::GITHUB_COPILOT_DEFAULT_BASE_URL.to_string(),
204+
api_key: credential.to_string(),
205+
name: Some("GitHub Copilot".to_string()),
206+
use_bearer_auth: true,
207+
extra_headers: vec![],
208+
},
209+
);
195210
}
196211

197212
crate::config::LlmConfig {
@@ -431,6 +446,15 @@ pub(super) async fn get_providers(
431446
env_set(env_var)
432447
};
433448

449+
// Copilot: only check TOML key, not env var. The env var GITHUB_COPILOT_API_KEY
450+
// is a fallback for config loading but shouldn't keep the provider visible in
451+
// the settings UI after a remove — the env var can't be unset from the process.
452+
let copilot_configured = doc
453+
.get("llm")
454+
.and_then(|llm| llm.get("github_copilot_key"))
455+
.and_then(|val| val.as_str())
456+
.is_some_and(|s| resolve_value(s).is_some());
457+
434458
(
435459
has_value("anthropic_key", "ANTHROPIC_API_KEY"),
436460
has_value("openai_key", "OPENAI_API_KEY"),
@@ -454,7 +478,7 @@ pub(super) async fn get_providers(
454478
has_value("minimax_cn_key", "MINIMAX_CN_API_KEY"),
455479
has_value("moonshot_key", "MOONSHOT_API_KEY"),
456480
has_value("zai_coding_plan_key", "ZAI_CODING_PLAN_API_KEY"),
457-
has_value("github_copilot_key", "GITHUB_COPILOT_API_KEY"),
481+
copilot_configured,
458482
)
459483
} else {
460484
(
@@ -794,7 +818,7 @@ pub(super) async fn openai_browser_oauth_status(
794818
}
795819

796820
#[utoipa::path(
797-
post,
821+
put,
798822
path = "/providers",
799823
request_body = ProviderUpdateRequest,
800824
responses(
@@ -1083,4 +1107,24 @@ mod tests {
10831107
assert_eq!(provider.base_url, "http://remote-ollama.local:11434");
10841108
assert_eq!(provider.api_key, "");
10851109
}
1110+
1111+
#[test]
1112+
fn build_test_llm_config_registers_github_copilot_provider() {
1113+
let config = build_test_llm_config("github-copilot", "ghp_test_pat_token");
1114+
let provider = config
1115+
.providers
1116+
.get("github-copilot")
1117+
.expect("github-copilot provider should be registered");
1118+
1119+
assert_eq!(
1120+
provider.base_url,
1121+
crate::config::GITHUB_COPILOT_DEFAULT_BASE_URL
1122+
);
1123+
assert_eq!(provider.api_key, "ghp_test_pat_token");
1124+
assert!(provider.use_bearer_auth);
1125+
assert_eq!(
1126+
config.github_copilot_key.as_deref(),
1127+
Some("ghp_test_pat_token")
1128+
);
1129+
}
10861130
}

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)