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:
parent
d64a6e9794
commit
bc7316c411
5 changed files with 251 additions and 40 deletions
|
|
@ -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
124
src/cli/commands.rs
Normal 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
5
src/cli/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
//! CLI module for command-line interface
|
||||
|
||||
pub mod commands;
|
||||
|
||||
pub use commands::{run_model_command, ModelListReport, ModelReport};
|
||||
|
|
@ -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() }
|
||||
|
|
|
|||
59
src/main.rs
59
src/main.rs
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue