将 claw-code/rust/crates 的完整实现合并到主 workspace,涵盖
9 个 crate 的更新与 2 个新 crate 的引入。
API 层:
- 用原生 Anthropic 客户端(anthropic.rs)替换 claw_provider,
新增 prompt cache 减少重复请求开销
- 新增 HTTP 客户端构建器统一代理配置,OpenAI 兼容端增加
DashScope/Qwen 支持与抖动重试
- MessageRequest 扩展 temperature/top_p 等模型调参字段
- SSE 解析器增加 provider 上下文感知的错误信息
运行时(~11,000 行新增):
- 新增 bash 命令安全校验、分支锁碰撞检测、配置文件校验
- 新增会话存储与控制面、MCP 生命周期状态机与服务端实现
- 新增权限执行引擎、策略引擎、插件生命周期管理
- 新增 worker 启动编排、任务/定时任务注册表、信任解析器
- 保留 Windows cmd /C fallback
命令/插件/工具:
- commands 大幅重写,扩展 sandbox、doctor、plan 等 slash 命令
- plugins 新增 PostToolUseFailure hook 与宽容加载机制
- tools 新增 PDF 提取与 lane 补全工具
新增 crate:mock-anthropic-service(测试)、telemetry(遥测)
适配 claw-cli/server:ClawApiClient→AnthropicClient 重命名,
SlashCommand::parse 返回 Result,移除 session 级 Thinking 变体,
TokenUsage/ConversationMessage 补充序列化支持
174 lines
5.4 KiB
Rust
174 lines
5.4 KiB
Rust
use std::ffi::OsString;
|
|
use std::sync::{Mutex, OnceLock};
|
|
|
|
use api::{build_http_client_with, ProxyConfig};
|
|
|
|
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
|
|
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
|
LOCK.get_or_init(|| Mutex::new(()))
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
}
|
|
|
|
struct EnvVarGuard {
|
|
key: &'static str,
|
|
original: Option<OsString>,
|
|
}
|
|
|
|
impl EnvVarGuard {
|
|
fn set(key: &'static str, value: Option<&str>) -> Self {
|
|
let original = std::env::var_os(key);
|
|
match value {
|
|
Some(value) => std::env::set_var(key, value),
|
|
None => std::env::remove_var(key),
|
|
}
|
|
Self { key, original }
|
|
}
|
|
}
|
|
|
|
impl Drop for EnvVarGuard {
|
|
fn drop(&mut self) {
|
|
match &self.original {
|
|
Some(value) => std::env::set_var(self.key, value),
|
|
None => std::env::remove_var(self.key),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_config_from_env_reads_uppercase_proxy_vars() {
|
|
// given
|
|
let _lock = env_lock();
|
|
let _http = EnvVarGuard::set("HTTP_PROXY", Some("http://proxy.corp:3128"));
|
|
let _https = EnvVarGuard::set("HTTPS_PROXY", Some("http://secure.corp:3129"));
|
|
let _no = EnvVarGuard::set("NO_PROXY", Some("localhost,127.0.0.1"));
|
|
let _http_lower = EnvVarGuard::set("http_proxy", None);
|
|
let _https_lower = EnvVarGuard::set("https_proxy", None);
|
|
let _no_lower = EnvVarGuard::set("no_proxy", None);
|
|
|
|
// when
|
|
let config = ProxyConfig::from_env();
|
|
|
|
// then
|
|
assert_eq!(config.http_proxy.as_deref(), Some("http://proxy.corp:3128"));
|
|
assert_eq!(
|
|
config.https_proxy.as_deref(),
|
|
Some("http://secure.corp:3129")
|
|
);
|
|
assert_eq!(config.no_proxy.as_deref(), Some("localhost,127.0.0.1"));
|
|
assert!(config.proxy_url.is_none());
|
|
assert!(!config.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_config_from_env_reads_lowercase_proxy_vars() {
|
|
// given
|
|
let _lock = env_lock();
|
|
let _http = EnvVarGuard::set("HTTP_PROXY", None);
|
|
let _https = EnvVarGuard::set("HTTPS_PROXY", None);
|
|
let _no = EnvVarGuard::set("NO_PROXY", None);
|
|
let _http_lower = EnvVarGuard::set("http_proxy", Some("http://lower.corp:3128"));
|
|
let _https_lower = EnvVarGuard::set("https_proxy", Some("http://lower-secure.corp:3129"));
|
|
let _no_lower = EnvVarGuard::set("no_proxy", Some(".internal"));
|
|
|
|
// when
|
|
let config = ProxyConfig::from_env();
|
|
|
|
// then
|
|
assert_eq!(config.http_proxy.as_deref(), Some("http://lower.corp:3128"));
|
|
assert_eq!(
|
|
config.https_proxy.as_deref(),
|
|
Some("http://lower-secure.corp:3129")
|
|
);
|
|
assert_eq!(config.no_proxy.as_deref(), Some(".internal"));
|
|
assert!(!config.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_config_from_env_is_empty_when_no_vars_set() {
|
|
// given
|
|
let _lock = env_lock();
|
|
let _http = EnvVarGuard::set("HTTP_PROXY", None);
|
|
let _https = EnvVarGuard::set("HTTPS_PROXY", None);
|
|
let _no = EnvVarGuard::set("NO_PROXY", None);
|
|
let _http_lower = EnvVarGuard::set("http_proxy", None);
|
|
let _https_lower = EnvVarGuard::set("https_proxy", None);
|
|
let _no_lower = EnvVarGuard::set("no_proxy", None);
|
|
|
|
// when
|
|
let config = ProxyConfig::from_env();
|
|
|
|
// then
|
|
assert!(config.is_empty());
|
|
assert!(config.http_proxy.is_none());
|
|
assert!(config.https_proxy.is_none());
|
|
assert!(config.no_proxy.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_config_from_env_treats_empty_values_as_unset() {
|
|
// given
|
|
let _lock = env_lock();
|
|
let _http = EnvVarGuard::set("HTTP_PROXY", Some(""));
|
|
let _https = EnvVarGuard::set("HTTPS_PROXY", Some(""));
|
|
let _http_lower = EnvVarGuard::set("http_proxy", Some(""));
|
|
let _https_lower = EnvVarGuard::set("https_proxy", Some(""));
|
|
let _no = EnvVarGuard::set("NO_PROXY", Some(""));
|
|
let _no_lower = EnvVarGuard::set("no_proxy", Some(""));
|
|
|
|
// when
|
|
let config = ProxyConfig::from_env();
|
|
|
|
// then
|
|
assert!(config.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn build_client_with_env_proxy_config_succeeds() {
|
|
// given
|
|
let _lock = env_lock();
|
|
let _http = EnvVarGuard::set("HTTP_PROXY", Some("http://proxy.corp:3128"));
|
|
let _https = EnvVarGuard::set("HTTPS_PROXY", Some("http://secure.corp:3129"));
|
|
let _no = EnvVarGuard::set("NO_PROXY", Some("localhost"));
|
|
let _http_lower = EnvVarGuard::set("http_proxy", None);
|
|
let _https_lower = EnvVarGuard::set("https_proxy", None);
|
|
let _no_lower = EnvVarGuard::set("no_proxy", None);
|
|
let config = ProxyConfig::from_env();
|
|
|
|
// when
|
|
let result = build_http_client_with(&config);
|
|
|
|
// then
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn build_client_with_proxy_url_config_succeeds() {
|
|
// given
|
|
let config = ProxyConfig::from_proxy_url("http://unified.corp:3128");
|
|
|
|
// when
|
|
let result = build_http_client_with(&config);
|
|
|
|
// then
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn proxy_config_from_env_prefers_uppercase_over_lowercase() {
|
|
// given
|
|
let _lock = env_lock();
|
|
let _http_upper = EnvVarGuard::set("HTTP_PROXY", Some("http://upper.corp:3128"));
|
|
let _http_lower = EnvVarGuard::set("http_proxy", Some("http://lower.corp:3128"));
|
|
let _https = EnvVarGuard::set("HTTPS_PROXY", None);
|
|
let _https_lower = EnvVarGuard::set("https_proxy", None);
|
|
let _no = EnvVarGuard::set("NO_PROXY", None);
|
|
let _no_lower = EnvVarGuard::set("no_proxy", None);
|
|
|
|
// when
|
|
let config = ProxyConfig::from_env();
|
|
|
|
// then
|
|
assert_eq!(config.http_proxy.as_deref(), Some("http://upper.corp:3128"));
|
|
}
|