Skip to content

PLAN-FNOS-026 OpenCode Zen 与 OpenCode Go 模型接入及套餐用量 ​

字段内容
计划编号PLAN-FNOS-026
计划日期2026-10-23
对应需求FNOS-026 OpenCode Zen 与 OpenCode Go 模型接入及套餐用量
本轮功能FNOS-026-01、FNOS-026-02、FNOS-026-03、FNOS-026-04、FNOS-026-05、FNOS-026-06、FNOS-026-07、FNOS-026-08、FNOS-026-09
计划状态已完成(插件已发布到 npm,任务全部完成并通过验收)

计划目标 ​

按 FNOS-026 的功能编号实现一个不依赖 fnOS 宿主桥接的 DSH 插件 @tnnevol/dsh-opencode:

  • FNOS-026-01:宿主侧的 Console 登录(设备码与 API Key)与凭据持久化;已登录时账号卡片隐藏 API Key 登录入口;
  • FNOS-026-02:把 OpenCode Zen(按量)与 OpenCode Go(套餐)两组活 roster、公开目录与账号级覆盖合并注册成 DSH 模型供应商 OpenCode(路由 dsh-opencode);每个模型的显示名追加 · zen / · go 后缀(同名模型因此可区分),内部 id 用 zen: / go: 前缀保证唯一;
  • FNOS-026-03、FNOS-026-04:按上游目录把协议族、容量、推理、工具、图片输入、文本与免费标记投影到合并列表中的模型,并保证对话可用;
  • FNOS-026-05、FNOS-026-06:Go 套餐额度卡片与对话输入区用量胶囊;zen 后缀模型不显示套餐额度,胶囊 auto 策略只对 go 后缀模型挂出;
  • FNOS-026-07、FNOS-026-08:模型目录管理(固定高度、搜索、计数与能力图标、按 plan:id 逐模型显隐)、登录态与偏好持久化;
  • FNOS-026-09:文档站说明页与菜单;市场源列表登记随 npm 发布执行。

技术边界:只新增插件与本仓库文档,不改 DSH 核心、其它插件与既有需求文档。FNOS-026-09 本轮完成页面与菜单;源列表登记、市场索引重生成与应用发布清单登记依赖 npm 发布,列为待执行任务。

当前实现与目标设计 ​

领域当前实现目标实现迁移影响
供应商路由本插件曾注册两组路由 dsh-opencode(OpenCode)与 dsh-opencode-go(OpenCode Go),同名模型在两个分组各出现一次只注册一个路由 dsh-opencode(显示名 OpenCode);Zen 与 Go 两类来源在同一个供应商下列表选择器只出现一个 OpenCode 分组;退出登录时该分组整体撤销;与手工配置 opencode / opencode-go 并存
模型标识与显示名两组路由各自使用原始模型 id,同名模型只能靠供应商分组区分合并列表内模型 id 用 zen:<真实 id> / go:<真实 id> 前缀保证唯一,显示名追加 · zen / · go 后缀;发请求前剥离前缀目录、选择器与会话里显示后缀;网关仍收到原始 id;偏好与隐藏列表按 plan:id 记录
模型目录两组目录各自归一、各自缓存,目录卡片用分组筛选区分来源两类来源分别取得 roster / 公开目录 / Console 覆盖后合并为一份目录,条目携带 plan;某来源失败只影响该来源条目一份快照、一套计数;Console 覆盖仍按来源应用;目录来源说明按来源列出
模型能力投影两组投影;能力字段为推理、工具调用、图片输入、文本输入、免费、上下文、最大输出;目录行显示 plan 标签合并投影;能力字段不变;目录行以 · zen / · go 后缀表达来源,能力仍用图标与悬浮气泡表达五项勾选目录 UI 与选择器字段同步;plan 标签移除
账号登录单账号登录;同一凭据服务两组路由同一 Console 账号与同一凭据服务合并后的一个供应商账号卡片交互不变;凭据格式与持久化位置不变
套餐额度两组供应商下额度只对 OpenCode Go 生效额度仍只对 OpenCode Go 生效;合并列表内以 go 后缀(内部键 go: 前缀)识别;胶囊 auto 策略只在选中这类模型时挂出额度卡片仍按账号读取 Go 额度;zen 模型不占用套餐额度
插件详情页目录卡片固定高度、内部滚动,工具栏含「全部 / Go / Zen」分组筛选、plan 标签与计数目录卡片固定高度、内部滚动;工具栏只保留搜索与计数(含已隐藏数);每行显示后缀、能力图标与逐模型显隐开关移除分组筛选与 plan 标签;显隐偏好改按 plan:id 保存
分发与文档仓库中没有该插件的页面、菜单条目与市场条目说明页 + 插件总览行 + 菜单条目;发布后登记市场源列表、重生成索引并登记应用发布清单文档站与市场可见性随发布推进

供应商注册从两组路由改为单路由合并列表,属于本计划内部的实现口径变更,下面用状态图与时序图标出变更点。

供应商注册的状态变化:旧实现注册两组 dsh- 路由并把同名模型分到两个分组;新实现只注册一个 dsh-opencode,在合并列表内用后缀区分来源。

mermaid
stateDiagram-v2
  [*] --> 未登录
  未登录 --> 已登录: 登录成功
  已登录 --> 旧双路由注册: 旧实现
  旧双路由注册 --> 两组供应商: 变更点:选择器出现两个分组
  两组供应商 --> 同名模型重复: 变更点:同名模型在两个分组各出现一次
  已登录 --> 单路由注册: 新实现
  单路由注册 --> 合并列表可用: 变更点:只出现一个 OpenCode 分组
  合并列表可用 --> 未登录: 退出登录
  同名模型重复 --> 未登录: 退出登录

