From 5284abc3e71aa4ff1098a227f67b8fae5474399b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Sat, 9 May 2026 20:18:27 +0200 Subject: [PATCH] agent: consolidate listModels logic into Agent.zig --- src/agent.zig | 2 +- src/agent/Agent.zig | 35 +++++++++++++++++++++++++++++++++++ src/agent/list_models.zig | 33 --------------------------------- src/main.zig | 14 +------------- 4 files changed, 37 insertions(+), 47 deletions(-) delete mode 100644 src/agent/list_models.zig diff --git a/src/agent.zig b/src/agent.zig index ee2f1850..d9f555ca 100644 --- a/src/agent.zig +++ b/src/agent.zig @@ -6,8 +6,8 @@ pub const CommandExecutor = @import("agent/CommandExecutor.zig"); pub const Recorder = @import("agent/Recorder.zig"); pub const Verifier = @import("agent/Verifier.zig"); pub const SlashCommand = @import("agent/SlashCommand.zig"); -pub const listModels = @import("agent/list_models.zig").run; pub const autoDetectProvider = Agent.autoDetectProvider; +pub const listModels = Agent.listModels; test { _ = Agent; diff --git a/src/agent/Agent.zig b/src/agent/Agent.zig index 3c4db43e..fc304b85 100644 --- a/src/agent/Agent.zig +++ b/src/agent/Agent.zig @@ -959,6 +959,41 @@ fn resolveApiKey(provider: ?Config.AiProvider, needs_llm: bool) !?[:0]const u8 { return error.MissingApiKey; } +/// One-shot for `--list-models`: resolve the provider (explicit, then env +/// auto-detect), reject `--no-llm`, fetch chat-capable model IDs from the +/// provider, and print them to stdout (sorted, one per line). Shares +/// provider-resolution logic with `init` so the two paths can't drift. +pub fn listModels(allocator: std.mem.Allocator, opts: Config.Agent) !void { + if (opts.no_llm) { + log.fatal(.app, "list-models needs LLM", .{ + .hint = "--no-llm and --list-models conflict; drop --no-llm", + }); + return error.ConflictingFlags; + } + const provider = opts.provider orelse (try autoDetectProvider()) orelse { + log.fatal(.app, "list-models needs LLM", .{ + .hint = "set ANTHROPIC_API_KEY (or OPENAI_API_KEY / GOOGLE_API_KEY) or pass --provider", + }); + return error.MissingProvider; + }; + const api_key = zenai.provider.envApiKey(provider) orelse { + log.fatal(.app, "missing API key", .{ + .provider = @tagName(provider), + .env = envVarName(provider), + }); + return error.MissingApiKey; + }; + + var arena_state = std.heap.ArenaAllocator.init(allocator); + defer arena_state.deinit(); + const ids = try zenai.provider.listChatModelIds(allocator, arena_state.allocator(), provider, api_key, opts.base_url); + + var stdout_file = std.fs.File.stdout().writer(&.{}); + const w = &stdout_file.interface; + for (ids) |id| try w.print("{s}\n", .{id}); + try w.flush(); +} + /// Pick a provider from env keys when `--provider` was not given. /// Notices go to stderr unconditionally so users always know which mode they're in. pub fn autoDetectProvider() !?Config.AiProvider { diff --git a/src/agent/list_models.zig b/src/agent/list_models.zig deleted file mode 100644 index fc16c503..00000000 --- a/src/agent/list_models.zig +++ /dev/null @@ -1,33 +0,0 @@ -const std = @import("std"); -const zenai = @import("zenai"); -const log = @import("../log.zig"); - -const Allocator = std.mem.Allocator; -const ProviderKind = zenai.provider.ProviderKind; - -/// List the chat-capable models for `provider` and print their IDs to stdout, -/// one per line, sorted. The per-provider listing logic lives in -/// `zenai.provider.listChatModelIds`. -pub fn run(allocator: Allocator, provider: ProviderKind, base_url_override: ?[:0]const u8) !void { - const api_key = zenai.provider.envApiKey(provider) orelse { - log.fatal(.app, "missing API key", .{ - .provider = @tagName(provider), - .env = switch (provider) { - .anthropic => "ANTHROPIC_API_KEY", - .openai => "OPENAI_API_KEY", - .gemini => "GOOGLE_API_KEY or GEMINI_API_KEY", - .ollama => "(none)", - }, - }); - return error.MissingApiKey; - }; - - var arena_state = std.heap.ArenaAllocator.init(allocator); - defer arena_state.deinit(); - const ids = try zenai.provider.listChatModelIds(allocator, arena_state.allocator(), provider, api_key, base_url_override); - - var stdout_file = std.fs.File.stdout().writer(&.{}); - const w = &stdout_file.interface; - for (ids) |id| try w.print("{s}\n", .{id}); - try w.flush(); -} diff --git a/src/main.zig b/src/main.zig index 93b3680c..92de75c8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -64,19 +64,7 @@ fn run(allocator: Allocator, main_arena: Allocator) !void { return std.process.cleanExit(); }, .agent => |opts| if (opts.list_models) { - if (opts.no_llm) { - log.fatal(.app, "list-models needs LLM", .{ - .hint = "--no-llm and --list-models conflict; drop --no-llm", - }); - return args.printUsageAndExit(false); - } - const provider = opts.provider orelse (try lp.agent.autoDetectProvider()) orelse { - log.fatal(.app, "list-models needs LLM", .{ - .hint = "set ANTHROPIC_API_KEY (or OPENAI_API_KEY / GOOGLE_API_KEY) or pass --provider", - }); - return args.printUsageAndExit(false); - }; - try lp.agent.listModels(allocator, provider, opts.base_url); + try lp.agent.listModels(allocator, opts); return std.process.cleanExit(); }, else => {},