Skip to content

feat(qoder): 上下文窗口按上游真值,并对齐 Trae 的「更大上下文」开关 - #233

Open
BrianBoyCN wants to merge 3 commits into
caigee-cmd:mainfrom
BrianBoyCN:feat/qoder-model-context
Open

BrianBoyCN wants to merge 3 commits into
caigee-cmd:mainfrom
BrianBoyCN:feat/qoder-model-context

Conversation

@BrianBoyCN

Copy link
Copy Markdown

依赖 #232(模型价格):本分支从 #232 之上切出,因此 diff 里暂时也包含 #232 的改动。#232 合入 main 后,本 PR 会自动收敛为只含下文这几项。

概述

修 Qoder 模型上下文窗口——改用上游真实窗口,并按 Trae 那套「更大上下文」开关呈现。

现状(两个问题)

  1. 窗口是硬编码的 180000:control.DefaultContextForModel 只对 minimax-m3 特判,其余一律 180000。而 Qoder 每个模型都上报自己的窗口,于是 glm-5.3-flash(1M)被压小、deepseek-v4-pro(96K)被放大。
  2. worker 把窗口元数据丢了:Qoder 的模型条目带 default_context_window / available_context_windows / max_output_tokens,但 createModelCatalogSnapshot 只留了 max_input_tokens,所以 catalog_context_length 在 API 里恒为空、被默认值顶掉。
  3. UI 不对:控制台给 Qoder 用的是裸数字输入框,详情弹窗还会命中 WorkBuddy 的提示文案("不是 Trae 的 is_max_mode…")。

改动

worker(worker/src/catalog.mjs):透传 default_context_window / available_context_windows / max_output_tokens(缺失时保持 absent)。

qoder adapter:映射到 catalog_context_length(默认窗口)与 catalog_context_length_max(最大可选窗口),进程内路径同时填入 ModelCapabilities.ContextWindow / ContextWindowMax / MaxOutput。

control:新增 decorateQoderContext,对齐既有 decorateProviderSettings:

  • supports_max_mode = 目录里确实有更大的档;
  • max_mode = 由存储的窗口推导;
  • Qoder 的模型设置读写数字窗口(Settings.MaxContextLength),开关即映射到适配层已经在上游发送的 context_length;
  • 硬编码默认值仅在目录完全无窗口时兜底。

前端:Qoder 模型改用与 Trae 相同的「窗口显示 + CompactSwitch 开关」,配 Qoder 专属提示;去掉数字输入框 / Save / Reset 那一行,详情弹窗也不再显示 WorkBuddy 文案。

一个取舍

Trae 是二档(默认 ↔ Max,靠上游布尔 is_max_mode);Qoder 上游没有 is_max_mode,窗口是 available_context_windows(多数模型有 3 档:200k / 400k / 1M),且只能靠数字 context_length 表达。按维护者对"对齐已有 Trae"的要求,这里做成开关(默认 ↔ 最大档),代价是放弃中间的 400k 档。

真机验证(Qoder CN 账号)

qwen3.8-max 最终渲染:

{ "catalog_context_length": 200000, "catalog_context_length_max": 1000000,
  "supports_max_mode": true, "max_mode": true,
  "default_context_length": 200000, "context_length": 1000000, "context_custom": true }
  • 默认档来自 default_context_window(200000),更大档来自 max(available)=1000000;
  • glm-5.3-flash → 1M,deepseek-v4-pro → 96000,minimax-m2.7(只有一档)→ supports_max_mode:false,不显示开关;
  • PATCH 打开开关 → 存 1M 并返回 max_mode:true;关闭 → 清除覆盖、回落默认窗口。

Test plan

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

另附:新增单测覆盖窗口映射、ApplyModelContext、Qoder 装饰逻辑与开关往返;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.
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.
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 caigee-cmd#232
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.

1 participant