Files
sub2api/backend/internal/service/cline.go
T
SavitarCandClaude Opus 5.5 874871a25a feat(platform): 接入 Cline 平台
Cline API(https://api.cline.bot/api/v1)是 OpenAI 兼容的多模型聚合端点,
只提供 Chat Completions(Cline 自己的客户端同样只走该端点),模型名带厂商
前缀。同一个 API Key 下有两个互不影响的额度来源,由模型名决定扣哪一边:
cline-pass/* 走 ClinePass 订阅(5 小时 / 7 天 / 30 天滚动上限),其余付费
模型扣账户积分;cline-free/* 免费模型只有逐模型的每日额度。

转发:
- 平台清单与 provider profile 新增 cline:按入站协议分流,只登记 Chat
  Completions 端点,Responses / Anthropic 入站由现有转换链转为 Chat
  Completions,无需新增转发代码。
- 只有一个接入模式,不需要选择账号类型。
- 连接测试未指定模型时使用 profile 默认模型(固定 Chat Completions 的账号
  此前落到不带厂商前缀的通用默认模型);订阅了 ClinePass 的账号改用订阅
  模型,不消耗积分,ClinePass 冷却中时仍用按量模型。

用量与余额:API Key 调 /api/v1/users/me/plan/usage-limits(三档窗口的已用
百分比与重置时间,未订阅时 404)与 /api/v1/users/me、/users/{id}/balance
(余额单位为百万分之一积分,1 积分 = 1 美元),兼容 {success, data} 信封,
只查官方主机。复用额度探测与周期检测:账号列表显示 ClinePass 窗口与积分
余额;窗口用满时 ClinePass 模型冷却到重置时间,未订阅时不调度 ClinePass
模型,积分低于阈值时冷却付费模型;数据显示恢复后自动解除,但只解除探测能
证明已恢复的原因(窗口用满、未订阅、积分不足),组织账户、所需订阅档位、推理
额度与花费上限的冷却到期后由请求重新确认。周期检测中走账号接口探测的平台
改为登记表(平台、支持判定、检测函数一一对应),按账号所属平台分派。

上游错误(依据 docs.cline.bot/api/errors 与 Cline 客户端的错误分类,按文案
识别):积分不足(含其他 402)、组织花费上限、推理额度上限、ClinePass 超限、
未订阅 ClinePass / 组织账户、免费模型每日额度。两边的超限只冷却请求模型所属
的一边(模型级冷却键 cline:pass / cline:credits,调度按请求模型检查),不停
整个账号;免费模型只限该模型,冷却到文案中的 "try again in …"。ClinePass
超限冷却到快照中对应窗口的重置时间,没有快照时 5 小时窗口 1 小时、7 天
6 小时、30 天 24 小时。普通频率限制走默认 429 逻辑。402 的冷却写入失败时
也不落入默认分支永久置 error(探测只覆盖激活账号,置 error 后无法自动恢复)。
余额类冷却时长抽为 cnBalanceCheckCooldown 共用。

上游计费探测的官方域名加入 cline.bot。

前端:官方标志(机器人头像)、violet 配色(品牌紫)、复用额度与余额单元格
(同样只对官方主机上的 API Key 账号显示);接入模式展示名抽为共用函数,编辑
弹窗不再显示原始模式值。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:11:06 +08:00

86 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"net/url"
"strings"
)
// Cline API(https://docs.cline.bot/api/overview):OpenAI 兼容的多模型聚合端点,
// 基址 https://api.cline.bot/api/v1,只提供 Chat Completions(Cline 自己的客户端同样
// 只走该端点),模型名带厂商前缀(如 anthropic/claude-sonnet-4-6)。Responses 与
// Anthropic 入站由网关转换为 Chat Completions。
//
// 同一个 API Key 下有两个互不影响的额度来源(下称"钱包"),由模型名决定扣哪一个:
// - 积分:cline-pass/* 与 cline-free/* 以外的模型按量扣账户积分,余额可查;
// - ClinePass 订阅:cline-pass/* 模型,受 5 小时 / 7 天 / 30 天三档滚动用量上限限制,
// 各档已用百分比与重置时间可查(/api/v1/users/me/plan/usage-limits);
// - cline-free/* 免费模型不扣任何一边,只有逐模型的每日额度。
//
// 一边用完只影响该边的模型,所以超限冷却按钱包落在模型级冷却键上(见
// clineWalletRateLimitKey),不停整个账号,也不需要管理员声明账号类型。
const (
// DefaultClineTestModel 是连接测试的默认模型(按量积分,价格低、长期在售)。
DefaultClineTestModel = "deepseek/deepseek-v4-flash"
// DefaultClinePassTestModel 是订阅了 ClinePass 的账号连接测试的默认模型。
DefaultClinePassTestModel = "cline-pass/glm-5.3-flash"
clineAPIHost = "api.cline.bot"
clinePassModelPrefix = "cline-pass/"
clineFreeModelPrefix = "cline-free/"
// clinePassRateLimitKey / clineCreditsRateLimitKey 是两个钱包的模型级冷却键:
// 命中后该钱包的全部模型都不再调度到该账号,另一边不受影响。
clinePassRateLimitKey = "cline:pass"
clineCreditsRateLimitKey = "cline:credits"
)
// IsCline 报告账号是否为 Cline 平台账号。
func (a *Account) IsCline() bool {
return a != nil && a.Platform == PlatformCline
}
// clineWalletRateLimitKey 返回模型所属钱包的冷却键;免费模型不属于任何钱包,返回空串。
func clineWalletRateLimitKey(model string) string {
model = strings.ToLower(strings.TrimSpace(model))
switch {
case model == "":
return ""
case strings.HasPrefix(model, clinePassModelPrefix):
return clinePassRateLimitKey
case strings.HasPrefix(model, clineFreeModelPrefix):
return ""
default:
return clineCreditsRateLimitKey
}
}
// isOfficialClineHost 报告 URL 是否指向官方 Cline API 主机。
func isOfficialClineHost(target string) bool {
parsed, err := url.Parse(strings.TrimSpace(target))
if err != nil {
return false
}
return strings.EqualFold(parsed.Scheme, "https") && strings.EqualFold(parsed.Hostname(), clineAPIHost)
}
// clineAccountAPISupported 报告账号能否查询积分余额与 ClinePass 用量:API Key 账号且
// 推理端点指向官方主机。自定义中转的 Key 不发往官方账号接口。
func (a *Account) clineAccountAPISupported() bool {
return a.IsCline() && a.Type == AccountTypeAPIKey && isOfficialClineHost(a.GetOpenAIBaseURL())
}
// clinePassSubscribed 报告最近一次用量探测是否显示账号订阅了 ClinePass(有窗口快照)。
func (a *Account) clinePassSubscribed() bool {
if a == nil || a.Extra == nil {
return false
}
for _, suffix := range []string{cnExtraSuffix5hUsed, cnExtraSuffixWeeklyUsed, cnExtraSuffixMonthlyUsed} {
if value, ok := a.Extra[cnExtraKey(PlatformCline, suffix)]; ok && value != nil {
return true
}
}
return false
}