1use std::fmt;
4
5#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
7#[repr(u8)]
8pub enum ToolKind {
9 Initialize = 0,
10 BashCommand = 1,
11 ReadFiles = 2,
12 FileWriteOrEdit = 3,
13 MultiFileEdit = 4,
14 VerifyEdit = 5,
15 UndoEdit = 6,
16 ContextSave = 7,
17 ReadImage = 8,
18 CodeMap = 9,
19 ApplyPatch = 10,
20 EditFiles = 11,
23 Execution = 12,
25 Agent = 13,
26}
27
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum ToolAccess {
30 ReadOnly,
31 Neutral,
32 Destructive,
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum ToolOutputContract {
37 Shared,
38 CodeMap,
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub enum ToolWorld {
43 Closed,
44 Open,
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum ToolSessionContract {
49 Initializes,
50 RequiresInitialized,
51}
52
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54pub struct ToolDescriptor {
55 pub access: ToolAccess,
56 pub world: ToolWorld,
57 pub session: ToolSessionContract,
58 pub output_contract: ToolOutputContract,
59}
60
61impl ToolKind {
62 pub const ALL: [Self; 14] = [
63 Self::Initialize,
64 Self::BashCommand,
65 Self::ReadFiles,
66 Self::FileWriteOrEdit,
67 Self::MultiFileEdit,
68 Self::VerifyEdit,
69 Self::UndoEdit,
70 Self::ContextSave,
71 Self::ReadImage,
72 Self::CodeMap,
73 Self::ApplyPatch,
74 Self::EditFiles,
75 Self::Execution,
76 Self::Agent,
77 ];
78
79 pub const fn as_str(self) -> &'static str {
80 match self {
81 Self::Initialize => "Initialize",
82 Self::BashCommand => "BashCommand",
83 Self::ReadFiles => "ReadFiles",
84 Self::FileWriteOrEdit => "FileWriteOrEdit",
85 Self::MultiFileEdit => "MultiFileEdit",
86 Self::VerifyEdit => "VerifyEdit",
87 Self::UndoEdit => "UndoEdit",
88 Self::ContextSave => "ContextSave",
89 Self::ReadImage => "ReadImage",
90 Self::CodeMap => "CodeMap",
91 Self::ApplyPatch => "ApplyPatch",
92 Self::EditFiles => "EditFiles",
93 Self::Execution => "Execution",
94 Self::Agent => "Agent",
95 }
96 }
97
98 pub fn parse(name: &str) -> Option<Self> {
99 match name {
100 "Initialize" => Some(Self::Initialize),
101 "BashCommand" => Some(Self::BashCommand),
102 "ReadFiles" => Some(Self::ReadFiles),
103 "FileWriteOrEdit" => Some(Self::FileWriteOrEdit),
104 "MultiFileEdit" => Some(Self::MultiFileEdit),
105 "VerifyEdit" => Some(Self::VerifyEdit),
106 "UndoEdit" => Some(Self::UndoEdit),
107 "ContextSave" => Some(Self::ContextSave),
108 "ReadImage" => Some(Self::ReadImage),
109 "CodeMap" => Some(Self::CodeMap),
110 "ApplyPatch" => Some(Self::ApplyPatch),
111 "EditFiles" => Some(Self::EditFiles),
112 "Execution" => Some(Self::Execution),
113 "Agent" => Some(Self::Agent),
114 _ => None,
115 }
116 }
117
118 pub const fn bit(self) -> u16 {
119 1_u16 << self as u8
120 }
121
122 pub const fn descriptor(self) -> ToolDescriptor {
123 let (access, world) = match self {
124 Self::Initialize
125 | Self::ReadFiles
126 | Self::ReadImage
127 | Self::CodeMap
128 | Self::Execution => (ToolAccess::ReadOnly, ToolWorld::Closed),
129 Self::ContextSave => (ToolAccess::Neutral, ToolWorld::Closed),
130 Self::UndoEdit => (ToolAccess::Destructive, ToolWorld::Closed),
131 Self::BashCommand
132 | Self::Agent
133 | Self::FileWriteOrEdit
134 | Self::MultiFileEdit
135 | Self::ApplyPatch
136 | Self::VerifyEdit
137 | Self::EditFiles => (ToolAccess::Destructive, ToolWorld::Open),
138 };
139 ToolDescriptor {
140 access,
141 world,
142 session: if matches!(self, Self::Initialize) {
143 ToolSessionContract::Initializes
144 } else {
145 ToolSessionContract::RequiresInitialized
146 },
147 output_contract: if matches!(self, Self::CodeMap) {
148 ToolOutputContract::CodeMap
149 } else {
150 ToolOutputContract::Shared
151 },
152 }
153 }
154
155 pub const fn is_file_mutation(self) -> bool {
156 matches!(
157 self,
158 Self::FileWriteOrEdit | Self::MultiFileEdit | Self::ApplyPatch | Self::EditFiles
159 )
160 }
161
162 pub const fn requires_bash_companion(self) -> bool {
163 matches!(self, Self::VerifyEdit)
164 }
165}
166
167impl fmt::Display for ToolKind {
168 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
169 formatter.write_str(self.as_str())
170 }
171}
172
173pub const ALL_TOOL_NAMES: [&str; 14] = [
175 ToolKind::Initialize.as_str(),
176 ToolKind::BashCommand.as_str(),
177 ToolKind::ReadFiles.as_str(),
178 ToolKind::FileWriteOrEdit.as_str(),
179 ToolKind::MultiFileEdit.as_str(),
180 ToolKind::VerifyEdit.as_str(),
181 ToolKind::UndoEdit.as_str(),
182 ToolKind::ContextSave.as_str(),
183 ToolKind::ReadImage.as_str(),
184 ToolKind::CodeMap.as_str(),
185 ToolKind::ApplyPatch.as_str(),
186 ToolKind::EditFiles.as_str(),
187 ToolKind::Execution.as_str(),
188 ToolKind::Agent.as_str(),
189];
190
191#[cfg(test)]
192mod tests {
193 use std::collections::HashSet;
194
195 use super::*;
196
197 #[test]
198 fn names_bits_and_parser_are_one_to_one() {
199 assert_eq!(ALL_TOOL_NAMES, ToolKind::ALL.map(ToolKind::as_str));
200 let names = ToolKind::ALL.into_iter().map(ToolKind::as_str).collect::<HashSet<_>>();
201 let bits = ToolKind::ALL.into_iter().map(ToolKind::bit).collect::<HashSet<_>>();
202 assert_eq!(names.len(), ToolKind::ALL.len());
203 assert_eq!(bits.len(), ToolKind::ALL.len());
204 for kind in ToolKind::ALL {
205 assert_eq!(ToolKind::parse(kind.as_str()), Some(kind));
206 }
207 assert_eq!(ToolKind::parse("readfiles"), None);
208 }
209
210 #[test]
211 fn every_current_tool_is_principal_and_workspace_session_aware() {
212 assert_eq!(ToolKind::Initialize.descriptor().session, ToolSessionContract::Initializes);
213 assert!(ToolKind::ALL
214 .into_iter()
215 .filter(|kind| *kind != ToolKind::Initialize)
216 .all(|kind| { kind.descriptor().session == ToolSessionContract::RequiresInitialized }));
217 }
218
219 #[test]
220 fn workflow_tools_append_bits_and_preserve_access_contracts() {
221 assert_eq!(ToolKind::EditFiles.bit(), 1 << 11);
222 assert_eq!(ToolKind::Execution.bit(), 1 << 12);
223 assert_eq!(ToolKind::Agent.bit(), 1 << 13);
224 assert_eq!(ToolKind::Execution.descriptor().access, ToolAccess::ReadOnly);
225 assert_eq!(ToolKind::Execution.descriptor().world, ToolWorld::Closed);
226 assert_eq!(ToolKind::Agent.descriptor().access, ToolAccess::Destructive);
227 assert_eq!(ToolKind::Agent.descriptor().world, ToolWorld::Open);
228 for kind in [ToolKind::Execution, ToolKind::Agent] {
229 assert_eq!(kind.descriptor().output_contract, ToolOutputContract::Shared);
230 assert!(!kind.is_file_mutation());
231 assert!(!kind.requires_bash_companion());
232 }
233 }
234}