登录后的注册与请求时序:旧实现注册两组路由;新实现只注册 dsh-opencode,并在发请求前把合并用的前缀剥掉,网关收到的仍是原始模型 id。

mermaid
sequenceDiagram
  participant User as 用户
  participant Plugin as 插件宿主
  participant LLM as DSH 模型层
  participant Selector as 模型选择器
  participant Gateway as OpenCode 推理网关
  User->>Plugin: 登录 OpenCode Console
  Note over Plugin,LLM: 变更点:旧实现注册 dsh-opencode 与 dsh-opencode-go 两组路由
  Plugin->>LLM: registerAdapter('dsh-opencode')
  Plugin->>LLM: registerAdapter('dsh-opencode-go')
  LLM-->>Selector: OpenCode 与 OpenCode Go 两个分组,同名模型重复
  Note over Plugin,LLM: 变更点:新实现只注册一个 dsh-opencode,合并目录内用 zen: / go: 区分
  Plugin->>LLM: registerAdapter('dsh-opencode')
  LLM-->>Selector: 一个 OpenCode 分组,模型名带 · zen / · go
  Note over Plugin,Gateway: 变更点:发请求前剥离 zen: / go: 前缀
  Plugin->>Gateway: 以原始模型 id 发起推理

影响范围分析 ​

需求功能代码模块配置与数据测试文档目标环境
FNOS-026-01plugins/dsh-opencode-plugin/src/host/(登录、凭据)、src/contracts/(状态与路由)、src/client/(账号卡片)$DSH_HOME/.dsh-opencode-auth.json(0600)登录态、非登录态、登录流程与已登录隐藏 API Key 入口用例说明页「登录」小节DSH Web、DSH Desktop
FNOS-026-02合并供应商注册、目录服务、模型键与路由状态诊断无新增用户配置;目录快照仅内存缓存合并列表、前后缀、Console 增补/停用、来源回退与路由冲突回归用例说明页「模型」小节DSH Web、DSH Desktop
FNOS-026-03目录归一化与模型投影(推理、工具、图片、文本、免费、容量)无能力字段、协议族、图片输入、思考等级与图标气泡用例说明页「模型能力」小节DSH Web、DSH Desktop
FNOS-026-04适配器调用路径(含请求前剥离 plan 前缀)与上游错误呈现会话数据格式不变文本、图片、402/403 与失败路径用例说明页常见问题DSH Web、DSH Desktop
FNOS-026-05额度服务、宿主路由、额度卡片无持久化;快照带获取时间三个窗口、倒计时、未订阅与失败态用例说明页「套餐额度」小节DSH Web、DSH Desktop
FNOS-026-06输入区插槽组件与 go 后缀过滤$DSH_HOME/.dsh-opencode-preferences.json三种策略、仅 go 后缀模型显示与数据一致性用例说明页「用量胶囊」小节DSH Web、DSH Desktop
FNOS-026-07目录卡片(固定高度、搜索、计数、能力图标、按 plan:id 显隐、刷新)、偏好读写$DSH_HOME/.dsh-opencode-preferences.json搜索、滚动、同名条目分别隐藏、刷新用例说明页设置表与模型目录小节DSH Web、DSH Desktop
FNOS-026-08凭据与偏好存储上述两个宿主文件重启后持久化与隔离用例说明页已知约束DSH Web、DSH Desktop
FNOS-026-09docs/plugins/、docs/.vitepress/config.mts、docs/public/plugins/market/plugins.txt 与三处索引无文档构建、菜单链接与源列表核对本需求的三份 SDD 文档与插件说明页文档站构建环境

架构与数据流 ​

插件分宿主与浏览器两侧:宿主侧负责凭据、合并后的模型目录、Go 套餐额度与单一供应商适配;浏览器侧只渲染详情页卡片与输入区胶囊,两者通过插件同源 Web 路由通信。所有上游请求由宿主发出,浏览器不接触令牌。

mermaid
flowchart LR
  subgraph Client["DSH 浏览器半侧"]
    Page["插件详情页:账号 / 额度 / 合并目录(搜索、计数、能力图标、显隐)"]
    Pill["对话输入区用量胶囊(auto 仅 · go 模型)"]
  end
  subgraph Host["DSH 宿主半侧(@tnnevol/dsh-opencode)"]
    Routes["同源 Web 路由"]
    Auth["账号服务与凭据存储"]
    Directory["模型目录服务(Zen + Go 合并,plan:id)"]
    Usage["Go 套餐额度服务"]
    Prefs["偏好存储"]
    Adapter["模型供应商适配器(dsh-opencode;请求前剥离 plan 前缀)"]
  end
  subgraph Upstream["OpenCode"]
    Console["OpenCode Console(账号、覆盖配置、额度)"]
    ZenRoster["OpenCode Zen 活 roster"]
    GoRoster["OpenCode Go 活 roster"]
    Public["公开目录 api.json"]
    Gateway["OpenCode 推理网关"]
  end
  Page --> Routes
  Pill --> Routes
  Routes --> Auth
  Routes --> Directory
  Routes --> Usage
  Routes --> Prefs
  Auth --> Console
  Directory --> Console
  Directory --> ZenRoster
  Directory --> GoRoster
  Directory --> Public
  Usage --> Console
  Adapter --> Directory
  Adapter --> Gateway

