# Development — 开发指南 ## 代码结构 - `lib/index.js` —— **HOST 接线层**:只做 `webServer` 路由注册、`tapIndex` 面板注入、HTTP 小工具与 autostart/清理;逻辑全部在下面四个模块里。 - `lib/config.js` —— 控制文件配置(`dsh-owui-chat2api-control.json`)与共享 路径常量(`PACKAGE_ROOT` / `CHAT2API_DIR` / `DSH_HOME`)。 - `lib/diagnostics.js` —— 缓存式 python/依赖探测(唯一探测 python 的地方, TTL 20s,过期后台重探)。 - `lib/proxy-process.js` —— `chat2api.py` 子进程生命周期:proxy 启停、登录 流程与结果记账、进程日志尾部、python 子进程的 env 白名单。 - `lib/effort-scan.js` —— 一键扫描后端模型,把模型 + `reasoningEfforts` 写入 `~/.dsh/settings.yaml`(自动备份、写后自验证)。 - `lib/prices.js` —— 手动价格表(每百万 tokens、单一货币符号),host 在 `/api/usage` 上叠加逐模型成本;未填价的模型只统计、不计费。 - `lib/panel.js` —— **CLIENT** 半区:注入 DSH 网页壳的 Vanilla JS 面板逻辑 (车头自包含,不使用 Cordis 客户端模块);图标为内联 Tabler SVG (`ICONS` 表 + `ic()` 助手,零外部请求,stroke 跟随主题色)。 - `lib/panel-i18n.js` —— 面板中英字典(`window.__dshOwuiI18n`),host 在 `panel.js` 之前注入;panel.js 读不到时降级显示 key,不会崩。 - `lib/panel.css` —— 面板样式,由 host 经 `/dsh-owui-chat2api/panel.css` 提供,`tapIndex` 以 `` 注入(独立文件,浏览器可缓存)。 - `lib/settings-patch.js` —— 文本级 YAML 补丁器:向 `~/.dsh/settings.yaml` 写入/更新 `reasoningEfforts` 与模型列表(保留注释和顺序,不整文件回写)。 - `chat2api/chat2api.py` —— 内置的 Open WebUI 反代(一个上游项目的快照,见 THIRD_PARTY_NOTICES.md)。DSH 侧新增的用量统计/估算已抽到同目录 `owui_usage.py`,内嵌 dashboard 已抽到 `dashboard.html`(运行期读取一次后 缓存),使本文件保持贴近上游、便于 diff。 - `chat2api/token.json`、`usage.db`、`.chrome-profile/` —— **运行期产物**, 绝不允许进入提交或发布物。 ## 目录布局(安装副本) ``` ~/.dsh/plugins/dsh-owui-chat2api-/ package.json name: dsh-owui-chat2api, dsh.bundle.patch -> cordis.patch.yml cordis.patch.yml 把插件行插入 profile 的组合文件 lib/index.js HOST:路由 / tapIndex / autostart(接线层) lib/config.js HOST:控制文件配置 + 共享路径 lib/diagnostics.js HOST:python/依赖缓存探测 lib/proxy-process.js HOST:子进程生命周期 / env 白名单 / 日志尾部 / 登录记账 lib/effort-scan.js HOST:模型与 reasoningEfforts 写入 settings.yaml lib/prices.js HOST:手动价格表 + 逐模型成本估算 lib/panel.js CLIENT:自包含控制面板(逻辑) lib/panel-i18n.js CLIENT:面板中英字典(先于 panel.js 注入) lib/panel.css CLIENT:面板样式(/panel.css 提供) lib/settings-patch.js settings.yaml 文本补丁器 chat2api/ chat2api.py + owui_usage.py + dashboard.html + requirements.txt(.chrome-profile 运行期才有) ``` (`test/` 只在仓库里,不进安装副本。) ## 运行期数据放哪 - 配置:`/dsh-owui-chat2api-control.json`(`node_modules` 可只读)。 - 用量库:`/dsh-owui-chat2api-usage.db`——通过环境变量 `DSH_OWUI_USAGE_DB` 传给 python(独立运行时回退到目录内 `usage.db`)。 - 登录凭据:`/dsh-owui-chat2api-token.json`——通过环境变量 `DSH_OWUI_TOKEN_FILE` 传给 python(首次运行会把旧版插件目录里的 `token.json` 迁移过来)。 - 价格表:`/dsh-owui-chat2api-prices.json`——每模型 `{input, cached, output}` 单价(每百万 tokens)+ 货币符号,面板可直接编辑。 ## 路由(同源) | Method | Path | 用途 | | ------ | ---- | ---- | | GET | `/dsh-owui-chat2api/panel.js` | 面板脚本(经 tapIndex) | | GET | `/dsh-owui-chat2api/panel-i18n.js` | 面板字典(先于 panel.js 注入) | | GET | `/dsh-owui-chat2api/panel.css` | 面板样式(经 tapIndex) | | GET | `/dsh-owui-chat2api/api/status` | 配置 + 运行状态 + 诊断 + 最近一次登录结果 + 日志尾部 | | GET/POST | `/dsh-owui-chat2api/api/config` | 读取 / 保存配置(false→true 时自动启动) | | POST | `/dsh-owui-chat2api/api/start` | 启动 `chat2api.py` | | POST | `/dsh-owui-chat2api/api/stop` | 停止 `chat2api.py` | | POST | `/dsh-owui-chat2api/api/login` | 一次性 Open WebUI 登录 | | POST | `/dsh-owui-chat2api/api/effort-scan` / `effort-scan-force` | 启动模型扫描(立即返回,结果经 status 轮询 + 面板通知) | | GET | `/dsh-owui-chat2api/api/usage?range=` | 同源代理到 `http://:/v1/usage`,host 侧叠加逐模型成本 | | GET/POST | `/dsh-owui-chat2api/api/prices` | 读取 / 保存手动价格表 | 用量走同源(经 `/api/usage`),所以 HTTPS 与外网访问 DSH 时也没有混合内容 / CORS 问题。 ## 核心约定(改代码前先读) - **任何 web handler 都不得阻塞事件循环**:python/依赖探测是缓存式的 (TTL 20s),过期后由**真异步** probe(spawn,非 spawnSync)后台重探; start/login 也在请求路径之外启动子进程。改这块时保持这个不变量。 - `startImpl` / `loginImpl` / `startEffortScan` 返回 `{ async: true }`,面板靠 轮询收敛最终状态(登录结果在 status 的 `login` 字段,扫描在 `effortScan`)。 - 给 python 子进程的 env 是**白名单**(`childEnv()`),不是整个 `process.env` ——以免把 DSH 里其它服务的密钥泄漏给第三方代理。 - `settings-patch.js` 只做文本级修补(保留注释/顺序)。写完要能通过自身的 verify 步骤;schema 若随 DSH 变动,这里会静默失效,改动时要留意。 - 日志尾部 `LOG_MAX = 80` 行,逐行接受(面板会按行做严重度着色),不要改成 整体字符串。 - 面板的**一切动作反馈都走吸顶 `.ow-notice`**(`setBanner`),不要另起一套 内联提示。 ## 本地测试你的改动(源码 → 安装副本) 仓库是**唯一真源(source of truth)**。DSH 真正跑的是 `~/.dsh/plugins/dsh-owui-chat2api-` 的安装副本(活的 `.chrome-profile` 也在这里,**绝不能提交**)。改完代码,把改动装进安装副本: ```powershell powershell -ExecutionPolicy Bypass -File scripts\pack.ps1 # 用 package.json 的当前版本 powershell -ExecutionPolicy Bypass -File scripts\pack.ps1 -Version 0.8.0 # 或指定版本 ``` 脚本用 `robocopy /MIR` 组装到 `~/.dsh/plugins/dsh-owui-chat2api-`, 然后**自动重链**:重建 junction(版本号变了才需要)、把 profile `package.json` 的依赖改成 `link:<新目录>`(保留原格式,强制 UTF-8 **无 BOM** 写回——DSH 的 JSON 解析器不认 BOM)。它**按构造排除** `.chrome-profile/`、 `usage.db`、`token.json`、`__pycache__`、`.git` 与构建噪声——`/MIR` 不会删除 目标根目录,也不碰 `/XD` 排除的目录,所以代理正在跑时也能安全重打包,不会 误删在线登录态。 所以本地实测循环就两步: 1. `powershell -ExecutionPolicy Bypass -File scripts\pack.ps1` 2. 重启 DSH Desktop 重启 DSH 后 `lib/*` 生效;**只改了 `chat2api.py` 不用重启**(它每次 spawn 都从 磁盘读)。 ## 测试 - `npm test`(即 `node --test test/settings-patch.test.js`)——覆盖 `lib/settings-patch.js` 的全部行为:effort 块插入位置与缩进、幂等 (二次运行 `NO_CHANGE`)、CRLF 检测与保持、注释/无关 provider 字节级保留、 `NO_PROVIDER`/`NOOP` 失败路径、空 `models:` 列表、`ensureProvider` 全链创建 与重名避让(`chat2api-1`),以及 effort-scan "写后自验证"依赖的 **三轮 round-trip 不变量**(create → patch → 再跑一遍全部报 already)。 - settings.yaml 是用户的核心配置,**改 `settings-patch.js` 必须先跑测试**; 新增行为先加测试再改实现。 - `test/` 是纯开发工件:不进 npm 发布(`files` 白名单不含),也不进打包 副本(pack.ps1 `/XD` 排除)。 ## 备注 - 面板是经 `webServer.tapIndex` 注入的覆盖层,**不是** Settings 导航槽(那个 槽只给 Cordis 动态插件用)。 - 如果 DSH 的 agent 会话本身走这条代理,停掉它会把当前聊天打断——控件已有 保护,这必须是有意的操作。 - `scripts/pack.ps1` 是 Windows 路径;如需 mac/linux,加一个对等脚本。