mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-05 20:44:07 +08:00
179 lines
5.6 KiB
Rust
179 lines
5.6 KiB
Rust
use std::{env, fs, net::SocketAddr, path::PathBuf, time::Duration};
|
|
|
|
#[cfg(unix)]
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
use crate::{Error, Result};
|
|
|
|
const DATA_DIR_NAME: &str = ".cursor-byok-v3";
|
|
const DATABASE_FILE_NAME: &str = "cursor-byok.db";
|
|
const V0049_DATA_DIR_NAME: &str = ".cursor-local-assistant-v2";
|
|
const V0049_CONFIG_FILE_NAME: &str = "config.yaml";
|
|
const DEFAULT_PROVIDER_REQUEST_TIMEOUT: Duration = Duration::from_secs(3000);
|
|
|
|
pub fn managed_data_dir() -> Result<PathBuf> {
|
|
let home_dir = dirs::home_dir()
|
|
.ok_or_else(|| Error::Config("cannot resolve user home directory".into()))?;
|
|
let data_dir = home_dir.join(DATA_DIR_NAME);
|
|
fs::create_dir_all(&data_dir)?;
|
|
#[cfg(unix)]
|
|
fs::set_permissions(&data_dir, fs::Permissions::from_mode(0o700))?;
|
|
Ok(data_dir)
|
|
}
|
|
|
|
pub fn v0049_config_path() -> Result<PathBuf> {
|
|
let home_dir = dirs::home_dir()
|
|
.ok_or_else(|| Error::Config("cannot resolve user home directory".into()))?;
|
|
Ok(home_dir
|
|
.join(V0049_DATA_DIR_NAME)
|
|
.join(V0049_CONFIG_FILE_NAME))
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub enum ProviderKind {
|
|
OpenAiChat,
|
|
OpenAiResponses,
|
|
Anthropic,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct ProviderConfig {
|
|
pub kind: ProviderKind,
|
|
pub request_url: String,
|
|
pub api_key: String,
|
|
pub custom_headers: reqwest::header::HeaderMap,
|
|
pub max_output_tokens: Option<u64>,
|
|
pub request_timeout: Duration,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct Config {
|
|
pub listen_addr: SocketAddr,
|
|
pub database_url: String,
|
|
pub provider_request_timeout: Duration,
|
|
pub console: Option<ConsoleSource>,
|
|
pub use_persisted_ports: bool,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum ConsoleSource {
|
|
Directory(PathBuf),
|
|
Proxy(url::Url),
|
|
}
|
|
|
|
impl Config {
|
|
pub fn from_env() -> Result<Self> {
|
|
let listen_addr = env::var("CURSOR_LISTEN_ADDR")
|
|
.unwrap_or_else(|_| "127.0.0.1:3000".into())
|
|
.parse()
|
|
.map_err(|error| Error::Config(format!("invalid CURSOR_LISTEN_ADDR: {error}")))?;
|
|
let request_timeout = match env::var("CURSOR_PROVIDER_TIMEOUT_SECONDS") {
|
|
Ok(value) => Duration::from_secs(value.parse().map_err(|error| {
|
|
Error::Config(format!("invalid CURSOR_PROVIDER_TIMEOUT_SECONDS: {error}"))
|
|
})?),
|
|
Err(env::VarError::NotPresent) => DEFAULT_PROVIDER_REQUEST_TIMEOUT,
|
|
Err(error) => {
|
|
return Err(Error::Config(format!(
|
|
"invalid CURSOR_PROVIDER_TIMEOUT_SECONDS: {error}"
|
|
)))
|
|
}
|
|
};
|
|
let console_dir = env::var_os("CURSOR_CONSOLE_DIR").map(PathBuf::from);
|
|
let console_proxy = env::var("CURSOR_CONSOLE_PROXY")
|
|
.ok()
|
|
.map(|value| {
|
|
value.parse().map_err(|error| {
|
|
Error::Config(format!("invalid CURSOR_CONSOLE_PROXY: {error}"))
|
|
})
|
|
})
|
|
.transpose()?;
|
|
let console = match (console_dir, console_proxy) {
|
|
(Some(_), Some(_)) => {
|
|
return Err(Error::Config(
|
|
"CURSOR_CONSOLE_DIR and CURSOR_CONSOLE_PROXY cannot both be set".into(),
|
|
))
|
|
}
|
|
(Some(directory), None) => Some(ConsoleSource::Directory(directory)),
|
|
(None, Some(proxy)) => Some(ConsoleSource::Proxy(proxy)),
|
|
(None, None) => None,
|
|
};
|
|
Ok(Self {
|
|
listen_addr,
|
|
database_url: database_url_from_env()?,
|
|
provider_request_timeout: request_timeout,
|
|
console,
|
|
use_persisted_ports: false,
|
|
})
|
|
}
|
|
|
|
pub fn desktop() -> Result<Self> {
|
|
Ok(Self {
|
|
listen_addr: "127.0.0.1:0"
|
|
.parse()
|
|
.expect("desktop listen address is static"),
|
|
database_url: default_database_url()?,
|
|
provider_request_timeout: DEFAULT_PROVIDER_REQUEST_TIMEOUT,
|
|
console: None,
|
|
use_persisted_ports: true,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn database_url_from_env() -> Result<String> {
|
|
match env::var("CURSOR_DATABASE_URL") {
|
|
Ok(database_url) => Ok(database_url),
|
|
Err(env::VarError::NotPresent) => default_database_url(),
|
|
Err(error) => Err(Error::Config(format!(
|
|
"invalid CURSOR_DATABASE_URL: {error}"
|
|
))),
|
|
}
|
|
}
|
|
|
|
fn default_database_url() -> Result<String> {
|
|
let data_dir = managed_data_dir()?;
|
|
database_url_for_dir(&data_dir)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
fn database_url_in(home_dir: &std::path::Path) -> Result<String> {
|
|
let data_dir = home_dir.join(DATA_DIR_NAME);
|
|
fs::create_dir_all(&data_dir)?;
|
|
|
|
#[cfg(unix)]
|
|
fs::set_permissions(&data_dir, fs::Permissions::from_mode(0o700))?;
|
|
|
|
database_url_for_dir(&data_dir)
|
|
}
|
|
|
|
fn database_url_for_dir(data_dir: &std::path::Path) -> Result<String> {
|
|
let database_path = data_dir.join(DATABASE_FILE_NAME);
|
|
let database_path = database_path
|
|
.to_str()
|
|
.ok_or_else(|| Error::Config("database path is not valid UTF-8".into()))?;
|
|
Ok(format!("sqlite://{database_path}"))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn managed_database_supports_home_paths_with_spaces() {
|
|
let directory = tempfile::tempdir().unwrap();
|
|
let home_dir = directory.path().join("home with spaces");
|
|
let database_url = database_url_in(&home_dir).unwrap();
|
|
|
|
let store = crate::store::Store::connect(&database_url).await.unwrap();
|
|
drop(store);
|
|
|
|
let data_dir = home_dir.join(DATA_DIR_NAME);
|
|
assert!(data_dir.join(DATABASE_FILE_NAME).is_file());
|
|
|
|
#[cfg(unix)]
|
|
assert_eq!(
|
|
fs::metadata(data_dir).unwrap().permissions().mode() & 0o777,
|
|
0o700
|
|
);
|
|
}
|
|
}
|