mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-03 07:34:23 +00:00
docs: 插件 SDK 文档站(API 参考从源码生成 + 自建检索)
为插件作者建一个文档站,重点是**能按描述搜到 API**,以及**明确能力边界**。
## 为什么 API 参考要生成而不是手写
公开 API 面有 115 个符号、11 个接口。手抄必然与代码漂移——这是文档站最常见的
死法(本仓 README 里已经有过几处「文档说一套、代码是另一套」)。
所以 `tools/apidoc` 直接从 `sdk/*.go` 提取签名、文档注释与代码块示例,渲染成
`docs/api/*.md`。发现文档不对时改的是**源码注释**,不是生成物。生成页首行带
「勿手改」标记,防止有人改了下次构建白改。
- `extract.go`:go/ast + go/doc 提取(只用标准库,离线可跑,不引入依赖)
- `gensite/`:渲染 Markdown + 检索索引
- `gensite/usages.go`:从 `example/` 21 个示例插件里反查**真实调用点**,
贴在每个 API 下(源码注释里几乎没有可运行示例,但示例插件都是能编译跑的真代码)
## 能力边界:写这个站时查出的三处文档错误
这是本次最有价值的部分。原以为「公开 SDK 里有的 API 外部插件都能用」,
实测对照桥接模板后发现三处不符,站内已更正:
1. **`PluginMgr()` 被写成「仅内置可用」——错的。** 桥接模板第 692 行显式
`base.SetPluginMgrAPI(procPluginMgr{})`,公开 `PluginMgrAPI` 注释也写「外部插件可调用」。
真正的区别是**方法数**:公开面 3 个(ReloadOne/ListLoadedPlugins/IsPluginDisabled),
内部面 9 个。容易混淆是因为两个包里有同名但不同的接口。
2. **`Events()` 外部插件恒为 nil。** `SetEventSubscriber` 全仓只有定义、无调用点,
故 subscriber 从未被注入。外部插件的事件订阅实际由生成的运行时走
`events.subscribe` RPC 完成——旧文档把它当成可用入口,会让人写出必然失效的代码。
3. **`UnregisterOutputChannel` 是静默无效,不是报错。** 桥接只注入 registrar、
不注入 unregistrar,于是 `regOutputUnreg == nil`,函数命中 else 分支**直接返回 nil**
(sdk/plugin.go:539-549)——不报错、通道也没注销。
每条裁定的依据写进 `tools/apidoc/tiers.json`(文件:行号 或 grep 结论),
站上以告警框呈现,读者可自行核对。判断依据三源:桥接模板的 `base.Set*` 注入点、
`internal/sdk` 完整面、`internal/plugin/proc/protocol.go` 的 RPC 表。
## 检索(用户的核心诉求)
两套互补:
- **MkDocs 内置搜索**:全文,中文走 jieba 分词。
- **自建 API 检索**(`docs/javascripts/api-search.js` + `assets/api-index.json`):
支持四类查询——按名称、**按功能描述**(「注册工具」→ RegisterTool、
「崩溃」→ SetAutoRestart)、按 `限定符.方法`(`memory.recall` → MemoryAPI.Recall)、
按签名片段(`(string) error`)。并标出「仅内置」,避免外部插件作者踩空。
自建的理由:Material 内置搜索按整页文本索引,搜 `InjectText` 会列出所有提到它的
页面,但分不清哪条是它的定义;而且它要等 mkdocs build 才更新。
## 文档结构
- `docs/guide/`:快速开始、Go/Lua 首个插件、能力边界、打包发布、多平台、受限 SDK 与安全
- `docs/api/`:10 个按「你想做什么」划分的章节(工具/阶段/记忆/通道/配置/生命周期/
事件/LLM/常量/桥接)+ 仅内置汇总页
- `docs/versions.md`:SDK 版本语义(跟随内核中版本、patch 恒为 .0)、
1.0.0 是唯一破坏性变更、RPC 协议版本
## 验证
- `mkdocs build --strict` 零告警
- 23 个页面的全部站内链接与锚点可达(自动校验)
- 1440 / 768 / 390px 三视口:无横向溢出、无控制台错误
- 四种检索模式实测有结果且跳转锚点正确
- 构建产物 `site_build/` 已 gitignore
用法:`tools/apidoc/build.sh`(生成+构建)、`tools/apidoc/build.sh serve`(预览)。
This commit is contained in:
204
docs/api/bridge.md
Normal file
204
docs/api/bridge.md
Normal file
@ -0,0 +1,204 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 桥接装配点(Bridge)
|
||||
|
||||
以下方法**不是给插件业务代码调的**——它们由 `hmapdev` 生成的运行时在启动时调用,用来把内核能力注入到 SDK 实例。列在这里是为了让「公开 API 面」完整,并说明每个注入点对应什么能力。
|
||||
|
||||
### `APIRegistrar`
|
||||
|
||||
```go
|
||||
type APIRegistrar func(name string) error
|
||||
```
|
||||
|
||||
APIRegistrar registers a plugin API for external access.
|
||||
|
||||
<small>`plugin.go:311`</small>
|
||||
|
||||
### `InputChannelRegistrar`
|
||||
|
||||
```go
|
||||
type InputChannelRegistrar func(name string, def ChannelDef) error
|
||||
```
|
||||
|
||||
InputChannelRegistrar registers an input channel with its memory behavior.
|
||||
|
||||
<small>`plugin.go:314`</small>
|
||||
|
||||
### `OutputChannelRegistrar`
|
||||
|
||||
```go
|
||||
type OutputChannelRegistrar func(name string, caps int, desc string, def ChannelDef, handler ToolHandler) error
|
||||
```
|
||||
|
||||
OutputChannelRegistrar registers an output channel that the output_send tool can use.
|
||||
|
||||
<small>`plugin.go:317`</small>
|
||||
|
||||
### `OutputChannelUnregistrar`
|
||||
|
||||
```go
|
||||
type OutputChannelUnregistrar func(name string) error
|
||||
```
|
||||
|
||||
OutputChannelUnregistrar 注销一个输出通道。
|
||||
|
||||
为什么需要它:输出通道不止有"启动时注册一次"的静态通道,还有**随外部资源生灭**的
|
||||
动态通道 —— 典型是远程设备:`device/<id>` 只在设备在线期间存在,设备掉线后
|
||||
必须注销,否则 output_list_channels 会一直列着它、模型会往一个死通道发消息。
|
||||
|
||||
<small>`plugin.go:324`</small>
|
||||
|
||||
### `PluginSDK.SetDocMemoryAPI`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetDocMemoryAPI(dm DocMemoryAPI)
|
||||
```
|
||||
|
||||
<small>`plugin.go:614`</small>
|
||||
|
||||
### `PluginSDK.SetEventSubscriber`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
同上:无调用点。标 builtin 而非 bridge,因为连桥接运行时都不注入它——外部插件无法用它获得事件订阅能力。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetEventSubscriber(es EventSubscriber)
|
||||
```
|
||||
|
||||
<small>`plugin.go:638`</small>
|
||||
|
||||
### `PluginSDK.SetIOInjector`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetIOInjector(io IOInjector)
|
||||
```
|
||||
|
||||
SetIOInjector sets the IO injector (called by the core at startup).
|
||||
|
||||
<small>`plugin.go:595`</small>
|
||||
|
||||
### `PluginSDK.SetInputChannelRegistrar`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetInputChannelRegistrar(r InputChannelRegistrar)
|
||||
```
|
||||
|
||||
SetInputChannelRegistrar sets the input channel registrar (called by the core at startup).
|
||||
|
||||
<small>`plugin.go:588`</small>
|
||||
|
||||
### `PluginSDK.SetKnowledgeAPI`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetKnowledgeAPI(kn KnowledgeAPI)
|
||||
```
|
||||
|
||||
<small>`plugin.go:620`</small>
|
||||
|
||||
### `PluginSDK.SetLLMAPI`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetLLMAPI(llm LLMAPI)
|
||||
```
|
||||
|
||||
<small>`plugin.go:626`</small>
|
||||
|
||||
### `PluginSDK.SetMemoryAPI`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetMemoryAPI(mem MemoryAPI)
|
||||
```
|
||||
|
||||
SetMemoryAPI sets the memory API (called by the core at startup).
|
||||
|
||||
<small>`plugin.go:602`</small>
|
||||
|
||||
### `PluginSDK.SetOutputChannelRegistrar`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetOutputChannelRegistrar(r OutputChannelRegistrar)
|
||||
```
|
||||
|
||||
SetOutputChannelRegistrar sets the output channel registrar (called by the core at startup).
|
||||
|
||||
<small>`plugin.go:574`</small>
|
||||
|
||||
### `PluginSDK.SetOutputChannelUnregistrar`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
同上,桥接模板不注入。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetOutputChannelUnregistrar(r OutputChannelUnregistrar)
|
||||
```
|
||||
|
||||
SetOutputChannelUnregistrar sets the output channel unregistrar (called by the core at startup).
|
||||
|
||||
<small>`plugin.go:581`</small>
|
||||
|
||||
### `PluginSDK.SetPluginMgrAPI`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetPluginMgrAPI(pm PluginMgrAPI)
|
||||
```
|
||||
|
||||
SetPluginMgrAPI sets the plugin manager API (called by the bridge at startup).
|
||||
|
||||
<small>`plugin.go:645`</small>
|
||||
|
||||
### `PluginSDK.SetSocialAPI`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetSocialAPI(social SocialAPI)
|
||||
```
|
||||
|
||||
<small>`plugin.go:632`</small>
|
||||
|
||||
### `PluginSDK.SetTextMemoryAPI`
|
||||
|
||||
!!! info "桥接装配点"
|
||||
桥接运行时注入点(由 hmapdev 生成的 proc_main 调用,插件业务代码不调用)
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetTextMemoryAPI(tm TextMemoryAPI)
|
||||
```
|
||||
|
||||
<small>`plugin.go:608`</small>
|
||||
|
||||
### `ToolRegistrar`
|
||||
|
||||
```go
|
||||
type ToolRegistrar func(name string, def ToolDef, handler ToolHandler) error
|
||||
```
|
||||
|
||||
ToolRegistrar registers a tool dynamically.
|
||||
|
||||
<small>`plugin.go:305`</small>
|
||||
|
||||
79
docs/api/builtin-only.md
Normal file
79
docs/api/builtin-only.md
Normal file
@ -0,0 +1,79 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 仅内置插件可用的 API
|
||||
|
||||
这些 API **存在于公开 SDK 包里**,但在外部(第三方)插件的运行路径上不可用:要么桥接运行时根本不注入它(拿到 nil),要么内核会拒绝/降级。列在这里是为了让边界显式——而不是让你在运行时才发现拿不到。
|
||||
|
||||
判断依据全部来自源码与 `hmapdev` 桥接模板,逐条记在每条说明里。
|
||||
|
||||
### `PriorityL4`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
sdk/plugin.go:119-126 明确:L1..L3 任何插件可声明,L4 只有内核级插件可用,外部插件声明会被内核夹到 L3(内核侧有 priority_clamp_test.go 守着)。外部插件文档应说明「声明 L4 无效」。
|
||||
|
||||
```go
|
||||
const PriorityL4
|
||||
```
|
||||
|
||||
PriorityL4 仅内核级(内置)插件可用;外部插件声明会被夹到 L3。
|
||||
|
||||
<small>`plugin.go:127`</small>
|
||||
|
||||
### `PluginSDK.Events`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
实测全仓 SetEventSubscriber 只有定义、无任何调用点(grep -rn SetEventSubscriber --include=*.go 仅命中 sdk/plugin.go 的定义与 lua_plugin.go 的一句注释)。外部插件拿到的 Events() 恒为 nil。外部插件订阅事件走的是桥接运行时的 events.subscribe RPC(proc_main.go.tmpl:1421 在插件侧分发、内核 protocol.go MethodEventsSubscribe),Lua 插件则走内部 SDK 的 Subscribe。这正是 PLUGIN_DEV.md 里没有说清的一处。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) Events() EventSubscriber
|
||||
```
|
||||
|
||||
Events returns the event subscriber for listening to kernel events (may be nil if not available).
|
||||
|
||||
<small>`plugin.go:446`</small>
|
||||
|
||||
### `PluginSDK.SetEventSubscriber`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
同上:无调用点。标 builtin 而非 bridge,因为连桥接运行时都不注入它——外部插件无法用它获得事件订阅能力。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetEventSubscriber(es EventSubscriber)
|
||||
```
|
||||
|
||||
<small>`plugin.go:638`</small>
|
||||
|
||||
### `PluginSDK.SetOutputChannelUnregistrar`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
同上,桥接模板不注入。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetOutputChannelUnregistrar(r OutputChannelUnregistrar)
|
||||
```
|
||||
|
||||
SetOutputChannelUnregistrar sets the output channel unregistrar (called by the core at startup).
|
||||
|
||||
<small>`plugin.go:581`</small>
|
||||
|
||||
### `PluginSDK.UnregisterOutputChannel`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
外部插件的桥接模板只注入 SetOutputChannelRegistrar(proc_main.go.tmpl:693-705),**未**注入 SetOutputChannelUnregistrar(grep Unregistrar 在 templates/ 下无命中)。因此外部插件的 regOutputUnreg 为 nil,UnregisterOutputChannel 会命中 `if reg != nil` 的 else 分支**直接返回 nil**(sdk/plugin.go:539-549)——即**静默无效**:不报错、通道也没注销。内置插件由 internal/sdk/plugin.go:330 注入 cfg.RegOutputUnreg,真正生效。外部插件要让通道下线,只能重载插件。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) UnregisterOutputChannel(name string) error
|
||||
```
|
||||
|
||||
UnregisterOutputChannel 注销一个输出通道(动态通道随资源生灭时必须调用)。
|
||||
|
||||
<small>`plugin.go:539`</small>
|
||||
|
||||
### `EventSubscriber.Subscribe`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
|
||||
```go
|
||||
Subscribe(eventType EventType, handler EventHandler) func()
|
||||
```
|
||||
|
||||
661
docs/api/channels.md
Normal file
661
docs/api/channels.md
Normal file
@ -0,0 +1,661 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 输入 / 输出通道
|
||||
|
||||
通道是插件与外界(设备、其他 Agent、外部系统)交换消息的入口。**输入通道**接收外部消息,**输出通道**把消息投递出去。
|
||||
|
||||
## `IOInjector`
|
||||
|
||||
IOInjector provides methods for injecting input and interrupts into the agent pipeline.
|
||||
All methods accept (source, channel) where channel is the target output channel
|
||||
for routing the agent's response.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`InjectInputMedia`](#ioinjectorinjectinputmedia) | |
|
||||
| [`InjectInputMediaOpts`](#ioinjectorinjectinputmediaopts) | |
|
||||
| [`InjectInputMediaSync`](#ioinjectorinjectinputmediasync) | |
|
||||
| [`InjectInputMediaSyncOpts`](#ioinjectorinjectinputmediasyncopts) | |
|
||||
| [`InjectInputSync`](#ioinjectorinjectinputsync) | InjectInputSync 注入输入事件并同步等待 agent 回复,返回回复文本(无回复时返回空串)。 |
|
||||
| [`InjectInputSyncOpts`](#ioinjectorinjectinputsyncopts) | |
|
||||
| [`InjectInterruptMedia`](#ioinjectorinjectinterruptmedia) | |
|
||||
| [`InjectInterruptMediaOpts`](#ioinjectorinjectinterruptmediaopts) | |
|
||||
| [`InjectInterruptText`](#ioinjectorinjectinterrupttext) | |
|
||||
| [`InjectInterruptTextOpts`](#ioinjectorinjectinterrupttextopts) | |
|
||||
| [`InjectText`](#ioinjectorinjecttext) | |
|
||||
| [`InjectTextNoMemory`](#ioinjectorinjecttextnomemory) | |
|
||||
| [`InjectTextOpts`](#ioinjectorinjecttextopts) | 以下 Opts 变体让调用点在**这一次注入**上声明记忆与裁剪行为。 |
|
||||
| [`SetToolBlocks`](#ioinjectorsettoolblocks) | SetToolBlocks 插件工具注入多模态内容块(image_url/audio_url),内核在下一条 |
|
||||
|
||||
### `IOInjector.InjectInputMedia`
|
||||
|
||||
```go
|
||||
InjectInputMedia(source, channel, text string, blocks []ContentBlock)
|
||||
```
|
||||
|
||||
<small>`plugin.go:230`</small>
|
||||
|
||||
### `IOInjector.InjectInputMediaOpts`
|
||||
|
||||
```go
|
||||
InjectInputMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
|
||||
```
|
||||
|
||||
<small>`plugin.go:241`</small>
|
||||
|
||||
### `IOInjector.InjectInputMediaSync`
|
||||
|
||||
```go
|
||||
InjectInputMediaSync(source, channel, text string, blocks []ContentBlock) string
|
||||
```
|
||||
|
||||
<small>`plugin.go:231`</small>
|
||||
|
||||
### `IOInjector.InjectInputMediaSyncOpts`
|
||||
|
||||
```go
|
||||
InjectInputMediaSyncOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions) string
|
||||
```
|
||||
|
||||
<small>`plugin.go:242`</small>
|
||||
|
||||
### `IOInjector.InjectInputSync`
|
||||
|
||||
```go
|
||||
InjectInputSync(source, channel, text string) string
|
||||
```
|
||||
|
||||
InjectInputSync 注入输入事件并同步等待 agent 回复,返回回复文本(无回复时返回空串)。
|
||||
用于通道消息的完整闭环:收到入站 → agent 处理 → 回复取回 → 送回通道。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:345` | `reply := p.sdk.InjectInputSync(p.name, p.name,` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:237` | `reply = p.sdk.InjectInputSync(p.name, p.name,` |
|
||||
|
||||
<small>`plugin.go:226`</small>
|
||||
|
||||
### `IOInjector.InjectInputSyncOpts`
|
||||
|
||||
```go
|
||||
InjectInputSyncOpts(source, channel, text string, opts InjectOptions) string
|
||||
```
|
||||
|
||||
<small>`plugin.go:240`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptMedia`
|
||||
|
||||
```go
|
||||
InjectInterruptMedia(source, channel, text string, blocks []ContentBlock)
|
||||
```
|
||||
|
||||
<small>`plugin.go:232`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptMediaOpts`
|
||||
|
||||
```go
|
||||
InjectInterruptMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
|
||||
```
|
||||
|
||||
<small>`plugin.go:243`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptText`
|
||||
|
||||
```go
|
||||
InjectInterruptText(source, channel, text string)
|
||||
```
|
||||
|
||||
<small>`plugin.go:221`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptTextOpts`
|
||||
|
||||
```go
|
||||
InjectInterruptTextOpts(source, channel, text string, opts InjectOptions)
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:1319` | `p.sdk.InjectInterruptTextOpts(p.name, p.name,` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:527` | `p.sdk.InjectInterruptTextOpts("calendar", "calendar", msg, sdk.InjectOptions{NoMemory: true})` |
|
||||
| [`memo`](../examples/index.md#memo) | `example/memo/plugin.go:299` | `p.sdk.InjectInterruptTextOpts(p.name, p.name,` |
|
||||
| [`qq`](../examples/index.md#qq) | `example/qq/plugin.go:1493` | `p.sdk.InjectInterruptTextOpts(p.name, p.name, text, sdk.InjectOptions{` |
|
||||
|
||||
<small>`plugin.go:239`</small>
|
||||
|
||||
### `IOInjector.InjectText`
|
||||
|
||||
```go
|
||||
InjectText(source, channel, text string)
|
||||
```
|
||||
|
||||
<small>`plugin.go:222`</small>
|
||||
|
||||
### `IOInjector.InjectTextNoMemory`
|
||||
|
||||
```go
|
||||
InjectTextNoMemory(source, channel, text string)
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:1114` | `p.sdk.InjectTextNoMemory(p.name, p.name, fmt.Sprintf("[浏览器 %s 已导航到 %s]", id, rawURL))` |
|
||||
|
||||
<small>`plugin.go:223`</small>
|
||||
|
||||
### `IOInjector.InjectTextOpts`
|
||||
|
||||
```go
|
||||
InjectTextOpts(source, channel, text string, opts InjectOptions)
|
||||
```
|
||||
|
||||
以下 Opts 变体让调用点在**这一次注入**上声明记忆与裁剪行为。
|
||||
|
||||
上面那些不带 opts 的方法等价于传零值 InjectOptions(记入记忆 + 不裁剪),
|
||||
保留它们是为了不破坏已有插件;新代码应当用 Opts 变体把意图写清楚。
|
||||
|
||||
<small>`plugin.go:238`</small>
|
||||
|
||||
### `IOInjector.SetToolBlocks`
|
||||
|
||||
```go
|
||||
SetToolBlocks(blocks []ContentBlock)
|
||||
```
|
||||
|
||||
SetToolBlocks 插件工具注入多模态内容块(image_url/audio_url),内核在下一条
|
||||
tool message 的 content 数组里带上这些块,让模型在后续轮次看到图/听到音频。
|
||||
|
||||
<small>`plugin.go:229`</small>
|
||||
|
||||
### `CapAudio`
|
||||
|
||||
```go
|
||||
const CapAudio
|
||||
```
|
||||
|
||||
Output capability flags
|
||||
|
||||
<small>`plugin.go:331`</small>
|
||||
|
||||
### `CapFile`
|
||||
|
||||
```go
|
||||
const CapFile
|
||||
```
|
||||
|
||||
Output capability flags
|
||||
|
||||
<small>`plugin.go:329`</small>
|
||||
|
||||
### `CapImage`
|
||||
|
||||
```go
|
||||
const CapImage
|
||||
```
|
||||
|
||||
Output capability flags
|
||||
|
||||
<small>`plugin.go:330`</small>
|
||||
|
||||
### `CapStructured`
|
||||
|
||||
```go
|
||||
const CapStructured
|
||||
```
|
||||
|
||||
Output capability flags
|
||||
|
||||
<small>`plugin.go:332`</small>
|
||||
|
||||
### `CapText`
|
||||
|
||||
```go
|
||||
const CapText
|
||||
```
|
||||
|
||||
Output capability flags
|
||||
|
||||
<small>`plugin.go:328`</small>
|
||||
|
||||
### `ChannelDef`
|
||||
|
||||
```go
|
||||
type ChannelDef struct { NoMemory bool `json:"no_memory,omitempty"` Cleaner func(string) string `json:"-"` ContextPolicy string `json: …
|
||||
```
|
||||
|
||||
ChannelDef 描述通道在记忆计算层的行为,与 ToolDef.NoMemory/Cleaner 语义一致。
|
||||
NoMemory: 此通道输入/输出不参与记忆计算(向量化/关键词提取/蒸馏),但原文保留在上下文中
|
||||
Cleaner: 计算层过滤函数,不改原文;仅在向量化/jieba/蒸馏/存档提取关键词时调用
|
||||
ContextPolicy: 此通道的输入到达后是否据此裁剪上下文,默认 none(不裁剪)
|
||||
RecallPolicy: 此通道的输入到达后是否据此召回相关记忆,默认 auto(召回)
|
||||
|
||||
JSON tag 是必需的:通道定义要跨进程传给内核,而 Cleaner 是函数(必须忽略)。
|
||||
没有 tag 时既无法整体 marshal(func 不支持),又会诱使调用方手写字段白名单——
|
||||
那样新增字段会被静默丢掉。
|
||||
|
||||
<small>`plugin.go:139`</small>
|
||||
|
||||
### `ContextPolicyNone`
|
||||
|
||||
```go
|
||||
const ContextPolicyNone
|
||||
```
|
||||
|
||||
上下文策略:决定一次工具调用/输入/注入是否依据其内容裁剪上下文。
|
||||
|
||||
默认(空串或 ContextPolicyNone)**不裁剪**:裁剪会归档丢弃低相关事件,
|
||||
必须由工具/通道/注入点显式声明才发生——否则一个只想往上下文里塞内容的
|
||||
插件会在背后把别人的内容挤掉,且看不出是谁干的。
|
||||
|
||||
<small>`plugin.go:44`</small>
|
||||
|
||||
### `ContextPolicyPrune`
|
||||
|
||||
```go
|
||||
const ContextPolicyPrune
|
||||
```
|
||||
|
||||
上下文策略:决定一次工具调用/输入/注入是否依据其内容裁剪上下文。
|
||||
|
||||
默认(空串或 ContextPolicyNone)**不裁剪**:裁剪会归档丢弃低相关事件,
|
||||
必须由工具/通道/注入点显式声明才发生——否则一个只想往上下文里塞内容的
|
||||
插件会在背后把别人的内容挤掉,且看不出是谁干的。
|
||||
|
||||
<small>`plugin.go:45`</small>
|
||||
|
||||
### `IOInjector`
|
||||
|
||||
```go
|
||||
type IOInjector interface { InjectInterruptText(source, channel, text string) InjectText(source, channel, text string) InjectTextNoMemory(source, channel, text string) // I …
|
||||
```
|
||||
|
||||
IOInjector provides methods for injecting input and interrupts into the agent pipeline.
|
||||
All methods accept (source, channel) where channel is the target output channel
|
||||
for routing the agent's response.
|
||||
|
||||
<small>`plugin.go:220`</small>
|
||||
|
||||
### `PluginSDK.InjectInputMedia`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInputMedia(source, channel, text string, blocks []ContentBlock)
|
||||
```
|
||||
|
||||
InjectInputMedia 注入带媒体内容块(image_url/audio_url)的输入。
|
||||
blocks 会落进媒体存储被记忆引用捕获,同时作为当前轮 content 数组
|
||||
发给 LLM,让模型在「本轮」就看到图/听到音频——区别于 SetToolBlocks
|
||||
的「下一轮 tool message」语义。
|
||||
等价于 InjectInputMediaOpts(..., InjectOptions{})。
|
||||
|
||||
<small>`plugin.go:701`</small>
|
||||
|
||||
### `PluginSDK.InjectInputMediaOpts`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInputMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
|
||||
```
|
||||
|
||||
InjectInputMediaOpts 注入带媒体块的输入,并声明记忆/裁剪行为。
|
||||
|
||||
<small>`plugin.go:740`</small>
|
||||
|
||||
### `PluginSDK.InjectInputMediaSync`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInputMediaSync(source, channel, text string, blocks []ContentBlock) string
|
||||
```
|
||||
|
||||
InjectInputMediaSync 注入带媒体内容块的输入并同步等待 agent 回复。
|
||||
等价于 InjectInputMediaSyncOpts(..., InjectOptions{})。
|
||||
|
||||
<small>`plugin.go:707`</small>
|
||||
|
||||
### `PluginSDK.InjectInputMediaSyncOpts`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInputMediaSyncOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions) string
|
||||
```
|
||||
|
||||
InjectInputMediaSyncOpts 注入带媒体块的输入并同步等待回复,同时声明记忆/裁剪行为。
|
||||
|
||||
<small>`plugin.go:747`</small>
|
||||
|
||||
### `PluginSDK.InjectInputSync`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInputSync(source, channel, text string) string
|
||||
```
|
||||
|
||||
InjectInputSync injects a text message and synchronously waits for the agent reply,
|
||||
returning the reply text (empty string if none). Replies must be dispatched back
|
||||
to the source channel by the caller.
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:345` | `reply := p.sdk.InjectInputSync(p.name, p.name,` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:237` | `reply = p.sdk.InjectInputSync(p.name, p.name,` |
|
||||
|
||||
<small>`plugin.go:692`</small>
|
||||
|
||||
### `PluginSDK.InjectInputSyncOpts`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInputSyncOpts(source, channel, text string, opts InjectOptions) string
|
||||
```
|
||||
|
||||
InjectInputSyncOpts 注入输入并同步等待回复,同时在这次注入上声明记忆/裁剪行为。
|
||||
|
||||
<small>`plugin.go:731`</small>
|
||||
|
||||
### `PluginSDK.InjectInterruptMedia`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInterruptMedia(source, channel, text string, blocks []ContentBlock)
|
||||
```
|
||||
|
||||
InjectInterruptMedia 注入带媒体内容块的中断,可抢占当前 LLM 处理。
|
||||
blocks 随中断消息一起发给模型。
|
||||
|
||||
<small>`plugin.go:764`</small>
|
||||
|
||||
### `PluginSDK.InjectInterruptMediaOpts`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInterruptMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
|
||||
```
|
||||
|
||||
InjectInterruptMediaOpts 注入带媒体块的中断,并声明记忆/裁剪行为。
|
||||
|
||||
<small>`plugin.go:756`</small>
|
||||
|
||||
### `PluginSDK.InjectInterruptText`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInterruptText(source, channel, text string)
|
||||
```
|
||||
|
||||
InjectInterruptText injects a text interrupt that can preempt current LLM processing.
|
||||
等价于 InjectInterruptTextOpts(..., InjectOptions{}):记入记忆、不裁剪。
|
||||
|
||||
<small>`plugin.go:673`</small>
|
||||
|
||||
### `PluginSDK.InjectInterruptTextOpts`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectInterruptTextOpts(source, channel, text string, opts InjectOptions)
|
||||
```
|
||||
|
||||
InjectInterruptTextOpts 注入可抢占当前处理的中断文本。
|
||||
|
||||
中断也允许声明 ContextPolicyPrune:中断同样携带内容进入上下文,
|
||||
是否需要据此裁剪由调用方决定(默认不裁剪)。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:1319` | `p.sdk.InjectInterruptTextOpts(p.name, p.name,` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:527` | `p.sdk.InjectInterruptTextOpts("calendar", "calendar", msg, sdk.InjectOptions{NoMemory: true})` |
|
||||
| [`memo`](../examples/index.md#memo) | `example/memo/plugin.go:299` | `p.sdk.InjectInterruptTextOpts(p.name, p.name,` |
|
||||
| [`qq`](../examples/index.md#qq) | `example/qq/plugin.go:1493` | `p.sdk.InjectInterruptTextOpts(p.name, p.name, text, sdk.InjectOptions{` |
|
||||
|
||||
<small>`plugin.go:724`</small>
|
||||
|
||||
### `InjectOptions`
|
||||
|
||||
```go
|
||||
type InjectOptions struct { NoMemory bool ContextPolicy string // RecallPolicy 声明此次注入是否据其内容召回相关记忆。 // 空串 = 默认(输入/注<><E6B3A8> …
|
||||
```
|
||||
|
||||
InjectOptions 声明一次注入行为在记忆层与上下文层的表现。
|
||||
|
||||
零值 = 记入记忆 + 不裁剪上下文,与历史行为(三参数注入方法)完全一致,
|
||||
因此调用方只有在确实需要改变行为时才需要填它。
|
||||
|
||||
为什么注入也要这两个标志:注入的内容来源千差万别——轮询到的频道消息
|
||||
属于真实对话(该记),而“任务还在跑”“连接已重连”这类提醒不该污染记忆,
|
||||
也不该把上下文按它的内容裁一遍。按调用点声明比按通道一刀切准确。
|
||||
|
||||
NoMemory: 此次注入不参与记忆计算(向量化/关键词提取/蒸馏),原文仍留在上下文
|
||||
ContextPolicy: 此次注入后是否依据(清洗后的)内容裁剪上下文;默认不裁剪。
|
||||
RecallPolicy: 此次注入是否依据(清洗后的)内容召回相关记忆;默认 auto(召回)。
|
||||
|
||||
中断注入也允许声明 prune——它同样会携带内容进入上下文。
|
||||
|
||||
CleanerName: 此次注入的内容用哪个**已注册的通道 cleaner** 清洗。
|
||||
|
||||
空串 = 按注入的 source 查通道定义(既有行为)。
|
||||
为什么要能显式指定:注入的 source 未必是注册过的输入通道名,
|
||||
而注入内容往往带 ANSI/JSON 包装,需要清洗后才是有效内容;
|
||||
不指定就只能退到「按 source 查不到就不清洗」。
|
||||
|
||||
<small>`plugin.go:98`</small>
|
||||
|
||||
### `PluginSDK.InjectText`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectText(source, channel, text string)
|
||||
```
|
||||
|
||||
InjectText injects a text message into the agent pipeline.
|
||||
等价于 InjectTextOpts(..., InjectOptions{}):记入记忆、不裁剪。
|
||||
|
||||
<small>`plugin.go:679`</small>
|
||||
|
||||
### `PluginSDK.InjectTextNoMemory`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectTextNoMemory(source, channel, text string)
|
||||
```
|
||||
|
||||
InjectTextNoMemory injects a text message without generating memory.
|
||||
等价于 InjectTextOpts(..., InjectOptions{NoMemory: true})。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:1114` | `p.sdk.InjectTextNoMemory(p.name, p.name, fmt.Sprintf("[浏览器 %s 已导航到 %s]", id, rawURL))` |
|
||||
|
||||
<small>`plugin.go:685`</small>
|
||||
|
||||
### `PluginSDK.InjectTextOpts`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) InjectTextOpts(source, channel, text string, opts InjectOptions)
|
||||
```
|
||||
|
||||
InjectTextOpts 注入文本到 agent,并在这一次注入上声明记忆与裁剪行为。
|
||||
|
||||
<small>`plugin.go:714`</small>
|
||||
|
||||
### `PriorityL1`
|
||||
|
||||
```go
|
||||
const PriorityL1
|
||||
```
|
||||
|
||||
中断优先级取值。
|
||||
|
||||
L1..L3 任何插件都可声明;**L4 只有内核级插件**(编译期内置插件,
|
||||
如 cli/webui/timer)才能声明——它用于实现真正的“立即打断”能力,
|
||||
例如 WebUI 的终止按钮。外部插件(走 proc 桥)声明 L4 会被内核夹到 L3。
|
||||
|
||||
<small>`plugin.go:123`</small>
|
||||
|
||||
### `PriorityL2`
|
||||
|
||||
```go
|
||||
const PriorityL2
|
||||
```
|
||||
|
||||
中断优先级取值。
|
||||
|
||||
L1..L3 任何插件都可声明;**L4 只有内核级插件**(编译期内置插件,
|
||||
如 cli/webui/timer)才能声明——它用于实现真正的“立即打断”能力,
|
||||
例如 WebUI 的终止按钮。外部插件(走 proc 桥)声明 L4 会被内核夹到 L3。
|
||||
|
||||
<small>`plugin.go:124`</small>
|
||||
|
||||
### `PriorityL3`
|
||||
|
||||
```go
|
||||
const PriorityL3
|
||||
```
|
||||
|
||||
中断优先级取值。
|
||||
|
||||
L1..L3 任何插件都可声明;**L4 只有内核级插件**(编译期内置插件,
|
||||
如 cli/webui/timer)才能声明——它用于实现真正的“立即打断”能力,
|
||||
例如 WebUI 的终止按钮。外部插件(走 proc 桥)声明 L4 会被内核夹到 L3。
|
||||
|
||||
<small>`plugin.go:125`</small>
|
||||
|
||||
### `PriorityL4`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
sdk/plugin.go:119-126 明确:L1..L3 任何插件可声明,L4 只有内核级插件可用,外部插件声明会被内核夹到 L3(内核侧有 priority_clamp_test.go 守着)。外部插件文档应说明「声明 L4 无效」。
|
||||
|
||||
```go
|
||||
const PriorityL4
|
||||
```
|
||||
|
||||
PriorityL4 仅内核级(内置)插件可用;外部插件声明会被夹到 L3。
|
||||
|
||||
<small>`plugin.go:127`</small>
|
||||
|
||||
### `RecallPolicyAuto`
|
||||
|
||||
```go
|
||||
const RecallPolicyAuto
|
||||
```
|
||||
|
||||
召回策略:决定一次工具调用/输入/注入是否据其内容**召回**(注入)相关记忆。
|
||||
|
||||
与 ContextPolicy **正交**:ContextPolicy 管「裁剪」(把低相关 L0 事件归档),
|
||||
RecallPolicy 管「召回」(把 L2/L3 的相关记忆注入本轮)。两者默认值刻意相反——
|
||||
裁剪是破坏性的,默认关(必须显式声明);召回是只读增量、日常对话本就需要,
|
||||
默认 auto(输入/注入),仅**工具**默认 none(工具输出多为噪声,按需声明)。
|
||||
|
||||
<small>`plugin.go:65`</small>
|
||||
|
||||
### `RecallPolicyNone`
|
||||
|
||||
```go
|
||||
const RecallPolicyNone
|
||||
```
|
||||
|
||||
召回策略:决定一次工具调用/输入/注入是否据其内容**召回**(注入)相关记忆。
|
||||
|
||||
与 ContextPolicy **正交**:ContextPolicy 管「裁剪」(把低相关 L0 事件归档),
|
||||
RecallPolicy 管「召回」(把 L2/L3 的相关记忆注入本轮)。两者默认值刻意相反——
|
||||
裁剪是破坏性的,默认关(必须显式声明);召回是只读增量、日常对话本就需要,
|
||||
默认 auto(输入/注入),仅**工具**默认 none(工具输出多为噪声,按需声明)。
|
||||
|
||||
<small>`plugin.go:64`</small>
|
||||
|
||||
### `PluginSDK.RegisterInputChannel`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RegisterInputChannel(name string, def ChannelDef) error
|
||||
```
|
||||
|
||||
RegisterInputChannel registers an input channel with its memory behavior.
|
||||
|
||||
契约:**凡是用 InjectText*/InjectInput*/InjectInterrupt*(source, "<name>", ...)
|
||||
注入的通道名,都应当在这里登记**。inputch 是内核里最基本的**输入路由单位**:
|
||||
只有登记过的通道才能在 inputch 登记表里出现,父 agent 才能"把某个 inputch 划给驻留子";
|
||||
没登记就划分会直接失败(`inputch 未注册`)。
|
||||
|
||||
只登记输出通道(RegisterOutputChannel)而没登记输入通道时,内核会兜底登记同名
|
||||
inputch 并打告警日志 —— 兜底只为兼容老插件,新插件请显式登记。
|
||||
|
||||
def.NoMemory: 此通道输入不参与记忆计算
|
||||
def.Cleaner: 计算层对输入文本清洗后(不改原文)再向量化/提关键词
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:52` | `_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{})` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:52` | `_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{})` |
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:209` | `_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{})` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:281` | `_ = s.RegisterInputChannel("calendar", sdk.ChannelDef{NoMemory: true})` |
|
||||
|
||||
<small>`plugin.go:561`</small>
|
||||
|
||||
### `PluginSDK.RegisterOutputChannel`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RegisterOutputChannel(name string, caps int, desc string, def ChannelDef, handler ToolHandler) error
|
||||
```
|
||||
|
||||
RegisterOutputChannel registers an output channel that the output_send tool can route to.
|
||||
|
||||
与 RegisterInputChannel 的分工:本函数声明**出站**(output_send__<name> 的回复发给谁);
|
||||
入站(谁会往 <name> 注入输入)是另一件事,用 RegisterInputChannel 声明。
|
||||
若该通道同时也是你的注入入口,两个都要登记。
|
||||
|
||||
name: channel name (e.g. "qq", "webui")。
|
||||
|
||||
❗**命名约束**:内核会把通道名拼进 LLM 的函数名(`output_send__<name>`),
|
||||
而上游对函数名的规范是 `^[a-zA-Z0-9_-]{1,64}$`。违反的后果不是"这个工具不可用",
|
||||
而是**整条请求被上游 400 拒绝**(`Invalid 'tools[N].function.name'`),
|
||||
网关的 auto tier 会全链条失败 —— 表现成"整个 agent 不说话了"。
|
||||
所以通道名只能用 `[A-Za-z0-9_-]`,且总长要留出 `output_send__`(13 字符)的余量。
|
||||
若通道名来自外部输入(设备自报 id 之类),请**在插件侧派生一个合规且唯一的名字**,
|
||||
而不是把原始值直接当通道名。
|
||||
caps: bitmask of supported output capabilities (CapText, CapFile, etc.)
|
||||
desc: description of the channel, expected meta format, and type enum
|
||||
def: 通道在记忆计算层的行为(NoMemory/Cleaner)
|
||||
handler: receives args map with keys: payload (string), type (string), meta (string|optional)
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:57` | `if err := s.RegisterOutputChannel(p.name, 1, "A2A Agent 互联通道(外部 agent 查询的回复由此返回)", sdk.ChannelDef{}, func(args…` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:57` | `s.RegisterOutputChannel(p.name, 1, "ACP Agent 互联通道(外部 agent 会话的回复由此返回)", sdk.ChannelDef{}, func(args map[strin…` |
|
||||
| [`qq`](../examples/index.md#qq) | `example/qq/plugin.go:411` | `s.RegisterOutputChannel("qq", sdk.CapText\|sdk.CapFile\|sdk.CapImage\|sdk.CapAudio,` |
|
||||
| [`weather`](../examples/index.md#weather) | `example/weather/plugin.go:104` | `if err := s.RegisterOutputChannel(tp+"weather_out", 0, "push weather to user", sdk.ChannelDef{` |
|
||||
|
||||
<small>`plugin.go:528`</small>
|
||||
|
||||
### `PluginSDK.SetToolBlocks`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetToolBlocks(blocks []ContentBlock)
|
||||
```
|
||||
|
||||
SetToolBlocks 在工具处理函数内注入多模态内容块,内核在下一条 tool message
|
||||
的 content 数组里带上它们。需要「本轮就让模型看到」时用 InjectInputMedia。
|
||||
|
||||
<small>`plugin.go:772`</small>
|
||||
|
||||
### `ValidContextPolicy`
|
||||
|
||||
```go
|
||||
func ValidContextPolicy(policy string) bool
|
||||
```
|
||||
|
||||
ValidContextPolicy 校验策略取值;空串等价于 ContextPolicyNone。
|
||||
|
||||
<small>`plugin.go:49`</small>
|
||||
|
||||
### `ValidRecallPolicy`
|
||||
|
||||
```go
|
||||
func ValidRecallPolicy(policy string) bool
|
||||
```
|
||||
|
||||
ValidRecallPolicy 校验召回策略取值;空串按调用面取默认值。
|
||||
|
||||
<small>`plugin.go:69`</small>
|
||||
|
||||
72
docs/api/constants.md
Normal file
72
docs/api/constants.md
Normal file
@ -0,0 +1,72 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 常量与枚举
|
||||
|
||||
SDK 里的取值枚举。其中带「仅内置」标注的取值在内核侧会被夹到较低级别。
|
||||
|
||||
## StageOnInput 等
|
||||
|
||||
| 名称 | 说明 |
|
||||
|---|---|
|
||||
| `StageOnInput` | |
|
||||
| `StagePreAction` | |
|
||||
| `StagePostAction` | |
|
||||
| `StageBeforeToolcall` | |
|
||||
| `StageAfterToolcall` | |
|
||||
| `StageBeforeOutput` | |
|
||||
| `StageAfterOutput` | |
|
||||
|
||||
## ContextPolicyNone 等
|
||||
|
||||
| 名称 | 说明 |
|
||||
|---|---|
|
||||
| `ContextPolicyNone` | |
|
||||
| `ContextPolicyPrune` | |
|
||||
|
||||
## RecallPolicyNone 等
|
||||
|
||||
| 名称 | 说明 |
|
||||
|---|---|
|
||||
| `RecallPolicyNone` | |
|
||||
| `RecallPolicyAuto` | |
|
||||
|
||||
## PriorityL1 等
|
||||
|
||||
| 名称 | 说明 |
|
||||
|---|---|
|
||||
| `PriorityL1` | |
|
||||
| `PriorityL2` | |
|
||||
| `PriorityL3` | |
|
||||
| `PriorityL4` | PriorityL4 仅内核级(内置)插件可用;外部插件声明会被夹到 L3。 |
|
||||
|
||||
## EventRawInput 等
|
||||
|
||||
| 名称 | 说明 |
|
||||
|---|---|
|
||||
| `EventRawInput` | |
|
||||
| `EventAgentOutput` | |
|
||||
| `EventAgentLLMChain` | |
|
||||
| `EventToolCall` | |
|
||||
| `EventReasoning` | |
|
||||
| `EventStage` | |
|
||||
| `EventSystem` | |
|
||||
| `EventReasoningDelta` | 流式增量事件(token 级):核心 process() 流式化后每收到一个增量块发布。 |
|
||||
| `EventContentDelta` | |
|
||||
|
||||
## StageScopeGlobal 等
|
||||
|
||||
| 名称 | 说明 |
|
||||
|---|---|
|
||||
| `StageScopeGlobal` | StageScopeGlobal receives all stage events (default). |
|
||||
| `StageScopeOwnTools` | StageScopeOwnTools only receives events for this plugin's own tool calls |
|
||||
|
||||
## CapText 等
|
||||
|
||||
| 名称 | 说明 |
|
||||
|---|---|
|
||||
| `CapText` | |
|
||||
| `CapFile` | |
|
||||
| `CapImage` | |
|
||||
| `CapAudio` | |
|
||||
| `CapStructured` | |
|
||||
|
||||
81
docs/api/events.md
Normal file
81
docs/api/events.md
Normal file
@ -0,0 +1,81 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 事件(Events)
|
||||
|
||||
订阅内核事件。**注意**:外部分布式插件的事件订阅不走 `Events()`(该接口在外部插件路径上未被注入,恒为 nil),而是由 `hmapdev` 生成的运行时通过 `events.subscribe` 完成。详见下方说明。
|
||||
|
||||
## `EventSubscriber`
|
||||
|
||||
EventSubscriber allows plugins to subscribe to kernel events.
|
||||
This is a restricted interface: plugins can subscribe but the kernel
|
||||
controls which events are delivered.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Subscribe`](#eventsubscribersubscribe) | |
|
||||
|
||||
### `EventSubscriber.Subscribe`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
|
||||
```go
|
||||
Subscribe(eventType EventType, handler EventHandler) func()
|
||||
```
|
||||
|
||||
<small>`plugin.go:279`</small>
|
||||
|
||||
### `Event`
|
||||
|
||||
```go
|
||||
type Event struct { Type EventType `json:"type"` Source string `json:"source"` Payload map[string]interface{} `json:"payload"` T …
|
||||
```
|
||||
|
||||
Event represents a system event published by the kernel.
|
||||
|
||||
<small>`plugin.go:265`</small>
|
||||
|
||||
### `EventHandler`
|
||||
|
||||
```go
|
||||
type EventHandler func(evt *Event)
|
||||
```
|
||||
|
||||
EventHandler processes a system event.
|
||||
|
||||
<small>`plugin.go:273`</small>
|
||||
|
||||
### `EventSubscriber`
|
||||
|
||||
```go
|
||||
type EventSubscriber interface { Subscribe(eventType EventType, handler EventHandler) func() }
|
||||
```
|
||||
|
||||
EventSubscriber allows plugins to subscribe to kernel events.
|
||||
This is a restricted interface: plugins can subscribe but the kernel
|
||||
controls which events are delivered.
|
||||
|
||||
<small>`plugin.go:278`</small>
|
||||
|
||||
### `EventType`
|
||||
|
||||
```go
|
||||
type EventType string
|
||||
```
|
||||
|
||||
EventType identifies the kind of system event.
|
||||
|
||||
<small>`plugin.go:247`</small>
|
||||
|
||||
### `PluginSDK.Events`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
实测全仓 SetEventSubscriber 只有定义、无任何调用点(grep -rn SetEventSubscriber --include=*.go 仅命中 sdk/plugin.go 的定义与 lua_plugin.go 的一句注释)。外部插件拿到的 Events() 恒为 nil。外部插件订阅事件走的是桥接运行时的 events.subscribe RPC(proc_main.go.tmpl:1421 在插件侧分发、内核 protocol.go MethodEventsSubscribe),Lua 插件则走内部 SDK 的 Subscribe。这正是 PLUGIN_DEV.md 里没有说清的一处。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) Events() EventSubscriber
|
||||
```
|
||||
|
||||
Events returns the event subscriber for listening to kernel events (may be nil if not available).
|
||||
|
||||
<small>`plugin.go:446`</small>
|
||||
|
||||
39
docs/api/index.md
Normal file
39
docs/api/index.md
Normal file
@ -0,0 +1,39 @@
|
||||
# API 参考
|
||||
|
||||
本页所有内容**从源码生成**(`tools/apidoc`),签名与说明直接取自 `sdk/*.go` 的
|
||||
文档注释。因此不存在「文档写了一套、代码是另一套」的情况——发现不一致时,
|
||||
改的是源码注释,不是这里。
|
||||
|
||||
## 怎么找 API
|
||||
|
||||
<div id="api-search"></div>
|
||||
|
||||
用上面的搜索框可以:
|
||||
|
||||
- **按名称搜**:`InjectText`、`RegisterTool`、`memory.recall`
|
||||
- **按描述搜**:`注册工具`、`注入`、`重载`、`崩溃`
|
||||
- **按签名搜**:`(string) error`、`[]ContentBlock`
|
||||
- 带 <span class="api-badge api-badge-builtin">仅内置</span>
|
||||
标记的条目在**外部插件里拿不到**,多数情况下你不需要它
|
||||
|
||||
## 章节划分
|
||||
|
||||
按「你想做什么」组织,不是按 Go 的符号类别:
|
||||
|
||||
| 章节 | 内容 |
|
||||
|---|---|
|
||||
| [工具(Tools)](tools.md) | 注册 LLM 可调用的工具——插件最常用的能力形态 |
|
||||
| [阶段钩子(Stages)](stages.md) | 在处理管道的固定点位插入逻辑 |
|
||||
| [记忆(Memory)](memory.md) | 三层记忆的读写:图 / 文档 / 文本,以及知识库 |
|
||||
| [输入/输出通道](channels.md) | 与外界交换消息,以及往流水线里注入内容 |
|
||||
| [配置(Settings)](settings.md) | 声明插件配置项,内核渲染到 WebUI |
|
||||
| [生命周期(Lifecycle)](lifecycle.md) | 启动、停止、卸载、自动重启 |
|
||||
| [事件(Events)](events.md) | 订阅内核事件 |
|
||||
| [LLM 调用](llm.md) | 插件主动调用模型 |
|
||||
| [常量与枚举](constants.md) | 取值枚举 |
|
||||
| [桥接装配点](bridge.md) | 由 `hmapdev` 生成的运行时调用,插件业务代码不碰 |
|
||||
| [仅内置插件可用](builtin-only.md) | 边界汇总——外部插件拿不到的 API 全在这里 |
|
||||
|
||||
!!! tip "先看「能力边界」能省很多时间"
|
||||
如果你正在设计插件,先读 [能力边界](../guide/capability-boundary.md):
|
||||
它说明哪些能力外部插件有、哪些没有,以及**为什么**。
|
||||
230
docs/api/lifecycle.md
Normal file
230
docs/api/lifecycle.md
Normal file
@ -0,0 +1,230 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 生命周期(Lifecycle)
|
||||
|
||||
插件的启动、停止与卸载回调。停止与卸载是两件事:**停止**是进程/加载状态变化,**卸载**(onRemove)是插件被删除前的清理机会。
|
||||
|
||||
## `Plugin`
|
||||
|
||||
Plugin is the interface every plugin must implement.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Name`](#pluginname) | |
|
||||
| [`Start`](#pluginstart) | |
|
||||
| [`Stop`](#pluginstop) | |
|
||||
|
||||
### `Plugin.Name`
|
||||
|
||||
```go
|
||||
Name() string
|
||||
```
|
||||
|
||||
<small>`plugin.go:14`</small>
|
||||
|
||||
### `Plugin.Start`
|
||||
|
||||
```go
|
||||
Start(sdk *PluginSDK) error
|
||||
```
|
||||
|
||||
<small>`plugin.go:15`</small>
|
||||
|
||||
### `Plugin.Stop`
|
||||
|
||||
```go
|
||||
Stop() error
|
||||
```
|
||||
|
||||
<small>`plugin.go:16`</small>
|
||||
|
||||
## `PluginMgrAPI`
|
||||
|
||||
PluginMgrAPI 提供插件管理能力(外部插件可调用)。
|
||||
由 bridge 注入 dispatch 实现,走 C ABI CORE_PLUGIN_RELOAD_ONE 等。
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`IsPluginDisabled`](#pluginmgrapiisplugindisabled) | IsPluginDisabled 查询插件是否被禁用。 |
|
||||
| [`ListLoadedPlugins`](#pluginmgrapilistloadedplugins) | ListLoadedPlugins 列出已加载插件。 |
|
||||
| [`ReloadOne`](#pluginmgrapireloadone) | ReloadOne 重载单个插件(停止后重新加载)。 |
|
||||
|
||||
### `PluginMgrAPI.IsPluginDisabled`
|
||||
|
||||
```go
|
||||
IsPluginDisabled(name string) bool
|
||||
```
|
||||
|
||||
IsPluginDisabled 查询插件是否被禁用。
|
||||
|
||||
<small>`plugin.go:290`</small>
|
||||
|
||||
### `PluginMgrAPI.ListLoadedPlugins`
|
||||
|
||||
```go
|
||||
ListLoadedPlugins() []string
|
||||
```
|
||||
|
||||
ListLoadedPlugins 列出已加载插件。
|
||||
|
||||
<small>`plugin.go:288`</small>
|
||||
|
||||
### `PluginMgrAPI.ReloadOne`
|
||||
|
||||
```go
|
||||
ReloadOne(name string) error
|
||||
```
|
||||
|
||||
ReloadOne 重载单个插件(停止后重新加载)。
|
||||
|
||||
<small>`plugin.go:286`</small>
|
||||
|
||||
### `PluginSDK.AutoRestart`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) AutoRestart() bool
|
||||
```
|
||||
|
||||
AutoRestart 返回插件是否允许自动重启。
|
||||
|
||||
<small>`plugin.go:791`</small>
|
||||
|
||||
### `Plugin`
|
||||
|
||||
```go
|
||||
type Plugin interface { Name() string Start(sdk *PluginSDK) error Stop() error }
|
||||
```
|
||||
|
||||
Plugin is the interface every plugin must implement.
|
||||
|
||||
<small>`plugin.go:13`</small>
|
||||
|
||||
### `PluginSDK.PluginMgr`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) PluginMgr() PluginMgrAPI
|
||||
```
|
||||
|
||||
PluginMgr returns the plugin manager API (ReloadOne / ReloadPlugins / list).
|
||||
May be nil if the host did not wire it.
|
||||
|
||||
<small>`plugin.go:653`</small>
|
||||
|
||||
### `PluginMgrAPI`
|
||||
|
||||
```go
|
||||
type PluginMgrAPI interface { // ReloadOne 重载单个插件(停止后重新加载)。 ReloadOne(name string) error // ListLoadedPlugins 列出已加载插件。 ListLoa …
|
||||
```
|
||||
|
||||
PluginMgrAPI 提供插件管理能力(外部插件可调用)。
|
||||
由 bridge 注入 dispatch 实现,走 C ABI CORE_PLUGIN_RELOAD_ONE 等。
|
||||
|
||||
<small>`plugin.go:284`</small>
|
||||
|
||||
### `PluginSDK.PluginName`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) PluginName() string
|
||||
```
|
||||
|
||||
PluginName returns the name of the plugin.
|
||||
|
||||
<small>`plugin.go:397`</small>
|
||||
|
||||
### `PluginSDK.RegisterOnRemoveHandler`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RegisterOnRemoveHandler(fn func())
|
||||
```
|
||||
|
||||
RegisterOnRemoveHandler 注册插件被删除(卸载)时的清理回调。
|
||||
注册的 handler 会在插件目录被移除前按"后注册先执行"的顺序调用,
|
||||
适用于清理外部资源、删除配置表、下线状态等删除后处理。
|
||||
可注册多个;执行后清空(一次删除只执行一次)。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:296` | `s.RegisterOnRemoveHandler(p.cleanupData)` |
|
||||
| [`memo`](../examples/index.md#memo) | `example/memo/plugin.go:69` | `s.RegisterOnRemoveHandler(p.cleanupData)` |
|
||||
| [`rss`](../examples/index.md#rss) | `example/rss/plugin.go:127` | `s.RegisterOnRemoveHandler(p.cleanupData)` |
|
||||
|
||||
<small>`plugin.go:826`</small>
|
||||
|
||||
### `PluginSDK.RegisterPluginAPI`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RegisterPluginAPI(name string) error
|
||||
```
|
||||
|
||||
RegisterPluginAPI registers this plugin's API for access by other plugins.
|
||||
|
||||
<small>`plugin.go:502`</small>
|
||||
|
||||
### `PluginSDK.RegisterStopHandler`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RegisterStopHandler(fn func())
|
||||
```
|
||||
|
||||
RegisterStopHandler 注册插件停止阶段的清理回调。
|
||||
注册的 handler 会在插件 Stop() 之前按"后注册先执行"的顺序调用,
|
||||
适用于释放资源、落盘状态、关闭子进程等停止时清理操作。
|
||||
可注册多个;执行后清空(进程停止前只执行一次)。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:294` | `s.RegisterStopHandler(p.saveEvents)` |
|
||||
| [`deepsearch`](../examples/index.md#deepsearch) | `example/deepsearch/plugin.go:695` | `s.RegisterStopHandler(func() { p.shutdownSearxng() })` |
|
||||
|
||||
<small>`plugin.go:801`</small>
|
||||
|
||||
### `PluginSDK.RunOnRemoveHandlers`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RunOnRemoveHandlers()
|
||||
```
|
||||
|
||||
RunOnRemoveHandlers 执行全部已注册的 onRemove handler(后注册先执行,执行后清空,幂等)。
|
||||
由内核在卸载插件(registry.RemovePlugin)时、插件 Stop() 之后执行。
|
||||
|
||||
<small>`plugin.go:837`</small>
|
||||
|
||||
### `PluginSDK.RunStopHandlers`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RunStopHandlers()
|
||||
```
|
||||
|
||||
RunStopHandlers 执行全部已注册的 stop handler(后注册先执行,执行后清空,幂等)。
|
||||
由内核(内置插件)或插件桥接层(外部插件 z_bridge 的 StopPlugin)在调用插件 Stop() 前执行。
|
||||
|
||||
<small>`plugin.go:812`</small>
|
||||
|
||||
### `PluginSDK.SetAutoRestart`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) SetAutoRestart(enabled bool)
|
||||
```
|
||||
|
||||
SetAutoRestart 设置插件崩溃后内核是否自动重启它。
|
||||
默认 true。如果插件有无法恢复的状态(如外部连接),应设为 false。
|
||||
|
||||
重启是有限度的:线性退避(第 n 次等 n×1s,即 1s→2s→3s),
|
||||
且同一 5 分钟窗口内第 4 次崩溃就停下不再拉起(详见 README)。
|
||||
注意这与「重载」(换 plugin.bin 后重新加载)是两回事。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:47` | `s.SetAutoRestart(true)` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:47` | `s.SetAutoRestart(true)` |
|
||||
| [`ai_image`](../examples/index.md#ai_image) | `example/ai_image/plugin.go:110` | `s.SetAutoRestart(true)` |
|
||||
| [`bili`](../examples/index.md#bili) | `example/bili/plugin.go:25` | `s.SetAutoRestart(true)` |
|
||||
|
||||
<small>`plugin.go:784`</small>
|
||||
|
||||
60
docs/api/llm.md
Normal file
60
docs/api/llm.md
Normal file
@ -0,0 +1,60 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# LLM 调用
|
||||
|
||||
让插件自己调用模型(而不是只等模型来调你)。
|
||||
|
||||
## `LLMAPI`
|
||||
|
||||
LLMAPI provides access to the LLM provider manager.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`CurrentSource`](#llmapicurrentsource) | |
|
||||
| [`ListSources`](#llmapilistsources) | |
|
||||
| [`SetSource`](#llmapisetsource) | |
|
||||
|
||||
### `LLMAPI.CurrentSource`
|
||||
|
||||
```go
|
||||
CurrentSource() string
|
||||
```
|
||||
|
||||
<small>`llm.go:7`</small>
|
||||
|
||||
### `LLMAPI.ListSources`
|
||||
|
||||
```go
|
||||
ListSources() []string
|
||||
```
|
||||
|
||||
<small>`llm.go:5`</small>
|
||||
|
||||
### `LLMAPI.SetSource`
|
||||
|
||||
```go
|
||||
SetSource(name string) error
|
||||
```
|
||||
|
||||
<small>`llm.go:6`</small>
|
||||
|
||||
### `PluginSDK.LLM`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) LLM() LLMAPI
|
||||
```
|
||||
|
||||
LLM returns the LLM provider API (may be nil if not available).
|
||||
|
||||
<small>`plugin.go:432`</small>
|
||||
|
||||
### `LLMAPI`
|
||||
|
||||
```go
|
||||
type LLMAPI interface { ListSources() []string SetSource(name string) error CurrentSource() string }
|
||||
```
|
||||
|
||||
LLMAPI provides access to the LLM provider manager.
|
||||
|
||||
<small>`llm.go:4`</small>
|
||||
|
||||
434
docs/api/memory.md
Normal file
434
docs/api/memory.md
Normal file
@ -0,0 +1,434 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 记忆(Memory)
|
||||
|
||||
三层记忆的读写接口:**图记忆**(三元组关系)、**文档记忆**(带元数据的文档)、**文本记忆**(事件流水)。以及知识库。
|
||||
|
||||
## `DocMemoryAPI`
|
||||
|
||||
DocMemoryAPI provides access to the document vector store.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Insert`](#docmemoryapiinsert) | |
|
||||
| [`InsertWithMedia`](#docmemoryapiinsertwithmedia) | InsertWithMedia 写入文档并关联媒体。attachments 里带 Data 的会落进 |
|
||||
| [`Query`](#docmemoryapiquery) | |
|
||||
| [`Remove`](#docmemoryapiremove) | |
|
||||
| [`Stats`](#docmemoryapistats) | |
|
||||
|
||||
### `DocMemoryAPI.Insert`
|
||||
|
||||
```go
|
||||
Insert(doc *Doc) error
|
||||
```
|
||||
|
||||
<small>`memory.go:76`</small>
|
||||
|
||||
### `DocMemoryAPI.InsertWithMedia`
|
||||
|
||||
```go
|
||||
InsertWithMedia(doc *Doc, attachments []MediaAttachment) error
|
||||
```
|
||||
|
||||
InsertWithMedia 写入文档并关联媒体。attachments 里带 Data 的会落进
|
||||
内容寻址存储(相同字节只存一份),只带 Digest 的直接引用已有内容。
|
||||
媒体成为文档直接持有的一等记忆块:文档向量会融合它们的原生向量,
|
||||
因此图片按自己的向量被召回,不依赖任何生成的描述文本。
|
||||
|
||||
<small>`memory.go:81`</small>
|
||||
|
||||
### `DocMemoryAPI.Query`
|
||||
|
||||
```go
|
||||
Query(text string, topK int) []*Doc
|
||||
```
|
||||
|
||||
<small>`memory.go:75`</small>
|
||||
|
||||
### `DocMemoryAPI.Remove`
|
||||
|
||||
```go
|
||||
Remove(id string)
|
||||
```
|
||||
|
||||
<small>`memory.go:82`</small>
|
||||
|
||||
### `DocMemoryAPI.Stats`
|
||||
|
||||
```go
|
||||
Stats() map[string]interface{}
|
||||
```
|
||||
|
||||
<small>`memory.go:83`</small>
|
||||
|
||||
## `KnowledgeAPI`
|
||||
|
||||
KnowledgeAPI provides access to the knowledge store.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Add`](#knowledgeapiadd) | |
|
||||
| [`List`](#knowledgeapilist) | |
|
||||
| [`Search`](#knowledgeapisearch) | |
|
||||
|
||||
### `KnowledgeAPI.Add`
|
||||
|
||||
```go
|
||||
Add(name, content string) error
|
||||
```
|
||||
|
||||
<small>`knowledge.go:6`</small>
|
||||
|
||||
### `KnowledgeAPI.List`
|
||||
|
||||
```go
|
||||
List() ([]string, error)
|
||||
```
|
||||
|
||||
<small>`knowledge.go:7`</small>
|
||||
|
||||
### `KnowledgeAPI.Search`
|
||||
|
||||
```go
|
||||
Search(query string, topK int) ([]*Knowledge, error)
|
||||
```
|
||||
|
||||
<small>`knowledge.go:5`</small>
|
||||
|
||||
## `MemoryAPI`
|
||||
|
||||
MemoryAPI provides access to the graph memory (entity-relation store).
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Commit`](#memoryapicommit) | |
|
||||
| [`Introspect`](#memoryapiintrospect) | |
|
||||
| [`MergeEntities`](#memoryapimergeentities) | |
|
||||
| [`Purge`](#memoryapipurge) | |
|
||||
| [`Recall`](#memoryapirecall) | |
|
||||
|
||||
### `MemoryAPI.Commit`
|
||||
|
||||
```go
|
||||
Commit(triples []Triple) error
|
||||
```
|
||||
|
||||
<small>`memory.go:6`</small>
|
||||
|
||||
### `MemoryAPI.Introspect`
|
||||
|
||||
```go
|
||||
Introspect() (map[string]interface{}, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:7`</small>
|
||||
|
||||
### `MemoryAPI.MergeEntities`
|
||||
|
||||
```go
|
||||
MergeEntities(source, target string) (int, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:8`</small>
|
||||
|
||||
### `MemoryAPI.Purge`
|
||||
|
||||
```go
|
||||
Purge(criteria map[string]string, mode string) (int, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:9`</small>
|
||||
|
||||
### `MemoryAPI.Recall`
|
||||
|
||||
```go
|
||||
Recall(query []string, depth int) ([]Entity, []Relation, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:5`</small>
|
||||
|
||||
## `SocialAPI`
|
||||
|
||||
SocialAPI provides read-only access to the social graph (person profiles and relationships).
|
||||
External plugins can query person traits and social networks but cannot modify them.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`GetNetwork`](#socialapigetnetwork) | |
|
||||
| [`GetPerson`](#socialapigetperson) | |
|
||||
| [`GetRelations`](#socialapigetrelations) | |
|
||||
| [`GetTrait`](#socialapigettrait) | |
|
||||
| [`ListPersons`](#socialapilistpersons) | |
|
||||
|
||||
### `SocialAPI.GetNetwork`
|
||||
|
||||
```go
|
||||
GetNetwork(name string, depth int) ([]*PersonProfile, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:104`</small>
|
||||
|
||||
### `SocialAPI.GetPerson`
|
||||
|
||||
```go
|
||||
GetPerson(name string) (*PersonProfile, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:101`</small>
|
||||
|
||||
### `SocialAPI.GetRelations`
|
||||
|
||||
```go
|
||||
GetRelations(name string) ([]SocialRelation, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:103`</small>
|
||||
|
||||
### `SocialAPI.GetTrait`
|
||||
|
||||
```go
|
||||
GetTrait(name, trait string) (string, bool)
|
||||
```
|
||||
|
||||
<small>`memory.go:102`</small>
|
||||
|
||||
### `SocialAPI.ListPersons`
|
||||
|
||||
```go
|
||||
ListPersons() ([]string, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:105`</small>
|
||||
|
||||
## `TextMemoryAPI`
|
||||
|
||||
TextMemoryAPI provides access to chronological text event storage.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Append`](#textmemoryapiappend) | |
|
||||
|
||||
### `TextMemoryAPI.Append`
|
||||
|
||||
```go
|
||||
Append(evt TextEvent) error
|
||||
```
|
||||
|
||||
<small>`memory.go:44`</small>
|
||||
|
||||
### `Doc`
|
||||
|
||||
```go
|
||||
type Doc struct { ID string `json:"id"` Title string `json:"title"` Content string `json:"content"` Score …
|
||||
```
|
||||
|
||||
Doc represents a document in the document store.
|
||||
|
||||
MediaDigests / Attachments 在 Query 返回时由内核填充(仅元数据,不带字节)。
|
||||
|
||||
<small>`memory.go:89`</small>
|
||||
|
||||
### `PluginSDK.DocMemory`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) DocMemory() DocMemoryAPI
|
||||
```
|
||||
|
||||
DocMemory returns the document memory API (may be nil if not available).
|
||||
|
||||
<small>`plugin.go:418`</small>
|
||||
|
||||
### `DocMemoryAPI`
|
||||
|
||||
```go
|
||||
type DocMemoryAPI interface { Query(text string, topK int) []*Doc Insert(doc *Doc) error // InsertWithMedia 写入文档并关联媒体。attachments 里带 Data 的会落进 …
|
||||
```
|
||||
|
||||
DocMemoryAPI provides access to the document vector store.
|
||||
|
||||
<small>`memory.go:74`</small>
|
||||
|
||||
### `Entity`
|
||||
|
||||
```go
|
||||
type Entity struct { Name string `json:"name"` Type string `json:"type"` MentionCount int `json:"mention_count"` }
|
||||
```
|
||||
|
||||
Entity represents a named entity in the knowledge graph.
|
||||
|
||||
<small>`memory.go:13`</small>
|
||||
|
||||
### `PluginSDK.Knowledge`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) Knowledge() KnowledgeAPI
|
||||
```
|
||||
|
||||
Knowledge returns the knowledge store API (may be nil if not available).
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`recoverydiag`](../examples/index.md#recoverydiag) | `example/recoverydiag/plugin.go:978` | `if p.sdk != nil && p.sdk.Knowledge() != nil {` |
|
||||
|
||||
<small>`plugin.go:425`</small>
|
||||
|
||||
### `Knowledge`
|
||||
|
||||
```go
|
||||
type Knowledge struct { Name string `json:"name"` Content string `json:"content"` }
|
||||
```
|
||||
|
||||
Knowledge represents a knowledge entry.
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`recoverydiag`](../examples/index.md#recoverydiag) | `example/recoverydiag/plugin.go:978` | `if p.sdk != nil && p.sdk.Knowledge() != nil {` |
|
||||
|
||||
<small>`knowledge.go:11`</small>
|
||||
|
||||
### `KnowledgeAPI`
|
||||
|
||||
```go
|
||||
type KnowledgeAPI interface { Search(query string, topK int) ([]*Knowledge, error) Add(name, content string) error List() ([]string, error) }
|
||||
```
|
||||
|
||||
KnowledgeAPI provides access to the knowledge store.
|
||||
|
||||
<small>`knowledge.go:4`</small>
|
||||
|
||||
### `MediaAttachment`
|
||||
|
||||
```go
|
||||
type MediaAttachment struct { Digest string `json:"digest,omitempty"` MIME string `json:"mime,omitempty"` Data []byte `json:"data,omitempty"` Name string `json:"name,omite …
|
||||
```
|
||||
|
||||
TextEvent represents a single text memory event.
|
||||
MediaAttachment 描述一份与记忆关联的媒体。
|
||||
|
||||
两个方向共用一个类型:
|
||||
- 写入(InsertWithMedia):给 Data + MIME 就是新内容;只给 Digest 则是引用已有内容。
|
||||
- 读出(Query):内核只填 Digest/MIME,**不回 Data**——
|
||||
一次检索可能命中几十张图,把字节全塞回插件会把 ABI 消息撑爆。
|
||||
需要字节时拿 Digest 单独取。
|
||||
|
||||
刻意没有 Description 字段:媒体不作为文本被索引,也不带任何生成的描述。
|
||||
它只按自己的原生向量被检索与召回;附加文字请写在文档 / 三元组的文本里。
|
||||
|
||||
<small>`memory.go:58`</small>
|
||||
|
||||
### `PluginSDK.Memory`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) Memory() MemoryAPI
|
||||
```
|
||||
|
||||
Memory returns the graph memory API (may be nil if not available).
|
||||
|
||||
<small>`plugin.go:404`</small>
|
||||
|
||||
### `MemoryAPI`
|
||||
|
||||
```go
|
||||
type MemoryAPI interface { Recall(query []string, depth int) ([]Entity, []Relation, error) Commit(triples []Triple) error Introspect() (map[string]interface{}, error) Merg …
|
||||
```
|
||||
|
||||
MemoryAPI provides access to the graph memory (entity-relation store).
|
||||
|
||||
<small>`memory.go:4`</small>
|
||||
|
||||
### `PersonProfile`
|
||||
|
||||
```go
|
||||
type PersonProfile struct { Name string `json:"name"` Traits map[string]string `json:"traits,omitempty"` Relations []SocialRelation `json:"relations,omitemp …
|
||||
```
|
||||
|
||||
PersonProfile represents a person's complete profile (traits + social relations).
|
||||
|
||||
<small>`memory.go:109`</small>
|
||||
|
||||
### `Relation`
|
||||
|
||||
```go
|
||||
type Relation struct { SourceName string `json:"source_name"` TargetName string `json:"target_name"` RelationType string `json:"relation_type"` Confidence float6 …
|
||||
```
|
||||
|
||||
Relation represents a relationship between two entities.
|
||||
|
||||
<small>`memory.go:20`</small>
|
||||
|
||||
### `PluginSDK.Social`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) Social() SocialAPI
|
||||
```
|
||||
|
||||
Social returns the social graph API (may be nil if not available).
|
||||
|
||||
<small>`plugin.go:439`</small>
|
||||
|
||||
### `SocialAPI`
|
||||
|
||||
```go
|
||||
type SocialAPI interface { GetPerson(name string) (*PersonProfile, error) GetTrait(name, trait string) (string, bool) GetRelations(name string) ([]SocialRelation, error) G …
|
||||
```
|
||||
|
||||
SocialAPI provides read-only access to the social graph (person profiles and relationships).
|
||||
External plugins can query person traits and social networks but cannot modify them.
|
||||
|
||||
<small>`memory.go:100`</small>
|
||||
|
||||
### `SocialRelation`
|
||||
|
||||
```go
|
||||
type SocialRelation struct { Person string `json:"person"` Relation string `json:"relation"` }
|
||||
```
|
||||
|
||||
SocialRelation represents a social relationship between two persons.
|
||||
|
||||
<small>`memory.go:116`</small>
|
||||
|
||||
### `TextEvent`
|
||||
|
||||
```go
|
||||
type TextEvent struct { Role string `json:"role"` Content string `json:"content"` Timestamp int64 `json:"timestamp"` Channel …
|
||||
```
|
||||
|
||||
<small>`memory.go:65`</small>
|
||||
|
||||
### `PluginSDK.TextMemory`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) TextMemory() TextMemoryAPI
|
||||
```
|
||||
|
||||
TextMemory returns the text memory API (may be nil if not available).
|
||||
|
||||
<small>`plugin.go:411`</small>
|
||||
|
||||
### `TextMemoryAPI`
|
||||
|
||||
```go
|
||||
type TextMemoryAPI interface { Append(evt TextEvent) error }
|
||||
```
|
||||
|
||||
TextMemoryAPI provides access to chronological text event storage.
|
||||
|
||||
<small>`memory.go:43`</small>
|
||||
|
||||
### `Triple`
|
||||
|
||||
```go
|
||||
type Triple struct { Subject string `json:"subject"` Relation string `json:"relation"` Object string `json:"object"` Confidence float64 `json:"c …
|
||||
```
|
||||
|
||||
Triple represents a subject-relation-object triple for the knowledge graph.
|
||||
|
||||
SentenceText 是这条三元组的原句,会写进 sentences 表;媒体引用挂在句子上,
|
||||
所以 MediaDigests 非空时内核会保证句子存在(不给就自动合成一句)。
|
||||
|
||||
<small>`memory.go:31`</small>
|
||||
|
||||
736
docs/api/misc.md
Normal file
736
docs/api/misc.md
Normal file
@ -0,0 +1,736 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 其他类型
|
||||
|
||||
剩余的类型与方法:`PluginSDK` 本体的访问器、`StageContext` 的并发控制,以及多模态辅助类型。没有归入上面任何一个主题,但可能仍会用到。
|
||||
|
||||
## `DocMemoryAPI`
|
||||
|
||||
DocMemoryAPI provides access to the document vector store.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Insert`](#docmemoryapiinsert) | |
|
||||
| [`InsertWithMedia`](#docmemoryapiinsertwithmedia) | InsertWithMedia 写入文档并关联媒体。attachments 里带 Data 的会落进 |
|
||||
| [`Query`](#docmemoryapiquery) | |
|
||||
| [`Remove`](#docmemoryapiremove) | |
|
||||
| [`Stats`](#docmemoryapistats) | |
|
||||
|
||||
### `DocMemoryAPI.Insert`
|
||||
|
||||
```go
|
||||
Insert(doc *Doc) error
|
||||
```
|
||||
|
||||
<small>`memory.go:76`</small>
|
||||
|
||||
### `DocMemoryAPI.InsertWithMedia`
|
||||
|
||||
```go
|
||||
InsertWithMedia(doc *Doc, attachments []MediaAttachment) error
|
||||
```
|
||||
|
||||
InsertWithMedia 写入文档并关联媒体。attachments 里带 Data 的会落进
|
||||
内容寻址存储(相同字节只存一份),只带 Digest 的直接引用已有内容。
|
||||
媒体成为文档直接持有的一等记忆块:文档向量会融合它们的原生向量,
|
||||
因此图片按自己的向量被召回,不依赖任何生成的描述文本。
|
||||
|
||||
<small>`memory.go:81`</small>
|
||||
|
||||
### `DocMemoryAPI.Query`
|
||||
|
||||
```go
|
||||
Query(text string, topK int) []*Doc
|
||||
```
|
||||
|
||||
<small>`memory.go:75`</small>
|
||||
|
||||
### `DocMemoryAPI.Remove`
|
||||
|
||||
```go
|
||||
Remove(id string)
|
||||
```
|
||||
|
||||
<small>`memory.go:82`</small>
|
||||
|
||||
### `DocMemoryAPI.Stats`
|
||||
|
||||
```go
|
||||
Stats() map[string]interface{}
|
||||
```
|
||||
|
||||
<small>`memory.go:83`</small>
|
||||
|
||||
## `EventSubscriber`
|
||||
|
||||
EventSubscriber allows plugins to subscribe to kernel events.
|
||||
This is a restricted interface: plugins can subscribe but the kernel
|
||||
controls which events are delivered.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Subscribe`](#eventsubscribersubscribe) | |
|
||||
|
||||
### `EventSubscriber.Subscribe`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
|
||||
```go
|
||||
Subscribe(eventType EventType, handler EventHandler) func()
|
||||
```
|
||||
|
||||
<small>`plugin.go:279`</small>
|
||||
|
||||
## `IOInjector`
|
||||
|
||||
IOInjector provides methods for injecting input and interrupts into the agent pipeline.
|
||||
All methods accept (source, channel) where channel is the target output channel
|
||||
for routing the agent's response.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`InjectInputMedia`](#ioinjectorinjectinputmedia) | |
|
||||
| [`InjectInputMediaOpts`](#ioinjectorinjectinputmediaopts) | |
|
||||
| [`InjectInputMediaSync`](#ioinjectorinjectinputmediasync) | |
|
||||
| [`InjectInputMediaSyncOpts`](#ioinjectorinjectinputmediasyncopts) | |
|
||||
| [`InjectInputSync`](#ioinjectorinjectinputsync) | InjectInputSync 注入输入事件并同步等待 agent 回复,返回回复文本(无回复时返回空串)。 |
|
||||
| [`InjectInputSyncOpts`](#ioinjectorinjectinputsyncopts) | |
|
||||
| [`InjectInterruptMedia`](#ioinjectorinjectinterruptmedia) | |
|
||||
| [`InjectInterruptMediaOpts`](#ioinjectorinjectinterruptmediaopts) | |
|
||||
| [`InjectInterruptText`](#ioinjectorinjectinterrupttext) | |
|
||||
| [`InjectInterruptTextOpts`](#ioinjectorinjectinterrupttextopts) | |
|
||||
| [`InjectText`](#ioinjectorinjecttext) | |
|
||||
| [`InjectTextNoMemory`](#ioinjectorinjecttextnomemory) | |
|
||||
| [`InjectTextOpts`](#ioinjectorinjecttextopts) | 以下 Opts 变体让调用点在**这一次注入**上声明记忆与裁剪行为。 |
|
||||
| [`SetToolBlocks`](#ioinjectorsettoolblocks) | SetToolBlocks 插件工具注入多模态内容块(image_url/audio_url),内核在下一条 |
|
||||
|
||||
### `IOInjector.InjectInputMedia`
|
||||
|
||||
```go
|
||||
InjectInputMedia(source, channel, text string, blocks []ContentBlock)
|
||||
```
|
||||
|
||||
<small>`plugin.go:230`</small>
|
||||
|
||||
### `IOInjector.InjectInputMediaOpts`
|
||||
|
||||
```go
|
||||
InjectInputMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
|
||||
```
|
||||
|
||||
<small>`plugin.go:241`</small>
|
||||
|
||||
### `IOInjector.InjectInputMediaSync`
|
||||
|
||||
```go
|
||||
InjectInputMediaSync(source, channel, text string, blocks []ContentBlock) string
|
||||
```
|
||||
|
||||
<small>`plugin.go:231`</small>
|
||||
|
||||
### `IOInjector.InjectInputMediaSyncOpts`
|
||||
|
||||
```go
|
||||
InjectInputMediaSyncOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions) string
|
||||
```
|
||||
|
||||
<small>`plugin.go:242`</small>
|
||||
|
||||
### `IOInjector.InjectInputSync`
|
||||
|
||||
```go
|
||||
InjectInputSync(source, channel, text string) string
|
||||
```
|
||||
|
||||
InjectInputSync 注入输入事件并同步等待 agent 回复,返回回复文本(无回复时返回空串)。
|
||||
用于通道消息的完整闭环:收到入站 → agent 处理 → 回复取回 → 送回通道。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:345` | `reply := p.sdk.InjectInputSync(p.name, p.name,` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:237` | `reply = p.sdk.InjectInputSync(p.name, p.name,` |
|
||||
|
||||
<small>`plugin.go:226`</small>
|
||||
|
||||
### `IOInjector.InjectInputSyncOpts`
|
||||
|
||||
```go
|
||||
InjectInputSyncOpts(source, channel, text string, opts InjectOptions) string
|
||||
```
|
||||
|
||||
<small>`plugin.go:240`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptMedia`
|
||||
|
||||
```go
|
||||
InjectInterruptMedia(source, channel, text string, blocks []ContentBlock)
|
||||
```
|
||||
|
||||
<small>`plugin.go:232`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptMediaOpts`
|
||||
|
||||
```go
|
||||
InjectInterruptMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
|
||||
```
|
||||
|
||||
<small>`plugin.go:243`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptText`
|
||||
|
||||
```go
|
||||
InjectInterruptText(source, channel, text string)
|
||||
```
|
||||
|
||||
<small>`plugin.go:221`</small>
|
||||
|
||||
### `IOInjector.InjectInterruptTextOpts`
|
||||
|
||||
```go
|
||||
InjectInterruptTextOpts(source, channel, text string, opts InjectOptions)
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:1319` | `p.sdk.InjectInterruptTextOpts(p.name, p.name,` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:527` | `p.sdk.InjectInterruptTextOpts("calendar", "calendar", msg, sdk.InjectOptions{NoMemory: true})` |
|
||||
| [`memo`](../examples/index.md#memo) | `example/memo/plugin.go:299` | `p.sdk.InjectInterruptTextOpts(p.name, p.name,` |
|
||||
| [`qq`](../examples/index.md#qq) | `example/qq/plugin.go:1493` | `p.sdk.InjectInterruptTextOpts(p.name, p.name, text, sdk.InjectOptions{` |
|
||||
|
||||
<small>`plugin.go:239`</small>
|
||||
|
||||
### `IOInjector.InjectText`
|
||||
|
||||
```go
|
||||
InjectText(source, channel, text string)
|
||||
```
|
||||
|
||||
<small>`plugin.go:222`</small>
|
||||
|
||||
### `IOInjector.InjectTextNoMemory`
|
||||
|
||||
```go
|
||||
InjectTextNoMemory(source, channel, text string)
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:1114` | `p.sdk.InjectTextNoMemory(p.name, p.name, fmt.Sprintf("[浏览器 %s 已导航到 %s]", id, rawURL))` |
|
||||
|
||||
<small>`plugin.go:223`</small>
|
||||
|
||||
### `IOInjector.InjectTextOpts`
|
||||
|
||||
```go
|
||||
InjectTextOpts(source, channel, text string, opts InjectOptions)
|
||||
```
|
||||
|
||||
以下 Opts 变体让调用点在**这一次注入**上声明记忆与裁剪行为。
|
||||
|
||||
上面那些不带 opts 的方法等价于传零值 InjectOptions(记入记忆 + 不裁剪),
|
||||
保留它们是为了不破坏已有插件;新代码应当用 Opts 变体把意图写清楚。
|
||||
|
||||
<small>`plugin.go:238`</small>
|
||||
|
||||
### `IOInjector.SetToolBlocks`
|
||||
|
||||
```go
|
||||
SetToolBlocks(blocks []ContentBlock)
|
||||
```
|
||||
|
||||
SetToolBlocks 插件工具注入多模态内容块(image_url/audio_url),内核在下一条
|
||||
tool message 的 content 数组里带上这些块,让模型在后续轮次看到图/听到音频。
|
||||
|
||||
<small>`plugin.go:229`</small>
|
||||
|
||||
## `KnowledgeAPI`
|
||||
|
||||
KnowledgeAPI provides access to the knowledge store.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Add`](#knowledgeapiadd) | |
|
||||
| [`List`](#knowledgeapilist) | |
|
||||
| [`Search`](#knowledgeapisearch) | |
|
||||
|
||||
### `KnowledgeAPI.Add`
|
||||
|
||||
```go
|
||||
Add(name, content string) error
|
||||
```
|
||||
|
||||
<small>`knowledge.go:6`</small>
|
||||
|
||||
### `KnowledgeAPI.List`
|
||||
|
||||
```go
|
||||
List() ([]string, error)
|
||||
```
|
||||
|
||||
<small>`knowledge.go:7`</small>
|
||||
|
||||
### `KnowledgeAPI.Search`
|
||||
|
||||
```go
|
||||
Search(query string, topK int) ([]*Knowledge, error)
|
||||
```
|
||||
|
||||
<small>`knowledge.go:5`</small>
|
||||
|
||||
## `LLMAPI`
|
||||
|
||||
LLMAPI provides access to the LLM provider manager.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`CurrentSource`](#llmapicurrentsource) | |
|
||||
| [`ListSources`](#llmapilistsources) | |
|
||||
| [`SetSource`](#llmapisetsource) | |
|
||||
|
||||
### `LLMAPI.CurrentSource`
|
||||
|
||||
```go
|
||||
CurrentSource() string
|
||||
```
|
||||
|
||||
<small>`llm.go:7`</small>
|
||||
|
||||
### `LLMAPI.ListSources`
|
||||
|
||||
```go
|
||||
ListSources() []string
|
||||
```
|
||||
|
||||
<small>`llm.go:5`</small>
|
||||
|
||||
### `LLMAPI.SetSource`
|
||||
|
||||
```go
|
||||
SetSource(name string) error
|
||||
```
|
||||
|
||||
<small>`llm.go:6`</small>
|
||||
|
||||
## `MemoryAPI`
|
||||
|
||||
MemoryAPI provides access to the graph memory (entity-relation store).
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Commit`](#memoryapicommit) | |
|
||||
| [`Introspect`](#memoryapiintrospect) | |
|
||||
| [`MergeEntities`](#memoryapimergeentities) | |
|
||||
| [`Purge`](#memoryapipurge) | |
|
||||
| [`Recall`](#memoryapirecall) | |
|
||||
|
||||
### `MemoryAPI.Commit`
|
||||
|
||||
```go
|
||||
Commit(triples []Triple) error
|
||||
```
|
||||
|
||||
<small>`memory.go:6`</small>
|
||||
|
||||
### `MemoryAPI.Introspect`
|
||||
|
||||
```go
|
||||
Introspect() (map[string]interface{}, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:7`</small>
|
||||
|
||||
### `MemoryAPI.MergeEntities`
|
||||
|
||||
```go
|
||||
MergeEntities(source, target string) (int, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:8`</small>
|
||||
|
||||
### `MemoryAPI.Purge`
|
||||
|
||||
```go
|
||||
Purge(criteria map[string]string, mode string) (int, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:9`</small>
|
||||
|
||||
### `MemoryAPI.Recall`
|
||||
|
||||
```go
|
||||
Recall(query []string, depth int) ([]Entity, []Relation, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:5`</small>
|
||||
|
||||
## `Plugin`
|
||||
|
||||
Plugin is the interface every plugin must implement.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Name`](#pluginname) | |
|
||||
| [`Start`](#pluginstart) | |
|
||||
| [`Stop`](#pluginstop) | |
|
||||
|
||||
### `Plugin.Name`
|
||||
|
||||
```go
|
||||
Name() string
|
||||
```
|
||||
|
||||
<small>`plugin.go:14`</small>
|
||||
|
||||
### `Plugin.Start`
|
||||
|
||||
```go
|
||||
Start(sdk *PluginSDK) error
|
||||
```
|
||||
|
||||
<small>`plugin.go:15`</small>
|
||||
|
||||
### `Plugin.Stop`
|
||||
|
||||
```go
|
||||
Stop() error
|
||||
```
|
||||
|
||||
<small>`plugin.go:16`</small>
|
||||
|
||||
## `PluginMgrAPI`
|
||||
|
||||
PluginMgrAPI 提供插件管理能力(外部插件可调用)。
|
||||
由 bridge 注入 dispatch 实现,走 C ABI CORE_PLUGIN_RELOAD_ONE 等。
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`IsPluginDisabled`](#pluginmgrapiisplugindisabled) | IsPluginDisabled 查询插件是否被禁用。 |
|
||||
| [`ListLoadedPlugins`](#pluginmgrapilistloadedplugins) | ListLoadedPlugins 列出已加载插件。 |
|
||||
| [`ReloadOne`](#pluginmgrapireloadone) | ReloadOne 重载单个插件(停止后重新加载)。 |
|
||||
|
||||
### `PluginMgrAPI.IsPluginDisabled`
|
||||
|
||||
```go
|
||||
IsPluginDisabled(name string) bool
|
||||
```
|
||||
|
||||
IsPluginDisabled 查询插件是否被禁用。
|
||||
|
||||
<small>`plugin.go:290`</small>
|
||||
|
||||
### `PluginMgrAPI.ListLoadedPlugins`
|
||||
|
||||
```go
|
||||
ListLoadedPlugins() []string
|
||||
```
|
||||
|
||||
ListLoadedPlugins 列出已加载插件。
|
||||
|
||||
<small>`plugin.go:288`</small>
|
||||
|
||||
### `PluginMgrAPI.ReloadOne`
|
||||
|
||||
```go
|
||||
ReloadOne(name string) error
|
||||
```
|
||||
|
||||
ReloadOne 重载单个插件(停止后重新加载)。
|
||||
|
||||
<small>`plugin.go:286`</small>
|
||||
|
||||
## `SettingsAPI`
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`DataDir`](#settingsapidatadir) | DataDir returns the plugin-specific data directory (guaranteed to exist): |
|
||||
| [`Defs`](#settingsapidefs) | Defs returns config definitions matching the prefix. |
|
||||
| [`Dump`](#settingsapidump) | Dump returns all config values. |
|
||||
| [`Get`](#settingsapiget) | Get reads the plugin's own config value (config_<name> table). |
|
||||
| [`GetCore`](#settingsapigetcore) | GetCore reads the core config table. |
|
||||
| [`GetPlugin`](#settingsapigetplugin) | GetPlugin reads another plugin's config table. |
|
||||
| [`List`](#settingsapilist) | List returns all keys matching the given prefix. |
|
||||
| [`ListCore`](#settingsapilistcore) | ListCore lists core config keys matching the prefix. |
|
||||
| [`ListPlugin`](#settingsapilistplugin) | ListPlugin lists another plugin's config keys matching the prefix. |
|
||||
| [`Plugins`](#settingsapiplugins) | Plugins returns a list of all plugin config namespaces. |
|
||||
| [`RegisterDef`](#settingsapiregisterdef) | RegisterDef registers a config definition for UI display. |
|
||||
| [`Set`](#settingsapiset) | Set writes a config value to the plugin's own config table. |
|
||||
| [`SetCore`](#settingsapisetcore) | SetCore writes to the core config table. |
|
||||
| [`SetPlugin`](#settingsapisetplugin) | SetPlugin writes to another plugin's config table. |
|
||||
|
||||
### `SettingsAPI.DataDir`
|
||||
|
||||
```go
|
||||
DataDir() string
|
||||
```
|
||||
|
||||
DataDir returns the plugin-specific data directory (guaranteed to exist):
|
||||
<daemon data>/plugin_data/<plugin_name>. Plugins should persist any
|
||||
runtime files (generated images, caches, downloads) here.
|
||||
|
||||
<small>`settings.go:25`</small>
|
||||
|
||||
### `SettingsAPI.Defs`
|
||||
|
||||
```go
|
||||
Defs(prefix string) []*ConfigDef
|
||||
```
|
||||
|
||||
Defs returns config definitions matching the prefix.
|
||||
|
||||
<small>`settings.go:40`</small>
|
||||
|
||||
### `SettingsAPI.Dump`
|
||||
|
||||
```go
|
||||
Dump() map[string]interface{}
|
||||
```
|
||||
|
||||
Dump returns all config values.
|
||||
|
||||
<small>`settings.go:43`</small>
|
||||
|
||||
### `SettingsAPI.Get`
|
||||
|
||||
```go
|
||||
Get(key string) (interface{}, error)
|
||||
```
|
||||
|
||||
Get reads the plugin's own config value (config_<name> table).
|
||||
|
||||
<small>`settings.go:5`</small>
|
||||
|
||||
### `SettingsAPI.GetCore`
|
||||
|
||||
```go
|
||||
GetCore(key string) (interface{}, error)
|
||||
```
|
||||
|
||||
GetCore reads the core config table.
|
||||
|
||||
<small>`settings.go:14`</small>
|
||||
|
||||
### `SettingsAPI.GetPlugin`
|
||||
|
||||
```go
|
||||
GetPlugin(plugin, key string) (interface{}, error)
|
||||
```
|
||||
|
||||
GetPlugin reads another plugin's config table.
|
||||
|
||||
<small>`settings.go:28`</small>
|
||||
|
||||
### `SettingsAPI.List`
|
||||
|
||||
```go
|
||||
List(prefix string) ([]string, error)
|
||||
```
|
||||
|
||||
List returns all keys matching the given prefix.
|
||||
|
||||
<small>`settings.go:11`</small>
|
||||
|
||||
### `SettingsAPI.ListCore`
|
||||
|
||||
```go
|
||||
ListCore(prefix string) ([]string, error)
|
||||
```
|
||||
|
||||
ListCore lists core config keys matching the prefix.
|
||||
|
||||
<small>`settings.go:20`</small>
|
||||
|
||||
### `SettingsAPI.ListPlugin`
|
||||
|
||||
```go
|
||||
ListPlugin(plugin, prefix string) ([]string, error)
|
||||
```
|
||||
|
||||
ListPlugin lists another plugin's config keys matching the prefix.
|
||||
|
||||
<small>`settings.go:34`</small>
|
||||
|
||||
### `SettingsAPI.Plugins`
|
||||
|
||||
```go
|
||||
Plugins() []string
|
||||
```
|
||||
|
||||
Plugins returns a list of all plugin config namespaces.
|
||||
|
||||
<small>`settings.go:46`</small>
|
||||
|
||||
### `SettingsAPI.RegisterDef`
|
||||
|
||||
```go
|
||||
RegisterDef(def ConfigDef)
|
||||
```
|
||||
|
||||
RegisterDef registers a config definition for UI display.
|
||||
|
||||
<small>`settings.go:37`</small>
|
||||
|
||||
### `SettingsAPI.Set`
|
||||
|
||||
```go
|
||||
Set(key string, value interface{}) error
|
||||
```
|
||||
|
||||
Set writes a config value to the plugin's own config table.
|
||||
|
||||
<small>`settings.go:8`</small>
|
||||
|
||||
### `SettingsAPI.SetCore`
|
||||
|
||||
```go
|
||||
SetCore(key string, value interface{}) error
|
||||
```
|
||||
|
||||
SetCore writes to the core config table.
|
||||
|
||||
<small>`settings.go:17`</small>
|
||||
|
||||
### `SettingsAPI.SetPlugin`
|
||||
|
||||
```go
|
||||
SetPlugin(plugin, key string, value interface{}) error
|
||||
```
|
||||
|
||||
SetPlugin writes to another plugin's config table.
|
||||
|
||||
<small>`settings.go:31`</small>
|
||||
|
||||
## `SocialAPI`
|
||||
|
||||
SocialAPI provides read-only access to the social graph (person profiles and relationships).
|
||||
External plugins can query person traits and social networks but cannot modify them.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`GetNetwork`](#socialapigetnetwork) | |
|
||||
| [`GetPerson`](#socialapigetperson) | |
|
||||
| [`GetRelations`](#socialapigetrelations) | |
|
||||
| [`GetTrait`](#socialapigettrait) | |
|
||||
| [`ListPersons`](#socialapilistpersons) | |
|
||||
|
||||
### `SocialAPI.GetNetwork`
|
||||
|
||||
```go
|
||||
GetNetwork(name string, depth int) ([]*PersonProfile, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:104`</small>
|
||||
|
||||
### `SocialAPI.GetPerson`
|
||||
|
||||
```go
|
||||
GetPerson(name string) (*PersonProfile, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:101`</small>
|
||||
|
||||
### `SocialAPI.GetRelations`
|
||||
|
||||
```go
|
||||
GetRelations(name string) ([]SocialRelation, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:103`</small>
|
||||
|
||||
### `SocialAPI.GetTrait`
|
||||
|
||||
```go
|
||||
GetTrait(name, trait string) (string, bool)
|
||||
```
|
||||
|
||||
<small>`memory.go:102`</small>
|
||||
|
||||
### `SocialAPI.ListPersons`
|
||||
|
||||
```go
|
||||
ListPersons() ([]string, error)
|
||||
```
|
||||
|
||||
<small>`memory.go:105`</small>
|
||||
|
||||
## `TextMemoryAPI`
|
||||
|
||||
TextMemoryAPI provides access to chronological text event storage.
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`Append`](#textmemoryapiappend) | |
|
||||
|
||||
### `TextMemoryAPI.Append`
|
||||
|
||||
```go
|
||||
Append(evt TextEvent) error
|
||||
```
|
||||
|
||||
<small>`memory.go:44`</small>
|
||||
|
||||
### `AudioURL`
|
||||
|
||||
```go
|
||||
type AudioURL struct { URL string `json:"url"` }
|
||||
```
|
||||
|
||||
<small>`plugin.go:862`</small>
|
||||
|
||||
### `ImageURL`
|
||||
|
||||
```go
|
||||
type ImageURL struct { URL string `json:"url"` Detail string `json:"detail,omitempty"` }
|
||||
```
|
||||
|
||||
<small>`plugin.go:857`</small>
|
||||
|
||||
### `MemItem`
|
||||
|
||||
```go
|
||||
type MemItem struct { Role string `json:"role"` Content string `json:"content"` Score float64 `json:"score"` }
|
||||
```
|
||||
|
||||
MemItem represents a memory item in stage context.
|
||||
|
||||
<small>`plugin.go:179`</small>
|
||||
|
||||
### `PluginSDK`
|
||||
|
||||
```go
|
||||
type PluginSDK struct { name string regTool ToolRegistrar regStage StageRegistrar regAPI APIRegistrar regOutput OutputChannelRegistrar …
|
||||
```
|
||||
|
||||
PluginSDK is the main API surface provided to plugins at runtime.
|
||||
It wraps tool registration, settings, memory, knowledge, LLM, and IO injection.
|
||||
|
||||
<small>`plugin.go:337`</small>
|
||||
|
||||
### `SDKVersion`
|
||||
|
||||
```go
|
||||
var SDKVersion
|
||||
```
|
||||
|
||||
SDKVersion 是对外暴露的 SDK 版本号。
|
||||
|
||||
<small>`plugin.go:10`</small>
|
||||
|
||||
### `PluginSDK.UnregisterOutputChannel`
|
||||
|
||||
!!! warning "仅内核内置插件可用"
|
||||
外部插件的桥接模板只注入 SetOutputChannelRegistrar(proc_main.go.tmpl:693-705),**未**注入 SetOutputChannelUnregistrar(grep Unregistrar 在 templates/ 下无命中)。因此外部插件的 regOutputUnreg 为 nil,UnregisterOutputChannel 会命中 `if reg != nil` 的 else 分支**直接返回 nil**(sdk/plugin.go:539-549)——即**静默无效**:不报错、通道也没注销。内置插件由 internal/sdk/plugin.go:330 注入 cfg.RegOutputUnreg,真正生效。外部插件要让通道下线,只能重载插件。
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) UnregisterOutputChannel(name string) error
|
||||
```
|
||||
|
||||
UnregisterOutputChannel 注销一个输出通道(动态通道随资源生灭时必须调用)。
|
||||
|
||||
<small>`plugin.go:539`</small>
|
||||
|
||||
205
docs/api/settings.md
Normal file
205
docs/api/settings.md
Normal file
@ -0,0 +1,205 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 配置(Settings)
|
||||
|
||||
声明插件自己的配置项,内核会把它渲染到 WebUI 的设置页,并为每个插件维护独立的配置表。
|
||||
|
||||
## `SettingsAPI`
|
||||
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| [`DataDir`](#settingsapidatadir) | DataDir returns the plugin-specific data directory (guaranteed to exist): |
|
||||
| [`Defs`](#settingsapidefs) | Defs returns config definitions matching the prefix. |
|
||||
| [`Dump`](#settingsapidump) | Dump returns all config values. |
|
||||
| [`Get`](#settingsapiget) | Get reads the plugin's own config value (config_<name> table). |
|
||||
| [`GetCore`](#settingsapigetcore) | GetCore reads the core config table. |
|
||||
| [`GetPlugin`](#settingsapigetplugin) | GetPlugin reads another plugin's config table. |
|
||||
| [`List`](#settingsapilist) | List returns all keys matching the given prefix. |
|
||||
| [`ListCore`](#settingsapilistcore) | ListCore lists core config keys matching the prefix. |
|
||||
| [`ListPlugin`](#settingsapilistplugin) | ListPlugin lists another plugin's config keys matching the prefix. |
|
||||
| [`Plugins`](#settingsapiplugins) | Plugins returns a list of all plugin config namespaces. |
|
||||
| [`RegisterDef`](#settingsapiregisterdef) | RegisterDef registers a config definition for UI display. |
|
||||
| [`Set`](#settingsapiset) | Set writes a config value to the plugin's own config table. |
|
||||
| [`SetCore`](#settingsapisetcore) | SetCore writes to the core config table. |
|
||||
| [`SetPlugin`](#settingsapisetplugin) | SetPlugin writes to another plugin's config table. |
|
||||
|
||||
### `SettingsAPI.DataDir`
|
||||
|
||||
```go
|
||||
DataDir() string
|
||||
```
|
||||
|
||||
DataDir returns the plugin-specific data directory (guaranteed to exist):
|
||||
<daemon data>/plugin_data/<plugin_name>. Plugins should persist any
|
||||
runtime files (generated images, caches, downloads) here.
|
||||
|
||||
<small>`settings.go:25`</small>
|
||||
|
||||
### `SettingsAPI.Defs`
|
||||
|
||||
```go
|
||||
Defs(prefix string) []*ConfigDef
|
||||
```
|
||||
|
||||
Defs returns config definitions matching the prefix.
|
||||
|
||||
<small>`settings.go:40`</small>
|
||||
|
||||
### `SettingsAPI.Dump`
|
||||
|
||||
```go
|
||||
Dump() map[string]interface{}
|
||||
```
|
||||
|
||||
Dump returns all config values.
|
||||
|
||||
<small>`settings.go:43`</small>
|
||||
|
||||
### `SettingsAPI.Get`
|
||||
|
||||
```go
|
||||
Get(key string) (interface{}, error)
|
||||
```
|
||||
|
||||
Get reads the plugin's own config value (config_<name> table).
|
||||
|
||||
<small>`settings.go:5`</small>
|
||||
|
||||
### `SettingsAPI.GetCore`
|
||||
|
||||
```go
|
||||
GetCore(key string) (interface{}, error)
|
||||
```
|
||||
|
||||
GetCore reads the core config table.
|
||||
|
||||
<small>`settings.go:14`</small>
|
||||
|
||||
### `SettingsAPI.GetPlugin`
|
||||
|
||||
```go
|
||||
GetPlugin(plugin, key string) (interface{}, error)
|
||||
```
|
||||
|
||||
GetPlugin reads another plugin's config table.
|
||||
|
||||
<small>`settings.go:28`</small>
|
||||
|
||||
### `SettingsAPI.List`
|
||||
|
||||
```go
|
||||
List(prefix string) ([]string, error)
|
||||
```
|
||||
|
||||
List returns all keys matching the given prefix.
|
||||
|
||||
<small>`settings.go:11`</small>
|
||||
|
||||
### `SettingsAPI.ListCore`
|
||||
|
||||
```go
|
||||
ListCore(prefix string) ([]string, error)
|
||||
```
|
||||
|
||||
ListCore lists core config keys matching the prefix.
|
||||
|
||||
<small>`settings.go:20`</small>
|
||||
|
||||
### `SettingsAPI.ListPlugin`
|
||||
|
||||
```go
|
||||
ListPlugin(plugin, prefix string) ([]string, error)
|
||||
```
|
||||
|
||||
ListPlugin lists another plugin's config keys matching the prefix.
|
||||
|
||||
<small>`settings.go:34`</small>
|
||||
|
||||
### `SettingsAPI.Plugins`
|
||||
|
||||
```go
|
||||
Plugins() []string
|
||||
```
|
||||
|
||||
Plugins returns a list of all plugin config namespaces.
|
||||
|
||||
<small>`settings.go:46`</small>
|
||||
|
||||
### `SettingsAPI.RegisterDef`
|
||||
|
||||
```go
|
||||
RegisterDef(def ConfigDef)
|
||||
```
|
||||
|
||||
RegisterDef registers a config definition for UI display.
|
||||
|
||||
<small>`settings.go:37`</small>
|
||||
|
||||
### `SettingsAPI.Set`
|
||||
|
||||
```go
|
||||
Set(key string, value interface{}) error
|
||||
```
|
||||
|
||||
Set writes a config value to the plugin's own config table.
|
||||
|
||||
<small>`settings.go:8`</small>
|
||||
|
||||
### `SettingsAPI.SetCore`
|
||||
|
||||
```go
|
||||
SetCore(key string, value interface{}) error
|
||||
```
|
||||
|
||||
SetCore writes to the core config table.
|
||||
|
||||
<small>`settings.go:17`</small>
|
||||
|
||||
### `SettingsAPI.SetPlugin`
|
||||
|
||||
```go
|
||||
SetPlugin(plugin, key string, value interface{}) error
|
||||
```
|
||||
|
||||
SetPlugin writes to another plugin's config table.
|
||||
|
||||
<small>`settings.go:31`</small>
|
||||
|
||||
### `ConfigDef`
|
||||
|
||||
```go
|
||||
type ConfigDef struct { Key string `json:"key"` Default interface{} `json:"default,omitempty"` Type string `json:"type"` DisplayName string …
|
||||
```
|
||||
|
||||
ConfigDef describes a configuration field for the WebUI.
|
||||
|
||||
<small>`settings.go:50`</small>
|
||||
|
||||
### `PluginSDK.Settings`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) Settings() SettingsAPI
|
||||
```
|
||||
|
||||
Settings returns the settings API for reading/writing plugin configuration.
|
||||
sett 在 New 时一次性写入且无 setter,故不需要加锁。
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:68` | `s.Settings().RegisterDef(sdk.ConfigDef{` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:63` | `s.Settings().RegisterDef(sdk.ConfigDef{` |
|
||||
| [`ai_image`](../examples/index.md#ai_image) | `example/ai_image/plugin.go:114` | `s.Settings().RegisterDef(sdk.ConfigDef{` |
|
||||
| [`bili`](../examples/index.md#bili) | `example/bili/plugin.go:29` | `s.Settings().RegisterDef(sdk.ConfigDef{` |
|
||||
|
||||
<small>`plugin.go:401`</small>
|
||||
|
||||
### `SettingsAPI`
|
||||
|
||||
```go
|
||||
type SettingsAPI interface { // Get reads the plugin's own config value (config_<name> table). Get(key string) (interface{}, error) // Set writes a config value to the plugi …
|
||||
```
|
||||
|
||||
<small>`settings.go:3`</small>
|
||||
|
||||
154
docs/api/stages.md
Normal file
154
docs/api/stages.md
Normal file
@ -0,0 +1,154 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 阶段钩子(Stages)
|
||||
|
||||
在消息处理管道的固定点位插入自己的逻辑。阶段比工具更底层:工具是模型主动调用的,阶段是流程经过时必然触发的。
|
||||
|
||||
### `StageContext.IsResponded`
|
||||
|
||||
```go
|
||||
func (c *StageContext) IsResponded() bool
|
||||
```
|
||||
|
||||
<small>`plugin.go:172`</small>
|
||||
|
||||
### `StageContext.Lock`
|
||||
|
||||
```go
|
||||
func (c *StageContext) Lock()
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:158` | `p.sessMu.Lock()` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:128` | `p.srvMu.Lock()` |
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:427` | `p.mu.Lock()` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:433` | `p.mu.Lock()` |
|
||||
|
||||
<small>`plugin.go:170`</small>
|
||||
|
||||
### `StageContext.RLock`
|
||||
|
||||
```go
|
||||
func (c *StageContext) RLock()
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:267` | `p.mu.RLock()` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:649` | `p.mu.RLock()` |
|
||||
| [`memo`](../examples/index.md#memo) | `example/memo/plugin.go:224` | `p.mu.RLock()` |
|
||||
| [`qq`](../examples/index.md#qq) | `example/qq/plugin.go:1152` | `ctx.RLock()` |
|
||||
|
||||
<small>`plugin.go:168`</small>
|
||||
|
||||
### `StageContext.RUnlock`
|
||||
|
||||
```go
|
||||
func (c *StageContext) RUnlock()
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:273` | `p.mu.RUnlock()` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:650` | `defer p.mu.RUnlock()` |
|
||||
| [`memo`](../examples/index.md#memo) | `example/memo/plugin.go:229` | `p.mu.RUnlock()` |
|
||||
| [`qq`](../examples/index.md#qq) | `example/qq/plugin.go:1155` | `ctx.RUnlock()` |
|
||||
|
||||
<small>`plugin.go:169`</small>
|
||||
|
||||
### `PluginSDK.RegisterStage`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RegisterStage(stage Stage, handler StageHandler, scope ...StageScope)
|
||||
```
|
||||
|
||||
RegisterStage registers a handler for a pipeline stage.
|
||||
|
||||
scope: StageScopeGlobal (default) — receives all stage events.
|
||||
StageScopeOwnTools — only before_toolcall/after_toolcall for this plugin's tools.
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`memo`](../examples/index.md#memo) | `example/memo/plugin.go:152` | `s.RegisterStage(sdk.StagePreAction, p.stagePreAction)` |
|
||||
| [`qq`](../examples/index.md#qq) | `example/qq/plugin.go:719` | `s.RegisterStage(sdk.StageOnInput, p.onInputAuthContext, sdk.StageScopeGlobal)` |
|
||||
| [`sanitizer`](../examples/index.md#sanitizer) | `example/sanitizer/plugin.go:52` | `s.RegisterStage(sdk.StageOnInput, func(ctx *sdk.StageContext) error {` |
|
||||
| [`weather`](../examples/index.md#weather) | `example/weather/plugin.go:94` | `s.RegisterStage(sdk.StageAfterToolcall, func(ctx *sdk.StageContext) error {` |
|
||||
|
||||
<small>`plugin.go:467`</small>
|
||||
|
||||
### `Stage`
|
||||
|
||||
```go
|
||||
type Stage string
|
||||
```
|
||||
|
||||
Stage represents a point in the message processing pipeline.
|
||||
|
||||
<small>`plugin.go:26`</small>
|
||||
|
||||
### `StageContext`
|
||||
|
||||
```go
|
||||
type StageContext struct { mu sync.RWMutex RawMessage string UserID string GroupID string ContextMsgs []map[string]interface{} L …
|
||||
```
|
||||
|
||||
StageContext provides context for stage handlers.
|
||||
|
||||
<small>`plugin.go:148`</small>
|
||||
|
||||
### `StageHandler`
|
||||
|
||||
```go
|
||||
type StageHandler func(ctx *StageContext) error
|
||||
```
|
||||
|
||||
StageHandler is a function that handles a pipeline stage event.
|
||||
|
||||
<small>`plugin.go:23`</small>
|
||||
|
||||
### `StageRegistrar`
|
||||
|
||||
```go
|
||||
type StageRegistrar func(stage Stage, handler StageHandler)
|
||||
```
|
||||
|
||||
StageRegistrar registers a stage handler.
|
||||
|
||||
<small>`plugin.go:308`</small>
|
||||
|
||||
### `StageScope`
|
||||
|
||||
```go
|
||||
type StageScope int
|
||||
```
|
||||
|
||||
StageScope controls which events a stage handler receives.
|
||||
|
||||
<small>`plugin.go:294`</small>
|
||||
|
||||
### `StageContext.Unlock`
|
||||
|
||||
```go
|
||||
func (c *StageContext) Unlock()
|
||||
```
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:164` | `p.sessMu.Unlock()` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:129` | `defer p.srvMu.Unlock()` |
|
||||
| [`browser`](../examples/index.md#browser) | `example/browser/plugin.go:432` | `p.mu.Unlock()` |
|
||||
| [`calendar`](../examples/index.md#calendar) | `example/calendar/plugin.go:523` | `p.mu.Unlock()` |
|
||||
|
||||
<small>`plugin.go:171`</small>
|
||||
|
||||
77
docs/api/tools.md
Normal file
77
docs/api/tools.md
Normal file
@ -0,0 +1,77 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 工具(Tools)
|
||||
|
||||
注册 LLM 可调用的工具。工具是插件最主要的能力形态:模型看到 `ToolDef` 的说明后决定是否调用,调用时执行你的 `ToolHandler`。
|
||||
|
||||
### `ContentBlock`
|
||||
|
||||
```go
|
||||
type ContentBlock struct { Type string `json:"type"` Text string `json:"text,omitempty"` ImageURL *ImageURL `json:"image_url,omitempty"` AudioURL *AudioURL `jso …
|
||||
```
|
||||
|
||||
ContentBlock 是多模态内容块(OpenAI 格式:text/image_url/audio_url)。
|
||||
插件工具返回结果时可用 PluginSDK.SetToolBlocks 注入,让下一轮 LLM
|
||||
请求在 tool message 的 content 数组里带上图片/音频,实现"模型看图/听音频"。
|
||||
|
||||
<small>`plugin.go:850`</small>
|
||||
|
||||
### `PluginSDK.RegisterTool`
|
||||
|
||||
```go
|
||||
func (s *PluginSDK) RegisterTool(name string, def ToolDef, handler ToolHandler) error
|
||||
```
|
||||
|
||||
RegisterTool registers a tool that the LLM can call.
|
||||
|
||||
**示例插件里的真实用法**
|
||||
|
||||
| 插件 | 位置 | 代码 |
|
||||
|---|---|---|
|
||||
| [`a2a`](../examples/index.md#a2a) | `example/a2a/plugin.go:76` | `s.RegisterTool(tp+"a2a_query", sdk.ToolDef{` |
|
||||
| [`acp`](../examples/index.md#acp) | `example/acp/plugin.go:70` | `s.RegisterTool(tp+"acp_query", sdk.ToolDef{` |
|
||||
| [`ai_image`](../examples/index.md#ai_image) | `example/ai_image/plugin.go:159` | `s.RegisterTool(tp+"generate", sdk.ToolDef{` |
|
||||
| [`bili`](../examples/index.md#bili) | `example/bili/plugin.go:47` | `s.RegisterTool(tp+"video", sdk.ToolDef{` |
|
||||
|
||||
<small>`plugin.go:453`</small>
|
||||
|
||||
### `ToolCall`
|
||||
|
||||
```go
|
||||
type ToolCall struct { ID string `json:"id"` Name string `json:"name"` Plugin string `json:"plugin,omitempty …
|
||||
```
|
||||
|
||||
ToolCall represents a model's request to call a tool.
|
||||
|
||||
<small>`plugin.go:186`</small>
|
||||
|
||||
### `ToolDef`
|
||||
|
||||
```go
|
||||
type ToolDef struct { Name string `json:"name"` Plugin string `json:"plugin,omitempty"` Description string …
|
||||
```
|
||||
|
||||
ToolDef describes a tool that the plugin exposes.
|
||||
|
||||
<small>`plugin.go:203`</small>
|
||||
|
||||
### `ToolHandler`
|
||||
|
||||
```go
|
||||
type ToolHandler func(args map[string]interface{}) (interface{}, error)
|
||||
```
|
||||
|
||||
ToolHandler is a function that handles a tool call.
|
||||
|
||||
<small>`plugin.go:20`</small>
|
||||
|
||||
### `ToolResult`
|
||||
|
||||
```go
|
||||
type ToolResult struct { CallID string `json:"call_id"` Name string `json:"name"` Plugin string `json:"plugin,omitempty"` Success bool `json:"suc …
|
||||
```
|
||||
|
||||
ToolResult represents the result of a tool call.
|
||||
|
||||
<small>`plugin.go:194`</small>
|
||||
|
||||
2202
docs/assets/api-index.json
Normal file
2202
docs/assets/api-index.json
Normal file
File diff suppressed because it is too large
Load Diff
60
docs/examples/index.md
Normal file
60
docs/examples/index.md
Normal file
@ -0,0 +1,60 @@
|
||||
<!-- 本页由 tools/apidoc/gensite 从源码生成,请勿手改;要改文档就改 sdk/*.go 的注释。 -->
|
||||
|
||||
# 示例插件
|
||||
|
||||
SDK 仓 `example/` 下有多个**真实可编译**的示例插件,覆盖工具注册、通道、记忆读写、LLM 调用、生命周期等常见形态。
|
||||
|
||||
每个示例都能用 `hmapdev build` 打成 `.hmap` 装进内核直接跑。
|
||||
|
||||
## `a2a`
|
||||
|
||||
用到的 API:`InjectInputSync` · `Lock` · `RegisterInputChannel` · `RegisterOutputChannel` · `RegisterTool` · `SetAutoRestart` · `Settings` · `Unlock`
|
||||
|
||||
## `acp`
|
||||
|
||||
用到的 API:`InjectInputSync` · `Lock` · `RLock` · `RUnlock` · `RegisterInputChannel` · `RegisterOutputChannel` · `RegisterTool` · `SetAutoRestart` · `Settings` · `Unlock`
|
||||
|
||||
## `ai_image`
|
||||
|
||||
用到的 API:`RegisterTool` · `SetAutoRestart` · `Settings`
|
||||
|
||||
## `bili`
|
||||
|
||||
用到的 API:`RegisterTool` · `SetAutoRestart` · `Settings`
|
||||
|
||||
## `browser`
|
||||
|
||||
用到的 API:`InjectInterruptTextOpts` · `InjectTextNoMemory` · `Lock` · `RegisterInputChannel` · `Unlock`
|
||||
|
||||
## `calendar`
|
||||
|
||||
用到的 API:`InjectInterruptTextOpts` · `Lock` · `RLock` · `RUnlock` · `RegisterInputChannel` · `RegisterOnRemoveHandler` · `RegisterStopHandler` · `Unlock`
|
||||
|
||||
## `deepsearch`
|
||||
|
||||
用到的 API:`RegisterStopHandler`
|
||||
|
||||
## `memo`
|
||||
|
||||
用到的 API:`InjectInterruptTextOpts` · `RLock` · `RUnlock` · `RegisterOnRemoveHandler` · `RegisterStage`
|
||||
|
||||
## `qq`
|
||||
|
||||
用到的 API:`InjectInterruptTextOpts` · `RLock` · `RUnlock` · `RegisterOutputChannel` · `RegisterStage`
|
||||
|
||||
## `recoverydiag`
|
||||
|
||||
用到的 API:`Knowledge`
|
||||
|
||||
## `rss`
|
||||
|
||||
用到的 API:`RegisterOnRemoveHandler`
|
||||
|
||||
## `sanitizer`
|
||||
|
||||
用到的 API:`RegisterStage`
|
||||
|
||||
## `weather`
|
||||
|
||||
用到的 API:`RegisterOutputChannel` · `RegisterStage`
|
||||
|
||||
75
docs/guide/capability-boundary.md
Normal file
75
docs/guide/capability-boundary.md
Normal file
@ -0,0 +1,75 @@
|
||||
# 能力边界:哪些 API 外部插件能用
|
||||
|
||||
HomeAgent 有两类插件:
|
||||
|
||||
| 类型 | 说明 | 分发 |
|
||||
|---|---|---|
|
||||
| **外部插件** | 第三方开发,编译成 `.hmap` 后安装 | 独立分发,**可闭源** |
|
||||
| **内置插件** | 编译进内核,`init()` 自注册 | 随内核发行,需合入主仓 |
|
||||
|
||||
SDK 包是**同一个** `gitcode.com/JianFeeeee/homeagent-sdk/sdk`,但两类插件拿到的
|
||||
**能力不同**:外部插件跑在独立进程里,由内核通过桥接注入能力(IPC,不是共享内存里的直接调用)。
|
||||
|
||||
本页说明边界在哪、为什么,以及**怎么在写代码前就知道某个 API 是否可用**。
|
||||
|
||||
## 一句话规则
|
||||
|
||||
> **公开 SDK 包里声明的符号,不等于外部插件拿得到。**
|
||||
|
||||
原因是:有些能力只有进程内的内置插件才可能拥有(比如直接读事件发布通道、
|
||||
直接注入到内核 IO 层)。外部插件通过桥接运行时拿到的是一份**受注入的能力集合**。
|
||||
|
||||
## 外部插件**不可用**的 API
|
||||
|
||||
这些 API 在公开包里存在,但在外部插件路径上拿不到。文档里每条都带
|
||||
<span class="api-badge api-badge-builtin">仅内置</span> 标记,
|
||||
完整清单见 [仅内置插件可用](../api/builtin-only.md)。
|
||||
|
||||
| API | 外部插件的实际情况 | 该用什么 |
|
||||
|---|---|---|
|
||||
| `sdk.PluginSDK.Events()` | **恒为 nil**。桥接运行时不注入 event subscriber(`SetEventSubscriber` 全仓无调用点) | 桥接运行时已按你的声明完成 `events.subscribe`;Lua 插件用 `sdk.events.subscribe` |
|
||||
| `sdk.PluginSDK.SetEventSubscriber` | 无人调用 | 同上 |
|
||||
| `UnregisterOutputChannel` | 桥接只注入 registrar、**不注入 unregistrar**,调用是**静默无效**(返回 nil,不报错也不注销) | `RegisterOutputChannel` 可用;注销需重载插件 |
|
||||
| `SocialAPI` 的写操作 | 公开接口只有 6 个**只读**方法 | 读用 `s.GetPerson` 等;写需内置插件 |
|
||||
| `EventSubscriber.Publish` | 公开接口**刻意只有 Subscribe**,没有 Publish | 只订阅 |
|
||||
| `PriorityL4` | 声明会被内核**夹到 L3** | 用 L1–L3 |
|
||||
| `RegisterChannel` / `ListChannels` / `OutputChan` / `InjectInput` / `InjectInterrupt` | 只存在于内核内部 SDK | `RegisterInputChannel` / `RegisterOutputChannel` / `InjectText` 等公开方法 |
|
||||
| `PluginMgr()` 的完整能力 | 公开 `PluginMgrAPI` **只有 3 个方法**(`ReloadOne` / `ListLoadedPlugins` / `IsPluginDisabled`) | 就这 3 个;`ReloadPlugins`/`Disable`/`Remove` 属内部接口 |
|
||||
|
||||
!!! warning "两处常见的文档错误(本站已更正)"
|
||||
1. **`PluginMgr()` 不是「仅内置可用」**。桥接模板显式注入了它
|
||||
(`base.SetPluginMgrAPI(procPluginMgr{})`),公开 `PluginMgrAPI` 也注明
|
||||
「外部插件可调用」。真正的区别是**方法数量**:公开面 3 个,内部面 9 个。
|
||||
容易混淆是因为两个包里有**同名但不同**的接口:
|
||||
`sdk.PluginMgrAPI`(3 方法)与 `internal/sdk.PluginManager`(9 方法)。
|
||||
2. **`Events()` 恒为 nil 这件事以前没写清**。旧文档把 `Events()` 当作
|
||||
可用的订阅入口,但桥接运行时不注入 subscriber。外部插件的事件订阅
|
||||
实际由生成的运行时通过 `events.subscribe` 完成。
|
||||
|
||||
## 判定依据来自哪里
|
||||
|
||||
本站的「仅内置」标记不是猜的,逐条来自:
|
||||
|
||||
1. **`tools/hmapdev/templates/proc_main.go.tmpl`** —— 外部插件运行时**实际注入**
|
||||
哪些能力,看 `buildPluginSDK()` 里的 `base.Set*` 调用。
|
||||
2. **`internal/sdk`** —— 内置插件用的完整接口,与公开包对照。
|
||||
3. **内核 RPC 协议表**(`internal/plugin/proc/protocol.go`)—— 外部插件**能发哪些请求**。
|
||||
|
||||
每条裁定的具体依据写在该 API 的告警框里,可以直接核对。
|
||||
|
||||
## 怎么快速确认
|
||||
|
||||
- 用 [API 搜索](../api/index.md) 搜 API 名或功能描述,带
|
||||
<span class="api-badge api-badge-builtin">仅内置</span> 的就是外部不可用
|
||||
- 直接看 [仅内置插件可用](../api/builtin-only.md) 汇总页
|
||||
- 拿不准时,**读 `example/` 下的示例插件** —— 它们全是外部插件,
|
||||
能被它们编译通过的写法,外部就一定可用
|
||||
|
||||
## 为什么这样设计
|
||||
|
||||
不是为了限制,而是**IPC 边界决定了能力边界**:外部插件跑在独立进程里,
|
||||
内核只能通过显式的注入点把能力交过去。凡是需要「持有内核内部数据结构」
|
||||
的能力(事件发布通道、IO 通道、插件注册表全量操作),进程外都无法安全暴露。
|
||||
|
||||
这套边界同时带来好处:插件崩溃不会带崩内核(进程隔离),
|
||||
以及**插件可以闭源**(SDK 是 MIT,见[首页](../index.md#_3))。
|
||||
111
docs/guide/first-lua-plugin.md
Normal file
111
docs/guide/first-lua-plugin.md
Normal file
@ -0,0 +1,111 @@
|
||||
# 第一个 Lua 插件
|
||||
|
||||
Lua 插件适合**轻量、快速原型**:不需要 Go 编译环境,改完重启内核即可生效。
|
||||
但它有一个必须理解的限制 —— 执行模型是**被动回调**。
|
||||
|
||||
## 执行模型(先读这段)
|
||||
|
||||
Lua 插件跑在内核进程内的 gopher-lua 解释器里(单 Lua 状态 + 互斥锁):
|
||||
|
||||
- **被动回调**:`main.lua` 只在加载时执行一次。此后工具、阶段钩子、
|
||||
输入输出通道全部由内核事件驱动回调你的 Lua 函数。**插件不能自己启动后台任务。**
|
||||
- **没有并发**:Lua 侧没有 goroutine、协程调度,也没有 `os` / `io` 库和 socket 监听。
|
||||
唯一主动出站通道是 `sdk.http.get/post`(同步请求)。
|
||||
- **任何阻塞循环都会持锁卡死该插件的全部调用。**
|
||||
|
||||
!!! warning "要常驻服务就用 Go 插件"
|
||||
需要监听端口、后台轮询、定时任务的,请用 [Go 插件](first-plugin.md)
|
||||
(可自行启动 goroutine)。Lua 侧的等价做法是**事件驱动**:把逻辑挂在
|
||||
工具、阶段钩子或通道回调上。
|
||||
|
||||
## 生成工程
|
||||
|
||||
```bash
|
||||
hmapdev init myluaplugin --lua
|
||||
cd myluaplugin
|
||||
```
|
||||
|
||||
结构:
|
||||
|
||||
```
|
||||
myluaplugin/
|
||||
├── plg.json — entry: "main.lua", targets: "lua"
|
||||
├── main.lua — 插件实现
|
||||
├── sdk.lua — SDK 模拟层(支持独立测试)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 一个完整的插件
|
||||
|
||||
```lua
|
||||
-- main.lua
|
||||
local plugin = {
|
||||
name = "myluaplugin"
|
||||
}
|
||||
|
||||
function plugin.start(sdk)
|
||||
sdk.log("info", "myluaplugin starting...")
|
||||
|
||||
sdk.register_tool("myluaplugin_hello", {
|
||||
description = "向指定的人打招呼",
|
||||
parameters = {
|
||||
type = "object",
|
||||
properties = {
|
||||
who = { type = "string", description = "要打招呼的对象" }
|
||||
},
|
||||
required = { "who" }
|
||||
}
|
||||
}, function(args)
|
||||
return { content = "hello, " .. (args.who or "world") .. "!" }
|
||||
end)
|
||||
|
||||
sdk.log("info", "myluaplugin started")
|
||||
end
|
||||
|
||||
function plugin.stop()
|
||||
sdk.log("info", "myluaplugin stopped")
|
||||
end
|
||||
|
||||
return plugin
|
||||
```
|
||||
|
||||
## 本地测试
|
||||
|
||||
`sdk.lua` 是纯 Lua 的 SDK 模拟实现,可以直接用解释器跑:
|
||||
|
||||
```bash
|
||||
lua main.lua
|
||||
# [lua-plugin] info: myluaplugin starting...
|
||||
# [lua-plugin] register_tool: myluaplugin_hello
|
||||
# [lua-plugin] info: myluaplugin started
|
||||
```
|
||||
|
||||
在内核里运行时,`sdk.*` 由 Go 层注入,`sdk.lua` 里所有 `-- !impl` 标记的函数
|
||||
会被替换成真实实现。
|
||||
|
||||
## API 约定的两点
|
||||
|
||||
- **注册类函数调用即时报错**(抛 Lua error)—— 注册失败不会静默。
|
||||
- **数据类函数统一返回 `(result, err)`**,`err` 为 nil 表示成功。
|
||||
核心未装配的子系统(如 SocialAPI)返回空值而非报错。
|
||||
|
||||
Lua 侧的 `sdk.*` 能力与外部 Go 插件对齐至 SDK 1.3.0(需内核 1.4.0+)。
|
||||
|
||||
!!! note "历史提醒"
|
||||
1.1–1.3 期间,媒体 / 注入标志位 / 优先级能力只在 Go 侧有,Lua 侧静默缺失。
|
||||
现已全量对齐,并由 `internal/plugin/lua_surface_test.go` 的契约测试守住
|
||||
「`sdk.lua` 承诺的每个函数都有运行时绑定」。
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
hmapdev build # → dist/myluaplugin_lua.hmap
|
||||
```
|
||||
|
||||
Lua 插件直接打包源码,不经过编译。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [能力边界](capability-boundary.md) —— Lua 与 Go 外部插件的能力面一致
|
||||
- [打包与发布](packaging.md)
|
||||
- [示例](../examples/index.md) —— `example/luademo` 是 Lua 版参考实现
|
||||
162
docs/guide/first-plugin.md
Normal file
162
docs/guide/first-plugin.md
Normal file
@ -0,0 +1,162 @@
|
||||
# 第一个 Go 插件
|
||||
|
||||
以下是一个**能直接跑起来**的最小插件:注册一个工具、声明一项配置、处理停止与卸载。
|
||||
|
||||
## 1. 生成工程
|
||||
|
||||
```bash
|
||||
hmapdev init myplugin
|
||||
cd myplugin
|
||||
```
|
||||
|
||||
生成的结构:
|
||||
|
||||
```
|
||||
myplugin/
|
||||
├── plg.json — 插件元信息(名称、版本、入口、目标平台)
|
||||
├── plugin.go — 插件实现
|
||||
├── go.mod — 模块定义
|
||||
├── README.md
|
||||
└── thirdpart/ — 外部源码存放目录(可选)
|
||||
```
|
||||
|
||||
`hmapdev build` 时会在构建目录自动生成子进程运行时(`z_proc_gen.go` 等),
|
||||
**不需要手工创建,也不要提交**。
|
||||
|
||||
## 2. 插件实现
|
||||
|
||||
插件的全部契约是一个 `Plugin` 接口([API 参考](../api/lifecycle.md#plugin)):
|
||||
|
||||
| 方法 | 何时调用 |
|
||||
|---|---|
|
||||
| `Name() string` | 内核需要标识这个插件时 |
|
||||
| `Start(*sdk.PluginSDK) error` | 插件加载后。**在这里注册工具、通道、配置** |
|
||||
| `Stop() error` | 插件停止时(重载、禁用、内核退出都会触发) |
|
||||
|
||||
再加一个工厂函数。**名字必须是 `NewPluginFactory`** —— 生成的运行时按这个名字调用:
|
||||
|
||||
```go
|
||||
func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||
return &Plugin{name: name}, nil
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning "不要写成 `NewPlugin`"
|
||||
生成的子进程运行时调用的入口是 `NewPluginFactory`。仓库里有 3 个早期示例
|
||||
同时保留了两个名字(`NewPlugin` 只是遗留别名),但新插件只写
|
||||
`NewPluginFactory` 即可。写错名字的后果是**编译能过、加载时找不到入口**。
|
||||
|
||||
## 3. 一个完整的例子
|
||||
|
||||
这是一个「打招呼」工具,带一项配置:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitcode.com/JianFeeeee/homeagent-sdk/sdk"
|
||||
)
|
||||
|
||||
type Plugin struct {
|
||||
name string
|
||||
sdk *sdk.PluginSDK
|
||||
}
|
||||
|
||||
func (p *Plugin) Name() string { return p.name }
|
||||
|
||||
func (p *Plugin) Start(s *sdk.PluginSDK) error {
|
||||
p.sdk = s
|
||||
|
||||
// ① 声明配置项:内核会把它渲染到 WebUI 设置页
|
||||
s.Settings().RegisterDef(sdk.ConfigDef{
|
||||
Key: "plugin.myplugin.greeting",
|
||||
Default: "hello",
|
||||
Type: "string",
|
||||
DisplayName: "问候语",
|
||||
Description: "打招呼时使用的前缀",
|
||||
Category: "myplugin",
|
||||
})
|
||||
|
||||
// ② 注册工具:模型看到 Description 后决定是否调用
|
||||
tp := p.name + "_"
|
||||
s.RegisterTool(tp+"hello", sdk.ToolDef{
|
||||
Name: tp + "hello",
|
||||
Description: "向指定的人打招呼",
|
||||
Parameters: map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"who": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "要打招呼的对象",
|
||||
},
|
||||
},
|
||||
"required": []string{"who"},
|
||||
},
|
||||
}, p.handleHello)
|
||||
|
||||
// ③ 卸载(插件被删除)前清理自己产生的数据。
|
||||
// 注意与 Stop 的区别:Stop 在每次重载时也会触发。
|
||||
s.RegisterOnRemoveHandler(func() {
|
||||
fmt.Printf("[%s] 清理数据\n", p.name)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Plugin) Stop() error { return nil }
|
||||
|
||||
func (p *Plugin) handleHello(args map[string]interface{}) (interface{}, error) {
|
||||
who, _ := args["who"].(string)
|
||||
|
||||
greeting := "hello"
|
||||
if v, err := p.sdk.Settings().Get("plugin.myplugin.greeting"); err == nil && v != "" {
|
||||
greeting = v
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"content": fmt.Sprintf("%s, %s!", greeting, who),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewPluginFactory(name string, config map[string]interface{}) (sdk.Plugin, error) {
|
||||
return &Plugin{name: name}, nil
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 工具返回值的两条约定
|
||||
|
||||
`ToolHandler` 返回 `(interface{}, error)`,模型侧看到的是一条 tool message:
|
||||
|
||||
- **正常结果**:返回一个 map,把要展示给模型的文本放在 `content` 字段。
|
||||
未识别的字段也会一并传给模型,可以放结构化数据。
|
||||
- **业务失败**:返回 `map[string]interface{}{"isError": true, "content": "原因"}`
|
||||
**并返回 nil error**。这样模型能看到失败原因并自行调整;
|
||||
若返回 Go 的 `error`,那是**工具调用本身出错**,语义不同。
|
||||
|
||||
```go
|
||||
func errorResult(msg string) map[string]interface{} {
|
||||
return map[string]interface{}{"isError": true, "content": msg}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 构建与安装
|
||||
|
||||
```bash
|
||||
hmapdev build # 默认产出多平台 bundle
|
||||
# → dist/myplugin_bundle.hmap
|
||||
|
||||
hmapdev build --no-bundle # 只构建当前平台
|
||||
# → dist/myplugin_linux_amd64.hmap
|
||||
```
|
||||
|
||||
安装到内核:在 WebUI 的插件管理页上传 `.hmap`,或从 URL / 本地路径安装。
|
||||
详见 [打包与发布](packaging.md)。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [能力边界](capability-boundary.md) —— 哪些 API 外部插件能用
|
||||
- [工具(Tools)](../api/tools.md) —— `ToolDef` 的完整字段
|
||||
- [记忆(Memory)](../api/memory.md) —— 让插件读写长期记忆
|
||||
- [示例插件](../examples/index.md) —— `example/memo` 是个完整的可读实现
|
||||
62
docs/guide/getting-started.md
Normal file
62
docs/guide/getting-started.md
Normal file
@ -0,0 +1,62 @@
|
||||
# 环境与工具链
|
||||
|
||||
`hmapdev` 是 SDK 仓提供的统一插件开发工具链,Go 与 Lua 两种插件都用它,
|
||||
最终产出 `.hmap` 插件包(工具名即取自这个包格式)。
|
||||
|
||||
!!! note "改名说明"
|
||||
1.2.0 起工具链由 `plugindev` 更名为 `hmapdev`;SDK 存储目录同时由
|
||||
`~/.homeagent/plugindev/sdk` 迁到 `~/.homeagent/hmapdev/sdk`
|
||||
(旧目录会自动继续沿用)。
|
||||
|
||||
## 安装
|
||||
|
||||
从源码构建:
|
||||
|
||||
```bash
|
||||
git clone https://gitcode.com/JianFeeeee/homeagent-sdk
|
||||
cd homeagent-sdk/tools/hmapdev
|
||||
go build -o hmapdev
|
||||
# 把 hmapdev 放进 PATH,或直接用 ./hmapdev
|
||||
```
|
||||
|
||||
也可以从 SDK 的 release 附件下载预编译二进制(`hmapdev_linux_amd64` 等)。
|
||||
|
||||
## SDK 版本管理
|
||||
|
||||
`hmapdev` 会维护一份本地 SDK 存储,`init` 时按 `plg.json` 里的 `sdk` 字段
|
||||
选择版本。两者**必须**一致,否则编译出的插件与内核协议可能错配。
|
||||
|
||||
```bash
|
||||
hmapdev sdk list # 已安装的 SDK 版本
|
||||
hmapdev sdk current # 当前使用的版本
|
||||
hmapdev sdk latest # 最新可用版本
|
||||
hmapdev sdk install v1.2.0 # 安装指定版本
|
||||
hmapdev sdk use v1.2.0 # 切换版本
|
||||
hmapdev sdk path # 当前 SDK 路径
|
||||
```
|
||||
|
||||
存储在 `~/.homeagent/hmapdev/sdk/<version>/`。
|
||||
|
||||
!!! warning "版本未命中会**明确报错**"
|
||||
`plg.json` 声明的 `sdk` 版本若不在本地存储里,`hmapdev` 不会退回某个默认版本,
|
||||
而是报错并让你先 `hmapdev sdk install`。这是有意的:静默降级会产出与内核
|
||||
协议不匹配的插件,那种失败要到运行时才暴露。
|
||||
|
||||
## 源码调试
|
||||
|
||||
不编译直接跑插件源码,输出调用轨迹:
|
||||
|
||||
```bash
|
||||
hmapdev debug [dir] # dir 默认当前目录
|
||||
```
|
||||
|
||||
写 Lua 插件时更简单——`sdk.lua` 是 SDK 模拟层,可以直接用解释器跑:
|
||||
|
||||
```bash
|
||||
lua main.lua
|
||||
```
|
||||
|
||||
## 下一步
|
||||
|
||||
- [第一个 Go 插件](first-plugin.md)
|
||||
- [第一个 Lua 插件](first-lua-plugin.md)
|
||||
56
docs/guide/multi-platform.md
Normal file
56
docs/guide/multi-platform.md
Normal file
@ -0,0 +1,56 @@
|
||||
# 多平台构建
|
||||
|
||||
## 默认就是多平台
|
||||
|
||||
`hmapdev build` 默认 bundle 模式,一次产出含三个平台的单个 `.hmap`:
|
||||
|
||||
```
|
||||
dist/myplugin_bundle.hmap
|
||||
└── plugin.bin.linux.amd64
|
||||
└── plugin.bin.darwin.amd64
|
||||
└── plugin.bin.windows.amd64
|
||||
```
|
||||
|
||||
安装时内核挑当前平台那份,重命名为 `plugin.bin`。
|
||||
|
||||
## 逐平台构建
|
||||
|
||||
```bash
|
||||
hmapdev build --no-bundle # 按 plg.json 的 targets 构建
|
||||
hmapdev build --target linux/arm64 # 追加一个目标
|
||||
```
|
||||
|
||||
`plg.json` 里声明目标:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "myplugin",
|
||||
"version": "1.0.0",
|
||||
"targets": "linux/amd64,windows/amd64"
|
||||
}
|
||||
```
|
||||
|
||||
单平台输出文件名:`{name}_{os}_{arch}.hmap`。
|
||||
|
||||
## 交叉编译
|
||||
|
||||
子进程插件**不再需要 cgo**,所以交叉编译不需要目标平台的 C 工具链 ——
|
||||
这是 v1.0.0 的收益之一。
|
||||
|
||||
!!! note "bundle 模式忽略 `targets`"
|
||||
固定构建 linux/amd64、darwin/amd64、windows/amd64。如果你只需要其中一个,
|
||||
用 `--no-bundle` 更快。
|
||||
|
||||
## 平台能力差异
|
||||
|
||||
历史上有过一处真实的平台断层,现已消除:
|
||||
|
||||
- **v1.0.0 之前**:Windows 上插件只看到 **3 个 stage 字段、且无法写回**。
|
||||
- **v1.0.0 起**:Windows 与其他平台**共用同一套 RPC 实现**,16 字段全可见 + 写回。
|
||||
|
||||
因此**不必**为 Windows 写条件分支 —— 除非你的插件自己用了平台专有的外部命令。
|
||||
|
||||
## 下一步
|
||||
|
||||
- [打包与发布](packaging.md)
|
||||
- [环境与工具链](getting-started.md)
|
||||
114
docs/guide/packaging.md
Normal file
114
docs/guide/packaging.md
Normal file
@ -0,0 +1,114 @@
|
||||
# 打包与发布
|
||||
|
||||
`hmapdev build` 一次完成编译与打包,产出 `.hmap` 分发包(zip 格式,内含
|
||||
`plugin.json` 清单 + 二进制)。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
hmapdev build # 默认 bundle(多平台合集)
|
||||
hmapdev build --no-bundle # 只构建 plg.json targets 里的平台
|
||||
hmapdev build --target linux/arm64 # 在 targets 基础上追加目标
|
||||
hmapdev build --outdir out # 指定输出目录(默认 dist)
|
||||
hmapdev build --sdk-path <path> # 覆盖 go.mod 的 replace 指向的 SDK
|
||||
hmapdev build --replace <mod@path> # 追加 go.mod replace(可多次)
|
||||
```
|
||||
|
||||
执行流程:
|
||||
|
||||
1. 读 `plg.json` 的 `targets` / `bundle` 决定构建目标
|
||||
2. 生成子进程运行时代码(`z_proc_gen.go`、`z_proc_shm_*.go`)
|
||||
3. **Go 插件**:`go build`(普通可执行文件,`CGO_ENABLED=0`)
|
||||
**Lua 插件**:直接打包源码,不编译
|
||||
4. 生成 `plugin.json` 输出清单
|
||||
5. 打成 `.hmap`
|
||||
|
||||
## 两个 JSON 的区别
|
||||
|
||||
这一点经常混淆:
|
||||
|
||||
| 文件 | 谁维护 | 作用 | 关键字段 |
|
||||
|---|---|---|---|
|
||||
| `plg.json` | **你** | 项目元信息,构建输入 | `targets`、`bundle` |
|
||||
| `plugin.json` | `hmapdev` 自动生成 | 构建产物清单 | `entry`、`platforms` |
|
||||
|
||||
`plg.json` 里的 `sdk` 字段声明**本插件针对的 SDK 版本**;未命中本地 SDK 存储
|
||||
会明确报错(见[环境与工具链](getting-started.md))。
|
||||
|
||||
## 多平台(bundle)
|
||||
|
||||
`build` 默认就是 bundle 模式:一次编译 linux/amd64、darwin/amd64、windows/amd64,
|
||||
产出一个含全部平台二进制的 `.hmap`;安装时内核挑当前平台那份。
|
||||
|
||||
```bash
|
||||
hmapdev build # → dist/myplugin_bundle.hmap
|
||||
hmapdev build --no-bundle # → dist/myplugin_linux_amd64.hmap 等
|
||||
```
|
||||
|
||||
!!! note "bundle 模式会忽略 `plg.json` 的 `targets`"
|
||||
固定构建上述三个平台。交叉编译需要对应工具链(如 Linux 上构建 darwin 需要
|
||||
clang / macOS SDK),缺工具链时会失败 —— 此时用 `--no-bundle` 只构建当前平台。
|
||||
|
||||
bundle 包内按 `plugin.bin.<goos>.<goarch>` 区分,安装时重命名为 `plugin.bin`。
|
||||
|
||||
## 产物形态
|
||||
|
||||
子进程插件是**普通可执行文件**,不分平台后缀:
|
||||
|
||||
| 平台 | 二进制 |
|
||||
|---|---|
|
||||
| Linux / macOS / Windows | `plugin.bin` |
|
||||
|
||||
!!! warning "v1.0.0 破坏性变更:不再加载 `.so` / `.dll`"
|
||||
外部插件从 C ABI 动态库改为**子进程 + 共享内存**。
|
||||
|
||||
- `plugin.so` / `plugin.dylib` / `plugin.dll` **不再被加载**。
|
||||
新内核遇到旧产物会跳过并报可操作错误,不崩溃。
|
||||
- **业务代码不用改一行** —— 公开 SDK 接口零改动,用新版 `hmapdev`
|
||||
(原 `plugindev`)重编即可。
|
||||
- `plg.json` 的 `entry` 字段对 Go 插件**已无意义**(写 `plugin.so` 也无妨),
|
||||
现在只用于区分 Lua 插件。
|
||||
- 产物不再需要 cgo,交叉编译无需目标平台 C 工具链。
|
||||
|
||||
## 安装
|
||||
|
||||
三种方式(`9876` 是 pluginmgr 的本地端口,默认只监听 `127.0.0.1`、无鉴权):
|
||||
|
||||
```bash
|
||||
# 从 URL 安装(仅 http/https,流式下载不落盘)
|
||||
curl -X POST http://127.0.0.1:9876/plugins \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"url": "https://example.com/myplugin.hmap"}'
|
||||
|
||||
# 从本地路径安装(读取文件,不移动原文件)
|
||||
curl -X POST http://127.0.0.1:9876/plugins \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"path": "/path/to/myplugin.hmap"}'
|
||||
|
||||
# 直接上传二进制
|
||||
curl -X POST http://127.0.0.1:9876/plugins \
|
||||
--data-binary @dist/myplugin_bundle.hmap
|
||||
```
|
||||
|
||||
安装后调用 `/api/v1/plugins/reload` 或重启内核生效。
|
||||
|
||||
走 WebUI 的 HTTP API(默认 `8080`,需 `api_key` 鉴权,内部代理到 pluginmgr):
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8080/api/v1/plugins \
|
||||
-H "Authorization: Bearer <api_key>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"path": "/path/to/myplugin.hmap"}'
|
||||
```
|
||||
|
||||
也可以在 WebUI 的插件管理页面上传。
|
||||
|
||||
## 发布前自查
|
||||
|
||||
- [ ] `plg.json` 的 `sdk` 版本与目标内核匹配
|
||||
- [ ] `version` 已递增(内核按版本判断是否需要重装)
|
||||
- [ ] 若插件有外部状态,`SetAutoRestart(false)` 或在 `Start` 里重建连接
|
||||
(崩溃重启是**线性退避** 1s→2s→3s,5 分钟内第 4 次崩溃即停止,
|
||||
见[生命周期](../api/lifecycle.md#pluginsdksetautorestart))
|
||||
- [ ] `RegisterOnRemoveHandler` 里清理自己写下的数据文件
|
||||
- [ ] 在 `-race` 下跑一遍:插件的 `Start` 与工具的并发访问是最常见的竞态来源
|
||||
81
docs/guide/security.md
Normal file
81
docs/guide/security.md
Normal file
@ -0,0 +1,81 @@
|
||||
# 受限 SDK 与安全
|
||||
|
||||
外部插件与内置插件的区别不只是「能不能调某个函数」,还包含一层**安全边界**:
|
||||
外部插件的进程不共享内核地址空间,能力通过显式注入点交过去。
|
||||
|
||||
## 三层隔离
|
||||
|
||||
| 层 | 机制 | 防住了什么 |
|
||||
|---|---|---|
|
||||
| **进程** | 插件跑在独立子进程 | 插件 panic / 内存越界**不会带崩内核** |
|
||||
| **能力** | 只注入显式声明的接口 | 插件拿不到未授权的内核内部结构 |
|
||||
| **权限** | 公开接口是内部接口的**只读子集** | 插件无法改写他人数据 |
|
||||
|
||||
第一种是 v1.0.0 从 C ABI 动态库改为子进程 + 共享内存的直接收益:
|
||||
在此之前,插件 panic 会带崩 `homed`。
|
||||
|
||||
## 受限接口是怎么实现的
|
||||
|
||||
**按接口裁剪,而不是按方法裁剪。** 同一个概念在公开包与内部包里是**两个不同的
|
||||
接口声明**,公开的那个只保留安全子集:
|
||||
|
||||
```go
|
||||
// 公开 SDK:6 个只读方法
|
||||
type SocialAPI interface {
|
||||
GetPerson(name string) (*PersonProfile, error)
|
||||
GetTrait(name, trait string) (string, bool)
|
||||
GetRelations(name string) ([]SocialRelation, error)
|
||||
GetNetwork(name string, depth int) ([]*PersonProfile, error)
|
||||
ListPersons() ([]string, error)
|
||||
}
|
||||
```
|
||||
|
||||
写操作只在内核内部接口里。这样外部插件**在类型层面就调不到**,
|
||||
不是靠运行时检查拦截。
|
||||
|
||||
同理,`EventSubscriber` 公开版**刻意只有 `Subscribe`,没有 `Publish`**:
|
||||
|
||||
```go
|
||||
// 插件可以订阅,但由内核决定投递哪些事件
|
||||
type EventSubscriber interface {
|
||||
Subscribe(eventType EventType, handler EventHandler) func()
|
||||
}
|
||||
```
|
||||
|
||||
## 进程边界带来的约束
|
||||
|
||||
事件订阅是理解这层边界的典型例子。公开包里有一个 `Events() EventSubscriber`,
|
||||
但**外部插件拿到的恒为 nil** —— 桥接运行时不注入它(`SetEventSubscriber`
|
||||
在全仓没有调用点)。外部插件的事件订阅由生成的运行时通过 `events.subscribe`
|
||||
RPC 完成,Lua 插件走内部 SDK 的 `Subscribe`。
|
||||
|
||||
这不是缺陷,而是进程边界的结果:跨进程无法共享内核的事件发布通道。
|
||||
详见[能力边界](capability-boundary.md)。
|
||||
|
||||
## 共享内存中的数据面
|
||||
|
||||
工具调用帧、Cleaner、输入输出通道、媒体块、文档与知识正文**都走共享内存**,
|
||||
RPC 只传偏移描述符。因此:
|
||||
|
||||
- 大对象不经 JSON 序列化,避免了大 payload 的性能与内存放大;
|
||||
- StageContext 在同一份状态上读改写,消除了副本模型的 lost update
|
||||
(实测由 35.8~36.8% 降到 0)。
|
||||
|
||||
`SharedRef`(共享内存描述符)是**内部实现细节**,插件开发者看不到它 ——
|
||||
公开 SDK 只暴露普通字符串与 map。
|
||||
|
||||
## 插件作者的实践建议
|
||||
|
||||
- **不要在 `Start` 里长时间阻塞** —— 内核在等待它返回。
|
||||
- **工具处理器要可并发**:模型可能并发发起多个调用;共享状态用锁保护
|
||||
(`example/memo` 用 `sync.RWMutex`)。
|
||||
- **写文件用原子替换**(临时文件 + rename),避免进程被强杀时截断数据。
|
||||
- **声明 `NoMemory`**:定时提醒、连接状态这类不是对话内容的东西,
|
||||
别让它们污染记忆(`InjectOptions{NoMemory: true}`)。
|
||||
- **在 `-race` 下测**:插件重载瞬间的并发访问是历史高发缺陷。
|
||||
|
||||
## 许可与分发
|
||||
|
||||
SDK 是 **MIT**,插件可以**闭源分发**,可商用、可私有,无需回馈。
|
||||
这是刻意的:SDK 随插件静态链接(源码进入插件二进制),用传染性许可会
|
||||
强迫插件开源。内核本身是 AGPL-3.0-only,但那是内核的许可,与外部插件无关。
|
||||
49
docs/index.md
Normal file
49
docs/index.md
Normal file
@ -0,0 +1,49 @@
|
||||
# HomeAgent 插件 SDK
|
||||
|
||||
用 **Go** 或 **Lua** 为 HomeAgent 编写插件。插件跑在独立进程里,通过公开 SDK 与内核通信:
|
||||
注册工具供模型调用、挂阶段钩子干预流程、读写三层记忆、注册输入输出通道、订阅事件。
|
||||
|
||||
<div id="api-search"></div>
|
||||
|
||||
## 从这里开始
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-rocket-launch: **第一次写插件**
|
||||
|
||||
装工具链、生成工程、写一个工具、打包成 `.hmap` 装进内核跑起来。
|
||||
|
||||
[:octicons-arrow-right-24: 快速开始](guide/getting-started.md)
|
||||
|
||||
- :material-book-open-variant: **API 参考**
|
||||
|
||||
逐个符号的签名与说明,直接取自源码注释。附示例插件里的真实调用点。
|
||||
|
||||
[:octicons-arrow-right-24: 工具(Tools)](api/tools.md)
|
||||
|
||||
- :material-shield-lock: **能力边界**
|
||||
|
||||
哪些 API 外部插件能用、哪些仅内置插件可用,以及为什么。**先看这个能省很多时间。**
|
||||
|
||||
[:octicons-arrow-right-24: 能力边界](guide/capability-boundary.md)
|
||||
|
||||
- :material-code-braces: **示例插件**
|
||||
|
||||
`example/` 下有多个真实可编译的插件,覆盖常见形态。
|
||||
|
||||
[:octicons-arrow-right-24: 示例总览](examples/index.md)
|
||||
|
||||
</div>
|
||||
|
||||
## 许可
|
||||
|
||||
SDK 以 **MIT** 发布 —— 插件作者可**自由选择自己的许可**(闭源、商业、私有均可),
|
||||
不必同许可、也不必回馈。原因:SDK 会随插件一起静态链接(源码进入插件二进制),
|
||||
若用传染性许可,插件作者就被强制开源;MIT 让第三方插件生态不必承担这个代价。
|
||||
|
||||
内核本身是 **AGPL-3.0-only**,但那是内核的许可,与外部插件无关 ——
|
||||
SDK 完全自包含(`go.mod` 零外部依赖,只依赖 Go 标准库),不引用内核任何代码。
|
||||
|
||||
## 版本
|
||||
|
||||
本文档站的 API 参考从源码生成,对应 SDK 版本见 [版本与兼容](versions.md)。
|
||||
245
docs/javascripts/api-search.js
Normal file
245
docs/javascripts/api-search.js
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* API 即时检索。
|
||||
*
|
||||
* 为什么要自建:Material 内置搜索按「整页文本」建索引,搜 `InjectText`
|
||||
* 会把所有提到它的页面都列出来,但**分不清哪一条是它的定义**;而且内置
|
||||
* 索引要等 mkdocs build 才生成,改一行 API 也得重建。
|
||||
*
|
||||
* 这里读的是 `assets/api-index.json`——由 tools/apidoc/gensite 直接产出,
|
||||
* 每条记录带 名称/签名/描述/类别/所属页面/是否仅内置/源文件:行号。
|
||||
* 因此可以做到:
|
||||
* - 按名称搜(精确/前缀优先)
|
||||
* - 按描述搜(中文按字、英文按词,都对 API 的文档注释做匹配)
|
||||
* - 按签名搜(如 "(string) error")
|
||||
* - 过滤「仅内置」——外部插件作者最容易被这个绊住
|
||||
*
|
||||
* 设计取舍:纯前端、零依赖、不阻塞页面。索引 ~130 条、约 40KB,一次拉取足够。
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var INDEX_URL = (function () {
|
||||
// 文档站可能部署在子路径下,按当前页面深度回推到站点根。
|
||||
var path = window.location.pathname;
|
||||
var marker = "/api/";
|
||||
var i = path.indexOf(marker);
|
||||
if (i >= 0) return path.slice(0, i) + "/assets/api-index.json";
|
||||
// guide/ 等目录同样回退一层。
|
||||
var lastSlash = path.lastIndexOf("/");
|
||||
return path.slice(0, lastSlash) + "/assets/api-index.json";
|
||||
})();
|
||||
|
||||
var state = { all: [], loaded: false, loading: false };
|
||||
|
||||
function load() {
|
||||
if (state.loaded || state.loading) return Promise.resolve(state.all);
|
||||
state.loading = true;
|
||||
return fetch(INDEX_URL)
|
||||
.then(function (r) {
|
||||
if (!r.ok) throw new Error("HTTP " + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
state.all = data || [];
|
||||
state.loaded = true;
|
||||
return state.all;
|
||||
})
|
||||
.catch(function () {
|
||||
state.all = [];
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- 打分 ---------- */
|
||||
//
|
||||
// 三级优先级:名称命中 > 描述命中 > 签名命中。
|
||||
// 名称命中里再分「完全相等 / 前缀 / 子串」,因为用户敲 `InjectText` 时
|
||||
// 想要的是那个符号,不是所有名字里含它的。
|
||||
|
||||
function score(item, q) {
|
||||
var name = (item.n || "").toLowerCase();
|
||||
var ql = q.toLowerCase();
|
||||
var s = 0;
|
||||
|
||||
if (name === ql) s += 1000;
|
||||
else if (name.indexOf(ql) === 0) s += 600;
|
||||
else if (name.indexOf(ql) > 0) s += 350;
|
||||
|
||||
// 限定符:PluginSDK.RegisterTool / IOInjector.InjectText。
|
||||
// 额外支持「去掉 API/SDK 后缀」与「去掉点号」两种写法,
|
||||
// 因为读者习惯写 `memory.recall`(RPC 名),而 Go 名是 `MemoryAPI.Recall`。
|
||||
var qual = ((item.r || "") + "." + name).toLowerCase();
|
||||
if (item.r && qual.indexOf(ql) >= 0) s += 200;
|
||||
if (item.r) {
|
||||
var flat = qual.replace(/[._]/g, "").replace(/apis?dk|sdk|api/g, "");
|
||||
var qflat = ql.replace(/[._\s]/g, "");
|
||||
if (qflat && flat.indexOf(qflat) >= 0) s += 180;
|
||||
}
|
||||
|
||||
var desc = (item.d || "").toLowerCase();
|
||||
if (desc.indexOf(ql) >= 0) s += 120;
|
||||
|
||||
// 签名按 token 匹配:把查询拆词(去掉括号/逗号等标点),全部命中才算。
|
||||
// 这样 `(string) error`、`ContentBlock 媒体` 这类片段都能搜到。
|
||||
// 注意必须先去标点:否则 token `(string)` 永远匹配不到签名里的 `string`。
|
||||
var sig = (item.s || "").toLowerCase();
|
||||
if (sig.indexOf(ql) >= 0) s += 60;
|
||||
var toks = ql
|
||||
.replace(/[()\[\]{},;:]/g, " ")
|
||||
.split(/\s+/)
|
||||
.filter(function (t) { return t.length > 1; });
|
||||
if (toks.length && sig.length) {
|
||||
var allSig = toks.every(function (t) { return sig.indexOf(t) >= 0; });
|
||||
if (allSig) s += 55;
|
||||
}
|
||||
|
||||
// 中文按字匹配:中文没有词边界,逐字命中比整串更实用。
|
||||
if (/[\u4e00-\u9fa5]/.test(q)) {
|
||||
var hit = 0;
|
||||
for (var i = 0; i < q.length; i++) {
|
||||
if (desc.indexOf(q[i]) >= 0) hit++;
|
||||
}
|
||||
if (hit === q.length) s += 100; // 全部字都出现
|
||||
else s += hit * 8;
|
||||
}
|
||||
|
||||
// 公开 API 略优先于「仅内置」——后者通常是噪声。
|
||||
if (s > 0 && !item.b) s += 15;
|
||||
return s;
|
||||
}
|
||||
|
||||
function search(q) {
|
||||
var qq = (q || "").trim();
|
||||
if (!qq) return [];
|
||||
var out = [];
|
||||
for (var i = 0; i < state.all.length; i++) {
|
||||
var sc = score(state.all[i], qq);
|
||||
if (sc > 0) out.push({ item: state.all[i], score: sc });
|
||||
}
|
||||
out.sort(function (a, b) {
|
||||
if (b.score !== a.score) return b.score - a.score;
|
||||
return (a.item.n || "").length - (b.item.n || "").length;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
/* ---------- 渲染 ---------- */
|
||||
//
|
||||
// 挂在 Material 首页/目录页的一个容器上:#api-search。
|
||||
// 没找到容器就不做任何事——这样同一份 JS 可以安全地全站引入。
|
||||
|
||||
function el(tag, cls, text) {
|
||||
var e = document.createElement(tag);
|
||||
if (cls) e.className = cls;
|
||||
if (text != null) e.textContent = text;
|
||||
return e;
|
||||
}
|
||||
|
||||
function render(mount, q) {
|
||||
mount.innerHTML = "";
|
||||
if (!q.trim()) {
|
||||
mount.appendChild(el("p", "api-hint",
|
||||
"输入 API 名称、描述或签名片段。例:InjectText、注册工具、崩溃、memory.recall、ContentBlock"));
|
||||
return;
|
||||
}
|
||||
var results = search(q);
|
||||
if (!results.length) {
|
||||
mount.appendChild(el("p", "api-hint", "没有匹配的 API。试试更短的词,或按功能描述搜(如「注入」「重载」)。"));
|
||||
return;
|
||||
}
|
||||
var head = el("p", "api-count", "命中 " + results.length + " 个 API");
|
||||
mount.appendChild(head);
|
||||
|
||||
var list = el("ul", "api-results");
|
||||
results.slice(0, 40).forEach(function (r) {
|
||||
var it = r.item;
|
||||
var li = el("li", "api-result");
|
||||
|
||||
var title = el("a", "api-name", (it.r ? it.r + "." : "") + it.n);
|
||||
// 锚点必须用**完整标题文本**(`PluginSDK.InjectText`,点号被 slug 丢掉),
|
||||
// 不是裸方法名 —— 否则跳到页面顶部而到不了那一条。
|
||||
title.href = pageURL(it.p) + "#" + anchorOf((it.r ? it.r + "." : "") + it.n);
|
||||
li.appendChild(title);
|
||||
|
||||
if (it.b) {
|
||||
var badge = el("span", "api-badge api-badge-builtin", "仅内置");
|
||||
badge.title = "外部(第三方)插件运行时拿不到这个 API";
|
||||
li.appendChild(badge);
|
||||
}
|
||||
|
||||
li.appendChild(el("code", "api-sig", it.s || ""));
|
||||
|
||||
if (it.d) {
|
||||
var d = el("span", "api-desc", it.d);
|
||||
li.appendChild(d);
|
||||
}
|
||||
if (it.f) {
|
||||
li.appendChild(el("span", "api-loc", it.f + (it.l ? ":" + it.l : "")));
|
||||
}
|
||||
list.appendChild(li);
|
||||
});
|
||||
mount.appendChild(list);
|
||||
}
|
||||
|
||||
function pageURL(page) {
|
||||
if (!page) return "#";
|
||||
// 所有 API 章节都在 /api/ 下(生成物),示例页在 /examples/。
|
||||
// 从当前 URL 回推到站点根,保证部署在子路径下也能用。
|
||||
var path = window.location.pathname;
|
||||
var i = path.indexOf("/api/");
|
||||
var root;
|
||||
if (i >= 0) {
|
||||
root = path.slice(0, i + 1);
|
||||
} else {
|
||||
var j = path.indexOf("/guide/");
|
||||
if (j >= 0) root = path.slice(0, j + 1);
|
||||
else if (path.indexOf("/examples/") >= 0) root = path.slice(0, path.indexOf("/examples/") + 1);
|
||||
else root = path.slice(0, path.lastIndexOf("/") + 1);
|
||||
}
|
||||
var dir = page === "examples" ? "examples" : "api";
|
||||
return root + dir + "/" + page + "/";
|
||||
}
|
||||
|
||||
// anchorOf 复现 MkDocs 的 slug:小写、去掉非 [a-z0-9_-] 的字符(点号被去掉)、
|
||||
// 下划线保留、空格转连字符。
|
||||
function anchorOf(name) {
|
||||
return String(name)
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9_ -]/g, "")
|
||||
.replace(/\s+/g, "-");
|
||||
}
|
||||
|
||||
function mount() {
|
||||
var box = document.getElementById("api-search");
|
||||
if (!box) return;
|
||||
|
||||
var input = el("input", "api-input");
|
||||
input.type = "search";
|
||||
input.placeholder = "搜索 API:名称、描述、签名…";
|
||||
input.setAttribute("autocomplete", "off");
|
||||
input.setAttribute("spellcheck", "false");
|
||||
|
||||
var out = el("div", "api-output");
|
||||
box.appendChild(input);
|
||||
box.appendChild(out);
|
||||
|
||||
load().then(function () {
|
||||
render(out, "");
|
||||
input.addEventListener("input", function () {
|
||||
render(out, input.value);
|
||||
});
|
||||
});
|
||||
|
||||
// 支持 ?q= 直达(可从别处链接到一次检索)。
|
||||
var m = /[?&]q=([^&]+)/.exec(window.location.search);
|
||||
if (m) {
|
||||
input.value = decodeURIComponent(m[1].replace(/\+/g, " "));
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", mount);
|
||||
} else {
|
||||
mount();
|
||||
}
|
||||
})();
|
||||
115
docs/stylesheets/extra.css
Normal file
115
docs/stylesheets/extra.css
Normal file
@ -0,0 +1,115 @@
|
||||
/* API 即时检索与文档站的少量本地样式。
|
||||
只补 Material 没覆盖的部分,不覆盖主题变量(保持深浅色自动适配)。 */
|
||||
|
||||
#api-search {
|
||||
margin: 1.2rem 0 2rem;
|
||||
}
|
||||
|
||||
.api-input {
|
||||
width: 100%;
|
||||
padding: 0.7rem 0.9rem;
|
||||
font-size: 1rem;
|
||||
border: 1px solid var(--md-default-fg-color--lightest);
|
||||
border-radius: 0.3rem;
|
||||
background: var(--md-default-bg-color);
|
||||
color: var(--md-default-fg-color);
|
||||
}
|
||||
|
||||
.api-input:focus {
|
||||
outline: 2px solid var(--md-accent-fg-color);
|
||||
outline-offset: 1px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.api-hint {
|
||||
color: var(--md-default-fg-color--light);
|
||||
font-size: 0.8rem;
|
||||
margin: 0.6rem 0 0;
|
||||
}
|
||||
|
||||
.api-count {
|
||||
color: var(--md-default-fg-color--light);
|
||||
font-size: 0.75rem;
|
||||
margin: 0.8rem 0 0.4rem;
|
||||
}
|
||||
|
||||
.api-results {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.api-result {
|
||||
padding: 0.55rem 0.6rem;
|
||||
border-left: 3px solid var(--md-primary-fg-color);
|
||||
margin-bottom: 0.4rem;
|
||||
background: var(--md-code-bg-color);
|
||||
border-radius: 0 0.2rem 0.2rem 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.api-name {
|
||||
font-family: var(--md-code-font-family, monospace);
|
||||
font-weight: 700;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.api-sig {
|
||||
font-size: 0.72rem;
|
||||
color: var(--md-default-fg-color--light);
|
||||
background: none;
|
||||
padding: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.api-desc {
|
||||
flex-basis: 100%;
|
||||
font-size: 0.78rem;
|
||||
color: var(--md-default-fg-color--light);
|
||||
}
|
||||
|
||||
.api-loc {
|
||||
flex-basis: 100%;
|
||||
font-size: 0.68rem;
|
||||
color: var(--md-default-fg-color--lighter, var(--md-default-fg-color--light));
|
||||
font-family: var(--md-code-font-family, monospace);
|
||||
}
|
||||
|
||||
.api-badge {
|
||||
font-size: 0.62rem;
|
||||
padding: 0.08rem 0.34rem;
|
||||
border-radius: 0.6rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.api-badge-builtin {
|
||||
background: rgba(245, 158, 11, 0.18);
|
||||
color: #b45309;
|
||||
border: 1px solid rgba(245, 158, 11, 0.5);
|
||||
}
|
||||
|
||||
[data-md-color-scheme="slate"] .api-badge-builtin {
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
/* 「仅内置」告警块里的依据说明通常很长,窄屏下允许更小字号。 */
|
||||
@media screen and (max-width: 44.98em) {
|
||||
.api-sig { font-size: 0.68rem; }
|
||||
}
|
||||
|
||||
/* 签名与长类型定义可能超出正文宽度(如 ContentBlock 的字段列表)。
|
||||
highlight 代码块默认不换行,靠横向滚动;把默认改为换行显示,
|
||||
因为文档读者更希望一眼看全签名而不是拖滚动条。 */
|
||||
.md-typeset pre > code {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* 接口方法表里的签名可能很长,允许在任意位置折行。 */
|
||||
.md-typeset table code {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
77
docs/versions.md
Normal file
77
docs/versions.md
Normal file
@ -0,0 +1,77 @@
|
||||
# 版本与兼容
|
||||
|
||||
## SDK 版本语义
|
||||
|
||||
**SDK 版本跟随内核的中版本,patch 位恒为 `.0`。**
|
||||
|
||||
整条内核 `1.1.x` 线(1.1.0、1.1.1、1.1.7…)共用 **SDK 1.1.0**;
|
||||
只有内核进入 `1.2.0` 这种中版本跃迁时,SDK 才升到 1.2.0。
|
||||
|
||||
这样插件作者只需关心「我在为哪个中版本写插件」,不必跟着内核的每个 bugfix 换依赖。
|
||||
当前内核声明的兼容上限是 **SDK 1.3.0**。
|
||||
|
||||
## 版本历史
|
||||
|
||||
| SDK | 内核 | 变化 | 需要重编? |
|
||||
|---|---|---|---|
|
||||
| **1.3.0** | 1.4.0+ | 驻留子 agent、`RecallPolicy` 等 | 想用新 API 才需要 |
|
||||
| **1.2.0** | 1.2.0 / 1.3.x | `InjectOptions{NoMemory, ContextPolicy}`、六个 `*Opts` 变体、`ChannelDef.ContextPolicy` | 不需要 |
|
||||
| **1.1.0** | 1.1.x | 多模态贯通:`Triple.SentenceText`、`Doc.Attachments`、`MediaAttachment`、`InsertWithMedia`、媒体注入方法 | 不需要 |
|
||||
| **1.0.0** | 1.0.0+ | **运行模型变更**:C ABI 动态库 → 子进程 + 共享内存 | **需要** |
|
||||
|
||||
### 1.0.0 是唯一一次破坏性变更
|
||||
|
||||
- `.so` / `.dylib` / `.dll` **不再被加载**,遇到旧产物会跳过并报可操作错误(不崩溃)。
|
||||
- **业务代码不用改一行** —— 公开 SDK 接口零改动,用新版 `hmapdev` 重编即可。
|
||||
- 产物从 `plugin.so` 变为 `plugin.bin`;不再需要 cgo。
|
||||
|
||||
### 1.1.0 / 1.2.0 是纯追加
|
||||
|
||||
两次都是**新增方法由插件调用、内核实现**,不调就不受影响。
|
||||
零值 `InjectOptions` 与旧的三参数方法完全等价,因此存量插件**不需要改、也不需要重编**;
|
||||
想用新字段的重编即可。
|
||||
|
||||
!!! tip "什么时候必须重编"
|
||||
只有两种情况:① 内核跨了中版本(如 1.1 → 1.2)且你用了新 API;
|
||||
② 内核的 RPC 协议版本变了(`.hmap` 里的 `protocol` 字段与内核不匹配)。
|
||||
后者的错配**不会静默失效** —— 握手时会显式拦下。
|
||||
|
||||
## RPC 协议版本
|
||||
|
||||
插件包里带 `protocol` 字段,必须等于内核的 `ProtocolVersion`(当前 **2**)。
|
||||
|
||||
协议 v2 引入了调用帧(tool / cleaner / output)与 `blocks_ref` 媒体块。
|
||||
v1 插件遇上 v2 内核会拿到空参数,反过来 v2 插件发 `blocks_ref` 会被 v1 内核静默忽略 ——
|
||||
**两边错配都不报错、只是静默失效**,所以协议版本在握手上显式校验。
|
||||
|
||||
## 怎么确认自己在用什么
|
||||
|
||||
装的 SDK 版本:
|
||||
|
||||
```bash
|
||||
hmapdev sdk current
|
||||
hmapdev sdk list
|
||||
```
|
||||
|
||||
插件声明的目标版本在 `plg.json` 的 `sdk` 字段。若该版本不在本地存储里,
|
||||
`hmapdev` 会**明确报错**,不静默降级 —— 静默降级会产出与内核协议不匹配的包,
|
||||
那种失败要到运行时才暴露。
|
||||
|
||||
## 文档站对应的版本
|
||||
|
||||
本页与 [API 参考](api/index.md) 由 `tools/apidoc` 从源码生成,
|
||||
内容随源码一起演进。发现文档与代码不一致时,**改的是源码注释**,
|
||||
`go run ./tools/apidoc` 重新生成即可(见下方「维护」)。
|
||||
|
||||
## 维护(给 SDK 维护者)
|
||||
|
||||
```bash
|
||||
cd homeagent-sdk
|
||||
go run ./tools/apidoc -pkgdir ./sdk -out /tmp/api.json
|
||||
go run ./tools/apidoc/gensite -api /tmp/api.json -out ./docs -examples ./example
|
||||
mkdocs serve # 本地预览
|
||||
mkdocs build # 产出 site_build/
|
||||
```
|
||||
|
||||
API 面的**能力分层**(哪些 API 仅内置可用)记在 `tools/apidoc/tiers.json`,
|
||||
每条裁定都附源码依据 —— 改这里而不是改生成物。
|
||||
Reference in New Issue
Block a user