Watch
1
0
Fork
You've already forked souveraine
0

fix: resolve compilation errors in ModelRouter + Bifrost integration

- Fixed cli/commands.rs config path resolution (removed non-existent get_config_path())
- Removed .await from ConsciousnessConfig::load() (method is synchronous)
- Fixed command routing in main.rs (Commands::Model vs Commands::Models)
- Removed dead run_models() function
- Dereferenced json/verbose bools in command handler
This commit is contained in:
Fimeg 2026-05-07 15:49:58 -04:00
commit bc7316c411
5 changed files with 251 additions and 40 deletions

View file

@ -3,6 +3,7 @@ use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{debug, info};
use crate::bridge::bifrost::BifrostClient;
use crate::core::config::{ModelConfig, TaskType};
/// Context pressure — how full the context window is
@ -78,6 +79,9 @@ pub struct ModelRouter {
configs: HashMap<String, ModelConfig>,
current_usage: Arc<RwLock<TokenUsage>>,
token_counter: TokenCounter,
bifrost_client: Option<BifrostClient>,
bifrost_models: Vec<String>,
selected_model: String,
}
impl ModelRouter {
@ -94,9 +98,22 @@ impl ModelRouter {
configs,
current_usage: Arc::new(RwLock::new(TokenUsage::default())),
token_counter: TokenCounter::new(),
bifrost_client: None,
bifrost_models: Vec::new(),
selected_model: String::new(),
}
}
/// Create a ModelRouter with Bifrost client for dynamic model discovery
pub fn with_bifrost(
configs: HashMap<String, ModelConfig>,
bifrost_client: BifrostClient,
) -> Self {
let mut router = Self::new(configs);
router.bifrost_client = Some(bifrost_client);
router
}
/// Count tokens in text using real tiktoken
pub fn count_tokens(&self, text: &str) -> usize {
self.token_counter.count(text)
@ -184,6 +201,84 @@ impl ModelRouter {
providers.dedup();
providers
}
/// Fetch models from Bifrost dynamically
pub async fn fetch_bifrost_models(&mut self) -> anyhow::Result<Vec<String>> {
if let Some(client) = &self.bifrost_client {
match client.list_models().await {
Ok(models) => {
self.bifrost_models = models.clone();
info!("🌐 Fetched {} models from Bifrost ", models.len());
Ok(models)
}
Err(e) => {
tracing::warn!("Failed to fetch models from Bifrost: {}", e);
Ok(vec![])
}
}
} else {
Ok(vec![])
}
}
/// Get all models (Bifrost-discovered + configured)
pub fn all_models(&self) -> Vec<String> {
let mut models = self.bifrost_models.clone();
for name in self.configs.keys() {
if !models.contains(name) {
models.push(name.clone());
}
}
models
}
/// Set the selected model
pub fn set_model(&mut self, name: &str) -> anyhow::Result<()> {
if !self.all_models().contains(&name.to_string()) {
anyhow::bail!("Model '{}' not found. Available: {:?}", name, self.all_models());
}
self.selected_model = name.to_string();
info!("🎯 Selected model: {}", name);
Ok(())
}
/// Get current selected model
pub fn current_model(&self) -> &str {
if self.selected_model.is_empty() {
// Return first configured model or empty string
self.configs.keys().next().map(|s| s.as_str()).unwrap_or("")
} else {
&self.selected_model
}
}
/// Check if model is from Bifrost
pub fn is_bifrost_model(&self, name: &str) -> bool {
self.bifrost_models.contains(&name.to_string())
}
/// Get model info for display
pub fn model_info(&self, name: &str) -> Option<ModelInfo> {
self.configs.get(name).map(|cfg| ModelInfo {
name: name.to_string(),
provider: cfg.provider.clone(),
context_limit: cfg.context_limit,
output_limit: cfg.output_limit,
preferred_for: cfg.preferred_for.clone(),
from_bifrost: self.is_bifrost_model(name),
})
}
}
/// Model information for display
#[derive(Debug, Clone)]
pub struct ModelInfo {
pub name: String,
pub provider: String,
pub context_limit: usize,
pub output_limit: usize,
pub preferred_for: Vec<TaskType>,
pub from_bifrost: bool,
}
#[cfg(test)]

