diff --git a/src/agent/Agent.zig b/src/agent/Agent.zig index e3956265..7a9c483a 100644 --- a/src/agent/Agent.zig +++ b/src/agent/Agent.zig @@ -677,7 +677,7 @@ fn setProvider(self: *Agent, credentials: Credentials) !void { _ = completionModels(self, self.allocator); } -const SaveMode = enum { replace, append }; +const SaveMode = enum { append, replace }; const PathAndMode = struct { path: []const u8, mode: SaveMode }; @@ -818,12 +818,11 @@ fn promptSaveMode(self: *Agent, path: []const u8) ?SaveMode { var header_buf: [256]u8 = undefined; const header = std.fmt.bufPrint(&header_buf, "{s} already exists. Pick save mode:", .{path}) catch "File already exists. Pick save mode:"; - const labels: []const []const u8 = &.{ "replace", "append" }; - const idx = Terminal.promptNumberedChoice(header, labels, null) catch { + const idx = Terminal.promptNumberedChoice(header, std.meta.fieldNames(SaveMode), 0) catch { self.terminal.printInfo("Save cancelled.", .{}); return null; }; - return if (idx == 0) .replace else .append; + return @enumFromInt(idx); } fn writeSaveFile(self: *Agent, path: []const u8, mode: SaveMode) !void { @@ -848,18 +847,12 @@ fn failSave(self: *Agent, reason: []const u8) void { /// LLM-synthesized `/save`: hand the model the builtin catalog, the full /// conversation, and the deterministic record of what ran, then write the -/// idiomatic script it returns. Always replaces the target file. +/// idiomatic script it returns. fn synthesizeSave(self: *Agent, arena: std.mem.Allocator, filename: ?[]const u8, prompt: ?[]const u8) void { const provider_client = self.ai_client.?; - const path: []const u8 = blk: { - if (filename) |f| break :blk f; - if (self.save_path) |p| break :blk p; - break :blk randomSaveFilename(arena) catch |err| { - self.terminal.printError("failed to choose save filename: {s}", .{@errorName(err)}); - return; - }; - }; + const resolved = self.resolveSavePathAndMode(arena, filename) orelse return; + const path = resolved.path; self.ensureSystemPrompt() catch return self.failSave("out of memory"); @@ -922,7 +915,7 @@ fn synthesizeSave(self: *Agent, arena: std.mem.Allocator, filename: ?[]const u8, // The save turn is a meta-action; keep it out of the ongoing conversation. self.rollbackMessages(baseline); - writeContentFile(path, script, .replace) catch |err| { + writeContentFile(path, script, resolved.mode) catch |err| { self.terminal.printError("failed to save {s}: {s}", .{ path, @errorName(err) }); return; }; diff --git a/src/agent/Terminal.zig b/src/agent/Terminal.zig index bada83cf..1a7d678a 100644 --- a/src/agent/Terminal.zig +++ b/src/agent/Terminal.zig @@ -905,7 +905,7 @@ pub fn interactiveTty() bool { /// Numbered TTY picker. `default` (if set) marks that row "(default)" and /// makes Enter start on that index. Up/Down moves the active row; Enter /// selects it. Numbered input still works for users who prefer typing. -pub fn promptNumberedChoice(header: []const u8, items: []const []const u8, default: ?usize) !usize { +pub fn promptNumberedChoice(header: []const u8, items: []const [:0]const u8, default: ?usize) !usize { if (items.len == 0) return error.NoChoice; const valid_default: ?usize = if (default) |d| if (d < items.len) d else null else null; if (interactiveTty()) { @@ -918,7 +918,7 @@ pub fn promptNumberedChoice(header: []const u8, items: []const []const u8, defau } /// Line-oriented fallback. Errors with NoChoice after 3 invalid attempts. -fn promptNumberedChoiceLine(header: []const u8, items: []const []const u8, default: ?usize) !usize { +fn promptNumberedChoiceLine(header: []const u8, items: []const [:0]const u8, default: ?usize) !usize { var stdin_buf: [128]u8 = undefined; var stdin = std.fs.File.stdin().reader(&stdin_buf); @@ -1005,7 +1005,7 @@ const RawTerminal = struct { } }; -fn promptInteractiveChoice(header: []const u8, items: []const []const u8, default: ?usize) !usize { +fn promptInteractiveChoice(header: []const u8, items: []const [:0]const u8, default: ?usize) !usize { var raw = try RawTerminal.enable(); defer raw.restore(); @@ -1046,7 +1046,7 @@ fn moveChoiceRenderStart(line_count: usize) void { } } -fn renderChoice(header: []const u8, items: []const []const u8, default: ?usize, selected: usize, first_render: bool) void { +fn renderChoice(header: []const u8, items: []const [:0]const u8, default: ?usize, selected: usize, first_render: bool) void { if (!first_render) moveChoiceRenderStart(items.len + 2); std.debug.print(ansi.clear_line ++ "{s}\r\n", .{header}); for (items, 0..) |item, idx| { diff --git a/src/agent/settings.zig b/src/agent/settings.zig index a9d5f09a..ddd914dd 100644 --- a/src/agent/settings.zig +++ b/src/agent/settings.zig @@ -82,7 +82,7 @@ pub fn resolveCredentials(opts: Config.Agent, remembered: ?Remembered, allow_pic return .{ .credentials = found[0], .source = .detected }; } - var names: [zenai.provider.default_candidates.len][]const u8 = undefined; + var names: [zenai.provider.default_candidates.len][:0]const u8 = undefined; for (found, 0..) |cred, i| names[i] = @tagName(cred.provider); std.debug.print("\n", .{}); const idx = Terminal.promptNumberedChoice(" Select a provider:", names[0..found.len], 0) catch {