use crate::command::chat::agent::compact::CompactConfig;
use crate::command::chat::constants::{
DEFAULT_MAX_CONTEXT_TOKENS, DEFAULT_MAX_HISTORY_MESSAGES, DEFAULT_MAX_TOOL_ROUNDS,
};
use crate::command::chat::render::theme::ThemeName;
use crate::config::YamlConfig;
use crate::error;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ModelProvider {
pub name: String,
pub api_base: String,
pub api_key: String,
pub model: String,
#[serde(default)]
pub supports_vision: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AgentConfig {
#[serde(default)]
pub providers: Vec<ModelProvider>,
#[serde(default)]
pub active_index: usize,
#[serde(default)]
pub system_prompt: Option<String>,
#[serde(default = "default_max_history_messages")]
pub max_history_messages: usize,
#[serde(default = "default_max_context_tokens")]
pub max_context_tokens: usize,
#[serde(default)]
pub theme: ThemeName,
#[serde(default)]
pub tools_enabled: bool,
#[serde(default = "default_max_tool_rounds")]
pub max_tool_rounds: usize,
#[serde(default)]
pub style: Option<String>,
#[serde(default)]
pub tool_confirm_timeout: u64,
#[serde(default)]
pub disabled_tools: Vec<String>,
#[serde(default)]
pub disabled_skills: Vec<String>,
#[serde(default)]
pub disabled_commands: Vec<String>,
#[serde(default)]
pub compact: CompactConfig,
#[serde(default)]
pub auto_restore_session: bool,
}
fn default_max_history_messages() -> usize {
DEFAULT_MAX_HISTORY_MESSAGES
}
fn default_max_context_tokens() -> usize {
DEFAULT_MAX_CONTEXT_TOKENS
}
fn default_max_tool_rounds() -> usize {
DEFAULT_MAX_TOOL_ROUNDS
}
pub fn agent_data_dir() -> PathBuf {
let dir = YamlConfig::data_dir().join("agent").join("data");
let _ = fs::create_dir_all(&dir);
dir
}
pub fn agent_config_path() -> PathBuf {
agent_data_dir().join("agent_config.json")
}
pub fn system_prompt_path() -> PathBuf {
agent_data_dir().join("system_prompt.md")
}
pub fn style_path() -> PathBuf {
agent_data_dir().join("style.md")
}
pub fn memory_path() -> PathBuf {
agent_data_dir().join("memory.md")
}
pub fn soul_path() -> PathBuf {
agent_data_dir().join("soul.md")
}
pub fn hooks_config_path() -> PathBuf {
let dir = YamlConfig::data_dir().join("agent");
let _ = fs::create_dir_all(&dir);
dir.join("hooks.yaml")
}
pub fn load_agent_config() -> AgentConfig {
let path = agent_config_path();
if !path.exists() {
return AgentConfig::default();
}
match fs::read_to_string(&path) {
Ok(content) => serde_json::from_str(&content).unwrap_or_else(|e| {
error!("✖️ 解析 agent_config.json 失败: {}", e);
AgentConfig::default()
}),
Err(e) => {
error!("✖️ 读取 agent_config.json 失败: {}", e);
AgentConfig::default()
}
}
}
pub fn save_agent_config(config: &AgentConfig) -> bool {
let path = agent_config_path();
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
let mut config_to_save = config.clone();
config_to_save.system_prompt = None;
config_to_save.style = None;
match serde_json::to_string_pretty(&config_to_save) {
Ok(json) => match fs::write(&path, json) {
Ok(_) => true,
Err(e) => {
error!("✖️ 保存 agent_config.json 失败: {}", e);
false
}
},
Err(e) => {
error!("✖️ 序列化 agent 配置失败: {}", e);
false
}
}
}
pub fn load_system_prompt() -> Option<String> {
let path = system_prompt_path();
if !path.exists() {
return None;
}
match fs::read_to_string(path) {
Ok(content) => {
let trimmed = content.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
Err(e) => {
error!("✖️ 读取 system_prompt.md 失败: {}", e);
None
}
}
}
pub fn save_system_prompt(prompt: &str) -> bool {
let path = system_prompt_path();
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
let trimmed = prompt.trim();
if trimmed.is_empty() {
return match fs::remove_file(&path) {
Ok(_) => true,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,
Err(e) => {
error!("✖️ 删除 system_prompt.md 失败: {}", e);
false
}
};
}
match fs::write(path, trimmed) {
Ok(_) => true,
Err(e) => {
error!("✖️ 保存 system_prompt.md 失败: {}", e);
false
}
}
}
pub fn load_style() -> Option<String> {
let path = style_path();
if !path.exists() {
return None;
}
match fs::read_to_string(path) {
Ok(content) => {
let trimmed = content.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
Err(e) => {
error!("✖️ 读取 style.md 失败: {}", e);
None
}
}
}
pub fn save_style(style: &str) -> bool {
let path = style_path();
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
let trimmed = style.trim();
if trimmed.is_empty() {
return match fs::remove_file(&path) {
Ok(_) => true,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => true,
Err(e) => {
error!("✖️ 删除 style.md 失败: {}", e);
false
}
};
}
match fs::write(path, trimmed) {
Ok(_) => true,
Err(e) => {
error!("✖️ 保存 style.md 失败: {}", e);
false
}
}
}
pub fn load_memory() -> Option<String> {
let path = memory_path();
if !path.exists() {
return None;
}
match fs::read_to_string(path) {
Ok(content) => {
let trimmed = content.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
Err(e) => {
error!("✖️ 读取 memory.md 失败: {}", e);
None
}
}
}
pub fn load_soul() -> Option<String> {
let path = soul_path();
if !path.exists() {
return None;
}
match fs::read_to_string(path) {
Ok(content) => {
let trimmed = content.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
}
Err(e) => {
error!("✖️ 读取 soul.md 失败: {}", e);
None
}
}
}
pub fn save_memory(content: &str) -> bool {
let path = memory_path();
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
match fs::write(path, content) {
Ok(_) => true,
Err(e) => {
error!("✖️ 保存 memory.md 失败: {}", e);
false
}
}
}
pub fn save_soul(content: &str) -> bool {
let path = soul_path();
if let Some(parent) = path.parent() {
let _ = fs::create_dir_all(parent);
}
match fs::write(path, content) {
Ok(_) => true,
Err(e) => {
error!("✖️ 保存 soul.md 失败: {}", e);
false
}
}
}