Skip to content

feat(qoder): 模型列表显示价格与限时免费标记 - #232

Merged
caigee-cmd merged 1 commit into
caigee-cmd:mainfrom
BrianBoyCN:feat/qoder-model-credits
Sep 25, 2026
Merged

caigee-cmd merged 1 commit into
caigee-cmd:mainfrom
BrianBoyCN:feat/qoder-model-credits

Conversation

@BrianBoyCN

Copy link
Copy Markdown
Contributor

概述

在 Qoder(国内版/国际版)的模型列表里显示每个模型的价格与免费标记。

现状

控制台对任何 Qoder 账号都不显示价格。根因是上游给了、worker 丢了:

Qoder CLI getAvailableModels()   ✅ 每模型带 price_factor(客户端自己渲染成 "0.50x Credit")/ is_free / tags
        ↓
worker/src/catalog.mjs           ❌ createModelCatalogSnapshot 只留 5 个字段,价格全丢
        ↓
Go qoder/adapter.go              ✅ 本就「会读」credits / free(只是上游没给)
        ↓
/api/models + 前端                ✅ 已支持 credits 文本与免费 Chip(无需改前端)

改动

  • worker/src/catalog.mjs:透传 price_factor / is_free / tags;缺失时保持 absent,避免「没价 = 免费」的误判。
  • internal/providers/qoder/:把三者映射到控制台读的 credits / free,覆盖两条目录路径(进程内 ModelInfos 与 Qoder 实际走的遗留 worker DisplayCatalog)。

免费判定对齐 Qoder 客户端自身的标签规则:

tags.includes("limited_time_free") → 显示 "Free";否则显示 ${price_factor.toFixed(2)}x Credit

即:limited_time_free 标签(或倍率为 0)算免费,正倍率即计费。特意不把裸 is_free 当免费 —— Qwen3.8-Max 上游同时报 is_free: true 和 price_factor: 0.5,Qoder 客户端仍标注 0.50x Credit;这样控制台显示的是倍率,而不是自相矛盾的「免费 / x0.5」。

真机验证(Qoder CN 账号实测)

14 个模型全部上报 price_factor(0 / 0.1 / 0.2 / 0.5 / 0.6 / 0.8 / 1.4):

  • qwen3.8-flash(倍率 0)→ 免费
  • qwen3.8-max(倍率 0.5,尽管 is_free: true)→ 计费,显示 x0.5

Test plan

  • go test ./...
  • go vet ./...
  • cd worker && npm test
  • cd frontend && npm run build && npm run lint
  • Added changelog/unreleased/qoder-model-credits.md, or this PR has no user-facing change
  • No tokens, auth blobs, raw captures, or host details in the diff

另附:新增单测覆盖价格映射、免费判定(含 is_free + 正倍率 的双字段情形)与 worker 透传;go test -race ./...、gitleaks 本地通过。

Qoder's model catalog carries a per-model `price_factor` (the multiplier its
own client renders as `0.50x Credit`) plus an `is_free` flag and a `tags`
array, but the worker's snapshot dropped them, so the console showed no price
for any Qoder account (Global or CN).

- worker/src/catalog.mjs: forward `price_factor`, `is_free`, and `tags`,
  keeping them absent when upstream omits them so a missing price is never
  read as 0.
- qoder adapter: map them onto the console's `credits`/`free` on both catalog
  paths (in-process ModelInfos and the legacy worker DisplayCatalog that Qoder
  actually serves).

The free signal follows the Qoder client's own label rule: the
`limited_time_free` tag (or a zero factor) marks a model free, and a positive
factor is priced. This deliberately does NOT treat a bare `is_free` as free:
Qwen3.8-Max reports `is_free: true` alongside `price_factor: 0.5`, and the
Qoder client still labels it `0.50x Credit`, so the console now shows a
multiplier instead of the contradictory "免费 / x0.5" pair.

Verified against a live Qoder CN account: all 14 models report a factor
(0/0.1/0.2/0.5/0.6/0.8/1.4); qwen3.8-flash (factor 0) is free, qwen3.8-max
(factor 0.5) is priced.
@caigee-cmd
caigee-cmd merged commit e138872 into caigee-cmd:main Sep 25, 2026
6 checks passed
caigee-cmd added a commit that referenced this pull request Sep 25, 2026
* feat(qoder): show per-model price and limited-time-free badge

Qoder's model catalog carries a per-model `price_factor` (the multiplier its
own client renders as `0.50x Credit`) plus an `is_free` flag and a `tags`
array, but the worker's snapshot dropped them, so the console showed no price
for any Qoder account (Global or CN).

- worker/src/catalog.mjs: forward `price_factor`, `is_free`, and `tags`,
  keeping them absent when upstream omits them so a missing price is never
  read as 0.
- qoder adapter: map them onto the console's `credits`/`free` on both catalog
  paths (in-process ModelInfos and the legacy worker DisplayCatalog that Qoder
  actually serves).

The free signal follows the Qoder client's own label rule: the
`limited_time_free` tag (or a zero factor) marks a model free, and a positive
factor is priced. This deliberately does NOT treat a bare `is_free` as free:
Qwen3.8-Max reports `is_free: true` alongside `price_factor: 0.5`, and the
Qoder client still labels it `0.50x Credit`, so the console now shows a
multiplier instead of the contradictory "免费 / x0.5" pair.

Verified against a live Qoder CN account: all 14 models report a factor
(0/0.1/0.2/0.5/0.6/0.8/1.4); qwen3.8-flash (factor 0) is free, qwen3.8-max
(factor 0.5) is priced.

* feat(qoder): use upstream per-model context windows

Qoder reports a `default_context_window` and a set of
`available_context_windows` per model, but the worker only kept
`max_input_tokens`, so the console defaulted every Qoder model to a hardcoded
180000 — under-sizing glm-5.3-flash (1M) and over-sizing deepseek-v4-pro (96K).

- worker/src/catalog.mjs: forward `default_context_window`,
  `available_context_windows`, and `max_output_tokens`, absent when upstream
  omits them.
- qoder adapter: map them to `catalog_context_length` (upstream default) and
  `catalog_context_length_max` (largest selectable window) on both catalog
  paths, and onto ModelCapabilities.ContextWindow / ContextWindowMax /
  MaxOutput for the in-process path.
- control: prefer the catalog's window over the static fallback in both
  DecorateModelsWithContext and the per-model setting response, via a new
  Settings.DefaultContextLength backed by Catalog.ModelContextLength bound
  during app assembly. DefaultContextForModel stays as the last-resort default
  when Qoder reports no window.

Verified against a live Qoder CN account: qwen3.8-max defaults to 200000 with a
1M max tier, glm-5.3-flash to 1M, deepseek-v4-pro to 96000.

* feat(qoder): present context window like Trae's max-context switch

Qoder has no upstream is_max_mode flag, so the console showed a raw number
input defaulting to a hardcoded 180000. Align Qoder with the existing Trae
max-context UX instead: a "default → larger" window with a switch.

- control: `decorateQoderContext` mirrors `decorateProviderSettings` —
  `supports_max_mode` when the catalog advertises a larger tier, `max_mode`
  derived from the stored window. Qoder settings now read/write the numeric
  window (Settings.MaxContextLength) so the toggle maps onto the value the
  adapter already forwards; the static default is only a last resort.
- frontend: Qoder models use the same window display + CompactSwitch as Trae
  with a Qoder-specific hint, and drop the number input / save / reset row.

Refs #232

---------

Co-authored-by: BrianBoyCN <175931469+BrianBoyCN@users.noreply.github.com>
Co-authored-by: 蔡及 <522caiji@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants