为插件作者建一个文档站,重点是**能按描述搜到 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`(预览)。
23 KiB
输入 / 输出通道
通道是插件与外界(设备、其他 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 |
|
InjectInputMediaOpts |
|
InjectInputMediaSync |
|
InjectInputMediaSyncOpts |
|
InjectInputSync |
InjectInputSync 注入输入事件并同步等待 agent 回复,返回回复文本(无回复时返回空串)。 |
InjectInputSyncOpts |
|
InjectInterruptMedia |
|
InjectInterruptMediaOpts |
|
InjectInterruptText |
|
InjectInterruptTextOpts |
|
InjectText |
|
InjectTextNoMemory |
|
InjectTextOpts |
以下 Opts 变体让调用点在这一次注入上声明记忆与裁剪行为。 |
SetToolBlocks |
SetToolBlocks 插件工具注入多模态内容块(image_url/audio_url),内核在下一条 |
IOInjector.InjectInputMedia
InjectInputMedia(source, channel, text string, blocks []ContentBlock)
plugin.go:230
IOInjector.InjectInputMediaOpts
InjectInputMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
plugin.go:241
IOInjector.InjectInputMediaSync
InjectInputMediaSync(source, channel, text string, blocks []ContentBlock) string
plugin.go:231
IOInjector.InjectInputMediaSyncOpts
InjectInputMediaSyncOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions) string
plugin.go:242
IOInjector.InjectInputSync
InjectInputSync(source, channel, text string) string
InjectInputSync 注入输入事件并同步等待 agent 回复,返回回复文本(无回复时返回空串)。 用于通道消息的完整闭环:收到入站 → agent 处理 → 回复取回 → 送回通道。
示例插件里的真实用法
| 插件 | 位置 | 代码 |
|---|---|---|
a2a |
example/a2a/plugin.go:345 |
reply := p.sdk.InjectInputSync(p.name, p.name, |
acp |
example/acp/plugin.go:237 |
reply = p.sdk.InjectInputSync(p.name, p.name, |
plugin.go:226
IOInjector.InjectInputSyncOpts
InjectInputSyncOpts(source, channel, text string, opts InjectOptions) string
plugin.go:240
IOInjector.InjectInterruptMedia
InjectInterruptMedia(source, channel, text string, blocks []ContentBlock)
plugin.go:232
IOInjector.InjectInterruptMediaOpts
InjectInterruptMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
plugin.go:243
IOInjector.InjectInterruptText
InjectInterruptText(source, channel, text string)
plugin.go:221
IOInjector.InjectInterruptTextOpts
InjectInterruptTextOpts(source, channel, text string, opts InjectOptions)
示例插件里的真实用法
| 插件 | 位置 | 代码 |
|---|---|---|
browser |
example/browser/plugin.go:1319 |
p.sdk.InjectInterruptTextOpts(p.name, p.name, |
calendar |
example/calendar/plugin.go:527 |
p.sdk.InjectInterruptTextOpts("calendar", "calendar", msg, sdk.InjectOptions{NoMemory: true}) |
memo |
example/memo/plugin.go:299 |
p.sdk.InjectInterruptTextOpts(p.name, p.name, |
qq |
example/qq/plugin.go:1493 |
p.sdk.InjectInterruptTextOpts(p.name, p.name, text, sdk.InjectOptions{ |
plugin.go:239
IOInjector.InjectText
InjectText(source, channel, text string)
plugin.go:222
IOInjector.InjectTextNoMemory
InjectTextNoMemory(source, channel, text string)
示例插件里的真实用法
| 插件 | 位置 | 代码 |
|---|---|---|
browser |
example/browser/plugin.go:1114 |
p.sdk.InjectTextNoMemory(p.name, p.name, fmt.Sprintf("[浏览器 %s 已导航到 %s]", id, rawURL)) |
plugin.go:223
IOInjector.InjectTextOpts
InjectTextOpts(source, channel, text string, opts InjectOptions)
以下 Opts 变体让调用点在这一次注入上声明记忆与裁剪行为。
上面那些不带 opts 的方法等价于传零值 InjectOptions(记入记忆 + 不裁剪), 保留它们是为了不破坏已有插件;新代码应当用 Opts 变体把意图写清楚。
plugin.go:238
IOInjector.SetToolBlocks
SetToolBlocks(blocks []ContentBlock)
SetToolBlocks 插件工具注入多模态内容块(image_url/audio_url),内核在下一条 tool message 的 content 数组里带上这些块,让模型在后续轮次看到图/听到音频。
plugin.go:229
CapAudio
const CapAudio
Output capability flags
plugin.go:331
CapFile
const CapFile
Output capability flags
plugin.go:329
CapImage
const CapImage
Output capability flags
plugin.go:330
CapStructured
const CapStructured
Output capability flags
plugin.go:332
CapText
const CapText
Output capability flags
plugin.go:328
ChannelDef
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 不支持),又会诱使调用方手写字段白名单—— 那样新增字段会被静默丢掉。
plugin.go:139
ContextPolicyNone
const ContextPolicyNone
上下文策略:决定一次工具调用/输入/注入是否依据其内容裁剪上下文。
默认(空串或 ContextPolicyNone)不裁剪:裁剪会归档丢弃低相关事件, 必须由工具/通道/注入点显式声明才发生——否则一个只想往上下文里塞内容的 插件会在背后把别人的内容挤掉,且看不出是谁干的。
plugin.go:44
ContextPolicyPrune
const ContextPolicyPrune
上下文策略:决定一次工具调用/输入/注入是否依据其内容裁剪上下文。
默认(空串或 ContextPolicyNone)不裁剪:裁剪会归档丢弃低相关事件, 必须由工具/通道/注入点显式声明才发生——否则一个只想往上下文里塞内容的 插件会在背后把别人的内容挤掉,且看不出是谁干的。
plugin.go:45
IOInjector
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.
plugin.go:220
PluginSDK.InjectInputMedia
func (s *PluginSDK) InjectInputMedia(source, channel, text string, blocks []ContentBlock)
InjectInputMedia 注入带媒体内容块(image_url/audio_url)的输入。 blocks 会落进媒体存储被记忆引用捕获,同时作为当前轮 content 数组 发给 LLM,让模型在「本轮」就看到图/听到音频——区别于 SetToolBlocks 的「下一轮 tool message」语义。 等价于 InjectInputMediaOpts(..., InjectOptions{})。
plugin.go:701
PluginSDK.InjectInputMediaOpts
func (s *PluginSDK) InjectInputMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
InjectInputMediaOpts 注入带媒体块的输入,并声明记忆/裁剪行为。
plugin.go:740
PluginSDK.InjectInputMediaSync
func (s *PluginSDK) InjectInputMediaSync(source, channel, text string, blocks []ContentBlock) string
InjectInputMediaSync 注入带媒体内容块的输入并同步等待 agent 回复。 等价于 InjectInputMediaSyncOpts(..., InjectOptions{})。
plugin.go:707
PluginSDK.InjectInputMediaSyncOpts
func (s *PluginSDK) InjectInputMediaSyncOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions) string
InjectInputMediaSyncOpts 注入带媒体块的输入并同步等待回复,同时声明记忆/裁剪行为。
plugin.go:747
PluginSDK.InjectInputSync
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 |
example/a2a/plugin.go:345 |
reply := p.sdk.InjectInputSync(p.name, p.name, |
acp |
example/acp/plugin.go:237 |
reply = p.sdk.InjectInputSync(p.name, p.name, |
plugin.go:692
PluginSDK.InjectInputSyncOpts
func (s *PluginSDK) InjectInputSyncOpts(source, channel, text string, opts InjectOptions) string
InjectInputSyncOpts 注入输入并同步等待回复,同时在这次注入上声明记忆/裁剪行为。
plugin.go:731
PluginSDK.InjectInterruptMedia
func (s *PluginSDK) InjectInterruptMedia(source, channel, text string, blocks []ContentBlock)
InjectInterruptMedia 注入带媒体内容块的中断,可抢占当前 LLM 处理。 blocks 随中断消息一起发给模型。
plugin.go:764
PluginSDK.InjectInterruptMediaOpts
func (s *PluginSDK) InjectInterruptMediaOpts(source, channel, text string, blocks []ContentBlock, opts InjectOptions)
InjectInterruptMediaOpts 注入带媒体块的中断,并声明记忆/裁剪行为。
plugin.go:756
PluginSDK.InjectInterruptText
func (s *PluginSDK) InjectInterruptText(source, channel, text string)
InjectInterruptText injects a text interrupt that can preempt current LLM processing. 等价于 InjectInterruptTextOpts(..., InjectOptions{}):记入记忆、不裁剪。
plugin.go:673
PluginSDK.InjectInterruptTextOpts
func (s *PluginSDK) InjectInterruptTextOpts(source, channel, text string, opts InjectOptions)
InjectInterruptTextOpts 注入可抢占当前处理的中断文本。
中断也允许声明 ContextPolicyPrune:中断同样携带内容进入上下文, 是否需要据此裁剪由调用方决定(默认不裁剪)。
示例插件里的真实用法
| 插件 | 位置 | 代码 |
|---|---|---|
browser |
example/browser/plugin.go:1319 |
p.sdk.InjectInterruptTextOpts(p.name, p.name, |
calendar |
example/calendar/plugin.go:527 |
p.sdk.InjectInterruptTextOpts("calendar", "calendar", msg, sdk.InjectOptions{NoMemory: true}) |
memo |
example/memo/plugin.go:299 |
p.sdk.InjectInterruptTextOpts(p.name, p.name, |
qq |
example/qq/plugin.go:1493 |
p.sdk.InjectInterruptTextOpts(p.name, p.name, text, sdk.InjectOptions{ |
plugin.go:724
InjectOptions
type InjectOptions struct { NoMemory bool ContextPolicy string // RecallPolicy 声明此次注入是否据其内容召回相关记忆。 // 空串 = 默认(输入/注<><E6B3A8> …
InjectOptions 声明一次注入行为在记忆层与上下文层的表现。
零值 = 记入记忆 + 不裁剪上下文,与历史行为(三参数注入方法)完全一致, 因此调用方只有在确实需要改变行为时才需要填它。
为什么注入也要这两个标志:注入的内容来源千差万别——轮询到的频道消息 属于真实对话(该记),而“任务还在跑”“连接已重连”这类提醒不该污染记忆, 也不该把上下文按它的内容裁一遍。按调用点声明比按通道一刀切准确。
NoMemory: 此次注入不参与记忆计算(向量化/关键词提取/蒸馏),原文仍留在上下文 ContextPolicy: 此次注入后是否依据(清洗后的)内容裁剪上下文;默认不裁剪。 RecallPolicy: 此次注入是否依据(清洗后的)内容召回相关记忆;默认 auto(召回)。
中断注入也允许声明 prune——它同样会携带内容进入上下文。
CleanerName: 此次注入的内容用哪个已注册的通道 cleaner 清洗。
空串 = 按注入的 source 查通道定义(既有行为)。
为什么要能显式指定:注入的 source 未必是注册过的输入通道名,
而注入内容往往带 ANSI/JSON 包装,需要清洗后才是有效内容;
不指定就只能退到「按 source 查不到就不清洗」。
plugin.go:98
PluginSDK.InjectText
func (s *PluginSDK) InjectText(source, channel, text string)
InjectText injects a text message into the agent pipeline. 等价于 InjectTextOpts(..., InjectOptions{}):记入记忆、不裁剪。
plugin.go:679
PluginSDK.InjectTextNoMemory
func (s *PluginSDK) InjectTextNoMemory(source, channel, text string)
InjectTextNoMemory injects a text message without generating memory. 等价于 InjectTextOpts(..., InjectOptions{NoMemory: true})。
示例插件里的真实用法
| 插件 | 位置 | 代码 |
|---|---|---|
browser |
example/browser/plugin.go:1114 |
p.sdk.InjectTextNoMemory(p.name, p.name, fmt.Sprintf("[浏览器 %s 已导航到 %s]", id, rawURL)) |
plugin.go:685
PluginSDK.InjectTextOpts
func (s *PluginSDK) InjectTextOpts(source, channel, text string, opts InjectOptions)
InjectTextOpts 注入文本到 agent,并在这一次注入上声明记忆与裁剪行为。
plugin.go:714
PriorityL1
const PriorityL1
中断优先级取值。
L1..L3 任何插件都可声明;L4 只有内核级插件(编译期内置插件, 如 cli/webui/timer)才能声明——它用于实现真正的“立即打断”能力, 例如 WebUI 的终止按钮。外部插件(走 proc 桥)声明 L4 会被内核夹到 L3。
plugin.go:123
PriorityL2
const PriorityL2
中断优先级取值。
L1..L3 任何插件都可声明;L4 只有内核级插件(编译期内置插件, 如 cli/webui/timer)才能声明——它用于实现真正的“立即打断”能力, 例如 WebUI 的终止按钮。外部插件(走 proc 桥)声明 L4 会被内核夹到 L3。
plugin.go:124
PriorityL3
const PriorityL3
中断优先级取值。
L1..L3 任何插件都可声明;L4 只有内核级插件(编译期内置插件, 如 cli/webui/timer)才能声明——它用于实现真正的“立即打断”能力, 例如 WebUI 的终止按钮。外部插件(走 proc 桥)声明 L4 会被内核夹到 L3。
plugin.go:125
PriorityL4
!!! warning "仅内核内置插件可用" sdk/plugin.go:119-126 明确:L1..L3 任何插件可声明,L4 只有内核级插件可用,外部插件声明会被内核夹到 L3(内核侧有 priority_clamp_test.go 守着)。外部插件文档应说明「声明 L4 无效」。
const PriorityL4
PriorityL4 仅内核级(内置)插件可用;外部插件声明会被夹到 L3。
plugin.go:127
RecallPolicyAuto
const RecallPolicyAuto
召回策略:决定一次工具调用/输入/注入是否据其内容召回(注入)相关记忆。
与 ContextPolicy 正交:ContextPolicy 管「裁剪」(把低相关 L0 事件归档), RecallPolicy 管「召回」(把 L2/L3 的相关记忆注入本轮)。两者默认值刻意相反—— 裁剪是破坏性的,默认关(必须显式声明);召回是只读增量、日常对话本就需要, 默认 auto(输入/注入),仅工具默认 none(工具输出多为噪声,按需声明)。
plugin.go:65
RecallPolicyNone
const RecallPolicyNone
召回策略:决定一次工具调用/输入/注入是否据其内容召回(注入)相关记忆。
与 ContextPolicy 正交:ContextPolicy 管「裁剪」(把低相关 L0 事件归档), RecallPolicy 管「召回」(把 L2/L3 的相关记忆注入本轮)。两者默认值刻意相反—— 裁剪是破坏性的,默认关(必须显式声明);召回是只读增量、日常对话本就需要, 默认 auto(输入/注入),仅工具默认 none(工具输出多为噪声,按需声明)。
plugin.go:64
PluginSDK.RegisterInputChannel
func (s *PluginSDK) RegisterInputChannel(name string, def ChannelDef) error
RegisterInputChannel registers an input channel with its memory behavior.
契约:凡是用 InjectText/InjectInput/InjectInterrupt*(source, "", ...)
注入的通道名,都应当在这里登记**。inputch 是内核里最基本的输入路由单位:
只有登记过的通道才能在 inputch 登记表里出现,父 agent 才能"把某个 inputch 划给驻留子";
没登记就划分会直接失败(inputch 未注册)。
只登记输出通道(RegisterOutputChannel)而没登记输入通道时,内核会兜底登记同名 inputch 并打告警日志 —— 兜底只为兼容老插件,新插件请显式登记。
def.NoMemory: 此通道输入不参与记忆计算 def.Cleaner: 计算层对输入文本清洗后(不改原文)再向量化/提关键词
示例插件里的真实用法
| 插件 | 位置 | 代码 |
|---|---|---|
a2a |
example/a2a/plugin.go:52 |
_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{}) |
acp |
example/acp/plugin.go:52 |
_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{}) |
browser |
example/browser/plugin.go:209 |
_ = s.RegisterInputChannel(p.name, sdk.ChannelDef{}) |
calendar |
example/calendar/plugin.go:281 |
_ = s.RegisterInputChannel("calendar", sdk.ChannelDef{NoMemory: true}) |
plugin.go:561
PluginSDK.RegisterOutputChannel
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__ 的回复发给谁); 入站(谁会往 注入输入)是另一件事,用 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 |
example/a2a/plugin.go:57 |
if err := s.RegisterOutputChannel(p.name, 1, "A2A Agent 互联通道(外部 agent 查询的回复由此返回)", sdk.ChannelDef{}, func(args… |
acp |
example/acp/plugin.go:57 |
s.RegisterOutputChannel(p.name, 1, "ACP Agent 互联通道(外部 agent 会话的回复由此返回)", sdk.ChannelDef{}, func(args map[strin… |
qq |
example/qq/plugin.go:411 |
s.RegisterOutputChannel("qq", sdk.CapText|sdk.CapFile|sdk.CapImage|sdk.CapAudio, |
weather |
example/weather/plugin.go:104 |
if err := s.RegisterOutputChannel(tp+"weather_out", 0, "push weather to user", sdk.ChannelDef{ |
plugin.go:528
PluginSDK.SetToolBlocks
func (s *PluginSDK) SetToolBlocks(blocks []ContentBlock)
SetToolBlocks 在工具处理函数内注入多模态内容块,内核在下一条 tool message 的 content 数组里带上它们。需要「本轮就让模型看到」时用 InjectInputMedia。
plugin.go:772
ValidContextPolicy
func ValidContextPolicy(policy string) bool
ValidContextPolicy 校验策略取值;空串等价于 ContextPolicyNone。
plugin.go:49
ValidRecallPolicy
func ValidRecallPolicy(policy string) bool
ValidRecallPolicy 校验召回策略取值;空串按调用面取默认值。
plugin.go:69