Repository navigation
fix(zhipu): 修正 GLM Coding Plan 5h/weekly 用量窗口标反 / fix swapped GLM coding plan usage windows - #8087
Merged
Wei-Shaw merged 1 commit intoOct 15, 2026
Conversation
Contributor
|
All contributors have signed the CLA. ✅ |
Contributor
Author
|
I have read the CLA Document and I hereby sign the CLA |
parseZhipuTokenTiers 只在 limitType == "TOKENS_LIMIT" 分支里按 unit 显式分类, 而智谱当前把 5h / weekly 两条窗口都标成 CREDIT_LIMIT(level=lite)。这些条目被放进 creditFallback,entry 结构体又不带 unit,信息在此永久丢失,降级路径退化为 「按 nextResetTime 升序填空位」。当周窗口先于 5h 重置时(每个周期临尾的几小时), 周窗口会被先填进 5h 槽,两档必然标反 —— 函数文档注释第 1 条已警告过该场景, 只是防线建在了错误的 type 分支上。 因为只在「CREDIT_LIMIT 且周窗口更早重置」这一种组合下出错,症状是间歇性的, 容易被误判为随机抽风。该函数自 v0.2.0 到 v0.2.16 逐字节未变,不属于版本回归。 改动: - entry 增加 unit 字段;classify 改为直接接收 unit,不再依赖 item。 - 仅在降级路径(无任何 TOKENS_LIMIT 条目)上,对 creditFallback 中 unit 可识别的 条目先按 unit 分类;unit 仍不可识别的才落入原有的 reset 时间启发式。 - TOKENS_LIMIT 优先占槽的既有语义保持不变:两种 type 同时返回时 CREDIT_LIMIT 依旧不参与槽位竞争。 - 更新 cnZhipuWindow 与 parseZhipuTokenTiers 的文档注释。 - cn_providers_test.go 补充 3 个回归用例。 验证: - 反向验证:仅回退源文件保留测试,CreditLimitUnitClassification 失败于 14 vs 90(两档被对调);另两个钉住原语义的用例修复前后均通过。 - go build ./... 通过;gofmt -l 无输出;golangci-lint v2.14.0 0 issues。 - go test -tags=unit ./internal/service/ 全量通过。 影响范围:backend/internal/service 两个文件(service + unit test)。
coder-zhw
force-pushed
the
fix/zhipu-credit-limit-window-classification
branch
from
October 15, 2026 06:56
46ea122 to
1774b79
Compare
Contributor
Author
|
I have read the CLA Document and I hereby sign the CLA |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
中文
问题与原因
智谱 GLM Coding Plan 账号在管理端「用量窗口」列会把
5h与weekly两档的用量互换:5hweekly一个 7 天窗口只剩 4 小时就重置,说明两档串了。上游响应本身完全正确。
根因在
backend/internal/service/cn_provider_quota_service.go的parseZhipuTokenTiers。unit是窗口语义的权威来源(classifyZhipuWindowUnit:3 = 5h,6 = weekly),但classify只在limitType == "TOKENS_LIMIT"分支里被调用。而 GLM Coding Plan(data.level = "lite")当前把两条窗口都标成CREDIT_LIMIT,其中unit依然有效:{ "data": { "limits": [ {"type":"CREDIT_LIMIT","unit":3,"number":5,"usage":2000,"currentValue":299,"remaining":1700,"percentage":14,"nextResetTime":1791987903321}, {"type":"CREDIT_LIMIT","unit":6,"number":1,"usage":10000,"currentValue":9062,"remaining":937,"percentage":90,"nextResetTime":1791980214984} ], "level": "lite" } }(
unit=3→ 5h 窗口,unit=6→ weekly;nextResetTime分别对应 14:25:03Z 与 12:16:54Z。注意 weekly 的重置时刻比 5h 更早,这正是周期末尾的形态。)于是
CREDIT_LIMIT条目被放进creditFallback;entry结构体不含unit,信息在这一步永久丢失,降级路径退化为「按nextResetTime升序依次填空位」。当周窗口先于 5h 重置时,排序后周窗口首先被填进 5h 槽 —— 两档必然标反。函数自己的文档注释第 1 条已经警告过这个场景:
只是防线只建在
TOKENS_LIMIT分支上,上游一改type就失效。症状是间歇性的:只有「
CREDIT_LIMIT且周窗口先于 5h 重置」这一种组合出错。一周里大部分时间周窗口的重置时刻远在 5h 之后,此时解析正确;只有每个周期临尾的那几个小时会标反。因此很容易被误判为随机抽风或某次升级引入 —— 实际上该函数自v0.2.0到v0.2.16逐字节未变(六个 tag 的parseZhipuTokenTiers函数体 sha256 相同)。参考对照:cc-switch 的
parse_zhipu_token_tiers(src-tauri/src/services/coding_plan.rs)对两种type都调用classify_zhipu_window,只有unit认不出时才走兜底,因此同一份上游数据不会标反。本次改动
沿用原有语义,只修降级路径:
entry增加unit字段;classify改为直接接收unit,不再依赖item。TOKENS_LIMIT条目)上,对creditFallback中unit可识别的条目先按unit分类;unit仍不可识别的条目才落入原有的 reset 时间启发式。TOKENS_LIMIT优先占槽的既有语义完全不变:两种 type 同时返回时,CREDIT_LIMIT依旧不参与槽位竞争(hasTokensLimit为真时creditFallback仍被丢弃),原有「避免信用额度污染阈值停调快照」的意图保留。cnZhipuWindow与parseZhipuTokenTiers的文档注释。cn_providers_test.go补充 3 个回归用例。只改了 service 层两个文件,未触及 handler、前端、Extra 快照键与调度逻辑。
回归覆盖与验证
新增用例:
TestParseZhipuTokenTiers_CreditLimitUnitClassification—— 两条CREDIT_LIMIT都带unit,且刻意让 weekly 的nextResetTime早于 5h(周期末尾),断言 5h = 14%、weekly = 90%。TestParseZhipuTokenTiers_CreditLimitWithoutUnitFallback——CREDIT_LIMIT缺unit时保留原有 fallback(无nextResetTime优先归 5h,其余按 reset 升序)。TestParseZhipuTokenTiers_TokensLimitWinsOverCreditLimit—— 两种 type 同时返回时TOKENS_LIMIT仍优先占槽,钉住未改动的语义。验证结果:
cn_provider_quota_service.go、保留测试,TestParseZhipuTokenTiers_CreditLimitUnitClassification失败于Max difference between 14 and 90 allowed is 1e-09, but difference was -76,即两档被对调;另两个「保持原语义」的用例在修复前后都通过。TestParseZhipuTokenTiers全部 7 个用例通过,含 4 个既有用例,无回归。go build ./...通过;gofmt -l无输出。go test -tags=unit ./...:56 个包通过。另有一个与本改动无关的用例TestInflightEstimate_AccountMappingNoDBAndBoundedMemory在整包并发跑时偶发失败(内存增长断言8742520略超8388608上界约 350 KB),单独复跑 3 次均通过,属负载/GC 敏感的既有 flake,未修改相关代码。English
Problem and cause
A Zhipu GLM Coding Plan account shows its
5handweeklyusage windows swapped in the admin "quota windows" column: the5hrow reports 90% with a ~2h reset countdown while theweeklyrow reports 14% with a ~4h countdown. A seven-day window cannot have four hours left. The upstream response itself is correct.Root cause is in
parseZhipuTokenTiers(backend/internal/service/cn_provider_quota_service.go).unitis the authoritative source of window semantics (classifyZhipuWindowUnit: 3 = 5h, 6 = weekly), butclassifyis only invoked on thelimitType == "TOKENS_LIMIT"branch. GLM Coding Plan (data.level = "lite") currently returns both windows asCREDIT_LIMIT, withunitstill populated.CREDIT_LIMITentries therefore go intocreditFallback, and theentrystruct carries nounit, so the information is lost at that point. The fallback degrades to "sort bynextResetTimeascending and fill the empty slots". Whenever the weekly window resets earlier than the 5h window, the weekly entry is filled into the 5h slot first — the two tiers are always swapped.The function's own doc comment already warns about this exact scenario; the guard was just placed on the wrong branch, so it stops working as soon as upstream switches
type.The symptom is intermittent. Only the combination "
CREDIT_LIMITand weekly resets before 5h" is affected. For most of a cycle the weekly reset is far later than the 5h reset and parsing is correct; only the last few hours before each weekly reset invert. This makes it look like random flakiness or a regression, but the function is byte-identical fromv0.2.0throughv0.2.16.For reference, cc-switch's
parse_zhipu_token_tiers(src-tauri/src/services/coding_plan.rs) callsclassify_zhipu_windowfor both types and only falls back whenunitis unrecognized, which is why it renders the same payload correctly.Changes
Preserves existing semantics; only the fallback path is fixed:
entrygains aunitfield;classifynow takesunitdirectly instead ofitem.TOKENS_LIMITentry at all),creditFallbackentries whoseunitis recognizable are classified byunitfirst; entries with an unrecognizedunitstill take the original reset-time heuristic.TOKENS_LIMIT-first slot priority is unchanged: when both types are present,CREDIT_LIMITstill does not compete for slots (creditFallbackis still dropped whenhasTokensLimitis true), preserving the original intent of keeping credit figures out of the scheduling-threshold snapshot.cnZhipuWindowandparseZhipuTokenTiersupdated.cn_providers_test.go.Only two files in the service layer change; handlers, frontend, Extra snapshot keys and scheduling logic are untouched.
Regression coverage and validation
TestParseZhipuTokenTiers_CreditLimitUnitClassification— bothCREDIT_LIMITentries carryunit, with the weeklynextResetTimedeliberately earlier than the 5h one; asserts 5h = 14% and weekly = 90%.TestParseZhipuTokenTiers_CreditLimitWithoutUnitFallback— aCREDIT_LIMITentry withoutunitkeeps the original fallback (nonextResetTimeprefers 5h, the rest fill remaining slots by ascending reset).TestParseZhipuTokenTiers_TokensLimitWinsOverCreditLimit— with both types present,TOKENS_LIMITstill wins the slot, pinning the untouched semantics.Validation performed:
cn_provider_quota_service.gowhile keeping the tests makesTestParseZhipuTokenTiers_CreditLimitUnitClassificationfail withMax difference between 14 and 90 allowed is 1e-09, but difference was -76, i.e. the two tiers swapped. The other two cases (which pin preserved behavior) pass both before and after the fix.TestParseZhipuTokenTiers_*cases pass, including the 4 pre-existing ones — no regression.go build ./...passes;gofmt -lis clean.go test -tags=unit ./...: 56 packages pass. One unrelated case,TestInflightEstimate_AccountMappingNoDBAndBoundedMemory, failed intermittently when the whole suite ran concurrently (a memory-growth assertion measured8742520against an8388608bound, ~350 KB over). It passes 3/3 when run in isolation and no related code is touched, so it is a pre-existing load/GC-sensitive flake.