afc43ce692
QWeather 天气查询从外部 bash 脚本重构为 Rust 原生 builtin 工具: - ed25519-dalek 替代 openssl JWT 签名 - reqwest + tokio::join! 并发查询替代串行 curl - serde_json 直接解析替代 python3 子进程 - city_id 内存缓存 (HashMap, 1h TTL) - 支持 3/7/10/15/30d 逐日 + 24/72/168h 逐时预报 - 删除 resources/skills/query_weather/ (builtin 优先级更高)
118 lines
5.4 KiB
Rust
118 lines
5.4 KiB
Rust
use crate::tools::skill::{RiskLevel, SkillResult};
|
|
use chrono::Local;
|
|
|
|
/// 内置工具执行器:统一处理参数校验、执行、错误返回
|
|
pub struct BuiltinRegistry;
|
|
|
|
impl BuiltinRegistry {
|
|
pub async fn execute(name: &str, args_json: &str) -> Option<SkillResult> {
|
|
match name {
|
|
"get_current_datetime" => Some(execute_datetime()),
|
|
"manage_memos" => Some(handle_memos(args_json).await),
|
|
"query_weather" => {
|
|
let params: serde_json::Value =
|
|
serde_json::from_str(args_json).unwrap_or_default();
|
|
Some(super::weather::execute(params).await)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// 返回所有内置工具的 spec 列表
|
|
pub fn specs() -> Vec<super::skill::SkillSpec> {
|
|
vec![
|
|
super::skill::SkillSpec {
|
|
name: "get_current_datetime".into(),
|
|
description: "获取当前日期时间(北京时间 UTC+8)".into(),
|
|
risk_level: RiskLevel::Low,
|
|
parameters: serde_json::json!({"type":"object","properties":{"format":{"type":"string"}}}),
|
|
timeout_secs: 5,
|
|
},
|
|
super::skill::SkillSpec {
|
|
name: "manage_memos".into(),
|
|
description: "管理备忘录:添加/列出/删除。高风险操作(add/delete)需确认".into(),
|
|
risk_level: RiskLevel::High,
|
|
parameters: serde_json::json!({"type":"object","properties":{"action":{"type":"string","enum":["add","list","delete"]},"content":{"type":"string"},"id":{"type":"integer"}}}),
|
|
timeout_secs: 10,
|
|
},
|
|
super::weather::spec(),
|
|
]
|
|
}
|
|
|
|
pub fn is_high_risk(name: &str) -> bool {
|
|
Self::specs().iter().any(|s| s.name == name && s.risk_level == RiskLevel::High)
|
|
}
|
|
|
|
pub fn is_builtin(name: &str) -> bool {
|
|
Self::specs().iter().any(|s| s.name == name)
|
|
}
|
|
}
|
|
|
|
fn execute_datetime() -> SkillResult {
|
|
let now = Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
|
|
SkillResult::ok(serde_json::json!({"datetime": now}).to_string())
|
|
}
|
|
|
|
async fn handle_memos(args_json: &str) -> SkillResult {
|
|
let mut params: serde_json::Value = serde_json::from_str(args_json).unwrap_or_default();
|
|
// call_capability 透传完整参数,action/content 在顶层
|
|
// 如果顶层没有 action,尝试从 prompt 推断
|
|
if params.get("action").is_none() {
|
|
let prompt = params.get("prompt").and_then(|v| v.as_str()).unwrap_or("");
|
|
let action = if prompt.contains("添加") || prompt.contains("新增") || prompt.contains("add") {
|
|
"add"
|
|
} else if prompt.contains("删除") || prompt.contains("移除") || prompt.contains("delete") {
|
|
"delete"
|
|
} else {
|
|
"list"
|
|
};
|
|
let content = params.get("prompt").and_then(|v| v.as_str()).unwrap_or("");
|
|
let memo_id: i32 = content.chars().filter(|c| c.is_ascii_digit()).collect::<String>().parse().unwrap_or(0);
|
|
params = serde_json::json!({"action": action, "content": content, "id": memo_id});
|
|
}
|
|
let action = params.get("action").and_then(|v| v.as_str()).unwrap_or("list");
|
|
let path = ".data/memos.json";
|
|
if let Err(e) = std::fs::create_dir_all(".data") { return SkillResult::error(format!("创建目录失败: {}", e)); }
|
|
|
|
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 SkillResult::error("请提供 content 参数"); }
|
|
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": Local::now().format("%Y-%m-%d %H:%M").to_string()
|
|
}));
|
|
if let Err(e) = std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()) {
|
|
return SkillResult::error(format!("写入备忘录失败: {}", e));
|
|
}
|
|
SkillResult::ok(format!("已添加备忘录 #{}", next_id))
|
|
}
|
|
"list" => {
|
|
if data.is_empty() { return SkillResult::ok("暂无备忘录".to_string()); }
|
|
let lines: Vec<String> = 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();
|
|
SkillResult::ok(lines.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 SkillResult::error(format!("未找到备忘录 #{}", id)); }
|
|
if let Err(e) = std::fs::write(path, serde_json::to_string_pretty(&data).unwrap()) {
|
|
return SkillResult::error(format!("写入备忘录失败: {}", e));
|
|
}
|
|
SkillResult::ok(format!("已删除备忘录 #{}", id))
|
|
}
|
|
_ => SkillResult::error("未知操作,支持: add, list, delete"),
|
|
}
|
|
}
|