目录与额度的刷新都走「先看缓存是否过期,再决定是否打上游」的路径;两类来源分别回退、合并成一份快照,Console 配置读取失败只影响账号覆盖,不影响模型全集:

mermaid
sequenceDiagram
  participant UI as 详情页 / 用量胶囊
  participant Routes as 插件 Web 路由
  participant Service as 目录与额度服务
  participant Console as OpenCode Console
  participant Roster as Zen / Go 活 roster
  participant Public as 公开目录
  UI->>Routes: 读取账号状态、目录与额度
  Routes->>Service: 判断缓存是否过期
  alt 已过期或用户强制刷新
    Service->>Console: 读取账号覆盖配置与 Go 额度(带账号令牌)
    Service->>Roster: 分别读取 Zen 与 Go 活 roster
    Service->>Public: 读取 api.json 的 opencode / opencode-go 段
    alt Console 不可用
      Console-->>Service: 失败原因
      Note over Service: 仍用两组 roster 与公开目录构建合并目录,只记录未应用账号覆盖
    end
    alt 某组 roster 不可用
      Roster-->>Service: 失败原因
      Service->>Public: 回退该来源公开目录段与内置目录
      Note over Service: 保留该来源上一份快照,不清空另一来源
    end
    Note over Service: 合并条目:显示名加 · zen / · go,稳定键为 plan:id
  end
  Service-->>Routes: 合并快照、获取时间与降级说明
  Routes-->>UI: 账号状态、模型目录、Go 额度

分阶段任务 ​

阶段一:宿主侧账号与凭据 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-026-T01-01FNOS-026-01 / AC-01、AC-02设备码登录状态机:发起授权、把一次性代码与验证地址交给详情页、读取授权结果、处理取消与过期;挑战只保存在宿主侧无单测用桩 Console 覆盖「授权成功」「取消」「过期」三条路径,状态迁移与凭据写入时机正确
PLAN-FNOS-026-T01-02FNOS-026-01 / AC-03服务账号 API Key 登录:校验输入、失败提示、与设备码登录共用同一份凭据记录T01-01 的路由形状无效 Key 得到可读错误且不写入凭据;有效 Key 进入已登录
PLAN-FNOS-026-T01-03FNOS-026-01 / AC-04、AC-05;FNOS-026-08 / AC-01凭据存储:宿主独占读写,文件 0600、目录 0700,原子写与文件锁,落盘格式带版本号;退出登录删除凭据无文件权限与格式断言;页面源码、网络响应与本地存储中查不到令牌明文;重启后登录态保持
PLAN-FNOS-026-T01-04FNOS-026-01 / AC-02、AC-03;FNOS-026-04 / AC-04凭据过期续期与失效降级:过期前续期,续期失败回到未登录并说明原因,不产生半登录状态T01-03桩时钟下过期触发续期;续期失败后状态与凭据一致

阶段二:模型目录与供应商适配 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-026-T02-01FNOS-026-02 / AC-01解析账号覆盖配置:按 opencode / opencode-go 两个 provider 读取工作空间级条目(可为空)、增补条目、停用标记、能力与容量覆盖,以及 provider 级 x-opencode-* 路由头;空条目按「无额外覆盖」处理,结构不符时返回「不可读」而不是空目录凭据可用(T01-03)用响应夹具断言空条目、增补、停用、能力覆盖与不可读分支
PLAN-FNOS-026-T02-02FNOS-026-02 / AC-02、AC-03模型全集来源与回退:Zen 与 Go 分别以活 roster 为准;某来源 roster 不可用时用该来源公开目录的非弃用条目,再失败回退内置目录或该来源上一份快照并记录失败原因;账号配置不可读不影响模型全集T02-01夹具覆盖「两组 roster 可读」「单组 roster 不可读」「两组都失败」;空 Console 条目仍得到完整目录
PLAN-FNOS-026-T02-03FNOS-026-03 / AC-01、AC-02、AC-03、AC-04能力归一化:按 Console 的 aisdk: 包名或公开目录的 npm 包名识别协议族,投影端点、上下文窗口、最大输出、推理、工具调用、图片/文本输入、免费标记与逐模型思考等级;跳过图片生成与无法识别协议的条目并记录说明T02-02公开目录夹具断言四种协议族、七项能力字段与容量;图片生成条目被跳过且给出说明
PLAN-FNOS-026-T02-04FNOS-026-02 / AC-01、AC-05只向模型层注册 dsh-opencode 一个路由并投影合并目录;用户手工配置的 opencode / opencode-go 不与插件冲突,同名 dsh-opencode 冲突给出可诊断错误T02-01、T02-03注册后选择器只出现一个 OpenCode;预置 opencode 配置时仍出现;同名 dsh-opencode 冲突场景得到明确错误
PLAN-FNOS-026-T02-05FNOS-026-07 / AC-03、AC-04;FNOS-026-08 / AC-02目录缓存与刷新:按刷新间隔节流、强制刷新忽略节流、并发刷新合并为一次;两类来源共享一次刷新但各自保留来源快照与失败原因T02-02桩计时验证节流与合并;单来源刷新失败只影响该来源条目
PLAN-FNOS-026-T02-06FNOS-026-05 / AC-01、AC-02、AC-03、AC-04;FNOS-026-06 / AC-01额度服务:按账号令牌读取 Go 套餐额度,归一化 5 小时、每周、每月三个窗口(已用百分比、重置时间、限流状态)与获取时间;胶囊只在选中带 go: 前缀的模型时挂出,zen: 模型不触发套餐额度请求;额度卡片仍按账号读取 Go 额度;缺字段显示未知、失败保留上一份快照T01-03响应夹具覆盖三窗口、缺字段与失败三种情况;失败时不产生 0%;选中 zen: 模型时胶囊不挂出、不请求额度
PLAN-FNOS-026-T02-07FNOS-026-02 / AC-01、AC-04、AC-05;FNOS-026-01 / AC-04;FNOS-026-04 / AC-01合并供应商与 zen/go 后缀:契约层只定义一个供应商 OpenCode(路由 dsh-opencode),plan 作为内部维度;合并列表内模型 id 用 zen: / go: 前缀、显示名用 · zen / · go 后缀;适配器在 stream / streamSimple 入口用 wireModelId 剥掉前缀;目录端点返回带 plan 的合并列表,显隐偏好使用稳定键 plan:id;退出登录时撤销该供应商;记录路由 id、前后缀与 plan:id 的稳定契约T02-04单测断言单供应商描述、前后缀映射与 wireModelId;选择器只出现一个分组;退出登录后分组消失;已配置 opencode 的回归场景通过

