File size: 4,453 Bytes
a21c316 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | use axum::http::HeaderMap;
use once_cell::sync::Lazy;
use std::sync::Arc; // [NEW] Import Arc
use super::client_adapters::OpencodeAdapter;
/// 客户端适配器 trait
///
/// 为不同的客户端(如 opencode、Cherry Studio)提供定制化的协议处理策略。
/// 每个客户端可以实现自己的适配器来处理特定的需求。
///
/// # 设计原则
/// 1. **完全隔离**:适配器作为可选的增强层,不修改现有协议核心逻辑
/// 2. **向后兼容**:未匹配到适配器的请求完全按照现有流程处理
/// 3. **单文件修改**:客户端特定逻辑封装在各自的适配器文件中
pub trait ClientAdapter: Send + Sync {
/// 判断该适配器是否匹配给定的请求
///
/// # Arguments
/// * `headers` - 请求头,通常通过 User-Agent 等字段识别客户端
///
/// # Returns
/// 如果匹配返回 true,否则返回 false
fn matches(&self, headers: &HeaderMap) -> bool;
/// 是否绕过签名校验
///
/// 某些客户端可能不需要严格的 thinking 签名匹配
#[allow(dead_code)]
fn bypass_signature_matching(&self) -> bool {
false
}
/// 是否采用 "let it crash" 哲学
///
/// 减少不必要的重试和恢复逻辑,让错误快速暴露
fn let_it_crash(&self) -> bool {
false
}
/// 签名缓存策略
///
/// 不同客户端可能需要不同的签名管理方式(FIFO/LIFO)
fn signature_buffer_strategy(&self) -> SignatureBufferStrategy {
SignatureBufferStrategy::Default
}
/// 注入客户端缺少的 Beta Header
///
/// 某些客户端可能需要特定的 Beta Header 才能正常工作
fn inject_beta_headers(&self, _headers: &mut HeaderMap) {
// 默认不注入
}
/// 声明支持的协议
///
/// 用于多协议客户端(如 opencode)
#[allow(dead_code)]
fn supported_protocols(&self) -> Vec<Protocol> {
vec![Protocol::Anthropic] // 默认只支持 Anthropic
}
}
/// 签名缓存策略
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignatureBufferStrategy {
/// 默认策略(当前实现)
Default,
/// FIFO(先进先出)- 适用于多并发工具调用
Fifo,
/// LIFO(后进先出)- 适用于嵌套调用
#[allow(dead_code)]
Lifo,
}
/// 支持的协议类型
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(dead_code)]
pub enum Protocol {
Anthropic,
OpenAI,
OACompatible,
GoogleGemini,
}
/// 全局客户端适配器注册表
///
/// 所有注册的适配器都会在请求处理时被检查
pub static CLIENT_ADAPTERS: Lazy<Vec<Arc<dyn ClientAdapter>>> = Lazy::new(|| {
vec![
Arc::new(OpencodeAdapter),
// 未来可以轻松添加更多适配器:
// Arc::new(CherryStudioAdapter),
]
});
/// 辅助函数:从 HeaderMap 中提取 User-Agent
pub fn get_user_agent(headers: &HeaderMap) -> Option<String> {
headers
.get("user-agent")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use axum::http::HeaderValue;
struct TestAdapter;
impl ClientAdapter for TestAdapter {
fn matches(&self, headers: &HeaderMap) -> bool {
get_user_agent(headers)
.map(|ua| ua.contains("test-client"))
.unwrap_or(false)
}
fn bypass_signature_matching(&self) -> bool {
true
}
}
#[test]
fn test_adapter_matches() {
let adapter = TestAdapter;
let mut headers = HeaderMap::new();
headers.insert("user-agent", HeaderValue::from_static("test-client/1.0"));
assert!(adapter.matches(&headers));
assert!(adapter.bypass_signature_matching());
}
#[test]
fn test_adapter_no_match() {
let adapter = TestAdapter;
let mut headers = HeaderMap::new();
headers.insert("user-agent", HeaderValue::from_static("other-client/1.0"));
assert!(!adapter.matches(&headers));
}
#[test]
fn test_get_user_agent() {
let mut headers = HeaderMap::new();
headers.insert("user-agent", HeaderValue::from_static("opencode/1.0"));
assert_eq!(get_user_agent(&headers), Some("opencode/1.0".to_string()));
}
}
|