Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:32:52.336Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

tools.rs

256 lines · 8.3 KB · rust
1mod apply_code_action_tool;
2mod context_server_registry;
3mod copy_path_tool;
4mod create_directory_tool;
5mod create_thread_tool;
6mod delete_path_tool;
7mod diagnostics_tool;
8mod edit_file_tool;
9mod edit_session;
10#[cfg(all(test, feature = "unit-eval"))]
11mod evals;
12mod fetch_tool;
13mod find_path_tool;
14mod find_references_tool;
15mod get_code_actions_tool;
16mod go_to_definition_tool;
17mod grep_tool;
18mod list_agents_and_models_tool;
19mod list_directory_tool;
20mod move_path_tool;
21mod read_file_tool;
22mod rename_tool;
23mod skill_tool;
24mod spawn_agent_tool;
25mod symbol_locator;
26mod terminal_tool;
27mod tool_permissions;
28mod web_search_tool;
29mod write_file_tool;
30
31use crate::AgentTool;
32use feature_flags::{
33    CreateThreadToolFeatureFlag, FeatureFlagAppExt as _, LspToolFeatureFlag, RenameToolFeatureFlag,
34};
35use gpui::App;
36use language_model::{LanguageModelRequestTool, LanguageModelToolSchemaFormat};
37use serde::{
38    Deserialize, Deserializer,
39    de::{DeserializeOwned, Error as _},
40};
41
42/// Deserialize a value that may have been provided as a JSON-encoded string
43/// instead of the structured value. Some models occasionally stringify nested
44/// arguments, so we accept either form.
45pub(crate) fn deserialize_maybe_stringified<'de, T, D>(deserializer: D) -> Result<T, D::Error>
46where
47    T: DeserializeOwned,
48    D: Deserializer<'de>,
49{
50    #[derive(Deserialize)]
51    #[serde(untagged)]
52    enum ValueOrJsonString<T> {
53        Value(T),
54        String(String),
55    }
56
57    match ValueOrJsonString::<T>::deserialize(deserializer)? {
58        ValueOrJsonString::Value(value) => Ok(value),
59        ValueOrJsonString::String(string) => serde_json::from_str::<T>(&string).map_err(|error| {
60            D::Error::custom(format!("failed to parse stringified value: {error}"))
61        }),
62    }
63}
64
65pub use apply_code_action_tool::*;
66pub use context_server_registry::*;
67pub use copy_path_tool::*;
68pub use create_directory_tool::*;
69pub use create_thread_tool::*;
70pub use delete_path_tool::*;
71pub use diagnostics_tool::*;
72pub use edit_file_tool::*;
73pub use fetch_tool::*;
74pub use find_path_tool::*;
75pub use find_references_tool::*;
76pub use get_code_actions_tool::*;
77pub use go_to_definition_tool::*;
78pub use grep_tool::*;
79pub use list_agents_and_models_tool::*;
80pub use list_directory_tool::*;
81pub use move_path_tool::*;
82pub use read_file_tool::*;
83pub use rename_tool::*;
84pub use skill_tool::*;
85pub use spawn_agent_tool::*;
86pub use symbol_locator::*;
87
88pub use terminal_tool::*;
89pub use tool_permissions::*;
90pub use web_search_tool::*;
91pub use write_file_tool::*;
92
93macro_rules! tools {
94    ($($tool:ty),* $(,)?) => {
95        /// Every built-in tool name, determined at compile time.
96        pub const ALL_TOOL_NAMES: &[&str] = &[
97            $(<$tool>::NAME,)*
98        ];
99
100        const _: () = {
101            const fn str_eq(a: &str, b: &str) -> bool {
102                let a = a.as_bytes();
103                let b = b.as_bytes();
104                if a.len() != b.len() {
105                    return false;
106                }
107                let mut i = 0;
108                while i < a.len() {
109                    if a[i] != b[i] {
110                        return false;
111                    }
112                    i += 1;
113                }
114                true
115            }
116
117            const NAMES: &[&str] = ALL_TOOL_NAMES;
118            let mut i = 0;
119            while i < NAMES.len() {
120                let mut j = i + 1;
121                while j < NAMES.len() {
122                    if str_eq(NAMES[i], NAMES[j]) {
123                        panic!("Duplicate tool name in tools! macro");
124                    }
125                    j += 1;
126                }
127                i += 1;
128            }
129        };
130
131        /// Returns whether the tool with the given name supports the given provider.
132        pub fn tool_supports_provider(name: &str, provider: &language_model::LanguageModelProviderId) -> bool {
133            $(
134                if name == <$tool>::NAME {
135                    return <$tool>::supports_provider(provider);
136                }
137            )*
138            false
139        }
140
141        /// Returns whether the tool with the given name may be provided to an
142        /// agent in a restricted workspace. Unknown tools (e.g. MCP tools) are
143        /// considered allowed.
144        pub fn tool_allowed_in_restricted_mode(name: &str) -> bool {
145            $(
146                if name == <$tool>::NAME {
147                    return <$tool>::allow_in_restricted_mode();
148                }
149            )*
150            true
151        }
152
153        /// A list of all built-in tools
154        pub fn built_in_tools() -> impl Iterator<Item = LanguageModelRequestTool> {
155            fn language_model_tool<T: AgentTool>() -> LanguageModelRequestTool {
156                LanguageModelRequestTool::function(
157                    T::NAME.to_string(),
158                    T::description().to_string(),
159                    T::input_schema(LanguageModelToolSchemaFormat::JsonSchema).to_value(),
160                    T::supports_input_streaming(),
161                )
162            }
163            [
164                $(
165                    language_model_tool::<$tool>(),
166                )*
167            ]
168            .into_iter()
169        }
170    };
171}
172
173// Adding a tool here (and constructing it in `Thread::add_default_tools`) is
174// not enough to make the model actually receive it. Three further gates will
175// silently drop the tool rather than fail to compile:
176//
177// 1. `assets/settings/default.json`: the `write` and `ask` agent profiles each
178//    carry an explicit `tools` allowlist. `Thread::enabled_tools` filters out
179//    any tool not present there with value `true`, so it never reaches the
180//    model.
181// 2. `test_all_tools_are_in_tool_info_or_excluded` in
182//    `crates/settings_ui/src/pages/tool_permissions_setup.rs`: every tool must
183//    be in the permission-UI `TOOLS` list (if it calls
184//    `decide_permission_from_settings`) or in `EXCLUDED_TOOLS`.
185// 3. `tool_feature_flag_enabled`: some tools are gated behind a feature flag and
186//    are dropped unless it is active. The agent-profile UI uses the same gate so
187//    it never offers a tool the agent can't actually use.
188tools! {
189    ApplyCodeActionTool,
190    CopyPathTool,
191    CreateDirectoryTool,
192    CreateThreadTool,
193    DeletePathTool,
194    DiagnosticsTool,
195    EditFileTool,
196    FetchTool,
197    FindPathTool,
198    FindReferencesTool,
199    GetCodeActionsTool,
200    GoToDefinitionTool,
201    GrepTool,
202    ListAgentsAndModelsTool,
203    ListDirectoryTool,
204    MovePathTool,
205    ReadFileTool,
206    RenameTool,
207    SkillTool,
208    SpawnAgentTool,
209    TerminalTool,
210    WebSearchTool,
211    WriteFileTool,
212}
213
214/// Some built-in tools are gated behind a feature flag and only become usable
215/// once that flag is active. Tools without a flag are always available.
216///
217/// This is the single source of truth for that gating: `Thread::enabled_tools`
218/// uses it to decide what the model receives, and the agent-profile
219/// configuration UI uses it to decide what to offer — so the UI can never list
220/// a tool the agent would silently drop (see #56778).
221pub fn tool_feature_flag_enabled(tool_name: &str, cx: &App) -> bool {
222    match tool_name {
223        RenameTool::NAME => cx.has_flag::<RenameToolFeatureFlag>(),
224        FindReferencesTool::NAME
225        | GetCodeActionsTool::NAME
226        | ApplyCodeActionTool::NAME
227        | GoToDefinitionTool::NAME => cx.has_flag::<LspToolFeatureFlag>(),
228        CreateThreadTool::NAME | ListAgentsAndModelsTool::NAME => {
229            cx.has_flag::<CreateThreadToolFeatureFlag>()
230        }
231        _ => true,
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238
239    #[test]
240    fn fetch_and_terminal_are_forbidden_in_restricted_mode() {
241        assert!(!tool_allowed_in_restricted_mode(FetchTool::NAME));
242        assert!(!tool_allowed_in_restricted_mode(TerminalTool::NAME));
243
244        // Every other built-in tool, and unknown (e.g. MCP) tools, are allowed.
245        for name in ALL_TOOL_NAMES {
246            let expected = *name != FetchTool::NAME && *name != TerminalTool::NAME;
247            assert_eq!(
248                tool_allowed_in_restricted_mode(name),
249                expected,
250                "unexpected restricted-mode policy for tool `{name}`"
251            );
252        }
253        assert!(tool_allowed_in_restricted_mode("some_mcp_tool"));
254    }
255}
256
Served at tenant.openagents/omega Member data and write actions are omitted.