Compare commits
2 Commits
9d3a092359
...
71d998f68e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71d998f68e | ||
|
|
489d3646f8 |
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::sync::{LazyLock, Mutex};
|
use std::sync::{LazyLock, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -427,7 +428,7 @@ pub fn load_endpoint_api_key(kind: AiEndpointKind) -> EngineResult<Option<String
|
|||||||
|
|
||||||
pub fn refresh_model_catalog(endpoint: &AiEndpointConfig) -> EngineResult<Vec<AiModelInfo>> {
|
pub fn refresh_model_catalog(endpoint: &AiEndpointConfig) -> EngineResult<Vec<AiModelInfo>> {
|
||||||
validate_endpoint_access(endpoint)?;
|
validate_endpoint_access(endpoint)?;
|
||||||
let client = build_http_client()?;
|
let client = build_http_client_for_endpoint(&endpoint.url)?;
|
||||||
let request = client.get(models_url(&endpoint.url));
|
let request = client.get(models_url(&endpoint.url));
|
||||||
let response = with_auth(request, endpoint).send()?.error_for_status()?;
|
let response = with_auth(request, endpoint).send()?.error_for_status()?;
|
||||||
let body: Value = response.json()?;
|
let body: Value = response.json()?;
|
||||||
@@ -494,7 +495,7 @@ pub fn test_chat(endpoint: &AiEndpointConfig, model: &str) -> EngineResult<()> {
|
|||||||
"stream": false,
|
"stream": false,
|
||||||
});
|
});
|
||||||
with_auth(
|
with_auth(
|
||||||
build_http_client()?
|
build_http_client_for_endpoint(&endpoint.url)?
|
||||||
.post(chat_completions_url(&endpoint.url))
|
.post(chat_completions_url(&endpoint.url))
|
||||||
.json(&payload),
|
.json(&payload),
|
||||||
endpoint,
|
endpoint,
|
||||||
@@ -535,7 +536,7 @@ pub fn run_one_shot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let client = build_http_client()?;
|
let client = build_http_client_for_endpoint(&endpoint.url)?;
|
||||||
let response = with_auth(
|
let response = with_auth(
|
||||||
client
|
client
|
||||||
.post(chat_completions_url(&endpoint.url))
|
.post(chat_completions_url(&endpoint.url))
|
||||||
@@ -575,8 +576,28 @@ fn parse_token_usage(body: &Value) -> TokenUsage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_http_client() -> EngineResult<Client> {
|
fn build_http_client_for_endpoint(endpoint_url: &str) -> EngineResult<Client> {
|
||||||
Ok(Client::builder().timeout(Duration::from_secs(5)).build()?)
|
let builder = Client::builder().timeout(Duration::from_secs(5));
|
||||||
|
let builder = if should_bypass_proxy(endpoint_url) {
|
||||||
|
builder.no_proxy()
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
Ok(builder.build()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_bypass_proxy(endpoint_url: &str) -> bool {
|
||||||
|
let Ok(url) = reqwest::Url::parse(endpoint_url) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(host) = url.host_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
host.eq_ignore_ascii_case("localhost")
|
||||||
|
|| host
|
||||||
|
.parse::<IpAddr>()
|
||||||
|
.map(|address| address.is_loopback())
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_endpoint(conn: &Connection, kind: AiEndpointKind) -> EngineResult<StoredAiEndpointConfig> {
|
fn load_endpoint(conn: &Connection, kind: AiEndpointKind) -> EngineResult<StoredAiEndpointConfig> {
|
||||||
@@ -936,7 +957,7 @@ fn store_endpoint_api_key(kind: AiEndpointKind, api_key: &str) -> EngineResult<(
|
|||||||
TEST_API_KEYS
|
TEST_API_KEYS
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|error| EngineError::Validation(error.to_string()))?
|
.map_err(|error| EngineError::Validation(error.to_string()))?
|
||||||
.insert(kind.as_str().to_string(), api_key.to_string());
|
.insert(test_api_key_name(kind), api_key.to_string());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
endpoint_keyring_entry(kind)?
|
endpoint_keyring_entry(kind)?
|
||||||
@@ -950,7 +971,7 @@ fn read_endpoint_api_key(kind: AiEndpointKind) -> EngineResult<Option<String>> {
|
|||||||
.lock()
|
.lock()
|
||||||
.map_err(|error| EngineError::Validation(error.to_string()))
|
.map_err(|error| EngineError::Validation(error.to_string()))
|
||||||
.map(|keys| {
|
.map(|keys| {
|
||||||
keys.get(kind.as_str())
|
keys.get(&test_api_key_name(kind))
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|key| !key.trim().is_empty())
|
.filter(|key| !key.trim().is_empty())
|
||||||
});
|
});
|
||||||
@@ -968,7 +989,7 @@ fn delete_endpoint_api_key(kind: AiEndpointKind) -> EngineResult<()> {
|
|||||||
TEST_API_KEYS
|
TEST_API_KEYS
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|error| EngineError::Validation(error.to_string()))?
|
.map_err(|error| EngineError::Validation(error.to_string()))?
|
||||||
.remove(kind.as_str());
|
.remove(&test_api_key_name(kind));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
match endpoint_keyring_entry(kind)?.delete_credential() {
|
match endpoint_keyring_entry(kind)?.delete_credential() {
|
||||||
@@ -989,6 +1010,10 @@ fn cargo_test_process() -> bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_api_key_name(kind: AiEndpointKind) -> String {
|
||||||
|
format!("{:?}:{}", std::thread::current().id(), kind.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
fn keyring_error(error: keyring::Error) -> EngineError {
|
fn keyring_error(error: keyring::Error) -> EngineError {
|
||||||
EngineError::Validation(error.to_string())
|
EngineError::Validation(error.to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::net::IpAddr;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::{Arc, Mutex, OnceLock, mpsc};
|
use std::sync::{Arc, Mutex, OnceLock, mpsc};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -836,16 +837,39 @@ pub fn build_context(
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn chat_http_client(endpoint_url: &str) -> EngineResult<Client> {
|
||||||
|
let builder = Client::builder()
|
||||||
|
.connect_timeout(Duration::from_secs(5))
|
||||||
|
.timeout(Duration::from_secs(300));
|
||||||
|
let builder = if should_bypass_proxy(endpoint_url) {
|
||||||
|
builder.no_proxy()
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
Ok(builder.build()?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_bypass_proxy(endpoint_url: &str) -> bool {
|
||||||
|
let Ok(url) = reqwest::Url::parse(endpoint_url) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(host) = url.host_str() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
host.eq_ignore_ascii_case("localhost")
|
||||||
|
|| host
|
||||||
|
.parse::<IpAddr>()
|
||||||
|
.map(|address| address.is_loopback())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
fn request_completion(
|
fn request_completion(
|
||||||
endpoint: &AiEndpointConfig,
|
endpoint: &AiEndpointConfig,
|
||||||
payload: &Value,
|
payload: &Value,
|
||||||
cancelled: &AtomicBool,
|
cancelled: &AtomicBool,
|
||||||
mut on_content: impl FnMut(&str),
|
mut on_content: impl FnMut(&str),
|
||||||
) -> EngineResult<AssembledResponse> {
|
) -> EngineResult<AssembledResponse> {
|
||||||
let client = Client::builder()
|
let client = chat_http_client(&endpoint.url)?;
|
||||||
.connect_timeout(Duration::from_secs(5))
|
|
||||||
.timeout(Duration::from_secs(300))
|
|
||||||
.build()?;
|
|
||||||
let mut request = client
|
let mut request = client
|
||||||
.post(chat_completions_url(&endpoint.url))
|
.post(chat_completions_url(&endpoint.url))
|
||||||
.json(payload);
|
.json(payload);
|
||||||
|
|||||||
@@ -664,6 +664,13 @@ mod tests {
|
|||||||
GUARD.get_or_init(|| Mutex::new(()))
|
GUARD.get_or_init(|| Mutex::new(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn preview_client() -> reqwest::blocking::Client {
|
||||||
|
reqwest::blocking::Client::builder()
|
||||||
|
.no_proxy()
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn setup_preview_fixture() -> (tempfile::TempDir, Database) {
|
fn setup_preview_fixture() -> (tempfile::TempDir, Database) {
|
||||||
let dir = tempfile::tempdir().unwrap();
|
let dir = tempfile::tempdir().unwrap();
|
||||||
std::fs::create_dir_all(dir.path().join("meta")).unwrap();
|
std::fs::create_dir_all(dir.path().join("meta")).unwrap();
|
||||||
@@ -1112,7 +1119,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = preview_client();
|
||||||
let mut body = None;
|
let mut body = None;
|
||||||
for _ in 0..20 {
|
for _ in 0..20 {
|
||||||
if let Ok(response) = client
|
if let Ok(response) = client
|
||||||
@@ -1229,7 +1236,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = preview_client();
|
||||||
let response = client
|
let response = client
|
||||||
.get(format!(
|
.get(format!(
|
||||||
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/media/../outside.txt"
|
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/media/../outside.txt"
|
||||||
@@ -1269,7 +1276,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = preview_client();
|
||||||
let media = client
|
let media = client
|
||||||
.get(format!("http://{PREVIEW_HOST}:{PREVIEW_PORT}/media/ok.txt"))
|
.get(format!("http://{PREVIEW_HOST}:{PREVIEW_PORT}/media/ok.txt"))
|
||||||
.send()
|
.send()
|
||||||
@@ -1362,7 +1369,7 @@ mod tests {
|
|||||||
"project-1".into(),
|
"project-1".into(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = preview_client();
|
||||||
let preview_html = client
|
let preview_html = client
|
||||||
.get(format!(
|
.get(format!(
|
||||||
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/__draft/post-1"
|
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/__draft/post-1"
|
||||||
@@ -1427,7 +1434,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = preview_client();
|
||||||
let response = client
|
let response = client
|
||||||
.get(format!(
|
.get(format!(
|
||||||
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/__style-preview?theme=amber&mode=dark"
|
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/__style-preview?theme=amber&mode=dark"
|
||||||
@@ -1487,7 +1494,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = preview_client();
|
||||||
let response = client
|
let response = client
|
||||||
.get(format!(
|
.get(format!(
|
||||||
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/?theme=amber&mode=light"
|
"http://{PREVIEW_HOST}:{PREVIEW_PORT}/?theme=amber&mode=light"
|
||||||
|
|||||||
@@ -671,7 +671,10 @@ fn protocol_routes_every_family_and_http_enforces_loopback_origin_and_cors() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let server = mcp::McpHttpServer::start(fixture.database_path.clone(), 0).unwrap();
|
let server = mcp::McpHttpServer::start(fixture.database_path.clone(), 0).unwrap();
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::builder()
|
||||||
|
.no_proxy()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
let response = client
|
let response = client
|
||||||
.post(server.endpoint())
|
.post(server.endpoint())
|
||||||
.header("accept", "application/json, text/event-stream")
|
.header("accept", "application/json, text/event-stream")
|
||||||
|
|||||||
@@ -123,8 +123,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn desktop_handler_queues_supported_menu_actions() {
|
fn desktop_handler_queues_supported_menu_actions() {
|
||||||
let queued = Arc::new(Mutex::new(Vec::new()));
|
let queued = Arc::new(Mutex::new(Vec::new()));
|
||||||
handler(Arc::clone(&queued), String::new())("trigger_menu_action", &[serde_json::json!("new_post")])
|
handler(Arc::clone(&queued), String::new())(
|
||||||
.unwrap();
|
"trigger_menu_action",
|
||||||
|
&[serde_json::json!("new_post")],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
assert_eq!(queued.lock().unwrap().as_slice(), &[MenuAction::NewPost]);
|
assert_eq!(queued.lock().unwrap().as_slice(), &[MenuAction::NewPost]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user