File size: 3,718 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
use super::super::client_adapter::{ClientAdapter, Protocol, SignatureBufferStrategy, get_user_agent};
use axum::http::{HeaderMap, HeaderValue};

/// Opencode CLI 客户端适配器
/// 
/// Opencode 是一个支持多协议的 AI CLI 工具,支持:
/// - Anthropic
/// - OpenAI
/// - OA-Compatible
/// - Google/Gemini
/// 
/// 该适配器提供以下定制策略:
/// 1. FIFO 签名管理策略(适应多并发工具调用)
/// 2. 标准化 SSE 错误格式(通过客户端的 Zod 类型检查)
/// 3. 自动注入 `context-1m-2025-08-07` beta header
pub struct OpencodeAdapter;

impl ClientAdapter for OpencodeAdapter {
    fn matches(&self, headers: &HeaderMap) -> bool {
        get_user_agent(headers)
            .map(|ua| ua.to_lowercase().contains("opencode"))
            .unwrap_or(false)
    }
    
    fn bypass_signature_matching(&self) -> bool {
        // Opencode 对签名校验较为宽松
        false
    }
    
    fn let_it_crash(&self) -> bool {
        // Opencode 倾向于快速失败,减少不必要的重试
        true
    }
    
    fn signature_buffer_strategy(&self) -> SignatureBufferStrategy {
        // 使用 FIFO 策略以适应多并发工具调用
        SignatureBufferStrategy::Fifo
    }
    
    fn inject_beta_headers(&self, headers: &mut HeaderMap) {
        // 注入 context-1m beta header
        let value = HeaderValue::from_static("context-1m-2025-08-07");
        headers.insert("anthropic-beta", value);
    }
    
    fn supported_protocols(&self) -> Vec<Protocol> {
        vec![
            Protocol::Anthropic,
            Protocol::OpenAI,
            Protocol::OACompatible,
            Protocol::GoogleGemini,
        ]
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_opencode_adapter_matches() {
        let adapter = OpencodeAdapter;
        
        let mut headers = HeaderMap::new();
        headers.insert("user-agent", HeaderValue::from_static("opencode/1.0.0"));
        
        assert!(adapter.matches(&headers));
    }

    #[test]
    fn test_opencode_adapter_case_insensitive() {
        let adapter = OpencodeAdapter;
        
        let mut headers = HeaderMap::new();
        headers.insert("user-agent", HeaderValue::from_static("OpenCode/1.0.0"));
        
        assert!(adapter.matches(&headers));
    }

    #[test]
    fn test_opencode_adapter_no_match() {
        let adapter = OpencodeAdapter;
        
        let mut headers = HeaderMap::new();
        headers.insert("user-agent", HeaderValue::from_static("curl/7.68.0"));
        
        assert!(!adapter.matches(&headers));
    }

    #[test]
    fn test_opencode_adapter_strategies() {
        let adapter = OpencodeAdapter;
        
        assert!(adapter.let_it_crash());
        assert_eq!(adapter.signature_buffer_strategy(), SignatureBufferStrategy::Fifo);
    }

    #[test]
    fn test_opencode_adapter_protocols() {
        let adapter = OpencodeAdapter;
        
        let protocols = adapter.supported_protocols();
        assert_eq!(protocols.len(), 4);
        assert!(protocols.contains(&Protocol::Anthropic));
        assert!(protocols.contains(&Protocol::OpenAI));
        assert!(protocols.contains(&Protocol::OACompatible));
        assert!(protocols.contains(&Protocol::GoogleGemini));
    }

    #[test]
    fn test_opencode_adapter_beta_headers() {
        let adapter = OpencodeAdapter;
        
        let mut headers = HeaderMap::new();
        adapter.inject_beta_headers(&mut headers);
        
        assert!(headers.contains_key("anthropic-beta"));
        assert_eq!(
            headers.get("anthropic-beta").unwrap().to_str().unwrap(),
            "context-1m-2025-08-07"
        );
    }
}