Skip to main content

j_agent/tools/
load_tool.rs

1use crate::tools::{PlanDecision, Tool, ToolResult, schema_to_tool_params};
2use schemars::JsonSchema;
3use serde::Deserialize;
4use serde_json::Value;
5use std::borrow::Cow;
6use std::sync::{Arc, Mutex, atomic::AtomicBool};
7
8/// LoadTool 参数
9#[derive(Deserialize, JsonSchema)]
10#[allow(dead_code)]
11struct LoadToolParams {
12    /// 要加载的工具名称
13    name: String,
14}
15
16/// LoadTool: 让模型可以动态加载 deferred 的工具
17///
18/// 当工具被设置为 defer 时,它不会出现在初始的工具列表中。
19/// 模型可以通过调用 LoadTool 来加载这些工具,加载后该工具在后续轮次中可用。
20/// 加载操作只影响当前会话的运行时状态,不修改用户的持久化配置。
21#[derive(Debug)]
22pub struct LoadTool {
23    /// 延迟加载的工具列表(加载后从中移除,运行时生效)
24    deferred_tools: Arc<Mutex<Vec<String>>>,
25    /// 本会话已加载的 deferred 工具(追踪记录,会话持久化)
26    session_loaded_deferred: Arc<Mutex<Vec<String>>>,
27}
28
29impl LoadTool {
30    pub const NAME: &'static str = "LoadTool";
31
32    /// 静态描述。**不要**在这里读 `self.deferred_tools` —— 一旦读,就违反了
33    /// "Tool::description 是廉价静态查询" 的 trait 契约,并会导致与外层持锁
34    /// 调用栈的自死锁(参见 notes/锁与不变量.md)。
35    ///
36    /// "当前可加载的工具列表" 这个动态信息由 `ToolRegistry` 在拼装
37    /// system prompt / LLM tool 列表时,由调用方从外部 `deferred: &[String]`
38    /// 拼到本工具描述末尾。
39    pub const STATIC_DESCRIPTION: &'static str = "Load a deferred tool so it becomes available in subsequent turns. \
40         Use this when you need a tool that is not currently available in your tool list. \
41         The tool name must match exactly. After loading, the tool will be available in the next turn.";
42
43    pub fn new(
44        deferred_tools: Arc<Mutex<Vec<String>>>,
45        session_loaded_deferred: Arc<Mutex<Vec<String>>>,
46    ) -> Self {
47        Self {
48            deferred_tools,
49            session_loaded_deferred,
50        }
51    }
52}
53
54impl Tool for LoadTool {
55    fn name(&self) -> &str {
56        Self::NAME
57    }
58
59    fn description(&self) -> Cow<'_, str> {
60        Cow::Borrowed(Self::STATIC_DESCRIPTION)
61    }
62
63    fn parameters_schema(&self) -> Value {
64        schema_to_tool_params::<LoadToolParams>()
65    }
66
67    fn execute(&self, arguments: &str, _cancelled: &Arc<AtomicBool>) -> ToolResult {
68        // 1. 解析参数获取工具名
69        let params: LoadToolParams = match serde_json::from_str(arguments) {
70            Ok(p) => p,
71            Err(e) => {
72                return ToolResult {
73                    output: format!("Failed to parse arguments: {e}"),
74                    is_error: true,
75                    images: vec![],
76                    plan_decision: PlanDecision::None,
77                };
78            }
79        };
80
81        let tool_name = params.name.trim().to_string();
82        if tool_name.is_empty() {
83            return ToolResult {
84                output: "Tool name cannot be empty.".to_string(),
85                is_error: true,
86                images: vec![],
87                plan_decision: PlanDecision::None,
88            };
89        }
90
91        // 2. 获取 deferred 列表的写锁,将工具名从中移除
92        // 安全性:deferred_tools 仅在 execute 和 UI 配置中被修改,
93        // execute 由 agent loop 单线程调用,UI 在主线程,Mutex 保证互斥。
94        let mut deferred = match self.deferred_tools.lock() {
95            Ok(guard) => guard,
96            Err(e) => {
97                // Mutex poison 不应发生在正常使用中,获取已恢复的数据
98                e.into_inner()
99            }
100        };
101        let idx = deferred.iter().position(|n| n == &tool_name);
102        match idx {
103            Some(i) => {
104                deferred.remove(i);
105                // 记录到 session_loaded_deferred(会话级追踪,不影响用户配置)
106                if let Ok(mut loaded) = self.session_loaded_deferred.lock()
107                    && !loaded.iter().any(|n| n == &tool_name)
108                {
109                    loaded.push(tool_name.clone());
110                }
111                ToolResult {
112                    output: format!(
113                        "Tool '{}' has been loaded successfully. It will be available in the next turn.",
114                        tool_name
115                    ),
116                    is_error: false,
117                    images: vec![],
118                    plan_decision: PlanDecision::None,
119                }
120            }
121            None => {
122                // 工具可能已经加载过,或者从未被标记为 deferred
123                ToolResult {
124                    output: format!(
125                        "Tool '{}' is not in the deferred list. It may already be loaded or does not exist.",
126                        tool_name
127                    ),
128                    is_error: true,
129                    images: vec![],
130                    plan_decision: PlanDecision::None,
131                }
132            }
133        }
134    }
135
136    fn requires_confirmation(&self) -> bool {
137        false
138    }
139}