新增: AI 工具(download_file + http output_file + grep 窗口 + fetch_search 搜索 + get_app_config + generate_image)

download_file 跨平台 URL 到文件流式下载;http_request output_file 落盘 + 截断可配;
grep context_chars 大单行窗口;fetch_search DuckDuckGo 免 key 搜索;get_app_config
只读返当前配置(治 AI 查配置绕 PowerShell);generate_image SenseNova 图像生成。
This commit is contained in:
lxy
2026-08-02 02:21:32 +08:00
parent e4f7b432aa
commit b999f79fdf
17 changed files with 2862 additions and 56 deletions
+29 -16
View File
@@ -2,16 +2,22 @@
//!
//! 迁自 `tool_registry.rs::register_http_tools`(原 1 个 http_request),改用 `declare_tool!` 宏。
//!
//! 迁移策略(handler 逻辑零变更):
//! - handler body 逐字照搬原 async move 块(转调 `crate::commands::ai::http::execute_http_request`),
//! 仅闭包包装改由 `declare_tool!` 宏生成。
//! 迁移策略(原 handler 逻辑零变更,2026-08-01 增 output_file/max_response_chars + allowed_dirs 捕获):
//! - handler body 转调 `crate::commands::ai::http::execute_http_request`,闭包包装由 `declare_tool!` 宏生成。
//! output_file 参数引入后,handler 内增 `allowed_dirs.read().await.clone()` 快照传入(路径校验用,
//! 无 output_file 时校验不触发,行为零变更)。
//! - name/desc/schema/risk 与原手写定义逐字一致(headers 是对象 map,object_schema 仅支持扁平
//! 标量三元组,故保留原手工拼 serde_json::Map schema 表达式)。
//! - 无捕获:原 handler 不 clone db(handler 仅转调 http.rs),用占位 `Arc<()>` 捕获(宏要求 capture 形参)。
//! 标量三元组,故保留原手工拼 serde_json::Map schema 表达式);新增 output_file/max_response_chars 两 prop。
//! - 捕获:handler 不持 db(纯转调 http.rs),但需 allowed_dirs 做 output_file 路径校验,
//! 故捕获 `Arc<RwLock<AllowedDirs>>`(宏要求 capture 形参)。
//!
//! 风险:GET=Medium(只读但触发外发)/ 写方法=High(须人工批准),一个工具名两种 risk 不支持(register
//! 单一 risk_level),故按最高风险 High 注册(写方法 High 兜底;GET 也走 High 审批更保守)。
//!
//! 2026-08-01 `output_file` 参数:响应可落盘(全量写不截断),路径走文件路径校验
//! (validate_path + resolve_workspace_path_with_allowed + parent 授权,与 write_file 同源),
//! 故 register 增 `allowed_dirs` 捕获,转调 execute_http_request 时读快照传入。
//!
//! 等价性验证:基线测试 `test_build_ai_tool_registry_baseline_tool_count` 仍断言 48 总量 +
//! 工具名集合稳定(防 rename / 漏注册)。
@@ -19,18 +25,21 @@ use std::sync::Arc;
use df_ai::ai_tools::{AiToolRegistry, RiskLevel};
use df_ai::declare_tool;
use tokio::sync::RwLock;
/// 注册 http_request 工具到 `$registry`(无 db 捕获,纯 reqwest 调用)。
use crate::state::AllowedDirs;
/// 注册 http_request 工具到 `$registry`(无 db 捕获,但捕获 allowed_dirs 供 output_file 路径校验)。
///
/// 与原手写 register(name, desc, schema, risk, handler) 语义 1:1:
/// - name/desc/schema 字符串与 JSON Schema 逐字照搬原定义
/// 与原手写 register(name, desc, schema, risk, handler) 语义 1:1(2026-08-01 增 output_file /
/// max_response_chars 两参数 + allowed_dirs 捕获):
/// - name/desc/schema 字符串与 JSON Schema 逐字照搬原定义(新增两 prop)
/// - risk=High(写方法兜底,GET 同走审批更保守)
/// - handler body 与原 async move 块逐字一致(逻辑零变更)
/// - handler body 与原 async move 块逐字一致(逻辑零变更),新增 allowed_dirs read lock clone
/// 传入 execute_http_request(output_file 路径校验用,无 output_file 时不触发)
///
/// 唯一差异:闭包包装改由 `declare_tool!` 宏生成,handler body 直接写业务逻辑。
pub fn register(registry: &mut AiToolRegistry) {
// 无捕获:占位 Arc<()>(原 handler 不持 db,仅转调 http.rs)。
let dummy: Arc<()> = Arc::new(());
pub fn register(registry: &mut AiToolRegistry, allowed_dirs: &Arc<RwLock<AllowedDirs>>) {
// schema:headers 是对象 map,object_schema 仅支持扁平标量三元组,故手工拼 serde_json::Map。
// 提取为 let 绑定:declare_tool! 的 schema: $schema:expr 形参对花括号块表达式解析有歧义,
// 先求值到局部变量再传入,语义等价(原手写 register 亦以此 Map 作为 schema 实参)。
@@ -42,6 +51,8 @@ pub fn register(registry: &mut AiToolRegistry) {
props.insert("body".into(), serde_json::json!({ "type": "string", "description": "请求体(POST/PUT/PATCH 用),原样发送,Content-Type 须在 headers 显式指定" }));
props.insert("timeout_secs".into(), serde_json::json!({ "type": "integer", "description": "超时秒数(默认 30,上限 60)", "minimum": 1, "maximum": 60 }));
props.insert("parse".into(), serde_json::json!({ "type": "string", "description": "响应 body 解析:json(pretty 格式化)/text(原样)/auto(按 Content-Type 自动,默认)", "enum": ["json", "text", "auto"] }));
props.insert("output_file".into(), serde_json::json!({ "type": "string", "description": "响应 body 写入指定文件路径(而非返回 JSON body),适合大响应。路径走文件路径校验 + 授权目录,全量写不截断。提供时返回 body=null + path/bytes_written/truncated=false" }));
props.insert("max_response_chars".into(), serde_json::json!({ "type": "integer", "description": "body 截断阈值(字节,默认 51200/50KB,clamp [1024, 10485760])。替代默认截断上限;有 output_file 时不截断", "minimum": 1024, "maximum": 10485760 }));
serde_json::json!({
"type": "object",
"properties": props,
@@ -50,14 +61,16 @@ pub fn register(registry: &mut AiToolRegistry) {
};
declare_tool!(
registry,
dummy: Arc<()>,
allowed_dirs: Arc<RwLock<AllowedDirs>>,
"http_request",
"发起结构化 HTTP 请求(GET/POST/PUT/PATCH/DELETE),用于查询外部 API。参数:method(默认 GET)、url(http/https)、headers(对象 map)、body(请求体字符串)、timeout_secs(默认 30,上限 60)、parse(json/text/auto,默认 auto)。安全:仅 http/https 协议,拒绝私网/保留 IP(SSRF 防护含 DNS resolve 后校验防重绑定),重定向≤3 跳且每跳重校验。响应 body 截断 50KB。GET 为只读但触发外发网络,POST/PUT/PATCH/DELETE 有副作用,统一按高风险须人工批准",
"发起结构化 HTTP 请求(GET/POST/PUT/PATCH/DELETE),用于查询外部 API。参数:method(默认 GET)、url(http/https)、headers(对象 map)、body(请求体字符串)、timeout_secs(默认 30,上限 60)、parse(json/text/auto,默认 auto)、output_file(响应落盘路径,适合大响应,全量写不截断)、max_response_chars(body 截断阈值字节,默认 50KB,clamp [1KB,10MB])。安全:仅 http/https 协议,拒绝私网/保留 IP(SSRF 防护含 DNS resolve 后校验防重绑定),重定向≤3 跳且每跳重校验。响应 body 默认截断 50KB(可 max_response_chars 覆盖或 output_file 落盘)。GET 为只读但触发外发网络,POST/PUT/PATCH/DELETE 有副作用,统一按高风险须人工批准",
RiskLevel::High,
schema: schema,
args => {
// 转调 http.rs handler(SSRF 防护 + 重定向 + 截断全在那)
crate::commands::ai::http::execute_http_request(args).await
// allowed_dirs read lock clone(output_file 路径校验用;无 output_file 时校验不触发)
let snap = allowed_dirs.read().await.clone();
// 转调 http.rs handler(SSRF 防护 + 重定向 + 截断 + output_file 落盘全在那)
crate::commands::ai::http::execute_http_request(args, &snap).await
}
);
}