269 Commits
Author SHA1 Message Date
Adrià Arrufat 51d4387349 deps: bump isocline to 41bb0c5
An agent REPL whose terminal went away kept running. Killing the tmux
session it ran in, or dropping the ssh connection, left read() returning
0 on stdin while select() kept reporting it readable, and isocline
treated that as "no key yet": the editor redrew and read again in a tight
loop, allocating each time. Two of them left behind by a screencast had
grown past 4 GiB each within minutes.

isocline now ends the edit on EOF or a read error, as Ctrl-D on an empty
line does, so the REPL quits. Its signal handler also stops swallowing
termination signals it has no previous handler to chain to, which is
what let the SIGHUP from that hangup go unnoticed.

SIGTERM in the REPL still behaves like Ctrl-C: that is Sighandler's
no_hard_exit, and isocline never sees it.
2026-10-27 16:36:21 +01:00
Adrià Arrufat 4be7a93f35 agent: add xAI provider support
Bump zenai to include the xAI/Grok provider (PR #45). The provider is
auto-detected from XAI_API_KEY and defaults to grok-4.7 via
https://api.x.ai/v1. Add XAI_API_KEY to the api_keys_hint string shown
when no provider is configured.

Claude-Session: https://claude.ai/code/session_019RCx8JRGQcuMZYo9XW2xXV
2026-10-26 13:53:38 +01:00
Adrià Arrufat 035fe29848 Update brotli to v1.2.1
Picks up the upstream security fix in brotli 1.2.1.
2026-10-22 20:54:41 +02:00
Karl Seguin 51640f2f85 update libcurl to 8.23.0 2026-10-15 21:43:49 +08:00
Adrià Arrufat 706dc87e23 agent: handle Effort.null, bump zenai
zenai adds `Effort.null` (send no reasoning field, model default) and now
sends `.none` explicitly so reasoning can be turned off on models that
reason by default. Mistral defaults to `.null`, and its array content
from reasoning models parses.

Keep `.null` and `.none` as chosen in the /save synthesis turn instead
of bumping them.
2026-10-14 15:49:59 +02:00
Adrià Arrufat b7cef38f1e deps: bump zenai for connection reuse on JSON calls
zenai now asks for identity encoding, so a compressed reply no longer
stops std.http from pooling the connection. Calls behind a compressing
CDN, TypeSafe's included, stop paying a new TLS handshake each time.
2026-10-13 22:31:11 +02:00
Adrià Arrufat ce4c617623 Bump zenai to 470e8fb 2026-10-13 12:42:27 +02:00
Adrià Arrufat 1123b3eeee Bump zenai to 5dd2002
Picks up lightpanda-io/zenai#37: on a network that advertises an IPv6
default route without a global address, new LLM connections no longer
stall until the IPv6 SYN times out.
2026-10-09 12:02:12 +02:00
Adrià Arrufat 3d23c5df8a Bump zenai to e3e5864
Picks up lightpanda-io/zenai#36: a Gemini model turn without parts no
longer fails the request with MissingField.
2026-10-09 11:28:30 +02:00
Adrià Arrufat 928c9649bc agent: --trace writes one JSON line per step
Measuring the agent took a logging proxy in front of the provider: the
usage it prints is a run total, and nothing records per-tool latency,
result size or the page each call left behind. --trace PATH writes, as
each step ends, a JSON line per model call (phase, latency, tokens,
finish reason, tool calls requested) and per tool call (arguments with
$LP_* placeholders kept, latency, result size, error, navigated, page),
then a summary.

Model records come from zenai's before_request/after_response hooks.
Tool records come from Session.tool_observer, read by browser_tools.call
and by the script runtime when an async goto settles, so every path is
covered without opting in.

The $usage line now uses the trace's token fields: input (all input),
cached, cache_creation and output, replacing prompt (fresh input only),
completion and total, so the two can't be read with different meanings.
2026-10-06 22:44:29 +02:00
Adrià Arrufat e48160d400 agent: drop stale tool results within a run
Conversation.prune only runs between turns, and a --task run is one
turn, so every tool result piled up and was re-sent on every request:
on a browsing benchmark 135k of 138k chars of tool results in the last
request were from pages the model had moved past, and calls averaged
27-32k input tokens (up to 52k).

compactForRequest runs before each model request (zenai's new
before_request hook). It keeps the newest 3 large results, extract
output and anything under 2 KiB, and stubs older ones only once they
add up to 60 KiB, so history changes in batches and the provider's
prompt cache survives between them. Image expiry moves to the same
hook and shares the walk.
2026-10-06 21:47:27 +02:00
Adrià Arrufat 8fe219b99e deps: bump zenai for Anthropic base URLs without /v1
lightpanda-io/zenai#31: --provider anthropic --base-url now accepts the
Anthropic SDK form (no /v1) that Anthropic-compatible gateways document,
instead of returning HTTP 404.
2026-10-06 09:38:35 +02:00
Karl Seguin a428f9ae58 Merge pull request #3785 from lightpanda-io/nikneym/zstd
`curl`: enable zstd
2026-10-06 07:42:08 +08:00
Adrià Arrufat 9926051fd9 deps: bump zenai for Anthropic prompt caching via OpenRouter and Vercel
lightpanda-io/zenai#30: --provider openrouter (anthropic/* models) and
--provider vercel now send the gateway's cache hint, and cache writes
show up in the $usage line.
2026-10-05 22:57:44 +02:00
Halil Durak 258a47ad57 curl: enable zstd
All major browsers seem to option zstd in `Accept-Encoding`; I believe it won't hurt if we do so.
2026-10-05 16:26:38 +03:00
Karl Seguin f4544da758 Fix build
Update zig-v8-fork dependency

Optimize string.repeat

Provide arraybuffer allocation max size (pre-merging https://github.com/lightpanda-io/browser/pull/3751
since the zig-v8-fork now needs it).
2026-10-05 11:33:42 +08:00
Adrià Arrufat 10609b025a Upgrade to Zig 0.17
Dependencies:
- v8: zig-v8-fork 0.17 branch (lightpanda-io/zig-v8-fork#218).
- sqlite3: build the amalgamation directly; the allyourcodebase wrapper
  has no 0.17 support yet.
- pcre2: master commit with 0.17 support (no release yet).
- translate_c package (2.0.0) replaces the deprecated addTranslateC.
  default_init is set to keep 0.16's zero-initialized struct fields.

Build:
- b.pathFromRoot/build_root/args/sysroot are gone: resolve paths from
  b.root, use addPassthruArgs, declare configure-time file and directory
  dependencies, and poison the configure cache for the git version.
- Drop curl config values the template never used (now an error).
- Drop the 0.16 zip-fetch workaround from the Makefile and CI.
- -Doptimize=ReleaseFast -> -Doptimize=fast.

Language and std:
- @intFromEnum/@enumFromInt -> @backingInt/@fromBackingInt.
- Struct-of-arrays @typeInfo (field_names, field_types, decl_names,
  param_types, error_names).
- `a ** n` removed: @splat for arrays, string.repeat for strings.
- errdefer captures removed: split WebDriver.run and fulfillRedirect.
- @hasDecl only sees pub decls: make StyleManager's Spec.finish pub,
  otherwise it silently stops running.
- SafeAllocator replaces DebugAllocator; the test runner now has to
  initialize std.testing.allocator_instance itself.
- std.fmt.allocPrint/bufPrint -> Allocator.print/std.mem.print,
  dupeZ -> dupeSentinel, std.builtin -> std.lang, builtin.os ->
  builtin.target.os, zon.parse arena API, BufferFirstAllocator, and the
  remaining renames (getLastOrNull, bit_set, ascii.find*, meta.Int).
2026-10-03 16:09:42 +02:00
Pierre Tachoire 78aa336f5b prepare 1.0 release 2026-10-01 14:49:50 +02:00
Karl Seguin bad259a51f update v8 2026-09-30 11:23:47 +08:00
Karl Seguin 4c8e2f20bf v8: update to latest stable release 15.5.35.13
Depends on https://github.com/lightpanda-io/zig-v8-fork/pull/215 and
https://github.com/lightpanda-io/pkg/pull/3
2026-09-30 11:23:47 +08:00
Karl Seguin 0609111fd0 update pcre to latest release (10.48 -> 10.49) 2026-09-29 11:27:42 +08:00
Karl Seguin 78015e7d70 update boringssl to latest release (0.20260813.0) 2026-09-29 11:26:03 +08:00
Karl Seguin 9fd6afc08c update nghttp2 1.69.0 -> 1.70.0 2026-09-29 11:18:04 +08:00
Adrià Arrufat a6df7b04d0 agent: drop the OrcaRouter provider
zenai no longer ships the OrcaRouter preset. It still works as an
OpenAI-compatible endpoint via OPENAI_BASE_URL + OPENAI_API_KEY.
2026-09-26 23:58:30 +02:00
Adrià Arrufat ec31d4b711 Merge branch 'main' into prompt-audit-cleanup
# Conflicts:
#	build.zig.zon
#	src/browser/tools.zig
2026-09-25 11:13:12 +02:00
Adrià Arrufat b5896c259b Merge branch 'main' into agent-fixes
# Conflicts:
#	build.zig.zon
#	src/SemanticTree.zig
2026-09-25 10:44:05 +02:00
Karl Seguin db98e85ce3 v8: update for changes to v8__String__WriteUtf8
https://github.com/lightpanda-io/zig-v8-fork/pull/211
2026-09-25 11:52:42 +08:00
Adrià Arrufat b5a30be076 build: bump zenai
Picks up effort=none sending thinking disabled to Anthropic (zenai#19)
and the reasoning max_tokens floor on every provider (zenai#18).
ErrorDetail now carries the parsed error message instead of the raw
body.
2026-09-24 20:43:11 +02:00
Adrià Arrufat 9c583f1fb0 js: hand --locale to ICU instead of LC_ALL
A BCP 47 tag in LC_ALL is not a POSIX locale, so setlocale(LC_ALL, "")
failed for the rest of the process and for any child. In the REPL,
isocline took the terminal for non-UTF-8 and dropped every non-ASCII
keystroke. Set ICU's default locale through the new
v8__V8__SetDefaultLocale binding and leave the C library alone.

Pins zig-v8-fork to lightpanda-io/zig-v8-fork#209; CI links once that is
tagged and action.yml's zig-v8 is bumped.
2026-09-23 11:18:44 +02:00
Adrià Arrufat 3514d8d46b deps: bump isocline for the strdup_from_utf8 overflow
isocline copied the submitted line into a buffer one byte short whenever
the locale is not UTF-8. A line whose length the allocator serves
exactly, such as the 24-byte `/screenshot path=out.png`, corrupted the
heap on Enter.
2026-09-23 10:59:30 +02:00
Adrià Arrufat 2a9abd880f deps: bump zenai for the reasoning-token floor
Picks up the per-effort `max_tokens` floor, so a tight budget no longer
buys mostly thinking and a truncated answer, and the search clients
logging a non-2xx like the model clients already did.

The bump also carries zenai's `ErrorDetail.body` -> `.message` rename,
which the search failure path reads.
2026-09-22 16:20:24 +02:00
Adrià Arrufat 9b0b74b5e4 deps: bump zenai for the OpenRouter and OrcaRouter presets 2026-09-16 12:25:25 +02:00
Karl Seguin 3eb1fcbc12 update v8 dep 2026-09-16 10:12:27 +08:00
Karl Seguin ea0443cec9 internal: simplify v8.Value serialization
Requires: https://github.com/lightpanda-io/zig-v8-fork/pull/207

Inspired by https://github.com/lightpanda-io/browser/pull/3504 this simplifies
v8::Value serialization (e.g. as used in console.log(...)).

1 - It doesn't executes JS and thus can't have a side effect, which is otherwise
    possible if we invoke a getter or through a proxy

2 - It removes the debugValue debug-only path

(2) is potentially a loss in debug builds, but I think the usefulness of that
was always, at best. The upside is code elimination and consistency in how
values are reported in debug/release
2026-09-16 10:12:27 +08:00
Karl Seguin 65f8a83585 v8: call v8__Platform__NotifyIsolateShutdown on isolate shutdown
The v8 documentation says this "Has to be called", and claude flagged it as a
potential leak.

Required a new zig-v8-fork build (v0.5.5) which I released.
2026-09-11 10:20:20 +08:00
Adrià Arrufat c9329d5955 AdBlocker: run /regex/ filters with PCRE2
Filter lists carry a few hundred rules written as JavaScript regex
literals (24 in EasyList, 165 in uBO's badware list); they parsed but
were dropped as unsupported. PCRE2 reads that syntax as-is, `\/` and
friends included, its compiled patterns are immutable so the one
blocker shared by every HTTP client thread can run them, and 10.48
ships a build.zig for 0.16, so it is wired like sqlite3.

`Regex.Context` routes every PCRE2 allocation through the blocker's
allocator, which puts the compiled patterns under the test runner's
leak detection, and caps match and depth so a broken pattern costs a
false negative rather than a stalled request. As in uBO, a regex
tests the raw URL with the case-insensitive flag unless `$match-case`.

Regex filters are still never tokenized: they ride the fallback bucket.
2026-09-09 12:43:39 +02:00
Adrià Arrufat 94a47f3a4b build: update curl to 8.22.0
curl 8.22 moved the resolver sources into lib/vdns/, renamed the QUIC
backends to cf-ngtcp2*/cf-quiche, folded noproxy.c into proxy.c and
added a handful of new files. Regenerate the source list from the
tarball and drop the four config defines the 8.22 template no longer
declares (Zig errors on unused config header values).
2026-09-03 10:27:02 +02:00
Adrià Arrufat 0d1e3c0c8b agent: fix agent history in JS mode 2026-08-28 09:07:16 +02:00
Adrià Arrufat e70b316aea agent: use zenai's hasImage
zenai exports it now; drop the local copy and re-pin.
2026-08-27 15:03:19 +02:00
Adrià Arrufat e6203ff1fe agent: hand the model inline screenshots
zenai tool results now carry image parts (lightpanda-io/zenai#12, #13),
so the model-driven tool path opts into inline images: a screenshot
without `path` reaches the model as text plus the PNG, on every backend.
The slash-command path still needs `path`, its result goes to the
terminal.
2026-08-27 15:03:19 +02:00
Karl Seguin 6955751c10 Merge pull request #3285 from lightpanda-io/mem-vmhwm
mem: include v8 functions in binary layout optimization
2026-08-27 07:35:30 +08:00
Karl Seguin c8abf899ef mem: include v8 functions in binary layout optimization
https://github.com/lightpanda-io/browser/pull/3271 introduce an orderfile so
that hot sections of code were grouped together in the binary, resulting in more
efficient loading. But, it excluded v8 functions because v8 is compiled with
`-fno-unique-section-names` so each section gets the same name. Because of this
the orderfile can't target specific "hot" or "cold" functions.

As a follow up to 3271, I thought we'd be able to re-compile v8 without that
flag, but:

1 - The flag isn't directly exposed by v8, so we'd need to change v8's own build
    BUILD.gn in our build process
2 - It would make the .a file ~ 40MB larger (though the final lightpanda binary
    would stay the same size
3 - If we wanted to created two .a files (one with -fno-unique-section-names and
    one without), it would require a full rebuild (for both x86-64 and arm).

I didn't love those compromises, so I asked Claude if the sections names could
be generated in a separate file and then that could be used when generating out
lightpanda.ld. What claude came up with was a small Zig script
(mark_hot_sections.zig) which is now run as part of the build. It takes the
v8.a file (which still has `-fno-unique-section-names`), it takes a text list
of hot functions, and it re-generates v8.a with a special .text.hot and
.rodata.hot sections. lightpanda.ld can include these.

This PR depends on https://github.com/lightpanda-io/zig-v8-fork/pull/202 but
202 doesn't require a v8 rebuild. It merely allows prebuilt_v8_path to be
a lazypath, which we need because the prebuilt_v8_path that we pass is now
generated from this build script.
2026-08-26 12:53:45 +08:00
Adrià Arrufat 42c3cbf20b search: default to Keenable's keyless endpoint, drop the DuckDuckGo scrape
The DuckDuckGo HTML endpoint the search tool fell back to is disallowed by
its robots.txt, so it is removed: the `duckduckgo` engine, the scrape via
`performGoto`, and the tool's browser-side dependency (`execSearch` no
longer needs a session). In its place `.auto` is a single walk over the
API engines in table order — each when its key is set — with Keenable
last, keyed if `KEENABLE_API_KEY` is set and its public endpoint
otherwise. Search therefore always has a rung with zero configuration,
and a set key is never shadowed by a keyless retry.

Every search now goes through zenai's `std.http.Client`, which had no
timeout at all; a stalled response would have blocked the MCP server's
shared browser thread for every session. zenai now bounds each attempt
(lightpanda-io/zenai#10, pinned here), and the tool's `timeout` argument
drives it, defaulting to 10 s. Retries are a property of the call path:
off in the cascade, where the next rung is the retry, and the engine
default for an explicit engine.

Cleanups from the #3252 review: `keyless` is derived from the client's
`api_key` type instead of a hand-set table flag; `engineIndex` no longer
depends on enum declaration order; one `searchKeyStatus` query replaces
three pub helpers for the REPL; the engine order and env-var list in the
tool description and `/searchEngine` help are generated from the table;
an empty result title renders as its URL instead of `****`.
2026-08-25 09:35:26 +02:00
Adrià Arrufat f1742ad1af search: repin zenai to the merged commit and finish the review follow-ups
- build.zig.zon points at lightpanda-io/zenai's merge commit of zenai#9
  (same tree, so the hash is unchanged)
- the keyless rung is no longer skipped when KEENABLE_API_KEY is set:
  the public endpoint has its own rate-limit regime, so it is worth
  trying after a failed keyed attempt — which the search tool
  description already promised
- keenable retries are disabled: the fallback cascade is the retry
  mechanism, and honoring the public endpoint's 60s Retry-After would
  stall the search tool short of the DDG rung
- stale SearchEngine/searchExplicit doc comments updated for keyless
2026-08-24 10:39:47 +02:00
Ilya Bogin 56e5daed54 search: track zenai review changes for the keenable engine
zenai's review round made keyless explicit in the client's type
(init takes ?[]const u8; null selects the public endpoint) and removed
the always-empty description field from results:

- the .auto keyless rung and searchExplicit pass null instead of "";
  the null-key call is comptime-gated to keyless-capable engines and
  apiSearch's key parameter is anytype to admit both shapes
- a set-but-empty KEENABLE_API_KEY is now keyed and fails loudly
  instead of silently switching to the keyless rate-limit regime
- formatKeenableMarkdown reads snippet only (description no longer
  exists to fall back to)
- build.zig.zon repinned to the reviewed zenai commit
2026-08-24 10:44:38 +03:00
Ilya Bogin 6cbe30b088 search: add Keenable engine with a keyless rung above the DDG scrape
Adds keenable to the search tool's engines (KEENABLE_API_KEY, tried
after brave/tavily/exa in .auto). Unlike the other engines it also
answers without any key: its client routes an empty key to the public
endpoint (rate-limited per client IP), so .auto now tries that as the
last rung before the DuckDuckGo scrape, and an explicit
/searchEngine keenable works keyless instead of erroring. Engine table
entries gain init_options so the Keenable client can carry the
lightpanda attribution header.

Needs the zenai Keenable search client (lightpanda-io/zenai PR #9); the
zon pin points at that branch until it lands.
2026-08-23 15:42:06 +03:00
Adrià Arrufat 9a57a34b46 tools: cap exa search highlights to 3 sentences 2026-08-19 16:42:43 +02:00
Adrià Arrufat d76ead300a browser: add exa search engine support 2026-08-18 21:10:45 +02:00
Karl Seguin c95135b5c2 update v8 dep 2026-08-13 19:31:37 +08:00
Karl Seguin af99e845df dev: support for pre-build shared library
Supports prebuild-v8 shared library and adds a `download-v8-shared` make target.
Depends on: https://github.com/lightpanda-io/zig-v8-fork/pull/198

The auto-discovery of the .so is done in the build.zig, which is different than
the auto-discovery of the .a which is done in the Makefile. This is intentional
and if we inverse -Ddev_fast so that it's on by default, the .a's auto discovery
will be moved to build.zig for consistency and explicitness. As-is, we have no
way to tell whether -Dprebuilt_v8_path=v8.a is being injected by Makefile or
is explicit (which isn't a problem so long as we're not doing dev_fast by
default).
2026-08-13 19:28:21 +08:00