dsh-media-lens 0.1.0 → 0.2.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 +44 -4
- package/README.md +26 -6
- package/dsh.plugin.json +2 -2
- package/lib/client.js +112 -3
- package/lib/config.d.ts +35 -1
- package/lib/config.js +46 -2
- package/lib/discovery.d.ts +6 -0
- package/lib/discovery.js +13 -0
- package/lib/index.d.ts +17 -1
- package/lib/index.js +52 -10
- package/lib/inject.d.ts +91 -0
- package/lib/inject.js +512 -0
- package/lib/providers/asr-shell.js +1 -1
- package/lib/providers/local-file.js +1 -1
- package/lib/providers/multimodal.js +2 -2
- package/lib/providers/registered.js +2 -2
- package/lib/refine.d.ts +27 -17
- package/lib/refine.js +61 -39
- package/lib/router/failover.d.ts +2 -0
- package/lib/router/failover.js +1 -1
- package/lib/router/registry.d.ts +2 -1
- package/lib/tools/chains.js +6 -6
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
- OpenAI-compatible 端点逗号 key 轮换(auth/429/quota 换 key,网络/5xx 保链)。
|
|
20
20
|
- 音视频直传防幻觉:未接管线路的 handle 文本回声被检测并放弃该跳,落抽帧/ASR 兜底。
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
## 0.2.0 (2026-10-23)
|
|
23
|
+
|
|
24
|
+
vision-bridge 发布版:选择器可见的透镜包装 + 配置热生效根因修复 + 宿主三线全 rc 支持。以下批次随本版首发(modlens / provider pair 两批原标注 0.1.0 同版本并入,npm 0.1.0 未含,实际随本版出)。
|
|
25
|
+
|
|
26
|
+
### modlens 源码吸收批(2026-10-06 深析,随 0.2.0 首发)
|
|
23
27
|
- 冷却语义升级(FR-10):仅 keyFailure 类失败触发冷却,普通失败永不冷却(正文碰巧含 quota 字样不算);冷却 provider **垫队尾**仍作最后尝试(软化熔断,非踢出);`Retry-After` 头 / `Resets in Xh Ym Zs` 解析进冷却时长;成功即恢复。
|
|
24
28
|
- attempts 带 `keyIndex`(多 key 轮换可见);`meta.warnings` 人类可读路由通告(failover 去向、冷却垫尾、轮换成功);错误文本先脱密(apiKey/baseUrl 精确匹配)再截断(redact-before-clip)。
|
|
25
29
|
- 证据合同 v2:新增 `uncertainty?: string[]`(模型自报读不清处,宁标不猜);可选字段 null 归一(不拒整读);处理指令带反注入行(媒体内指令视为数据)、verbatim 转写行、bbox 保守措辞。
|
|
@@ -27,7 +31,7 @@
|
|
|
27
31
|
- 入参 magic-byte sniff(FR-14):png/jpeg/gif/webp/bmp、ftyp/EBML、RIFF/ID3/fLaC/OggS/ADTS/ASF 头字节判型,改名伪装文件在入参侧拒绝。
|
|
28
32
|
- 单成员链穷尽保留原始可行动错误(不被聚合错误吞掉)。
|
|
29
33
|
|
|
30
|
-
### provider pair 吸收批 v2(2026-10-17,链出口 refine pass
|
|
34
|
+
### provider pair 吸收批 v2(2026-10-17,链出口 refine pass,随 0.2.0 首发)
|
|
31
35
|
- 新增顶层配置 `refine: { enabled, model }`(FR-15,opt-in 默认关):开启后主链首个成功 → `finalizeChunk` 之后 → return 之前过一次可选文本模型(对齐 medialens 型项目 "读手 + 转述手" pair)。model 必须显式 `'provider/model'`;`auto` / 空串 = 未启用(宿主 text 组 = 所有已注册模型,auto = 随便加透镜)。
|
|
32
36
|
- refine 失败保 raw(FR-17):LLM throw / JSON 解析失败 / validateEvidence 不通过 三情形均不 throw 到链下一级,仅追 `meta.warnings` 一条 `refine failed: <redacted>`;主链 attempts 不新增(读手已成功,不浪费下一跳抽帧/ASR)。
|
|
33
37
|
- refine 归属分层(FR-19):`MediaEvidence.meta` 新增 `refineAttempt?: Attempt`,`provider = 'refined:<selection>'`;`meta.provider` 保留读手前缀(registered/multimodal/configured/asr/frames/local);source/attempts/generatedAt/degraded 不被 refine 修改。
|
|
@@ -36,7 +40,43 @@
|
|
|
36
40
|
- 传输硬化(FR-19):错误路径先 `redactSecrets` 后截断(对齐 M23);`structuredOutput` 开关共享,ctx.llm.stream 不接 response_format(宿主 GenerateOptions 无该字段),指令层 STRICT JSON 兑住。
|
|
37
41
|
- 新文件 `src/refine.ts`(导出 `refineEvidence` + `EVIDENCE_REFINE_INSTRUCTION`);`router/failover.ts` `ChainRunOptions` 新可选 `refine` hook;工具层从 `LensDeps.refineFn` 取闭包传入 runChain(hook 抛异常时 failover 兑底,仍保 raw)。
|
|
38
42
|
|
|
43
|
+
### refine-source-gate 吸收批 v3(2026-10-21,转述手来源闸门,随 0.2.0 首发)
|
|
44
|
+
- **来源闸门(转述手只认用户自配端点)**:refine 传输从 `ctx.llm.stream`(宿主 runtime 路由表,无法区分宿主原生注册与插件注入模型)改为 **configured 直连**(`callVlWithRotation`,够不到任何注入面)。新增 `isTrustedRefineProvider(endpoint, provider)`:provider 名白名单(`openai`)+ 凭据存在性(baseUrl+apiKey 非空)双判据(`src/config.ts`)。
|
|
45
|
+
- **静默跳过语义**:selection 解析出 `registered:` / `multimodal:` / 未知 provider,或 configured 凭据未配齐 → 原样返回 evidence,不写 `refineAttempt`、不追 warning(守 M28「关时逐字段一致」,与 llm 缺失/非显式 selection 现有跳过路径同构)。
|
|
46
|
+
- **归一与单源**:裸 model(无 slash)归一为 configured 白名单 `openai`(S1);请求 model 取 `providers.openai.model` 单源,不采纳 selection 的 model 段;`refineAttempt.provider` 统一 `refined:openai/<model>`,`meta.provider` 仍留读手前缀。
|
|
47
|
+
- **传输硬化继承**:直连后 `structuredOutput` 真正注入 `response_format`(顺带修复 v2 中 ctx.llm.stream 注不进去的已知缺陷);多 key 轮换 keyIndex 透传 `refineAttempt`;错误先脱密后截断不变。
|
|
48
|
+
- 工程:`tests/refine.test.ts` 改写为 fetch mock 直连断言(18 例:G1 真值表 / 闸门静默跳过 / 直连 URL 与请求体 / structuredOutput 两态 / 失败保 raw / runChain 集成),全量 105 vitest 用例绿 + lint 零错 + `check:line OK`。
|
|
49
|
+
|
|
50
|
+
### vision-bridge 吸收批 v4(2026-10-22,选择器可见的透镜包装)
|
|
51
|
+
- **包装 provider 注入**:新增配置 `inject: { enabled, families }`(默认关;families 默认 `['deepseek','glm','mimo']`,`'*'` = 全部)。开启后经宿主 `llm.registerAdapter` 注册包装路由 `media-lens-vision-<upstream>`,模型选择器出现 `<模型名> (media-lens vision)` 条目(provider 组名 `<上游名> (media-lens)`);选中它作主对话模型,请求中的媒体先经透镜转证据文本再委托真实路由。
|
|
52
|
+
- **包装判据(用户裁定)= 透镜能补模态就包,omni 坚决不包**:纯文本模型 → 图像+音视频全部证据化;图像模型(声明 image、非 omni)→ 图像块原样透传(保留原生视觉),仅音视频证据化;omni(MiMo bare 非 `-pro` 段名字启发式)与 vision 专精词表(deepseek-vl / glm-4v / `*vision*` / glm-5.3-flash 等)拒包,`resolveModel` 响亮拒绝并指路原路由。
|
|
53
|
+
- **请求改写管线(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)。
|
|
54
|
+
- **改写永不 throw**:读手失败按阶段落常量占位文本(线字节稳定,保 provider prefix cache);证据缓存按块稳定键(`attachmentId` / 盘上路径),成功永久、失败 60s 后重试、LRU 256。
|
|
55
|
+
- **生命周期纪律**(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 无条件调用,纯对象必须自带)。
|
|
56
|
+
- **防递归(必须)**:discovery 快照 `filterDiscoverySnapshot` 剔除 `media-lens-vision-*` —— wrapper 注册后自己也在宿主注册表里,不剔除则读手 auto 会选中自己的 wrapper 无限递归;工具链 / models-route 候选 / 显式选择共用同一快照,一处过滤全覆盖。
|
|
57
|
+
- **信号穿透**:`Provider.run(req, signal?)` + `ChainRunOptions.signal` 全链下传(providers 七处 + frames/reencode 内层透传),桥的改写随宿主请求中止;工具层不传 signal 行为不变。
|
|
58
|
+
- **客户端**:设置卡新增「视觉桥」节(enabled 勾选 + families 逗号分隔输入,中英逗号皆收);9 语 locale;configForms mutate/set 六路径提交。
|
|
59
|
+
- 工程:`tests/inject.test.ts` 23 例(判据真值表 / handle 精确识别 / 改写 copy-on-write 与回标与透传分流 / 缓存成功永久失败重试 LRU / sweep 注册刷新回收 / 桥端到端 读手→证据→委托→缓存命中 / 失败常量占位 / discovery 自排除);全量 128 vitest 用例绿 + typecheck/lint 零错。
|
|
60
|
+
|
|
61
|
+
### volatile 活读修复批 v4.1(2026-10-22 深夜,配置热生效根因修复)
|
|
62
|
+
- **根因(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 的选择器开关使其显形)。
|
|
63
|
+
- **修复**:`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 构造期取值,重建会丢冷却状态,权衡不改)。
|
|
64
|
+
- 工程:`tests/inject.test.ts` 增 3 例(cell 形态 resolveConfig 取直 / 活值重算 / 普通对象照旧),全量 131 vitest 用例绿 + typecheck/lint 零错。
|
|
65
|
+
|
|
66
|
+
### 宿主三线全 rc 枚举批(2026-10-23,用户裁定口径)
|
|
67
|
+
- **口径变更(用户裁定)**: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` 两格。
|
|
68
|
+
- **契约面核实(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 在位逐一实查。
|
|
69
|
+
- `hosts.mjs` 口径段重写(声明枚举与 farm 实测账本 `.compat-results/verified.json` 分属两档,后者不动),`sync-hosts --write` 三写点下发。
|
|
70
|
+
- 全门禁:131 vitest 用例绿 + typecheck/lint 零错。
|
|
71
|
+
|
|
72
|
+
### 宿主 0.2.1-rc.2 支持批(2026-10-23,engines 枚举补格 + wrapper 直调面补齐)
|
|
73
|
+
- **背景**:desktop 宿主 manifest 推荐 rc 已是 0.2.1-rc.2;engines.dsh 自 0.2.1-alpha.4 起是独立且优先的拒载判据,枚举不含 rc.2 则宿主升级即拒载本插件。
|
|
74
|
+
- **契约面核实(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 实查)。
|
|
75
|
+
- **修复**: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 三写点。
|
|
76
|
+
- **verified.json 不动**:rc.2 未过 farm 逐格实测,仅静态 diff + 冒烟级核实(诚实档位);farm 补测后另行入账。
|
|
77
|
+
- 工程:sweep 测试增 adapter 直调面断言(pricing undefined / target 保留语义),全量 131 vitest 用例绿 + typecheck/lint 零错。
|
|
78
|
+
|
|
39
79
|
### 工程
|
|
40
80
|
- 宿主 service 全部 `ctx.get()` 软读优雅降级(C2);运行时依赖仅 schemastery。
|
|
41
|
-
-
|
|
42
|
-
-
|
|
81
|
+
- 131 vitest 用例全绿(含 `tests/inject.test.ts` 26 例覆盖 vision-bridge 与 volatile 取直);`check:line` 门禁保证 lib/ 与 src/ 逐字节对齐(C3)。
|
|
82
|
+
- 宿主线钉三线全 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)。
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ media_read_video / media_read_audio / media_read_image
|
|
|
11
11
|
```
|
|
12
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.2` `0.2.0-rc.2` `0.2.1-rc.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` (6 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
16
|
<!-- host-compat:end -->
|
|
17
17
|
|
|
@@ -53,7 +53,7 @@ dsh-media-lens:
|
|
|
53
53
|
endpoints: [] # 多端点选择项,按序尝试;与 command 合并(command 在前)
|
|
54
54
|
refine:
|
|
55
55
|
enabled: false # Phase 7:链出口一次 refine pass,默认关
|
|
56
|
-
model: '' #
|
|
56
|
+
model: '' # 开启时必须写 'openai/<model>' 或裸 model;auto / 空串 = 未启用。转述手只认 configured 自配端点(registered:/插件注入模型无效)
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
ASR 命令模板示例(Windows,模板内双引号保护空格路径):
|
|
@@ -87,19 +87,39 @@ python "C:\tools\sensevoice_cli.py" --input "{input}" --json
|
|
|
87
87
|
```yaml
|
|
88
88
|
refine:
|
|
89
89
|
enabled: true
|
|
90
|
-
model: "
|
|
90
|
+
model: "openai/your-text-model" # 或裸 model;auto / 空串 = 未启用
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
默认关,理由:
|
|
94
94
|
|
|
95
95
|
- **隐私**:多一跳远端 = 多一次媒体内容(raw evidence)暴露面;`strictLocalOnly` 下链仅到 `local:metadata`,此时 refine 自动跳过(无文本可精炼),不会拉回远端。
|
|
96
96
|
- **延迟**:直出证据已可用,refine 是锦上添花。
|
|
97
|
-
- **不作 auto
|
|
97
|
+
- **不作 auto**:auto = 随便把所有模型都加透镜,不允许。
|
|
98
98
|
|
|
99
|
-
|
|
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 修改。
|
|
100
102
|
|
|
101
103
|
传输硬化:`EVIDENCE_REFINE_INSTRUCTION` 四要素与读手指令同源(反注入 / verbatim / bbox 保守 / uncertainty),另加「保留原 source 与 attempts」一行——媒体内出现的指令会随 raw evidence 二次进入 LLM 上下文,反注入行必须再吃一遍,防洗证据。错误文本先脱密后截断。
|
|
102
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
|
+
|
|
103
123
|
## 安装
|
|
104
124
|
|
|
105
125
|
```powershell
|
|
@@ -129,7 +149,7 @@ lazy 机制(两路注入均在 apply 期顶层注册,工厂只在「声明
|
|
|
129
149
|
|
|
130
150
|
## 边界(YAGNI)
|
|
131
151
|
|
|
132
|
-
有 host+client 双半身与设置卡(家族子 tab / 独立节 / bundle
|
|
152
|
+
有 host+client 双半身与设置卡(家族子 tab / 独立节 / bundle 卡,见上节),但除此之外无对话内卡片、无客户端粘贴拦截(vision-bridge 的请求改写发生在宿主 adapter 面,非 UI 拦截)、无 TTS / 媒体编辑、不内置任何 key。更多 UI 需求出现时另立计划。
|
|
133
153
|
|
|
134
154
|
## 版本线
|
|
135
155
|
|
package/dsh.plugin.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-media-lens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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.2 || 0.2.0-rc.2 || 0.2.1-rc.1"
|
|
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"
|
|
8
8
|
},
|
|
9
9
|
"contributes": {
|
|
10
10
|
"tools": [],
|
package/lib/client.js
CHANGED
|
@@ -30,11 +30,32 @@ window.__ModuleLoader__.load({
|
|
|
30
30
|
refine: {
|
|
31
31
|
enabled: false,
|
|
32
32
|
model: ""
|
|
33
|
+
},
|
|
34
|
+
inject: {
|
|
35
|
+
enabled: false,
|
|
36
|
+
families: [
|
|
37
|
+
"deepseek",
|
|
38
|
+
"glm",
|
|
39
|
+
"mimo"
|
|
40
|
+
]
|
|
33
41
|
}
|
|
34
42
|
};
|
|
35
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* 宿主 volatile 字段是带 `.get()` 的活引用(settings/plainConfig 同款判别):
|
|
45
|
+
* 每次读取都返回当前值,写入后变。resolveConfig 必须先递归取直——平铺展开 cell
|
|
46
|
+
* 会把全部字段静默丢成默认值(宿主半区从未见过用户配置的根因,v4.1 修复)。
|
|
47
|
+
* duck-type 判别照 models-route WebServerLike 纪律,不引 cosmokit。
|
|
48
|
+
*/
|
|
49
|
+
function detachConfigCells(value) {
|
|
50
|
+
if (value === null || typeof value !== "object") return value;
|
|
51
|
+
const candidate = value;
|
|
52
|
+
if (typeof candidate.get === "function") return detachConfigCells(candidate.get());
|
|
53
|
+
if (Array.isArray(value)) return value.map((entry) => detachConfigCells(entry));
|
|
54
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, detachConfigCells(child)]));
|
|
55
|
+
}
|
|
56
|
+
/** 深合并宿主传入的部分配置与默认值(undefined 字段一律回落默认;volatile cell 先取直)。 */
|
|
36
57
|
function resolveConfig(partial) {
|
|
37
|
-
const p = partial ?? {};
|
|
58
|
+
const p = detachConfigCells(partial ?? {});
|
|
38
59
|
const models = {
|
|
39
60
|
...DEFAULT_CONFIG.models,
|
|
40
61
|
...p.models ?? {}
|
|
@@ -51,6 +72,10 @@ window.__ModuleLoader__.load({
|
|
|
51
72
|
...DEFAULT_CONFIG.refine,
|
|
52
73
|
...p.refine ?? {}
|
|
53
74
|
};
|
|
75
|
+
const inject = {
|
|
76
|
+
...DEFAULT_CONFIG.inject,
|
|
77
|
+
...p.inject ?? {}
|
|
78
|
+
};
|
|
54
79
|
return {
|
|
55
80
|
strictLocalOnly: p.strictLocalOnly ?? DEFAULT_CONFIG.strictLocalOnly,
|
|
56
81
|
cooldownMs: typeof p.cooldownMs === "number" && Number.isFinite(p.cooldownMs) && p.cooldownMs >= 0 ? p.cooldownMs : DEFAULT_CONFIG.cooldownMs,
|
|
@@ -74,6 +99,10 @@ window.__ModuleLoader__.load({
|
|
|
74
99
|
refine: {
|
|
75
100
|
enabled: refine.enabled === true,
|
|
76
101
|
model: typeof refine.model === "string" ? refine.model.trim() : ""
|
|
102
|
+
},
|
|
103
|
+
inject: {
|
|
104
|
+
enabled: inject.enabled === true,
|
|
105
|
+
families: Array.isArray(inject.families) ? inject.families.filter((f) => typeof f === "string" && f.trim() !== "").map((f) => f.trim()) : [...DEFAULT_CONFIG.inject.families]
|
|
77
106
|
}
|
|
78
107
|
};
|
|
79
108
|
}
|
|
@@ -431,6 +460,35 @@ window.__ModuleLoader__.load({
|
|
|
431
460
|
onCommit: (v) => patch((d) => {
|
|
432
461
|
d.refine.model = v;
|
|
433
462
|
})
|
|
463
|
+
}),
|
|
464
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
465
|
+
style: heading,
|
|
466
|
+
children: t("injectTitle")
|
|
467
|
+
}),
|
|
468
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Check, {
|
|
469
|
+
label: t("injectEnabled"),
|
|
470
|
+
value: value.inject.enabled,
|
|
471
|
+
disabled: !editable,
|
|
472
|
+
onCommit: (v) => patch((d) => {
|
|
473
|
+
d.inject.enabled = v;
|
|
474
|
+
})
|
|
475
|
+
}),
|
|
476
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
477
|
+
style: hint,
|
|
478
|
+
children: t("injectEnabledHint")
|
|
479
|
+
}),
|
|
480
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BufferedText, {
|
|
481
|
+
label: t("injectFamilies"),
|
|
482
|
+
value: value.inject.families.join(", "),
|
|
483
|
+
disabled: !editable,
|
|
484
|
+
placeholder: "deepseek, glm, mimo",
|
|
485
|
+
onCommit: (v) => patch((d) => {
|
|
486
|
+
d.inject.families = v.split(/[,,]/).map((s) => s.trim()).filter((s) => s !== "");
|
|
487
|
+
})
|
|
488
|
+
}),
|
|
489
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
490
|
+
style: hint,
|
|
491
|
+
children: t("injectFamiliesHint")
|
|
434
492
|
})
|
|
435
493
|
]
|
|
436
494
|
});
|
|
@@ -468,6 +526,11 @@ window.__ModuleLoader__.load({
|
|
|
468
526
|
refineTitle: "链出口 refine",
|
|
469
527
|
refineEnabled: "启用 refine pass",
|
|
470
528
|
refineModel: "refine 模型(\"provider/model\")",
|
|
529
|
+
injectTitle: "视觉桥",
|
|
530
|
+
injectEnabled: "在选择器中包装模型",
|
|
531
|
+
injectEnabledHint: "开启后模型选择器出现“<模型名> (media-lens vision)”条目:选中它,请求中的图片/音视频先经本插件处理端转成证据文本再交给原模型。宽模态(如 mimo-v2.6-flash)与 vision 专精模型不包装。",
|
|
532
|
+
injectFamilies: "包装 families(逗号分隔前缀,* = 全部)",
|
|
533
|
+
injectFamiliesHint: "前缀匹配模型 id 或名称;宽模态与 vision 专精模型始终排除。",
|
|
471
534
|
save: "保存",
|
|
472
535
|
saving: "保存中…",
|
|
473
536
|
saved: "已保存",
|
|
@@ -500,6 +563,11 @@ window.__ModuleLoader__.load({
|
|
|
500
563
|
refineTitle: "Chain-exit refine",
|
|
501
564
|
refineEnabled: "Enable refine pass",
|
|
502
565
|
refineModel: "Refine model (\"provider/model\")",
|
|
566
|
+
injectTitle: "Vision bridge",
|
|
567
|
+
injectEnabled: "Wrap models in the selector",
|
|
568
|
+
injectEnabledHint: "Adds \"<model> (media-lens vision)\" entries to the model selector: picking one routes pasted images and attached audio/video through this plugin's processor into evidence text before the original model answers. Omni (e.g. mimo-v2.6-flash) and vision-specialized models are never wrapped.",
|
|
569
|
+
injectFamilies: "Wrap families (comma-separated prefixes, * = all)",
|
|
570
|
+
injectFamiliesHint: "Prefix-matches model ids or names; omni and vision-specialized models are always excluded.",
|
|
503
571
|
save: "Save",
|
|
504
572
|
saving: "Saving…",
|
|
505
573
|
saved: "Saved",
|
|
@@ -537,6 +605,11 @@ window.__ModuleLoader__.load({
|
|
|
537
605
|
refineTitle: "チェーン末尾の refine",
|
|
538
606
|
refineEnabled: "refine pass を有効化",
|
|
539
607
|
refineModel: "refine モデル(\"provider/model\")",
|
|
608
|
+
injectTitle: "ビジョンブリッジ",
|
|
609
|
+
injectEnabled: "セレクターでモデルをラップ",
|
|
610
|
+
injectEnabledHint: "モデルセレクターに「<モデル名> (media-lens vision)」エントリを追加します。選ぶと画像/音声/動画がまず本プラグインの処理モデルで証拠テキストに変換され、元のモデルに渡ります。オムニ(mimo-v2.6-flash など)と vision 専門モデルはラップしません。",
|
|
611
|
+
injectFamilies: "ラップ対象 families(カンマ区切りプレフィックス、* = 全部)",
|
|
612
|
+
injectFamiliesHint: "モデル id や名前に前方一致。オムニと vision 専門モデルは常に除外。",
|
|
540
613
|
save: "保存",
|
|
541
614
|
saving: "保存中…",
|
|
542
615
|
saved: "保存しました",
|
|
@@ -573,6 +646,11 @@ window.__ModuleLoader__.load({
|
|
|
573
646
|
refineTitle: "체인 출구 refine",
|
|
574
647
|
refineEnabled: "refine pass 사용",
|
|
575
648
|
refineModel: "refine 모델(\"provider/model\")",
|
|
649
|
+
injectTitle: "비전 브리지",
|
|
650
|
+
injectEnabled: "셀렉터에서 모델 래핑",
|
|
651
|
+
injectEnabledHint: "모델 셀렉터에 “<모델명> (media-lens vision)” 항목을 추가합니다. 선택하면 이미지/오디오/비디오가 먼저 이 플러그인의 처리 모델로 증거 텍스트로 변환된 뒤 원래 모델에 전달됩니다. 옴니(mimo-v2.6-flash 등)와 vision 전용 모델은 래핑하지 않습니다.",
|
|
652
|
+
injectFamilies: "래핑 families(쉼표 구분 접두사, * = 전체)",
|
|
653
|
+
injectFamiliesHint: "모델 id나 이름에 접두사 매칭. 옴니와 vision 전용 모델은 항상 제외됩니다.",
|
|
576
654
|
save: "저장",
|
|
577
655
|
saving: "저장 중…",
|
|
578
656
|
saved: "저장됨",
|
|
@@ -609,6 +687,11 @@ window.__ModuleLoader__.load({
|
|
|
609
687
|
refineTitle: "Raffinage en sortie de chaîne",
|
|
610
688
|
refineEnabled: "Activer le passage de raffinage",
|
|
611
689
|
refineModel: "Modèle de raffinage (\"provider/model\")",
|
|
690
|
+
injectTitle: "Pont vision",
|
|
691
|
+
injectEnabled: "Emballer des modèles dans le sélecteur",
|
|
692
|
+
injectEnabledHint: "Ajoute des entrées « <modèle> (media-lens vision) » au sélecteur : les images collées et l'audio/vidéo joints passent d'abord par le processeur de ce plugin comme texte de preuve avant que le modèle d'origine réponde. Les modèles omni (ex. mimo-v2.6-flash) et spécialisés vision ne sont jamais emballés.",
|
|
693
|
+
injectFamilies: "Familles emballées (préfixes séparés par virgules, * = tous)",
|
|
694
|
+
injectFamiliesHint: "Correspondance par préfixe sur les ids ou noms de modèles ; les modèles omni et spécialisés vision sont toujours exclus.",
|
|
612
695
|
save: "Enregistrer",
|
|
613
696
|
saving: "Enregistrement…",
|
|
614
697
|
saved: "Enregistré",
|
|
@@ -645,6 +728,11 @@ window.__ModuleLoader__.load({
|
|
|
645
728
|
refineTitle: "Kettenausgang-Verfeinerung",
|
|
646
729
|
refineEnabled: "Verfeinerungsdurchlauf aktivieren",
|
|
647
730
|
refineModel: "Verfeinerungsmodell (\"provider/model\")",
|
|
731
|
+
injectTitle: "Vision-Brücke",
|
|
732
|
+
injectEnabled: "Modelle in den Selektor einpacken",
|
|
733
|
+
injectEnabledHint: "Fügt Einträge „<Modell> (media-lens vision)“ zur Modellauswahl hinzu: Eingefügte Bilder und angehängte Audio-/Videodateien werden zuerst durch den Prozessor dieses Plugins als Beweistext geleitet, bevor das Originalmodell antwortet. Omni-Modelle (z. B. mimo-v2.6-flash) und vision-spezialisierte Modelle werden nie eingepackt.",
|
|
734
|
+
injectFamilies: "Einzupackende Families (kommagetrennte Präfixe, * = alle)",
|
|
735
|
+
injectFamiliesHint: "Präfix-Abgleich auf Modell-IDs oder Namen; Omni- und vision-spezialisierte Modelle sind immer ausgeschlossen.",
|
|
648
736
|
save: "Speichern",
|
|
649
737
|
saving: "Speichere…",
|
|
650
738
|
saved: "Gespeichert",
|
|
@@ -681,6 +769,11 @@ window.__ModuleLoader__.load({
|
|
|
681
769
|
refineTitle: "Raffinamento a fine catena",
|
|
682
770
|
refineEnabled: "Abilita passaggio di raffinamento",
|
|
683
771
|
refineModel: "Modello di raffinamento (\"provider/model\")",
|
|
772
|
+
injectTitle: "Ponte vision",
|
|
773
|
+
injectEnabled: "Incapsula modelli nel selettore",
|
|
774
|
+
injectEnabledHint: "Aggiunge voci «<modello> (media-lens vision)» al selettore: immagini incollate e audio/video allegati passano prima dal processore di questo plugin come testo di evidenza, poi risponde il modello originale. I modelli omni (es. mimo-v2.6-flash) e specializzati vision non vengono mai incapsulati.",
|
|
775
|
+
injectFamilies: "Families da incapsulare (prefissi separati da virgola, * = tutti)",
|
|
776
|
+
injectFamiliesHint: "Corrispondenza per prefisso su id o nomi dei modelli; i modelli omni e specializzati vision sono sempre esclusi.",
|
|
684
777
|
save: "Salva",
|
|
685
778
|
saving: "Salvataggio…",
|
|
686
779
|
saved: "Salvato",
|
|
@@ -717,6 +810,11 @@ window.__ModuleLoader__.load({
|
|
|
717
810
|
refineTitle: "Уточнение на выходе цепочки",
|
|
718
811
|
refineEnabled: "Включить проход уточнения",
|
|
719
812
|
refineModel: "Модель уточнения (\"provider/model\")",
|
|
813
|
+
injectTitle: "Визуальный мост",
|
|
814
|
+
injectEnabled: "Оборачивать модели в селекторе",
|
|
815
|
+
injectEnabledHint: "Добавляет в селектор моделей записи «<модель> (media-lens vision)»: вставленные изображения и приложенные аудио/видео сначала превращаются процессором этого плагина в текст доказательств, и лишь затем отвечает исходная модель. Омни-модели (напр. mimo-v2.6-flash) и специализированные vision-модели не оборачиваются.",
|
|
816
|
+
injectFamilies: "Семейства для обёртывания (префиксы через запятую, * = все)",
|
|
817
|
+
injectFamiliesHint: "Сопоставление по префиксу id или имени модели; омни- и vision-специализированные модели всегда исключены.",
|
|
720
818
|
save: "Сохранить",
|
|
721
819
|
saving: "Сохранение…",
|
|
722
820
|
saved: "Сохранено",
|
|
@@ -753,6 +851,11 @@ window.__ModuleLoader__.load({
|
|
|
753
851
|
refineTitle: "Refinamiento a la salida de la cadena",
|
|
754
852
|
refineEnabled: "Activar pasada de refinamiento",
|
|
755
853
|
refineModel: "Modelo de refinamiento (\"provider/model\")",
|
|
854
|
+
injectTitle: "Puente de visión",
|
|
855
|
+
injectEnabled: "Envolver modelos en el selector",
|
|
856
|
+
injectEnabledHint: "Añade entradas «<modelo> (media-lens vision)» al selector de modelos: las imágenes pegadas y el audio/vídeo adjuntos pasan primero por el procesador de este plugin como texto de evidencia antes de que responda el modelo original. Los modelos omni (p. ej. mimo-v2.6-flash) y los especializados en visión nunca se envuelven.",
|
|
857
|
+
injectFamilies: "Familias a envolver (prefijos separados por comas, * = todos)",
|
|
858
|
+
injectFamiliesHint: "Coincidencia por prefijo de id o nombre de modelo; los modelos omni y especializados en visión siempre se excluyen.",
|
|
756
859
|
save: "Guardar",
|
|
757
860
|
saving: "Guardando…",
|
|
758
861
|
saved: "Guardado",
|
|
@@ -883,6 +986,11 @@ window.__ModuleLoader__.load({
|
|
|
883
986
|
op: "set",
|
|
884
987
|
path: ["refine"],
|
|
885
988
|
value: next.refine
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
op: "set",
|
|
992
|
+
path: ["inject"],
|
|
993
|
+
value: next.inject
|
|
886
994
|
}
|
|
887
995
|
]);
|
|
888
996
|
return (await Promise.all([
|
|
@@ -890,7 +998,8 @@ window.__ModuleLoader__.load({
|
|
|
890
998
|
form.set("cooldownMs", next.cooldownMs),
|
|
891
999
|
form.set("models", next.models),
|
|
892
1000
|
form.set("providers", next.providers),
|
|
893
|
-
form.set("refine", next.refine)
|
|
1001
|
+
form.set("refine", next.refine),
|
|
1002
|
+
form.set("inject", next.inject)
|
|
894
1003
|
])).every(Boolean);
|
|
895
1004
|
} catch (error) {
|
|
896
1005
|
console.warn("[dsh-media-lens] 写入失败:", error);
|
package/lib/config.d.ts
CHANGED
|
@@ -46,6 +46,16 @@ export interface MediaLensConfig {
|
|
|
46
46
|
enabled: boolean;
|
|
47
47
|
model: string;
|
|
48
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* vision-bridge(v4):把透镜包装成宿主选择器可见的 provider 路由
|
|
51
|
+
* (`<模型名> (media-lens vision)` 条目)——默认关(注册面 + 媒体改写均为
|
|
52
|
+
* 额外暴露面,与 refine 同隐私哲学)。families 前缀过滤包装候选
|
|
53
|
+
* ('*' = 全部,仍受 omni/vision 词表否决)。
|
|
54
|
+
*/
|
|
55
|
+
inject: {
|
|
56
|
+
enabled: boolean;
|
|
57
|
+
families: string[];
|
|
58
|
+
};
|
|
49
59
|
}
|
|
50
60
|
export declare const DEFAULT_CONFIG: Required<MediaLensConfig>;
|
|
51
61
|
/** 宿主传入的配置形态(schemastery 展开后的全量或部分字段)。 */
|
|
@@ -58,11 +68,35 @@ export interface MediaLensConfigInput {
|
|
|
58
68
|
asr?: Partial<MediaLensConfig['providers']['asr']>;
|
|
59
69
|
};
|
|
60
70
|
refine?: Partial<MediaLensConfig['refine']>;
|
|
71
|
+
inject?: Partial<MediaLensConfig['inject']>;
|
|
61
72
|
}
|
|
62
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* 宿主 volatile 字段是带 `.get()` 的活引用(settings/plainConfig 同款判别):
|
|
75
|
+
* 每次读取都返回当前值,写入后变。resolveConfig 必须先递归取直——平铺展开 cell
|
|
76
|
+
* 会把全部字段静默丢成默认值(宿主半区从未见过用户配置的根因,v4.1 修复)。
|
|
77
|
+
* duck-type 判别照 models-route WebServerLike 纪律,不引 cosmokit。
|
|
78
|
+
*/
|
|
79
|
+
export declare function detachConfigCells<T>(value: T): T;
|
|
80
|
+
/** 深合并宿主传入的部分配置与默认值(undefined 字段一律回落默认;volatile cell 先取直)。 */
|
|
63
81
|
export declare function resolveConfig(partial: MediaLensConfigInput | undefined): Required<MediaLensConfig>;
|
|
64
82
|
/** 解析 `provider/model` 选择串(首个 `/` 分隔);非显式选择返回 undefined。 */
|
|
65
83
|
export declare function parseModelSelection(value: string | undefined): {
|
|
66
84
|
provider: string;
|
|
67
85
|
model: string;
|
|
68
86
|
} | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* refine 来源闸门(refine-source-gate):转述手只允许「用户自配、非插件注入」的端点。
|
|
89
|
+
*
|
|
90
|
+
* 可信判据 = provider 名命中 configured 白名单(当前仅 `openai`,即用户显式填了
|
|
91
|
+
* baseUrl+apiKey 的直连端点);`registered:` / `multimodal:`(宿主 discovery 枚举,
|
|
92
|
+
* 可能含其它插件注入的模型)一律不可信。判据与凭据存在性绑定,无需宿主暴露
|
|
93
|
+
* 「注入来源」信号。
|
|
94
|
+
*
|
|
95
|
+
* 注意:裸 model(无 `provider/` 前缀)的归一由调用方先归到 `openai` 再进本判据。
|
|
96
|
+
*/
|
|
97
|
+
export declare function isTrustedRefineProvider(endpoint: {
|
|
98
|
+
baseUrl: string;
|
|
99
|
+
apiKey: string;
|
|
100
|
+
model?: string;
|
|
101
|
+
structuredOutput?: boolean;
|
|
102
|
+
} | undefined, provider: string | undefined): boolean;
|
package/lib/config.js
CHANGED
|
@@ -16,14 +16,32 @@ export const DEFAULT_CONFIG = {
|
|
|
16
16
|
asr: { command: '', endpoints: [] },
|
|
17
17
|
},
|
|
18
18
|
refine: { enabled: false, model: '' },
|
|
19
|
+
inject: { enabled: false, families: ['deepseek', 'glm', 'mimo'] },
|
|
19
20
|
};
|
|
20
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* 宿主 volatile 字段是带 `.get()` 的活引用(settings/plainConfig 同款判别):
|
|
23
|
+
* 每次读取都返回当前值,写入后变。resolveConfig 必须先递归取直——平铺展开 cell
|
|
24
|
+
* 会把全部字段静默丢成默认值(宿主半区从未见过用户配置的根因,v4.1 修复)。
|
|
25
|
+
* duck-type 判别照 models-route WebServerLike 纪律,不引 cosmokit。
|
|
26
|
+
*/
|
|
27
|
+
export function detachConfigCells(value) {
|
|
28
|
+
if (value === null || typeof value !== 'object')
|
|
29
|
+
return value;
|
|
30
|
+
const candidate = value;
|
|
31
|
+
if (typeof candidate.get === 'function')
|
|
32
|
+
return detachConfigCells(candidate.get());
|
|
33
|
+
if (Array.isArray(value))
|
|
34
|
+
return value.map((entry) => detachConfigCells(entry));
|
|
35
|
+
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, detachConfigCells(child)]));
|
|
36
|
+
}
|
|
37
|
+
/** 深合并宿主传入的部分配置与默认值(undefined 字段一律回落默认;volatile cell 先取直)。 */
|
|
21
38
|
export function resolveConfig(partial) {
|
|
22
|
-
const p = partial ?? {};
|
|
39
|
+
const p = detachConfigCells(partial ?? {});
|
|
23
40
|
const models = { ...DEFAULT_CONFIG.models, ...(p.models ?? {}) };
|
|
24
41
|
const openai = { ...DEFAULT_CONFIG.providers.openai, ...(p.providers?.openai ?? {}) };
|
|
25
42
|
const asr = { ...DEFAULT_CONFIG.providers.asr, ...(p.providers?.asr ?? {}) };
|
|
26
43
|
const refine = { ...DEFAULT_CONFIG.refine, ...(p.refine ?? {}) };
|
|
44
|
+
const inject = { ...DEFAULT_CONFIG.inject, ...(p.inject ?? {}) };
|
|
27
45
|
return {
|
|
28
46
|
strictLocalOnly: p.strictLocalOnly ?? DEFAULT_CONFIG.strictLocalOnly,
|
|
29
47
|
cooldownMs: typeof p.cooldownMs === 'number' && Number.isFinite(p.cooldownMs) && p.cooldownMs >= 0
|
|
@@ -51,6 +69,13 @@ export function resolveConfig(partial) {
|
|
|
51
69
|
// 空串与 'auto' 同义(均视为未启用);'provider/model' 显式选择保留原串。
|
|
52
70
|
model: typeof refine.model === 'string' ? refine.model.trim() : '',
|
|
53
71
|
},
|
|
72
|
+
inject: {
|
|
73
|
+
enabled: inject.enabled === true,
|
|
74
|
+
// families:合法字符串过滤;空数组 = 全不包(显式关闭语义),缺省回落默认集。
|
|
75
|
+
families: Array.isArray(inject.families)
|
|
76
|
+
? inject.families.filter((f) => typeof f === 'string' && f.trim() !== '').map((f) => f.trim())
|
|
77
|
+
: [...DEFAULT_CONFIG.inject.families],
|
|
78
|
+
},
|
|
54
79
|
};
|
|
55
80
|
}
|
|
56
81
|
/** 解析 `provider/model` 选择串(首个 `/` 分隔);非显式选择返回 undefined。 */
|
|
@@ -65,3 +90,22 @@ export function parseModelSelection(value) {
|
|
|
65
90
|
return undefined;
|
|
66
91
|
return { provider: trimmed.slice(0, slash), model: trimmed.slice(slash + 1) };
|
|
67
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* refine 来源闸门(refine-source-gate):转述手只允许「用户自配、非插件注入」的端点。
|
|
95
|
+
*
|
|
96
|
+
* 可信判据 = provider 名命中 configured 白名单(当前仅 `openai`,即用户显式填了
|
|
97
|
+
* baseUrl+apiKey 的直连端点);`registered:` / `multimodal:`(宿主 discovery 枚举,
|
|
98
|
+
* 可能含其它插件注入的模型)一律不可信。判据与凭据存在性绑定,无需宿主暴露
|
|
99
|
+
* 「注入来源」信号。
|
|
100
|
+
*
|
|
101
|
+
* 注意:裸 model(无 `provider/` 前缀)的归一由调用方先归到 `openai` 再进本判据。
|
|
102
|
+
*/
|
|
103
|
+
export function isTrustedRefineProvider(endpoint, provider) {
|
|
104
|
+
if (provider === undefined || provider.trim() === '')
|
|
105
|
+
return false;
|
|
106
|
+
if (provider.trim() !== 'openai')
|
|
107
|
+
return false;
|
|
108
|
+
return endpoint !== undefined
|
|
109
|
+
&& endpoint.baseUrl.trim() !== ''
|
|
110
|
+
&& endpoint.apiKey.trim() !== '';
|
|
111
|
+
}
|
package/lib/discovery.d.ts
CHANGED
|
@@ -53,3 +53,9 @@ export interface ModelSelection {
|
|
|
53
53
|
export declare function resolveSelection(snapshot: DiscoverySnapshot, kind: MediaKind, selection: string | undefined): ModelSelection | undefined;
|
|
54
54
|
/** 供注入:从宿主上下文软读 llm runtime(无则 undefined)。 */
|
|
55
55
|
export declare function softReadLlm(ctx: unknown): LlmRuntime | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* 快照过滤(vision-bridge 自排除面):剔除 keep 为假的模型并重算模态分组。
|
|
58
|
+
* wrapper 注册后自己也会出现在宿主注册表——不剔除则读手 auto 会选中自己的
|
|
59
|
+
* wrapper 造成递归。工具链、选择、候选路由共用同一快照,一处过滤全覆盖。
|
|
60
|
+
*/
|
|
61
|
+
export declare function filterDiscoverySnapshot(snapshot: DiscoverySnapshot, keep: (model: DiscoveredModel) => boolean): DiscoverySnapshot;
|
package/lib/discovery.js
CHANGED
|
@@ -99,3 +99,16 @@ export function softReadLlm(ctx) {
|
|
|
99
99
|
return undefined;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* 快照过滤(vision-bridge 自排除面):剔除 keep 为假的模型并重算模态分组。
|
|
104
|
+
* wrapper 注册后自己也会出现在宿主注册表——不剔除则读手 auto 会选中自己的
|
|
105
|
+
* wrapper 造成递归。工具链、选择、候选路由共用同一快照,一处过滤全覆盖。
|
|
106
|
+
*/
|
|
107
|
+
export function filterDiscoverySnapshot(snapshot, keep) {
|
|
108
|
+
if (snapshot.available === false)
|
|
109
|
+
return snapshot;
|
|
110
|
+
const models = snapshot.models.filter(keep);
|
|
111
|
+
if (models.length === snapshot.models.length)
|
|
112
|
+
return snapshot;
|
|
113
|
+
return { available: true, models, groups: groupByKind(models) };
|
|
114
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -71,6 +71,13 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
71
71
|
enabled: z<boolean, boolean, "defined">;
|
|
72
72
|
model: z<string, string, "defined">;
|
|
73
73
|
}>>>, "volatile">;
|
|
74
|
+
inject: z<NoInfer<Schemastery.ObjectS<NoInfer<{
|
|
75
|
+
enabled: z<boolean, boolean, "defined">;
|
|
76
|
+
families: z<string[], string[], "defined">;
|
|
77
|
+
}>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
|
|
78
|
+
enabled: z<boolean, boolean, "defined">;
|
|
79
|
+
families: z<string[], string[], "defined">;
|
|
80
|
+
}>>>, "volatile">;
|
|
74
81
|
}>>, Schemastery.ObjectT<NoInfer<{
|
|
75
82
|
strictLocalOnly: z<boolean, boolean, "volatile-defined">;
|
|
76
83
|
cooldownMs: z<number, number, "volatile-defined">;
|
|
@@ -129,6 +136,13 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
|
|
|
129
136
|
enabled: z<boolean, boolean, "defined">;
|
|
130
137
|
model: z<string, string, "defined">;
|
|
131
138
|
}>>>, "volatile">;
|
|
139
|
+
inject: z<NoInfer<Schemastery.ObjectS<NoInfer<{
|
|
140
|
+
enabled: z<boolean, boolean, "defined">;
|
|
141
|
+
families: z<string[], string[], "defined">;
|
|
142
|
+
}>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
|
|
143
|
+
enabled: z<boolean, boolean, "defined">;
|
|
144
|
+
families: z<string[], string[], "defined">;
|
|
145
|
+
}>>>, "volatile">;
|
|
132
146
|
}>>, "plain">;
|
|
133
147
|
export declare function apply(ctx: Context, config?: MediaLensConfigInput): void;
|
|
134
148
|
export { resolveConfig, DEFAULT_CONFIG, parseModelSelection } from './config.ts';
|
|
@@ -138,6 +152,8 @@ export type { MediaEvidence, Attempt, Chunk, MediaKind } from './evidence.ts';
|
|
|
138
152
|
export { AllProvidersFailed, CooldownGate, runChain } from './router/failover.ts';
|
|
139
153
|
export { ProviderRegistry } from './router/registry.ts';
|
|
140
154
|
export { decideBudget } from './router/budget.ts';
|
|
141
|
-
export { discoverRegisteredModels, resolveSelection } from './discovery.ts';
|
|
155
|
+
export { discoverRegisteredModels, filterDiscoverySnapshot, resolveSelection } from './discovery.ts';
|
|
142
156
|
export { refineEvidence, EVIDENCE_REFINE_INSTRUCTION } from './refine.ts';
|
|
143
157
|
export type { RefineDeps } from './refine.ts';
|
|
158
|
+
export { createVisionBridge, shouldWrapModel, evidenceBridgeText, fileHandlePath, rewriteMessages, EvidenceMemo, BRIDGE_FAILURES, OWN_PROVIDER_PREFIX, VISION_MODEL_SUFFIX } from './inject.ts';
|
|
159
|
+
export type { VisionBridge, VisionBridgeInput } from './inject.ts';
|