Skip to content

Home zh

Dev edited this page Sep 7, 2026 · 3 revisions

English · 中文

nanopi Wiki

一个用 Rust 写的 minimal Pi port。这里是面向使用者和插件作者的文档。

本 wiki 对应 nanopi v0.12.0(3a762b1)。

Wiki 是独立仓库,不随代码走 PR,所以它会漂移。每页顶部都标了对应版本和重新验证的命令——发现对不上时,以仓库里的 wit/nanopi-extension.wit 和 src/config.rs 为准。


从这里开始

你想做什么 去哪页
先看看 nanopi 到底实现了什么 Features-zh
配置模型 / API key / 两级配置的优先级 Configuration-zh
写一个自己的 WASM 插件 Plugin-Development-zh
搞清楚插件能访问什么(文件、网络、自己的存储、模型上下文、内置工具、你的钱) Plugin-Capabilities-zh
让插件观察生命周期事件 [[插件事件订阅
写 shell hooks——唯一能否决的机制 [[生命周期 hooks
插件写完了不知道对不对 Debugging-Plugins-zh

插件系统一句话概览

nanopi 的插件是独立的 WASM component,不 link nanopi、不跑在同一个进程的信任域里。它跟宿主的全部约定是 九个导入、六个导出,分布在 wit/nanopi-extension.wit 的三个 world 里:

// world extension —— 所有导入都在这里,向上被继承
import host-log:               func(level: u8, message: string)
import host-fs-read:           func(path: string) -> string                 // 需 allow_fs
import host-http-get:          func(url: string) -> string                  // 需 allow_network + url_allowlist
import host-store-get:         func(key: string) -> string                  // 需 allow_store
import host-store-set:         func(key: string, value: string) -> string   // 需 allow_store
import host-notify:            func(text: string) -> string                 // 无门,但限速
import host-set-context:       func(text: string) -> string                 // 需 allow_context
import host-call-tool:         func(name: string, args-json: string) -> string  // 需 allow_tools,按工具名
import host-send-user-message: func(text: string) -> string                 // 需 allow_send_message

export list-tools:      func() -> string
export execute-tool:    func(name: string, args-json: string) -> string

// world extension-commands = extension + 用户输入的斜杠命令
export list-commands:   func() -> string
export execute-command: func(name: string, args: string) -> string

// world extension-events = extension-commands + 生命周期观察
export list-events:     func() -> string
export handle-event:    func(event: string, payload-json: string) -> string

这三个 world 是一条线性的阶梯,不是格:

extension  ⊂  extension-commands  ⊂  extension-events

选够用的那一层就好。WIT 表达不了"可选导出"——声明了却没实现,wasm-tools component new 直接失败——所以一个只想收事件的插件也必须把两个命令导出写成 stub。这是不想"今天四个 world、下个特性八个"所付的代价。导入不需要新 world:它们全在 world extension 上,用旧 WIT 编译的 guest 根本不引用新导入,这就是 v0.11 时期的二进制今天照样能加载的原因。

结构化数据一律以 JSON 字符串过边界——这让整个 ABI 只有一个 primitive 类型,宿主因此可以用 Instance::get_typed_func 手工绑定,不必引入 wit-bindgen 构建步骤。

能力是默认关闭的。 插件装上去之后,只要你不显式开门,它读不了文件、连不上网络、存不下任何东西、调不了任何内置工具、往模型上下文里塞不进一个字、也发不起任何一轮对话。网络还要再过一道 host 白名单,空白名单拒绝一切;生命周期事件则要"配置授权"和"插件自己申请"两边都有。详见 Plugin-Capabilities-zh 和 插件事件订阅。


和上游 Pi 的一个重要差异

上游 Pi 的 extension 是 jiti.import() 进主进程的 TypeScript,拿到完整 Node 全局——fetch、node:net、child_process 全都能用,没有沙箱,也没有任何 URL 白名单。Pi 的 SECURITY.md 把这个写成了明确设计:"the Pi coding agent intentionally does not have a sandbox"。

nanopi 走了相反的路:插件跑在 wasmtime 里,能力逐项 opt-in。所以移植 Pi 插件时不能假设同样的权限,Pi 那边随手一个 fetch() 在这边需要用户在 config.toml 里同时打开 allow_network 并把 host 写进 url_allowlist。


构建注意

宿主必须带 --features wasm 编译才有 WASM 运行时:

cargo build --features wasm

发行版的静态 musl 二进制不含 WASM 运行时。装了插件却看不到 registered extension tool 日志时,先确认这一条。

Clone this wiki locally