mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-15 23:22:59 +08:00
Merge pull request #8087 from coder-zhw/fix/zhipu-credit-limit-window-classification
fix(zhipu): 修正 GLM Coding Plan 5h/weekly 用量窗口标反 / fix swapped GLM coding plan usage windows
This commit is contained in:
@@ -497,7 +497,7 @@ func minimaxResetTime(v gjson.Result) string {
|
||||
return time.UnixMilli(ms).UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// cnZhipuWindow 标识智谱 TOKENS_LIMIT 条目所属窗口。
|
||||
// cnZhipuWindow 标识智谱额度条目(TOKENS_LIMIT / CREDIT_LIMIT)所属窗口。
|
||||
type cnZhipuWindow int
|
||||
|
||||
const (
|
||||
@@ -529,7 +529,8 @@ func classifyZhipuWindowUnit(unit int64) cnZhipuWindow {
|
||||
//
|
||||
// CREDIT_LIMIT(信用额度)与 TOKENS_LIMIT(token 窗口)度量不同:两者同时返回时
|
||||
// 只让 TOKENS_LIMIT 参与 5h/weekly 槽位竞争,避免信用额度百分比污染阈值停调
|
||||
// 快照;仅当无任何 TOKENS_LIMIT 条目时才降级用 CREDIT_LIMIT 展示。
|
||||
// 快照;仅当无任何 TOKENS_LIMIT 条目时才降级用 CREDIT_LIMIT 展示 —— 此时
|
||||
// CREDIT_LIMIT 同样按 unit 分类(它也携带 unit),不依赖 reset 时间排序。
|
||||
// 老套餐只回 1 条 TOKENS_LIMIT,自然降级为仅 5h;新套餐回 2 条。
|
||||
func parseZhipuTokenTiers(data gjson.Result) []CNQuotaTier {
|
||||
type entry struct {
|
||||
@@ -537,6 +538,7 @@ func parseZhipuTokenTiers(data gjson.Result) []CNQuotaTier {
|
||||
hasReset bool
|
||||
percentage float64
|
||||
resetISO string
|
||||
unit int64
|
||||
}
|
||||
var (
|
||||
fiveHour entry
|
||||
@@ -546,8 +548,8 @@ func parseZhipuTokenTiers(data gjson.Result) []CNQuotaTier {
|
||||
unclassified []entry
|
||||
)
|
||||
|
||||
classify := func(item gjson.Result, e entry) {
|
||||
switch classifyZhipuWindowUnit(item.Get("unit").Int()) {
|
||||
classify := func(unit int64, e entry) {
|
||||
switch classifyZhipuWindowUnit(unit) {
|
||||
case cnZhipuWindow5h:
|
||||
if !fiveHourSet {
|
||||
fiveHour, fiveHourSet = e, true
|
||||
@@ -592,10 +594,10 @@ func parseZhipuTokenTiers(data gjson.Result) []CNQuotaTier {
|
||||
hasReset = resetISO != ""
|
||||
}
|
||||
}
|
||||
e := entry{resetMs: resetMs, hasReset: hasReset, percentage: percentage, resetISO: resetISO}
|
||||
e := entry{resetMs: resetMs, hasReset: hasReset, percentage: percentage, resetISO: resetISO, unit: item.Get("unit").Int()}
|
||||
if limitType == "TOKENS_LIMIT" {
|
||||
hasTokensLimit = true
|
||||
classify(item, e)
|
||||
classify(e.unit, e)
|
||||
} else {
|
||||
creditFallback = append(creditFallback, e)
|
||||
}
|
||||
@@ -603,8 +605,17 @@ func parseZhipuTokenTiers(data gjson.Result) []CNQuotaTier {
|
||||
})
|
||||
|
||||
// 无任何 TOKENS_LIMIT 条目(部分套餐只报信用额度):降级用 CREDIT_LIMIT 展示。
|
||||
// CREDIT_LIMIT 条目同样带 unit(3=5h / 6=weekly;实测 GLM Coding Plan 两条窗口
|
||||
// 都是 CREDIT_LIMIT),必须和 TOKENS_LIMIT 一样先按 unit 显式分类 —— 否则会退化
|
||||
// 成 reset 时间启发式,而周期末尾周窗口比 5h 更早重置,时间排序必然把两档标反。
|
||||
if !hasTokensLimit {
|
||||
unclassified = append(unclassified, creditFallback...)
|
||||
for _, e := range creditFallback {
|
||||
if classifyZhipuWindowUnit(e.unit) != cnZhipuWindowUnknown {
|
||||
classify(e.unit, e)
|
||||
continue
|
||||
}
|
||||
unclassified = append(unclassified, e)
|
||||
}
|
||||
}
|
||||
|
||||
// 无 reset 的条目排前,再按 reset 升序,依次填入仍空缺的槽位。
|
||||
|
||||
@@ -126,6 +126,60 @@ func TestParseZhipuTokenTiers_UnitClassification(t *testing.T) {
|
||||
require.InDelta(t, 70.0, tiers[1].UsedPercent, 1e-9)
|
||||
}
|
||||
|
||||
// TestParseZhipuTokenTiers_CreditLimitUnitClassification GLM Coding Plan 当前把 5h / weekly
|
||||
// 两条窗口都标成 CREDIT_LIMIT(实测 level=lite)。CREDIT_LIMIT 同样携带 unit,
|
||||
// 必须和 TOKENS_LIMIT 一样按 unit 显式分类,否则会退化成 reset 时间排序 ——
|
||||
// 此处刻意让 weekly 的 nextResetTime 早于 5h(周期末尾),两档会被标反。
|
||||
func TestParseZhipuTokenTiers_CreditLimitUnitClassification(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := gjson.Parse(`{
|
||||
"limits": [
|
||||
{"type":"CREDIT_LIMIT","unit":3,"number":5,"percentage":14,"nextResetTime":1700000099999},
|
||||
{"type":"CREDIT_LIMIT","unit":6,"number":1,"percentage":90,"nextResetTime":1700000000000}
|
||||
]
|
||||
}`)
|
||||
tiers := parseZhipuTokenTiers(data)
|
||||
require.Len(t, tiers, 2)
|
||||
require.Equal(t, "5h", tiers[0].Window)
|
||||
require.InDelta(t, 14.0, tiers[0].UsedPercent, 1e-9)
|
||||
require.Equal(t, "weekly", tiers[1].Window)
|
||||
require.InDelta(t, 90.0, tiers[1].UsedPercent, 1e-9)
|
||||
}
|
||||
|
||||
// TestParseZhipuTokenTiers_CreditLimitWithoutUnitFallback CREDIT_LIMIT 缺 unit 时,
|
||||
// 保留原有 fallback:无 nextResetTime 的优先归 5h,其余按 reset 升序填入剩余槽位。
|
||||
func TestParseZhipuTokenTiers_CreditLimitWithoutUnitFallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := gjson.Parse(`{
|
||||
"limits": [
|
||||
{"type":"CREDIT_LIMIT","percentage":30,"nextResetTime":1700000000000},
|
||||
{"type":"CREDIT_LIMIT","percentage":80}
|
||||
]
|
||||
}`)
|
||||
tiers := parseZhipuTokenTiers(data)
|
||||
require.Len(t, tiers, 2)
|
||||
require.Equal(t, "5h", tiers[0].Window)
|
||||
require.InDelta(t, 80.0, tiers[0].UsedPercent, 1e-9)
|
||||
require.Equal(t, "weekly", tiers[1].Window)
|
||||
require.InDelta(t, 30.0, tiers[1].UsedPercent, 1e-9)
|
||||
}
|
||||
|
||||
// TestParseZhipuTokenTiers_TokensLimitWinsOverCreditLimit 两种 type 同时返回时,
|
||||
// TOKENS_LIMIT 仍然优先占槽、CREDIT_LIMIT 不参与(原有语义保持不变)。
|
||||
func TestParseZhipuTokenTiers_TokensLimitWinsOverCreditLimit(t *testing.T) {
|
||||
t.Parallel()
|
||||
data := gjson.Parse(`{
|
||||
"limits": [
|
||||
{"type":"TOKENS_LIMIT","unit":3,"percentage":20,"nextResetTime":1700000000000},
|
||||
{"type":"CREDIT_LIMIT","unit":6,"percentage":99,"nextResetTime":1700000099999}
|
||||
]
|
||||
}`)
|
||||
tiers := parseZhipuTokenTiers(data)
|
||||
require.Len(t, tiers, 1)
|
||||
require.Equal(t, "5h", tiers[0].Window)
|
||||
require.InDelta(t, 20.0, tiers[0].UsedPercent, 1e-9)
|
||||
}
|
||||
|
||||
// TestParseZhipuTokenTiers_SingleTierOldPlan 老套餐仅回 1 条 → 降级为仅 5h。
|
||||
func TestParseZhipuTokenTiers_SingleTierOldPlan(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
Reference in New Issue
Block a user