1use crate::budget::{CompressionMode, TaskScope};
10use crate::input::InputFormat;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum TransformId {
14 JsonMinify,
15 JsonFieldFold,
16 JsonValueDict,
17 SchemaCompaction,
18 LogFieldFold,
19 LogCompaction,
20 DiffCompaction,
21}
22
23impl TransformId {
24 pub fn as_str(&self) -> &'static str {
25 match self {
26 TransformId::JsonMinify => "json_minify",
27 TransformId::JsonFieldFold => "json_field_fold",
28 TransformId::JsonValueDict => "json_value_dict",
29 TransformId::SchemaCompaction => "schema_compaction",
30 TransformId::LogFieldFold => "log_field_fold",
31 TransformId::LogCompaction => "log_compaction",
32 TransformId::DiffCompaction => "diff_compaction",
33 }
34 }
35}
36
37#[derive(Debug, Clone, Copy)]
38pub struct ModeEntry {
39 pub transform_id: TransformId,
40 pub version: &'static str,
41 pub conservative_enabled: bool,
42 pub balanced_enabled: bool,
43 pub aggressive_enabled: bool,
44 pub experimental: bool,
45 pub max_ratio_conservative: f64,
46 pub max_ratio_balanced: f64,
47 pub max_ratio_aggressive: f64,
48 pub task_scopes: &'static [TaskScope],
49 pub applicable_formats: &'static [InputFormat],
50}
51
52impl ModeEntry {
53 pub fn enabled_for(&self, mode: CompressionMode) -> bool {
54 match mode {
55 CompressionMode::Conservative => self.conservative_enabled,
56 CompressionMode::Balanced => self.balanced_enabled,
57 CompressionMode::Aggressive => self.aggressive_enabled,
58 }
59 }
60
61 pub fn max_ratio_for(&self, mode: CompressionMode) -> f64 {
62 match mode {
63 CompressionMode::Conservative => self.max_ratio_conservative,
64 CompressionMode::Balanced => self.max_ratio_balanced,
65 CompressionMode::Aggressive => self.max_ratio_aggressive,
66 }
67 }
68
69 fn applies_to_format(&self, format: InputFormat) -> bool {
70 self.applicable_formats.contains(&format)
71 }
72}
73
74pub static ALL_ENTRIES: &[ModeEntry] = &[
81 ModeEntry {
82 transform_id: TransformId::JsonMinify,
83 version: "1.0.0",
84 conservative_enabled: true,
85 balanced_enabled: true,
86 aggressive_enabled: true,
87 experimental: false,
88 max_ratio_conservative: 1.0,
89 max_ratio_balanced: 1.0,
90 max_ratio_aggressive: 1.0,
91 task_scopes: &[TaskScope::All],
92 applicable_formats: &[
93 InputFormat::OpenAiJson,
94 InputFormat::AnthropicJson,
95 InputFormat::Json,
96 ],
97 },
98 ModeEntry {
104 transform_id: TransformId::JsonFieldFold,
105 version: "1.0.0",
106 conservative_enabled: false,
107 balanced_enabled: true,
108 aggressive_enabled: true,
109 experimental: false,
110 max_ratio_conservative: 0.0,
111 max_ratio_balanced: 1.0,
112 max_ratio_aggressive: 1.0,
113 task_scopes: &[TaskScope::All],
114 applicable_formats: &[InputFormat::Json],
115 },
116 ModeEntry {
120 transform_id: TransformId::JsonValueDict,
121 version: "1.0.0",
122 conservative_enabled: false,
123 balanced_enabled: true,
124 aggressive_enabled: true,
125 experimental: false,
126 max_ratio_conservative: 0.0,
127 max_ratio_balanced: 1.0,
128 max_ratio_aggressive: 1.0,
129 task_scopes: &[TaskScope::All],
130 applicable_formats: &[InputFormat::Json],
131 },
132 ModeEntry {
133 transform_id: TransformId::SchemaCompaction,
134 version: "1.0.0",
135 conservative_enabled: true,
136 balanced_enabled: true,
137 aggressive_enabled: true,
138 experimental: false,
139 max_ratio_conservative: 0.15,
140 max_ratio_balanced: 0.30,
141 max_ratio_aggressive: 0.50,
142 task_scopes: &[TaskScope::All],
143 applicable_formats: &[InputFormat::OpenAiJson, InputFormat::AnthropicJson],
144 },
145 ModeEntry {
152 transform_id: TransformId::LogFieldFold,
153 version: "1.0.0",
154 conservative_enabled: false,
155 balanced_enabled: false,
156 aggressive_enabled: false,
157 experimental: true,
158 max_ratio_conservative: 0.0,
159 max_ratio_balanced: 1.0,
160 max_ratio_aggressive: 1.0,
161 task_scopes: &[TaskScope::All],
162 applicable_formats: &[InputFormat::PlainText, InputFormat::CommandOutput],
163 },
164 ModeEntry {
165 transform_id: TransformId::LogCompaction,
166 version: "1.0.0",
167 conservative_enabled: false,
173 balanced_enabled: true,
174 aggressive_enabled: true,
175 experimental: false,
176 max_ratio_conservative: 0.0,
177 max_ratio_balanced: 0.65, max_ratio_aggressive: 0.75,
179 task_scopes: &[TaskScope::General, TaskScope::ChangeSummary],
180 applicable_formats: &[InputFormat::PlainText, InputFormat::CommandOutput],
181 },
182 ModeEntry {
183 transform_id: TransformId::DiffCompaction,
184 version: "1.0.0",
185 conservative_enabled: false,
194 balanced_enabled: false,
195 aggressive_enabled: false,
196 experimental: true,
197 max_ratio_conservative: 0.0,
198 max_ratio_balanced: 0.60,
199 max_ratio_aggressive: 0.70,
200 task_scopes: &[TaskScope::CodeReview, TaskScope::ChangeSummary],
201 applicable_formats: &[
202 InputFormat::PlainText,
203 InputFormat::CommandOutput,
204 InputFormat::GitDiff,
205 ],
206 },
207 ];
210
211pub fn pipeline_for(
215 mode: CompressionMode,
216 task_scope: TaskScope,
217 format: InputFormat,
218 experimental: bool,
219 enabled_ids: &[String],
220 disabled_ids: &[String],
221) -> Vec<&'static ModeEntry> {
222 ALL_ENTRIES
223 .iter()
224 .filter(|e| {
225 let mode_enabled = e.enabled_for(mode);
226 let experimentally_enabled = e.experimental && experimental;
227 let explicitly_enabled = (!e.experimental || experimental)
228 && enabled_ids.iter().any(|id| id == e.transform_id.as_str());
229 mode_enabled || experimentally_enabled || explicitly_enabled
230 })
231 .filter(|e| !disabled_ids.iter().any(|id| id == e.transform_id.as_str()))
232 .filter(|e| e.task_scopes.contains(&TaskScope::All) || e.task_scopes.contains(&task_scope))
233 .filter(|e| e.applies_to_format(format))
234 .collect()
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn transform_id_as_str_matches_canonical_ids() {
243 assert_eq!(TransformId::JsonMinify.as_str(), "json_minify");
244 assert_eq!(TransformId::SchemaCompaction.as_str(), "schema_compaction");
245 assert_eq!(TransformId::LogCompaction.as_str(), "log_compaction");
246 assert_eq!(TransformId::DiffCompaction.as_str(), "diff_compaction");
247 }
248
249 #[test]
250 fn conservative_mode_never_includes_experimental_lossy_transforms() {
251 let entries = pipeline_for(
252 CompressionMode::Conservative,
253 TaskScope::All,
254 InputFormat::PlainText,
255 true,
256 &[],
257 &[],
258 );
259 assert!(
260 entries
261 .iter()
262 .all(|e| e.transform_id != TransformId::LogCompaction
263 && e.transform_id != TransformId::DiffCompaction)
264 );
265 }
266
267 #[test]
268 fn balanced_mode_lossless_transforms_apply_to_openai_json() {
269 let entries = pipeline_for(
270 CompressionMode::Balanced,
271 TaskScope::All,
272 InputFormat::OpenAiJson,
273 false,
274 &[],
275 &[],
276 );
277 let ids: Vec<_> = entries.iter().map(|e| e.transform_id).collect();
278 assert!(ids.contains(&TransformId::JsonMinify));
279 assert!(ids.contains(&TransformId::SchemaCompaction));
280 }
281
282 #[test]
283 fn experimental_flag_enables_log_compaction_for_matching_task_scope() {
284 let entries = pipeline_for(
285 CompressionMode::Balanced,
286 TaskScope::General,
287 InputFormat::CommandOutput,
288 true,
289 &[],
290 &[],
291 );
292 assert!(
293 entries
294 .iter()
295 .any(|e| e.transform_id == TransformId::LogCompaction)
296 );
297 }
298
299 #[test]
300 fn log_compaction_skipped_for_non_applicable_format_even_when_experimental() {
301 let entries = pipeline_for(
302 CompressionMode::Balanced,
303 TaskScope::General,
304 InputFormat::OpenAiJson,
305 true,
306 &[],
307 &[],
308 );
309 assert!(
310 !entries
311 .iter()
312 .any(|e| e.transform_id == TransformId::LogCompaction)
313 );
314 }
315
316 #[test]
317 fn disabled_ids_remove_a_transform_even_when_otherwise_enabled() {
318 let entries = pipeline_for(
319 CompressionMode::Balanced,
320 TaskScope::All,
321 InputFormat::OpenAiJson,
322 false,
323 &[],
324 &["json_minify".to_string()],
325 );
326 assert!(
327 !entries
328 .iter()
329 .any(|e| e.transform_id == TransformId::JsonMinify)
330 );
331 }
332
333 #[test]
334 fn diff_compaction_requires_matching_task_scope_even_with_enable_flag() {
335 let entries = pipeline_for(
337 CompressionMode::Balanced,
338 TaskScope::Debugging,
339 InputFormat::GitDiff,
340 true,
341 &["diff_compaction".to_string()],
342 &[],
343 );
344 assert!(
345 !entries
346 .iter()
347 .any(|e| e.transform_id == TransformId::DiffCompaction)
348 );
349 }
350}