阶段三:插件详情页与模型目录 UI ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-026-T03-01FNOS-026-01 / AC-01、AC-02、AC-03账号卡片:未登录、授权中(含一次性代码与复制按钮)、已登录(账号与组织)、错误四种状态;已登录时不渲染「使用 API Key 登录」入口,只保留退出登录T01-01、T01-02DSH Web 与 DSH Desktop 上逐状态目视 + DOM 断言;已登录状态查不到 API Key 登录按钮
PLAN-FNOS-026-T03-02FNOS-026-05 / AC-01、AC-02、AC-03、AC-04额度卡片:三个窗口的已用百分比、重置倒计时、获取时间与刷新按钮;未订阅 Go 时显示未订阅说明;失败态显示原因T02-06亮暗主题下百分比与倒计时可读;失败态不显示 0%;未订阅态不显示 0%
PLAN-FNOS-026-T03-03FNOS-026-07 / AC-01、AC-02目录卡片:搜索、计数、按 plan:id 的逐模型显隐开关、刷新与获取时间;同名 zen/go 条目可分别隐藏T02-04隐藏 zen: 条目后 go: 同名条目仍在;选择器不再出现被隐藏的条目;恢复后重新出现
PLAN-FNOS-026-T03-04FNOS-026-06 / AC-01、AC-02、AC-03、AC-04输入区用量胶囊:按策略显隐、显示已用比例与重置时间、修改策略后即时生效;auto 策略只在当前选中模型带 go: 前缀时挂出T03-02 的额度数据三种策略各断言一次;选中 zen: 模型时不显示胶囊;亮暗主题下均可读
PLAN-FNOS-026-T03-05FNOS-026-08 / AC-02;FNOS-026-07 / AC-02偏好设置区:胶囊策略与自动刷新间隔的读写、校验与持久化T01-03 的宿主文件约定重启后偏好保持;越界或非法值回落到默认值
PLAN-FNOS-026-T03-06FNOS-026-06 / AC-03胶囊与额度卡片同源:同一份快照驱动两处展示,刷新后同步更新T03-02、T03-04两处已用比例一致,不出现两个数字
PLAN-FNOS-026-T03-07FNOS-026-02 / AC-04;FNOS-026-03 / AC-05;FNOS-026-07 / AC-05;FNOS-026-01 / AC-03模型目录 UI 合并:单个 OpenCode 合并列表;目录卡片固定高度、内部滚动,搜索框与计数固定;移除「全部 / Go / Zen」分组筛选与 plan 标签,来源由模型名 · zen / · go 后缀表达;能力改为「推理 / 工具 / 图片 / 文本 / 免费」图标,悬浮气泡显示勾选;每行保留逐模型显隐开关(按 plan:id);已登录时移除 API Key 登录入口T02-07、T03-01、T03-03DSH Web 与 DSH Desktop 亮暗主题走查;DOM 断言只有一组供应商、无分组筛选控件、计数与滚动容器固定;同名条目显示不同后缀且可分别隐藏;悬浮能看到气泡勾选;已登录状态无 API Key 入口

阶段四:文档站与发布登记 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-026-T04-01FNOS-026-09 / AC-01、AC-02新增 docs/plugins/dsh-opencode.md;在插件总览加入一行并修正运行时插件数量表述;在 /plugins/ 菜单加入条目无文档构建通过;菜单与页面可达
PLAN-FNOS-026-T04-02FNOS-026-09 / AC-03随 npm 发布执行:在插件市场索引源列表加入 @tnnevol/dsh-opencode;不手工改写生成的索引文件npm 发布成功源列表含该包;生成的索引文件未被手改
PLAN-FNOS-026-T04-03全部需求、计划与测试用例文档登记:docs/requirements/、docs/plans/、docs/tests/ 与本页,同步三个索引与三处菜单无pnpm run check -- --sdd 通过;文档构建通过
PLAN-FNOS-026-T04-04FNOS-026-09 / AC-03插件发布后重新生成市场索引并核对条目,随后登记到应用发布清单npm 发布成功;发布前确认目标版本可用索引条目与源列表一致;应用安装后插件可见

阶段五:验证与收尾 ​

