From c4be2b8ad09e86314471231b77630c174592a207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95mir=20=D0=92=2E?= <224896369+emixor@users.noreply.github.com> Date: Mon, 18 May 2026 19:04:41 +0000 Subject: [PATCH 1/6] fix(providers): remap abstract tier models for custom cloud slugs --- src/openhuman/inference/provider/factory.rs | 36 +++++++++++++++++- .../inference/provider/factory_test.rs | 37 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/openhuman/inference/provider/factory.rs b/src/openhuman/inference/provider/factory.rs index 6cda94b031..b5fc4877cb 100644 --- a/src/openhuman/inference/provider/factory.rs +++ b/src/openhuman/inference/provider/factory.rs @@ -32,6 +32,13 @@ pub const PROVIDER_OPENHUMAN: &str = "openhuman"; /// Prefix for Ollama-local providers: `"ollama:"`. pub const OLLAMA_PROVIDER_PREFIX: &str = "ollama:"; +fn is_abstract_tier_model(model: &str) -> bool { + matches!( + model.trim(), + "reasoning-v1" | "reasoning-quick-v1" | "agentic-v1" | "coding-v1" | "summarization-v1" + ) +} + /// Auth-profile storage key for a slug-keyed provider. /// /// New writes use `"provider:"`. Lookups also try the bare `` @@ -316,12 +323,39 @@ fn make_cloud_provider_by_slug( // Resolve effective model: use provided model if non-empty, else fall back // to the entry's legacy default_model (if any), else empty → error. - let effective_model = if model.trim().is_empty() { + let mut effective_model = if model.trim().is_empty() { entry.default_model.clone().unwrap_or_default() } else { model.to_string() }; + if entry.auth_style != AuthStyle::OpenhumanJwt && is_abstract_tier_model(&effective_model) { + if let Some(default_model) = entry + .default_model + .as_deref() + .map(str::trim) + .filter(|m| !m.is_empty() && !is_abstract_tier_model(m)) + { + log::info!( + "[providers][chat-factory] role={} slug={} remapping abstract model {} -> {}", + role, + slug, + effective_model, + default_model + ); + effective_model = default_model.to_string(); + } else { + anyhow::bail!( + "[chat-factory] model '{}' is an abstract tier for role '{}', \ + but cloud provider slug '{}' has no concrete default_model configured. \ + Set cloud_providers[].default_model to a provider-native model id (e.g. deepseek-v4-pro).", + effective_model, + role, + slug + ); + } + } + log::info!( "[providers][chat-factory] role={} slug={} model={} endpoint_host={}", role, diff --git a/src/openhuman/inference/provider/factory_test.rs b/src/openhuman/inference/provider/factory_test.rs index 201b1f4acc..35fa70cf63 100644 --- a/src/openhuman/inference/provider/factory_test.rs +++ b/src/openhuman/inference/provider/factory_test.rs @@ -114,6 +114,43 @@ fn openrouter_slug_model() { assert_eq!(model, "meta-llama/llama-3.1-8b"); } +#[test] +fn custom_provider_remaps_abstract_tier_to_concrete_default_model() { + let mut config = Config::default(); + config.cloud_providers.push(CloudProviderCreds { + id: "p_ds".to_string(), + slug: "deepseek".to_string(), + label: "DeepSeek".to_string(), + endpoint: "https://api.deepseek.com/v1".to_string(), + auth_style: AuthStyle::Bearer, + default_model: Some("deepseek-v4-pro".to_string()), + ..Default::default() + }); + + let (_, model) = + create_chat_provider_from_string("reasoning", "deepseek:reasoning-v1", &config) + .expect("abstract tier should remap to concrete default model"); + assert_eq!(model, "deepseek-v4-pro"); +} + +#[test] +fn custom_provider_rejects_abstract_tier_without_concrete_default_model() { + let mut config = Config::default(); + config.cloud_providers.push(CloudProviderCreds { + id: "p_ds".to_string(), + slug: "deepseek".to_string(), + label: "DeepSeek".to_string(), + endpoint: "https://api.deepseek.com/v1".to_string(), + auth_style: AuthStyle::Bearer, + default_model: None, + ..Default::default() + }); + + let err = create_chat_provider_from_string("reasoning", "deepseek:reasoning-v1", &config) + .expect_err("abstract tier without concrete provider default should fail"); + assert!(err.to_string().contains("abstract tier")); +} + #[test] fn ollama_prefix() { let config = Config::default(); From 9704272c0a498ae33639f72c3a19e3aa173bee51 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 19 May 2026 20:08:19 -0700 Subject: [PATCH 2/6] refactor(inference): use canonical tier constants in abstract-tier check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses @coderabbitai nitpick on factory.rs:35-40 — use crate::openhuman::config::MODEL_* constants instead of duplicated literals so abstract-tier detection follows tier renames automatically. Also fixes the new failing-default test: Box doesn't implement Debug, so `.expect_err(..)` failed to compile. Replace with an explicit match so the test builds (was breaking the Rust Core Tests CI job on this PR). --- src/openhuman/inference/provider/factory.rs | 16 ++++++++++++---- src/openhuman/inference/provider/factory_test.rs | 9 +++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/openhuman/inference/provider/factory.rs b/src/openhuman/inference/provider/factory.rs index 20e0655375..39fb671fa1 100644 --- a/src/openhuman/inference/provider/factory.rs +++ b/src/openhuman/inference/provider/factory.rs @@ -36,10 +36,18 @@ pub const PROVIDER_OPENHUMAN: &str = "openhuman"; pub const OLLAMA_PROVIDER_PREFIX: &str = "ollama:"; fn is_abstract_tier_model(model: &str) -> bool { - matches!( - model.trim(), - "reasoning-v1" | "reasoning-quick-v1" | "agentic-v1" | "coding-v1" | "summarization-v1" - ) + use crate::openhuman::config::{ + MODEL_AGENTIC_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, + }; + // No dedicated constant for the summarization tier yet; keep the literal + // in sync with the tier name used by the summarizer sub-agent. + const MODEL_SUMMARIZATION_V1: &str = "summarization-v1"; + let trimmed = model.trim(); + trimmed == MODEL_REASONING_V1 + || trimmed == MODEL_REASONING_QUICK_V1 + || trimmed == MODEL_AGENTIC_V1 + || trimmed == MODEL_CODING_V1 + || trimmed == MODEL_SUMMARIZATION_V1 } /// Auth-profile storage key for a slug-keyed provider. diff --git a/src/openhuman/inference/provider/factory_test.rs b/src/openhuman/inference/provider/factory_test.rs index ad42e4e965..e039789b7a 100644 --- a/src/openhuman/inference/provider/factory_test.rs +++ b/src/openhuman/inference/provider/factory_test.rs @@ -146,8 +146,13 @@ fn custom_provider_rejects_abstract_tier_without_concrete_default_model() { ..Default::default() }); - let err = create_chat_provider_from_string("reasoning", "deepseek:reasoning-v1", &config) - .expect_err("abstract tier without concrete provider default should fail"); + // Can't use `.expect_err(..)` here because `Box` doesn't + // implement `Debug`, so the success arm has no Debug to print. + let err = match create_chat_provider_from_string("reasoning", "deepseek:reasoning-v1", &config) + { + Ok(_) => panic!("abstract tier without concrete provider default should fail"), + Err(e) => e, + }; assert!(err.to_string().contains("abstract tier")); } From 0c9afd0a08091b203d18ebc0869878ea55c5edc2 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 19 May 2026 21:21:46 -0700 Subject: [PATCH 3/6] chore(tauri): bump tauri-cef submodule pin to c90c8a330 Bumps vendored tauri-cef submodule from 4cabccfa8 to c90c8a330 (latest on feat/cef). Picks up: - fix(cef): popup blank-page reload guard skip - fix(cef): move blank reload guard onto UI thread - AppImage fixes (glibc, NSS libs, ld-linux bundling) cargo check passes on both core and tauri shell. --- app/src-tauri/vendor/tauri-cef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src-tauri/vendor/tauri-cef b/app/src-tauri/vendor/tauri-cef index 4cabccfa82..c90c8a3300 160000 --- a/app/src-tauri/vendor/tauri-cef +++ b/app/src-tauri/vendor/tauri-cef @@ -1 +1 @@ -Subproject commit 4cabccfa82c53e5f9d8409894cc47a648057c90e +Subproject commit c90c8a330056286e7c0d05439ae3d4527fa4fafe From 6fc71c555449335f25103c40ac8630694d4e0a54 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 19 May 2026 21:26:43 -0700 Subject: [PATCH 4/6] Revert "chore(tauri): bump tauri-cef submodule pin to c90c8a330" This reverts commit 0c9afd0a08091b203d18ebc0869878ea55c5edc2. --- app/src-tauri/vendor/tauri-cef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src-tauri/vendor/tauri-cef b/app/src-tauri/vendor/tauri-cef index c90c8a3300..4cabccfa82 160000 --- a/app/src-tauri/vendor/tauri-cef +++ b/app/src-tauri/vendor/tauri-cef @@ -1 +1 @@ -Subproject commit c90c8a330056286e7c0d05439ae3d4527fa4fafe +Subproject commit 4cabccfa82c53e5f9d8409894cc47a648057c90e From 4534ae38f4c0789d522117d85d5cfb1206a2d47a Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 19 May 2026 21:33:02 -0700 Subject: [PATCH 5/6] chore(tauri): update tauri-cef expected SHA to c90c8a330 --- .github/tauri-cef-expected-sha | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/tauri-cef-expected-sha b/.github/tauri-cef-expected-sha index 1b3addaee1..cab89c30c7 100644 --- a/.github/tauri-cef-expected-sha +++ b/.github/tauri-cef-expected-sha @@ -1 +1 @@ -e22ec719034fdac3994c42a3c040fafa10672219 +c90c8a330056286e7c0d05439ae3d4527fa4fafe \ No newline at end of file From 4071771c61ab6697b4f3f6e27184074c6f73d09a Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 19 May 2026 21:39:58 -0700 Subject: [PATCH 6/6] udpate --- app/src-tauri/vendor/tauri-cef | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src-tauri/vendor/tauri-cef b/app/src-tauri/vendor/tauri-cef index 4cabccfa82..c90c8a3300 160000 --- a/app/src-tauri/vendor/tauri-cef +++ b/app/src-tauri/vendor/tauri-cef @@ -1 +1 @@ -Subproject commit 4cabccfa82c53e5f9d8409894cc47a648057c90e +Subproject commit c90c8a330056286e7c0d05439ae3d4527fa4fafe