mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
agent: add --list-models flag
This commit is contained in:
@@ -39,8 +39,8 @@
|
||||
.hash = "N-V-__8AAJ4HAgCX79UDBfNwhqAqUVoGC44ib6UYa18q6oa_",
|
||||
},
|
||||
.zenai = .{
|
||||
.url = "git+https://github.com/lightpanda-io/zenai.git#37cad37184736fafea8afcff9d1593b7009a71eb",
|
||||
.hash = "zenai-0.0.0-iOY_VGRyAwCRQnr-DCFhnQBC-eM2JVHjS1mUKWBHTWmm",
|
||||
.url = "git+https://github.com/lightpanda-io/zenai.git#580f172579e211b07ecaff311149543e6a47a1f3",
|
||||
.hash = "zenai-0.0.0-iOY_VASPAwAYCAzeFaDegm0F227A-I-hYNFnZpV1yw3A",
|
||||
},
|
||||
.libidn2 = .{
|
||||
.url = "https://ftp.gnu.org/gnu/libidn/libidn2-2.3.8.tar.gz",
|
||||
|
||||
@@ -200,6 +200,7 @@ const Commands = cli.Builder(.{
|
||||
.{ .name = "task", .type = ?[]const u8 },
|
||||
.{ .name = "task_attachments", .type = []const u8, .multiple = true },
|
||||
.{ .name = "verbosity", .type = AgentVerbosity, .default = AgentVerbosity.low },
|
||||
.{ .name = "list_models", .type = bool },
|
||||
},
|
||||
.shared_options = CommonOptions,
|
||||
},
|
||||
@@ -783,6 +784,10 @@ pub fn printUsageAndExit(self: *const Config, success: bool) void {
|
||||
\\ a positional script is present, any new commands
|
||||
\\ entered in the REPL are appended to that file.
|
||||
\\
|
||||
\\--list-models Print the model IDs usable with `agent` for
|
||||
\\ --provider, one per line, sorted, and exit.
|
||||
\\ Requires --provider; the API key must be set.
|
||||
\\
|
||||
\\--verbosity Stderr chatter level: low, medium, high.
|
||||
\\ Default: low. In a TTY REPL, low shows a single-
|
||||
\\ line agent indicator and a per-turn summary;
|
||||
|
||||
@@ -6,6 +6,7 @@ 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;
|
||||
|
||||
test {
|
||||
_ = Agent;
|
||||
|
||||
96
src/agent/list_models.zig
Normal file
96
src/agent/list_models.zig
Normal file
@@ -0,0 +1,96 @@
|
||||
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 models usable with the lightpanda agent for `provider` and print
|
||||
/// their IDs to stdout, one per line, sorted alphabetically. Returns
|
||||
/// `error.MissingApiKey` when the provider's env var isn't set; other errors
|
||||
/// propagate from the underlying HTTP call.
|
||||
///
|
||||
/// Filtering uses each provider's `isChatModel` predicate from zenai.
|
||||
/// Ollama is unfiltered — local catalogs don't follow a naming convention
|
||||
/// the heuristic could rely on.
|
||||
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 arena = arena_state.allocator();
|
||||
|
||||
var ids: std.ArrayList([]const u8) = .empty;
|
||||
|
||||
switch (provider) {
|
||||
.anthropic => {
|
||||
var client = zenai.anthropic.Client.init(allocator, api_key, .{});
|
||||
defer client.deinit();
|
||||
var resp = try client.listModels();
|
||||
defer resp.deinit();
|
||||
for (resp.value.data orelse &.{}) |m| {
|
||||
if (!zenai.anthropic.Client.isChatModel(m)) continue;
|
||||
if (m.id) |id| try ids.append(arena, try arena.dupe(u8, id));
|
||||
}
|
||||
},
|
||||
.openai => {
|
||||
var client = zenai.openai.Client.init(allocator, api_key, if (base_url_override) |u| .{ .base_url = u } else .{});
|
||||
defer client.deinit();
|
||||
var resp = try client.listModels();
|
||||
defer resp.deinit();
|
||||
for (resp.value.data orelse &.{}) |m| {
|
||||
if (!zenai.openai.Client.isChatModel(m)) continue;
|
||||
if (m.id) |id| try ids.append(arena, try arena.dupe(u8, id));
|
||||
}
|
||||
},
|
||||
.ollama => {
|
||||
const opts: zenai.openai.Client.InitOptions = if (base_url_override) |u|
|
||||
.{ .base_url = u }
|
||||
else
|
||||
.{ .base_url = "http://localhost:11434/v1" };
|
||||
var client = zenai.openai.Client.init(allocator, api_key, opts);
|
||||
defer client.deinit();
|
||||
var resp = try client.listModels();
|
||||
defer resp.deinit();
|
||||
for (resp.value.data orelse &.{}) |m| {
|
||||
if (m.id) |id| try ids.append(arena, try arena.dupe(u8, id));
|
||||
}
|
||||
},
|
||||
.gemini => {
|
||||
var client = zenai.gemini.Client.init(allocator, api_key, .{});
|
||||
defer client.deinit();
|
||||
var resp = try client.listModels(.{});
|
||||
defer resp.deinit();
|
||||
for (resp.value.models orelse &.{}) |m| {
|
||||
if (!zenai.gemini.Client.isChatModel(m)) continue;
|
||||
const name = m.name orelse continue;
|
||||
// Gemini returns "models/<id>"; strip the prefix so the
|
||||
// output is pipe-ready into `--model`.
|
||||
const stripped = if (std.mem.startsWith(u8, name, "models/")) name["models/".len..] else name;
|
||||
try ids.append(arena, try arena.dupe(u8, stripped));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
std.mem.sort([]const u8, ids.items, {}, lessThan);
|
||||
|
||||
var stdout_file = std.fs.File.stdout().writer(&.{});
|
||||
const w = &stdout_file.interface;
|
||||
for (ids.items) |id| try w.print("{s}\n", .{id});
|
||||
try w.flush();
|
||||
}
|
||||
|
||||
fn lessThan(_: void, a: []const u8, b: []const u8) bool {
|
||||
return std.mem.lessThan(u8, a, b);
|
||||
}
|
||||
@@ -63,6 +63,14 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
|
||||
try stdout.interface.print("{s}\n", .{lp.build_config.version});
|
||||
return std.process.cleanExit();
|
||||
},
|
||||
.agent => |opts| if (opts.list_models) {
|
||||
const provider = opts.provider orelse {
|
||||
log.fatal(.app, "missing --provider", .{ .flag = "--list-models" });
|
||||
return args.printUsageAndExit(false);
|
||||
};
|
||||
try lp.agent.listModels(allocator, provider, opts.base_url);
|
||||
return std.process.cleanExit();
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user