1use serde::{Deserialize, Serialize};
8use serde_json::{json, Value};
9
10const ALLOWED_CALLER: &str = "code_execution_20260120";
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ToolDefinition {
15 pub name: String,
16 pub description: String,
17 pub input_schema: Value,
18 pub allowed_callers: Vec<String>,
19}
20
21pub fn tool_definitions() -> Vec<ToolDefinition> {
23 vec![
24 ToolDefinition {
25 name: "dkod_connect".into(),
26 description: concat!(
27 "Establish an isolated session workspace on a dkod repository. ",
28 "Returns a session_id, base_commit hash, codebase summary ",
29 "(languages, modules, symbol count), and count of other active ",
30 "sessions. The session workspace is automatically isolated — ",
31 "changes made in this session are invisible to other sessions ",
32 "until merged. Response is JSON: {session_id, base_commit, ",
33 "codebase_summary: {languages, total_symbols, total_files}, ",
34 "active_sessions}."
35 ).into(),
36 input_schema: json!({
37 "type": "object",
38 "properties": {
39 "codebase": {
40 "type": "string",
41 "description": "Repository identifier: 'org/repo'"
42 },
43 "intent": {
44 "type": "string",
45 "description": "What this agent session intends to accomplish"
46 },
47 "mode": {
48 "type": "string",
49 "enum": ["ephemeral", "persistent"],
50 "description": "Ephemeral (default): auto-cleanup on disconnect. Persistent: survives disconnect for later resume."
51 }
52 },
53 "required": ["codebase", "intent"]
54 }),
55 allowed_callers: vec![ALLOWED_CALLER.into()],
56 },
57 ToolDefinition {
58 name: "dkod_context".into(),
59 description: concat!(
60 "Query semantic context from the codebase. Returns symbols ",
61 "(functions, classes, types) matching the query, with signatures, ",
62 "file locations, call graph edges, and associated tests. ",
63 "Response is JSON: {symbols: [{name, qualified_name, kind, ",
64 "file_path, signature, source, callers, callees}], token_count, ",
65 "freshness}. Results reflect this session's workspace (including ",
66 "uncommitted local changes)."
67 ).into(),
68 input_schema: json!({
69 "type": "object",
70 "properties": {
71 "session_id": {
72 "type": "string"
73 },
74 "query": {
75 "type": "string",
76 "description": "Natural language or structured query: 'All functions that handle user authentication' or 'symbol:authenticate_user'"
77 },
78 "depth": {
79 "type": "string",
80 "enum": ["signatures", "full", "call_graph"],
81 "description": "signatures: names + types only. full: complete source. call_graph: signatures + caller/callee edges."
82 },
83 "include_tests": {
84 "type": "boolean"
85 },
86 "max_tokens": {
87 "type": "integer",
88 "description": "Cap response size in tokens"
89 }
90 },
91 "required": ["session_id", "query"]
92 }),
93 allowed_callers: vec![ALLOWED_CALLER.into()],
94 },
95 ToolDefinition {
96 name: "dkod_read_file".into(),
97 description: concat!(
98 "Read a file from this session's workspace. Returns the session's ",
99 "view: if the file was modified in this session, returns the ",
100 "modified version; otherwise returns the base version. Response is ",
101 "JSON: {content, hash, modified_in_session}."
102 ).into(),
103 input_schema: json!({
104 "type": "object",
105 "properties": {
106 "session_id": { "type": "string" },
107 "path": { "type": "string" }
108 },
109 "required": ["session_id", "path"]
110 }),
111 allowed_callers: vec![ALLOWED_CALLER.into()],
112 },
113 ToolDefinition {
114 name: "dkod_write_file".into(),
115 description: concat!(
116 "Write a file to this session's workspace overlay. The change is ",
117 "only visible to this session until submitted. Response is JSON: ",
118 "{new_hash, detected_changes: [{symbol_name, change_type}]}."
119 ).into(),
120 input_schema: json!({
121 "type": "object",
122 "properties": {
123 "session_id": { "type": "string" },
124 "path": { "type": "string" },
125 "content": { "type": "string" }
126 },
127 "required": ["session_id", "path", "content"]
128 }),
129 allowed_callers: vec![ALLOWED_CALLER.into()],
130 },
131 ToolDefinition {
132 name: "dkod_submit".into(),
133 description: concat!(
134 "Submit this session's changes as a semantic changeset for ",
135 "verification and merge. The platform auto-rebases onto current ",
136 "HEAD if the base moved. Response is JSON with one of: ",
137 "{status: 'accepted', version, changeset_id} or ",
138 "{status: 'verification_failed', failures: [{gate, test_name, ",
139 "error, suggestion}]} or {status: 'conflict', conflicts: [{file, ",
140 "symbol, our_change, their_change}]} or {status: 'pending_review', ",
141 "changeset_id}."
142 ).into(),
143 input_schema: json!({
144 "type": "object",
145 "properties": {
146 "session_id": { "type": "string" },
147 "intent": {
148 "type": "string",
149 "description": "What this changeset accomplishes"
150 },
151 "verify": {
152 "type": "array",
153 "items": { "type": "string" },
154 "description": "Verification gates to run: 'typecheck', 'affected_tests', 'all_tests', 'lint', 'invariants'"
155 }
156 },
157 "required": ["session_id", "intent"]
158 }),
159 allowed_callers: vec![ALLOWED_CALLER.into()],
160 },
161 ToolDefinition {
162 name: "dkod_session_status".into(),
163 description: concat!(
164 "Get the current state of this session's workspace. Response is ",
165 "JSON: {session_id, base_commit, files_modified, symbols_modified, ",
166 "overlay_size_bytes, active_other_sessions}."
167 ).into(),
168 input_schema: json!({
169 "type": "object",
170 "properties": {
171 "session_id": { "type": "string" }
172 },
173 "required": ["session_id"]
174 }),
175 allowed_callers: vec![ALLOWED_CALLER.into()],
176 },
177 ]
178}
179
180pub fn generate_manifest() -> String {
182 serde_json::to_string_pretty(&tool_definitions()).expect("tool definitions are valid JSON")
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_tool_definitions_count() {
191 assert_eq!(tool_definitions().len(), 6);
192 }
193
194 #[test]
195 fn test_all_tools_have_allowed_callers() {
196 for tool in tool_definitions() {
197 assert_eq!(tool.allowed_callers, vec!["code_execution_20260120"]);
198 }
199 }
200
201 #[test]
202 fn test_manifest_is_valid_json() {
203 let manifest = generate_manifest();
204 let parsed: Vec<ToolDefinition> = serde_json::from_str(&manifest).unwrap();
205 assert_eq!(parsed.len(), 6);
206 }
207
208 #[test]
209 fn test_tool_names() {
210 let names: Vec<String> = tool_definitions().iter().map(|t| t.name.clone()).collect();
211 assert!(names.contains(&"dkod_connect".to_string()));
212 assert!(names.contains(&"dkod_context".to_string()));
213 assert!(names.contains(&"dkod_read_file".to_string()));
214 assert!(names.contains(&"dkod_write_file".to_string()));
215 assert!(names.contains(&"dkod_submit".to_string()));
216 assert!(names.contains(&"dkod_session_status".to_string()));
217 }
218
219 #[test]
220 fn test_required_fields_present() {
221 for tool in tool_definitions() {
222 let schema = &tool.input_schema;
223 assert!(schema.get("required").is_some(),
224 "tool {} must have required fields", tool.name);
225 assert!(schema.get("properties").is_some(),
226 "tool {} must have properties", tool.name);
227 }
228 }
229
230 #[test]
231 fn generate_manifest_file() {
232 let manifest = generate_manifest();
233 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
234 .parent().unwrap() .parent().unwrap() .join("sdk/dkod-tools.json");
237 if let Some(parent) = path.parent() {
239 std::fs::create_dir_all(parent).unwrap();
240 }
241 std::fs::write(&path, &manifest).unwrap();
242 println!("Manifest written to {}", path.display());
243 }
244}