Skip to content

Releases: ChrisZhangJin/nanopi

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 07:22

Full Changelog: v0.12.1...v0.13.0

v0.12.1

Choose a tag to compare

@github-actions github-actions released this 11 Sep 06:24

Full Changelog: v0.10.1...v0.12.1

v0.12.0-preview — plugins can act, not just observe

Choose a tag to compare

@github-actions github-actions released this 09 Sep 08:11

nanopi v0.12.0-preview

A preview of v0.12.0. The headline is plugins: a WASM extension can
now do things rather than only see them, and /reload picks up plugin
changes without a restart.

Feature-complete and tested — 901 automated tests green (0 ignored, 0
warnings on every build) plus a 64-row manual pass against a live model,
which found and fixed seven defects. Tagged -preview rather than
0.12.0 because it has only been exercised on Linux against one
provider surface; see Known gaps at the bottom.


Plugins can now act, not just observe

v0.11 gave plugins tools, slash commands and lifecycle events — all of
it read-only apart from returning a tool result. v0.12 adds an outbound
surface in five stages. Every capability is deny-by-default and
named in the plugin's [[extensions]] entry.

Grant Capability
allow_store host-store-get / host-store-set — a keyed, quota-bounded, crash-safe store (temp file + rename), one file per plugin, surviving restarts and /reload
allow_context host-set-context — contribute up to 4 KiB to the model's system prompt, attributed to the plugin
allow_tools = [...] host-call-tool — call named built-in tools, per tool, never wholesale
allow_send_message host-send-user-message — start or steer a turn, loop-guarded and capped per session

host-notify needs no grant: it is output rather than access, so it is
bounded by a per-turn rate limit instead of a gate, and the host
applies the attribution prefix so one plugin cannot pose as another.

/tools gained a Plugin grants section, so what a plugin may do is
never invisible. A plugin granted nothing still gets a row reading no grants — "installed and powerless" and "not installed" must not look
the same.

Three details that are load-bearing rather than decorative:

  • A plugin's tool call carries a ToolCallOrigin and is deliberately
    not written to the session transcript. A tool_call the model never
    emitted replays as a tool_use nobody answered, which is the exact
    shape that used to make sessions permanently unresumable. The
    consequence is stated plainly: --continue will not show what a plugin
    did, so the host prints a live disclosure line per plugin-initiated
    call instead — on its own budget, so a plugin cannot suppress it.
  • Context contributions are disclosed, and the host neutralises any
    header-shaped line in the body.
  • host-send-user-message is the only grant that spends money, so it
    warns at startup on its own, and carries three separate loop bounds.

/reload now reloads [[extensions]]

For two releases /reload re-read skills and hooks and deliberately
skipped plugins, and said so. It no longer skips them — edit a grant,
run /reload, done.

Built around the failure cases rather than the happy path:

  • Load before unregister. A plugin whose .wasm no longer compiles
    never becomes live, so its previous instance stays live and callable,
    and the report line names the failure. You asked to pick up an edit;
    the edit not compiling should not also take away the plugin that was
    working a second ago.
  • A call in flight when the swap lands is refused in-band, with two
    different wordings on purpose. One for a call refused before entering
    the guest; one for a call that had already entered, which says the
    result is discarded and that any side effects it already had stand.
  • Never "extensions reloaded" on its own — a reload that claims more
    than it did is worse than one that admits a gap.

State across a reload is decided per kind: host-store files survive,
plugin_send's loop guard and spent budget survive, and a context
contribution survives for a plugin still holding allow_context and is
dropped otherwise, with the report naming every drop.

Five example plugins, two of them worth installing

Catalogued in examples/README.md.

Plugin What it is for
wasm-plugin-minimal The skeleton to copy — boilerplate and "your part" split by one marker
wasm-plugin The reference: four tools, slash commands, all three command actions
wasm-plugin-events Event subscription, and the two-list grant
wasm-plugin-memory Durable project memory. Facts become markdown under .nanopi/memory/ you can read, edit, diff and commit. Only the compact index enters the model's context; it calls recall for the full text of whatever looks relevant
wasm-plugin-report Where the time went. Per-tool counts, duration and failures across sessions — tool_execution_end carries a duration_ms that nothing else persists, so "why was that slow" had no answer at all

The last two are deliberately complementary: between them they exercise
the whole outbound surface. Neither does automatic capture on events,
and the memory plugin's README explains why — event delivery is
drop-on-busy and does not queue, so a memory system built on it would
quietly forget things with no way to tell you which.

message_end now carries the assistant's reply

Previously {turn_count, response_length} — a number. A plugin could see
what the user said and every tool result but never what the model said,
so "remember the preference" was possible and "remember the conclusion"
was not.

