Files
skill-lib/src/config/settings.rs
木炎 6aad2ce48e feat: restore zhihu browser skills
Reconnect the recovered Zhihu skill flows to the live browser runtime and resolve their resources relative to the executable so they work outside the repo root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 14:29:38 +08:00

121 lines
3.9 KiB
Rust

use std::path::{Path, PathBuf};
use serde::Deserialize;
use thiserror::Error;
const DEFAULT_DEEPSEEK_BASE_URL: &str = "https://api.deepseek.com";
const DEFAULT_DEEPSEEK_MODEL: &str = "deepseek-chat";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeepSeekSettings {
pub api_key: String,
pub base_url: String,
pub model: String,
}
impl DeepSeekSettings {
pub fn from_env() -> Result<Self, ConfigError> {
Self::maybe_from_env()?.ok_or(ConfigError::MissingEnv("DEEPSEEK_API_KEY"))
}
pub fn load(config_path: Option<&Path>) -> Result<Option<Self>, ConfigError> {
if let Some(path) = config_path {
if path.exists() {
return Self::from_config_path(path).map(Some);
}
}
Self::maybe_from_env()
}
fn maybe_from_env() -> Result<Option<Self>, ConfigError> {
let api_key = match std::env::var("DEEPSEEK_API_KEY") {
Ok(value) => value,
Err(std::env::VarError::NotPresent) => return Ok(None),
Err(std::env::VarError::NotUnicode(_)) => {
return Err(ConfigError::InvalidEnv("DEEPSEEK_API_KEY"))
}
};
let base_url = std::env::var("DEEPSEEK_BASE_URL")
.unwrap_or_else(|_| DEFAULT_DEEPSEEK_BASE_URL.to_string());
let model =
std::env::var("DEEPSEEK_MODEL").unwrap_or_else(|_| DEFAULT_DEEPSEEK_MODEL.to_string());
Ok(Some(Self::new(api_key, base_url, model)?))
}
fn from_config_path(path: &Path) -> Result<Self, ConfigError> {
let raw = std::fs::read_to_string(path)
.map_err(|err| ConfigError::ConfigRead(path.to_path_buf(), err.to_string()))?;
let config: RawDeepSeekSettings = serde_json::from_str(&raw)
.map_err(|err| ConfigError::ConfigParse(path.to_path_buf(), err.to_string()))?;
Self::new(config.api_key, config.base_url, config.model).map_err(|err| err.with_path(path))
}
fn new(api_key: String, base_url: String, model: String) -> Result<Self, ConfigError> {
let api_key = api_key.trim().to_string();
let base_url = if base_url.trim().is_empty() {
DEFAULT_DEEPSEEK_BASE_URL.to_string()
} else {
base_url.trim().to_string()
};
let model = if model.trim().is_empty() {
DEFAULT_DEEPSEEK_MODEL.to_string()
} else {
model.trim().to_string()
};
if api_key.is_empty() {
return Err(ConfigError::EmptyValue("DEEPSEEK_API_KEY"));
}
if base_url.is_empty() {
return Err(ConfigError::EmptyValue("DEEPSEEK_BASE_URL"));
}
if model.is_empty() {
return Err(ConfigError::EmptyValue("DEEPSEEK_MODEL"));
}
Ok(Self {
api_key,
base_url,
model,
})
}
}
#[derive(Debug, Deserialize)]
struct RawDeepSeekSettings {
#[serde(rename = "apiKey", default)]
api_key: String,
#[serde(rename = "baseUrl", default)]
base_url: String,
#[serde(default)]
model: String,
}
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum ConfigError {
#[error("missing environment variable: {0}")]
MissingEnv(&'static str),
#[error("environment variable must not be empty: {0}")]
EmptyValue(&'static str),
#[error("invalid non-utf8 environment variable: {0}")]
InvalidEnv(&'static str),
#[error("failed to read DeepSeek config file {0}: {1}")]
ConfigRead(PathBuf, String),
#[error("invalid DeepSeek config JSON in {0}: {1}")]
ConfigParse(PathBuf, String),
#[error("DeepSeek config value must not be empty: {0} ({1})")]
ConfigValueEmpty(&'static str, PathBuf),
}
impl ConfigError {
fn with_path(self, path: &Path) -> Self {
match self {
Self::EmptyValue(field) => Self::ConfigValueEmpty(field, path.to_path_buf()),
other => other,
}
}
}