Skip to main content

aidens_tool_kit/
descriptors.rs

1use aidens_contracts::{CanonicalToolSideEffectClass, ToolDescriptorV1, ToolSchemaV1};
2
3pub fn safe_coding_tool_plan() -> Vec<(ToolDescriptorV1, bool)> {
4    vec![
5        (repo_read_descriptor(), true),
6        (repo_list_descriptor(), true),
7        (file_stat_descriptor(), true),
8        (repo_search_descriptor(), true),
9        (patch_propose_descriptor(), true),
10        (patch_apply_descriptor(), true),
11        (run_checks_descriptor(), true),
12        (
13            side_effect_descriptor("file-write", CanonicalToolSideEffectClass::Write),
14            false,
15        ),
16        (
17            side_effect_descriptor("shell", CanonicalToolSideEffectClass::Admin),
18            false,
19        ),
20        (
21            side_effect_descriptor("network", CanonicalToolSideEffectClass::Analysis),
22            false,
23        ),
24        (
25            side_effect_descriptor("memory-write", CanonicalToolSideEffectClass::Write),
26            false,
27        ),
28        (
29            side_effect_descriptor("schedule", CanonicalToolSideEffectClass::Admin),
30            false,
31        ),
32    ]
33}
34
35pub fn safe_coding_tool_declarations() -> Vec<ToolDescriptorV1> {
36    safe_coding_tool_plan()
37        .into_iter()
38        .map(|(descriptor, _enabled)| descriptor)
39        .collect()
40}
41
42pub fn repo_read_descriptor() -> ToolDescriptorV1 {
43    ToolDescriptorV1 {
44        namespace: "aidens".into(),
45        name: "repo-read".into(),
46        version: "1".into(),
47        description: "Read one UTF-8 file inside the configured repository sandbox.".into(),
48        risk_class: CanonicalToolSideEffectClass::ReadOnly,
49        read_only: true,
50        hidden: false,
51        requires_native_tool_loop: false,
52        schema: ToolSchemaV1 {
53            input_schema: serde_json::json!({
54                "type": "object",
55                "additionalProperties": false,
56                "required": ["path"],
57                "properties": {
58                    "path": { "type": "string" }
59                }
60            }),
61            output_schema: serde_json::json!({
62                "type": "object",
63                "required": ["tool_id", "path", "bytes", "content"],
64                "properties": {
65                    "tool_id": { "type": "string" },
66                    "path": { "type": "string" },
67                    "bytes": { "type": "integer", "minimum": 0 },
68                    "content": { "type": "string" }
69                }
70            }),
71            parser_fallback_hint: "Call aidens:repo-read:1 with JSON {\"path\":\"relative/file\"}."
72                .into(),
73        },
74    }
75}
76
77pub fn patch_propose_descriptor() -> ToolDescriptorV1 {
78    ToolDescriptorV1 {
79        namespace: "aidens".into(),
80        name: "patch-propose".into(),
81        version: "1".into(),
82        description: "Propose a patch without applying it; this tool never mutates files.".into(),
83        risk_class: CanonicalToolSideEffectClass::ReadOnly,
84        read_only: true,
85        hidden: false,
86        requires_native_tool_loop: false,
87        schema: ToolSchemaV1 {
88            input_schema: serde_json::json!({
89                "type": "object",
90                "additionalProperties": false,
91                "required": ["summary", "diff"],
92                "properties": {
93                    "summary": { "type": "string" },
94                    "diff": { "type": "string" }
95                }
96            }),
97            output_schema: serde_json::json!({
98                "type": "object",
99                "required": ["tool_id", "proposal", "mutates_files"],
100                "properties": {
101                    "tool_id": { "type": "string" },
102                    "proposal": { "type": "object" },
103                    "mutates_files": { "type": "boolean" }
104                }
105            }),
106            parser_fallback_hint: "Call aidens:patch-propose:1 with JSON {\"summary\":\"...\",\"diff\":\"unified diff\"}."
107                .into(),
108        },
109    }
110}
111
112pub fn repo_list_descriptor() -> ToolDescriptorV1 {
113    ToolDescriptorV1 {
114        namespace: "aidens".into(),
115        name: "repo-list".into(),
116        version: "1".into(),
117        description: "List entries inside one directory under the repository sandbox.".into(),
118        risk_class: CanonicalToolSideEffectClass::ReadOnly,
119        read_only: true,
120        hidden: false,
121        requires_native_tool_loop: false,
122        schema: ToolSchemaV1 {
123            input_schema: serde_json::json!({
124                "type": "object",
125                "additionalProperties": false,
126                "properties": {
127                    "path": { "type": "string" },
128                    "max_entries": { "type": "integer", "minimum": 1, "maximum": 1000 }
129                }
130            }),
131            output_schema: serde_json::json!({
132                "type": "object",
133                "required": ["tool_id", "path", "entries", "receipt"],
134                "properties": {
135                    "tool_id": { "type": "string" },
136                    "path": { "type": "string" },
137                    "entries": { "type": "array" },
138                    "receipt": { "type": "object" }
139                }
140            }),
141            parser_fallback_hint: "Call aidens:repo-list:1 with JSON {\"path\":\"relative/dir\"}."
142                .into(),
143        },
144    }
145}
146
147pub fn file_stat_descriptor() -> ToolDescriptorV1 {
148    ToolDescriptorV1 {
149        namespace: "aidens".into(),
150        name: "file-stat".into(),
151        version: "1".into(),
152        description: "Inspect metadata for one file or directory inside the sandbox.".into(),
153        risk_class: CanonicalToolSideEffectClass::ReadOnly,
154        read_only: true,
155        hidden: false,
156        requires_native_tool_loop: false,
157        schema: ToolSchemaV1 {
158            input_schema: serde_json::json!({
159                "type": "object",
160                "additionalProperties": false,
161                "required": ["path"],
162                "properties": {
163                    "path": { "type": "string" }
164                }
165            }),
166            output_schema: serde_json::json!({
167                "type": "object",
168                "required": ["tool_id", "path", "is_file", "is_dir", "bytes"],
169                "properties": {
170                    "tool_id": { "type": "string" },
171                    "path": { "type": "string" },
172                    "is_file": { "type": "boolean" },
173                    "is_dir": { "type": "boolean" },
174                    "bytes": { "type": "integer", "minimum": 0 },
175                    "content_digest": { "type": ["object", "null"] }
176                }
177            }),
178            parser_fallback_hint: "Call aidens:file-stat:1 with JSON {\"path\":\"relative/file\"}."
179                .into(),
180        },
181    }
182}
183
184pub fn repo_search_descriptor() -> ToolDescriptorV1 {
185    ToolDescriptorV1 {
186        namespace: "aidens".into(),
187        name: "repo-search".into(),
188        version: "1".into(),
189        description: "Search UTF-8 files inside the sandbox for a literal string.".into(),
190        risk_class: CanonicalToolSideEffectClass::ReadOnly,
191        read_only: true,
192        hidden: false,
193        requires_native_tool_loop: false,
194        schema: ToolSchemaV1 {
195            input_schema: serde_json::json!({
196                "type": "object",
197                "additionalProperties": false,
198                "required": ["query"],
199                "properties": {
200                    "query": { "type": "string" },
201                    "path": { "type": "string" },
202                    "max_matches": { "type": "integer", "minimum": 1, "maximum": 200 }
203                }
204            }),
205            output_schema: serde_json::json!({
206                "type": "object",
207                "required": ["tool_id", "query", "path", "matches"],
208                "properties": {
209                    "tool_id": { "type": "string" },
210                    "query": { "type": "string" },
211                    "path": { "type": "string" },
212                    "matches": { "type": "array" }
213                }
214            }),
215            parser_fallback_hint:
216                "Call aidens:repo-search:1 with JSON {\"query\":\"needle\",\"path\":\".\"}.".into(),
217        },
218    }
219}
220
221pub fn patch_apply_descriptor() -> ToolDescriptorV1 {
222    ToolDescriptorV1 {
223        namespace: "aidens".into(),
224        name: "patch-apply".into(),
225        version: "1".into(),
226        description: "Apply an approved unified patch inside the sandbox.".into(),
227        risk_class: CanonicalToolSideEffectClass::Write,
228        read_only: false,
229        hidden: false,
230        requires_native_tool_loop: false,
231        schema: ToolSchemaV1 {
232            input_schema: serde_json::json!({
233                "type": "object",
234                "additionalProperties": false,
235                "required": ["diff"],
236                "properties": {
237                    "diff": { "type": "string" },
238                    "check_only": { "type": "boolean" },
239                    "dry_run": { "type": "boolean" }
240                }
241            }),
242            output_schema: serde_json::json!({
243                "type": "object",
244                "required": ["tool_id", "applied", "dry_run_checked", "changed_files", "receipt"],
245                "properties": {
246                    "tool_id": { "type": "string" },
247                    "applied": { "type": "boolean" },
248                    "dry_run_checked": { "type": "boolean" },
249                    "changed_files": { "type": "array", "items": { "type": "string" } },
250                    "semantic_status": { "type": "string" },
251                    "failure_kind": { "type": "string" },
252                    "touched_paths": { "type": "array", "items": { "type": "string" } },
253                    "receipt": { "type": "object" }
254                }
255            }),
256            parser_fallback_hint:
257                "aidens:patch-apply:1 requires explicit scoped file-write permit evidence.".into(),
258        },
259    }
260}
261
262pub fn run_checks_descriptor() -> ToolDescriptorV1 {
263    ToolDescriptorV1 {
264        namespace: "aidens".into(),
265        name: "run-checks".into(),
266        version: "1".into(),
267        description: "Run an allowlisted local check command inside the sandbox.".into(),
268        risk_class: CanonicalToolSideEffectClass::Admin,
269        read_only: false,
270        hidden: false,
271        requires_native_tool_loop: false,
272        schema: ToolSchemaV1 {
273            input_schema: serde_json::json!({
274                "type": "object",
275                "additionalProperties": false,
276                "required": ["command"],
277                "properties": {
278                    "command": {
279                        "type": "array",
280                        "items": { "type": "string" },
281                        "minItems": 1
282                    }
283                }
284            }),
285            output_schema: serde_json::json!({
286                "type": "object",
287                "required": ["tool_id", "command", "succeeded", "receipt"],
288                "properties": {
289                    "tool_id": { "type": "string" },
290                    "command": { "type": "array", "items": { "type": "string" } },
291                    "succeeded": { "type": "boolean" },
292                    "exit_code": { "type": ["integer", "null"] },
293                    "stdout": { "type": "string" },
294                    "stderr": { "type": "string" },
295                    "receipt": { "type": "object" }
296                }
297            }),
298            parser_fallback_hint:
299                "aidens:run-checks:1 requires explicit scoped shell permit evidence.".into(),
300        },
301    }
302}
303
304pub fn side_effect_descriptor(
305    name: &str,
306    risk_class: CanonicalToolSideEffectClass,
307) -> ToolDescriptorV1 {
308    ToolDescriptorV1 {
309        namespace: "aidens".into(),
310        name: name.into(),
311        version: "1".into(),
312        description: format!("{name} is a side-effect tool and requires explicit permit evidence."),
313        risk_class,
314        read_only: false,
315        hidden: false,
316        requires_native_tool_loop: false,
317        schema: ToolSchemaV1 {
318            input_schema: serde_json::json!({
319                "type": "object",
320                "additionalProperties": true
321            }),
322            output_schema: serde_json::json!({
323                "type": "object",
324                "additionalProperties": true
325            }),
326            parser_fallback_hint: format!("{name} is not registered by the safe coding profile."),
327        },
328    }
329}