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}