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()));
    }
}