mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-01 02:06:17 -04:00
Merge branch 'agent' into agent-page-syntax
This commit is contained in:
@@ -35,8 +35,8 @@
|
||||
.hash = "sqlite3-3.51.0-DMxLWssOAABZ8cAvU_LfBIbp0kZjm824PU8sSLXpEDdr",
|
||||
},
|
||||
.zenai = .{
|
||||
.url = "git+https://github.com/lightpanda-io/zenai.git#3f9315fdf390b37ee8bda55f7e454327b0c1d931",
|
||||
.hash = "zenai-0.0.0-iOY_VJSBBADUifICag2s31rv-XZZ924Be_MVXXxFx22S",
|
||||
.url = "git+https://github.com/lightpanda-io/zenai.git#4cb8958edb451434271e98866624fd5bb386234f",
|
||||
.hash = "zenai-0.0.0-iOY_VJ2IBAA2mVbT8OWrftQKV8-r4X4H1746f-lGIZ7O",
|
||||
},
|
||||
.isocline = .{
|
||||
.url = "git+https://github.com/arrufat/isocline?ref=lightpanda#832a9fe25f5f4458fcc47b5acc7c21db669c2f47",
|
||||
|
||||
@@ -259,6 +259,9 @@ total_usage: zenai.provider.Usage = .{},
|
||||
/// Set when the last turn ended in a model refusal (safety stop).
|
||||
last_turn_refused: bool = false,
|
||||
available_providers: []const []const u8,
|
||||
/// Lazily-probed Ollama reachability for `/provider` completion, cached so the
|
||||
/// per-keystroke hinter probes the local server at most once per session.
|
||||
ollama_completable: ?bool = null,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, app: *App, opts: Config.Agent) !*Agent {
|
||||
var providers_buf: [@typeInfo(Config.AiProvider).@"enum".fields.len]Credentials = undefined;
|
||||
@@ -311,14 +314,15 @@ pub fn init(allocator: std.mem.Allocator, app: *App, opts: Config.Agent) !*Agent
|
||||
// "No API key detected" for a run that does not need one.
|
||||
const resolve = !opts.no_llm and requires_llm;
|
||||
|
||||
// Print the banner before provider resolution so it precedes any
|
||||
// interactive "Select a provider" prompt. On error paths (missing key / no
|
||||
// key detected) resolveCredentials prints its own message; banner skipped.
|
||||
if (will_repl and (!resolve or settings.wouldResolve(allocator, opts, remembered))) {
|
||||
welcome.print(resolve);
|
||||
}
|
||||
// Print the banner before resolution so it precedes the interactive picker.
|
||||
// The Ollama-only path never prompts, so its banner is deferred (below) to
|
||||
// avoid probing the local server a second time just to decide ordering.
|
||||
const banner_before = will_repl and (!resolve or settings.hasDetectableKey(opts, remembered));
|
||||
if (banner_before) welcome.print(resolve);
|
||||
|
||||
const resolved: ?settings.ResolvedProvider = if (resolve) try settings.resolveCredentials(allocator, opts, remembered, will_repl) else null;
|
||||
|
||||
if (will_repl and !banner_before and resolved != null) welcome.print(resolve);
|
||||
const llm: ?Credentials = if (resolved) |r| r.credentials else null;
|
||||
|
||||
if (llm == null and requires_llm) {
|
||||
@@ -889,6 +893,11 @@ fn handleProvider(self: *Agent, _: std.mem.Allocator, rest: []const u8) void {
|
||||
self.terminal.printError("no API key for {s}; set {s}", .{ @tagName(provider), zenai.provider.envVarName(provider) });
|
||||
return;
|
||||
};
|
||||
// Ollama's key is a placeholder, so probe the server instead of trusting it.
|
||||
if (provider == .ollama and settings.detectOllama(self.allocator, self.model_base_url) == null) {
|
||||
self.terminal.printError("no Ollama server with a pulled model at {s}", .{self.model_base_url orelse zenai.provider.ollama_default_base_url});
|
||||
return;
|
||||
}
|
||||
self.setProvider(.{ .provider = provider, .key = key }) catch |err| {
|
||||
self.terminal.printError("failed to set provider: {s}", .{@errorName(err)});
|
||||
};
|
||||
@@ -1713,14 +1722,29 @@ const ModelCompletions = struct {
|
||||
/// avoid reading environment variables on each autocomplete keypress.
|
||||
fn completionProviders(context: *anyopaque, arena: std.mem.Allocator) []const []const u8 {
|
||||
const self: *Agent = @ptrCast(@alignCast(context));
|
||||
const names = arena.alloc([]const u8, self.available_providers.len + 1) catch return &.{};
|
||||
const ollama = self.ollamaCompletable();
|
||||
const names = arena.alloc([]const u8, self.available_providers.len + 1 + @as(usize, @intFromBool(ollama))) catch return &.{};
|
||||
for (self.available_providers, 0..) |p, i| {
|
||||
names[i] = arena.dupe(u8, p) catch return &.{};
|
||||
}
|
||||
names[self.available_providers.len] = provider_off_keyword;
|
||||
var n = self.available_providers.len;
|
||||
if (ollama) {
|
||||
names[n] = @tagName(Config.AiProvider.ollama);
|
||||
n += 1;
|
||||
}
|
||||
names[n] = provider_off_keyword;
|
||||
return names;
|
||||
}
|
||||
|
||||
/// Ollama joins `/provider` completions only when a server actually answers,
|
||||
/// since its env key is a placeholder. Probed once and cached (see field).
|
||||
fn ollamaCompletable(self: *Agent) bool {
|
||||
if (self.ollama_completable) |v| return v;
|
||||
const v = settings.detectOllama(self.allocator, self.model_base_url) != null;
|
||||
self.ollama_completable = v;
|
||||
return v;
|
||||
}
|
||||
|
||||
/// `CompletionSource.models`. Blocks on a one-time fetch per provider, caching
|
||||
/// success or empty so the per-keystroke hinter pays the round-trip once.
|
||||
fn completionModels(context: *anyopaque, _: std.mem.Allocator) []const []const u8 {
|
||||
|
||||
@@ -41,8 +41,10 @@ pub const ResolvedProvider = struct {
|
||||
};
|
||||
|
||||
/// Ollama needs no API key, so it's excluded from env detection
|
||||
/// (`default_candidates`) and only probed here as a last resort.
|
||||
fn detectOllama(allocator: std.mem.Allocator, base_url: ?[:0]const u8) ?Credentials {
|
||||
/// (`default_candidates`) and only probed here. Null means no server answered
|
||||
/// with a pulled model — the only honest signal of Ollama availability, since
|
||||
/// its env key is a constant placeholder.
|
||||
pub fn detectOllama(allocator: std.mem.Allocator, base_url: ?[:0]const u8) ?Credentials {
|
||||
const key = zenai.provider.envApiKey(.ollama) orelse return null;
|
||||
var arena: std.heap.ArenaAllocator = .init(allocator);
|
||||
defer arena.deinit();
|
||||
@@ -51,14 +53,14 @@ fn detectOllama(allocator: std.mem.Allocator, base_url: ?[:0]const u8) ?Credenti
|
||||
return .{ .provider = .ollama, .key = key };
|
||||
}
|
||||
|
||||
/// Returns true when resolveCredentials would succeed (no error, non-null).
|
||||
/// Used by callers that need to print a banner before calling resolveCredentials.
|
||||
pub fn wouldResolve(allocator: std.mem.Allocator, opts: Config.Agent, remembered: ?Remembered) bool {
|
||||
/// True when a non-Ollama provider key is available (flag, remembered, or
|
||||
/// env-detected). Skips the Ollama probe so it isn't run twice at startup; the
|
||||
/// interactive picker only fires on detected keys, which this still catches.
|
||||
pub fn hasDetectableKey(opts: Config.Agent, remembered: ?Remembered) bool {
|
||||
if (opts.provider) |p| return zenai.provider.envApiKey(p) != null;
|
||||
if (remembered) |r| if (r.provider) |p| if (zenai.provider.envApiKey(p)) |_| return true;
|
||||
var buf: [zenai.provider.default_candidates.len]Credentials = undefined;
|
||||
if (zenai.provider.detectKeys(&buf, zenai.provider.default_candidates).len > 0) return true;
|
||||
return detectOllama(allocator, opts.base_url) != null;
|
||||
return zenai.provider.detectKeys(&buf, zenai.provider.default_candidates).len > 0;
|
||||
}
|
||||
|
||||
/// Precedence: `--provider` > remembered (if its key is still set) > first
|
||||
@@ -143,8 +145,10 @@ pub fn saveRemembered(remembered: Remembered) !void {
|
||||
try std.fs.cwd().writeFile(.{ .sub_path = remembered_path, .data = w.buffered() });
|
||||
}
|
||||
|
||||
/// Cloud providers with a key set. Ollama is excluded — its availability needs
|
||||
/// a live probe (`detectOllama`), too costly for an unconditional startup scan.
|
||||
pub fn availableProviders(buf: []Credentials) []Credentials {
|
||||
return zenai.provider.detectKeys(buf, std.enums.values(Config.AiProvider));
|
||||
return zenai.provider.detectKeys(buf, zenai.provider.default_candidates);
|
||||
}
|
||||
|
||||
pub fn resolveModelName(opts: Config.Agent, resolved: ?ResolvedProvider, remembered: ?Remembered) []const u8 {
|
||||
|
||||
Reference in New Issue
Block a user