From 92a6b698fce611bfdd5d521edf5b246b32c90cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Tue, 22 Sep 2026 15:50:15 +0200 Subject: [PATCH] agent: --search-engine, and say when the engine has no key The search engine could only be set from the REPL's `/searchEngine`, so a one-shot run had no way to pin one -- and a benchmark that wants its results to mean something has to record which API answered. `/searchEngine` also carried the only warning about key state, which is the half that matters more. A keyless engine is not an error and starts fine, so a run that silently falls back to a rate-limited public endpoint looks exactly like a working one until every search begins failing, and then it looks like a bad agent. Both messages now fire wherever the engine is resolved, not just from the command. `resolveSearchEngine`'s doc comment said there was no CLI flag. There is one now. --- src/Config.zig | 2 ++ src/agent/Agent.zig | 10 +++++++++- src/agent/settings.zig | 4 ++-- src/help.zon | 6 ++++++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Config.zig b/src/Config.zig index 708236f5b..493cb8c1a 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -347,6 +347,7 @@ pub const AiProvider = std.meta.Tag(zenai.provider.Client); /// in `Agent.init` (explicit flag > remembered > mode default), so there is /// no Config-level accessor like `agentVerbosity`. pub const Effort = zenai.provider.Effort; +pub const SearchEngine = @import("browser/tools.zig").SearchEngine; /// Controls how chatty `agent` mode is on stderr. pub const AgentVerbosity = enum { @@ -470,6 +471,7 @@ const Commands = cli.Builder(.{ .{ .name = "attach", .short = 'a', .type = []const u8, .multiple = true }, .{ .name = "verbosity", .type = ?AgentVerbosity }, .{ .name = "effort", .type = ?Effort }, + .{ .name = "search_engine", .type = ?SearchEngine }, .{ .name = "list_models", .type = bool }, .{ .name = "no_llm", .type = bool }, }, diff --git a/src/agent/Agent.zig b/src/agent/Agent.zig index ae870f162..9f4f0f219 100644 --- a/src/agent/Agent.zig +++ b/src/agent/Agent.zig @@ -289,7 +289,15 @@ pub fn init(allocator: std.mem.Allocator, app: *App, opts: Config.Agent) !*Agent const effort = settings.resolveEffort(opts, remembered, will_repl, if (resolved) |r| r.credential.provider else null); const verbosity = settings.resolveVerbosity(opts, remembered); const stream_enabled = settings.resolveStream(remembered); - browser_tools.search_engine = settings.resolveSearchEngine(remembered); + browser_tools.search_engine = opts.search_engine orelse settings.resolveSearchEngine(remembered); + // Only the REPL's `/searchEngine` used to say this. A keyless engine that + // has hit its cap fails every search, and without a word here that reads + // as a bad agent rather than a missing key. + if (browser_tools.searchKeyStatus(browser_tools.search_engine)) |key| switch (key.state) { + .set => {}, + .keyless => log.info(.app, "keyless search endpoint", .{ .env_var = key.env_var, .limit = "rate-limited per client IP" }), + .missing => log.warn(.app, "search key missing", .{ .env_var = key.env_var, .engine = @tagName(browser_tools.search_engine) }), + }; if (resolved) |r| { if (r.source == .picked) { diff --git a/src/agent/settings.zig b/src/agent/settings.zig index 1e90b714b..8c6d00b9e 100644 --- a/src/agent/settings.zig +++ b/src/agent/settings.zig @@ -369,8 +369,8 @@ pub fn resolveStream(remembered: ?Remembered) bool { return true; } -/// Precedence: remembered `.lp-agent.zon` value > default (auto). No CLI -/// flag — the REPL `/searchEngine` command sets and persists it. +/// Precedence: `--search-engine` > remembered `.lp-agent.zon` value > +/// default (auto). The REPL `/searchEngine` command also sets and persists it. pub fn resolveSearchEngine(remembered: ?Remembered) lp.tools.SearchEngine { if (remembered) |r| if (r.search_engine) |e| return e; return .auto; diff --git a/src/help.zon b/src/help.zon index fc329aafb..d59303992 100644 --- a/src/help.zon +++ b/src/help.zon @@ -297,6 +297,12 @@ \\ it to PATH, instead of printing the answer. Replay it later with \\ `run PATH` (no LLM calls). Overwrites PATH if it exists. \\ Requires --task. + \\ --search-engine + \\ Which web search API the `search` tool uses. Default: auto, + \\ which takes the first with a key in the environment, in the + \\ order brave, tavily, exa, keenable. `keenable` also works with + \\ no key at all, through a rate-limited public endpoint. + \\ Allowed values: auto, brave, tavily, exa, keenable. \\ --system-prompt \\ Override the default system prompt. \\ --task