# 更新记录 版本号与 GitHub 的 `vX.Y.Z` 标签一一对应。安装方式见 [README](README.md)。 ## 0.1.11 两处正确性修正。 ### 胶囊只在花这份额度时出现 输入框旁边那枚胶囊原来只要插件启用就出现,不看当前选中的是哪个模型。这会和同样用 `conversation.input.right` 的其他插件抢位置,也会在别的 provider 的请求旁边显示一份不属于它的余额。 现在它读会话的模型目录,只有当前选择是本插件的模型时才渲染。选择别的模型即消失,切回来即恢复;`composerUsage` 仍是独立开关,两者都要满足。 ### 推理内容不再丢失(issue #2) 网关把增量写在 `reasoning`(以及结构化的 `reasoning_details`)里,而适配器只认 `reasoning_content`。其后果不是「少了点思考文本」:思考块从未打开,推理模型的输出被整段丢弃,turn 可能直接以空响应结束,报的还是一句难以归因的空响应错误。 现在三种拼写都读,且只取第一个有内容的——它们是同一个增量的不同写法,`reasoning` 与 `reasoning_details` 常常同时出现,叠加会把每个词报两遍。 ## 0.1.10 这一版没有单独发布到 npm:它的内容随 0.1.11 一起上线,所以版本序列是 `0.1.9 → 0.1.11`。 额度现在就在输入框旁边,设置页也换了版式。 ### 为什么加这枚胶囊 5 小时窗口是**滚动**的,重置点不像 7 天窗口那样落在整点,所以「现在还能不能跑一次长任务」这件事,只有在设置页 → 插件 → 额度卡片里才能看到。而真正要判断的时刻,是正在输入框里准备发请求的那一秒。 胶囊因此常驻在会话输入框右侧:`5h 62% · 7d 88%`。点开是明细——每窗口的进度条、剩余金额、重置倒计时、钱包余额和「读取于」时间。 ### 它不产生额外开销 宿主本来就把每轮额度读数写进配置的 `usageJson`,胶囊只解析这份已经存在的文档:不轮询、不开第二条传输、不额外调 `vela`。刷新按钮写的是 `refreshNonce`,与会话外那条既有链路完全相同,没有第二套机制。 口径与设置页一致:显示**剩余**(越大越健康,条越长),按两个窗口里最差的那个着色(≤ 25% 黄,≤ 5% 红)。没有读数时整枚胶囊不渲染,不留空壳。开关是 `composerUsage`,默认开;关掉只是不渲染,宿主照常读取,重新打开立刻有数据。 ### 界面的做法变了 原来的客户端是纯内联样式,靠一堆 `rgba(128,128,128,…)` 手工凑出层次,既没有 hover 态也不跟随主题。现在: - 引入 CSS Modules 构建管线(esbuild 插件 + `lightningcss` 编译,class 名按文件哈希隔离),客户端仍是单个 CJS 文件,CSS 在模块加载时注入一次。 - 颜色全部映射到宿主的 `--dsw-alias-*` 变量,跟随用户选定的亮/暗与主题色,插件页面不再有自己的一套皮肤。 - 抽出 `Card` / `Row` / `Switch` / `Button` / `Badge` / `Bar` / `Field` / `Input` 这些基元,五张卡片与设置页共用同一套间距、圆角、分割线与字号层级。 设置页的信息顺序也重排了:启用 → 账号 → 模型 → 额度 → 对账 → 本地用量 → 额度来源 → 路由状态。先给「能不能用」,再给「用了多少」。 ### 顺手修掉一个会在 CI 假红的坑 `lightningcss` 返回的 class 名映射是哈希表顺序,两次构建的**字节内容相同、键顺序不同**。这会让 `npm run check:dist`(以及 `test:ci`)在什么都没改的情况下报「lib/ 与源码不一致」。已在 `scripts/css-modules.mjs` 里按键排序输出,并把「两次编译必须字节相同」当作验证标准。 ## 0.1.9 长对话不再在「只用了三成上下文」时被拒。 ### 为什么会被拒 网关的判据是 `输入 + max_tokens ≤ 窗口`。窗口实测就是 1,048,576(喂到 1,036,038 token 仍然 200,1,053,038 才 400),OpenDesign 没有偷偷缩容。问题出在 `max_tokens`:插件每次把模型公布的最大输出(DeepSeek 系 384,000)原样发上去,于是输入到 ~660K、约六成窗口时就必然超出。 而界面上显示的是三成。因为宿主只用 `contextWindow` 衡量对话有多满,从不扣掉为输出预留的部分,所以它的百分比与网关的判据差着这个预留量。两边都没错,只是量的不是同一个东西。 ### 现在怎么算 - 报给宿主的窗口改成**输入预算**:`窗口 − max(输出上限, 窗口/4)`。1M 窗口 + 384K 输出的模型报 750,000,于是宿主在七成半就开始压缩,留出的余量正好够一次长回答。 - `defaultMaxTokens` 同步报预留量(250,000)。 - 真正发出的 `max_tokens` 按本次请求的实际输入钳制:输入占满时省略该字段(那时任何预留都会被拒),留有余量时取剩余量。估算走字符数与图片数,图片按每张 1,024 token 计——按 base64 长度算会高估三个数量级。 - 工具声明也计入输入预算:本机真实工具集是 326 个声明、约 163K token,占窗口 15.6%,忽略它会让每次估算小三分之一。 - 设置页的模型行同时显示「可用输入」,例如 `上下文 1.00M(可用输入 750k)· 输出 384k`,改参数时能立刻看到自己填的数会换来多少可用输入。 实测对照:`输入 980,038` 时,`max_tokens` 给 68,500 通过,69,000 被拒;`输入 1,043,038` 时,4,096 通过,8,192 被拒。 ## 0.1.8 工具读到的图片,现在能真正到达模型。0.1.7 只修好了一半。 ### 图片为什么还是看不见 0.1.7 把图片按 `image_url` 放在 **tool** 消息里发出。直连网关时这条路径成立,但请求链路上多一层中间件时就不成立:那类中间件按 OpenAI 的常见形状重建 tool 消息,把 content 拍平成字符串再转发,`image_url` 部件被静默丢弃。现象是字节账上「客户端发了 12.8 MiB,转发出去只有 1.0 MiB」,而模型那边只收到一条 `…` 文本,于是回答「我看不到图片」。 这不是某个特定中间件的 bug,而是 tool 角色承载非文本部件本身缺乏共识:Chat Completions 规范没有约定它,各家重建实现各行其是。把它当成可靠通道,就是把可视化能力押在链路每一跳的行为上。 ### 现在怎么发 - 工具消息只发文本。它读到的图片被提取出来,放入紧随其后的一条 **user** 消息,带一句说明 `(Image content from the preceding tool result.)` 让模型知道这些图来自工具而不是用户新贴的。 - `user` 消息携带 `image_url` 是各家都实现得最完整的形状,实测在中间件前后都能到达模型。 - 一个 assistant 轮次可以发多个工具调用,它们的结果是连续多条 tool 消息。提取出的图片统一攒到这段连续区之后才发——插在中间会打断 tool_call 与结果配对的相邻性,严格校验的网关会直接拒绝。这条也补了测试。 - 逐模型实测(直连 / 经中间件,全部一致):`deepseek-v4.1-flash`、`deepseek-v4-flash`、`deepseek-v4-flash-vision-exp`、`claude-haiku-5.5`、`glm-5.3-flash`、`glm-5.3-flashx`、`gpt-6-luna`、`kimi-k2.7-code`、`mimo-v2.6-flash`、`mimo-v2.6-pro` 都能正确说出图片颜色。并行两条工具调用的形状也实测通过(先蓝后红,两张都被正确识别)。 - 本机按真实宿主链路验证:`read_image` 读取一张写着四个字母的图,模型复述出这四个字母。修复前同样的调用只能得到尺寸文本。 ### 其它 - 路由诊断新增 `attachmentsMounted`:附件服务没挂上时,图片变不成字节,而后果是模型说「我看不到图」——这与健康的请求从外部无法区分。现在这个事实会出现在设置页。 ## 0.1.7 三处修复:工具读到的图片现在真的会发给模型;配置写入不再每轮都抢 profile 的锁。 ### 工具读到的图片发不出去 - `read_image` 的回答是一条 **tool** 消息,而 `toChatMessage` 处理 tool 角色时把图片解析器传成了 `undefined`,于是每张工具侧图片都被换成占位文本 `[image omitted because this model accepts text only]`。模型于是回答"我看不到图片",看起来像模型不支持视觉,实际是我们自己没发。 - 现在 tool 消息也走同一个解析器,图片按 `image_url` 发出。网关实测:`tool` 角色带图返回 200 并正确识别颜色,**前提是请求同时声明了 `tools`**——宿主的真实请求总是带着工具表,所以这条路径可用;我最初用不声明 `tools` 的裸请求探测,撞到的是另一条规则(`reasoning_content must be passed back`),与图片无关。 - 新增测试固定住这条路径,并做过反向验证:把解析器改回 `undefined`,新测试立刻失败。 ### 配置写入不再无条件抢锁 - 宿主的 `settings.update` 会锁住 profile 根目录的 `package.json` 并重写整份 composition 文档(本机 55KB,1–2 秒),随后重算插件树。此前每次调用都无条件写入:`reportJson` 每次模型调用写一次(实测约 1814 次/天),`usageJson` 每 60 秒写一次(1440 次/天),一天数千次 profile 级加锁,插件市场安装要同一把锁就会长时间等待甚至超时。 - `fieldWriterOf` 现在先读该 entry 当前的 `value`,与待写入的 JSON 相等就直接返回;短路放在重试循环内部,每次重写前重新读,保持 revision 围栏语义——值变了仍带 `row.revision` 写入,不会覆盖页面上的并发编辑。 - 写入失败不再死重试 12 次:等服务与 entry 出现是免费的(纯定时器,`WAIT_ATTEMPTS = 12`),真正写入最多两次(`WRITE_ATTEMPTS = 2`);只有 `SETTINGS_CONFLICT` 值得重试一次,其他错误记一条 warn 就放弃。每次重试都可能触发一次插件树重建,代价远高于"这次没写进去"。 - `usageJson` 与 `reportJson` 加 5 分钟节流(`PUBLISH_MIN_INTERVAL_MS`):实测写入上限从约 1814 + 1440 次/天降到各 288 次/天。配置变化(含手动刷新)仍然立即发布,不等节流。 - `diagnosticsJson` 增加指纹比较,`asOf` 之外的内容没变就不写。 - 配置变化监听间隔 `FAST_WATCH_MS` 从 300ms 改为 1000ms。桌面端常开数天,高频率定时器是长期成本;1 秒对手动刷新仍然是即时响应。 ## 0.1.6 修好「模型不能识图」:模态字段读错了表,以及一处自相矛盾的声明。 - 宿主的图片工具会调用 `llm.resolveModelInfo`,只有解析出的路由显式声明了 `image` 才允许读图。`resolveModel` 此前从 `catalog.ts` 的静态目录取模态,而那张表只有 id 和 name,于是**每个**模型解析出的模态都是空,连视觉模型也被判成不能识图。现在改读 `MODEL_DEFAULTS`,与选择器和请求门控同源。 - `CatalogModel` 不再携带 `inputModalities` 字段,从类型上排除了两处再次分叉。 - `deepseek-v4-flash` 的模态改为 `text, image`。官方价格页脚注说它与 `deepseek-v4-flash-vision-exp` 都由 DeepSeek-V4.1-Flash-1017 承接,此前把同一个后端写成了两种能力。 - 每个模型的模态现在都对应一条厂商文档里的明确表述(DeepSeek 价格页的「图像理解」行、智谱的介绍页、Kimi 的模型页、MiMo 的模型列表、腾讯把视觉放在独立的 vision 系列),出处列在 `docs/models.md`。测试会把 12 个模型强制分进「支持图片」和「仅文本」两组并要求并集等于全集,漏写会失败。 - README 补三张设置页截图。 ## 0.1.5 修复历史回放在部分 Node 24 上重复计数。 - 会话日志每个事件一帧。此前的读法是「整块尾部解码一次,再跳到下一个帧头」,依赖「一次解码只返回第一帧」这个前提。部分 Node 24.x 构建一次解码会连带后面的帧,于是每一轮都重复解出剩余全部内容,三个事件被算成五个(CI 实测 `[2,3,2,3,3]` vs `[2,3]`)。 - 现在按帧边界切片,一片一帧,与运行时是否连帧无关。切片会自动加宽直到解码成功,所以压缩后恰好含帧头字节的那一帧也不会被切断。 - 本机的 zstd 不连帧,无法复现该 bug,因此把解码器做成可注入参数:测试用一个「会连帧」的模拟解码器验证,再与真实解码器交叉核对。把算法换回旧版,这两个测试立刻失败。 - 顺带修正:非 zstd 的日志(纯文本 fixture 或未来格式)此前会被当成空会话,现在按原样读出。 宿主兼容矩阵进入 CI。 - `engines.dsh` 写着 `>=0.2.0-rc.1`,但此前只在本机当前版本上验证过。版本范围是承诺,不是证据。 - 新增 `npm run test:hosts`:对每个受支持的宿主版本,在临时目录里装齐该版本的 12 个 peer,然后依次检查三层 - **符号**:产物导入的每个 `@deepseek-ai` 符号是否仍存在。导出被改名或删除时导入本身不报错,要到首次调用才炸,所以挂载通过不能替代这一层; - **类型**:源码能否用该版本的声明文件型检通过,抓符号还在但签名漂移的情况; - **挂载**:真实 Cordis 运行时是否接受产物入口。 - 覆盖 `0.2.0-rc.1`(范围下界)、`0.2.0-rc.2`、`0.2.1-rc.2`(当前最新)。三版全绿。 - 两条反向验证已做:向产物注入不存在的导出,`symbols` 报 `thisExportDoesNotExist`;向源码写类型错误,`types` 报 `TS2322`。不会永远通过。 - CI 新增 `hosts` 作业独立运行,避免拖慢主矩阵的四条组合。 ## 0.1.4 模型没注册时,页面顶部直接说明原因。 - 此前插件未注册时,设置页照常显示,只有页面中部一行「未注册」,而会话的模型选择器是空的。用户无法判断是没装好、没启用,还是凭据没解析——这三种情况的处理方式完全不同。 - 现在顶部会列出具体原因:开关是关的、凭据没解析到、或 profile 条目缺失(后者通常是安装被中断留下的半装状态),并给出各自的处理办法。 - 路由正常时不显示任何提示,避免用户习惯性忽略告警。 ## 0.1.3 启动时检查有没有新版本。 - DSH 没有插件级的「更新」入口,已安装的副本无法得知作者发了新版本,用户唯一的途径是人来通知。 - 现在每次启动读一次 npm registry,设置页显示「有新版本 X(当前 Y)」并附两条安装命令。 - 版本号按段做数字比较。若按字符串比较,`0.10.0` 会排在 `0.9.0` 下面,恰好藏掉它本该报告的那个更新。 - 构建时会校验 `PLUGIN_VERSION` 与 `package.json` 一致,不一致直接失败,避免比较到错的构建产物。 ## 0.1.2 修复推理等级。 - **每个模型只显示它真正支持的推理等级。** 此前所有模型共用四个等级,方向错了两头: - DeepSeek 系列的 `ultra` 和 `max` 从未出现过,可选上限比实际能力低两级。 - GLM-5.3 只接受 `low`、`high`、`max`,选到别的值网关直接返回 400。 - 现在会按模型过滤,并且把默认等级钳制到该模型支持的范围内(GLM 的默认原本是 `medium`,提交即报错)。 - 等级列表补全为 `none / minimal / low / medium / high / xhigh / ultra / max`,由弱到强排列。 ## 0.1.1 配额刷新的可用性与开销。 - **勾选「使用本机 OpenDesign 的登录凭证读取额度」后立即刷新。** 此前要等下一次轮询,并且往往要先发生一次对话才看到数字,开关看起来像没生效。 - **稳态轮询从 5 秒改为 60 秒。** 实测一次读取要跑两次 OpenDesign 命令行、耗时约 2.1 秒;5 秒间隔等于每天约 10 小时的进程时间,只为显示一个不常看的数字。 - **新增手动刷新按钮**,位于额度卡片右上角。 - 文档补充 GitHub 安装方式,并说明 `lib/` 构建产物入库的原因。 ## 0.1.0 首个发布版本。 - 把 OpenDesign Go 套餐的 12 个 Design Plan 模型接入 DSH。 - 通过本机 OpenDesign 桌面端的登录凭证读取真实的 5 小时 / 7 天额度。 - 按模型统计 token 与成本(厂商公开价目表的基础档)。 - 多账号:每个账号是一份独立订阅,可自动故障切换。 - 每模型可调上下文窗口与输出上限;不需要的模型可隐藏。 - 声明支持图片的模型可接收图片输入。