任务 ID对应需求/验收修改内容前置条件验证方式
PLAN-FNOS-026-T05-01全部插件的类型检查、单测与构建阶段一至三pnpm --filter @tnnevol/dsh-opencode run check 与 build 通过
PLAN-FNOS-026-T05-02FNOS-026-01 至 FNOS-026-08DSH Web 与 DSH Desktop 走查,按测试用例文档执行;包含单供应商与 zen/go 后缀、合并目录、能力图标与 opencode 路由冲突回归T05-01用例结论回填 docs/tests/FNOS-026-dsh-opencode-plugin.md
PLAN-FNOS-026-T05-03FNOS-026-09 / AC-04文档站构建与菜单链接核对T04-01 至 T04-03构建通过、菜单目标均存在;git diff --check 干净
PLAN-FNOS-026-T05-04全部版本更新:按仓库版本流程推进插件与应用版本(功能提交后独立版本提交)T05-01 至 T05-03版本串一致,发布流程可执行

交互和行为设计 ​

插件详情页 ​

入口为「插件管理 → OpenCode → 详情页」。页面由上到下依次是账号卡片、套餐额度卡片、模型目录卡片与设置区;所有操作只改插件自己的数据。

区块状态用户可见内容可用操作
账号卡片未登录两种登录方式说明,「设备码登录」与「用 API Key 登录」入口发起登录
账号卡片授权中一次性代码、复制按钮、验证地址、剩余有效期取消登录
账号卡片已登录账号、组织、登录方式与登录时间;只显示退出登录,不显示 API Key 登录入口退出登录
账号卡片错误失败原因与可执行的处置重试
额度卡片有数据OpenCode Go 的 5 小时、每周、每月三个窗口的已用百分比、重置倒计时、获取时间刷新
额度卡片失败或未订阅失败原因与最近一次获取时间,或未订阅 Go 的说明;两者都不显示 0%刷新
目录卡片有数据固定高度的滚动列表;固定的搜索框与计数;每行包含名称与 · zen / · go 后缀、标识、能力图标、容量与逐模型显隐开关搜索、隐藏/恢复、刷新
目录卡片空或失败说明与重试入口;失败时保留上一份列表刷新
设置区始终用量胶囊策略、自动刷新间隔修改并保存

模型目录卡片 ​

目录卡片自身固定高度,只有模型列表区内部滚动;卡片头部与工具栏(搜索框、计数、刷新)不随列表滚动,长目录不会把整页撑高。

列表项内容
来源与名称模型名后追加 · zen(按量)或 · go(套餐),与内部键 zen:<id> / go:<id> 一一对应
标识与容量原始模型 id、上下文窗口与最大输出
能力「推理 / 工具 / 图片 / 文本 / 免费」五个图标;悬浮时显示气泡,逐项标记勾选或未勾选
显隐逐模型开关;偏好按 plan:id 保存,同名 zen/go 条目可分别隐藏
  • 目录卡片不再提供「全部 / Go / Zen」分组筛选;一个列表内同时展示两类来源,来源由模型名后缀表达。
  • 搜索覆盖名称、id、家族、描述与协议族;计数显示当前搜索结果数,已隐藏数量另行标注。
  • 目录来源、获取时间与刷新入口保留在卡片头部;目录刷新失败时列表保留上一份快照并显示原因。
  • 合并列表内同名条目必须在显示后缀与隐藏键上可区分,不允许出现两个完全相同的行。

用量胶囊 ​

胶囊位于对话输入区右侧,与其它状态控件同行,不遮挡输入框。三种策略的切换关系如下:

mermaid
stateDiagram-v2
  [*] --> auto
  auto --> always: 用户选择常驻
  always --> auto: 用户改回自动
  auto --> off: 用户关闭
  off --> auto: 用户改回自动
  always --> off: 用户关闭
  off --> always: 用户选择常驻
  note right of auto: 仅在选中带 go 后缀的模型时显示
  note right of off: 隐藏并停止自动刷新
  • auto:默认值。只在当前选中的模型属于 OpenCode 且带 go 后缀(内部键以 go: 开头)时显示;带 zen 后缀的模型与其它供应商模型不显示,也不触发套餐额度请求。
  • always:只要插件已登录就常驻显示,与当前选中的供应方无关;没有 Go 套餐数据时显示未知或未订阅,不补 0%。
  • off:完全不显示,并停止额度自动刷新;详情页额度卡片不受影响,仍可手动刷新。

策略变化在宿主侧持久化,下一次进入会话或下一次对话即按新策略显示,无需重启 DSH。

文案与命名 ​

  • 供应商名称固定为 OpenCode,路由 id 固定为 dsh-opencode。
  • 两类来源名称固定为 OpenCode Zen(按量)与 OpenCode Go(套餐);合并列表中的显示名后缀固定为 · zen / · go,内部模型键固定为 zen:<id> / go:<id>。
  • 需要说明计费或约束时写「带 go 后缀的模型」「OpenCode Zen(按量)」,不写成「Zen 套餐」「Go 按量」,也不把后缀解释成独立供应商。
  • 目录卡片不显示 plan 标签;来源统一由模型名后缀表达,不再使用「按量 / Go 标签」的说法。
  • 三个额度窗口统一称「5 小时」「每周」「每月」,不混用「滚动窗口」「周额度」等别名。
  • 「目录来源」按来源说明模型集合来自活 roster、公开目录还是内置目录;账号配置不可读时另附「未应用账号覆盖」说明,不写成「降级」以免被理解为功能受损。
  • 上游限制使用错误码与原文短语(Insufficient account funds、FreeTierError),不翻译成模糊的「额度问题」或「权限问题」。

数据、权限和错误处理 ​

持久化 ​

