//! 灵感类 AI 工具声明式注册(register_idea_tools 2 个:list + create 迁入)。 //! //! 迁自 `tool_registry.rs::register_idea_tools`(原 2 个 list_ideas/create_idea), //! 改用 `declare_tool!` 宏。 //! //! 迁移策略(handler 逻辑零变更): //! - handler body 逐字照搬原 `register_idea_tools` 内 async move 块(逻辑等价), //! 仅闭包包装(`{ let db = db.clone(); Box::new(move |args| { let db = db.clone(); //! Box::pin(async move { ... }) }) }`)改由 `declare_tool!` 宏生成。 //! - name/desc/schema/risk 与原手写定义逐字一致。 //! - 复用类型/常量:new_id/IdeaStatus/IdeaRecord/now_millis(各 pub(crate) 来源,与原同源) + //! MAX_LIST_RESULTS(super::tool_registry 单真相源)。 //! //! 等价性验证:基线测试 `test_build_ai_tool_registry_baseline_tool_count` 仍断言 48 总量 + //! 工具名集合稳定(防 rename / 漏注册)。 use std::sync::Arc; use df_ai::ai_tools::{object_schema, AiToolRegistry, RiskLevel}; use df_ai::declare_tool; use df_storage::db::Database; use df_storage::models::IdeaRecord; use df_types::types::{new_id, IdeaStatus}; use crate::commands::now_millis; use crate::commands::ai::tool_registry::MAX_LIST_RESULTS; /// 注册 2 个灵感类工具(list/create)到 `$registry`。 /// /// 与原手写 register(name, desc, schema, risk, handler) 语义 1:1: /// - name/desc/schema 字符串与 JSON Schema 逐字照搬原定义 /// - risk 与原一致(list=Low,create=Medium) /// - handler body 与原 async move 块逐字一致(逻辑零变更) /// /// 唯一差异:闭包包装改由 `declare_tool!` 宏生成,handler body 直接写业务逻辑。 pub fn register(registry: &mut AiToolRegistry, db: &Arc) { declare_tool!( registry, db: Arc, "list_ideas", "列出所有灵感,支持 offset/limit 分页。返回 items、total、has_more。默认 limit=50", RiskLevel::Low, schema: object_schema(vec![("offset", "integer", false), ("limit", "integer", false)]), args => { let repo = df_storage::crud::IdeaRepo::new(&db); let items = repo.list_all().await?; let total = items.len(); let offset = args["offset"].as_u64().unwrap_or(0) as usize; let limit = args["limit"].as_u64().unwrap_or(MAX_LIST_RESULTS as u64).min(MAX_LIST_RESULTS as u64) as usize; let page_items: Vec<_> = items.into_iter().skip(offset).take(limit).collect(); let has_more = (offset + page_items.len()) < total; Ok(serde_json::json!({ "items": page_items, "total": total, "has_more": has_more })) } ); declare_tool!( registry, db: Arc, "create_idea", "捕获一个新灵感", RiskLevel::Medium, schema: object_schema(vec![ ("title", "string", true), ("description", "string", false), ("tags", "string", false), ("source", "string", false), // priority:与 commands::idea::CreateIdeaInput 默认值一致(=1,灵感默认普通优先级) ("priority", "integer", false), ]), args => { let title = args["title"].as_str().ok_or_else(|| anyhow::anyhow!("缺少 title"))?; let repo = df_storage::crud::IdeaRepo::new(&db); let record = IdeaRecord { id: new_id(), title: title.to_string(), description: args["description"].as_str().unwrap_or("").to_string(), // priority 默认 1:灵感默认普通优先级(与 commands::idea::default_priority 及 tasks 表 SQL DEFAULT 1 对齐) status: IdeaStatus::Draft, priority: args["priority"].as_i64().unwrap_or(1) as i32, score: None, tags: args["tags"].as_str().map(|s| s.to_string()), source: args["source"].as_str().map(|s| s.to_string()), promoted_to: None, ai_analysis: None, scores: None, related_ids: None, created_at: now_millis(), updated_at: now_millis(), }; let id = record.id.clone(); repo.insert(record).await?; Ok(serde_json::json!({ "id": id, "title": title, "status": "draft" })) } ); }