refactor: 删除 config.json + 内置工具抽离 + 统一接口

删除:
  - config.rs / config.json(AppConfig/LmStudioConfig)
  - LM Studio 残留(lmstudio.rs 已在之前删除)
  - main.rs 中散落的内置工具 match arms

新增:
  - src/tools/builtin.rs — BuiltinRegistry 统一切入
    get_current_datetime、manage_memos 为核心内置工具
    统一参数校验、风险等级、错误返回

改动:
  - 模型名直接从 DEEPSEEK_MODEL 环境变量读取
  - builtin 工具与 skills 工具统一经过 SkillRegistry 审批
  - query_capabilities 合并内置 + 外部技能列表
This commit is contained in:
2026-06-02 15:07:33 +08:00
parent a32593bc25
commit 079150b1d0
9 changed files with 106 additions and 404 deletions
+16 -64
View File
@@ -1,5 +1,4 @@
mod cli;
mod config;
mod context;
mod db;
mod llm;
@@ -12,7 +11,6 @@ mod wechat;
use clap::Parser;
use cli::{Cli, Commands, SkillsAction};
use config::AppConfig;
use context::MemoryStore;
use db::Database;
use llm::{Conversation, ConversationConfig, DEFAULT_SYSTEM_PROMPT, ToolExecutor, Usage};
@@ -44,9 +42,7 @@ async fn main() {
}
}
let config = AppConfig::from_file("config.json");
// 初始化数据库(如果配置了 DATABASE_URL
let file_state = state::StateManager::new(".data/weixin-ilink");
let database = match Database::connect().await {
Ok(db) => {
info!("✅ 数据库连接成功");
@@ -65,7 +61,7 @@ async fn main() {
memory_store.load("").await;
// 文件状态管理器(降级方案)
let file_state = state::StateManager::new(&config.storage.state_dir);
let file_state = state::StateManager::new(".data/weixin-ilink");
match cli.command {
Commands::Login { timeout } => {
@@ -75,15 +71,7 @@ async fn main() {
llm: enable_llm,
echo,
} => {
cmd_listen(
enable_llm,
echo,
&database,
&file_state,
&config,
&memory_store,
)
.await;
cmd_listen(enable_llm, echo, &database, &file_state, &memory_store).await;
}
Commands::Send {
to,
@@ -103,7 +91,7 @@ async fn main() {
cmd_usage(&database, since, until, model).await;
}
Commands::Service => {
cmd_listen(true, false, &database, &file_state, &config, &memory_store).await
cmd_listen(true, false, &database, &file_state, &memory_store).await
}
Commands::Skills { action } => {
cmd_skills(action).await;
@@ -162,7 +150,6 @@ async fn cmd_listen(
echo: bool,
database: &Option<Arc<Database>>,
file_state: &state::StateManager,
config: &AppConfig,
memory_store: &Arc<MemoryStore>,
) {
// 从数据库或文件加载认证
@@ -230,7 +217,7 @@ async fn cmd_listen(
let conversation = if enable_llm {
let sys_prompt = std::env::var("WEIXIN_LLM_SYSTEM_PROMPT")
.unwrap_or_else(|_| DEFAULT_SYSTEM_PROMPT.to_string());
let model = config.llm.deepseek.model.clone();
let model = std::env::var("DEEPSEEK_MODEL").unwrap_or_else(|_| "deepseek-v4-flash".to_string());
let mut cfg = ConversationConfig {
system_prompt: sys_prompt,
@@ -355,6 +342,12 @@ async fn cmd_listen(
Box::pin(async move {
let user_id = session.lock().await.current_user_id.clone();
// 内置工具优先
if let Some(result) = tools::builtin::BuiltinRegistry::execute(&n, &args).await {
return Ok(result.output);
}
match n.as_str() {
"read_memories" => return Ok(memory_store.read_for(&user_id).await),
"write_memory" => {
@@ -369,15 +362,12 @@ async fn cmd_listen(
"read_summaries" => return Ok(context::tools::read_summaries(&session).await),
"query_capabilities" => {
if let Some(r) = reg.as_ref() {
return Ok(build_capability_list(r));
let mut specs: Vec<tools::skill::SkillSpec> = r.list_specs().into_iter().cloned().collect();
specs.extend(tools::builtin::BuiltinRegistry::specs());
return Ok(build_capability_list(&specs));
}
return Ok("暂无工具".to_string());
}
"get_current_datetime" => {
let now = chrono::Local::now();
return Ok(serde_json::json!({"datetime": now.format("%Y-%m-%d %H:%M:%S").to_string()}).to_string());
}
"manage_memos" => return handle_memos(&args).await,
"call_capability" => {
let cap_params: serde_json::Value = serde_json::from_str(&args).unwrap_or_default();
let cap_name = cap_params.get("name").and_then(|v| v.as_str()).map(|s| s.to_string());
@@ -818,9 +808,9 @@ fn global_skills_dir() -> std::path::PathBuf {
.join("skills")
}
fn build_capability_list(reg: &SkillRegistry) -> String {
fn build_capability_list(specs: &[tools::skill::SkillSpec]) -> String {
let mut lines = vec!["📋 可用工具:".to_string()];
for spec in reg.list_specs() {
for spec in specs {
let risk = if spec.risk_level == tools::skill::RiskLevel::High { " ⚠️需确认" } else { "" };
lines.push(format!(
"\n🔹 {} {}{}", spec.name, risk, spec.description
@@ -829,44 +819,6 @@ fn build_capability_list(reg: &SkillRegistry) -> String {
lines.join("\n")
}
async fn handle_memos(args_json: &str) -> Result<String, String> {
use std::io::{Read, Write};
let params: serde_json::Value = serde_json::from_str(args_json).unwrap_or_default();
let action = params.get("action").and_then(|v| v.as_str()).unwrap_or("list");
let path = ".data/memos.json";
std::fs::create_dir_all(".data").ok();
let mut data: Vec<serde_json::Value> = if std::path::Path::new(path).exists() {
serde_json::from_str(&std::fs::read_to_string(path).unwrap_or_default()).unwrap_or_default()
} else {
vec![]
};
match action {
"add" => {
let content = params.get("content").and_then(|v| v.as_str()).unwrap_or("");
if content.is_empty() { return Ok(r#"{"error":"请提供 content 参数"}"#.to_string()); }
let next_id = data.iter().filter_map(|m| m.get("id").and_then(|v| v.as_i64())).max().unwrap_or(0) + 1;
data.push(serde_json::json!({"id": next_id, "content": content, "created_at": chrono::Local::now().format("%Y-%m-%d %H:%M").to_string()}));
std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()).ok();
Ok(format!("已添加备忘录 #{}", next_id))
}
"list" => {
if data.is_empty() { return Ok("暂无备忘录".to_string()); }
Ok(data.iter().map(|m| format!("#{} [{}] {}", m["id"], m.get("created_at").and_then(|v| v.as_str()).unwrap_or("?"), m["content"].as_str().unwrap_or("?"))).collect::<Vec<_>>().join("\n"))
}
"delete" => {
let id = params.get("id").and_then(|v| v.as_i64()).unwrap_or(0);
let before = data.len();
data.retain(|m| m.get("id").and_then(|v| v.as_i64()) != Some(id));
if data.len() == before { return Ok(format!("未找到备忘录 #{}", id)); }
std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()).ok();
Ok(format!("已删除备忘录 #{}", id))
}
_ => Ok("未知操作,支持: add, list, delete".to_string())
}
}
async fn cmd_skills(action: SkillsAction) {
use dialoguer::{MultiSelect, theme::ColorfulTheme};
use console::Term;