DSH / Atlas
2026-09-24implementedarchitecture

DeepSeek account models come from the account listing and gate by client version

DeepSeek 账号模型来自账号列表,并按客户端版本门控

The account route's catalog was deployment configuration: a build advertised the models it shipped with, a new model reached users only through a release, and nothing kept an older build from requesting a model it cannot drive. The account service publishes per-account model metadata at `GET /models`, and that listing can name models an older build does not understand.

English

Problem

The account route's catalog was deployment configuration: a build advertised the models it shipped with, a new model reached users only through a release, and nothing kept an older build from requesting a model it cannot drive. The account service publishes per-account model metadata at GET /models, and that listing can name models an older build does not understand.

Decision

  • llm-deepseek-account loads the listing lazily for each credential, waits for it once up to modelsTimeoutMs, refreshes it in the background after modelsMaxAgeMs, keeps the last good listing when a load fails or returns no model, and serves the bundled catalog before any listing loads. The cache lives in memory and is keyed by endpoint and token fingerprint, so per-account rollouts stay separate.
  • A listed model is available when its client_requirements["deepseek-harness"].min_version is at most the current DeepSeek Harness version in semantic-version order. Without that declaration, only ids the built-in catalog knows are available; the others stay hidden, because a client cannot tell which future version supports them. A request for a refused listed model fails before network I/O with CLIENT_UPGRADE_REQUIRED or MODEL_UNSUPPORTED.
  • Listed capabilities replace bundled ones field by field; the bundled entry keeps client-side knowledge such as tool-update mode and image budgets; modelOverrides applies last and reuses the models entry fields and validation. max_output_tokens bounds the request default instead of becoming it.
  • The shared transport gains only two provider hooks, connect and admit; listing, gating, and merging stay in the account package.

Alternatives considered

Server-side filtering by client version only. Codex sends client_version and lets its backend drop models. An older build then cannot show that a newer version supports a model, and a listing that omits a declaration would expose an unknown model.

Default parameters for unknown models. Reasonix and Pi drive unknown ids with generic defaults, and MiniMax treats that as a defect. A model that needs client changes would run with the wrong behavior.

Whole-listing validation. Codex, ZCode, and Kimi reject the whole listing on one bad field. One malformed row would then send every account back to the bundled catalog.

Persisting the last good listing. A durable cache adds a persistence format and its migration duty; the bundled catalog already covers offline starts.

Keeping models with merge-by-id semantics. The API-key route's models replaces its catalog, so the same field name with merge semantics would invert its meaning between sibling routes.

Generalizing the shared transport. Per-model effort levels, an output-capability field, and catalog-change announcements would widen the Messages package for one consumer. The listed effort levels match the bundled defaults, and a model that needs different ones needs a release, which the gate enforces.

Consequences

  • An older build never offers a model it does not know unless the service declares a minimum version that build satisfies, and the service must declare min_version for a future build to show that an upgrade enables a model.
  • A modified client can still request any id; the service enforces version floors only if it rejects such requests itself.
  • The first account access after sign-in may wait up to modelsTimeoutMs; an offline start uses bundled capabilities until a listing loads; a listing change during a running process reaches the Web picker at its next catalog refresh.

中文

问题

账号路由的目录原本是部署配置:每个构建只提供它发布时自带的模型,新模型只能随发版到达用户,也没有办法阻止旧构建请求它驱动不了的模型。账号服务在 GET /models 上按账号发布模型元数据,这份列表可能包含旧构建不认识的模型。

决策

  • llm-deepseek-account 按凭据懒加载列表,首次访问最多等待 modelsTimeoutMs,超过 modelsMaxAgeMs 后在后台刷新;获取失败或返回的列表中没有模型时保留上一份,还没有加载到任何列表时使用内置目录。缓存只在内存中,以端点和 token 指纹为键,因此按账号灰度互不干扰。
  • 列出的模型在其 client_requirements["deepseek-harness"].min_version 按 semver 顺序不高于当前 DeepSeek Harness 版本时可用。没有这个声明时,只有内置目录认识的 id 可用,其余保持隐藏,因为客户端无法判断将来哪个版本会支持它们。请求被拒绝的已列出模型时,在网络请求之前以 CLIENT_UPGRADE_REQUIRED 或 MODEL_UNSUPPORTED 失败。
  • 列表中的能力逐字段覆盖内置能力;内置条目保留工具更新模式、图片预算等客户端知识;modelOverrides 最后叠加,复用 models 条目的字段与校验。max_output_tokens 只限制请求默认值,不直接成为默认值。
  • 共享传输层只新增 connect 与 admit 两个提供方钩子;列表、门控与合并都留在账号包中。

考虑过的替代方案

只在服务端按客户端版本过滤。 Codex 发送 client_version,由后端去掉模型。这样旧构建无法提示新版本支持某个模型,而且列表漏掉声明时会暴露未知模型。

未知模型套用默认参数。 Reasonix 和 Pi 用通用默认值驱动未知 id,MiniMax 把这种做法当作缺陷。需要客户端改动的模型会以错误的方式运行。

整份列表严格校验。 Codex、ZCode 和 Kimi 会因为一个坏字段拒绝整份列表。一条格式错误的记录就会让所有账号退回内置目录。

持久化上一次成功的列表。 持久缓存要新增持久化格式并承担迁移责任;内置目录已经覆盖离线启动。

保留 models 并改为按 id 合并。 API key 路由的 models 会替换整个目录,同名字段在兄弟路由上改成合并语义会让含义相反。

扩展共享传输层。 按模型的推理档位、输出能力字段和目录变更通知,会为一个使用方扩大 Messages 包的范围。列表中的推理档位与内置默认值一致;需要不同档位的模型需要发版适配,门控正是用来保证这一点。

影响

  • 旧构建永远不会提供它不认识的模型,除非服务端声明了该构建满足的最低版本;服务端必须声明 min_version,将来的构建才能提示升级后可用。
  • 改过的客户端仍然可以请求任意 id;只有服务端自己拒绝这类请求时,版本下限才会被强制执行。
  • 登录后的第一次账号访问最多等待 modelsTimeoutMs;离线启动在加载到列表之前使用内置能力;进程运行中途的列表变化要等 Web 选择器下一次刷新目录时才会显示。