Now four fields, with two deliberate choices: response_length keeps
its old meaning
(the whole reply's byte length, so existing scripts
read the same number), and truncation at 16 KiB announces itself three
ways
— the flag, the length mismatch, and an in-band marker in the
text. The in-band marker is the one that matters: a consumer that only
reads response must not be able to draw a conclusion from half a
sentence.

⚠️ This payload is shared byte-for-byte with shell hooks, so it is a
behaviour change for existing [[hooks.message_end]] scripts. See
Compatibility.

Also in this release

  • One vocabulary for the eleven hook events. pre_tool_use →
    tool_execution_start, post_tool_use → tool_execution_end,
    user_prompt_submit → input, session_end → session_shutdown. No
    alias, no deprecation window — a retired key fails to load and the
    error names its replacement.
  • Honest session payloads. session_id on lifecycle events is
    always the real id; it used to hold the compaction reason. The reason
    moved to arguments.reason.
  • Per-tool executionMode, plus [tool_exec_overrides]. bash now
    declares itself sequential, which fixes two concurrent bash calls
    on one file losing an update while both reported success. Set
    bash = "parallel" to take the speed back.
  • /model and thinking-level switches enter the transcript.
    ModelChange had a reader, a replay path, an /export renderer and a
    roundtrip test — and no writer, ever. Both writers now exist.
  • An interrupted tool call no longer bricks a session. An orphan
    tool_call used to replay into a request both APIs reject, making the
    session permanently unresumable behind a provider 400. nanopi now
    synthesises a result that says the outcome is unknown — not
    "failed", which would be a lie in the case that matters most.
  • Inline <think> blocks render as thinking on any OpenAI-compatible
    endpoint, gated on position rather than vendor.

Fixed by the manual test pass

Seven defects, each with a regression test verified by reverting the fix
and watching it fail:

Defect Effect
Parallel tool batches Every batch of 2+ tools failed on the Anthropic transport, after the tools ran — side effects done, turn dead before the model saw a result
Settings validation One unsatisfiable matcher silently disarmed every hook in the file behind a scrolling warning
Shift+Tab The documented default never fired in any real terminal
Extended thinking Silently inert on the whole Claude 5 family while three UI surfaces reported it on
Mid-turn /reload Reloaded nothing and blamed a valid config
Plugin stores Ignored NANOPI_HOME, and fell back to a relative path on Windows
/tools rendering Plugin names past 20 columns ran into the value beside them

Compatibility

Breaking: hook event names. The four retired keys above fail to
load, with the error naming the replacement. NANOPI_EVENT is renamed
identically.

Breaking: session_start / session_shutdown fire on more than
exit
— around every agent swap (/new, /resume, /fork,
/import), with arguments.reason saying which.

Breaking: session_id no longer carries the compaction reason. Read
arguments.reason instead.

Behaviour change: message_end hooks now receive the reply text.
Anything reading turn_count or response_length is unaffected, but a
script that logs arguments wholesale or bills by payload size now sees
up to 16 KiB of reply per turn. Check those before upgrading.

Behaviour change: bash is sequential by default. A batch of bash
calls costs the sum rather than the max. [tool_exec_overrides] bash = "parallel" restores the old behaviour.

Behaviour change: unknown slash commands are refused rather than sent
as chat. Use //name to send it as text; a leading space is no longer an
escape hatch.

Behaviour change: an unsatisfiable matcher is now fatal. An
[[hooks.input]] entry with any matcher other than "*" used to load
and silently never fire.

Install

Assets are attached to this release for linux-x86_64 (glibc and static
musl), macOS aarch64, and Windows x86_64. WASM plugin support needs the
wasm feature:

cargo build --release --features wasm

The stock release binary has no WASM runtime and says so — it reports how
many [[extensions]] entries it ignored rather than failing silently.

Known gaps — why this is -preview

  • Verified on Linux against one provider surface only. Everyt...
Read more

v0.10.1

Choose a tag to compare

@github-actions github-actions released this 01 Sep 14:57

Full Changelog: v0.10.0...v0.10.1

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 26 Aug 03:45

Full Changelog: v0.9.3...v0.10.0

v0.9.3

Choose a tag to compare

@github-actions github-actions released this 22 Aug 14:47

Full Changelog: v0.9.2...v0.9.3

v0.9.2

Choose a tag to compare

@github-actions github-actions released this 11 Aug 13:19

Full Changelog: v0.9.1...v0.9.2

v0.9.1

Choose a tag to compare

@github-actions github-actions released this 10 Aug 03:12

Full Changelog: v0.8.0...v0.9.1

nanopi v0.8.0

Choose a tag to compare