数据位置权限读取与恢复策略
账号凭据(设备码令牌或 API Key、账号与组织信息)$DSH_HOME/.dsh-opencode-auth.json文件 0600,目录 0700只由插件宿主读写,同一凭据同时服务合并后的一个供应商;解析失败或格式版本不符时按未登录处理并给出可诊断错误,不覆盖原文件
插件偏好(胶囊策略、刷新间隔、隐藏模型)$DSH_HOME/.dsh-opencode-preferences.json文件 0600隐藏模型按 plan:id 保存;缺失或损坏时回落到默认值;越界值夹到合法区间
目录与额度快照进程内存—合并目录与两类来源快照分别缓存;进程重启后重建,不落盘,避免把过期数据当成事实

权限 ​

  • 不新增 DSH 权限项,也不申请 fnOS 宿主权限;插件只做宿主侧文件读写与对 opencode.ai 的出站请求。
  • 凭据文件权限被放宽或属主不可读时,按共享包的既有判定给出可诊断错误,不静默忽略。

错误处理 ​

场景用户可见恢复动作
未登录账号卡片显示登录方式说明发起登录
设备码过期或取消回到未登录并说明原因重新发起
令牌失效且续期失败账号卡片回到未登录并提示重新登录重新登录
账号覆盖配置不可读目录卡片按来源说明当前目录来源与未应用账号覆盖的原因刷新或稍后重试
某来源活 roster 不可读该来源回退公开目录与内置目录;目录卡片显示来源与失败原因刷新
合并目录来源都不可读目录卡片显示失败原因,保留上一份可用目录刷新
额度不可读额度卡片显示未知与失败原因,不显示 0%刷新
账号未订阅 OpenCode Go额度卡片显示未订阅说明,不显示 0%;模型目录仍可使用在 Console 订阅后刷新
Zen 余额不足(402)会话显示上游原文 Insufficient account funds 与可读说明;不自动重试在 Console 充值后重试
Zen 免费模型受限(403)会话显示 FreeTierError 与「仅可在 OpenCode 客户端使用」的说明;DSH 可能把该上游 403 归类为 AUTH 错误,按上游限制说明;不绕过上游限制改用付费 Zen 模型或 Go 模型
模型地区限制(403)会话显示上游原文(RegionError / unsupported_country_region_territory)与说明;不自动换模型、不重试改用当前地区可用的同协议模型
Google 工作空间隐私设置(400)会话显示上游原文与「需在工作空间 Privacy 设置允许会用于训练的付费端点」说明;不绕过在 Console 允许后重试
opencode / opencode-go 手工配置不冲突;插件路由 dsh-opencode 与手工配置并存无需处置
用户手工配置了同名 dsh-opencode详情页说明冲突来源与处置方式;插件不注册、不覆盖用户配置用户移除同名配置后重试
网络不可直达 opencode.ai各卡片显示连接失败检查网络后重试

依赖、风险和决策 ​

依赖 ​

依赖版本用途
@earendil-works/pi-ai1.1.0模型对象与思考等级语义、内置 opencode / opencode-go 目录的兼容开关
@deepseek-ai/dsh-llm、@deepseek-ai/dsh-llm-pi-ai0.2.1-rc.2供应商路由注册与适配器契约
@tnnevol/dsh-plugin-kitworkspace凭据文件权限判定
@tnnevol/dsh-semi-uiworkspace详情页与胶囊的组件与主题
OpenCode Console、Zen/Go 活 roster、公开目录、推理网关上游在线服务账号、合并目录、额度与推理

风险 ​

风险影响缓解
用户手工配置过同名路由旧实现注册 opencode-go 时整组失败,模型不出现已改为唯一 dsh-opencode 前缀,与手工 opencode / opencode-go 配置并存;仅同名的 dsh-opencode 冲突会给出诊断
合并列表内同名模型重复出现用户可能选错来源或隐藏错条目显示名后缀区分来源;隐藏偏好按 plan:id 保存;胶囊只对 go 后缀生效;说明页写明同名模型分属两类计费口径
内部模型 id 带 plan 前缀,请求前未剥离会把前缀发给网关上游可能拒绝请求或找不到模型适配器在 stream / streamSimple 入口统一用 wireModelId 剥离;单测覆盖,2026-10-24 以 zen:glm-5 实测返回上游 402(证明网关收到原始 id)
OpenCode Zen 按量模型需要 Console 余额未充值时模型可选中但调用失败(402)目录与选择器不隐藏模型;会话显示 Insufficient account funds 与可读说明;说明页与已知约束写明需余额,不自动重试
OpenCode Zen 免费模型只允许 OpenCode 客户端使用调用失败(403 FreeTierError,DSH 可能显示为 AUTH),用户误以为是插件缺陷目录仍按 roster 展示;会话按上游限制说明;说明页与测试文档登记为已知上游限制
部分模型受地区限制调用失败(403),用户误以为是插件缺陷目录仍按 roster 展示;会话显示上游原文;说明页与测试文档登记为已知上游限制
Gemini 需要工作空间隐私设置调用失败(400),用户误以为是协议未适配会话显示上游原文与设置入口说明;测试文档登记为阻塞项
两类来源的 roster 模型数量与名称持续变化文档中的「85 / 37」「122」与真实目录漂移文档只写约数与实测日期并注明以实时目录为准;目录每次刷新按 roster 重建,不依赖内置快照
Console 覆盖配置或额度响应字段漂移增补/停用、能力覆盖或额度解析失败模型全集不受影响(仍来自 roster/公开目录);账号覆盖与额度显示未知并给出来源说明;解析层以夹具固定已知形态,失败时降级而非抛错到界面
公开目录体积较大频繁刷新带来带宽与延迟开销公开目录按小时级节流,roster 与账号配置按分钟级;只有强制刷新才立即全量拉取
运行环境无法直连 opencode.ai登录、目录与对话全部不可用明确提示连接失败并停止重试;不内置代理,不静默回退到其它供应商
上游文档把 DSH 列为会话头支持不完整的客户端,部分模型路径可能缺稳定会话标识可能影响网关的路由与缓存优化,不阻断调用登记为观察项,不在本需求范围;以真实账号用例观察是否出现可感知影响
用户把「已用百分比」误解为金额或剩余额度误判可用性卡片与胶囊只展示百分比与重置时间,窗口名称与 Console 保持一致,不换算金额
固定高度滚动区域在小屏或键盘操作下可用性下降目录较长时用户难以定位或滚动用例核对滚动容器的键盘可达性与小屏布局;不满足时补焦点样式与滚动提示;搜索与计数保持固定
npm 上目标版本已被占用发布失败发布前核对目标版本,必要时先推进版本

