Skip to main content

recursive/acp/
tool_kind.rs

1//! Sprint-3: Custom `ToolKind` enum for ACP tool_call notifications.
2//!
3//! The upstream `agent-client-protocol-schema` v1.4 `ToolKind` is missing
4//! `Write` and `WebSearch` variants required by the contract.  This module
5//! defines a local `ToolKind` with those variants, serialised as snake_case.
6//!
7//! It also provides `AcpToolKind::from_acp_tool_name(&str)` for mapping
8//! internal tool names to ACP tool kinds during registry→bridge wiring.
9
10use serde::{Deserialize, Serialize};
11
12/// Category of tool being invoked, serialised as `snake_case` for ACP wire format.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum ToolKind {
16    Read,
17    Edit,
18    Write,
19    Execute,
20    Search,
21    Fetch,
22    WebSearch,
23    Other,
24}
25
26impl ToolKind {
27    /// Map an internal tool-registry name to its ACP tool kind.
28    ///
29    /// This is used to build the bridge's `kind_map` from the tool registry.
30    /// Every override in `Tool::kind()` should have a corresponding entry here
31    /// so the bridge can map names it sees in `AgentEvent::ToolCall` back to
32    /// `ToolKind` without coupling to the `Tool` trait.
33    pub fn from_acp_tool_name(name: &str) -> Self {
34        match name {
35            "Read" => ToolKind::Read,
36            "Edit" => ToolKind::Edit,
37            "Write" => ToolKind::Write,
38            "Bash" => ToolKind::Execute,
39            "Grep" => ToolKind::Search,
40            "Glob" => ToolKind::Search,
41            "WebFetch" => ToolKind::Fetch,
42            "WebSearch" => ToolKind::WebSearch,
43            _ => ToolKind::Other,
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn tool_kind_serialises_snake_case() {
54        let json = serde_json::to_string(&ToolKind::WebSearch).unwrap();
55        assert_eq!(json, r#""web_search""#);
56
57        let json = serde_json::to_string(&ToolKind::Read).unwrap();
58        assert_eq!(json, r#""read""#);
59
60        let json = serde_json::to_string(&ToolKind::Write).unwrap();
61        assert_eq!(json, r#""write""#);
62
63        let json = serde_json::to_string(&ToolKind::Other).unwrap();
64        assert_eq!(json, r#""other""#);
65    }
66
67    #[test]
68    fn from_acp_tool_name_maps_correctly() {
69        assert_eq!(ToolKind::from_acp_tool_name("Read"), ToolKind::Read);
70        assert_eq!(ToolKind::from_acp_tool_name("Write"), ToolKind::Write);
71        assert_eq!(ToolKind::from_acp_tool_name("Edit"), ToolKind::Edit);
72        assert_eq!(ToolKind::from_acp_tool_name("Bash"), ToolKind::Execute);
73        assert_eq!(ToolKind::from_acp_tool_name("Grep"), ToolKind::Search);
74        assert_eq!(ToolKind::from_acp_tool_name("Glob"), ToolKind::Search);
75        assert_eq!(ToolKind::from_acp_tool_name("WebFetch"), ToolKind::Fetch);
76        assert_eq!(
77            ToolKind::from_acp_tool_name("WebSearch"),
78            ToolKind::WebSearch
79        );
80        assert_eq!(ToolKind::from_acp_tool_name("UnknownTool"), ToolKind::Other);
81    }
82}