dsh-media-lens 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,90 +1,94 @@
1
- # Changelog
2
-
3
- 0.2.2: host enumeration addendum for DSH 0.2.1-rc.3 (declaration-only bump; contract plane zero diff).
4
-
5
- ## 0.1.0 (2026-10-06)
6
-
7
- 首版:透镜线(A)+ 路由线(B)单计划双线落地。
8
-
9
- ### 线 A 透镜(Evidence, not imagination)
10
- - `media_read_video` / `media_read_audio` / `media_read_image` 三个 host 工具,统一输出 MediaEvidence JSON 证据合同(timeline / transcript / regions + `meta.attempts`)。
11
- - 图片直读字节送视觉端点,绕开宿主 read_image 的 sharp 故障;bmp 等附件词表外格式由 configured 端点兜。
12
- - 视频:预算分档 full(≤10MB 直传全模态)/ reencode(10–19MB 540p x264 压缩)/ sampleFrames(>19MB 或 >10min 均匀抽 6–12 帧 + 图片链);timeline 证据带逐帧时间戳。
13
- - 音频:全模态直传转写 + 本地 ASR CLI(SenseVoice / batch-voice-transcriber 两种输出 fixture 的适配层:JSON segments 与 SRT/时间行)双路。
14
-
15
- ### 线 B 路由(fallback never silent)
16
- - 每媒体一条 failover 链,首个成功即返回;全败抛 `AllProvidersFailed{attempts}`。
17
- - 注册模型发现(FR-9):`ctx.llm.listProviders()/listModels()` 软读枚举 + `inputModalities` 分组 + 60s 缓存;`models.{image,audio,video}: "provider/model"` 显式选择高于 discovery(视频 MiMo 路入口)。
18
- - 预算裁决不硬拒:full / reencode / sampleFrames / segment / downsample 自动降级。
19
- - keyFailure 类失败触发冷却(FR-10,modlens 软化熔断语义),跨请求共享闸,到期自动恢复。
20
- - strictLocalOnly:链中剔除一切远端成员,媒体内容不出本机。
21
- - OpenAI-compatible 端点逗号 key 轮换(auth/429/quota 换 key,网络/5xx 保链)。
22
- - 音视频直传防幻觉:未接管线路的 handle 文本回声被检测并放弃该跳,落抽帧/ASR 兜底。
23
-
24
- ## 0.2.0 (2026-10-23)
25
-
26
- vision-bridge 发布版:选择器可见的透镜包装 + 配置热生效根因修复 + 宿主三线全 rc 支持。以下批次随本版首发(modlens / provider pair 两批原标注 0.1.0 同版本并入,npm 0.1.0 未含,实际随本版出)。
27
-
28
- ### modlens 源码吸收批(2026-10-06 深析,随 0.2.0 首发)
29
- - 冷却语义升级(FR-10):仅 keyFailure 类失败触发冷却,普通失败永不冷却(正文碰巧含 quota 字样不算);冷却 provider **垫队尾**仍作最后尝试(软化熔断,非踢出);`Retry-After` 头 / `Resets in Xh Ym Zs` 解析进冷却时长;成功即恢复。
30
- - attempts 带 `keyIndex`(多 key 轮换可见);`meta.warnings` 人类可读路由通告(failover 去向、冷却垫尾、轮换成功);错误文本先脱密(apiKey/baseUrl 精确匹配)再截断(redact-before-clip)。
31
- - 证据合同 v2:新增 `uncertainty?: string[]`(模型自报读不清处,宁标不猜);可选字段 null 归一(不拒整读);处理指令带反注入行(媒体内指令视为数据)、verbatim 转写行、bbox 保守措辞。
32
- - 传输硬化:请求显式 `stream: false`(网关缺省分歧);`providers.openai.structuredOutput` opt-in 注入 strict json_schema(由证据载荷派生,非手写镜像);`finish_reason=length` 截断判因(与形状问题区分)。
33
- - 入参 magic-byte sniff(FR-14):png/jpeg/gif/webp/bmp、ftyp/EBML、RIFF/ID3/fLaC/OggS/ADTS/ASF 头字节判型,改名伪装文件在入参侧拒绝。
34
- - 单成员链穷尽保留原始可行动错误(不被聚合错误吞掉)。
35
-
36
- ### provider pair 吸收批 v2(2026-10-17,链出口 refine pass,随 0.2.0 首发)
37
- - 新增顶层配置 `refine: { enabled, model }`(FR-15,opt-in 默认关):开启后主链首个成功 → `finalizeChunk` 之后 → return 之前过一次可选文本模型(对齐 medialens 型项目 "读手 + 转述手" pair)。model 必须显式 `'provider/model'`;`auto` / 空串 = 未启用(宿主 text 组 = 所有已注册模型,auto = 随便加透镜)。
38
- - refine 失败保 raw(FR-17):LLM throw / JSON 解析失败 / validateEvidence 不通过 三情形均不 throw 到链下一级,仅追 `meta.warnings` 一条 `refine failed: <redacted>`;主链 attempts 不新增(读手已成功,不浪费下一跳抽帧/ASR)。
39
- - refine 归属分层(FR-19):`MediaEvidence.meta` 新增 `refineAttempt?: Attempt`,`provider = 'refined:<selection>'`;`meta.provider` 保留读手前缀(registered/multimodal/configured/asr/frames/local);source/attempts/generatedAt/degraded 不被 refine 修改。
40
- - 指令硬化(FR-18):`EVIDENCE_REFINE_INSTRUCTION` 与读手 EVIDENCE_INSTRUCTION 同四要素(反注入/verbatim/bbox 保守/uncertainty),另加「保留原 source 与 attempts,不新增媒体路径信息」一行——防洗证据(raw evidence 内媒体指令会二次进入 LLM 上下文)。
41
- - local 兑底(FR-19):`meta.provider === 'local:metadata'` 或 `meta.degraded === 'local-metadata-only'` 时自动跳过 refine,无文本可精炼;守 strictLocalOnly 用户的隐私默认(“关了远端却被 refine 拉回远端”不允许)。
42
- - 传输硬化(FR-19):错误路径先 `redactSecrets` 后截断(对齐 M23);`structuredOutput` 开关共享,ctx.llm.stream 不接 response_format(宿主 GenerateOptions 无该字段),指令层 STRICT JSON 兑住。
43
- - 新文件 `src/refine.ts`(导出 `refineEvidence` + `EVIDENCE_REFINE_INSTRUCTION`);`router/failover.ts` `ChainRunOptions` 新可选 `refine` hook;工具层从 `LensDeps.refineFn` 取闭包传入 runChain(hook 抛异常时 failover 兑底,仍保 raw)。
44
-
45
- ### refine-source-gate 吸收批 v3(2026-10-21,转述手来源闸门,随 0.2.0 首发)
46
- - **来源闸门(转述手只认用户自配端点)**:refine 传输从 `ctx.llm.stream`(宿主 runtime 路由表,无法区分宿主原生注册与插件注入模型)改为 **configured 直连**(`callVlWithRotation`,够不到任何注入面)。新增 `isTrustedRefineProvider(endpoint, provider)`:provider 名白名单(`openai`)+ 凭据存在性(baseUrl+apiKey 非空)双判据(`src/config.ts`)。
47
- - **静默跳过语义**:selection 解析出 `registered:` / `multimodal:` / 未知 provider,或 configured 凭据未配齐 → 原样返回 evidence,不写 `refineAttempt`、不追 warning(守 M28「关时逐字段一致」,与 llm 缺失/非显式 selection 现有跳过路径同构)。
48
- - **归一与单源**:裸 model(无 slash)归一为 configured 白名单 `openai`(S1);请求 model 取 `providers.openai.model` 单源,不采纳 selection 的 model 段;`refineAttempt.provider` 统一 `refined:openai/<model>`,`meta.provider` 仍留读手前缀。
49
- - **传输硬化继承**:直连后 `structuredOutput` 真正注入 `response_format`(顺带修复 v2 中 ctx.llm.stream 注不进去的已知缺陷);多 key 轮换 keyIndex 透传 `refineAttempt`;错误先脱密后截断不变。
50
- - 工程:`tests/refine.test.ts` 改写为 fetch mock 直连断言(18 例:G1 真值表 / 闸门静默跳过 / 直连 URL 与请求体 / structuredOutput 两态 / 失败保 raw / runChain 集成),全量 105 vitest 用例绿 + lint 零错 + `check:line OK`。
51
-
52
- ### vision-bridge 吸收批 v4(2026-10-22,选择器可见的透镜包装)
53
- - **包装 provider 注入**:新增配置 `inject: { enabled, families }`(默认关;families 默认 `['deepseek','glm','mimo']`,`'*'` = 全部)。开启后经宿主 `llm.registerAdapter` 注册包装路由 `media-lens-vision-<upstream>`,模型选择器出现 `<模型名> (media-lens vision)` 条目(provider 组名 `<上游名> (media-lens)`);选中它作主对话模型,请求中的媒体先经透镜转证据文本再委托真实路由。
54
- - **包装判据(用户裁定)= 透镜能补模态就包,omni 坚决不包**:纯文本模型 → 图像+音视频全部证据化;图像模型(声明 image、非 omni)→ 图像块原样透传(保留原生视觉),仅音视频证据化;omni(MiMo bare 非 `-pro` 段名字启发式)与 vision 专精词表(deepseek-vl / glm-4v / `*vision*` / glm-5.3-flash 等)拒包,`resolveModel` 响亮拒绝并指路原路由。
55
- - **请求改写管线(0.2.0-rc.2 投影时序实锚)**:宿主在 adapter 收到请求前把 file 块无条件投影成确定性 handle 文本(按 `content.ts:151-158` 精确格式识别、提取只读盘上路径)→ `guardMediaPath`+`decideBudget`+`runChain` 全链 → 证据文本(反注入头 + `evidenceRender` 体)原位替换;image 块按上游实际模态分流。消息改写 copy-on-write(宿主 deep-frozen 不原地改),assistant 消息 `source.provider` 线副本回标上游(保 replay-state / forAdapter 身份检查,durable log 保留 wrapper id)。
56
- - **改写永不 throw**:读手失败按阶段落常量占位文本(线字节稳定,保 provider prefix cache);证据缓存按块稳定键(`attachmentId` / 盘上路径),成功永久、失败 60s 后重试、LRU 256。
57
- - **生命周期纪律**(modlens dsh/index.js 实证 + 本仓 C2 软读):`registerAdapter` 结构化探测(缺失即纯工具插件静默降级);`llm/adapters-updated` 事件驱动重扫(覆盖 llm-pi-ai 类迟注册路由),sweep 串行化防自触发风暴;`DUPLICATE_ADAPTER` 视为让位成功;注册句柄挂 `ctx.effect` 随 fiber 释放;上游消失/不再合格 → wrapper 回收;上游展示名变更 → `handle.replace` 原子重照快照;`imageRequestPricing` 显式 `undefined`(宿主 ≥0.1.2 无条件调用,纯对象必须自带)。
58
- - **防递归(必须)**:discovery 快照 `filterDiscoverySnapshot` 剔除 `media-lens-vision-*` —— wrapper 注册后自己也在宿主注册表里,不剔除则读手 auto 会选中自己的 wrapper 无限递归;工具链 / models-route 候选 / 显式选择共用同一快照,一处过滤全覆盖。
59
- - **信号穿透**:`Provider.run(req, signal?)` + `ChainRunOptions.signal` 全链下传(providers 七处 + frames/reencode 内层透传),桥的改写随宿主请求中止;工具层不传 signal 行为不变。
60
- - **客户端**:设置卡新增「视觉桥」节(enabled 勾选 + families 逗号分隔输入,中英逗号皆收);9 语 locale;configForms mutate/set 六路径提交。
61
- - 工程:`tests/inject.test.ts` 23 例(判据真值表 / handle 精确识别 / 改写 copy-on-write 与回标与透传分流 / 缓存成功永久失败重试 LRU / sweep 注册刷新回收 / 桥端到端 读手→证据→委托→缓存命中 / 失败常量占位 / discovery 自排除);全量 128 vitest 用例绿 + typecheck/lint 零错。
62
-
63
- ### volatile 活读修复批 v4.1(2026-10-22 深夜,配置热生效根因修复)
64
- - **根因(0.2.0-rc.2 宿主源码实锚)**:宿主对 `.volatile()` 字段传给插件的是带 `.get()` 的活引用(settings/plainConfig、agent-default-model `config.provider.get()` 实证),且写入后**只发 `loader/volatile-update` 事件、不重跑 apply**(llm-pi-ai / llm-deepseek / speech-to-text 三内置插件同范式自证)。media-lens 原先在 apply 期把 config 平铺展开——cell 被展开成垃圾属性,**全部字段静默回落默认值**:不仅视觉桥开关无效,处理端模型 / 端点 / refine 等所有用户配置宿主半区从未读到(v4 之前即存在的隐性缺陷,v4 的选择器开关使其显形)。
65
- - **修复**:`resolveConfig` 入口 `detachConfigCells` 递归取直(duck-type `.get()`,照 models-route 软读纪律,不引 cosmokit);apply 内 `resolved` 改为可整体重算的 `let`,全部闭包(vlConfig / selectionFor / strictLocalOnly / refineFn / bridge families+enabled)调用期解引用即热生效;订阅 `loader/volatile-update`(duck-type)→ 重算 resolved + `bridge.sweep()`(桥按新 enabled 注册/回收包装路由)。已知留置:cooldownMs 变更仍需重载(CooldownGate 构造期取值,重建会丢冷却状态,权衡不改)。
66
- - 工程:`tests/inject.test.ts` 增 3 例(cell 形态 resolveConfig 取直 / 活值重算 / 普通对象照旧),全量 131 vitest 用例绿 + typecheck/lint 零错。
67
-
68
- ### 宿主三线全 rc 枚举批(2026-10-23,用户裁定口径)
69
- - **口径变更(用户裁定)**:engines.dsh / peers 枚举从「每线一个 farm 保留 rc」改为 **0.1.7 / 0.2.0 / 0.2.1 三线全 rc 枚举**(`0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2`,alpha/beta 不入列)。新增 `0.1.7-rc.1` 与 `0.2.0-rc.1` 两格。
70
- - **契约面核实(tarball 逐字 diff,钉 npmjs)**:`0.2.0-rc.1` llm 类型与 rc.2 **零差异**;`0.1.7-rc.1` attachment 全类型零差异、llm 仅缺本插件不消费的 `toolHistory`/`toolUpdate` 可选面,`registerAdapter` / `llm/adapters-updated` 在位(vision-bridge 在该线可用)。六宿主子包 × 两 rc 共 12 项 npm 在位逐一实查。
71
- - `hosts.mjs` 口径段重写(声明枚举与 farm 实测账本 `.compat-results/verified.json` 分属两档,后者不动),`sync-hosts --write` 三写点下发。
72
- - 全门禁:131 vitest 用例绿 + typecheck/lint 零错。
73
-
74
- ### 宿主 0.2.1-rc.2 支持批(2026-10-23,engines 枚举补格 + wrapper 直调面补齐)
75
- - **背景**:desktop 宿主 manifest 推荐 rc 已是 0.2.1-rc.2;engines.dsh 自 0.2.1-alpha.4 起是独立且优先的拒载判据,枚举不含 rc.2 则宿主升级即拒载本插件。
76
- - **契约面核实(tarball 逐字 diff,0.2.1-rc.2 vs 0.2.0-rc.2)**:`dsh-attachment` 全类型文件零差异;`dsh-llm` message/content/retry-policy 零差异,index/types 仅**可选**新增(adapter 面 `imageRequestTarget`、runtime 面 `prepareCall(config, signal?, configure?)`——adapter 面签名不变、directory 侧 `LlmConfigurableProvider` 三可选字段)。六宿主子包 npm 全在位(钉 npmjs 实查)。
77
- - **修复**:wrapper adapter 补 `imageRequestTarget`(0.2.1-rc.1 起新增,runtime **直调无 optional chaining**,`lib/index.js:2064` 实锚——纯对象缺了在图像投影时 TypeError;照基类缺省语义原样保留归一化图像 `{width,height,maxBytes}`)。`hosts.mjs` 枚举加 `0.2.1-rc.2`,`sync-hosts --write` 下发 package.json / dsh.plugin.json / README 三写点。
78
- - **verified.json 不动**:rc.2 未过 farm 逐格实测,仅静态 diff + 冒烟级核实(诚实档位);farm 补测后另行入账。
79
- - 工程:sweep 测试增 adapter 直调面断言(pricing undefined / target 保留语义),全量 131 vitest 用例绿 + typecheck/lint 零错。
80
-
81
- ### 工程
82
- - 宿主 service 全部 `ctx.get()` 软读优雅降级(C2);运行时依赖仅 schemastery。
83
- - 131 vitest 用例全绿(含 `tests/inject.test.ts` 26 例覆盖 vision-bridge 与 volatile 取直);`check:line` 门禁保证 lib/ 与 src/ 逐字节对齐(C3)。
84
- - 宿主线钉三线全 rc:`0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2`(engines.dsh / peerDependencies / dsh.plugin.json / README 四写点由 sync-hosts 单源下发;publishConfig.tag = dsh-0.2.0)。
85
-
86
- ## 0.2.1 (2026-10-23)
87
-
88
- ### Changed
89
- - **声明配合姊妹插件**:将两个配合插件加入 `peerDependencies` 并标 `optional`(`dsh-thinking-levels >=4.4.4`、`dsh-llm-openai-completions >=0.4.4`)——多模态模型注册/认证与音视频直传靠它们接管(thinking-levels 管模型配置录入/接管登记,openai-completions 做 OpenAI-compatible 传输接管将 file 块序列化成 video_url/input_audio)。二者均为 optional peer,不装则相应能力自动降级不报错,不触发 npm 强制拉装、不与宿主版本闸门冲突(两包 latest engines.dsh 均已含 0.2.1-rc.2)。README 新增「配合插件」小节。
90
-
1
+ # Changelog
2
+
3
+ 0.3.0: PDF 直读 + 取证授权开关。新增 `media_read_pdf` 工具(PDF file 块直传注册模型,openai-completions 接管线按 extraInputs 的 `pdf` 声明放行,输出版面 regions 证据;无可用模型落 probe 元数据兜底);设置新增 `models.pdf` 处理端选择与 `modalities.{pdf,video,audio}` 三个取证授权开关(false = 该类媒体整体拒绝处理,工具响亮报错、vision-bridge 不改写;默认全开 = 行为不变)。能力发现同步 `pdf` 模态分组(extraInputs 声明的模型经 discovery 自动进 pdf 组候选)。
4
+
5
+ ## 0.1.0 (2026-10-06)
6
+
7
+ 0.2.2: host enumeration addendum for DSH 0.2.1-rc.3 (declaration-only bump; contract plane zero diff).
8
+
9
+ ## 0.1.0 (2026-10-06)
10
+
11
+ 首版:透镜线(A)+ 路由线(B)单计划双线落地。
12
+
13
+ ### 线 A 透镜(Evidence, not imagination)
14
+ - `media_read_video` / `media_read_audio` / `media_read_image` 三个 host 工具,统一输出 MediaEvidence JSON 证据合同(timeline / transcript / regions + `meta.attempts`)。
15
+ - 图片直读字节送视觉端点,绕开宿主 read_image 的 sharp 故障;bmp 等附件词表外格式由 configured 端点兜。
16
+ - 视频:预算分档 full(≤10MB 直传全模态)/ reencode(10–19MB 540p x264 压缩)/ sampleFrames(>19MB 或 >10min 均匀抽 6–12 帧 + 图片链);timeline 证据带逐帧时间戳。
17
+ - 音频:全模态直传转写 + 本地 ASR CLI(SenseVoice / batch-voice-transcriber 两种输出 fixture 的适配层:JSON segments 与 SRT/时间行)双路。
18
+
19
+ ### 线 B 路由(fallback never silent)
20
+ - 每媒体一条 failover 链,首个成功即返回;全败抛 `AllProvidersFailed{attempts}`。
21
+ - 注册模型发现(FR-9):`ctx.llm.listProviders()/listModels()` 软读枚举 + `inputModalities` 分组 + 60s 缓存;`models.{image,audio,video}: "provider/model"` 显式选择高于 discovery(视频 MiMo 路入口)。
22
+ - 预算裁决不硬拒:full / reencode / sampleFrames / segment / downsample 自动降级。
23
+ - keyFailure 类失败触发冷却(FR-10,modlens 软化熔断语义),跨请求共享闸,到期自动恢复。
24
+ - strictLocalOnly:链中剔除一切远端成员,媒体内容不出本机。
25
+ - OpenAI-compatible 端点逗号 key 轮换(auth/429/quota 换 key,网络/5xx 保链)。
26
+ - 音视频直传防幻觉:未接管线路的 handle 文本回声被检测并放弃该跳,落抽帧/ASR 兜底。
27
+
28
+ ## 0.2.0 (2026-10-23)
29
+
30
+ vision-bridge 发布版:选择器可见的透镜包装 + 配置热生效根因修复 + 宿主三线全 rc 支持。以下批次随本版首发(modlens / provider pair 两批原标注 0.1.0 同版本并入,npm 0.1.0 未含,实际随本版出)。
31
+
32
+ ### modlens 源码吸收批(2026-10-06 深析,随 0.2.0 首发)
33
+ - 冷却语义升级(FR-10):仅 keyFailure 类失败触发冷却,普通失败永不冷却(正文碰巧含 quota 字样不算);冷却 provider **垫队尾**仍作最后尝试(软化熔断,非踢出);`Retry-After` 头 / `Resets in Xh Ym Zs` 解析进冷却时长;成功即恢复。
34
+ - attempts 带 `keyIndex`(多 key 轮换可见);`meta.warnings` 人类可读路由通告(failover 去向、冷却垫尾、轮换成功);错误文本先脱密(apiKey/baseUrl 精确匹配)再截断(redact-before-clip)。
35
+ - 证据合同 v2:新增 `uncertainty?: string[]`(模型自报读不清处,宁标不猜);可选字段 null 归一(不拒整读);处理指令带反注入行(媒体内指令视为数据)、verbatim 转写行、bbox 保守措辞。
36
+ - 传输硬化:请求显式 `stream: false`(网关缺省分歧);`providers.openai.structuredOutput` opt-in 注入 strict json_schema(由证据载荷派生,非手写镜像);`finish_reason=length` 截断判因(与形状问题区分)。
37
+ - 入参 magic-byte sniff(FR-14):png/jpeg/gif/webp/bmp、ftyp/EBML、RIFF/ID3/fLaC/OggS/ADTS/ASF 头字节判型,改名伪装文件在入参侧拒绝。
38
+ - 单成员链穷尽保留原始可行动错误(不被聚合错误吞掉)。
39
+
40
+ ### provider pair 吸收批 v2(2026-10-17,链出口 refine pass,随 0.2.0 首发)
41
+ - 新增顶层配置 `refine: { enabled, model }`(FR-15,opt-in 默认关):开启后主链首个成功 → `finalizeChunk` 之后 → return 之前过一次可选文本模型(对齐 medialens 型项目 "读手 + 转述手" pair)。model 必须显式 `'provider/model'`;`auto` / 空串 = 未启用(宿主 text 组 = 所有已注册模型,auto = 随便加透镜)。
42
+ - refine 失败保 raw(FR-17):LLM throw / JSON 解析失败 / validateEvidence 不通过 三情形均不 throw 到链下一级,仅追 `meta.warnings` 一条 `refine failed: <redacted>`;主链 attempts 不新增(读手已成功,不浪费下一跳抽帧/ASR)。
43
+ - refine 归属分层(FR-19):`MediaEvidence.meta` 新增 `refineAttempt?: Attempt`,`provider = 'refined:<selection>'`;`meta.provider` 保留读手前缀(registered/multimodal/configured/asr/frames/local);source/attempts/generatedAt/degraded 不被 refine 修改。
44
+ - 指令硬化(FR-18):`EVIDENCE_REFINE_INSTRUCTION` 与读手 EVIDENCE_INSTRUCTION 同四要素(反注入/verbatim/bbox 保守/uncertainty),另加「保留原 source 与 attempts,不新增媒体路径信息」一行——防洗证据(raw evidence 内媒体指令会二次进入 LLM 上下文)。
45
+ - local 兑底(FR-19):`meta.provider === 'local:metadata'` 或 `meta.degraded === 'local-metadata-only'` 时自动跳过 refine,无文本可精炼;守 strictLocalOnly 用户的隐私默认(“关了远端却被 refine 拉回远端”不允许)。
46
+ - 传输硬化(FR-19):错误路径先 `redactSecrets` 后截断(对齐 M23);`structuredOutput` 开关共享,ctx.llm.stream 不接 response_format(宿主 GenerateOptions 无该字段),指令层 STRICT JSON 兑住。
47
+ - 新文件 `src/refine.ts`(导出 `refineEvidence` + `EVIDENCE_REFINE_INSTRUCTION`);`router/failover.ts` `ChainRunOptions` 新可选 `refine` hook;工具层从 `LensDeps.refineFn` 取闭包传入 runChain(hook 抛异常时 failover 兑底,仍保 raw)。
48
+
49
+ ### refine-source-gate 吸收批 v3(2026-10-21,转述手来源闸门,随 0.2.0 首发)
50
+ - **来源闸门(转述手只认用户自配端点)**:refine 传输从 `ctx.llm.stream`(宿主 runtime 路由表,无法区分宿主原生注册与插件注入模型)改为 **configured 直连**(`callVlWithRotation`,够不到任何注入面)。新增 `isTrustedRefineProvider(endpoint, provider)`:provider 名白名单(`openai`)+ 凭据存在性(baseUrl+apiKey 非空)双判据(`src/config.ts`)。
51
+ - **静默跳过语义**:selection 解析出 `registered:` / `multimodal:` / 未知 provider,或 configured 凭据未配齐 → 原样返回 evidence,不写 `refineAttempt`、不追 warning(守 M28「关时逐字段一致」,与 llm 缺失/非显式 selection 现有跳过路径同构)。
52
+ - **归一与单源**:裸 model(无 slash)归一为 configured 白名单 `openai`(S1);请求 model 取 `providers.openai.model` 单源,不采纳 selection 的 model 段;`refineAttempt.provider` 统一 `refined:openai/<model>`,`meta.provider` 仍留读手前缀。
53
+ - **传输硬化继承**:直连后 `structuredOutput` 真正注入 `response_format`(顺带修复 v2 中 ctx.llm.stream 注不进去的已知缺陷);多 key 轮换 keyIndex 透传 `refineAttempt`;错误先脱密后截断不变。
54
+ - 工程:`tests/refine.test.ts` 改写为 fetch mock 直连断言(18 例:G1 真值表 / 闸门静默跳过 / 直连 URL 与请求体 / structuredOutput 两态 / 失败保 raw / runChain 集成),全量 105 vitest 用例绿 + lint 零错 + `check:line OK`。
55
+
56
+ ### vision-bridge 吸收批 v4(2026-10-22,选择器可见的透镜包装)
57
+ - **包装 provider 注入**:新增配置 `inject: { enabled, families }`(默认关;families 默认 `['deepseek','glm','mimo']`,`'*'` = 全部)。开启后经宿主 `llm.registerAdapter` 注册包装路由 `media-lens-vision-<upstream>`,模型选择器出现 `<模型名> (media-lens vision)` 条目(provider 组名 `<上游名> (media-lens)`);选中它作主对话模型,请求中的媒体先经透镜转证据文本再委托真实路由。
58
+ - **包装判据(用户裁定)= 透镜能补模态就包,omni 坚决不包**:纯文本模型 → 图像+音视频全部证据化;图像模型(声明 image、非 omni)→ 图像块原样透传(保留原生视觉),仅音视频证据化;omni(MiMo bare 非 `-pro` 段名字启发式)与 vision 专精词表(deepseek-vl / glm-4v / `*vision*` / glm-5.3-flash 等)拒包,`resolveModel` 响亮拒绝并指路原路由。
59
+ - **请求改写管线(0.2.0-rc.2 投影时序实锚)**:宿主在 adapter 收到请求前把 file 块无条件投影成确定性 handle 文本(按 `content.ts:151-158` 精确格式识别、提取只读盘上路径)→ `guardMediaPath`+`decideBudget`+`runChain` 全链 → 证据文本(反注入头 + `evidenceRender` 体)原位替换;image 块按上游实际模态分流。消息改写 copy-on-write(宿主 deep-frozen 不原地改),assistant 消息 `source.provider` 线副本回标上游(保 replay-state / forAdapter 身份检查,durable log 保留 wrapper id)。
60
+ - **改写永不 throw**:读手失败按阶段落常量占位文本(线字节稳定,保 provider prefix cache);证据缓存按块稳定键(`attachmentId` / 盘上路径),成功永久、失败 60s 后重试、LRU 256。
61
+ - **生命周期纪律**(modlens dsh/index.js 实证 + 本仓 C2 软读):`registerAdapter` 结构化探测(缺失即纯工具插件静默降级);`llm/adapters-updated` 事件驱动重扫(覆盖 llm-pi-ai 类迟注册路由),sweep 串行化防自触发风暴;`DUPLICATE_ADAPTER` 视为让位成功;注册句柄挂 `ctx.effect` 随 fiber 释放;上游消失/不再合格 → wrapper 回收;上游展示名变更 → `handle.replace` 原子重照快照;`imageRequestPricing` 显式 `undefined`(宿主 ≥0.1.2 无条件调用,纯对象必须自带)。
62
+ - **防递归(必须)**:discovery 快照 `filterDiscoverySnapshot` 剔除 `media-lens-vision-*` —— wrapper 注册后自己也在宿主注册表里,不剔除则读手 auto 会选中自己的 wrapper 无限递归;工具链 / models-route 候选 / 显式选择共用同一快照,一处过滤全覆盖。
63
+ - **信号穿透**:`Provider.run(req, signal?)` + `ChainRunOptions.signal` 全链下传(providers 七处 + frames/reencode 内层透传),桥的改写随宿主请求中止;工具层不传 signal 行为不变。
64
+ - **客户端**:设置卡新增「视觉桥」节(enabled 勾选 + families 逗号分隔输入,中英逗号皆收);9 语 locale;configForms mutate/set 六路径提交。
65
+ - 工程:`tests/inject.test.ts` 23 例(判据真值表 / handle 精确识别 / 改写 copy-on-write 与回标与透传分流 / 缓存成功永久失败重试 LRU / sweep 注册刷新回收 / 桥端到端 读手→证据→委托→缓存命中 / 失败常量占位 / discovery 自排除);全量 128 vitest 用例绿 + typecheck/lint 零错。
66
+
67
+ ### volatile 活读修复批 v4.1(2026-10-22 深夜,配置热生效根因修复)
68
+ - **根因(0.2.0-rc.2 宿主源码实锚)**:宿主对 `.volatile()` 字段传给插件的是带 `.get()` 的活引用(settings/plainConfig、agent-default-model `config.provider.get()` 实证),且写入后**只发 `loader/volatile-update` 事件、不重跑 apply**(llm-pi-ai / llm-deepseek / speech-to-text 三内置插件同范式自证)。media-lens 原先在 apply 期把 config 平铺展开——cell 被展开成垃圾属性,**全部字段静默回落默认值**:不仅视觉桥开关无效,处理端模型 / 端点 / refine 等所有用户配置宿主半区从未读到(v4 之前即存在的隐性缺陷,v4 的选择器开关使其显形)。
69
+ - **修复**:`resolveConfig` 入口 `detachConfigCells` 递归取直(duck-type `.get()`,照 models-route 软读纪律,不引 cosmokit);apply 内 `resolved` 改为可整体重算的 `let`,全部闭包(vlConfig / selectionFor / strictLocalOnly / refineFn / bridge families+enabled)调用期解引用即热生效;订阅 `loader/volatile-update`(duck-type)→ 重算 resolved + `bridge.sweep()`(桥按新 enabled 注册/回收包装路由)。已知留置:cooldownMs 变更仍需重载(CooldownGate 构造期取值,重建会丢冷却状态,权衡不改)。
70
+ - 工程:`tests/inject.test.ts` 增 3 例(cell 形态 resolveConfig 取直 / 活值重算 / 普通对象照旧),全量 131 vitest 用例绿 + typecheck/lint 零错。
71
+
72
+ ### 宿主三线全 rc 枚举批(2026-10-23,用户裁定口径)
73
+ - **口径变更(用户裁定)**:engines.dsh / peers 枚举从「每线一个 farm 保留 rc」改为 **0.1.7 / 0.2.0 / 0.2.1 三线全 rc 枚举**(`0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2`,alpha/beta 不入列)。新增 `0.1.7-rc.1` 与 `0.2.0-rc.1` 两格。
74
+ - **契约面核实(tarball 逐字 diff,钉 npmjs)**:`0.2.0-rc.1` llm 类型与 rc.2 **零差异**;`0.1.7-rc.1` attachment 全类型零差异、llm 仅缺本插件不消费的 `toolHistory`/`toolUpdate` 可选面,`registerAdapter` / `llm/adapters-updated` 在位(vision-bridge 在该线可用)。六宿主子包 × 两 rc 共 12 项 npm 在位逐一实查。
75
+ - `hosts.mjs` 口径段重写(声明枚举与 farm 实测账本 `.compat-results/verified.json` 分属两档,后者不动),`sync-hosts --write` 三写点下发。
76
+ - 全门禁:131 vitest 用例绿 + typecheck/lint 零错。
77
+
78
+ ### 宿主 0.2.1-rc.2 支持批(2026-10-23,engines 枚举补格 + wrapper 直调面补齐)
79
+ - **背景**:desktop 宿主 manifest 推荐 rc 已是 0.2.1-rc.2;engines.dsh 自 0.2.1-alpha.4 起是独立且优先的拒载判据,枚举不含 rc.2 则宿主升级即拒载本插件。
80
+ - **契约面核实(tarball 逐字 diff,0.2.1-rc.2 vs 0.2.0-rc.2)**:`dsh-attachment` 全类型文件零差异;`dsh-llm` message/content/retry-policy 零差异,index/types 仅**可选**新增(adapter 面 `imageRequestTarget`、runtime 面 `prepareCall(config, signal?, configure?)`——adapter 面签名不变、directory 侧 `LlmConfigurableProvider` 三可选字段)。六宿主子包 npm 全在位(钉 npmjs 实查)。
81
+ - **修复**:wrapper adapter 补 `imageRequestTarget`(0.2.1-rc.1 起新增,runtime **直调无 optional chaining**,`lib/index.js:2064` 实锚——纯对象缺了在图像投影时 TypeError;照基类缺省语义原样保留归一化图像 `{width,height,maxBytes}`)。`hosts.mjs` 枚举加 `0.2.1-rc.2`,`sync-hosts --write` 下发 package.json / dsh.plugin.json / README 三写点。
82
+ - **verified.json 不动**:rc.2 未过 farm 逐格实测,仅静态 diff + 冒烟级核实(诚实档位);farm 补测后另行入账。
83
+ - 工程:sweep 测试增 adapter 直调面断言(pricing undefined / target 保留语义),全量 131 vitest 用例绿 + typecheck/lint 零错。
84
+
85
+ ### 工程
86
+ - 宿主 service 全部 `ctx.get()` 软读优雅降级(C2);运行时依赖仅 schemastery。
87
+ - 131 vitest 用例全绿(含 `tests/inject.test.ts` 26 例覆盖 vision-bridge 与 volatile 取直);`check:line` 门禁保证 lib/ 与 src/ 逐字节对齐(C3)。
88
+ - 宿主线钉三线全 rc:`0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2`(engines.dsh / peerDependencies / dsh.plugin.json / README 四写点由 sync-hosts 单源下发;publishConfig.tag = dsh-0.2.0)。
89
+
90
+ ## 0.2.1 (2026-10-23)
91
+
92
+ ### Changed
93
+ - **声明配合姊妹插件**:将两个配合插件加入 `peerDependencies` 并标 `optional`(`dsh-thinking-levels >=4.4.4`、`dsh-llm-openai-completions >=0.4.4`)——多模态模型注册/认证与音视频直传靠它们接管(thinking-levels 管模型配置录入/接管登记,openai-completions 做 OpenAI-compatible 传输接管将 file 块序列化成 video_url/input_audio)。二者均为 optional peer,不装则相应能力自动降级不报错,不触发 npm 强制拉装、不与宿主版本闸门冲突(两包 latest engines.dsh 均已含 0.2.1-rc.2)。README 新增「配合插件」小节。
94
+
package/README.md CHANGED
@@ -1,165 +1,165 @@
1
- # dsh-media-lens
2
-
3
- 媒体感知插件:把本地 video / audio / image 变成**结构化证据**(时间线 / 转写 / 版面区域)注入对话,让文本模型引用具体帧、秒、说话人 —— **Evidence, not imagination**。同时内置 provider 路由线:每个媒体一条 failover 链,预算超限自动降级,每次尝试记录在 `meta.attempts` —— **a fallback is never silent**。
4
-
5
- ```
6
- media_read_video / media_read_audio / media_read_image
7
- → 路径守卫(存在性 + 扩展名白名单)
8
- → ffprobe 探测 + 预算裁决(full / reencode / sampleFrames / segment / downsample)
9
- → failover 链(首个成功即返回;全败抛带 attempts 的结构化错误)
10
- → MediaEvidence JSON(timeline / transcript / regions + meta.attempts)
11
- ```
12
-
1
+ # dsh-media-lens
2
+
3
+ 媒体感知插件:把本地 video / audio / image 变成**结构化证据**(时间线 / 转写 / 版面区域)注入对话,让文本模型引用具体帧、秒、说话人 —— **Evidence, not imagination**。同时内置 provider 路由线:每个媒体一条 failover 链,预算超限自动降级,每次尝试记录在 `meta.attempts` —— **a fallback is never silent**。
4
+
5
+ ```
6
+ media_read_video / media_read_audio / media_read_image
7
+ → 路径守卫(存在性 + 扩展名白名单)
8
+ → ffprobe 探测 + 预算裁决(full / reencode / sampleFrames / segment / downsample)
9
+ → failover 链(首个成功即返回;全败抛带 attempts 的结构化错误)
10
+ → MediaEvidence JSON(timeline / transcript / regions + meta.attempts)
11
+ ```
12
+
13
13
  <!-- host-compat:begin — generated by scripts/sync-hosts.mjs from scripts/hosts.mjs; DO NOT EDIT -->