决策 ​

决策选择否决方案原因
供应商路由名唯一路由 dsh-opencode(显示名 OpenCode)沿用 opencode / opencode-goopencode / opencode-go 是 pi-ai 内置目录路由,用户可能在 llm-pi-ai 设置里手工声明;实测同名会与本插件注册冲突,导致登录后模型不出现。dsh- 前缀避开用户配置
供应商分组合并为一个 OpenCode 供应商,列表内用显示名后缀 · zen / · go 区分来源按来源拆成两组供应商(上一轮实现)两类来源共用同一账号与同一网关,同名模型在两组重复出现;合并后选择器只出现一个分组,来源仍由后缀可读,隐藏与胶囊判断用 plan:id / go: 前缀即可表达,不需要第二层供应商分组
合并列表内的模型标识内部 id 用 zen: / go: 前缀保证唯一,显示名追加后缀;请求前剥离前缀只加显示名后缀、不改 id;或不加前缀,靠顺序区分只改显示名无法让合并列表内的同名模型 id 唯一,选择、隐藏与请求都会指向同一 id;加前缀并在插件请求入口剥离,可同时保证唯一性与网关兼容
目录来源优先级每类来源「活 roster → 公开目录对应段 → 内置目录或上一份快照」;Console 配置只做增补、停用、能力/容量覆盖与路由头把 Console 配置当启用全集;或只用静态内置目录Console models 可为空,把它当全集会得到空目录;活 roster 才是模型全集,Console 只承载账号级覆盖与路由头
目录缓存策略分层 TTL、两类来源共享一次刷新并各自保留来源快照、强制刷新忽略节流每次打开都全量拉取公开目录体积大,分层可兼顾新鲜度与开销;单来源失败不拖累另一来源
凭据存放位置插件自有的 0600 宿主文件,一份凭据服务合并后的供应商写入 profile 配置或复用环境变量凭据形态(访问令牌、续期令牌与账号元信息)超出 DSH 通用凭据字段;环境变量对普通用户不可管理
目录卡片布局固定高度、列表内部滚动,搜索与计数固定在工具栏;不提供按来源分组筛选让卡片随模型数量撑高整页;或保留分组筛选合并后列表上百个模型;若随内容撑高会把详情页其它卡片推到很远。来源已由每行后缀表达,再加一层筛选只增加操作步骤
能力展示方式五个能力图标 + 悬浮气泡勾选,容量以文本显示每个能力一个标签堆在行内标签堆叠在合并后上百行时噪声大;图标加气泡在保持可扫读的同时提供细节
套餐额度适用范围只对 OpenCode Go 生效;zen 后缀模型不占用套餐额度两类来源共用同一额度面板Zen 按量不占用套餐额度,混用会误导用户判断可用性
界面承载方式插件详情页自定义区块设置弹框的官方配置表单账号、额度与目录都是交互型区块,官方配置 schema 表达不了(按插件 UI 规范第 2 档)
图片生成模型不纳入适配作为不可选条目展示DSH 对话需要文本输入协议;混入会让用户选中后无法对话
市场索引维护只加源列表,发布后重生成手工改写生成的索引文件索引由官方生成器从 npm 产物读取,手改会与源列表漂移

测试、打包、发布和回滚 ​

  • 包级验证:插件类型检查、单测与构建;SDD 文档检查与文档站构建。测试用例设计见测试用例文档 FNOS-026,本页不展开用例表。
  • DSH Web:pnpm run start -- --web(DSH_HOME 指向仓库 .dsh),用启动日志里带 token 的地址访问;覆盖单一 OpenCode 供应商与 · zen / · go 后缀、合并目录、登录、对话、Go 额度与胶囊,并执行「已手工配置 opencode」的路由冲突回归。
  • DSH Desktop:使用上游检出的开发态 Desktop(fork-pj/deepseek-harness),通过渲染进程调试端口取证;重点核对系统浏览器打开验证页、目录卡片固定高度与滚动、能力气泡、合并列表与输入区胶囊位置。
  • 文档站:pnpm run build -- --docs(或 pnpm exec vitepress build docs)与 git diff --check;逐项核对新增菜单与页面链接目标存在。
  • 打包与发布:按仓库版本流程更新版本(功能提交与版本提交分开),随后发布插件;发布后重新生成市场索引,并登记到应用发布清单。
  • 回滚:停用或卸载插件即可撤销 OpenCode 供应商注册,模型选择器回到手工配置方式;说明页、插件总览行、菜单条目与市场源列表按提交回退。插件自有的凭据与偏好文件只有用户明确要求时才删除。
  • 禁止删除:用户会话历史、其它供应商的凭据与配置;本插件不持久化目录与额度,无需清理这两类数据。

