use crate::config::Config;
use crate::session::chat::format_number;
use crate::session::chat::session::ChatSession;
use crate::session::{layers::layer_trait::Layer, layers::GenericLayer};
use anyhow::Result;
use colored::Colorize;
pub async fn execute_command_layer(
command_name: &str,
provided_input: &str,
chat_session: &mut ChatSession,
config: &Config,
role: &str,
operation_cancelled: tokio::sync::watch::Receiver<bool>,
) -> Result<String> {
let (_, _, _, commands_config, _) = config.get_role_config(role);
let command_config = commands_config
.and_then(|commands| commands.iter().find(|cmd| cmd.name == command_name))
.ok_or_else(|| anyhow::anyhow!("Command '{}' not found in configuration", command_name))?;
println!(
"{} {}",
"Executing command:".bright_cyan(),
command_name.bright_yellow()
);
if let Some(session_file) = &chat_session.session.session_file {
let log_entry = serde_json::json!({
"type": "COMMAND_EXEC",
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"command": command_name,
"role": role,
"config": {
"model": command_config.get_effective_model(&chat_session.session.info.model),
"temperature": command_config.temperature,
"input_mode": format!("{:?}", command_config.input_mode),
"mcp_enabled": !command_config.mcp.server_refs.is_empty()
}
});
let _ = crate::session::append_to_session_file(
session_file,
&serde_json::to_string(&log_entry)?,
);
}
let mut processed_config = command_config.clone();
if let Some(ref system_prompt) = processed_config.system_prompt {
let current_dir = crate::mcp::get_thread_working_directory();
let processed = crate::session::helper_functions::process_placeholders_async(
system_prompt,
¤t_dir,
)
.await;
processed_config.processed_system_prompt = Some(processed);
}
let command_layer = GenericLayer::new(processed_config);
let processed_input = match command_config.input_mode {
crate::session::layers::layer_trait::InputMode::Last => {
command_layer.prepare_input(provided_input, &chat_session.session)
}
crate::session::layers::layer_trait::InputMode::All => {
command_layer.prepare_input(provided_input, &chat_session.session)
}
crate::session::layers::layer_trait::InputMode::Summary => {
command_layer.prepare_input(provided_input, &chat_session.session)
}
};
if let Some(session_file) = &chat_session.session.session_file {
let log_entry = serde_json::json!({
"type": "COMMAND_INPUT",
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"command": command_name,
"input_length": processed_input.len(),
"input_mode": format!("{:?}", command_config.input_mode)
});
let _ = crate::session::append_to_session_file(
session_file,
&serde_json::to_string(&log_entry)?,
);
}
let result = command_layer
.process(
&processed_input,
&chat_session.session,
config,
operation_cancelled,
)
.await?;
if let Some(session_file) = &chat_session.session.session_file {
let log_entry = serde_json::json!({
"type": "COMMAND_RESULT",
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"command": command_name,
"output_length": result.outputs.iter().map(|s| s.len()).sum::<usize>(),
"usage": result.token_usage
});
let _ = crate::session::append_to_session_file(
session_file,
&serde_json::to_string(&log_entry)?,
);
}
if let Some(usage) = &result.token_usage {
let effective_model = command_config.get_effective_model(&chat_session.session.info.model);
let cost = usage.cost.unwrap_or(0.0);
chat_session.session.add_layer_stats(
&format!("command:{command_name}"),
&effective_model,
usage.input_tokens,
usage.output_tokens,
cost,
);
let _ = chat_session.save();
let _ = chat_session.save();
println!(
"{} {} input, {} completion tokens",
"Command usage:".bright_blue(),
format_number(usage.input_tokens).bright_green(),
format_number(usage.output_tokens).bright_green()
);
if cost > 0.0 {
println!(
"{} ${:.5}",
"Command cost:".bright_blue(),
cost.to_string().bright_magenta()
);
}
}
use crate::session::layers::layer_trait::OutputMode;
match command_config.output_mode {
OutputMode::None => {
println!(
"{}",
"Output mode: none (command output only)".bright_cyan()
);
}
OutputMode::Append => {
println!(
"{}",
"Output mode: append (adding to session)".bright_cyan()
);
for output_text in &result.outputs {
chat_session
.session
.add_message(command_config.output_role.as_str(), output_text);
}
if let Some(session_file) = &chat_session.session.session_file {
let log_entry = serde_json::json!({
"type": "OUTPUT_MODE_APPEND",
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"command": command_name,
"content_length": result.outputs.iter().map(|s| s.len()).sum::<usize>()
});
let _ = crate::session::append_to_session_file(
session_file,
&serde_json::to_string(&log_entry)?,
);
}
let _ = chat_session.save();
}
OutputMode::Replace => {
println!(
"{}",
"Output mode: replace (replacing session content)".bright_cyan()
);
if let Some(session_file) = &chat_session.session.session_file {
let log_entry = serde_json::json!({
"type": "OUTPUT_MODE_REPLACE",
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"command": command_name,
"previous_message_count": chat_session.session.messages.len(),
"content_length": result.outputs.iter().map(|s| s.len()).sum::<usize>()
});
let _ = crate::session::append_to_session_file(
session_file,
&serde_json::to_string(&log_entry)?,
);
}
let system_message = chat_session
.session
.messages
.iter()
.find(|m| m.role == "system")
.cloned();
chat_session.session.messages.clear();
let mut final_messages = Vec::new();
if let Some(sys_msg) = system_message {
final_messages.push(sys_msg);
}
let current_dir = crate::mcp::get_thread_working_directory();
if let Ok(initial_messages) =
crate::session::chat::session::get_initial_messages(config, role, ¤t_dir)
.await
{
final_messages.extend(initial_messages);
}
for output_text in &result.outputs {
let output_msg = crate::session::Message {
role: command_config.output_role.as_str().to_string(),
content: output_text.clone(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
cached: false,
tool_calls: None,
tool_call_id: None,
name: None,
images: None,
..Default::default()
};
final_messages.push(output_msg);
}
chat_session.session.messages = final_messages;
let _ = chat_session.save();
}
OutputMode::Last => {
println!(
"{}",
"Output mode: last (adding last response only to session)".bright_cyan()
);
if let Some(last_output) = result.outputs.last() {
chat_session
.session
.add_message(command_config.output_role.as_str(), last_output);
}
if let Some(session_file) = &chat_session.session.session_file {
let log_entry = serde_json::json!({
"type": "OUTPUT_MODE_LAST",
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"command": command_name,
"content_length": result.outputs.last().map(|s| s.len()).unwrap_or(0),
"total_outputs": result.outputs.len()
});
let _ = crate::session::append_to_session_file(
session_file,
&serde_json::to_string(&log_entry)?,
);
}
let _ = chat_session.save();
}
OutputMode::Restart => {
println!(
"{}",
"Output mode: restart (replacing session with last response only)".bright_cyan()
);
if let Some(session_file) = &chat_session.session.session_file {
let log_entry = serde_json::json!({
"type": "OUTPUT_MODE_RESTART",
"timestamp": std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs(),
"command": command_name,
"previous_message_count": chat_session.session.messages.len(),
"content_length": result.outputs.last().map(|s| s.len()).unwrap_or(0),
"total_outputs": result.outputs.len()
});
let _ = crate::session::append_to_session_file(
session_file,
&serde_json::to_string(&log_entry)?,
);
}
chat_session.session.messages.clear();
if let Some(last_output) = result.outputs.last() {
chat_session
.session
.add_message(command_config.output_role.as_str(), last_output);
}
let _ = chat_session.save();
}
}
Ok(result.outputs.last().unwrap_or(&String::new()).clone())
}
pub fn list_available_commands(config: &Config, role: &str) -> Vec<String> {
let (_, _, _, commands_config, _) = config.get_role_config(role);
commands_config
.map(|commands| commands.iter().map(|cmd| cmd.name.clone()).collect())
.unwrap_or_else(Vec::new)
}
pub fn command_exists(config: &Config, role: &str, command_name: &str) -> bool {
let (_, _, _, commands_config, _) = config.get_role_config(role);
commands_config
.map(|commands| commands.iter().any(|cmd| cmd.name == command_name))
.unwrap_or(false)
}
pub fn get_command_help(config: &Config, role: &str) -> String {
let (_, _, _, commands_config, _) = config.get_role_config(role);
if let Some(commands) = commands_config {
if commands.is_empty() {
"No command layers configured.".to_string()
} else {
let mut help_text = String::from("Available command layers:\n");
for command in commands {
help_text.push_str(&format!(
" /run {} - {}\n",
command.name, command.description
));
}
help_text.push_str("\nUsage: /run <command_name>\nExample: /run reduce");
help_text
}
} else {
"No command layers configured.".to_string()
}
}