跳转到主要内容
Aether 仓库中的 examples/aether-extension 包演示了如何在单个包中组合 Pi Agent 级工具与 Aether UI 扩展。本导读覆盖示例的每一部分:包清单、Pi 工厂、Aether 工厂、表面与页面注册、存储,以及 before_send 事件钩子。

此扩展做什么

  • 注册 Pi /aether-example 命令,在扩展激活时通知 Agent
  • 在输入框上方添加卡片表面,包含实时计数按钮与提示词预填操作
  • 添加全屏抽屉页面(「扩展仪表盘」),包含持久化计数卡片与嵌入式 WebView
  • 通过 Aether.postMessage 将 WebView 桥接回扩展操作
  • 在扩展存储中跨会话持久化计数器值
  • 挂钩 before_send 事件,从发出的消息中剥离 !raw 前缀

包声明

package.jsonpi.extensionsaether.extensions 下都列出同一个 index.ts 入口,因此单个文件同时驱动 Pi Agent 侧与 Aether UI 侧:

完整带注释代码导读

1. 导入与 defineAetherExtension

  • ExtensionAPI 为 Pi 工厂(默认导出)中的 pi 参数提供类型。
  • defineAetherExtension 包装 Aether 工厂,并为 aether 上下文对象提供类型安全访问。
  • ui 是在每个 render 回调中使用的声明式 UI 构建器。

2. Pi 工厂(默认导出):注册 /aether-example

因为文件声明在 pi.extensions 下,Aether 将其作为标准 Pi 扩展加载。你将 Pi 工厂导出为 默认导出。处理程序仅触发一条 info 通知 — 这是证明包的 Pi 侧与 Aether 侧同时生效的最小示例。

3. Aether 工厂(activateAether):计数操作 + 预填操作

你将 Aether 工厂导出为 activateAether(命名导出)。因为该文件也列在 aether.extensions 下,Aether 会发现它。
  • incrementaether.storage 读取当前 count(默认为 0),递增后写回,再通过 aether.host.invoke("app.notify", ...) 显示 Android toast。因为 storage 是同步的,并持久化在 ~/.aether/app-extension-state.json 中,该值可在热重载与应用重启后保留。
  • prefill 使用 app.setDraftInput 用现成提示词替换输入框文本。你可在 UI 中任意按钮上传入操作 ID "prefill" 来触发。

4. 在 chat.composer.top 注册表面

registerSurface 将内容挂到 chat.composer.top 插槽 — 即文本输入框正上方。要点:
  • order: 10 — 多个扩展注册同一插槽时控制堆叠顺序。
  • render 上下文 — 接收实时应用状态。storage 保存扩展的持久化键值对;is_running 反映 Agent 当前是否在生成。
  • ui.card — 将全部内容包在圆角卡片中。radius: 22 更大幅度圆角,以匹配输入框形态。
  • arrangement: "space-between"ui.row — 将标签与状态文本拉到两端。
  • wrap: trueui.row — 窄屏上三个按钮会换到第二行;rowSpacing: 8 为行间增加垂直间距。
  • ui.node("pageButton", { page: "dashboard", ... }) — 特殊按钮类型,导航到 ID 为 "dashboard" 的已注册页面,并在会话抽屉中打开。

5. 带 WebView 与操作桥的页面注册

registerPage 创建全屏目标页,Aether 会自动将其列在会话抽屉中。注意:
  • id: "dashboard" — 此字符串必须与上方表面中 pageButton 节点的 page 字段匹配。
  • render 上下文 — 接收与表面相同的应用状态对象。storage.count 读取持久化计数器。
  • ui.web — 将任意 HTML 文档注入原生 Android WebView。WebView 内启用 JavaScript、DOM 存储与网络访问。
  • Aether.postMessage(JSON.stringify({ action, args })) — 从 WebView JavaScript 回到扩展逻辑的唯一桥接。用户点击 “Increment through JavaScript” 时,WebView 会发送消息,Aether 将其路由到已注册的 increment 操作。你可以在 JSON 对象中传入任意 args;操作处理程序将其作为第一个参数接收。
  • scroll: trueui.column — 内容超出屏幕时使页面主体可垂直滚动。

6. before_send 事件钩子

aether.on("before_send", handler) 在用户消息提交给模型前立即触发。处理程序接收草稿载荷,并可返回部分对象以合并回去。这里,任何以 !raw 开头的消息会在发送前剥离该前缀。返回 undefined(或不返回)则保持载荷不变。

安装

  1. examples/aether-extension 目录打包为 .zip 归档。
  2. 在 Android 设备上打开 Aether。
  3. 前往 设置 → 扩展 并点按 导入扩展
  4. 选择 zip 文件。Aether 将其解压到 ~/.aether/extensions 并立即热加载 Script 侧。
请对照 Script Mod API 文档中关于表面、页面、存储与事件的部分阅读本示例,以理解各部分如何组合。组合扩展刻意保持精简 — 它使用的每项能力都可独立组合,因此你可以把单个片段(例如仅 before_send 钩子,或仅表面)复制到新包中,而无需其余部分。