mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-12 12:17:23 +00:00
feat(channels): 对外功能面全量对齐参考 channel-pack RPC + 逐平台余额/签到状态端点
此前只实现了参考 RPC 面的一部分(账号池/模型开关/供应商/备份)。本次按
用户要求把对外呈现的功能补齐到与参考 channel-pack-rpc.ts 一致,并逐平台
移植真实余额/签到状态端点(不再'选一部分')。
## 新增 RPC(对齐 channel-pack-rpc.ts 的 case 清单)
credits.status 逐账号签到状态(平台声明 checkin_status 时查,status_fn 解析;
无端点平台如实 null,与参考对 CodeArts/LobsterAI 处理一致)
credits.balances 逐账号余额(平台声明 balance 时查,total_fn 解析;无端点/无凭据
返回 {balance=null,error=...} 不编造数字 —— 对齐参考'不伪装余额')
credits.permanentLock 永久积分锁定读写(buddy/loomy 等)
account.reset/retest 清限流标记 / 重测(支持 __all__ 批量)
model.setDisabledMany 批量模型开关
model.clearDead 清黑名单里已下线的死模型(只清不在 models 表的 id)
provider.getOrder/setOrder 供应商显示顺序(bogus id 过滤)
gateway.getEnabled/setEnabled 网关整体开关(复用 provider_set_enabled 语义)
backup.status 备份健康度(账号数/无过期时间账号数)
usage.tokenLedger 本网关 token 用量快照(数据源 /api/stats,与 usage 插件同思路)
usage.badge 渠道徽标(credits.balances 汇总)
usage.badgePreference 徽标偏好读写(非法值拒绝不回落,对齐参考注释)
onboarding.status/claim 新人任务(raccoon 桌面登录奖励 3000 分实测端点)
## 逐平台真实余额/签到端点(照各 *-credits.ts 逐字移植)
minimax signin/status 预检 + credit/details 余额(total=Σ remaining_amount,
避开参考踩过的 total_count 误读坑)
trae checkin_credits/status + ide_user_ent_usage(remain=Σ(limit-amount))
buddy checkin-activity-status(不是 checkin-status 占位坑)+ get-user-resource
cline usage-limits(dollars,取剩余百分比最小的限额)
lobsterai profile-summary;qoder me/usage(userQuota+addOnQuota);raccoon points/v1/balance
每平台 CHANNEL_CREDITS 能力表声明 unit/claim(对齐参考同名表)。
## WebUI 补齐
账号行展示逐账号余额(credits.balances);详情动作区加「重置限流」「清死模型」;
顶栏加「Token 用量」弹窗(usage.tokenLedger)。node --check 过。
## 测试
TestChannelsExtendedRPCBehavior 10 子项全过:balances 无凭据报错不编造、status
无端点 null、reset、setDisabledMany、clearDead 只清死模型、provider order 过滤、
backup.status、badgePreference 非法拒绝、onboarding supported=false、permanentLock 读写。
全量 8 包回归通过。
已知未移植(如实说明,均需本机无法提供的运行时):captcha.*(zcode 验证码载体,
需浏览器端求解器)、login.sendSms/submitSms(loomy 短信,备用路径)、
cline.requestLog/usage.tokenLedgerHistory(历史聚合,内核未落盘日维度表)。
This commit is contained in:
@ -194,3 +194,129 @@ func TestChannelsAccountPoolBehavior(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestChannelsExtendedRPCBehavior:对齐参考剩余 RPC 面(credits.status/balances、
|
||||
// account.reset/retest、model.setDisabledMany/clearDead、provider 排序、
|
||||
// gateway/backup.status、usage.badgePreference、onboarding)。
|
||||
func TestChannelsExtendedRPCBehavior(t *testing.T) {
|
||||
_, ps := poolCore(t)
|
||||
|
||||
t.Run("credits_balances_无凭据报错_有凭据解析", func(t *testing.T) {
|
||||
m := invoke(t, ps, "account_add", `{"platform":"minimax","nickname":"b1"}`)
|
||||
aid, _ := m["account_id"].(string)
|
||||
j := invoke(t, ps, "credits_balances", `{"platform":"minimax"}`)
|
||||
var row map[string]interface{}
|
||||
for _, a := range asArr(j["accounts"]) {
|
||||
x := a.(map[string]interface{})
|
||||
if x["accountId"] == aid {
|
||||
row = x
|
||||
}
|
||||
}
|
||||
if row == nil {
|
||||
t.Fatal("balances 应含该账号")
|
||||
}
|
||||
if row["balance"] != nil || row["error"] == nil {
|
||||
t.Fatalf("无凭据应 error 而非编造余额: %v", row)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("credits_status_无端点平台如实null", func(t *testing.T) {
|
||||
m := invoke(t, ps, "account_add", `{"platform":"opencode","nickname":"oc1"}`)
|
||||
aid, _ := m["account_id"].(string)
|
||||
j := invoke(t, ps, "credits_status", `{"platform":"opencode"}`)
|
||||
for _, a := range asArr(j["accounts"]) {
|
||||
x := a.(map[string]interface{})
|
||||
if x["accountId"] == aid && x["status"] != nil {
|
||||
t.Fatalf("opencode 无状态端点应 null: %v", x)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("account_reset_clears", func(t *testing.T) {
|
||||
invoke(t, ps, "account_add", `{"platform":"minimax","nickname":"r1"}`)
|
||||
j := invoke(t, ps, "account_reset", `{"platform":"minimax","account_id":"__all__"}`)
|
||||
if j["ok"] != true {
|
||||
t.Fatalf("resetAll: %v", j)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("model_set_disabled_many", func(t *testing.T) {
|
||||
invoke(t, ps, "model_set_disabled_many", `{"platform":"minimax","model_ids":["MiniMax-M3","MiniMax-M2.7-highspeed"],"disabled":true}`)
|
||||
j := invoke(t, ps, "model_list", `{"platform":"minimax"}`)
|
||||
off := 0
|
||||
for _, x := range asArr(j["models"]) {
|
||||
mm := x.(map[string]interface{})
|
||||
if mm["disabled"] == true {
|
||||
off++
|
||||
}
|
||||
}
|
||||
if off != 2 {
|
||||
t.Fatalf("批量关2模型后应2个disabled, 实得 %d", off)
|
||||
}
|
||||
invoke(t, ps, "model_set_disabled_many", `{"platform":"minimax","model_ids":["MiniMax-M3","MiniMax-M2.7-highspeed"],"disabled":false}`)
|
||||
})
|
||||
|
||||
t.Run("model_clear_dead_只清黑名单里已下线的", func(t *testing.T) {
|
||||
invoke(t, ps, "model_set_disabled", `{"platform":"minimax","model_id":"MiniMax-M3","disabled":true}`)
|
||||
j := invoke(t, ps, "model_clear_dead", `{"platform":"minimax"}`)
|
||||
if n := len(asArr(j["removed"])); n != 0 {
|
||||
t.Fatalf("在册模型不算死模型, 实得 %d", n)
|
||||
}
|
||||
// 手动放一个不在 models 表里的黑名单项
|
||||
invoke(t, ps, "model_set_disabled_many", `{"platform":"minimax","model_ids":["ghost-model"],"disabled":true}`)
|
||||
j = invoke(t, ps, "model_clear_dead", `{"platform":"minimax"}`)
|
||||
if n := len(asArr(j["removed"])); n != 1 {
|
||||
t.Fatalf("死模型应被清: %v", j)
|
||||
}
|
||||
invoke(t, ps, "model_set_disabled", `{"platform":"minimax","model_id":"MiniMax-M3","disabled":false}`)
|
||||
})
|
||||
|
||||
t.Run("provider_order", func(t *testing.T) {
|
||||
invoke(t, ps, "provider_set_order", `{"order":["minimax","trae","bogus"]}`)
|
||||
j := invoke(t, ps, "provider_get_order", `{}`)
|
||||
order := asArr(j["order"])
|
||||
if len(order) != 2 {
|
||||
t.Fatalf("bogus 应被过滤, 实得 %v", order)
|
||||
}
|
||||
if order[0] != "minimax" {
|
||||
t.Fatalf("顺序应保留: %v", order)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("backup_status", func(t *testing.T) {
|
||||
invoke(t, ps, "account_add", `{"platform":"minimax","nickname":"s1"}`)
|
||||
j := invoke(t, ps, "backup_status", `{}`)
|
||||
if n, _ := j["accounts"].(float64); n < 1 {
|
||||
t.Fatalf("backup_status 应统计账号: %v", j)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("badge_preference_非法值拒绝", func(t *testing.T) {
|
||||
j := invoke(t, ps, "usage_badge_preference", `{"preference":"bogus"}`)
|
||||
if j["error"] == nil {
|
||||
t.Fatalf("非法偏好应拒绝: %v", j)
|
||||
}
|
||||
j = invoke(t, ps, "usage_badge_preference", `{"preference":"credits"}`)
|
||||
if j["preference"] != "credits" {
|
||||
t.Fatalf("合法偏好应保存: %v", j)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("onboarding_不支持平台如实报", func(t *testing.T) {
|
||||
j := invoke(t, ps, "onboarding_status", `{"platform":"minimax"}`)
|
||||
if j["supported"] != false {
|
||||
t.Fatalf("minimax 无新人任务应 supported=false: %v", j)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("permanent_lock_读写", func(t *testing.T) {
|
||||
j := invoke(t, ps, "credits_permanent_lock", `{"platform":"buddy","locked":true}`)
|
||||
if j["locked"] != true {
|
||||
t.Fatalf("锁定应生效: %v", j)
|
||||
}
|
||||
j = invoke(t, ps, "credits_permanent_lock", `{"platform":"buddy"}`)
|
||||
if j["locked"] != true {
|
||||
t.Fatalf("只读应回显锁定: %v", j)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user