@ChrisZhangJin ChrisZhangJin released this 08 Aug 15:05

nanopi v0.8.0

A big-features release: PI-parity across auto-compaction, fork-tree
navigation, extended thinking, vision input, and the full slash-command
menu. 301 lib tests passing (was 254 at v0.6.0).

Highlights

Auto-compaction

Token-based threshold using each model's real context window; fires
both pre-turn (safety) and post-response (matches PI). Visible
[compacting context (threshold)…] / [compacted N messages via summary] markers in the transcript.

Session fork tree

  • Esc-Esc (or /fork) opens a DFS-rendered picker walking the
    parent_id chain, showing every entry type (user / assistant /
    tool / compaction) — old branches stay visible after forking.
  • On select: 3-option "Summarize branch?" modal (No / Default /
    Custom prompt). LLM summarizes the abandoned tail and injects it as
    a BranchSummary entry into the new branch's context so the model
    picks up where you left off.

Extended thinking

Shift+Tab cycles Off → Minimal (1024) → Low (2048) → Medium (8192)
→ High/Xhigh/Max (16384). Anthropic adapter emits
thinking: {type: enabled, budget_tokens} with model-capability
gating (claude--4-, claude-3-7-sonnet).

Vision

read on a PNG / JPG / GIF / WebP (magic-byte detected, no
extension trust) attaches a base64 image block to the tool result.

  • Anthropic adapter forwards it as multimodal tool_result
    content.
  • OpenAI-compatible endpoints get an auto-injected user-role
    follow-up with image_url data URLs (OpenAI tool role is
    text-only per spec).
  • Vision-model gating downgrades to a placeholder for text-only
    models. 3.5 MB raw image cap under Anthropic's 5 MB encoded limit.

Provider selector

New api_kind config + --api-kind CLI flag:

  • openai (default) — talks to /chat/completions; works with
    oneapi / newapi / litellm / OpenAI / DeepSeek / Groq …
  • anthropic — talks to /v1/messages (native Anthropic API).
    Announces • api_kind = anthropic — talking to <url>/v1/messages
    at startup.

Slash-command palette (13 total)

Command What it does
/model Switch to a different model
/new Start a fresh session — clears view, keeps scrollback
/resume Pick a past session; replays its full transcript to scrollback
/fork Open the fork/tree picker at a past turn
/session Print stats — id / name / model / turns / tokens / cost
/name [text] Show or set the session name
/copy Copy last assistant reply via OSC 52
/export [path] Bare → self-contained HTML in cwd; .jsonl → raw dump
/import <path> Load a JSONL as a new session and switch to it
/compact Force context compaction
/hotkeys Dump the keybinding cheat-sheet
/quit / /exit Exit

TUI polish

  • Fish-style word-coalesced undo — Ctrl+Z / Ctrl+_ / Ctrl+-.
  • Prompt history — 100-entry ring; ↑/↓ on empty editor recall
    past submissions.
  • Esc interrupts a streaming turn immediately (biased select
    drops the reqwest stream); "Operation aborted" marker in
    transcript; aborted turn gets a directive-only marker in context
    so the next turn's LLM doesn't try to "continue" the abandoned
    reply.
  • Long lines wrap to multiple rows instead of being truncated at
    the terminal edge (via Paragraph + Wrap).
  • 60-second send-timeout on both providers so a hung gateway
    can't stall the session forever.
  • Duplicate-tool-use dedup — gateway-replayed tool_use ids get
    a fresh uuid before push so we never emit duplicate tool_result
    blocks.
  • /new clears the visible screen but preserves scrollback;
    scroll up to review previous content.
  • /resume replays the whole transcript (user echoes, assistant
    text with markdown, tool cards, compactions, branch summaries).

Provider fixes

  • reqwest .send() wrapped in tokio::time::timeout(60s) on both
    adapters — no more indefinite waits on stuck connections.
  • OpenAI adapter routes tool-returned images via a follow-up
    user-role message with image_url data URLs.
  • Anthropic adapter emits multimodal tool_result content when
    images are attached and the model supports vision.
  • Retry/backoff for 408 / 429 / 5xx / queue_timeout preserved.

Binary

nanopi-v0.8.0-linux-x86_64 — 3.8 MB stripped ELF, x86_64-linux-gnu.
Drop into /usr/local/bin/ or wherever your PATH prefers. Run
plain nanopi in a TTY to get the ratatui TUI; pipe input or add
--no-tui for the classic rustyline mode.

Config lives at ~/.nanopi/config.toml. Minimal example:

model = "claude-opus-4-7"
base_url = "https://api.anthropic.com"
api_kind = "anthropic"
api_key_file = "~/.nanopi/api_key"