124
src/cli/commands.rs Normal file
View file

@ -0,0 +1,124 @@
//! CLI command implementations
use anyhow::Result;
use serde::Serialize;
use std::path::PathBuf;
/// Report for model list (JSON output)
#[derive(Debug, Serialize)]
pub struct ModelListReport {
pub selected: String,
pub bifrost_discovered: Vec<String>,
pub configured: Vec<String>,
pub all_models: Vec<ModelReport>,
}
#[derive(Debug, Serialize)]
pub struct ModelReport {
pub name: String,
pub provider: String,
pub context_limit: usize,
pub output_limit: usize,
pub from_bifrost: bool,
}
/// List or set models
pub async fn run_model_command(
model_name: Option<&str>,
emit_json: bool,
verbose: bool,
) -> Result<()> {
use crate::bridge::bifrost::BifrostClient;
use crate::core::config::ConsciousnessConfig;
// Load config
let config_path = std::env::current_dir()
.map(|d| d.join("souveraine.toml"))
.unwrap_or_else(|_| PathBuf::from("souveraine.toml"));
let config = ConsciousnessConfig::load(&config_path)?;
// Create Bifrost client
let bifrost = BifrostClient::new(
&config.bifrost.base_url,
&config.bifrost.api_key,
&config.bifrost.virtual_key,
&config.bifrost.primary_model,
);
// Fetch models from Bifrost
let bifrost_models = bifrost.list_models().await.unwrap_or_default();
// Merge with configured models
let mut all_models = bifrost_models.clone();
for name in config.models.keys() {
if !all_models.contains(name) {
all_models.push(name.clone());
}
}
// Handle model selection
if let Some(name) = model_name {
if !all_models.contains(&name.to_string()) {
anyhow::bail!(
"Model '{}' not found. Available: {:?}",
name,
all_models
);
}
// Update config and save
let mut new_config = config.clone();
new_config.bifrost.primary_model = name.to_string();
new_config.save(&config_path)?;
println!("Selected model: {}", name);
return Ok(());
}
// List models
if emit_json {
let report = ModelListReport {
selected: config.bifrost.primary_model.clone(),
bifrost_discovered: bifrost_models.clone(),
configured: config.models.keys().cloned().collect(),
all_models: all_models
.iter()
.map(|name| {
let cfg = config.models.get(name);
ModelReport {
name: name.clone(),
provider: cfg
.map(|c| c.provider.clone())
.unwrap_or_else(|| "bifrost".to_string()),
context_limit: cfg
.map(|c| c.context_limit)
.unwrap_or(128_000),
output_limit: cfg
.map(|c| c.output_limit)
.unwrap_or(8_192),
from_bifrost: bifrost_models.contains(name),
}
})
.collect(),
};
println!("{}", serde_json::to_string_pretty(&report)?);
} else {
if verbose {
println!("Selected: {}", config.bifrost.primary_model);
println!("Bifrost discovered: {}", bifrost_models.len());
println!("Configured models: {}", config.models.len());
println!("Total available: {}", all_models.len());
println!();
}
for model in all_models {
let marker = if bifrost_models.contains(&model) {
""
} else {
"⚙️"
};
println!("{} {}", marker, model);
}
}
Ok(())
}

5
src/cli/mod.rs Normal file
View file

@ -0,0 +1,5 @@
//! CLI module for command-line interface
pub mod commands;
pub use commands::{run_model_command, ModelListReport, ModelReport};

View file

