-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagent_builder_state.zig
More file actions
276 lines (230 loc) · 8.94 KB
/
agent_builder_state.zig
File metadata and controls
276 lines (230 loc) · 8.94 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
// Agent Builder State - Manages the agent creation/editing UI
const std = @import("std");
/// Field types for agent builder form
pub const FieldType = enum {
/// Text input for single line (name, description)
text_input,
/// Multi-line text area for system prompt
text_area,
/// Checkbox list for tool selection
tool_checkboxes,
};
/// A single field in the agent builder form
pub const AgentField = struct {
/// Display label (e.g., "Agent Name")
label: []const u8,
/// Field type
field_type: FieldType,
/// Field identifier (e.g., "name")
key: []const u8,
/// Help text shown below the field
help_text: ?[]const u8 = null,
/// For text_input/text_area: current value being edited
edit_buffer: ?std.ArrayListUnmanaged(u8) = null,
/// For tool_checkboxes: available tool names
tool_options: ?[]const []const u8 = null,
/// For tool_checkboxes: which tools are selected
tool_selected: ?[]bool = null,
/// Whether this field is currently being edited
is_editing: bool = false,
/// For tool_checkboxes: which checkbox has focus
checkbox_focus_index: usize = 0,
};
/// Main state for the agent builder screen
pub const AgentBuilderState = struct {
/// Is the agent builder currently active?
active: bool = false,
/// Form fields
fields: []AgentField,
/// Currently focused field index
focused_field_index: usize = 0,
/// Allocator for managing state
allocator: std.mem.Allocator,
/// Has user made any changes?
has_changes: bool = false,
/// Scroll position (for when form is taller than screen)
scroll_y: usize = 0,
/// Validation errors (null if valid)
validation_error: ?[]const u8 = null,
/// Initialize agent builder state
pub fn init(allocator: std.mem.Allocator) !AgentBuilderState {
// Get list of all available tools
const tool_names = try getAllToolNames(allocator);
// Create form fields
var fields = std.ArrayListUnmanaged(AgentField){};
// Field 1: Agent Name (text input)
try fields.append(allocator, .{
.label = "Agent Name",
.field_type = .text_input,
.key = "name",
.help_text = "Unique identifier (lowercase, hyphens only)",
.edit_buffer = std.ArrayListUnmanaged(u8){},
});
// Field 2: Description (text input)
try fields.append(allocator, .{
.label = "Description",
.field_type = .text_input,
.key = "description",
.help_text = "Brief description of what this agent does",
.edit_buffer = std.ArrayListUnmanaged(u8){},
});
// Field 3: System Prompt (text area)
try fields.append(allocator, .{
.label = "System Prompt",
.field_type = .text_area,
.key = "system_prompt",
.help_text = "Instructions that define the agent's behavior",
.edit_buffer = std.ArrayListUnmanaged(u8){},
});
// Field 4: Tools (checkbox list)
const tool_selected = try allocator.alloc(bool, tool_names.len);
@memset(tool_selected, false); // Default: no tools selected
try fields.append(allocator, .{
.label = "Available Tools",
.field_type = .tool_checkboxes,
.key = "tools",
.help_text = "Select which tools this agent can use",
.tool_options = tool_names,
.tool_selected = tool_selected,
});
return AgentBuilderState{
.active = true,
.fields = try fields.toOwnedSlice(allocator),
.focused_field_index = 0,
.allocator = allocator,
.has_changes = false,
.scroll_y = 0,
.validation_error = null,
};
}
/// Clean up all allocated memory
pub fn deinit(self: *AgentBuilderState) void {
// Free fields
for (self.fields) |*field| {
if (field.edit_buffer) |*buffer| {
buffer.deinit(self.allocator);
}
if (field.tool_options) |options| {
for (options) |tool_name| {
self.allocator.free(tool_name);
}
self.allocator.free(options);
}
if (field.tool_selected) |selected| {
self.allocator.free(selected);
}
}
self.allocator.free(self.fields);
// Free validation error if present
if (self.validation_error) |err_msg| {
self.allocator.free(err_msg);
}
}
/// Get the currently focused field
pub fn getFocusedField(self: *AgentBuilderState) ?*AgentField {
if (self.focused_field_index < self.fields.len) {
return &self.fields[self.focused_field_index];
}
return null;
}
/// Move focus to next field (wraps around)
pub fn focusNext(self: *AgentBuilderState) void {
if (self.fields.len > 0) {
self.focused_field_index = (self.focused_field_index + 1) % self.fields.len;
}
}
/// Move focus to previous field (wraps around)
pub fn focusPrevious(self: *AgentBuilderState) void {
if (self.fields.len > 0) {
if (self.focused_field_index == 0) {
self.focused_field_index = self.fields.len - 1;
} else {
self.focused_field_index -= 1;
}
}
}
/// Validate current form state
pub fn validate(self: *AgentBuilderState) !bool {
// Free previous validation error
if (self.validation_error) |err| {
self.allocator.free(err);
self.validation_error = null;
}
// Get field values
const name_field = &self.fields[0];
const desc_field = &self.fields[1];
const prompt_field = &self.fields[2];
// Validate name
if (name_field.edit_buffer) |*buffer| {
if (buffer.items.len == 0) {
self.validation_error = try self.allocator.dupe(u8, "Agent name is required");
return false;
}
// Check name format (lowercase, hyphens, underscores only)
for (buffer.items) |char| {
const is_valid = std.ascii.isLower(char) or
std.ascii.isDigit(char) or
char == '-' or
char == '_';
if (!is_valid) {
self.validation_error = try self.allocator.dupe(u8, "Name must contain only lowercase letters, numbers, hyphens, and underscores");
return false;
}
}
}
// Validate description
if (desc_field.edit_buffer) |*buffer| {
if (buffer.items.len == 0) {
self.validation_error = try self.allocator.dupe(u8, "Description is required");
return false;
}
}
// Validate system prompt
if (prompt_field.edit_buffer) |*buffer| {
if (buffer.items.len == 0) {
self.validation_error = try self.allocator.dupe(u8, "System prompt is required");
return false;
}
}
return true;
}
/// Get list of selected tool names
pub fn getSelectedTools(self: *const AgentBuilderState, allocator: std.mem.Allocator) ![]const []const u8 {
const tools_field = &self.fields[3]; // Tools checkbox field
var selected = std.ArrayListUnmanaged([]const u8){};
// Note: No defer here - toOwnedSlice() transfers ownership to caller
if (tools_field.tool_options) |options| {
if (tools_field.tool_selected) |selected_flags| {
for (options, selected_flags) |tool_name, is_selected| {
if (is_selected) {
try selected.append(allocator, try allocator.dupe(u8, tool_name));
}
}
}
}
return selected.toOwnedSlice(allocator);
}
};
/// Get list of all available tool names dynamically
/// This automatically includes any new tools added to tools.zig
fn getAllToolNames(allocator: std.mem.Allocator) ![]const []const u8 {
// Import tools module to get tool list
const tools_module = @import("tools");
// Get all registered tool definitions
const all_tools = try tools_module.getAllToolDefinitions(allocator);
defer {
for (all_tools) |tool| {
allocator.free(tool.ollama_tool.function.name);
allocator.free(tool.ollama_tool.function.description);
allocator.free(tool.ollama_tool.function.parameters);
}
allocator.free(all_tools);
}
// Extract just the names
var names = std.ArrayListUnmanaged([]const u8){};
defer names.deinit(allocator);
for (all_tools) |tool| {
try names.append(allocator, try allocator.dupe(u8, tool.ollama_tool.function.name));
}
return names.toOwnedSlice(allocator);
}