steer_core/tools/
capability.rs1use bitflags::bitflags;
8use serde::{Deserialize, Serialize};
9
10bitflags! {
11 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
16 pub struct Capabilities: u64 {
17 const WORKSPACE = 1 << 0;
20
21 const AGENT_SPAWNER = 1 << 1;
24
25 const MODEL_CALLER = 1 << 2;
28
29 const NETWORK = 1 << 3;
31
32 const BASIC = Self::WORKSPACE.bits();
34
35 const AGENT = Self::WORKSPACE.bits() | Self::AGENT_SPAWNER.bits();
37
38 const MODEL = Self::WORKSPACE.bits() | Self::MODEL_CALLER.bits();
40 }
41}
42
43impl Capabilities {
44 pub fn satisfies(&self, required: Capabilities) -> bool {
46 self.contains(required)
47 }
48
49 pub fn names(&self) -> Vec<&'static str> {
51 let mut names = Vec::new();
52 if self.contains(Capabilities::WORKSPACE) {
53 names.push("workspace");
54 }
55 if self.contains(Capabilities::AGENT_SPAWNER) {
56 names.push("agent_spawner");
57 }
58 if self.contains(Capabilities::MODEL_CALLER) {
59 names.push("model_caller");
60 }
61 if self.contains(Capabilities::NETWORK) {
62 names.push("network");
63 }
64 names
65 }
66
67 pub fn missing(&self, required: Capabilities) -> Capabilities {
69 required - *self
70 }
71}
72
73impl Default for Capabilities {
74 fn default() -> Self {
75 Capabilities::empty()
76 }
77}
78
79impl std::fmt::Display for Capabilities {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 let names = self.names();
82 if names.is_empty() {
83 write!(f, "(none)")
84 } else {
85 write!(f, "{}", names.join(", "))
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_satisfies_exact() {
96 let available = Capabilities::WORKSPACE;
97 assert!(available.satisfies(Capabilities::WORKSPACE));
98 }
99
100 #[test]
101 fn test_satisfies_superset() {
102 let available = Capabilities::WORKSPACE | Capabilities::AGENT_SPAWNER;
103 assert!(available.satisfies(Capabilities::WORKSPACE));
104 assert!(available.satisfies(Capabilities::AGENT_SPAWNER));
105 assert!(available.satisfies(Capabilities::WORKSPACE | Capabilities::AGENT_SPAWNER));
106 }
107
108 #[test]
109 fn test_satisfies_missing() {
110 let available = Capabilities::WORKSPACE;
111 assert!(!available.satisfies(Capabilities::AGENT_SPAWNER));
112 assert!(!available.satisfies(Capabilities::WORKSPACE | Capabilities::AGENT_SPAWNER));
113 }
114
115 #[test]
116 fn test_missing_capabilities() {
117 let available = Capabilities::WORKSPACE;
118 let required = Capabilities::WORKSPACE | Capabilities::AGENT_SPAWNER;
119 let missing = available.missing(required);
120 assert_eq!(missing, Capabilities::AGENT_SPAWNER);
121 }
122
123 #[test]
124 fn test_convenience_flags() {
125 assert!(Capabilities::BASIC.contains(Capabilities::WORKSPACE));
126 assert!(Capabilities::AGENT.contains(Capabilities::WORKSPACE));
127 assert!(Capabilities::AGENT.contains(Capabilities::AGENT_SPAWNER));
128 }
129
130 #[test]
131 fn test_display() {
132 let caps = Capabilities::WORKSPACE | Capabilities::NETWORK;
133 let s = caps.to_string();
134 assert!(s.contains("workspace"));
135 assert!(s.contains("network"));
136 }
137}