mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 21:30:17 +08:00
8217bfff50
* feat: add Bedrock request optimizer (PRE-SEND thinking + cache injection) Add a PRE-SEND request optimizer that enhances Bedrock API requests before forwarding, complementing the existing POST-ERROR rectifier system. New modules: - thinking_optimizer: 3-path model detection (adaptive/legacy/skip) - Opus 4.6/Sonnet 4.6: adaptive thinking + effort max + 1M context beta - Legacy models: inject extended thinking with max budget - Haiku: skip (no modification) - cache_injector: auto-inject cache_control breakpoints (max 4) - Injects at tools/system/assistant message positions - TTL upgrade for existing breakpoints (5m → 1h) Gate: only activates for Bedrock providers (CLAUDE_CODE_USE_BEDROCK=1) Config: stored in SQLite settings table, default OFF, user opt-in UI: new Optimizer section in RectifierConfigPanel with 3 toggles + TTL 18 unit tests covering all paths. Verified against live Bedrock API. * chore: remove docs/plans directory * fix: address code review findings for Bedrock request optimizer P0 fixes: - Replace hardcoded Chinese with i18n t() calls in optimizer panel, add translation keys to zh/en/ja locale files - Fix u64 underflow: max_tokens - 1 → max_tokens.saturating_sub(1) - Move optimizer from before retry loop to per-provider with body cloning, preventing Bedrock fields leaking to non-Bedrock providers P1 fixes: - Replace .map() side-effect pattern with idiomatic if-let (clippy) - Fix module alphabetical ordering in mod.rs - Add cache_ttl whitelist validation in set_optimizer_config - Remove #[allow(unused_assignments)] and dead budget decrement --------- Co-authored-by: Keith (via OpenClaw) <keithyt06@users.noreply.github.com> Co-authored-by: Jason <farion1231@gmail.com>
204 lines
6.1 KiB
Rust
204 lines
6.1 KiB
Rust
#![allow(non_snake_case)]
|
|
|
|
use tauri::AppHandle;
|
|
|
|
fn merge_settings_for_save(
|
|
mut incoming: crate::settings::AppSettings,
|
|
existing: &crate::settings::AppSettings,
|
|
) -> crate::settings::AppSettings {
|
|
if incoming.webdav_sync.is_none() {
|
|
incoming.webdav_sync = existing.webdav_sync.clone();
|
|
}
|
|
incoming
|
|
}
|
|
|
|
/// 获取设置
|
|
#[tauri::command]
|
|
pub async fn get_settings() -> Result<crate::settings::AppSettings, String> {
|
|
Ok(crate::settings::get_settings_for_frontend())
|
|
}
|
|
|
|
/// 保存设置
|
|
#[tauri::command]
|
|
pub async fn save_settings(settings: crate::settings::AppSettings) -> Result<bool, String> {
|
|
let existing = crate::settings::get_settings();
|
|
let merged = merge_settings_for_save(settings, &existing);
|
|
crate::settings::update_settings(merged).map_err(|e| e.to_string())?;
|
|
Ok(true)
|
|
}
|
|
|
|
/// 重启应用程序(当 app_config_dir 变更后使用)
|
|
#[tauri::command]
|
|
pub async fn restart_app(app: AppHandle) -> Result<bool, String> {
|
|
// 在后台延迟重启,让函数有时间返回响应
|
|
tauri::async_runtime::spawn(async move {
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
app.restart();
|
|
});
|
|
Ok(true)
|
|
}
|
|
|
|
/// 获取 app_config_dir 覆盖配置 (从 Store)
|
|
#[tauri::command]
|
|
pub async fn get_app_config_dir_override(app: AppHandle) -> Result<Option<String>, String> {
|
|
Ok(crate::app_store::refresh_app_config_dir_override(&app)
|
|
.map(|p| p.to_string_lossy().to_string()))
|
|
}
|
|
|
|
/// 设置 app_config_dir 覆盖配置 (到 Store)
|
|
#[tauri::command]
|
|
pub async fn set_app_config_dir_override(
|
|
app: AppHandle,
|
|
path: Option<String>,
|
|
) -> Result<bool, String> {
|
|
crate::app_store::set_app_config_dir_to_store(&app, path.as_deref())?;
|
|
Ok(true)
|
|
}
|
|
|
|
/// 设置开机自启
|
|
#[tauri::command]
|
|
pub async fn set_auto_launch(enabled: bool) -> Result<bool, String> {
|
|
if enabled {
|
|
crate::auto_launch::enable_auto_launch().map_err(|e| format!("启用开机自启失败: {e}"))?;
|
|
} else {
|
|
crate::auto_launch::disable_auto_launch().map_err(|e| format!("禁用开机自启失败: {e}"))?;
|
|
}
|
|
Ok(true)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::merge_settings_for_save;
|
|
use crate::settings::{AppSettings, WebDavSyncSettings};
|
|
|
|
#[test]
|
|
fn save_settings_should_preserve_existing_webdav_when_payload_omits_it() {
|
|
let mut existing = AppSettings::default();
|
|
existing.webdav_sync = Some(WebDavSyncSettings {
|
|
base_url: "https://dav.example.com".to_string(),
|
|
username: "alice".to_string(),
|
|
password: "secret".to_string(),
|
|
..WebDavSyncSettings::default()
|
|
});
|
|
|
|
let incoming = AppSettings::default();
|
|
let merged = merge_settings_for_save(incoming, &existing);
|
|
|
|
assert!(merged.webdav_sync.is_some());
|
|
assert_eq!(
|
|
merged.webdav_sync.as_ref().map(|v| v.base_url.as_str()),
|
|
Some("https://dav.example.com")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn save_settings_should_keep_incoming_webdav_when_present() {
|
|
let mut existing = AppSettings::default();
|
|
existing.webdav_sync = Some(WebDavSyncSettings {
|
|
base_url: "https://dav.old.example.com".to_string(),
|
|
username: "old".to_string(),
|
|
password: "old-pass".to_string(),
|
|
..WebDavSyncSettings::default()
|
|
});
|
|
|
|
let mut incoming = AppSettings::default();
|
|
incoming.webdav_sync = Some(WebDavSyncSettings {
|
|
base_url: "https://dav.new.example.com".to_string(),
|
|
username: "new".to_string(),
|
|
password: "new-pass".to_string(),
|
|
..WebDavSyncSettings::default()
|
|
});
|
|
|
|
let merged = merge_settings_for_save(incoming, &existing);
|
|
|
|
assert_eq!(
|
|
merged.webdav_sync.as_ref().map(|v| v.base_url.as_str()),
|
|
Some("https://dav.new.example.com")
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 获取开机自启状态
|
|
#[tauri::command]
|
|
pub async fn get_auto_launch_status() -> Result<bool, String> {
|
|
crate::auto_launch::is_auto_launch_enabled().map_err(|e| format!("获取开机自启状态失败: {e}"))
|
|
}
|
|
|
|
/// 获取整流器配置
|
|
#[tauri::command]
|
|
pub async fn get_rectifier_config(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> Result<crate::proxy::types::RectifierConfig, String> {
|
|
state.db.get_rectifier_config().map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 设置整流器配置
|
|
#[tauri::command]
|
|
pub async fn set_rectifier_config(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
config: crate::proxy::types::RectifierConfig,
|
|
) -> Result<bool, String> {
|
|
state
|
|
.db
|
|
.set_rectifier_config(&config)
|
|
.map_err(|e| e.to_string())?;
|
|
Ok(true)
|
|
}
|
|
|
|
/// 获取优化器配置
|
|
#[tauri::command]
|
|
pub async fn get_optimizer_config(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> Result<crate::proxy::types::OptimizerConfig, String> {
|
|
state.db.get_optimizer_config().map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 设置优化器配置
|
|
#[tauri::command]
|
|
pub async fn set_optimizer_config(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
config: crate::proxy::types::OptimizerConfig,
|
|
) -> Result<bool, String> {
|
|
// Validate cache_ttl: only allow known values
|
|
match config.cache_ttl.as_str() {
|
|
"5m" | "1h" => {}
|
|
other => {
|
|
return Err(format!(
|
|
"Invalid cache_ttl value: '{other}'. Allowed values: '5m', '1h'"
|
|
))
|
|
}
|
|
}
|
|
state
|
|
.db
|
|
.set_optimizer_config(&config)
|
|
.map_err(|e| e.to_string())?;
|
|
Ok(true)
|
|
}
|
|
|
|
/// 获取日志配置
|
|
#[tauri::command]
|
|
pub async fn get_log_config(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> Result<crate::proxy::types::LogConfig, String> {
|
|
state.db.get_log_config().map_err(|e| e.to_string())
|
|
}
|
|
|
|
/// 设置日志配置
|
|
#[tauri::command]
|
|
pub async fn set_log_config(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
config: crate::proxy::types::LogConfig,
|
|
) -> Result<bool, String> {
|
|
state
|
|
.db
|
|
.set_log_config(&config)
|
|
.map_err(|e| e.to_string())?;
|
|
log::set_max_level(config.to_level_filter());
|
|
log::info!(
|
|
"日志配置已更新: enabled={}, level={}",
|
|
config.enabled,
|
|
config.level
|
|
);
|
|
Ok(true)
|
|
}
|