1use std::sync::OnceLock;
11
12use serde::{Deserialize, Serialize};
13
14use crate::ProviderKind;
15
16#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
24#[serde(rename_all = "kebab-case")]
25pub enum HarnessPostureKind {
26 #[default]
29 Standard,
30 CacheHeavy,
32 Lean,
35 Custom,
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
41#[serde(rename_all = "kebab-case")]
42pub enum HarnessCompactionStrategy {
43 #[default]
44 Default,
45 PrefixCache,
46 Aggressive,
47}
48
49#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
51#[serde(rename_all = "kebab-case")]
52pub enum HarnessToolSurface {
53 #[default]
54 Full,
55 ReadOnly,
56 Auto,
57}
58
59#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
61#[serde(rename_all = "kebab-case")]
62pub enum HarnessSafetyPosture {
63 #[default]
64 Standard,
65 Strict,
66 Permissive,
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
71#[serde(deny_unknown_fields)]
72pub struct HarnessPosture {
73 #[serde(default)]
75 pub kind: HarnessPostureKind,
76 #[serde(default)]
78 pub max_subagents: usize,
79 #[serde(default)]
81 pub prefer_codebase_search: bool,
82 #[serde(default)]
84 pub compaction_strategy: HarnessCompactionStrategy,
85 #[serde(default)]
87 pub tool_surface: HarnessToolSurface,
88 #[serde(default)]
90 pub safety_posture: HarnessSafetyPosture,
91}
92
93impl Default for HarnessPosture {
94 fn default() -> Self {
95 Self {
96 kind: HarnessPostureKind::Standard,
97 max_subagents: 0,
98 prefer_codebase_search: false,
99 compaction_strategy: HarnessCompactionStrategy::default(),
100 tool_surface: HarnessToolSurface::default(),
101 safety_posture: HarnessSafetyPosture::default(),
102 }
103 }
104}
105
106impl HarnessPosture {
107 #[must_use]
109 pub fn cache_heavy() -> Self {
110 Self {
111 kind: HarnessPostureKind::CacheHeavy,
112 max_subagents: 10,
113 prefer_codebase_search: false,
114 compaction_strategy: HarnessCompactionStrategy::PrefixCache,
115 tool_surface: HarnessToolSurface::Full,
116 safety_posture: HarnessSafetyPosture::Standard,
117 }
118 }
119
120 #[must_use]
122 pub fn lean() -> Self {
123 Self {
124 kind: HarnessPostureKind::Lean,
125 max_subagents: 20,
126 prefer_codebase_search: true,
127 compaction_strategy: HarnessCompactionStrategy::Aggressive,
128 tool_surface: HarnessToolSurface::Full,
129 safety_posture: HarnessSafetyPosture::Standard,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
136#[serde(deny_unknown_fields)]
137pub struct HarnessProfile {
138 pub provider_route: String,
141 pub model_pattern: String,
143 #[serde(default)]
145 pub posture: HarnessPosture,
146}
147
148impl HarnessProfile {
149 #[must_use]
154 pub fn matches_route(&self, provider_route: &str, model: &str) -> bool {
155 provider_routes_equal(&self.provider_route, provider_route)
156 && wildcard_pattern_matches(&self.model_pattern, model)
157 }
158}
159
160#[must_use]
165pub fn built_in_harness_profiles() -> &'static [HarnessProfile] {
166 static PROFILES: OnceLock<Vec<HarnessProfile>> = OnceLock::new();
167 PROFILES.get_or_init(|| {
168 vec![
169 HarnessProfile {
170 provider_route: "deepseek".to_string(),
171 model_pattern: "deepseek-v4*".to_string(),
172 posture: HarnessPosture::cache_heavy(),
173 },
174 HarnessProfile {
175 provider_route: "xiaomi-mimo".to_string(),
176 model_pattern: "mimo-v2.5*".to_string(),
177 posture: HarnessPosture::cache_heavy(),
178 },
179 HarnessProfile {
180 provider_route: "arcee".to_string(),
181 model_pattern: "trinity-large-thinking".to_string(),
182 posture: HarnessPosture::cache_heavy(),
183 },
184 HarnessProfile {
185 provider_route: "huggingface".to_string(),
186 model_pattern: "*".to_string(),
187 posture: HarnessPosture::lean(),
188 },
189 HarnessProfile {
190 provider_route: "sglang".to_string(),
191 model_pattern: "*".to_string(),
192 posture: HarnessPosture::lean(),
193 },
194 HarnessProfile {
195 provider_route: "vllm".to_string(),
196 model_pattern: "*".to_string(),
197 posture: HarnessPosture::lean(),
198 },
199 HarnessProfile {
200 provider_route: "ollama".to_string(),
201 model_pattern: "*".to_string(),
202 posture: HarnessPosture::lean(),
203 },
204 ]
205 })
206}
207
208fn provider_routes_equal(expected: &str, actual: &str) -> bool {
209 match (ProviderKind::parse(expected), ProviderKind::parse(actual)) {
210 (Some(expected), Some(actual)) => expected == actual,
211 _ => expected.trim().eq_ignore_ascii_case(actual.trim()),
212 }
213}
214
215fn wildcard_pattern_matches(pattern: &str, value: &str) -> bool {
216 wildcard_chars_match(
217 &pattern.chars().collect::<Vec<_>>(),
218 &value.chars().collect::<Vec<_>>(),
219 )
220}
221
222fn wildcard_chars_match(pattern: &[char], value: &[char]) -> bool {
223 let (mut pattern_idx, mut value_idx) = (0, 0);
224 let mut star_idx: Option<usize> = None;
225 let mut star_value_idx = 0;
226
227 while value_idx < value.len() {
228 if pattern_idx < pattern.len()
229 && (pattern[pattern_idx] == '?' || pattern[pattern_idx] == value[value_idx])
230 {
231 pattern_idx += 1;
232 value_idx += 1;
233 } else if pattern_idx < pattern.len() && pattern[pattern_idx] == '*' {
234 star_idx = Some(pattern_idx);
235 pattern_idx += 1;
236 star_value_idx = value_idx;
237 } else if let Some(star) = star_idx {
238 pattern_idx = star + 1;
239 star_value_idx += 1;
240 value_idx = star_value_idx;
241 } else {
242 return false;
243 }
244 }
245
246 pattern[pattern_idx..].iter().all(|ch| *ch == '*')
247}