mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-12 02:31:04 +08:00
- Wire parity: align Connect-RPC Sentry-Trace, User-Agent suppression, float32 double pattern, and dynamic 732-char hex device fingerprint - Session ordinal & cache: implement process-scoped Field 15.2 with bounded LRU (5000 entries) and Field 15.4=14 user boundary; prioritize stable session_id over previous_interaction_id to preserve prompt caching - Streaming robustness: unblock hung TCP reads on client cancellation via context watcher; accurately propagate stream read errors and trailer errors instead of swallowing truncated frames - Thought signature & reasoning: emit raw delta signatures directly in active thought steps; eliminate redundant tail base64 re-encoding; ensure 1:1 assistant signature and thinking alignment across multi-turn history - Tool call de-multiplexing: route parallel tool calls by tc.Index in both streaming step events and non-streaming aggregations - Security & transport: escape OAuth callback error HTML against reflected XSS, enforce strict state validation, and isolate Devin HTTP transport with tr.Clone()
239 lines
7.1 KiB
Go
239 lines
7.1 KiB
Go
package helps
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
|
)
|
|
|
|
// Family effort configurations extracted from GetCliModelConfigs (215 models).
|
|
var (
|
|
swe2Efforts = []string{"medium", "high", "max"}
|
|
fable51Efforts = []string{"low", "medium", "high", "xhigh", "max"}
|
|
astraEfforts = []string{"low", "medium", "high", "xhigh", "max"}
|
|
glm53Efforts = []string{"low", "high", "max"}
|
|
gemini38Efforts = []string{"low", "medium", "high"}
|
|
grok46Efforts = []string{"low", "medium", "high", "xhigh"}
|
|
deepseekV4FlashEfforts = []string{"high", "max"}
|
|
deepseekV41FlashEfforts = []string{"high", "max"}
|
|
)
|
|
|
|
// knownDevinSuffixes lists recognized model uid suffixes.
|
|
var knownDevinSuffixes = []string{
|
|
"-none",
|
|
"-low",
|
|
"-medium",
|
|
"-high",
|
|
"-xhigh",
|
|
"-max",
|
|
"-fast",
|
|
"-priority",
|
|
"-low-priority",
|
|
"-medium-priority",
|
|
"-high-priority",
|
|
"-xhigh-priority",
|
|
"-max-priority",
|
|
}
|
|
|
|
// HasDevinEffortSuffix reports whether the model name already ends with a known Devin effort suffix.
|
|
func HasDevinEffortSuffix(model string) bool {
|
|
lower := strings.ToLower(strings.TrimSpace(model))
|
|
for _, s := range knownDevinSuffixes {
|
|
if strings.HasSuffix(lower, s) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// NormalizeThinkingLevel converts numeric budgets or loose effort strings to canonical Devin efforts.
|
|
func NormalizeThinkingLevel(level string, budgetTokens int) string {
|
|
normalized := strings.ToLower(strings.TrimSpace(level))
|
|
switch normalized {
|
|
case "minimal", "low", "medium", "high", "xhigh", "max":
|
|
return normalized
|
|
case "none", "off", "disabled":
|
|
return "none"
|
|
case "auto", "adaptive":
|
|
return "high"
|
|
}
|
|
|
|
if budgetTokens > 0 {
|
|
switch {
|
|
case budgetTokens <= 4096:
|
|
return "low"
|
|
case budgetTokens <= 16384:
|
|
return "medium"
|
|
case budgetTokens <= 32768:
|
|
return "high"
|
|
default:
|
|
return "max"
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// ResolveDevinChatModelUID resolves a model identifier into a valid upstream Devin chat_model_uid.
|
|
// It prioritizes already-formed UIDs, strips CPA suffixes, resolves body effort/budgets,
|
|
// and clamps efforts to the specific family whitelist.
|
|
func ResolveDevinChatModelUID(rawModel string, thinkingLevel string, budgetTokens int) string {
|
|
model := strings.TrimSpace(rawModel)
|
|
if model == "" {
|
|
return "swe-2-high"
|
|
}
|
|
|
|
// 1. Strip devin/ prefix if present (case-insensitive)
|
|
cleanModel := model
|
|
if strings.HasPrefix(strings.ToLower(cleanModel), "devin/") {
|
|
cleanModel = cleanModel[6:]
|
|
}
|
|
|
|
// 2. If already ends with an exact Devin effort suffix, use directly
|
|
if HasDevinEffortSuffix(cleanModel) {
|
|
return cleanModel
|
|
}
|
|
|
|
// 3. Strip CPA colon or parenthesis suffix (suffix overrides body per CPA convention)
|
|
parsedSuffix := thinking.ParseSuffix(cleanModel)
|
|
baseModel := strings.TrimSpace(parsedSuffix.ModelName)
|
|
if parsedSuffix.HasSuffix {
|
|
thinkingLevel = parsedSuffix.RawSuffix
|
|
} else if colonIdx := strings.LastIndex(cleanModel, ":"); colonIdx != -1 {
|
|
baseModel = strings.TrimSpace(cleanModel[:colonIdx])
|
|
thinkingLevel = strings.TrimSpace(cleanModel[colonIdx+1:])
|
|
}
|
|
|
|
// 3. Normalize requested effort
|
|
effort := NormalizeThinkingLevel(thinkingLevel, budgetTokens)
|
|
|
|
// 4. Family-specific mapping and clamping
|
|
lowerBase := strings.ToLower(baseModel)
|
|
switch {
|
|
case strings.Contains(lowerBase, "swe-2"):
|
|
clamped := clampEffort(effort, swe2Efforts, "high")
|
|
return "swe-2-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "swe-1-7") || strings.Contains(lowerBase, "swe-1.7"):
|
|
if effort == "medium" {
|
|
return "swe-1-7-medium"
|
|
}
|
|
return "swe-1-7"
|
|
|
|
case strings.Contains(lowerBase, "fable-5-1") || strings.Contains(lowerBase, "fable-5.1"):
|
|
clamped := clampEffort(effort, fable51Efforts, "medium")
|
|
return "claude-fable-5-1-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "fable-5") || strings.Contains(lowerBase, "5-fable"):
|
|
clamped := clampEffort(effort, fable51Efforts, "medium")
|
|
return "claude-5-fable-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "claude-haiku-4-5") || strings.Contains(lowerBase, "claude-haiku-4.5") || strings.Contains(lowerBase, "haiku-4-5"):
|
|
return "MODEL_PRIVATE_11"
|
|
|
|
case strings.Contains(lowerBase, "claude-sonnet-4-5") || strings.Contains(lowerBase, "claude-sonnet-4.5") || strings.Contains(lowerBase, "sonnet-4-5"):
|
|
if effort != "" && effort != "none" {
|
|
return "MODEL_PRIVATE_3"
|
|
}
|
|
return "MODEL_PRIVATE_2"
|
|
|
|
case strings.Contains(lowerBase, "astra"):
|
|
clamped := clampEffort(effort, astraEfforts, "medium")
|
|
return "gpt-6-astra-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "gpt-4-1") || strings.Contains(lowerBase, "gpt-4.1"):
|
|
return "MODEL_CHAT_GPT_4_1_2025_04_14"
|
|
|
|
case strings.Contains(lowerBase, "glm-5-2") || strings.Contains(lowerBase, "glm-5.2"):
|
|
if effort == "none" {
|
|
return "glm-5-2-none"
|
|
}
|
|
return "glm-5-2"
|
|
|
|
case strings.Contains(lowerBase, "glm-5-3") || strings.Contains(lowerBase, "glm-5.3"):
|
|
clamped := clampEffort(effort, glm53Efforts, "high")
|
|
return "glm-5-3-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "gemini-3-8-flash") || strings.Contains(lowerBase, "gemini-3.8-flash"):
|
|
clamped := clampEffort(effort, gemini38Efforts, "high")
|
|
return "gemini-3-8-flash-" + clamped
|
|
|
|
case lowerBase == "gemini-3-flash":
|
|
clamped := clampEffort(effort, gemini38Efforts, "high")
|
|
return "gemini-3-8-flash-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "grok-4-6") || strings.Contains(lowerBase, "grok-4.6"):
|
|
clamped := clampEffort(effort, grok46Efforts, "high")
|
|
return "grok-4-6-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "deepseek-v4-1-flash") || strings.Contains(lowerBase, "deepseek-v4.1-flash"):
|
|
clamped := clampEffort(effort, deepseekV41FlashEfforts, "high")
|
|
return "deepseek-v4-1-flash-" + clamped
|
|
|
|
case strings.Contains(lowerBase, "deepseek-v4-flash"):
|
|
clamped := clampEffort(effort, deepseekV4FlashEfforts, "high")
|
|
return "deepseek-v4-flash-" + clamped
|
|
|
|
default:
|
|
// Default generic fallback: append effort if present, else return base
|
|
if effort != "" && effort != "none" {
|
|
return baseModel + "-" + effort
|
|
}
|
|
return baseModel
|
|
}
|
|
}
|
|
|
|
var devinStandardLevelOrder = []string{"minimal", "low", "medium", "high", "xhigh", "max"}
|
|
|
|
func devinLevelIndex(level string) int {
|
|
lower := strings.ToLower(strings.TrimSpace(level))
|
|
for i, l := range devinStandardLevelOrder {
|
|
if l == lower {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func clampEffort(requested string, allowed []string, defaultEffort string) string {
|
|
if requested == "" || requested == "none" {
|
|
return defaultEffort
|
|
}
|
|
reqLower := strings.ToLower(strings.TrimSpace(requested))
|
|
for _, a := range allowed {
|
|
if reqLower == strings.ToLower(strings.TrimSpace(a)) {
|
|
return a
|
|
}
|
|
}
|
|
|
|
reqIdx := devinLevelIndex(reqLower)
|
|
if reqIdx == -1 {
|
|
return defaultEffort
|
|
}
|
|
|
|
bestMatch := defaultEffort
|
|
bestDist := 999
|
|
bestIdx := -1
|
|
for _, a := range allowed {
|
|
aIdx := devinLevelIndex(a)
|
|
if aIdx == -1 {
|
|
continue
|
|
}
|
|
dist := reqIdx - aIdx
|
|
if dist < 0 {
|
|
dist = -dist
|
|
}
|
|
if dist < bestDist {
|
|
bestDist = dist
|
|
bestMatch = a
|
|
bestIdx = aIdx
|
|
} else if dist == bestDist && aIdx > bestIdx {
|
|
// On tie, prefer the higher effort (e.g. medium -> high for glm-5-3; xhigh -> max for swe-2)
|
|
bestMatch = a
|
|
bestIdx = aIdx
|
|
}
|
|
}
|
|
|
|
return bestMatch
|
|
}
|