Skip auth header for blank-key LLM model discovery
Model discovery now matches the keyless LLM path by omitting Authorization when the stored API key is blank. This keeps Fetch models usable for endpoints that reject empty Bearer credentials while preserving authenticated discovery for configured providers. Constraint: ASR still requires an API key Rejected: send a fake or empty Bearer header | breaks keyless endpoints Confidence: high Scope-risk: narrow Directive: Do not reintroduce auth headers for blank LLM keys without a provider-specific requirement Tested: cargo test --lib fetch_provider_models_omits_authorization_when_api_key_is_empty; rustfmt --edition 2021 --check src/commands.rs Not-tested: full frontend integration flow
This commit is contained in:
1 parent
0fe00ced9a
commit
1d0dcef18c
1 file changed
+60
-13
@@ -225,18 +225,17 @@ async fn fetch_provider_models(config: &ProviderConfig) -> Result<Vec<String>, S
|
||||
.timeout(Duration::from_secs(15))
|
||||
.build()
|
||||
.map_err(|e| format!("HTTP client 初始化失败: {e}"))?;
|
||||
let response = client
|
||||
.get(&url)
|
||||
.header("Authorization", format!("Bearer {}", config.api_key))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.is_timeout() {
|
||||
"请求超时".to_string()
|
||||
} else {
|
||||
format!("网络错误: {e}")
|
||||
}
|
||||
})?;
|
||||
let mut request = client.get(&url);
|
||||
if !config.api_key.trim().is_empty() {
|
||||
request = request.header("Authorization", format!("Bearer {}", config.api_key));
|
||||
}
|
||||
let response = request.send().await.map_err(|e| {
|
||||
if e.is_timeout() {
|
||||
"请求超时".to_string()
|
||||
} else {
|
||||
format!("网络错误: {e}")
|
||||
}
|
||||
})?;
|
||||
let status = response.status();
|
||||
let body = response
|
||||
.text()
|
||||
@@ -558,11 +557,17 @@ fn _ensure_snapshot_used(_: CredentialsSnapshot) {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{models_url, parse_model_ids, persist_settings, SettingsWriter};
|
||||
use super::{
|
||||
fetch_provider_models, models_url, parse_model_ids, persist_settings, ProviderConfig,
|
||||
SettingsWriter,
|
||||
};
|
||||
use crate::types::{
|
||||
HotkeyBinding, HotkeyMode, HotkeyTrigger, QaHotkeyBinding, UserPreferences,
|
||||
};
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpListener;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
|
||||
#[derive(Default)]
|
||||
struct FakeSettingsWriter {
|
||||
@@ -638,4 +643,46 @@ mod tests {
|
||||
assert_eq!(*writer.dictation_refreshes.lock().unwrap(), 1);
|
||||
assert_eq!(*writer.qa_refreshes.lock().unwrap(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fetch_provider_models_omits_authorization_when_api_key_is_empty() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let server = thread::spawn(move || {
|
||||
let (mut stream, _) = listener.accept().unwrap();
|
||||
let mut buf = [0u8; 8192];
|
||||
let mut request = Vec::new();
|
||||
loop {
|
||||
let n = stream.read(&mut buf).unwrap();
|
||||
if n == 0 {
|
||||
break;
|
||||
}
|
||||
request.extend_from_slice(&buf[..n]);
|
||||
if request.windows(4).any(|w| w == b"\r\n\r\n") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let request_text = String::from_utf8_lossy(&request);
|
||||
assert!(!request_text.contains("Authorization: Bearer"));
|
||||
|
||||
let body = r#"{"data":[{"id":"m1"},{"id":"m2"}]}"#;
|
||||
let response = format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
|
||||
body.len(),
|
||||
body
|
||||
);
|
||||
stream.write_all(response.as_bytes()).unwrap();
|
||||
});
|
||||
|
||||
let models = fetch_provider_models(&ProviderConfig {
|
||||
base_url: format!("http://{}", addr),
|
||||
api_key: String::new(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(models, vec!["m1".to_string(), "m2".to_string()]);
|
||||
server.join().unwrap();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user