14
- - **Supported DSH hosts:** `0.1.7-rc.1` `0.1.7-rc.2` `0.2.0-rc.1` `0.2.0-rc.2` `0.2.1-rc.1` `0.2.1-rc.2` `0.2.1-rc.3` (7 rc across lines 0.1.7 / 0.2.0 / 0.2.1)
14
+ - **Supported DSH hosts:** `0.1.7-rc.1` `0.1.7-rc.2` `0.2.0-rc.1` `0.2.0-rc.2` `0.2.1-rc.1` `0.2.1-rc.2` `0.2.1-rc.3` `0.2.1-rc.4` `0.2.1-rc.5` `0.2.2-rc.1` (10 rc across lines 0.1.7 / 0.2.0 / 0.2.1)
15
15
  - **Runtime-verified:** `0.1.7-rc.2`, `0.2.0-rc.2`, `0.2.1-rc.1` — evidence in `.compat-results/`
16
- <!-- host-compat:end -->
17
-
18
- ## 链序(每媒体)
19
-
20
- | 媒体 | 链 |
21
- | --- | --- |
22
- | video(≤10MB 且 ≤10min) | `multimodal`(注册全模态直传)→ `frames:`(ffmpeg 均匀抽帧 + 图片链)→ `local:metadata` |
23
- | video(10–19MB) | `reencode:multimodal`(540p x264 压缩后直传)→ 同上 |
24
- | video(>19MB 或 >10min) | 直接 `frames:` 抽帧路 |
25
- | audio | `multimodal`(全模态直传转写)→ `asr:endpoint-N`(本地 ASR CLI,多端点按序)→ `local:metadata` |
26
- | image | `registered:`(宿主已注册视觉模型)→ `configured:openai`(OpenAI-compatible 端点)→ `local:metadata` |
27
-
28
- ## 处理端来源(FR-9:已注册模型优先)
29
-
30
- - **宿主已注册模型**:插件经 `ctx.llm.listProviders()/listModels()` 枚举(60s 缓存),按 `inputModalities` 分组构成首选链;图片原生 image 块直送。
31
- - **显式选择**:配置 `models.{image,audio,video}: "provider/model"` 直接指定一个已注册模型 —— 视频的 MiMo 类全模态路就走这里(宿主模态词表当前只认 `text|image`,自动发现不了视频/音频声明,显式选择是唯一入口;实际承载由适配器(如 openai-completions 接管线)的模态硬闸把关,拒绝是响亮的)。
32
- - **自配端点**:`providers.openai.{baseUrl, apiKey, model}`,apiKey 支持逗号分隔多 key(auth/429/quota 换下一个 key;网络/5xx 跳过保链)。
33
- - **本地兜底**:strictLocalOnly 或全链失败时,`local:metadata` 只出 probe 元数据(时长/分辨率/大小),`degraded='local-metadata-only'`。
34
-
35
- ## 配置
36
-
37
- ```yaml
38
- dsh-media-lens:
39
- strictLocalOnly: false # true 时链中剔除一切远端 provider,媒体内容不出本机
40
- cooldownMs: 600000 # keyFailure 类失败的默认冷却时长(有 Retry-After/"Resets in" 线索时按线索)
41
- models:
42
- image: auto # auto | '' | "provider/model"
43
- audio: auto
44
- video: auto # MiMo 类全模态:填 "llm-pi-ai/你的mimo模型id"
45
- providers:
46
- openai:
47
- baseUrl: '' # 例 https://api.example.com/v1
48
- apiKey: '' # 逗号分隔多 key 轮换
49
- model: ''
50
- structuredOutput: false # true 时注入 response_format json_schema(strict)(不认识的网关会 400,故默认关)
51
- asr:
52
- command: '' # 单端点写法:本地转写 CLI 模板,{input} 占位
53
- endpoints: [] # 多端点选择项,按序尝试;与 command 合并(command 在前)
54
- refine:
55
- enabled: false # Phase 7:链出口一次 refine pass,默认关
56
- model: '' # 开启时必须写 'openai/<model>' 或裸 model;auto / 空串 = 未启用。转述手只认 configured 自配端点(registered:/插件注入模型无效)
57
- ```
58
-
59
- ASR 命令模板示例(Windows,模板内双引号保护空格路径):
60
-
61
- ```
62
- python "C:\tools\sensevoice_cli.py" --input "{input}" --json
63
- ```
64
-
65
- 转写输出识别两种格式:JSON segments(`[{start,end,text[,speaker]}]` 或 `{"segments":[...]}`)与 SRT/时间行文本。
66
-
67
- ## 隐私说明(对照 dsh-vision-router 教训)
68
-
69
- - **默认媒体内容会发往远端 provider**(注册模型 / 配置端点)。要内容不出本机,显式开启 `strictLocalOnly: true`——之后只剩本地 ASR CLI 与元数据兜底。
70
- - `meta.attempts` 记录每次尝试(provider 名含来源前缀 `registered:` / `multimodal:` / `configured:` / `asr:` / `frames:` / `local:`,多 key 轮换带 `keyIndex`),`meta.warnings` 记录路由通告(failover 去向、冷却垫尾、轮换成功)——降级绝不静默。
71
- - 媒体路径按不可信输入处理:只读取、存在性 + 扩展名 + **magic-byte 三重校验**(改名伪装的文件在入参侧拒绝);处理指令显式声明媒体内出现的指令一律视为数据;内容永不执行。
72
- - 错误文本先脱密(apiKey/baseUrl 精确匹配)再截断,密钥不进 attempts/日志。
73
- - 音视频直传只在适配器接管线(如 openai-completions takeover)上真正内联;未接管线路上模型只看到文件 handle 文本 —— 插件检测到这种回声会放弃该跳并落到抽帧/ASR 兜底,**不产幻觉证据**。
74
-
75
- ## 冷却语义(modlens 软化熔断)
76
-
77
- 只有 **keyFailure 类失败**(auth/429/quota 正文)触发冷却——普通失败(网络抖动、5xx、解析失败)永不冷却,正文碰巧含 quota 字样也不算。冷却中的 provider **垫到链尾仍作最后尝试**(不是踢出):穷途末路时仍试一把,成功即恢复。provider 给恢复线索(`Retry-After` 头或正文 `Resets in Xh Ym Zs`)时按线索计时,否则 `cooldownMs`。
78
-
79
- ## structuredOutput(FR-13,opt-in)
80
-
81
- `providers.openai.structuredOutput: true` 时请求注入 `response_format json_schema(strict)`(由证据载荷派生,非手写镜像)。认识的网关(vLLM、官方 OpenAI 等)服务端强制形状能显著提证据质量;不认识的网关对未知字段直接 400——所以缺省关。
82
-
83
- ## refine pass(Phase 7,链出口一次,opt-in)
84
-
85
- 主链首个成功后、返回前可选地再过一次**文本模型**将证据精炼规范化(去重 / 紧措辞 / 保留不确定性)——对齐 medialens 型项目常见的 "visionModel 读手 + textModel 转述手" pair 设计。
86
-
87
- ```yaml
88
- refine:
89
- enabled: true
90
- model: "openai/your-text-model" # 或裸 model;auto / 空串 = 未启用
91
- ```
92
-
93
- 默认关,理由:
94
-
95
- - **隐私**:多一跳远端 = 多一次媒体内容(raw evidence)暴露面;`strictLocalOnly` 下链仅到 `local:metadata`,此时 refine 自动跳过(无文本可精炼),不会拉回远端。
96
- - **延迟**:直出证据已可用,refine 是锦上添花。
97
- - **不作 auto**:auto = 随便把所有模型都加透镜,不允许。
98
-
99
- **来源闸门(转述手只认用户自配端点)**:refine 这一跳只允许 **configured 端点**(`providers.openai` 配了 `baseUrl` + `apiKey`)——`model` 必须写 `openai/<model>` 或裸 model,实际请求 model 取 `providers.openai.model` 单源。**`registered:` / `multimodal:`(宿主 discovery 枚举,可能含其它插件注入的模型)一律不可信**:命中即静默跳过(不写 `refineAttempt`、不追 warning,行为与未启用一致)。传输走 configured 直连(不经宿主 runtime 路由表),`structuredOutput` 开启时注入 `response_format`。
100
-
101
- 失败行为(**不静默**,但**不撤销已成**):refine 抛错 / 输出无 JSON / 校验不通过 → **保留 raw evidence**,`meta.warnings` 追加 `refine failed: <redacted>`,`meta.refineAttempt.ok = false`;主链 attempts 不新增(读手已成功,不浪费下一跳抽帧/ASR)。成功时 refine 归属写 `meta.refineAttempt.provider = 'refined:openai/<model>'`,`meta.provider` 保留读手前缀(registered / multimodal / configured / asr / frames / local);`source` / `meta.attempts` / `meta.generatedAt` 不被 refine 修改。
102
-
103
- 传输硬化:`EVIDENCE_REFINE_INSTRUCTION` 四要素与读手指令同源(反注入 / verbatim / bbox 保守 / uncertainty),另加「保留原 source 与 attempts」一行——媒体内出现的指令会随 raw evidence 二次进入 LLM 上下文,反注入行必须再吃一遍,防洗证据。错误文本先脱密后截断。
104
-
105
- ## vision-bridge(v4,选择器可见的透镜包装,opt-in)
106
-
107
- 把透镜包装成宿主模型选择器里**可见的路由**:经宿主 `llm.registerAdapter` 注册 `media-lens-vision-<upstream>` 包装 provider,选择器出现 `<模型名> (media-lens vision)` 条目。选中它作主对话模型后,请求中的媒体在发送前经 media-lens 链(读手 = 上文「处理端模型」配置)转成证据文本,再委托真实路由——纯文本主力模型由此获得图片与音视频 sight("让 deepseek 收到小米读出的证据")。
108
-
109
- ```yaml
110
- inject:
111
- enabled: true
112
- families: ["deepseek", "glm", "mimo"] # 前缀过滤;"*" = 全部(仍受否决)
113
- ```
114
-
115
- **包装判据 = 透镜能补模态就包,omni 坚决不包**:
116
-
117
- - 纯文本模型(deepseek-v4.1 类)→ 包:图像 + 音视频全部证据化;
118
- - 图像模型(声明 image、非 omni)→ 包:**图像块原样透传**(保留原生视觉),仅音视频证据化;
119
- - omni 宽模态(MiMo bare 非 `-pro` 段,名字启发式)与 vision 专精词表(`deepseek-vl` / `glm-4v` / `*vision*` 等)→ 拒包,`resolveModel` 响亮拒绝并指路原路由。
120
-
121
- 机制要点(0.2.0-rc.2 实锚):宿主在 adapter 前已把 file 块投影成确定性 handle 文本,桥按 `content.ts` 精确格式识别并提取只读路径走全链;改写永不 throw(读手失败落常量占位文本,请求继续);证据按块缓存(重放零重读);`llm/adapters-updated` 事件驱动重扫,迟注册路由(llm-pi-ai)自动跟进;discovery 快照剔除 `media-lens-vision-*`(读手永不选中自己的 wrapper)。宿主缺 `registerAdapter` 面时静默保持纯工具形态。
122
-
123
- ## 安装
124
-
125
- ```powershell
126
- # 宿主 0.2.0-rc.1 / 0.2.0-rc.2(engines.dsh 与 peerDependencies 对齐)
127
- pnpm add ./dsh-media-lens --ignore-workspace-root-check # 或 npm pack 后装 tgz
128
- ```
129
-
130
- `cordis.patch.yml` 提供 insert 注册行(`dsh.bundle.patch` 已在 package.json 对齐)。装完重启宿主。
131
-
132
- ## 依赖面
133
-
134
- - 运行时依赖仅 `@deepseek-ai/schemastery`(Config schema);`@deepseek-ai/dsh-tools`(defineTool)由宿主运行时提供(peer)。
135
- - ffmpeg / ffprobe 在 PATH 上可用时启用抽帧/重编码/时长探测;缺失时元数据降级(记录在 attempts)。
136
- - Node 原生 fetch(≥22.19)。
137
-
138
- ## 配合插件(可选 peer,非硬依赖)
139
-
140
- media-lens 本身提供「工具处理多模态对象」(media_read_video/audio/image)。要把**多模态模型注册/认证进宿主**并让**音视频直传**真正内联(而非只看到文件 handle 文本),需两个姊妹插件配合——二者声明为 `optional` peer,不装则相应能力自动降级,不报错:
141
-
142
- - **dsh-thinking-levels**:模型配置录入 / 认证与 reasoning_effort 管理面——音视频直传链路的模型由此登记。
143
- - **dsh-llm-openai-completions**:OpenAI-compatible 传输接管(takeover)——把 file 块序列化成 video_url / input_audio 真正上送,未接管线路宿主只投影 handle 文本,插件检测到即落抽帧/ASR 兜底。
144
-
145
- 装机走宿主 profile(autoInstallPeers:false + hoisted),姊妹插件由 bundles 各自注册,optional peer 不触发 npm 拉装、不与宿主版本闸门冲突。
146
-
147
- ## 家族节与 tab 权重(bundle + 家族子 tab)
148
-
149
- 本插件为 host+client 双半 bundle(`dsh.plugin.json.client.main` 载 client 半身)。客户端半身把设置卡贡献进 `dsh-family` 家族节(顶级节 order 40、label「起子插件设置」,与本插件无关、由家族所有者统一渲染):
150
-
151
- | 注册面 | 槽 | 权重(order) | 语义 |
152
- | --- | --- | --- | --- |
153
- | 家族子 tab 贡献卡(主路) | `dsh-family.tab` | **65**(排现存贡献卡之后,靠后) | lazy:等家族所有者注入渲染;贡献卡不自渲染节体 |
154
- | 独立兜底节 | `settings.section` | 24 | **无 injection 时自注册**:家族槽从未被渲染(无所有者/缺席)时,本卡以独立节出现,保留可见性 |
155
- | bundle 详情卡 | `plugins.bundle.config` | —(按包名 keyed) | 0.1.7+ 插件管理器详情设置卡 |
156
-
157
- lazy 机制(两路注入均在 apply 期顶层注册,工厂只在「声明事件=设置面板打开」fire,fire 序=注册序):家族贡献卡先注册并在其工厂 fire 时置 `familySeated`;独立节工厂读到该标记即退场(避免与家族 tab 重复展示),读不到才自注册独立节。**禁止宽限期后注册**(§3 反例)。写路径:条目配置各顶层字段标 `.volatile()`,client 经 `configForms.get(entryId).set(field, value)` 权威写 + 热生效(0.1.7+/0.2.0/0.2.1 唯一活写面;`settings.register` 在 0.1.7+ 已移除,webServer 桥不可作写路径)。
158
-
159
- ## 边界(YAGNI)
160
-
161
- 有 host+client 双半身与设置卡(家族子 tab / 独立节 / bundle 卡,见上节),但除此之外无对话内卡片、无客户端粘贴拦截(vision-bridge 的请求改写发生在宿主 adapter 面,非 UI 拦截)、无 TTS / 媒体编辑、不内置任何 key。更多 UI 需求出现时另立计划。
162
-
163
- ## 版本线
164
-
165
- `0.1.0`(engines.dsh `0.2.0-rc.1 || 0.2.0-rc.2`)。发布走 npm staged 人工审批;dist-tag 分层 `dsh-0.2.0`(对齐 date-wrapper 先例),latest 收敛在发版时统一处理。
16
+ <!-- host-compat:end -->
17
+
18
+ ## 链序(每媒体)
19
+
20
+ | 媒体 | 链 |
21
+ | --- | --- |
22
+ | video(≤10MB 且 ≤10min) | `multimodal`(注册全模态直传)→ `frames:`(ffmpeg 均匀抽帧 + 图片链)→ `local:metadata` |
23
+ | video(10–19MB) | `reencode:multimodal`(540p x264 压缩后直传)→ 同上 |
24
+ | video(>19MB 或 >10min) | 直接 `frames:` 抽帧路 |
25
+ | audio | `multimodal`(全模态直传转写)→ `asr:endpoint-N`(本地 ASR CLI,多端点按序)→ `local:metadata` |
26
+ | image | `registered:`(宿主已注册视觉模型)→ `configured:openai`(OpenAI-compatible 端点)→ `local:metadata` |
27
+
28
+ ## 处理端来源(FR-9:已注册模型优先)
29
+
30
+ - **宿主已注册模型**:插件经 `ctx.llm.listProviders()/listModels()` 枚举(60s 缓存),按 `inputModalities` 分组构成首选链;图片原生 image 块直送。
31
+ - **显式选择**:配置 `models.{image,audio,video}: "provider/model"` 直接指定一个已注册模型 —— 视频的 MiMo 类全模态路就走这里(宿主模态词表当前只认 `text|image`,自动发现不了视频/音频声明,显式选择是唯一入口;实际承载由适配器(如 openai-completions 接管线)的模态硬闸把关,拒绝是响亮的)。
32
+ - **自配端点**:`providers.openai.{baseUrl, apiKey, model}`,apiKey 支持逗号分隔多 key(auth/429/quota 换下一个 key;网络/5xx 跳过保链)。
33
+ - **本地兜底**:strictLocalOnly 或全链失败时,`local:metadata` 只出 probe 元数据(时长/分辨率/大小),`degraded='local-metadata-only'`。
34
+
35
+ ## 配置
36
+
37
+ ```yaml
38
+ dsh-media-lens:
39
+ strictLocalOnly: false # true 时链中剔除一切远端 provider,媒体内容不出本机
40
+ cooldownMs: 600000 # keyFailure 类失败的默认冷却时长(有 Retry-After/"Resets in" 线索时按线索)
41
+ models:
42
+ image: auto # auto | '' | "provider/model"
43
+ audio: auto
44
+ video: auto # MiMo 类全模态:填 "llm-pi-ai/你的mimo模型id"
45
+ providers:
46
+ openai:
47
+ baseUrl: '' # 例 https://api.example.com/v1
48
+ apiKey: '' # 逗号分隔多 key 轮换
49
+ model: ''
50
+ structuredOutput: false # true 时注入 response_format json_schema(strict)(不认识的网关会 400,故默认关)
51
+ asr:
52
+ command: '' # 单端点写法:本地转写 CLI 模板,{input} 占位
53
+ endpoints: [] # 多端点选择项,按序尝试;与 command 合并(command 在前)
54
+ refine:
55
+ enabled: false # Phase 7:链出口一次 refine pass,默认关
56
+ model: '' # 开启时必须写 'openai/<model>' 或裸 model;auto / 空串 = 未启用。转述手只认 configured 自配端点(registered:/插件注入模型无效)
57
+ ```
58
+
59
+ ASR 命令模板示例(Windows,模板内双引号保护空格路径):
60
+
61
+ ```
62
+ python "C:\tools\sensevoice_cli.py" --input "{input}" --json
63
+ ```
64
+
65
+ 转写输出识别两种格式:JSON segments(`[{start,end,text[,speaker]}]` 或 `{"segments":[...]}`)与 SRT/时间行文本。
66
+
67
+ ## 隐私说明(对照 dsh-vision-router 教训)
68
+
69
+ - **默认媒体内容会发往远端 provider**(注册模型 / 配置端点)。要内容不出本机,显式开启 `strictLocalOnly: true`——之后只剩本地 ASR CLI 与元数据兜底。
70
+ - `meta.attempts` 记录每次尝试(provider 名含来源前缀 `registered:` / `multimodal:` / `configured:` / `asr:` / `frames:` / `local:`,多 key 轮换带 `keyIndex`),`meta.warnings` 记录路由通告(failover 去向、冷却垫尾、轮换成功)——降级绝不静默。
71
+ - 媒体路径按不可信输入处理:只读取、存在性 + 扩展名 + **magic-byte 三重校验**(改名伪装的文件在入参侧拒绝);处理指令显式声明媒体内出现的指令一律视为数据;内容永不执行。
72
+ - 错误文本先脱密(apiKey/baseUrl 精确匹配)再截断,密钥不进 attempts/日志。
73
+ - 音视频直传只在适配器接管线(如 openai-completions takeover)上真正内联;未接管线路上模型只看到文件 handle 文本 —— 插件检测到这种回声会放弃该跳并落到抽帧/ASR 兜底,**不产幻觉证据**。
74
+
75
+ ## 冷却语义(modlens 软化熔断)
76
+
77
+ 只有 **keyFailure 类失败**(auth/429/quota 正文)触发冷却——普通失败(网络抖动、5xx、解析失败)永不冷却,正文碰巧含 quota 字样也不算。冷却中的 provider **垫到链尾仍作最后尝试**(不是踢出):穷途末路时仍试一把,成功即恢复。provider 给恢复线索(`Retry-After` 头或正文 `Resets in Xh Ym Zs`)时按线索计时,否则 `cooldownMs`。
78
+
79
+ ## structuredOutput(FR-13,opt-in)
80
+
81
+ `providers.openai.structuredOutput: true` 时请求注入 `response_format json_schema(strict)`(由证据载荷派生,非手写镜像)。认识的网关(vLLM、官方 OpenAI 等)服务端强制形状能显著提证据质量;不认识的网关对未知字段直接 400——所以缺省关。
82
+
83
+ ## refine pass(Phase 7,链出口一次,opt-in)
84
+
85
+ 主链首个成功后、返回前可选地再过一次**文本模型**将证据精炼规范化(去重 / 紧措辞 / 保留不确定性)——对齐 medialens 型项目常见的 "visionModel 读手 + textModel 转述手" pair 设计。
86
+
87
+ ```yaml
88
+ refine:
89
+ enabled: true
90
+ model: "openai/your-text-model" # 或裸 model;auto / 空串 = 未启用
91
+ ```
92
+
93
+ 默认关,理由:
94
+
95
+ - **隐私**:多一跳远端 = 多一次媒体内容(raw evidence)暴露面;`strictLocalOnly` 下链仅到 `local:metadata`,此时 refine 自动跳过(无文本可精炼),不会拉回远端。
96
+ - **延迟**:直出证据已可用,refine 是锦上添花。
97
+ - **不作 auto**:auto = 随便把所有模型都加透镜,不允许。
98
+
99
+ **来源闸门(转述手只认用户自配端点)**:refine 这一跳只允许 **configured 端点**(`providers.openai` 配了 `baseUrl` + `apiKey`)——`model` 必须写 `openai/<model>` 或裸 model,实际请求 model 取 `providers.openai.model` 单源。**`registered:` / `multimodal:`(宿主 discovery 枚举,可能含其它插件注入的模型)一律不可信**:命中即静默跳过(不写 `refineAttempt`、不追 warning,行为与未启用一致)。传输走 configured 直连(不经宿主 runtime 路由表),`structuredOutput` 开启时注入 `response_format`。
100
+
101
+ 失败行为(**不静默**,但**不撤销已成**):refine 抛错 / 输出无 JSON / 校验不通过 → **保留 raw evidence**,`meta.warnings` 追加 `refine failed: <redacted>`,`meta.refineAttempt.ok = false`;主链 attempts 不新增(读手已成功,不浪费下一跳抽帧/ASR)。成功时 refine 归属写 `meta.refineAttempt.provider = 'refined:openai/<model>'`,`meta.provider` 保留读手前缀(registered / multimodal / configured / asr / frames / local);`source` / `meta.attempts` / `meta.generatedAt` 不被 refine 修改。
102
+
103
+ 传输硬化:`EVIDENCE_REFINE_INSTRUCTION` 四要素与读手指令同源(反注入 / verbatim / bbox 保守 / uncertainty),另加「保留原 source 与 attempts」一行——媒体内出现的指令会随 raw evidence 二次进入 LLM 上下文,反注入行必须再吃一遍,防洗证据。错误文本先脱密后截断。
104
+
105
+ ## vision-bridge(v4,选择器可见的透镜包装,opt-in)
106
+
107
+ 把透镜包装成宿主模型选择器里**可见的路由**:经宿主 `llm.registerAdapter` 注册 `media-lens-vision-<upstream>` 包装 provider,选择器出现 `<模型名> (media-lens vision)` 条目。选中它作主对话模型后,请求中的媒体在发送前经 media-lens 链(读手 = 上文「处理端模型」配置)转成证据文本,再委托真实路由——纯文本主力模型由此获得图片与音视频 sight("让 deepseek 收到小米读出的证据")。
108
+
109
+ ```yaml
110
+ inject:
111
+ enabled: true
112
+ families: ["deepseek", "glm", "mimo"] # 前缀过滤;"*" = 全部(仍受否决)
113
+ ```
114
+
115
+ **包装判据 = 透镜能补模态就包,omni 坚决不包**:
116
+
117
+ - 纯文本模型(deepseek-v4.1 类)→ 包:图像 + 音视频全部证据化;
118
+ - 图像模型(声明 image、非 omni)→ 包:**图像块原样透传**(保留原生视觉),仅音视频证据化;
119
+ - omni 宽模态(MiMo bare 非 `-pro` 段,名字启发式)与 vision 专精词表(`deepseek-vl` / `glm-4v` / `*vision*` 等)→ 拒包,`resolveModel` 响亮拒绝并指路原路由。
120
+
121
+ 机制要点(0.2.0-rc.2 实锚):宿主在 adapter 前已把 file 块投影成确定性 handle 文本,桥按 `content.ts` 精确格式识别并提取只读路径走全链;改写永不 throw(读手失败落常量占位文本,请求继续);证据按块缓存(重放零重读);`llm/adapters-updated` 事件驱动重扫,迟注册路由(llm-pi-ai)自动跟进;discovery 快照剔除 `media-lens-vision-*`(读手永不选中自己的 wrapper)。宿主缺 `registerAdapter` 面时静默保持纯工具形态。
122
+
123
+ ## 安装
124
+
125
+ ```powershell
126
+ # 宿主 0.2.0-rc.1 / 0.2.0-rc.2(engines.dsh 与 peerDependencies 对齐)
127
+ pnpm add ./dsh-media-lens --ignore-workspace-root-check # 或 npm pack 后装 tgz
128
+ ```
129
+
130
+ `cordis.patch.yml` 提供 insert 注册行(`dsh.bundle.patch` 已在 package.json 对齐)。装完重启宿主。
131
+
132
+ ## 依赖面
133
+
134
+ - 运行时依赖仅 `@deepseek-ai/schemastery`(Config schema);`@deepseek-ai/dsh-tools`(defineTool)由宿主运行时提供(peer)。
135
+ - ffmpeg / ffprobe 在 PATH 上可用时启用抽帧/重编码/时长探测;缺失时元数据降级(记录在 attempts)。
136
+ - Node 原生 fetch(≥22.19)。
137
+
138
+ ## 配合插件(可选 peer,非硬依赖)
139
+
140
+ media-lens 本身提供「工具处理多模态对象」(media_read_video/audio/image)。要把**多模态模型注册/认证进宿主**并让**音视频直传**真正内联(而非只看到文件 handle 文本),需两个姊妹插件配合——二者声明为 `optional` peer,不装则相应能力自动降级,不报错:
141
+
142
+ - **dsh-thinking-levels**:模型配置录入 / 认证与 reasoning_effort 管理面——音视频直传链路的模型由此登记。
143
+ - **dsh-llm-openai-completions**:OpenAI-compatible 传输接管(takeover)——把 file 块序列化成 video_url / input_audio 真正上送,未接管线路宿主只投影 handle 文本,插件检测到即落抽帧/ASR 兜底。
144
+
145
+ 装机走宿主 profile(autoInstallPeers:false + hoisted),姊妹插件由 bundles 各自注册,optional peer 不触发 npm 拉装、不与宿主版本闸门冲突。
146
+
147
+ ## 家族节与 tab 权重(bundle + 家族子 tab)
148
+
149
+ 本插件为 host+client 双半 bundle(`dsh.plugin.json.client.main` 载 client 半身)。客户端半身把设置卡贡献进 `dsh-family` 家族节(顶级节 order 40、label「起子插件设置」,与本插件无关、由家族所有者统一渲染):
150
+
151
+ | 注册面 | 槽 | 权重(order) | 语义 |
152
+ | --- | --- | --- | --- |
153
+ | 家族子 tab 贡献卡(主路) | `dsh-family.tab` | **65**(排现存贡献卡之后,靠后) | lazy:等家族所有者注入渲染;贡献卡不自渲染节体 |
154
+ | 独立兜底节 | `settings.section` | 24 | **无 injection 时自注册**:家族槽从未被渲染(无所有者/缺席)时,本卡以独立节出现,保留可见性 |
155
+ | bundle 详情卡 | `plugins.bundle.config` | —(按包名 keyed) | 0.1.7+ 插件管理器详情设置卡 |
156
+
157
+ lazy 机制(两路注入均在 apply 期顶层注册,工厂只在「声明事件=设置面板打开」fire,fire 序=注册序):家族贡献卡先注册并在其工厂 fire 时置 `familySeated`;独立节工厂读到该标记即退场(避免与家族 tab 重复展示),读不到才自注册独立节。**禁止宽限期后注册**(§3 反例)。写路径:条目配置各顶层字段标 `.volatile()`,client 经 `configForms.get(entryId).set(field, value)` 权威写 + 热生效(0.1.7+/0.2.0/0.2.1 唯一活写面;`settings.register` 在 0.1.7+ 已移除,webServer 桥不可作写路径)。
158
+
159
+ ## 边界(YAGNI)
160
+
161
+ 有 host+client 双半身与设置卡(家族子 tab / 独立节 / bundle 卡,见上节),但除此之外无对话内卡片、无客户端粘贴拦截(vision-bridge 的请求改写发生在宿主 adapter 面,非 UI 拦截)、无 TTS / 媒体编辑、不内置任何 key。更多 UI 需求出现时另立计划。
162
+
163
+ ## 版本线
164
+
165
+ `0.1.0`(engines.dsh `0.2.0-rc.1 || 0.2.0-rc.2`)。发布走 npm staged 人工审批;dist-tag 分层 `dsh-0.2.0`(对齐 date-wrapper 先例),latest 收敛在发版时统一处理。
package/dsh.plugin.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-media-lens",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "main": "./lib/index.js",
5
5
  "description": "Media evidence lens + provider router for DeepSeek Harness (media_read_video / media_read_audio / media_read_image).",
6
6
  "engines": {
7
- "dsh": "0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3"
7
+ "dsh": "0.1.7-rc.1 || 0.1.7-rc.2 || 0.2.0-rc.1 || 0.2.0-rc.2 || 0.2.1-rc.1 || 0.2.1-rc.2 || 0.2.1-rc.3 || 0.2.1-rc.4 || 0.2.1-rc.5 || 0.2.2-rc.1"
8
8
  },
9
9
  "contributes": {
10
10
  "tools": [],