oino_models/resolver/
cycle.rs1use indexmap::IndexMap;
4use oino_ai::{Model, ThinkingLevel};
5
6use super::defaults::DEFAULT_THINKING_LEVEL;
7use super::initial::model_thinking_level_key;
8use super::scope::ScopedModel;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
12pub enum CycleDirection {
13 #[default]
15 Forward,
16 Backward,
18}
19
20fn same_model(a: &Model, b: &Model) -> bool {
21 a.id == b.id && a.provider == b.provider
22}
23
24fn step(len: usize, current: Option<usize>, direction: CycleDirection) -> usize {
25 let current = current.unwrap_or(0);
26 match direction {
27 CycleDirection::Forward => (current + 1) % len,
28 CycleDirection::Backward => (current + len - 1) % len,
29 }
30}
31
32#[must_use]
35pub fn next_scoped_model(
36 scoped: &[ScopedModel],
37 available: &[Model],
38 current: Option<&Model>,
39 direction: CycleDirection,
40) -> Option<ScopedModel> {
41 let candidates: Vec<&ScopedModel> = scoped
42 .iter()
43 .filter(|s| available.iter().any(|m| same_model(m, &s.model)))
44 .collect();
45 if candidates.len() <= 1 {
46 return None;
47 }
48 let index = current.and_then(|c| candidates.iter().position(|s| same_model(&s.model, c)));
49 Some(candidates[step(candidates.len(), index, direction)].clone())
50}
51
52#[must_use]
54pub fn next_available_model(
55 available: &[Model],
56 current: Option<&Model>,
57 direction: CycleDirection,
58) -> Option<Model> {
59 if available.len() <= 1 {
60 return None;
61 }
62 let index = current.and_then(|c| available.iter().position(|m| same_model(m, c)));
63 Some(available[step(available.len(), index, direction)].clone())
64}
65
66#[derive(Clone, Copy, Debug, Default)]
68pub struct ThinkingSwitch<'a> {
69 pub explicit: Option<ThinkingLevel>,
71 pub model_thinking_levels: Option<&'a IndexMap<String, ThinkingLevel>>,
73 pub default_thinking_level: Option<ThinkingLevel>,
75 pub current: Option<ThinkingLevel>,
77}
78
79#[must_use]
82pub fn thinking_level_for_model_switch(
83 target: Option<&Model>,
84 inputs: &ThinkingSwitch<'_>,
85) -> ThinkingLevel {
86 if let Some(explicit) = inputs.explicit {
87 return explicit;
88 }
89 if let Some(level) = target.and_then(|model| {
90 inputs
91 .model_thinking_levels?
92 .get(&model_thinking_level_key(&model.provider, &model.id))
93 .copied()
94 }) {
95 return level;
96 }
97 inputs
98 .default_thinking_level
99 .or(inputs.current)
100 .unwrap_or(DEFAULT_THINKING_LEVEL)
101}