-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtools.zig
More file actions
366 lines (322 loc) · 15.4 KB
/
tools.zig
File metadata and controls
366 lines (322 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
// Tools Registry - Centralized tool registration and execution
const std = @import("std");
const ollama = @import("ollama");
const permission = @import("permission");
const context_module = @import("context");
// Re-export mvzr for use by tool modules (grep_search)
pub const mvzr = @import("mvzr");
// Import all tool modules
const ls = @import("tools/ls.zig");
const read = @import("tools/read_lines.zig");
const write_file = @import("tools/write_file.zig");
const replace_lines = @import("tools/replace_lines.zig");
const insert_lines = @import("tools/insert_lines.zig");
const grep_search = @import("tools/grep_search.zig");
const current_time = @import("tools/current_time.zig");
const pwd = @import("tools/pwd.zig");
const add_todo = @import("tools/add_todo.zig");
const list_todos = @import("tools/list_todos.zig");
const update_todo = @import("tools/update_todo.zig");
// Task memory system tools (Beads-inspired)
const add_task = @import("tools/add_task.zig");
const list_tasks = @import("tools/list_tasks.zig");
const get_blocked_tasks = @import("tools/get_blocked_tasks.zig");
const add_dependency = @import("tools/add_dependency.zig");
const complete_task = @import("tools/complete_task.zig");
const update_task = @import("tools/update_task.zig");
// New Beads-style tools
const get_current_task = @import("tools/get_current_task.zig");
const start_task = @import("tools/start_task.zig");
const block_task = @import("tools/block_task.zig");
const add_task_comment = @import("tools/add_task_comment.zig");
const list_task_comments = @import("tools/list_task_comments.zig");
const add_subtask = @import("tools/add_subtask.zig");
const get_session_context = @import("tools/get_session_context.zig");
const land_the_plane = @import("tools/land_the_plane.zig");
// Session coordinator tools (main agent startup/shutdown)
const check_environment = @import("tools/check_environment.zig");
const init_environment = @import("tools/init_environment.zig");
const get_session_status = @import("tools/get_session_status.zig");
const end_session = @import("tools/end_session.zig");
// Task hierarchy and sync tools
const sync_to_git = @import("tools/sync_to_git.zig");
const run_agent = @import("tools/run_agent.zig");
const list_agents = @import("tools/list_agents.zig");
const planning_done = @import("tools/planning_done.zig");
const git_commit = @import("tools/submit_work.zig");
const request_revision = @import("tools/request_revision.zig");
const git_status = @import("tools/git_status.zig");
const git_diff = @import("tools/git_diff.zig");
const git_log = @import("tools/git_log.zig");
const git_add = @import("tools/git_add.zig");
const git_branch = @import("tools/git_branch.zig");
const git_checkout = @import("tools/git_checkout.zig");
const git_stash = @import("tools/git_stash.zig");
const git_push = @import("tools/git_push.zig");
const git_pull = @import("tools/git_pull.zig");
const git_reset = @import("tools/git_reset.zig");
const web_search = @import("tools/web_search.zig");
const web_fetch = @import("tools/web_fetch.zig");
const AppContext = context_module.AppContext;
// ============================================================================
// Tool Result Types (Shared by all tools)
// ============================================================================
pub const ToolErrorType = enum {
none,
not_found,
validation_failed,
permission_denied,
io_error,
parse_error,
internal_error,
invalid_arguments,
};
pub const ToolResult = struct {
success: bool,
data: ?[]const u8,
error_message: ?[]const u8,
error_type: ToolErrorType,
thinking: ?[]const u8 = null, // Optional thinking/reasoning from agents
metadata: struct {
execution_time_ms: i64,
data_size_bytes: usize,
timestamp: i64,
},
// Helper to create success result
pub fn ok(allocator: std.mem.Allocator, data: []const u8, start_time: i64, thinking_opt: ?[]const u8) !ToolResult {
const end_time = std.time.milliTimestamp();
return .{
.success = true,
.data = try allocator.dupe(u8, data),
.error_message = null,
.error_type = .none,
.thinking = if (thinking_opt) |t| try allocator.dupe(u8, t) else null,
.metadata = .{
.execution_time_ms = end_time - start_time,
.data_size_bytes = data.len,
.timestamp = end_time,
},
};
}
// Helper to create error result
pub fn err(allocator: std.mem.Allocator, error_type: ToolErrorType, message: []const u8, start_time: i64) !ToolResult {
const end_time = std.time.milliTimestamp();
return .{
.success = false,
.data = null,
.error_message = try allocator.dupe(u8, message),
.error_type = error_type,
.metadata = .{
.execution_time_ms = end_time - start_time,
.data_size_bytes = 0,
.timestamp = end_time,
},
};
}
// Serialize to JSON for model
pub fn toJSON(self: *const ToolResult, allocator: std.mem.Allocator) ![]const u8 {
// Struct for JSON serialization
const JsonOutput = struct {
success: bool,
data: ?[]const u8,
error_message: ?[]const u8,
error_type: []const u8,
metadata: struct {
execution_time_ms: i64,
data_size_bytes: usize,
timestamp: i64,
},
};
const output = JsonOutput{
.success = self.success,
.data = self.data,
.error_message = self.error_message,
.error_type = @tagName(self.error_type),
.metadata = .{
.execution_time_ms = self.metadata.execution_time_ms,
.data_size_bytes = self.metadata.data_size_bytes,
.timestamp = self.metadata.timestamp,
},
};
return std.fmt.allocPrint(allocator, "{f}", .{std.json.fmt(output, .{})});
}
// Format for user display (full transparency)
pub fn formatDisplay(self: *const ToolResult, allocator: std.mem.Allocator, tool_name: []const u8, args: []const u8) ![]const u8 {
var display = std.ArrayListUnmanaged(u8){};
defer display.deinit(allocator);
const writer = display.writer(allocator);
try writer.print("[Tool: {s}]\n", .{tool_name});
if (self.success) {
try writer.writeAll("Status: ✅ SUCCESS\n");
if (self.data) |d| {
// Truncate large outputs (increased limit for tree/search results)
const preview_len = @min(d.len, 10000);
try writer.print("Result: {s}", .{d[0..preview_len]});
if (d.len > 10000) {
try writer.print("... ({d} more bytes)", .{d.len - 10000});
}
try writer.writeAll("\n");
}
} else {
try writer.writeAll("Status: ❌ FAILED\n");
try writer.print("Error Type: {s}\n", .{@tagName(self.error_type)});
if (self.error_message) |e| {
try writer.print("Error: {s}\n", .{e});
}
}
try writer.print("Execution Time: {d}ms\n", .{self.metadata.execution_time_ms});
try writer.print("Data Size: {d} bytes\n", .{self.metadata.data_size_bytes});
try writer.print("Arguments: {s}", .{args});
return try display.toOwnedSlice(allocator);
}
pub fn deinit(self: *ToolResult, allocator: std.mem.Allocator) void {
if (self.data) |d| allocator.free(d);
if (self.error_message) |e| allocator.free(e);
if (self.thinking) |t| allocator.free(t);
}
};
// ============================================================================
// Tool Definition Structure
// ============================================================================
pub const ToolDefinition = struct {
// Ollama tool schema (for API calls)
ollama_tool: ollama.Tool,
// Permission metadata (for safety checks)
permission_metadata: permission.ToolMetadata,
// Execution function (Phase 1: accepts AppContext for future graph RAG)
execute: *const fn (std.mem.Allocator, []const u8, *AppContext) anyerror!ToolResult,
};
// ============================================================================
// Public API
// ============================================================================
/// Returns all tool definitions (caller owns memory)
pub fn getAllToolDefinitions(allocator: std.mem.Allocator) ![]ToolDefinition {
var tools = std.ArrayListUnmanaged(ToolDefinition){};
errdefer tools.deinit(allocator);
// File system tools
try tools.append(allocator, try ls.getDefinition(allocator));
try tools.append(allocator, try read.getDefinition(allocator));
try tools.append(allocator, try write_file.getDefinition(allocator));
try tools.append(allocator, try replace_lines.getDefinition(allocator));
try tools.append(allocator, try insert_lines.getDefinition(allocator));
try tools.append(allocator, try grep_search.getDefinition(allocator));
// System tools
try tools.append(allocator, try current_time.getDefinition(allocator));
try tools.append(allocator, try pwd.getDefinition(allocator));
// Git tools
try tools.append(allocator, try git_status.getDefinition(allocator));
try tools.append(allocator, try git_diff.getDefinition(allocator));
try tools.append(allocator, try git_log.getDefinition(allocator));
try tools.append(allocator, try git_add.getDefinition(allocator));
try tools.append(allocator, try git_branch.getDefinition(allocator));
try tools.append(allocator, try git_checkout.getDefinition(allocator));
try tools.append(allocator, try git_stash.getDefinition(allocator));
try tools.append(allocator, try git_push.getDefinition(allocator));
try tools.append(allocator, try git_pull.getDefinition(allocator));
try tools.append(allocator, try git_reset.getDefinition(allocator));
// Todo management tools (Phase 1 - legacy, kept for compatibility)
try tools.append(allocator, try add_todo.getDefinition(allocator));
try tools.append(allocator, try list_todos.getDefinition(allocator));
try tools.append(allocator, try update_todo.getDefinition(allocator));
// Task memory system tools (Beads-inspired)
try tools.append(allocator, try add_task.getDefinition(allocator));
try tools.append(allocator, try list_tasks.getDefinition(allocator));
try tools.append(allocator, try get_blocked_tasks.getDefinition(allocator));
try tools.append(allocator, try add_dependency.getDefinition(allocator));
try tools.append(allocator, try complete_task.getDefinition(allocator));
try tools.append(allocator, try update_task.getDefinition(allocator));
// New Beads-style tools
try tools.append(allocator, try get_current_task.getDefinition(allocator));
try tools.append(allocator, try start_task.getDefinition(allocator));
try tools.append(allocator, try block_task.getDefinition(allocator));
try tools.append(allocator, try add_task_comment.getDefinition(allocator));
try tools.append(allocator, try list_task_comments.getDefinition(allocator));
try tools.append(allocator, try add_subtask.getDefinition(allocator));
try tools.append(allocator, try get_session_context.getDefinition(allocator));
try tools.append(allocator, try land_the_plane.getDefinition(allocator));
// Session coordinator tools (main agent startup/shutdown)
try tools.append(allocator, try check_environment.getDefinition(allocator));
try tools.append(allocator, try init_environment.getDefinition(allocator));
try tools.append(allocator, try get_session_status.getDefinition(allocator));
try tools.append(allocator, try end_session.getDefinition(allocator));
// Task sync tools
try tools.append(allocator, try sync_to_git.getDefinition(allocator));
// Agent tools
try tools.append(allocator, try run_agent.getDefinition(allocator));
try tools.append(allocator, try list_agents.getDefinition(allocator));
try tools.append(allocator, try planning_done.getDefinition(allocator));
try tools.append(allocator, try git_commit.getDefinition(allocator));
try tools.append(allocator, try request_revision.getDefinition(allocator));
// Web tools
try tools.append(allocator, try web_search.getDefinition(allocator));
try tools.append(allocator, try web_fetch.getDefinition(allocator));
return try tools.toOwnedSlice(allocator);
}
/// Extracts just the Ollama tool schemas for API calls
pub fn getOllamaTools(allocator: std.mem.Allocator) ![]const ollama.Tool {
const definitions = try getAllToolDefinitions(allocator);
defer {
// Free the definitions array but NOT the ollama_tool contents
// (caller takes ownership of those)
allocator.free(definitions);
}
var tools = try allocator.alloc(ollama.Tool, definitions.len);
for (definitions, 0..) |def, i| {
tools[i] = def.ollama_tool;
}
return tools;
}
/// Extracts just the permission metadata for registration
pub fn getPermissionMetadata(allocator: std.mem.Allocator) ![]permission.ToolMetadata {
const definitions = try getAllToolDefinitions(allocator);
defer {
// Free the allocated strings inside each definition
for (definitions) |def| {
allocator.free(def.ollama_tool.function.name);
allocator.free(def.ollama_tool.function.description);
allocator.free(def.ollama_tool.function.parameters);
}
allocator.free(definitions);
}
var metadata = try allocator.alloc(permission.ToolMetadata, definitions.len);
for (definitions, 0..) |def, i| {
metadata[i] = def.permission_metadata;
}
return metadata;
}
/// Execute a tool by name (Phase 1: accepts AppContext for state access)
/// Creates a per-tool arena allocator for task queries - all task allocations are
/// automatically freed when tool execution completes.
pub fn executeToolCall(allocator: std.mem.Allocator, tool_call: ollama.ToolCall, context: *AppContext) !ToolResult {
const start_time = std.time.milliTimestamp();
// Create arena for task allocations during this tool execution
// Tasks loaded from SQLite use this arena, freed automatically on return
var task_arena = std.heap.ArenaAllocator.init(allocator);
defer task_arena.deinit();
// Set arena in context for TaskStore/TaskDB to use
const old_arena = context.task_arena;
context.task_arena = &task_arena;
defer context.task_arena = old_arena;
const definitions = try getAllToolDefinitions(allocator);
defer {
// Free the tool definitions
for (definitions) |def| {
allocator.free(def.ollama_tool.function.name);
allocator.free(def.ollama_tool.function.description);
allocator.free(def.ollama_tool.function.parameters);
}
allocator.free(definitions);
}
// Find matching tool and execute
for (definitions) |def| {
if (std.mem.eql(u8, def.ollama_tool.function.name, tool_call.function.name)) {
// Tool uses 'allocator' for ToolResult (must outlive arena)
// Task queries inside tool use arena via context.task_arena
return try def.execute(allocator, tool_call.function.arguments, context);
}
}
// Tool not found
const msg = try std.fmt.allocPrint(allocator, "Unknown tool: {s}", .{tool_call.function.name});
defer allocator.free(msg);
return ToolResult.err(allocator, .not_found, msg, start_time);
}