fix(memory): 时间线按声明渲染工具回执 + 能力判定改为配置层

时间线此前只写「→ 调用工具: a, b」,工具取回的正文下一轮彻底消失:
模型记得自己查过,却不记得查到了什么(刚说的话转头就忘)。
ToolDef 上的 Cleaner / NoMemory 声明一直存在,缺的是消费端。

- formatMergedTimeline 渲染 e.ToolResults:Cleaner 优先,NoMemory 工具跳过,
  单条 160 字、每事件最多 3 条(+N more),预算估算同步计入
- 模态能力改由配置层声明(ProviderManager.SetCapabilities),
  不再靠 provider.(ModalProvider) 断言穿过 adapter 与安全装饰器
- a2a 通道 RecallPolicy none→auto(NoMemory 仍 true:可读不写)
This commit is contained in:
JianFeeeee
2026-10-27 12:16:06 +08:00
parent 4af07d08fe
commit 051247e10f
6 changed files with 290 additions and 1 deletions

View File

@ -1087,6 +1087,68 @@ type ProviderManager struct {
order []string
default_ string
status map[string]*providerStatus
// caps 是**配置层声明**的模态能力(源名 → vision/audio)。
//
// ★ 为什么不靠 `provider.(ModalProvider)` 断言(2026-10-27 用户指正):
// 是否支持视觉是**配置事实**(core.llm.sources.<name>.vision=true),
// 而 adapter 只负责格式转换。用类型断言去"猜"能力,等于让能力声明
// 穿过 adapter 与安全装饰器两层——任何一层漏实现方法,能力就静默变成
// false(这就是反复出现的"配了视觉却说没有视觉")。
// 断言只保留为**兜底**(配置未声明时的旧路径)。
caps map[string]ProviderCaps
}
// ProviderCaps 是一个 provider 的模态能力(来自配置)。
type ProviderCaps struct {
Vision bool
Audio bool
}
// SetCapabilities 由装配层(llm 模块)把配置里的能力声明登记进来。
func (m *ProviderManager) SetCapabilities(name string, caps ProviderCaps) {
if m == nil || name == "" {
return
}
m.mu.Lock()
if m.caps == nil {
m.caps = map[string]ProviderCaps{}
}
m.caps[name] = caps
m.mu.Unlock()
}
// Capabilities 返回某 provider 的配置层能力(ok=false 表示配置未声明)。
func (m *ProviderManager) Capabilities(name string) (ProviderCaps, bool) {
if m == nil {
return ProviderCaps{}, false
}
m.mu.RLock()
defer m.mu.RUnlock()
c, ok := m.caps[name]
return c, ok
}
// SupportsVision / SupportsAudio 是**面向调用方**的能力查询:
// 先看配置声明,配置没说才退回类型断言(兼容外部/测试 provider)。
func (m *ProviderManager) SupportsVision(name string) bool {
if c, ok := m.Capabilities(name); ok {
return c.Vision
}
if p := m.Get(name); p != nil {
return ProviderSupportsVision(p)
}
return false
}
func (m *ProviderManager) SupportsAudio(name string) bool {
if c, ok := m.Capabilities(name); ok {
return c.Audio
}
if p := m.Get(name); p != nil {
return ProviderSupportsAudio(p)
}
return false
}
const (