agent: consolidate listModels logic into Agent.zig

This commit is contained in:
Adrià Arrufat
2026-05-09 20:18:27 +02:00
parent 938795ec8d
commit 5284abc3e7
4 changed files with 37 additions and 47 deletions

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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();
}

View File

@@ -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 => {},