Skip to main content

cookie_agent_models/compiler/
variants.rs

1use std::collections::BTreeMap;
2
3use cookie_agent_identity::{ConfiguredModelDefault, VariantId};
4use serde::Serialize;
5
6use crate::{
7    HeaderName, ProviderOptions, SafeStaticHeaderValue,
8    authoring::{ManagedModelOverride, ReasoningBehavior, RequestDefaults, VariantDefinition},
9    catalog::CatalogReasoningOption,
10};
11
12#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
13#[serde(rename_all = "snake_case")]
14pub enum CompiledVariantOrigin {
15    ModelsDevEffort,
16    ModelsDevToggle,
17    ModelsDevBudgetTokens,
18    Authored,
19}
20
21#[derive(Clone, Debug, Serialize)]
22pub struct CompiledVariant {
23    pub id: VariantId,
24    pub model_id: Option<crate::authoring::WireModelId>,
25    pub display_name: String,
26    pub defaults: RequestDefaults,
27    pub options: ProviderOptions,
28    pub reasoning: Option<ReasoningBehavior>,
29    pub headers: BTreeMap<HeaderName, SafeStaticHeaderValue>,
30    pub origin: CompiledVariantOrigin,
31}
32
33type CompiledVariants = (
34    BTreeMap<VariantId, CompiledVariant>,
35    Vec<VariantId>,
36    Option<VariantId>,
37);
38
39#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
40pub(crate) enum VariantCompileError {
41    #[error("invalid_variant")]
42    Invalid,
43    #[error("variant_collision")]
44    Collision,
45    #[error("unknown_default_variant")]
46    Default,
47}
48
49pub(crate) fn managed_variants(
50    source: &[CatalogReasoningOption],
51    override_: Option<&ManagedModelOverride>,
52    defaults: &RequestDefaults,
53    options: &ProviderOptions,
54) -> Result<CompiledVariants, VariantCompileError> {
55    let (mut variants, mut order) = generated(source)?;
56    for variant in variants.values_mut() {
57        variant.defaults = defaults.clone();
58        variant.options = options.clone();
59    }
60    if let Some(override_) = override_ {
61        apply_directives(
62            &mut variants,
63            &mut order,
64            &override_.variants,
65            defaults,
66            options,
67        )?;
68    }
69    let default = resolve_default(
70        override_.and_then(|value| value.default_variant.as_ref()),
71        &variants,
72    )?;
73    Ok((variants, order, default))
74}
75
76pub(crate) fn custom_variants(
77    directives: &BTreeMap<VariantId, VariantDefinition>,
78    default: Option<&ConfiguredModelDefault>,
79    defaults: &RequestDefaults,
80    options: &ProviderOptions,
81) -> Result<CompiledVariants, VariantCompileError> {
82    let mut variants = BTreeMap::new();
83    let mut order = Vec::new();
84    apply_directives(&mut variants, &mut order, directives, defaults, options)?;
85    let default = resolve_default(default, &variants)?;
86    Ok((variants, order, default))
87}
88
89fn generated(
90    source: &[CatalogReasoningOption],
91) -> Result<(BTreeMap<VariantId, CompiledVariant>, Vec<VariantId>), VariantCompileError> {
92    let mut variants = BTreeMap::new();
93    let mut order = Vec::new();
94    for option in source {
95        match option {
96            CatalogReasoningOption::Effort { values } => {
97                for value in values {
98                    let (id, reasoning) = if let Some(value) = value.as_deref() {
99                        let effort =
100                            serde_json::from_value(serde_json::Value::String(value.to_owned()))
101                                .map_err(|_| VariantCompileError::Invalid)?;
102                        (value, ReasoningBehavior::Effort { value: effort })
103                    } else {
104                        ("off", ReasoningBehavior::Toggle { enabled: false })
105                    };
106                    insert_generated(
107                        &mut variants,
108                        &mut order,
109                        id,
110                        reasoning,
111                        CompiledVariantOrigin::ModelsDevEffort,
112                    )?;
113                }
114            }
115            CatalogReasoningOption::Toggle => {
116                for (id, enabled) in [("off", false), ("on", true)] {
117                    insert_generated(
118                        &mut variants,
119                        &mut order,
120                        id,
121                        ReasoningBehavior::Toggle { enabled },
122                        CompiledVariantOrigin::ModelsDevToggle,
123                    )?;
124                }
125            }
126            CatalogReasoningOption::BudgetTokens { min, max } => {
127                if let Some(value) = min {
128                    insert_generated(
129                        &mut variants,
130                        &mut order,
131                        if *value == -1 {
132                            "budget-auto"
133                        } else {
134                            "budget-min"
135                        },
136                        ReasoningBehavior::BudgetTokens { value: *value },
137                        CompiledVariantOrigin::ModelsDevBudgetTokens,
138                    )?;
139                }
140                if let Some(value) = max {
141                    insert_generated(
142                        &mut variants,
143                        &mut order,
144                        "budget-max",
145                        ReasoningBehavior::BudgetTokens { value: *value },
146                        CompiledVariantOrigin::ModelsDevBudgetTokens,
147                    )?;
148                }
149            }
150        }
151    }
152    suppress_redundant_generated_toggle_on(&mut variants, &mut order);
153    Ok((variants, order))
154}
155
156fn suppress_redundant_generated_toggle_on(
157    variants: &mut BTreeMap<VariantId, CompiledVariant>,
158    order: &mut Vec<VariantId>,
159) {
160    let has_explicit_reasoning_level = variants.values().any(|variant| {
161        matches!(
162            (variant.origin, variant.reasoning.as_ref()),
163            (
164                CompiledVariantOrigin::ModelsDevEffort,
165                Some(ReasoningBehavior::Effort { .. })
166            ) | (
167                CompiledVariantOrigin::ModelsDevBudgetTokens,
168                Some(ReasoningBehavior::BudgetTokens { .. })
169            )
170        )
171    });
172    if !has_explicit_reasoning_level {
173        return;
174    }
175
176    let toggle_on = variants.iter().find_map(|(id, variant)| {
177        (variant.origin == CompiledVariantOrigin::ModelsDevToggle
178            && matches!(
179                variant.reasoning.as_ref(),
180                Some(ReasoningBehavior::Toggle { enabled: true })
181            ))
182        .then(|| id.clone())
183    });
184    if let Some(id) = toggle_on {
185        variants.remove(&id);
186        order.retain(|candidate| candidate != &id);
187    }
188}
189
190fn insert_generated(
191    variants: &mut BTreeMap<VariantId, CompiledVariant>,
192    order: &mut Vec<VariantId>,
193    id: &str,
194    reasoning: ReasoningBehavior,
195    origin: CompiledVariantOrigin,
196) -> Result<(), VariantCompileError> {
197    let id = VariantId::new(id).map_err(|_| VariantCompileError::Invalid)?;
198    let candidate = CompiledVariant {
199        model_id: None,
200        display_name: display_name(&id),
201        id: id.clone(),
202        defaults: RequestDefaults::default(),
203        options: ProviderOptions::default(),
204        reasoning: Some(reasoning),
205        headers: BTreeMap::new(),
206        origin,
207    };
208    if let Some(existing) = variants.get(&id) {
209        if serde_json::to_value(&existing.reasoning).ok()
210            != serde_json::to_value(&candidate.reasoning).ok()
211        {
212            return Err(VariantCompileError::Collision);
213        }
214        return Ok(());
215    }
216    order.push(id.clone());
217    variants.insert(id, candidate);
218    Ok(())
219}
220
221fn apply_directives(
222    variants: &mut BTreeMap<VariantId, CompiledVariant>,
223    order: &mut Vec<VariantId>,
224    directives: &BTreeMap<VariantId, VariantDefinition>,
225    defaults: &RequestDefaults,
226    options: &ProviderOptions,
227) -> Result<(), VariantCompileError> {
228    for (id, directive) in directives {
229        if directive.enabled == Some(false) {
230            if directive.disabled_has_settings() {
231                return Err(VariantCompileError::Invalid);
232            }
233            variants.remove(id);
234            order.retain(|candidate| candidate != id);
235            continue;
236        }
237        let variant = variants.entry(id.clone()).or_insert_with(|| {
238            order.push(id.clone());
239            CompiledVariant {
240                model_id: None,
241                id: id.clone(),
242                display_name: display_name(id),
243                defaults: defaults.clone(),
244                options: options.clone(),
245                reasoning: None,
246                headers: BTreeMap::new(),
247                origin: CompiledVariantOrigin::Authored,
248            }
249        });
250        if let Some(name) = &directive.display_name {
251            if name.trim().is_empty() || name.len() > 512 || name.chars().any(char::is_control) {
252                return Err(VariantCompileError::Invalid);
253            }
254            variant.display_name.clone_from(name);
255        }
256        if let Some(model_id) = &directive.model_id {
257            variant.model_id = Some(model_id.clone());
258        }
259        if let Some(defaults) = &directive.generation_options {
260            defaults.apply(&mut variant.defaults);
261        }
262        if let Some(options) = &directive.adaptor_options {
263            options.apply(&mut variant.options);
264        }
265        if let Some(reasoning) = &directive.reasoning {
266            variant.reasoning = Some(reasoning.clone());
267        }
268        // Keep deletion markers until global/provider/model headers are composed.
269        variant.headers.extend(directive.headers().clone());
270    }
271    Ok(())
272}
273
274fn resolve_default(
275    default: Option<&ConfiguredModelDefault>,
276    variants: &BTreeMap<VariantId, CompiledVariant>,
277) -> Result<Option<VariantId>, VariantCompileError> {
278    match default {
279        None | Some(ConfiguredModelDefault::Base) => Ok(None),
280        Some(ConfiguredModelDefault::Named(id)) if variants.contains_key(id) => {
281            Ok(Some(id.clone()))
282        }
283        Some(ConfiguredModelDefault::Named(_)) => Err(VariantCompileError::Default),
284    }
285}
286
287fn display_name(id: &VariantId) -> String {
288    id.as_str()
289        .split(['-', '_', '.'])
290        .map(|part| {
291            let mut chars = part.chars();
292            chars.next().map_or_else(String::new, |first| {
293                first.to_uppercase().chain(chars).collect()
294            })
295        })
296        .collect::<Vec<_>>()
297        .join(" ")
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303    use crate::authoring::PartialRequestDefaults;
304
305    fn managed_variants(
306        source: &[CatalogReasoningOption],
307        override_: Option<&ManagedModelOverride>,
308    ) -> Result<CompiledVariants, VariantCompileError> {
309        super::managed_variants(
310            source,
311            override_,
312            &RequestDefaults::default(),
313            &ProviderOptions::default(),
314        )
315    }
316
317    fn id(value: &str) -> VariantId {
318        VariantId::new(value).unwrap()
319    }
320
321    fn names(order: &[VariantId]) -> Vec<&str> {
322        order.iter().map(VariantId::as_str).collect()
323    }
324
325    fn override_with(
326        variants: BTreeMap<VariantId, VariantDefinition>,
327        default_variant: Option<ConfiguredModelDefault>,
328    ) -> ManagedModelOverride {
329        ManagedModelOverride {
330            model_id: None,
331            enabled: None,
332            display_name: None,
333            defaults: PartialRequestDefaults::default(),
334            variants,
335            default_variant,
336            options: crate::AdaptorOptions::default(),
337            pricing: None,
338            compaction: crate::NativeCompactionConfig::Unsupported,
339            headers: BTreeMap::new(),
340        }
341    }
342
343    #[test]
344    fn toggle_with_effort_suppresses_generated_on_and_resolves_default() {
345        let source = [
346            CatalogReasoningOption::Toggle,
347            CatalogReasoningOption::Effort {
348                values: vec![Some("low".into()), Some("high".into()), Some("max".into())],
349            },
350        ];
351        let override_ = override_with(
352            BTreeMap::new(),
353            Some(ConfiguredModelDefault::Named(id("high"))),
354        );
355
356        let (variants, order, default) = managed_variants(&source, Some(&override_)).unwrap();
357
358        assert_eq!(names(&order), ["off", "low", "high", "max"]);
359        assert!(!variants.contains_key(&id("on")));
360        assert_eq!(default, Some(id("high")));
361    }
362
363    #[test]
364    fn toggle_only_preserves_generated_on_and_resolves_default() {
365        let override_ = override_with(
366            BTreeMap::new(),
367            Some(ConfiguredModelDefault::Named(id("on"))),
368        );
369
370        let (variants, order, default) =
371            managed_variants(&[CatalogReasoningOption::Toggle], Some(&override_)).unwrap();
372
373        assert_eq!(names(&order), ["off", "on"]);
374        assert!(variants.contains_key(&id("on")));
375        assert_eq!(default, Some(id("on")));
376    }
377
378    #[test]
379    fn effort_only_is_unchanged_and_resolves_default() {
380        let override_ = override_with(
381            BTreeMap::new(),
382            Some(ConfiguredModelDefault::Named(id("high"))),
383        );
384
385        let (variants, order, default) = managed_variants(
386            &[CatalogReasoningOption::Effort {
387                values: vec![Some("low".into()), Some("high".into())],
388            }],
389            Some(&override_),
390        )
391        .unwrap();
392
393        assert_eq!(names(&order), ["low", "high"]);
394        assert_eq!(variants.len(), 2);
395        assert_eq!(default, Some(id("high")));
396    }
397
398    #[test]
399    fn toggle_with_budget_tokens_suppresses_generated_on() {
400        let override_ = override_with(
401            BTreeMap::new(),
402            Some(ConfiguredModelDefault::Named(id("budget-max"))),
403        );
404
405        let (variants, order, default) = managed_variants(
406            &[
407                CatalogReasoningOption::Toggle,
408                CatalogReasoningOption::BudgetTokens {
409                    min: Some(1024),
410                    max: Some(4096),
411                },
412            ],
413            Some(&override_),
414        )
415        .unwrap();
416
417        assert_eq!(names(&order), ["off", "budget-min", "budget-max"]);
418        assert!(!variants.contains_key(&id("on")));
419        assert_eq!(default, Some(id("budget-max")));
420    }
421
422    #[test]
423    fn managed_override_can_readd_on_after_generation_suppression() {
424        let override_ = override_with(
425            BTreeMap::from([(
426                id("on"),
427                VariantDefinition {
428                    reasoning: Some(ReasoningBehavior::Toggle { enabled: true }),
429                    ..VariantDefinition::default()
430                },
431            )]),
432            Some(ConfiguredModelDefault::Named(id("on"))),
433        );
434
435        let (variants, order, default) = managed_variants(
436            &[
437                CatalogReasoningOption::Toggle,
438                CatalogReasoningOption::Effort {
439                    values: vec![Some("low".into())],
440                },
441            ],
442            Some(&override_),
443        )
444        .unwrap();
445
446        assert_eq!(names(&order), ["off", "low", "on"]);
447        assert_eq!(variants[&id("on")].origin, CompiledVariantOrigin::Authored);
448        assert_eq!(default, Some(id("on")));
449    }
450}