Files
HomeAgent/internal/agent/api
JianFeeeee 662d5472ee fix(agent): 安全装饰器吃掉 provider 的模态能力声明(see_image 失效根因)
## 现象

调用 see_image 工具是通的、图片也确实注入了,但内核回:

    当前模型不支持图片,且没有可用的视觉回退源

而配置明明是 `core.llm.sources.llmsproxy.vision = true`,后端也确实支持看图。
用户以为是配置没热生效、以为要重启,实际都不是。

## 根因:一条完整的静默失效链

    vision=true(配置是对的)
      → LuaAdaptedProvider.Vision = true
      → ProviderManager.Register 把它包成 *secureProviderDecorator
        (security.Global() != nil 时无条件包装)
      → ProviderSupportsVision(p) 做 p.(ModalProvider) 断言
      → ★ 装饰器只实现了 Name/MaxContextTokens/Chat/ChatStream,
          **漏了 ModalProvider 的 SupportsVision/SupportsAudio**
      → 断言失败 ⇒ 返回 false
      → prepareToolBlocks 判定"主模型不支持图片"
      → see_image 的 image_url 被当成需要回退的块
      → resolveModalFallback 找不到可用视觉源
      → 「没有可用的视觉回退源」

装饰器只该**改写请求内容**(明文→别名防泄漏),不该改变能力声明。

## 修法

透传给内层:内层实现了 ModalProvider 就照实转,否则 false(不 panic)。

## 判据(4 条)

`internal/agent/api/provider_security_modal_test.go`:
 vision 透传 / audio 透传 / vision=false 不得被污染成 true / 无 ModalProvider 不 panic

★ 我第一版判据是**假绿**的,值得记下来:
 `WrapProviderForSecurity(p, nil)` 在 store==nil 时**直接返回原对象**
 (见其实现),所以根本没包装,测的是未包装的 stub ⇒ 删掉透传也不红。
 已改为构造真实 security.Store,并加**包装前置断言**
 (`_, ok := wrapped.(*secureProviderDecorator)`),防止再次假绿。

变异 2/2 咬住:
 · 删掉 SupportsVision 透传 → TestRegisterPreservesVisionCapability 红
 · 一律返回 true       → TestRegisterPreservesNonVisionCapability 红

## 实测(完整生产装配路径)

    源 llmsproxy  类型=*api.secureProviderDecorator  vision=true audio=false
    默认源 "llmsproxy": ProviderSupportsVision=true

重启本机实例后:22 插件建链 / 0 panic。
2026-10-17 20:20:49 +08:00
..