From ad90d417ccea0cac1721adc965169c9c06a1498f Mon Sep 17 00:00:00 2001 From: reaster Date: Wed, 18 Mar 2026 01:42:09 +0100 Subject: [PATCH] fix(kernel): resolve "default" provider in fallback_models before driver init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fallback model loop passed `provider = "default"` verbatim to `create_driver()`, which only recognises real provider names (ollama, openai, anthropic, …). The primary model overlay at spawn_agent() already resolves "default" → kernel config, but fallback_models was skipped, causing every bundled agent with a "default" fallback to log: Fallback driver 'default' failed to init: Unknown provider 'default' This meant agents had zero fallback drivers, silently degrading resilience for anyone whose config.toml sets a non-standard default provider (e.g. ollama pointing at a local proxy). Changes: - Mirror the primary-model overlay logic for fallback entries: resolve provider, model, api_key_env, and base_url from `config.default_model` when the fallback specifies "default" or empty. - Inherit `base_url` from default_model before falling back to `lookup_provider_url()`, so custom endpoints propagate correctly. - Use resolved values in `strip_provider_prefix()` and warn messages. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/openfang-kernel/src/kernel.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/openfang-kernel/src/kernel.rs b/crates/openfang-kernel/src/kernel.rs index 5e582d048..a9ad884b7 100644 --- a/crates/openfang-kernel/src/kernel.rs +++ b/crates/openfang-kernel/src/kernel.rs @@ -4685,26 +4685,44 @@ impl OpenFangKernel { String, )> = vec![(primary.clone(), String::new())]; for fb in &manifest.fallback_models { + // Resolve "default" provider/model to the kernel's configured defaults, + // mirroring the overlay logic for the primary model. + let dm = &self.config.default_model; + let fb_provider = if fb.provider.is_empty() || fb.provider == "default" { + dm.provider.clone() + } else { + fb.provider.clone() + }; + let fb_model_name = if fb.model.is_empty() || fb.model == "default" { + dm.model.clone() + } else { + fb.model.clone() + }; + let _ = &fb_model_name; // used below in strip_provider_prefix + let fb_api_key = if let Some(env) = &fb.api_key_env { std::env::var(env).ok() + } else if fb_provider == dm.provider && !dm.api_key_env.is_empty() { + std::env::var(&dm.api_key_env).ok() } else { // Resolve using provider_api_keys / convention for custom providers - let env_var = self.config.resolve_api_key_env(&fb.provider); + let env_var = self.config.resolve_api_key_env(&fb_provider); std::env::var(&env_var).ok() }; let config = DriverConfig { - provider: fb.provider.clone(), + provider: fb_provider.clone(), api_key: fb_api_key, base_url: fb .base_url .clone() - .or_else(|| self.lookup_provider_url(&fb.provider)), + .or_else(|| dm.base_url.clone()) + .or_else(|| self.lookup_provider_url(&fb_provider)), skip_permissions: true, }; match drivers::create_driver(&config) { - Ok(d) => chain.push((d, strip_provider_prefix(&fb.model, &fb.provider))), + Ok(d) => chain.push((d, strip_provider_prefix(&fb_model_name, &fb_provider))), Err(e) => { - warn!("Fallback driver '{}' failed to init: {e}", fb.provider); + warn!("Fallback driver '{}' failed to init: {e}", fb_provider); } } }