参考资料 ​

来源用途
OpenCode Console Go 文档Go 与 Go Plus 套餐、额度窗口、模型清单、协议端点与已知客户端问题
OpenCode Console 文档Console 工作空间、订阅与用量能力
https://opencode.ai/inference/v1/modelsOpenCode Zen 按量活 roster(模型集合)
https://opencode.ai/inference/go/v1/modelsOpenCode Go 套餐活 roster(模型集合)
公开模型目录模型清单与逐模型能力元数据形态(opencode / opencode-go 段)
@earendil-works/pi-ai@1.1.0内置 opencode / opencode-go 供应商、静态目录与思考等级语义
@deepseek-ai/dsh-llm-pi-ai@0.2.1-rc.2供应商路由注册与适配器契约
仓库规范 docs/charter/*-spec.md需求、计划、测试用例与菜单的要求

完成状态 ​

阶段状态完成条件
阶段一 宿主侧账号与凭据待完成设备码与 API Key 登录、凭据存储与续期通过单测与桩验证;已登录隐藏 API Key 入口
阶段二 模型目录与供应商适配待完成合并目录、来源回退、账号覆盖叠加、能力归一化、Go 额度读取、单路由注册与刷新通过单测;opencode 冲突回归通过
阶段三 插件详情页与模型目录 UI待完成三张卡片、固定高度目录、能力图标、按 plan:id 显隐与胶囊在 DSH Web 与 DSH Desktop 渲染并通过用例
阶段四 文档站与发布登记待完成T04-01 至 T04-03 完成;T04-04 待插件发布后执行
阶段五 验证与收尾待完成用例执行结论回填,构建与版本检查通过

插件实现已在同一分支落地(单供应商契约、合并目录归一化与单路由已实现),DSH Web 已实测;各阶段完成时在本页变更记录追加说明,不提前修改完成状态。

变更记录 ​

日期变更说明
2026-10-24供应商合并为单一 OpenCode 与 zen/go 后缀口径供应商由 dsh-opencode / dsh-opencode-go 两组路由改为唯一 dsh-opencode(显示名 OpenCode);契约层把 plan 作为内部维度:模型 id 用 zen: / go: 前缀保证合并列表唯一,显示名追加 · zen / · go 后缀,适配器在请求入口剥离前缀。目录卡片移除「全部 / Go / Zen」分组筛选与 plan 标签,只保留搜索、计数、能力图标气泡与逐模型显隐;显隐偏好按 plan:id 保存。胶囊 auto 策略改为只在选中带 go: 前缀的模型时挂出。同步改写计划目标、当前与目标设计、影响范围、架构与刷新时序、错误处理、风险与决策;更新「变更点」状态图与时序图(旧双路由 → 新单路由)。任务将 T02-07 双供应商与路由 id 改为「合并供应商与 zen/go 后缀」,T03-07 模型列表 UI 改版 改为「模型目录 UI 合并」,并同步 T02-04、T02-06、T03-03、T03-04 与 T05-02 的验收关联。
2026-10-23回填协议实测与地区/隐私限制修正 Go roster 独有模型的协议继承顺序(逐模型声明优先于 provider 缺省,grok-4.6/qwen3.6-plus 不再误投影为 Chat Completions);补充模型地区限制(403)与 Gemini 工作空间隐私设置(400)的错误处理与风险。
2026-10-23改为双供应商与模型列表 UI 改版口径供应商由单路由 opencode-go 改为 dsh-opencode(OpenCode,OpenCode Zen 按量)与 dsh-opencode-go(OpenCode Go,套餐)两组,规避与用户手工配置 opencode-go 的同名冲突(该冲突是登录后模型不出现的根因)。目录改为每组活 roster(Zen 约 85、Go 约 36)+ 公开目录对应段,Console 配置只做增补/停用/能力与容量覆盖/路由头。同步改写计划目标、当前与目标设计、影响范围、架构与刷新时序、错误处理、依赖、风险与决策;新增「变更点」状态图与时序图。任务新增 T02-07 双供应商与路由 id 与 T03-07 模型列表 UI 改版,并更新 T02-01 至 T02-06、T03-01 至 T03-04 的验收关联。风险新增 Zen 余额 402、免费模型 403 与同名模型分组;决策新增双供应商分组、固定高度目录、图标气泡与「额度只对 Go」。
2026-10-23按真实账号联调结果更正目录分层与任务口径实测订阅账号的 Console 配置 models 为空对象,确认它是工作空间级覆盖而非启用列表。据此改写计划目标、当前与目标设计、架构与刷新时序、T02-01/T02-02/T02-03 任务、决策「目录来源优先级」、风险与错误处理:模型全集以公开模型清单为准,Console 配置只在可读时叠加停用、能力/容量与路由头;协议族识别兼容 Console 的 aisdk: 包名与公开目录的 npm 包名。
2026-10-23建立 PLAN-FNOS-026依据 FNOS-026 拆出五个阶段 24 个任务:宿主侧账号与凭据、模型目录与供应商适配、详情页与用量胶囊、文档站与发布登记、验证与收尾。登记 7 条风险与 7 条决策,其中「同名路由冲突的处置」「目录来源优先级」「凭据存放位置」是本轮的关键技术判断;T04-04(市场索引重生成与应用发布清单登记)以 npm 发布为前置,不在本轮完成。

基于 VitePress 构建