yapi_core/agent_session/
models.rs1use std::sync::Arc;
4
5use yapi_ai::api::Apis;
6use yapi_ai::registry::ModelRegistry;
7use yapi_types::event::AgentEvent;
8use yapi_types::extension_event::ExtensionEvent;
9use yapi_types::message::ThinkingLevel;
10use yapi_types::model::Model;
11use yapi_types::sync::{lock, read, write};
12
13use super::AgentSession;
14use crate::extensions::ModelUpdate;
15
16#[derive(Clone, Debug, Default)]
18pub struct CatalogRefresh {
19 pub errors: Vec<(String, String)>,
21 pub aborted: bool,
23}
24
25impl AgentSession {
26 pub fn set_model(&self, model: Model) -> Result<(), String> {
29 if !self.registry().has_auth(&model.provider) {
30 return Err(format!("No API key for {}/{}", model.provider, model.id));
31 }
32 self.apply_model(model, None, "set");
33 Ok(())
34 }
35
36 pub fn keep_selection(&self, from: &AgentSession) {
39 let (model, level) = {
40 let state = lock(&from.inner.state);
41 (state.model.clone(), state.thinking_level)
42 };
43 {
44 let mut state = lock(&self.inner.state);
45 state.model = model;
46 state.thinking_level = level;
47 }
48 self.set_scoped_models(from.scoped_models());
49 }
50
51 pub fn scoped_models(&self) -> Vec<crate::model_resolver::ScopedModel> {
55 lock(&self.inner.scoped_models).clone()
56 }
57
58 pub fn models_in_scope(&self) -> Vec<Model> {
60 let scoped = self.scoped_models();
61 if scoped.is_empty() {
62 self.available_models()
63 } else {
64 scoped.into_iter().map(|entry| entry.model).collect()
65 }
66 }
67
68 pub fn set_scoped_models(&self, models: Vec<crate::model_resolver::ScopedModel>) {
70 *lock(&self.inner.scoped_models) = models;
71 }
72
73 pub fn save_default_model(&self, model: &Model) {
76 let _ = self.set_global_setting(
77 "defaultProvider",
78 Some(serde_json::Value::String(model.provider.clone())),
79 );
80 let _ = self.set_global_setting(
81 "defaultModel",
82 Some(serde_json::Value::String(model.id.clone())),
83 );
84 {
85 let mut scoped = lock(&self.inner.scoped_models);
86 if scoped.is_empty()
87 || scoped
88 .iter()
89 .any(|entry| entry.model.is(&model.provider, &model.id))
90 {
91 return;
92 }
93 scoped.push(crate::model_resolver::ScopedModel {
94 model: model.clone(),
95 thinking_level: None,
96 });
97 }
98 let Some(enabled) = self
99 .settings()
100 .enabled_models
101 .filter(|list| !list.is_empty())
102 else {
103 return;
104 };
105 let reference = model.reference();
106 if enabled
107 .iter()
108 .any(|pattern| pattern.eq_ignore_ascii_case(&reference))
109 {
110 return;
111 }
112 let mut list: Vec<serde_json::Value> =
113 enabled.into_iter().map(serde_json::Value::String).collect();
114 list.push(serde_json::Value::String(reference));
115 let _ = self.set_global_setting("enabledModels", Some(serde_json::Value::Array(list)));
116 }
117
118 fn apply_model(&self, model: Model, explicit: Option<ThinkingLevel>, source: &str) {
123 let settings = self.settings();
124 let level = explicit
125 .or_else(|| {
126 settings
127 .model_thinking_levels
128 .as_ref()
129 .and_then(|levels| levels.get(&model.reference()))
130 .copied()
131 })
132 .or_else(|| lock(&self.inner.settings).default_thinking_level())
133 .unwrap_or_else(|| self.thinking_level());
134 let previous = {
135 let mut state = lock(&self.inner.state);
136 let _ = state
137 .session
138 .append_model_change(&model.provider, &model.id);
139 state.model.replace(model.clone())
140 };
141 self.set_thinking_level(level);
142 if previous
143 .as_ref()
144 .is_none_or(|previous| !previous.is(&model.provider, &model.id))
145 {
146 self.announce(ExtensionEvent::ModelSelect {
147 model: &model,
148 previous_model: previous.as_ref(),
149 source,
150 });
151 }
152 }
153
154 pub fn cycle_model(&self, forward: bool) -> Option<(Model, bool)> {
159 let scoped = self.scoped_models();
160 let available = self.available_models();
161 let is_scoped = !scoped.is_empty();
162 let models: Vec<(Model, Option<ThinkingLevel>)> = if is_scoped {
163 scoped
164 .into_iter()
165 .filter(|entry| {
166 available
167 .iter()
168 .any(|m| m.is(&entry.model.provider, &entry.model.id))
169 })
170 .map(|entry| (entry.model, entry.thinking_level))
171 .collect()
172 } else {
173 available.into_iter().map(|model| (model, None)).collect()
174 };
175 if models.len() <= 1 {
176 return None;
177 }
178 let index = self
179 .model()
180 .and_then(|current| {
181 models
182 .iter()
183 .position(|(model, _)| model.is(¤t.provider, ¤t.id))
184 })
185 .unwrap_or(0);
186 let next = if forward {
187 (index + 1) % models.len()
188 } else {
189 (index + models.len() - 1) % models.len()
190 };
191 let (model, level) = models[next].clone();
192 self.apply_model(model.clone(), level, "cycle");
193 Some((model, is_scoped))
194 }
195
196 pub fn set_thinking_level(&self, level: ThinkingLevel) {
199 let mut state = lock(&self.inner.state);
200 let level = match &state.model {
201 Some(model) => yapi_ai::thinking::clamp_level(model, level),
202 None => level,
203 };
204 if level == state.thinking_level {
205 return;
206 }
207 let previous = std::mem::replace(&mut state.thinking_level, level);
208 let _ = state.session.append_thinking_level_change(level.as_str());
209 drop(state);
210 self.emit(&AgentEvent::ThinkingLevelChanged { level });
211 self.announce(ExtensionEvent::ThinkingLevelSelect {
212 level,
213 previous_level: previous,
214 });
215 }
216
217 pub fn available_thinking_levels(&self) -> Vec<ThinkingLevel> {
220 match self.model() {
221 Some(model) => yapi_ai::thinking::supported_levels(&model),
222 None => ThinkingLevel::ALL.to_vec(),
223 }
224 }
225
226 pub fn cycle_thinking_level(&self) -> Option<ThinkingLevel> {
229 let model = self.model()?;
230 if !model.reasoning {
231 return None;
232 }
233 let levels = yapi_ai::thinking::supported_levels(&model);
234 let current = self.thinking_level();
235 let index = levels.iter().position(|level| *level == current);
236 let next = levels[index.map_or(0, |index| (index + 1) % levels.len())];
237 self.set_thinking_level(next);
238 Some(self.thinking_level())
239 }
240
241 pub fn available_models(&self) -> Vec<Model> {
243 read(&self.inner.registry)
244 .available()
245 .into_iter()
246 .cloned()
247 .collect()
248 }
249
250 pub fn registry(&self) -> Arc<ModelRegistry> {
252 Arc::clone(&read(&self.inner.registry))
253 }
254
255 pub fn apis(&self) -> Apis {
257 read(&self.inner.apis).clone()
258 }
259
260 pub fn update_providers<T>(
265 &self,
266 change: impl FnOnce(&mut ModelRegistry, &mut Apis) -> T,
267 ) -> T {
268 let result = {
269 let mut slot = write(&self.inner.registry);
270 let mut next = (**slot).clone();
271 let result = change(&mut next, &mut write(&self.inner.apis));
272 *slot = Arc::new(next);
273 result
274 };
275 let registry = self.registry();
276 let mut state = lock(&self.inner.state);
277 if let Some(current) = &state.model
278 && let Some(model) = registry.find(¤t.provider, ¤t.id)
279 && model != current
280 {
281 state.model = Some(model.clone());
282 }
283 result
284 }
285
286 pub async fn refresh_model_catalogs(
291 &self,
292 options: yapi_ai::model_catalog::RefreshOptions,
293 ) -> CatalogRefresh {
294 let _running = self.inner.catalog_refresh.lock().await;
295 let registry = self.registry();
296 let Some(store) = registry.models_store().cloned() else {
297 return CatalogRefresh::default();
298 };
299 let targets = registry.catalog_targets().await;
300 let refreshed = yapi_ai::model_catalog::refresh(&targets, &store, &options).await;
301 let mut lists = Vec::new();
302 for extension in &self.inner.extensions {
303 lists.extend(extension.refresh_models(®istry, &options).await);
304 }
305 let mut errors = refreshed.errors;
306 let mut slot = write(&self.inner.registry);
308 let mut next = (**slot).clone();
309 next.apply_catalogs(refreshed.models);
310 for (provider, list) in lists {
311 if !next.is_extension_provider(&provider) {
314 continue;
315 }
316 let applied = list.and_then(|update| match update {
317 ModelUpdate::Definitions(models) => next.replace_models(&provider, models),
318 ModelUpdate::Modified(models) => {
319 next.modify_models(&provider, models);
320 Ok(())
321 }
322 ModelUpdate::Availability(availability) => {
323 next.set_availability(&provider, availability);
324 Ok(())
325 }
326 });
327 if let Err(error) = applied
328 && !options.cancel.is_cancelled()
329 {
330 errors.push((provider, error));
331 }
332 }
333 *slot = Arc::new(next);
334 drop(slot);
335 CatalogRefresh {
336 errors,
337 aborted: refreshed.aborted || options.cancel.is_cancelled(),
338 }
339 }
340
341 pub fn with_registry<T>(&self, f: impl FnOnce(&ModelRegistry) -> T) -> T {
343 f(&read(&self.inner.registry))
344 }
345}