From 9c0c48c82c7a691846c7681f43c23b33efa982d5 Mon Sep 17 00:00:00 2001 From: Nenad Banfic Date: Tue, 24 Mar 2026 02:00:10 +0000 Subject: [PATCH 1/5] Rust sutff --- sdk/rust/src/catalog.rs | 7 +++--- sdk/rust/src/configuration.rs | 39 ++++++++++++----------------- sdk/rust/src/model.rs | 24 +++++++++--------- sdk/rust/src/model_variant.rs | 4 +-- sdk/rust/src/openai/audio_client.rs | 4 +-- sdk/rust/src/openai/chat_client.rs | 4 +-- 6 files changed, 37 insertions(+), 45 deletions(-) diff --git a/sdk/rust/src/catalog.rs b/sdk/rust/src/catalog.rs index 78485bff..4e5bf134 100644 --- a/sdk/rust/src/catalog.rs +++ b/sdk/rust/src/catalog.rs @@ -216,14 +216,13 @@ impl Catalog { for info in infos { let id = info.id.clone(); let alias = info.alias.clone(); - let variant = ModelVariant::new( + let variant = Arc::new(ModelVariant::new( info, Arc::clone(&self.core), Arc::clone(&self.model_load_manager), self.invalidator.clone(), - ); - let variant_arc = Arc::new(variant.clone()); - id_map.insert(id, variant_arc); + )); + id_map.insert(id, Arc::clone(&variant)); alias_map_build .entry(alias.clone()) diff --git a/sdk/rust/src/configuration.rs b/sdk/rust/src/configuration.rs index d23d5986..c1ec2964 100644 --- a/sdk/rust/src/configuration.rs +++ b/sdk/rust/src/configuration.rs @@ -183,31 +183,24 @@ impl Configuration { let mut params = HashMap::new(); params.insert("AppName".into(), app_name); - if let Some(v) = config.app_data_dir { - params.insert("AppDataDir".into(), v); - } - if let Some(v) = config.model_cache_dir { - params.insert("ModelCacheDir".into(), v); - } - if let Some(v) = config.logs_dir { - params.insert("LogsDir".into(), v); - } - if let Some(level) = config.log_level { - params.insert("LogLevel".into(), level.as_core_str().into()); - } - if let Some(v) = config.web_service_urls { - params.insert("WebServiceUrls".into(), v); - } - if let Some(v) = config.service_endpoint { - params.insert("WebServiceExternalUrl".into(), v); - } - if let Some(v) = config.library_path { - params.insert("FoundryLocalCorePath".into(), v); + let optional_fields = [ + ("AppDataDir", config.app_data_dir), + ("ModelCacheDir", config.model_cache_dir), + ("LogsDir", config.logs_dir), + ("LogLevel", config.log_level.map(|l| l.as_core_str().into())), + ("WebServiceUrls", config.web_service_urls), + ("WebServiceExternalUrl", config.service_endpoint), + ("FoundryLocalCorePath", config.library_path), + ]; + + for (key, value) in optional_fields { + if let Some(v) = value { + params.insert(key.into(), v); + } } + if let Some(extra) = config.additional_settings { - for (k, v) in extra { - params.insert(k, v); - } + params.extend(extra); } Ok((Self { params }, config.logger)) diff --git a/sdk/rust/src/model.rs b/sdk/rust/src/model.rs index 4a197e3f..72101079 100644 --- a/sdk/rust/src/model.rs +++ b/sdk/rust/src/model.rs @@ -3,7 +3,7 @@ use std::fmt; use std::path::PathBuf; -use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use crate::detail::core_interop::CoreInterop; @@ -19,7 +19,7 @@ use crate::openai::ChatClient; pub struct Model { alias: String, core: Arc, - variants: Vec, + variants: Vec>, selected_index: AtomicUsize, } @@ -29,7 +29,7 @@ impl Clone for Model { alias: self.alias.clone(), core: Arc::clone(&self.core), variants: self.variants.clone(), - selected_index: AtomicUsize::new(self.selected_index.load(Relaxed)), + selected_index: AtomicUsize::new(self.selected_index.load(Ordering::Acquire)), } } } @@ -40,7 +40,7 @@ impl fmt::Debug for Model { .field("alias", &self.alias()) .field("id", &self.id()) .field("variants_count", &self.variants.len()) - .field("selected_index", &self.selected_index.load(Relaxed)) + .field("selected_index", &self.selected_index.load(Ordering::Acquire)) .finish() } } @@ -57,21 +57,21 @@ impl Model { /// Add a variant. If the new variant is cached and the current selection /// is not, the new variant becomes the selected one. - pub(crate) fn add_variant(&mut self, variant: ModelVariant) { + pub(crate) fn add_variant(&mut self, variant: Arc) { self.variants.push(variant); let new_idx = self.variants.len() - 1; - let current = self.selected_index.load(Relaxed); + let current = self.selected_index.load(Ordering::Acquire); // Prefer a cached variant over a non-cached one. if self.variants[new_idx].info().cached && !self.variants[current].info().cached { - self.selected_index.store(new_idx, Relaxed); + self.selected_index.store(new_idx, Ordering::Release); } } /// Select a variant by its unique id. pub fn select_variant(&self, id: &str) -> Result<()> { if let Some(pos) = self.variants.iter().position(|v| v.id() == id) { - self.selected_index.store(pos, Relaxed); + self.selected_index.store(pos, Ordering::Release); return Ok(()); } let available: Vec = self.variants.iter().map(|v| v.id().to_string()).collect(); @@ -85,11 +85,11 @@ impl Model { /// Returns a reference to the currently selected variant. pub fn selected_variant(&self) -> &ModelVariant { - &self.variants[self.selected_index.load(Relaxed)] + &self.variants[self.selected_index.load(Ordering::Acquire)] } /// Returns all variants that belong to this model. - pub fn variants(&self) -> &[ModelVariant] { + pub fn variants(&self) -> &[Arc] { &self.variants } @@ -144,11 +144,11 @@ impl Model { /// Create a [`ChatClient`] bound to the selected variant. pub fn create_chat_client(&self) -> ChatClient { - ChatClient::new(self.id().to_string(), Arc::clone(&self.core)) + ChatClient::new(self.id(), Arc::clone(&self.core)) } /// Create an [`AudioClient`] bound to the selected variant. pub fn create_audio_client(&self) -> AudioClient { - AudioClient::new(self.id().to_string(), Arc::clone(&self.core)) + AudioClient::new(self.id(), Arc::clone(&self.core)) } } diff --git a/sdk/rust/src/model_variant.rs b/sdk/rust/src/model_variant.rs index c4be6822..760306f6 100644 --- a/sdk/rust/src/model_variant.rs +++ b/sdk/rust/src/model_variant.rs @@ -143,11 +143,11 @@ impl ModelVariant { /// Create a [`ChatClient`] bound to this variant. pub fn create_chat_client(&self) -> ChatClient { - ChatClient::new(self.info.id.clone(), Arc::clone(&self.core)) + ChatClient::new(&self.info.id, Arc::clone(&self.core)) } /// Create an [`AudioClient`] bound to this variant. pub fn create_audio_client(&self) -> AudioClient { - AudioClient::new(self.info.id.clone(), Arc::clone(&self.core)) + AudioClient::new(&self.info.id, Arc::clone(&self.core)) } } diff --git a/sdk/rust/src/openai/audio_client.rs b/sdk/rust/src/openai/audio_client.rs index da0f9f5b..0319da38 100644 --- a/sdk/rust/src/openai/audio_client.rs +++ b/sdk/rust/src/openai/audio_client.rs @@ -116,9 +116,9 @@ pub struct AudioClient { } impl AudioClient { - pub(crate) fn new(model_id: String, core: Arc) -> Self { + pub(crate) fn new(model_id: &str, core: Arc) -> Self { Self { - model_id, + model_id: model_id.to_owned(), core, settings: AudioClientSettings::default(), } diff --git a/sdk/rust/src/openai/chat_client.rs b/sdk/rust/src/openai/chat_client.rs index 62d0be5b..6597de82 100644 --- a/sdk/rust/src/openai/chat_client.rs +++ b/sdk/rust/src/openai/chat_client.rs @@ -132,9 +132,9 @@ pub struct ChatClient { } impl ChatClient { - pub(crate) fn new(model_id: String, core: Arc) -> Self { + pub(crate) fn new(model_id: &str, core: Arc) -> Self { Self { - model_id, + model_id: model_id.to_owned(), core, settings: ChatClientSettings::default(), } From 7a0bbe1e6dbd3ba2e39e8dc8cbd9330ae0495bbd Mon Sep 17 00:00:00 2001 From: Nenad Banfic Date: Fri, 27 Mar 2026 19:48:50 +0000 Subject: [PATCH 2/5] More String to &str --- sdk/rust/src/catalog.rs | 4 ++-- sdk/rust/src/model.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk/rust/src/catalog.rs b/sdk/rust/src/catalog.rs index 4e5bf134..16a59088 100644 --- a/sdk/rust/src/catalog.rs +++ b/sdk/rust/src/catalog.rs @@ -135,7 +135,7 @@ impl Catalog { self.update_models().await?; let s = self.lock_state()?; s.models_by_alias.get(alias).cloned().ok_or_else(|| { - let available: Vec<&String> = s.models_by_alias.keys().collect(); + let available: Vec<&str> = s.models_by_alias.keys().map(|k| k.as_str()).collect(); FoundryLocalError::ModelOperation { reason: format!("Unknown model alias '{alias}'. Available: {available:?}"), } @@ -152,7 +152,7 @@ impl Catalog { self.update_models().await?; let s = self.lock_state()?; s.variants_by_id.get(id).cloned().ok_or_else(|| { - let available: Vec<&String> = s.variants_by_id.keys().collect(); + let available: Vec<&str> = s.variants_by_id.keys().map(|k| k.as_str()).collect(); FoundryLocalError::ModelOperation { reason: format!("Unknown variant id '{id}'. Available: {available:?}"), } diff --git a/sdk/rust/src/model.rs b/sdk/rust/src/model.rs index 72101079..7d54d339 100644 --- a/sdk/rust/src/model.rs +++ b/sdk/rust/src/model.rs @@ -29,7 +29,7 @@ impl Clone for Model { alias: self.alias.clone(), core: Arc::clone(&self.core), variants: self.variants.clone(), - selected_index: AtomicUsize::new(self.selected_index.load(Ordering::Acquire)), + selected_index: AtomicUsize::new(self.selected_index.load(Ordering::Relaxed)), } } } @@ -40,7 +40,7 @@ impl fmt::Debug for Model { .field("alias", &self.alias()) .field("id", &self.id()) .field("variants_count", &self.variants.len()) - .field("selected_index", &self.selected_index.load(Ordering::Acquire)) + .field("selected_index", &self.selected_index.load(Ordering::Relaxed)) .finish() } } @@ -60,21 +60,21 @@ impl Model { pub(crate) fn add_variant(&mut self, variant: Arc) { self.variants.push(variant); let new_idx = self.variants.len() - 1; - let current = self.selected_index.load(Ordering::Acquire); + let current = self.selected_index.load(Ordering::Relaxed); // Prefer a cached variant over a non-cached one. if self.variants[new_idx].info().cached && !self.variants[current].info().cached { - self.selected_index.store(new_idx, Ordering::Release); + self.selected_index.store(new_idx, Ordering::Relaxed); } } /// Select a variant by its unique id. pub fn select_variant(&self, id: &str) -> Result<()> { if let Some(pos) = self.variants.iter().position(|v| v.id() == id) { - self.selected_index.store(pos, Ordering::Release); + self.selected_index.store(pos, Ordering::Relaxed); return Ok(()); } - let available: Vec = self.variants.iter().map(|v| v.id().to_string()).collect(); + let available: Vec<&str> = self.variants.iter().map(|v| v.id()).collect(); Err(FoundryLocalError::ModelOperation { reason: format!( "Variant '{id}' not found for model '{}'. Available: {available:?}", @@ -85,7 +85,7 @@ impl Model { /// Returns a reference to the currently selected variant. pub fn selected_variant(&self) -> &ModelVariant { - &self.variants[self.selected_index.load(Ordering::Acquire)] + &self.variants[self.selected_index.load(Ordering::Relaxed)] } /// Returns all variants that belong to this model. From 307efb0ffc1abd52fe275d4c28fe044690416419 Mon Sep 17 00:00:00 2001 From: Nenad Banfic Date: Fri, 27 Mar 2026 20:04:36 +0000 Subject: [PATCH 3/5] Some bugs --- sdk/rust/src/catalog.rs | 4 +- sdk/rust/src/detail/core_interop.rs | 47 +++++++++++++++-------- sdk/rust/src/detail/model_load_manager.rs | 34 ++++++++-------- sdk/rust/src/model.rs | 24 +++++++----- 4 files changed, 65 insertions(+), 44 deletions(-) diff --git a/sdk/rust/src/catalog.rs b/sdk/rust/src/catalog.rs index 16a59088..9e04c943 100644 --- a/sdk/rust/src/catalog.rs +++ b/sdk/rust/src/catalog.rs @@ -225,8 +225,8 @@ impl Catalog { id_map.insert(id, Arc::clone(&variant)); alias_map_build - .entry(alias.clone()) - .or_insert_with(|| Model::new(alias, Arc::clone(&self.core))) + .entry(alias) + .or_insert_with_key(|a| Model::new(a.clone(), Arc::clone(&self.core))) .add_variant(variant); } diff --git a/sdk/rust/src/detail/core_interop.rs b/sdk/rust/src/detail/core_interop.rs index e69a6e98..75146164 100644 --- a/sdk/rust/src/detail/core_interop.rs +++ b/sdk/rust/src/detail/core_interop.rs @@ -137,25 +137,42 @@ impl<'a> StreamingCallbackState<'a> { /// Append raw bytes, decode as much valid UTF-8 as possible, and forward /// complete text to the callback. Any trailing incomplete multi-byte - /// sequence is kept in the buffer for the next call. + /// sequence is kept in the buffer for the next call. Invalid byte + /// sequences are skipped to prevent the buffer from growing unboundedly. fn push(&mut self, bytes: &[u8]) { self.buf.extend_from_slice(bytes); - let valid_up_to = match std::str::from_utf8(&self.buf) { - Ok(s) => { - (self.callback)(s); - s.len() - } - Err(e) => { - let n = e.valid_up_to(); - if n > 0 { - // SAFETY: `valid_up_to` guarantees this prefix is valid UTF-8. - let valid = unsafe { std::str::from_utf8_unchecked(&self.buf[..n]) }; - (self.callback)(valid); + loop { + match std::str::from_utf8(&self.buf) { + Ok(s) => { + if !s.is_empty() { + (self.callback)(s); + } + self.buf.clear(); + break; + } + Err(e) => { + let n = e.valid_up_to(); + if n > 0 { + // SAFETY: `valid_up_to` guarantees this prefix is valid UTF-8. + let valid = unsafe { std::str::from_utf8_unchecked(&self.buf[..n]) }; + (self.callback)(valid); + } + match e.error_len() { + Some(err_len) => { + // Definite invalid sequence — skip past it and + // continue decoding the remainder. + self.buf.drain(..n + err_len); + } + None => { + // Incomplete multi-byte sequence at the end — + // keep it for the next push. + self.buf.drain(..n); + break; + } + } } - n } - }; - self.buf.drain(..valid_up_to); + } } /// Flush any remaining bytes as lossy UTF-8 (called once after the native diff --git a/sdk/rust/src/detail/model_load_manager.rs b/sdk/rust/src/detail/model_load_manager.rs index 41507cbd..57eb3cfb 100644 --- a/sdk/rust/src/detail/model_load_manager.rs +++ b/sdk/rust/src/detail/model_load_manager.rs @@ -34,12 +34,12 @@ impl ModelLoadManager { let encoded_id = urlencoding::encode(model_id); self.http_get(&format!("{base_url}/models/load/{encoded_id}")) .await?; - return Ok(()); + } else { + let params = json!({ "Params": { "Model": model_id } }); + self.core + .execute_command_async("load_model".into(), Some(params)) + .await?; } - let params = json!({ "Params": { "Model": model_id } }); - self.core - .execute_command_async("load_model".into(), Some(params)) - .await?; Ok(()) } @@ -47,14 +47,14 @@ impl ModelLoadManager { pub async fn unload(&self, model_id: &str) -> Result { if let Some(base_url) = &self.external_service_url { let encoded_id = urlencoding::encode(model_id); - return self - .http_get(&format!("{base_url}/models/unload/{encoded_id}")) - .await; + self.http_get(&format!("{base_url}/models/unload/{encoded_id}")) + .await + } else { + let params = json!({ "Params": { "Model": model_id } }); + self.core + .execute_command_async("unload_model".into(), Some(params)) + .await } - let params = json!({ "Params": { "Model": model_id } }); - self.core - .execute_command_async("unload_model".into(), Some(params)) - .await } /// Return the list of currently loaded model identifiers. @@ -67,11 +67,11 @@ impl ModelLoadManager { .await? }; - if raw.trim().is_empty() { - return Ok(Vec::new()); - } - - let ids: Vec = serde_json::from_str(&raw)?; + let ids: Vec = if raw.trim().is_empty() { + Vec::new() + } else { + serde_json::from_str(&raw)? + }; Ok(ids) } diff --git a/sdk/rust/src/model.rs b/sdk/rust/src/model.rs index 7d54d339..19dfc88e 100644 --- a/sdk/rust/src/model.rs +++ b/sdk/rust/src/model.rs @@ -70,17 +70,21 @@ impl Model { /// Select a variant by its unique id. pub fn select_variant(&self, id: &str) -> Result<()> { - if let Some(pos) = self.variants.iter().position(|v| v.id() == id) { - self.selected_index.store(pos, Ordering::Relaxed); - return Ok(()); + match self.variants.iter().position(|v| v.id() == id) { + Some(pos) => { + self.selected_index.store(pos, Ordering::Relaxed); + Ok(()) + } + None => { + let available: Vec<&str> = self.variants.iter().map(|v| v.id()).collect(); + Err(FoundryLocalError::ModelOperation { + reason: format!( + "Variant '{id}' not found for model '{}'. Available: {available:?}", + self.alias + ), + }) + } } - let available: Vec<&str> = self.variants.iter().map(|v| v.id()).collect(); - Err(FoundryLocalError::ModelOperation { - reason: format!( - "Variant '{id}' not found for model '{}'. Available: {available:?}", - self.alias - ), - }) } /// Returns a reference to the currently selected variant. From 257e58c690cffcbc77a566cc61187cf5bd189834 Mon Sep 17 00:00:00 2001 From: Nenad Banfic Date: Fri, 27 Mar 2026 22:09:27 +0000 Subject: [PATCH 4/5] changes --- sdk/rust/src/model.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdk/rust/src/model.rs b/sdk/rust/src/model.rs index 19dfc88e..a5a4732e 100644 --- a/sdk/rust/src/model.rs +++ b/sdk/rust/src/model.rs @@ -3,7 +3,7 @@ use std::fmt; use std::path::PathBuf; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use std::sync::Arc; use crate::detail::core_interop::CoreInterop; @@ -29,7 +29,7 @@ impl Clone for Model { alias: self.alias.clone(), core: Arc::clone(&self.core), variants: self.variants.clone(), - selected_index: AtomicUsize::new(self.selected_index.load(Ordering::Relaxed)), + selected_index: AtomicUsize::new(self.selected_index.load(Relaxed)), } } } @@ -40,7 +40,7 @@ impl fmt::Debug for Model { .field("alias", &self.alias()) .field("id", &self.id()) .field("variants_count", &self.variants.len()) - .field("selected_index", &self.selected_index.load(Ordering::Relaxed)) + .field("selected_index", &self.selected_index.load(Relaxed)) .finish() } } @@ -60,11 +60,11 @@ impl Model { pub(crate) fn add_variant(&mut self, variant: Arc) { self.variants.push(variant); let new_idx = self.variants.len() - 1; - let current = self.selected_index.load(Ordering::Relaxed); + let current = self.selected_index.load(Relaxed); // Prefer a cached variant over a non-cached one. if self.variants[new_idx].info().cached && !self.variants[current].info().cached { - self.selected_index.store(new_idx, Ordering::Relaxed); + self.selected_index.store(new_idx, Relaxed); } } @@ -72,7 +72,7 @@ impl Model { pub fn select_variant(&self, id: &str) -> Result<()> { match self.variants.iter().position(|v| v.id() == id) { Some(pos) => { - self.selected_index.store(pos, Ordering::Relaxed); + self.selected_index.store(pos, Relaxed); Ok(()) } None => { @@ -89,7 +89,7 @@ impl Model { /// Returns a reference to the currently selected variant. pub fn selected_variant(&self) -> &ModelVariant { - &self.variants[self.selected_index.load(Ordering::Relaxed)] + &self.variants[self.selected_index.load(Relaxed)] } /// Returns all variants that belong to this model. From 8de6fb6331a284c72f76c43b9fa20e884c7e9451 Mon Sep 17 00:00:00 2001 From: Nenad Banfic Date: Fri, 27 Mar 2026 22:39:10 +0000 Subject: [PATCH 5/5] Fix Copilot --- sdk/rust/docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/rust/docs/api.md b/sdk/rust/docs/api.md index bdc86974..278402fb 100644 --- a/sdk/rust/docs/api.md +++ b/sdk/rust/docs/api.md @@ -149,7 +149,7 @@ pub struct Model { /* private fields */ } |--------|-----------|-------------| | `alias` | `fn alias(&self) -> &str` | Alias shared by all variants. | | `id` | `fn id(&self) -> &str` | Unique identifier of the selected variant. | -| `variants` | `fn variants(&self) -> &[ModelVariant]` | All variants in this model. | +| `variants` | `fn variants(&self) -> &[Arc]` | All variants in this model. | | `selected_variant` | `fn selected_variant(&self) -> &ModelVariant` | Currently selected variant. | | `select_variant` | `fn select_variant(&self, id: &str) -> Result<(), FoundryLocalError>` | Select a variant by id. | | `is_cached` | `async fn is_cached(&self) -> Result` | Whether the selected variant is cached on disk. |