Skip to content

fix: resolve relay model windows via registry + panel + variant + bidirectional self-heal (#392) - #424

Merged
ranxianglei merged 2 commits into
masterfrom
2026-08-31_fix-393-registry-window
Sep 4, 2026
Merged

ranxianglei merged 2 commits into
masterfrom
2026-08-31_fix-393-registry-window

Conversation

@ranxianglei

@ranxianglei ranxianglei commented Aug 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes #392 — Claude Opus 5 behind a relay (Anthropic /v1/messages, --upstream mode) was served the static-table 200K window instead of its real 1M window. Four independent defects compounded, plus one secondary logging issue.

Root causes & fixes

  1. models.dev lookup bypassed by a host gate. peekRegistryContext / contextFromRegistry were gated on a parsed host that only exists in MITM / /bili/<absURL> mode, so plain --upstream relays always fell through to the static table (/^claude-/ → 200K). Removed the gate — both now run unconditionally; a relay host (not in HOST_TO_PROVIDER) resolves via the existing */<model> suffix scan. The bundled snapshot already contained anthropic/claude-opus-5 → 1,000,000.

  2. Panel hardcoded a 200K fallback + effectiveContextLimit only written in plugin mode. Wire-mode sessions never got metadata.effectiveContextLimit, so the panel always showed 200K. Now effectiveContextLimit is recorded in every mode (post self-heal + output-headroom), and the panel shows 0 (honest 0% (x / 0)) instead of a fabricated 200K when there is genuinely no value.

  3. Inference-suffix variant mismatch. claude-opus-5-thinking is not a registry key (only the base claude-opus-5 is). registryLookup now tries modelVariants(name) — progressive strip of -thinking / -reasoning / -high / -medium / -low / -fast / -extended / -interleaved-thinking (longest-first) — so the variant resolves to the base model's window. Known-provider hosts keep the old no-suffix-scan behavior.

  4. Self-heal only corrected downward. An overflow only proves the window is smaller; a too-small fallback never overflows (we compress early), so it was never raised. Added an upward path: when a low-confidence fallback (nativeFromFallback) is in effect and a prior successful turn's reported input exceeded the window it was measured under (lastTurnWindow), raise the window to that input and persist it per-model in learnedContextLimits. A context that merely fit inside a larger (e.g. beta) window is not evidence (the prevInput > prevWindow guard).

  5. Secondary: GET /models (omp's openai-models-list discovery) was logged as a warn-level "unrecognized path". Now classified as a known discovery path.

Tests

New tests/issue-393-registry-window.test.ts (6 tests):

Pre-flight

  • npm run typecheck ✅
  • npm test ✅ 874 pass / 0 fail (868 existing + 6 new)
  • npm run build ✅
  • E2E (tests/e2e/e2e-codex.test.ts): skipped in this environment — the codex CLI binary is not installed here (spawn codex ENOENT). All changed code paths are covered by the new unit/integration tests above.

Note on the #416 mis-link

Cross-link corrections (2026-09-01): this PR fixes #392 (relay window defects). #393 is the omp plugin install/remove idempotency issue and belongs to PR #416, whose Fixes line has been corrected accordingly. The batch cross-links were fixed together: #415→#391, #416→#393, #418→#413.

…irectional self-heal (#393)

Claude Opus 5 behind a relay (Anthropic /v1/messages, --upstream mode) was
served the static-table 200K window instead of its real 1M window. Four
independent defects compounded:

1. models.dev lookup was gated on a parsed host that is only present in
   MITM / /bili/<absURL> mode, so plain --upstream relays always fell through
   to the static table. Removed the host gate: peekRegistryContext and the
   async contextFromRegistry now run unconditionally (a relay host resolves
   via the */<model> suffix scan).
2. The status panel hardcoded a 200K fallback and effectiveContextLimit was
   only written for plugin sessions, so wire-mode panels always showed 200K.
   effectiveContextLimit is now recorded in every mode (post self-heal +
   output-headroom), and the panel shows 0 (honest) instead of a fake 200K.
3. Inference suffixes like -thinking did not match the base model's entry.
   registryLookup now tries modelVariants (progressive suffix strip) so
   claude-opus-5-thinking resolves to the base model's 1M window.
4. Self-heal only corrected downward (from overflows). Added an upward path:
   when a low-confidence fallback window is in effect and a prior successful
   turn's input exceeded the window it was measured under, raise the window
   to that input and persist it per-model.

Also classify GET /models as a known discovery path so omp's model
enumeration no longer logs a warn-level "unrecognized path".
@github-actions

github-actions Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-08-31_fix-393-registry-window (7e4a781)

Option A — Install from npm PR tag (recommended)

npm install -g billion-context@pr-424

Each push to this PR publishes a new version under the pr-424 npm tag.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr424.tgz
npm install -g package

This comment is automatically updated on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · vllm-qwen/qwen3.8-27b

[bot] 🏷 PR #424 独立复核(checkout 分支后重跑全部预检 + 逐条核对代码)

结论:LGTM,无阻塞问题。 四项根因 + 次级问题均正确修复,测试覆盖到位。

预检(全绿)

逐条核对

  1. 去 host 门槛(根因①) peekRegistryContext/contextFromRegistry 现无条件调用(src/server.ts:843/:857)。两函数本就接受可选 host,host===undefined 时走既有 relay 后缀扫描,属纯调用点改动。后缀扫描用前导 /(/${name})锚定,不会误命中部分模型名;冷启动(cache 为 null)O(1) 短路。快照 355 个模型,warm 扫描微秒级。✅

  2. 面板 + effectiveContextLimit 全模式(根因②) src/plugin.ts:330 兜底 200000→0;已确认 acp-kernel buildStatusPanel 对 limit>0 做了两处百分比守卫、bar() 对 total=0 返回空串,故 0 值渲染成诚实的 0% (X / 0),无 NaN/Infinity。src/server.ts:1187 把 effectiveContextLimit 移到公共管线(self-heal + output-headroom 之后的最终值),不再限于 plugin 块 → wire 模式面板拿到真实窗口。✅

  3. 变体后缀剥离(根因③) modelVariants(src/registry.ts)在 thinking 家族内长者优先(-interleaved-thinking 先于 -thinking),且每个变体都先试原名,所以真实存在的"带后缀独立模型"会先命中、不被误剥。claude-opus-5-thinking → ["claude-opus-5-thinking", "claude-opus-5"] → base 1M。新增 conflicted 标志让"冲突/缺失的原名"落到下一变体而非直接 bail。✅

  4. 自愈上调(根因④) src/server.ts:1124-1151。时序正确:lastInputTokens(上一回合上报的 input)配 lastTurnWindow(上一回合最终窗口,写在 :1191)。守卫合理:nativeFromFallback && reqModel、prevInput>prevWindow(fallback 偏小的唯一实锤)、prevInput>resolved(跳过无效)、prevInput>=1000(噪声下限)。上调到 prevInput 安全(上游已接受该 input,即真实窗口下界)且收敛(input ≤ window 后稳定)。按 model 持久化 + markDirty。✅

    • 次要(非阻塞):非 Anthropic 协议有 output-headroom 预留,lastTurnWindow 记的是预留后(window−maxOutput)值,故在"上下文压不到窗口以下"的窄场景里,上调日志可能每回合重打;learned map 仅严格变大才更新,无正确性影响,仅该边缘场景日志略啰嗦。
  5. GET /models(次级) isModelDiscoveryPath(src/server.ts:114)匹配 /models(去尾斜杠),在 :1265 对 urlPath(已去 query)生效;只抑制 warn 日志,转发不受影响,测试对照确认未知路径仍 warn。✅

测试 6 个新用例与四项修复一一对应(modelVariants 单测、快照变体→base、--upstream 下 base+变体→1M、wire 模式 effectiveContextLimit、两回合上调自愈、/models 日志+对照),覆盖到位。

合并前建议跑一次 E2E(AGENTS.md §3) 本 PR 动了 src/server.ts(请求管线),按规范应跑 real-codex E2E;两个环境都跑不了(无 codex 二进制 / 无本地上游)。建议在一台有 codex 的机器上跑,或走 manual-dispatch 的 ci-e2e workflow(需 E2E_UPSTREAM_URL/E2E_UPSTREAM_KEY secrets)再合并。所有改动路径已被新增单测/集成测试覆盖,属双保险而非已知缺口。

关于 #416 误链 同意判断:#416 正文 Fixes #392. 是生成批次串号(#415→#394、#416→#392、#418 标题写 #412 实为 #413),按现状合并会误关 #392 而本票四层缺陷仍未处理。我这里同样改不了别的 PR 正文,标记给你在合并 #416 前修正/移除其 Fixes 行。

不合并 —— 合并是人工操作。你过目后(最好先跑上面的 E2E)即可合并。

@ranxianglei ranxianglei changed the title fix: resolve relay model windows via registry + panel + variant + bidirectional self-heal (#393) fix: resolve relay model windows via registry + panel + variant + bidirectional self-heal (#392) Sep 1, 2026
… variant loop from #393

Conflict resolution in registry.ts registryLookup: each variant's relay
suffix scan now takes Math.max across */<name> matches (warn once per
name on distinct values) and returns before falling through to the next
stripped variant; zero valid matches still falls through. Known-provider
hosts do not cross-provider scan but their stripped variants keep the
exact-key chance. Adds combined-semantics test (variant conflict resolves
to max, not the base name's window).
@ranxianglei

Copy link
Copy Markdown
Owner Author

Review: ✅ 方向正确,冲突已解,可合并(已推送到 PR 分支 7e4a781)

结论

四个根因的定位和修复都对,测试覆盖了关键路径。有两点小观察(不阻塞):

  1. 上行 self-heal 用的是 lastInputTokens(折叠后实际发送尺寸),所以它只在压缩失败/未触发时才会超窗触发——健康压缩下 sent ≤ window 永远不会 heal。作为兜底可以接受,毕竟 registry 修复(根因 1)已经解决了头部场景。
  2. isModelDiscoveryPath 匹配任何以 /models 结尾的路径(偏宽),但只影响日志级别,无害。

与 master(#490)的冲突已解决并推送到本 PR 分支

master 上的 #490(minCompressRange 单位修复)同时改了 registry.ts 的 relay 后缀扫描:冲突值从 return undefined(静默回落静态表)改成了 Math.max + 一次 warn。本 PR 把同一段代码改成了 variant 循环,两边语义必须合并:

  • 合并结果:每个 variant 的 */<name> relay 扫描内部采用 fix: minCompressRange unit is chars — new minCompressRangeChars key + registry conflict max-wins #490 的 max+warn——有有效匹配即取 max 返回;只有零有效匹配才 fall through 到下一个(剥离后缀的)variant。
  • 修正了一处原注释与代码不符:known-provider host 不做跨 provider 扫描,但它的剥离变体仍有 exact-key 机会(原文说 "known-provider 保持旧行为",实际 variant 循环对 provider host 也生效)。
  • 新增组合语义测试:v-ambig-thinking 在 relay 上冲突 100K/300K、剥离到 base v-ambig 有 200K → 断言返回 300K(max 优先,不 fall through)。

验证

  • typecheck ✓
  • registry.test.ts 16/16(含新组合用例)、issue-393 + issue402 共 28/28 ✓
  • 全量 991/993(2 个失败为 plugin-agent.test.ts 已知环境永久失败,master 上同样复现)

7e4a781 已推送到 2026-08-31_fix-393-registry-window,CI 跑绿后即可合并。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 ework agent · qwen3.8-27b

[bot] 🏷 复核更新后的分支 7e4a781(合并 master 解决 #490 冲突之后)

拉取最新状态、本地分支重置到 7e4a781、重跑全部预检。结论:冲突解得对,可合并。

冲突解决核对(src/registry.ts:288-332)

预检(在 7e4a781 上)

  • npm run typecheck ✅ exit 0
  • npm run build ✅(dist/index.js 自包含)
  • npm test ✅ 993 中 992 通过 / 1 失败 —— 唯一失败是环境性的,见下。
  • 合入 master:merge-tree 干净;master(bac8d91) 已是本分支祖先 → fast-forward,无冲突。

那 1 个失败非本 PR 引入(已在 master 复现)

  • 失败项:tests/launcher.test.ts:715 resolveClientCommand: codex/claude resolve to themselves。
  • 实际返回全路径形式、断言期望裸名(PATH 相关的环境敏感断言)。
  • 切到 master(bac8d91) 跑同一文件:同样 120/121、同一 ci: enforce version-only-on-release-branches #37 失败 → 预存环境性失败,与本 PR 无关。
  • 本 PR 原始提交 df7509b 只改 4 个文件(registry/plugin/server + issue-393 测试),未碰 launcher.ts。
  • (你那边看到的是 plugin-agent.test.ts 的 2 个环境失败;不同沙箱装的东西不同会触发不同的环境敏感用例,性质相同——都预存、都与本 PR 无关。)

你的两点观察我都同意

  1. 上行 self-heal 用 lastInputTokens(折叠后实发尺寸)→ 只在压缩失败/未触发时才超窗,健康压缩下 sent ≤ window 永不 heal;作为兜底可接受,头部场景已由 registry 修复(根因①)覆盖。
  2. isModelDiscoveryPath 匹配任何 /models 结尾路径偏宽,但只影响日志级别,无害。

结论:LGTM。 逻辑、注释、组合语义测试都对,冲突正确合并,预存失败已排除。CI 绿即可合并。

不合并 —— 合并是人工操作。

@ranxianglei
ranxianglei merged commit b97de8d into master Sep 4, 2026
6 checks passed
@ranxianglei
ranxianglei deleted the 2026-08-31_fix-393-registry-window branch October 15, 2026 05:32
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.

Claude Opus 5 经中转被当 200K 用:models.dev 查找被 host 门槛旁路 + 面板硬编码 200K 兜底 + 变体后缀失配 + 自愈只降不升

1 participant