foundry_local_sdk/
catalog.rs1use std::sync::Arc;
8
9use crate::detail::api::Api;
10use crate::detail::manager::NativeManager;
11use crate::detail::model::Model;
12use crate::detail::native::NativeCatalog;
13use crate::detail::task::spawn_blocking;
14use crate::error::{FoundryLocalError, Result};
15
16pub struct Catalog {
18 native: NativeCatalog,
19 name: String,
20}
21
22impl Catalog {
23 pub(crate) fn new(
24 api: Arc<Api>,
25 ptr: *mut crate::detail::ffi::flCatalog,
26 manager: Arc<NativeManager>,
27 ) -> Result<Self> {
28 let native = NativeCatalog::new(api, ptr, manager);
29 let name = native.name().unwrap_or_else(|_| "default".into());
30 Ok(Self { native, name })
31 }
32
33 pub fn name(&self) -> &str {
35 &self.name
36 }
37
38 pub async fn update_models(&self) -> Result<()> {
44 Ok(())
45 }
46
47 pub async fn get_models(&self) -> Result<Vec<Arc<Model>>> {
49 let native = self.native.clone();
50 spawn_blocking(move || {
51 native
52 .get_models()?
53 .into_iter()
54 .map(|m| Model::from_group(&native.api, m).map(Arc::new))
55 .collect()
56 })
57 .await
58 }
59
60 pub async fn get_model(&self, alias: &str) -> Result<Arc<Model>> {
62 if alias.trim().is_empty() {
63 return Err(FoundryLocalError::Validation {
64 reason: "Model alias must be a non-empty string".into(),
65 });
66 }
67 let native = self.native.clone();
68 let alias = alias.to_owned();
69 spawn_blocking(move || match native.get_model(&alias)? {
70 Some(m) => Model::from_group(&native.api, m).map(Arc::new),
71 None => {
72 let available: Vec<String> = native
73 .get_models()
74 .ok()
75 .map(|models| {
76 models
77 .iter()
78 .filter_map(|m| {
79 m.info_ptr().ok().map(|info| unsafe {
80 crate::detail::api::cstr_to_string((native
81 .api
82 .model_api()
83 .Info_GetAlias)(
84 info
85 ))
86 .unwrap_or_default()
87 })
88 })
89 .collect()
90 })
91 .unwrap_or_default();
92 Err(FoundryLocalError::ModelOperation {
93 reason: format!("Unknown model alias '{alias}'. Available: {available:?}"),
94 })
95 }
96 })
97 .await
98 }
99
100 pub async fn get_model_variant(&self, id: &str) -> Result<Arc<Model>> {
106 if id.trim().is_empty() {
107 return Err(FoundryLocalError::Validation {
108 reason: "Variant id must be a non-empty string".into(),
109 });
110 }
111 let native = self.native.clone();
112 let id = id.to_owned();
113 spawn_blocking(move || match native.get_model_variant(&id)? {
114 Some(m) => Model::from_variant(&native.api, m).map(Arc::new),
115 None => Err(FoundryLocalError::ModelOperation {
116 reason: format!("Unknown variant id '{id}'."),
117 }),
118 })
119 .await
120 }
121
122 pub async fn get_cached_models(&self) -> Result<Vec<Arc<Model>>> {
124 let native = self.native.clone();
125 spawn_blocking(move || {
126 native
127 .get_cached_models()?
128 .into_iter()
129 .map(|m| Model::from_variant(&native.api, m).map(Arc::new))
130 .collect()
131 })
132 .await
133 }
134
135 pub async fn get_loaded_models(&self) -> Result<Vec<Arc<Model>>> {
137 let native = self.native.clone();
138 spawn_blocking(move || {
139 native
140 .get_loaded_models()?
141 .into_iter()
142 .map(|m| Model::from_variant(&native.api, m).map(Arc::new))
143 .collect()
144 })
145 .await
146 }
147
148 pub async fn get_model_versions(
152 &self,
153 model_alias: &str,
154 model_name: Option<&str>,
155 max_versions: u32,
156 ) -> Result<Vec<Arc<Model>>> {
157 if model_alias.trim().is_empty() {
158 return Err(FoundryLocalError::Validation {
159 reason: "Model alias must be a non-empty string".into(),
160 });
161 }
162 let max_versions =
163 i32::try_from(max_versions).map_err(|_| FoundryLocalError::Validation {
164 reason: "max_versions must not exceed i32::MAX".into(),
165 })?;
166 let native = self.native.clone();
167 let model_alias = model_alias.to_owned();
168 let model_name = model_name.map(str::to_owned);
169 spawn_blocking(move || {
170 native
171 .get_model_versions(&model_alias, model_name.as_deref(), max_versions)?
172 .into_iter()
173 .map(|m| Model::from_variant(&native.api, m).map(Arc::new))
174 .collect()
175 })
176 .await
177 }
178
179 pub async fn get_latest_version(&self, model_or_model_variant: &Model) -> Result<Arc<Model>> {
181 let native = self.native.clone();
182 let target = model_or_model_variant.selected_native().clone();
183 spawn_blocking(move || {
184 let latest = native.get_latest_version(&target)?;
185 Model::from_variant(&native.api, latest).map(Arc::new)
186 })
187 .await
188 }
189}