package core import ( "testing" ) // `isFree` 的判定链(照参考 cline-models.ts)。 // // ★ 修的是一个与注释自相矛盾的缺陷:cline 的 3 个模型来自远端 **free 数组**, // 却声明在 `models` 里 ⇒ 被标成收费。用户看不到哪个可以不花钱用。 // // 参考的判定链(权威在前): // // ① 远端 free 数组(`recommended-models` 是**唯一权威**的免费集合; // `/api/v1/models` 的 460 个 id 里根本没有 cline-free/*) // ② 命名约定前缀 `cline-free/*`(远端未响应时的兜底) // ③ 声明的 models_free // ④ 能力表的 cost(models.dev) // // ⚠️ 免费是 **OR** 语义:多个来源任一确认免费就够。写成"后者覆盖前者"会让 // 「远端说免费、能力表拉不到」退回收费 —— 正是这类静默降级。 func TestIsFreeUsesAuthorityChain(t *testing.T) { _, ps := poolCore(t) // cline 的免费模型必须被标成免费(远端/free_prefix/声明 任一命中即可) out, err := ps.Call("channels", "model_list", map[string]interface{}{"platform": "cline"}) if err != nil { t.Fatalf("model_list: %v", err) } j, _ := out.(map[string]interface{})["json"].(map[string]interface{}) got := map[string]bool{} for _, it := range asArrInterface(j["models"]) { if mm, ok := it.(map[string]interface{}); ok { if id, ok := mm["id"].(string); ok { if f, ok := mm["isFree"].(bool); ok { got[id] = f } } } } // `cline-free/*` 前缀命中 ⇒ 必须免费(命名约定兜底) if !got["cline-free/mimo-v2.6-flash"] { t.Errorf("cline-free/* 前缀的模型必须免费(命名约定兜底),实得 %v", got) } // stealth/space-bunny-alpha 来自远端 free 数组,也必须免费 if !got["stealth/space-bunny-alpha"] { t.Errorf("远端 free 数组里的模型必须免费,实得 %v", got) } // 命名约定必须生效(远端未响应时唯一的兜底依据) probe, _ := ps.Call("channels", "__is_free_probe", map[string]interface{}{ "platform": "cline", "model_id": "cline-free/some-new-model", }) pj, _ := probe.(map[string]interface{})["json"].(map[string]interface{}) if pj["is_free"] != true { t.Error("远端未响应时,`cline-free/*` 前缀必须能判出免费(这是兜底依据)") } // 反向:收费通道的同名模型**不得**被判免费 //(参考点名:免费模型是独立 id,`cline-free/x` 与 `x` 是两个不同模型) probe2, _ := ps.Call("channels", "__is_free_probe", map[string]interface{}{ "platform": "cline", "model_id": "deepseek/deepseek-v4.1-flash", }) pj2, _ := probe2.(map[string]interface{})["json"].(map[string]interface{}) if pj2["is_free"] == true { t.Error("收费通道的模型不得因为前缀规则被判成免费") } }