@ -121,7 +121,7 @@ pub struct BifrostConfig {
pub api_key: String,
/// Virtual key for x-bf-vk header (required by some providers)
#[serde(default)]
#[serde(default = "default_bifrost_virtual_key")]
pub virtual_key: String,
/// Default model for conversation
@ -148,7 +148,7 @@ impl Default for BifrostConfig {
Self {
base_url: default_bifrost_url(),
api_key: default_bifrost_key(),
virtual_key: String::new(),
virtual_key: default_bifrost_virtual_key(),
primary_model: default_primary_model(),
models: HashMap::new(),
}
@ -480,6 +480,10 @@ fn default_server_url() -> String { "http://127.0.0.1:8484".to_string() }
fn default_bifrost_key() -> String {
std::env::var("BIFROST_KEY").unwrap_or_else(|_| "sk-bf-ae0d5801-9936-4fa9-ac9e-e956ffce6cfa".to_string())
}
fn default_bifrost_virtual_key() -> String {
std::env::var("BIFROST_VIRTUAL_KEY").unwrap_or_else(|_| String::new())
}
fn default_primary_model() -> String { "fireworks/accounts/fireworks/routers/kimi-k2p5-turbo".to_string() }
fn default_bandwidth_high() -> BandwidthClass { BandwidthClass::High }
fn default_presence_breathing() -> String { "breathing_color".to_string() }

View file

@ -6,6 +6,7 @@ use tracing::{info, warn, debug};
mod api;
mod backend;
mod bridge;
mod cli;
mod core;
mod harness;
mod interface;
@ -23,7 +24,9 @@ const CONFIG_TEMPLATE: &str = r##"# Souveraine — The world where your agents l
[bifrost]
base_url = "http://10.10.20.120:3360"
primary_model = "kimi-k2.5-turbo"
# Bearer token for auth (env: BIFROST_KEY)
api_key = ""
# Virtual key for x-bf-vk header, required by some providers (env: BIFROST_VIRTUAL_KEY)
virtual_key = ""
[server]
@ -141,9 +144,21 @@ enum Commands {
#[command(long_about = "Show every configured agent — who they are, which model they speak through, what triggers know them.")]
Agents,
/// List the voices available through Bifrost
#[command(long_about = "Query Bifrost for every model it can reach. Each is a possible voice the being can speak through.")]
Models,
/// List or set models
#[command(long_about = "List all available models from Bifrost and configured models. Or set a specific model as primary.")]
Model {
/// Model name to set as primary (omit to list all models)
#[arg(short, long)]
model: Option<String>,
/// Output as JSON
#[arg(long)]
json: bool,
/// Verbose output
#[arg(short, long)]
verbose: bool,
},
/// Show the world's current state
#[command(long_about = "Display Souveraine's running configuration and the state of every subsystem.")]
@ -212,7 +227,9 @@ async fn main() -> anyhow::Result<()> {
Commands::Tui => run_tui(config.clone(), cli.agent.clone()).await?,
Commands::Chat { message } => run_chat(config, cli.agent, message.clone(), cli.json, cli.quiet, cli.local).await?,
Commands::Agents => run_agents(config, cli.json, cli.local).await?,
Commands::Models => run_models(config, cli.json).await?,
Commands::Model { model, json, verbose } => {
cli::run_model_command(model.as_deref(), *json, *verbose).await?
}
Commands::Status => run_status(config, cli.json).await?,
Commands::Server { bind, port } => run_server(bind.clone(), *port, config).await?,
Commands::Init | Commands::Completions { .. } => unreachable!(),
@ -425,40 +442,6 @@ async fn run_agents(
Ok(())
}
async fn run_models(config: Arc<RwLock<ConsciousnessConfig>>, json: bool) -> anyhow::Result<()> {
let cfg = config.read().await;
let bifrost = bridge::BifrostClient::new(
&cfg.bifrost.base_url,
&cfg.bifrost.api_key,
&cfg.bifrost.virtual_key,
&cfg.bifrost.primary_model,
);
drop(cfg);
match bifrost.list_models().await {
Ok(models) => {
if json {
println!("{}", serde_json::to_string_pretty(&serde_json::json!({"models": models}))?);
} else {
println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!(" Voices ({} available)", models.len());
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
for m in models {
println!(" {}", m);
}
println!();
}
}
Err(e) => {
if json {
println!(r#"{{"error":"{}"}}"#, e.to_string().replace('"', r#"\""#));
} else {
eprintln!("Failed to fetch models: {}", e);
}
}
}
Ok(())
}
async fn run_status(config: Arc<RwLock<ConsciousnessConfig>>, json: bool) -> anyhow::Result<()> {
let cfg = config.read().await;