use std::fmt::Write as _;
use super::Agent;
use crate::channel::Channel;
use zeph_llm::provider::LlmProvider as _;
impl<C: Channel> Agent<C> {
pub(super) async fn handle_provider_command(&mut self, trimmed: &str) {
let arg = trimmed.strip_prefix("/provider").map_or("", str::trim);
match arg {
"" => self.handle_provider_list().await,
"status" => self.handle_provider_status().await,
name => self.handle_provider_switch(name).await,
}
}
async fn handle_provider_list(&mut self) {
let pool = &self.providers.provider_pool;
if pool.is_empty() {
let _ = self
.channel
.send("No providers configured in [[llm.providers]].")
.await;
return;
}
let current = self.provider.name().to_owned();
let mut lines = vec!["Configured providers:".to_string()];
for (i, entry) in pool.iter().enumerate() {
let name = entry.effective_name();
let model = entry.model.as_deref().unwrap_or("(default)");
let marker = if name == current { " (active)" } else { "" };
lines.push(format!(
" {}. {} [{}] model={}{}",
i + 1,
name,
entry.provider_type,
model,
marker
));
}
let _ = self.channel.send(&lines.join("\n")).await;
}
async fn handle_provider_status(&mut self) {
let mut out = String::from("Current provider:\n\n");
let _ = writeln!(out, "Name: {}", self.provider.name());
let _ = writeln!(out, "Model: {}", self.runtime.model_name);
if let Some(ref tx) = self.metrics.metrics_tx {
let m = tx.borrow();
let _ = writeln!(out, "API calls: {}", m.api_calls);
let _ = writeln!(
out,
"Tokens: {} prompt / {} completion",
m.prompt_tokens, m.completion_tokens
);
if m.cost_spent_cents > 0.0 {
let _ = writeln!(out, "Cost: ${:.4}", m.cost_spent_cents / 100.0);
}
}
let _ = self.channel.send(out.trim_end()).await;
}
async fn handle_provider_switch(&mut self, name: &str) {
let entry_clone = self
.providers
.provider_pool
.iter()
.find(|e| e.effective_name().eq_ignore_ascii_case(name))
.cloned();
let Some(entry) = entry_clone else {
let names: Vec<_> = self
.providers
.provider_pool
.iter()
.map(zeph_config::ProviderEntry::effective_name)
.collect();
let _ = self
.channel
.send(&format!(
"Unknown provider '{}'. Available: {}",
name,
names.join(", ")
))
.await;
return;
};
if self.provider.name().eq_ignore_ascii_case(name) {
let _ = self
.channel
.send(&format!(
"Provider '{}' is already active.",
self.provider.name()
))
.await;
return;
}
let Some(ref snapshot) = self.providers.provider_config_snapshot else {
let _ = self
.channel
.send("Provider switching unavailable (config snapshot missing).")
.await;
return;
};
match crate::bootstrap::build_provider_for_switch(&entry, snapshot) {
Ok(new_provider) => {
let model_name = entry
.model
.clone()
.unwrap_or_else(|| new_provider.name().to_owned());
self.provider = new_provider;
self.runtime.model_name = model_name.clone();
self.providers.cached_prompt_tokens = 0;
self.providers.server_compaction_active = entry.server_compaction;
self.metrics.extended_context = entry.enable_extended_context;
tracing::info!(
provider = self.provider.name(),
model = model_name,
"provider switched via /provider command"
);
if let Some(ref override_slot) = self.providers.provider_override
&& let Ok(mut slot) = override_slot.write()
{
*slot = None;
}
self.update_provider_instructions(&entry);
let _ = self
.channel
.send(&format!(
"Switched to provider: {} (model: {})",
self.provider.name(),
self.runtime.model_name
))
.await;
}
Err(e) => {
let _ = self
.channel
.send(&format!("Failed to switch to '{name}': {e}"))
.await;
}
}
}
fn update_provider_instructions(&mut self, entry: &zeph_config::ProviderEntry) {
let Some(ref mut reload_state) = self.instructions.reload_state else {
return;
};
reload_state.provider_kinds = vec![entry.provider_type];
if let Some(ref path) = entry.instruction_file
&& !reload_state.explicit_files.contains(path)
{
reload_state.explicit_files.push(path.clone());
}
let base_dir = reload_state.base_dir.clone();
let provider_kinds = reload_state.provider_kinds.clone();
let explicit_files = reload_state.explicit_files.clone();
let auto_detect = reload_state.auto_detect;
let new_blocks = crate::instructions::load_instructions(
&base_dir,
&provider_kinds,
&explicit_files,
auto_detect,
);
tracing::info!(
count = new_blocks.len(),
provider = ?entry.provider_type,
"reloaded instruction files after provider switch"
);
self.instructions.blocks = new_blocks;
}
}