Repository navigation
Home zh
English · 中文
一个用 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 的 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 日志时,先确认这一条。
English
- Home
- Features
- Configuration
- Hooks
- Plugin Development
- Example Plugins
- Plugin Capabilities
- Plugin Events
- Debugging Plugins
中文
v0.12.0 · source of truth:
wit/nanopi-extension.wit