win 21325afb33
Some checks failed
CI / test (push) Failing after 10s
CI / frontend (push) Failing after 8s
CI / golangci-lint (push) Failing after 5s
Security Scan / backend-security (push) Failing after 5s
Security Scan / frontend-security (push) Failing after 4s
feat(windsurf): 补全ops日志记录与endpoint派生,对齐其他平台
- windsurf_gateway_service: 添加上游延迟/TTFT/错误上下文记录
- endpoint: DeriveUpstreamEndpoint 添加 PlatformWindsurf 分支
- ops_error_logger: guessPlatformFromPath 添加 /windsurf/ 识别
2026-04-23 20:46:27 +08:00

339 lines
13 KiB
Go

package windsurf
import (
"strings"
"sync"
"time"
)
type ModelMeta struct {
Name string `json:"name"`
Provider string `json:"provider"`
EnumValue int `json:"enum_value"`
ModelUID string `json:"model_uid,omitempty"`
Credit float64 `json:"credit"`
}
type ModelListEntry struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
}
var catalog = map[string]ModelMeta{
// Anthropic
"claude-3.5-sonnet": {Name: "claude-3.5-sonnet", Provider: "anthropic", EnumValue: 166, Credit: 2},
"claude-3.7-sonnet": {Name: "claude-3.7-sonnet", Provider: "anthropic", EnumValue: 226, Credit: 2},
"claude-3.7-sonnet-thinking": {Name: "claude-3.7-sonnet-thinking", Provider: "anthropic", EnumValue: 227, Credit: 3},
"claude-4-sonnet": {Name: "claude-4-sonnet", Provider: "anthropic", EnumValue: 281, ModelUID: "MODEL_CLAUDE_4_SONNET", Credit: 2},
"claude-4-sonnet-thinking": {Name: "claude-4-sonnet-thinking", Provider: "anthropic", EnumValue: 282, ModelUID: "MODEL_CLAUDE_4_SONNET_THINKING", Credit: 3},
"claude-4-opus": {Name: "claude-4-opus", Provider: "anthropic", EnumValue: 290, ModelUID: "MODEL_CLAUDE_4_OPUS", Credit: 4},
"claude-4-opus-thinking": {Name: "claude-4-opus-thinking", Provider: "anthropic", EnumValue: 291, ModelUID: "MODEL_CLAUDE_4_OPUS_THINKING", Credit: 5},
"claude-4.1-opus": {Name: "claude-4.1-opus", Provider: "anthropic", EnumValue: 328, ModelUID: "MODEL_CLAUDE_4_1_OPUS", Credit: 4},
"claude-4.1-opus-thinking": {Name: "claude-4.1-opus-thinking", Provider: "anthropic", EnumValue: 329, ModelUID: "MODEL_CLAUDE_4_1_OPUS_THINKING", Credit: 5},
"claude-4.5-haiku": {Name: "claude-4.5-haiku", Provider: "anthropic", ModelUID: "MODEL_PRIVATE_11", Credit: 1},
"claude-4.5-sonnet": {Name: "claude-4.5-sonnet", Provider: "anthropic", EnumValue: 353, ModelUID: "MODEL_PRIVATE_2", Credit: 2},
"claude-4.5-sonnet-thinking": {Name: "claude-4.5-sonnet-thinking", Provider: "anthropic", EnumValue: 354, ModelUID: "MODEL_PRIVATE_3", Credit: 3},
"claude-4.5-opus": {Name: "claude-4.5-opus", Provider: "anthropic", EnumValue: 391, ModelUID: "MODEL_CLAUDE_4_5_OPUS", Credit: 4},
"claude-4.5-opus-thinking": {Name: "claude-4.5-opus-thinking", Provider: "anthropic", EnumValue: 392, ModelUID: "MODEL_CLAUDE_4_5_OPUS_THINKING", Credit: 5},
"claude-sonnet-4.6": {Name: "claude-sonnet-4.6", Provider: "anthropic", ModelUID: "claude-sonnet-4-6", Credit: 4},
"claude-sonnet-4.6-thinking": {Name: "claude-sonnet-4.6-thinking", Provider: "anthropic", ModelUID: "claude-sonnet-4-6-thinking", Credit: 6},
"claude-sonnet-4.6-1m": {Name: "claude-sonnet-4.6-1m", Provider: "anthropic", ModelUID: "claude-sonnet-4-6-1m", Credit: 12},
"claude-sonnet-4.6-thinking-1m": {Name: "claude-sonnet-4.6-thinking-1m", Provider: "anthropic", ModelUID: "claude-sonnet-4-6-thinking-1m", Credit: 16},
"claude-opus-4.6": {Name: "claude-opus-4.6", Provider: "anthropic", ModelUID: "claude-opus-4-6", Credit: 6},
"claude-opus-4.6-thinking": {Name: "claude-opus-4.6-thinking", Provider: "anthropic", ModelUID: "claude-opus-4-6-thinking", Credit: 8},
"claude-opus-4-7-medium": {Name: "claude-opus-4-7-medium", Provider: "anthropic", ModelUID: "claude-opus-4-7-medium", Credit: 8},
// OpenAI GPT
"gpt-4o": {Name: "gpt-4o", Provider: "openai", EnumValue: 109, ModelUID: "MODEL_CHAT_GPT_4O_2024_08_06", Credit: 1},
"gpt-4o-mini": {Name: "gpt-4o-mini", Provider: "openai", EnumValue: 113, Credit: 0.5},
"gpt-4.1": {Name: "gpt-4.1", Provider: "openai", EnumValue: 259, ModelUID: "MODEL_CHAT_GPT_4_1_2025_04_14", Credit: 1},
"gpt-4.1-mini": {Name: "gpt-4.1-mini", Provider: "openai", EnumValue: 260, Credit: 0.5},
"gpt-4.1-nano": {Name: "gpt-4.1-nano", Provider: "openai", EnumValue: 261, Credit: 0.25},
"gpt-5": {Name: "gpt-5", Provider: "openai", EnumValue: 340, ModelUID: "MODEL_PRIVATE_6", Credit: 0.5},
"gpt-5-medium": {Name: "gpt-5-medium", Provider: "openai", ModelUID: "MODEL_PRIVATE_7", Credit: 1},
"gpt-5-high": {Name: "gpt-5-high", Provider: "openai", ModelUID: "MODEL_PRIVATE_8", Credit: 2},
"gpt-5-mini": {Name: "gpt-5-mini", Provider: "openai", EnumValue: 337, Credit: 0.25},
"gpt-5-codex": {Name: "gpt-5-codex", Provider: "openai", EnumValue: 346, ModelUID: "MODEL_CHAT_GPT_5_CODEX", Credit: 0.5},
"gpt-5.2": {Name: "gpt-5.2", Provider: "openai", EnumValue: 401, ModelUID: "MODEL_GPT_5_2_MEDIUM", Credit: 2},
"gpt-5.2-low": {Name: "gpt-5.2-low", Provider: "openai", EnumValue: 400, ModelUID: "MODEL_GPT_5_2_LOW", Credit: 1},
"gpt-5.2-high": {Name: "gpt-5.2-high", Provider: "openai", EnumValue: 402, ModelUID: "MODEL_GPT_5_2_HIGH", Credit: 3},
"gpt-5.2-xhigh": {Name: "gpt-5.2-xhigh", Provider: "openai", EnumValue: 403, ModelUID: "MODEL_GPT_5_2_XHIGH", Credit: 8},
// O-series
"o3-mini": {Name: "o3-mini", Provider: "openai", EnumValue: 207, Credit: 0.5},
"o3": {Name: "o3", Provider: "openai", EnumValue: 218, ModelUID: "MODEL_CHAT_O3", Credit: 1},
"o3-high": {Name: "o3-high", Provider: "openai", ModelUID: "MODEL_CHAT_O3_HIGH", Credit: 1},
"o3-pro": {Name: "o3-pro", Provider: "openai", EnumValue: 294, Credit: 4},
"o4-mini": {Name: "o4-mini", Provider: "openai", EnumValue: 264, Credit: 0.5},
// Gemini
"gemini-2.5-pro": {Name: "gemini-2.5-pro", Provider: "google", EnumValue: 246, ModelUID: "MODEL_GOOGLE_GEMINI_2_5_PRO", Credit: 1},
"gemini-2.5-flash": {Name: "gemini-2.5-flash", Provider: "google", EnumValue: 312, ModelUID: "MODEL_GOOGLE_GEMINI_2_5_FLASH", Credit: 0.5},
"gemini-3.0-pro": {Name: "gemini-3.0-pro", Provider: "google", EnumValue: 412, ModelUID: "MODEL_GOOGLE_GEMINI_3_0_PRO_LOW", Credit: 1},
"gemini-3.0-flash": {Name: "gemini-3.0-flash", Provider: "google", EnumValue: 415, ModelUID: "MODEL_GOOGLE_GEMINI_3_0_FLASH_MEDIUM", Credit: 1},
// DeepSeek
"deepseek-v3": {Name: "deepseek-v3", Provider: "deepseek", EnumValue: 205, Credit: 0.5},
"deepseek-v3-2": {Name: "deepseek-v3-2", Provider: "deepseek", EnumValue: 409, Credit: 0.5},
"deepseek-r1": {Name: "deepseek-r1", Provider: "deepseek", EnumValue: 206, Credit: 1},
// Grok
"grok-3": {Name: "grok-3", Provider: "xai", EnumValue: 217, ModelUID: "MODEL_XAI_GROK_3", Credit: 1},
"grok-3-mini": {Name: "grok-3-mini", Provider: "xai", EnumValue: 234, Credit: 0.5},
// Qwen
"qwen-3": {Name: "qwen-3", Provider: "alibaba", EnumValue: 324, Credit: 0.5},
// Kimi
"kimi-k2": {Name: "kimi-k2", Provider: "moonshot", EnumValue: 323, ModelUID: "MODEL_KIMI_K2", Credit: 0.5},
// GLM
"glm-4.7": {Name: "glm-4.7", Provider: "zhipu", EnumValue: 417, ModelUID: "MODEL_GLM_4_7", Credit: 0.25},
// Windsurf SWE
"swe-1.5": {Name: "swe-1.5", Provider: "windsurf", EnumValue: 369, ModelUID: "MODEL_SWE_1_5_SLOW", Credit: 0.5},
"swe-1.5-fast": {Name: "swe-1.5-fast", Provider: "windsurf", EnumValue: 359, ModelUID: "MODEL_SWE_1_5", Credit: 0.5},
}
var (
lookupOnce sync.Once
lookupMap map[string]string
)
func buildLookup() {
lookupMap = make(map[string]string, len(catalog)*4)
for id, info := range catalog {
lookupMap[id] = id
lookupMap[strings.ToLower(id)] = id
if info.ModelUID != "" {
lookupMap[info.ModelUID] = id
lookupMap[strings.ToLower(info.ModelUID)] = id
}
}
aliases := map[string]string{
// Anthropic dated names
"claude-3-5-sonnet-20240620": "claude-3.5-sonnet",
"claude-3-5-sonnet-20241022": "claude-3.5-sonnet",
"claude-3-5-sonnet-latest": "claude-3.5-sonnet",
"claude-3-7-sonnet-20250219": "claude-3.7-sonnet",
"claude-3-7-sonnet-latest": "claude-3.7-sonnet",
"claude-sonnet-4-20250514": "claude-4-sonnet",
"claude-sonnet-4-0": "claude-4-sonnet",
"claude-opus-4-20250514": "claude-4-opus",
"claude-opus-4-0": "claude-4-opus",
"claude-opus-4-1": "claude-4.1-opus",
"claude-opus-4-1-20250805": "claude-4.1-opus",
"claude-sonnet-4-5": "claude-4.5-sonnet",
"claude-sonnet-4-5-20250929": "claude-4.5-sonnet",
"claude-opus-4-5": "claude-4.5-opus",
"claude-opus-4-5-20251101": "claude-4.5-opus",
"claude-opus-4-7": "claude-opus-4-7-medium",
"claude-opus-4-7-latest": "claude-opus-4-7-medium",
"claude-opus-4.7": "claude-opus-4-7-medium",
"claude-opus-4.7-thinking": "claude-opus-4-7-medium",
"claude-sonnet-4-6": "claude-sonnet-4.6",
"claude-opus-4-6": "claude-opus-4.6",
"claude-sonnet-4-6-thinking": "claude-sonnet-4.6-thinking",
"claude-opus-4-6-thinking": "claude-opus-4.6-thinking",
"MODEL_CLAUDE_4_5_SONNET": "claude-4.5-sonnet",
"MODEL_CLAUDE_4_5_SONNET_THINKING": "claude-4.5-sonnet-thinking",
// OpenAI dated names
"gpt-4o-2024-11-20": "gpt-4o",
"gpt-4o-2024-08-06": "gpt-4o",
"gpt-4o-2024-05-13": "gpt-4o",
"gpt-4o-mini-2024-07-18": "gpt-4o-mini",
"gpt-4.1-2025-04-14": "gpt-4.1",
"gpt-4.1-mini-2025-04-14": "gpt-4.1-mini",
"gpt-4.1-nano-2025-04-14": "gpt-4.1-nano",
"gpt-5-2025-08-07": "gpt-5",
// Cursor-friendly aliases
"opus-4.6": "claude-opus-4.6",
"opus-4.6-thinking": "claude-opus-4.6-thinking",
"opus-4-7": "claude-opus-4-7-medium",
"opus-4.7": "claude-opus-4-7-medium",
"sonnet-4.6": "claude-sonnet-4.6",
"sonnet-4.6-thinking": "claude-sonnet-4.6-thinking",
"sonnet-4.6-1m": "claude-sonnet-4.6-1m",
"sonnet-4.5": "claude-4.5-sonnet",
"sonnet-4.5-thinking": "claude-4.5-sonnet-thinking",
"haiku-4.5": "claude-4.5-haiku",
"sonnet-4": "claude-4-sonnet",
"opus-4": "claude-4-opus",
"opus-4.1": "claude-4.1-opus",
"sonnet-3.7": "claude-3.7-sonnet",
"sonnet-3.5": "claude-3.5-sonnet",
"ws-opus": "claude-opus-4.6",
"ws-sonnet": "claude-sonnet-4.6",
"ws-opus-thinking": "claude-opus-4.6-thinking",
"ws-sonnet-thinking": "claude-sonnet-4.6-thinking",
"ws-haiku": "claude-4.5-haiku",
}
for k, v := range aliases {
lookupMap[k] = v
lookupMap[strings.ToLower(k)] = v
}
}
func ensureLookup() {
lookupOnce.Do(buildLookup)
}
func ResolveModel(name string) string {
if name == "" {
return ""
}
ensureLookup()
if id, ok := lookupMap[name]; ok {
return id
}
if id, ok := lookupMap[strings.ToLower(name)]; ok {
return id
}
return name
}
func GetModelInfo(id string) *ModelMeta {
if m, ok := catalog[id]; ok {
return &m
}
return nil
}
func GetChatMode(m *ModelMeta, legacyEnumCutoff int) string {
if m == nil {
return "cascade"
}
if m.ModelUID != "" {
return "cascade"
}
if m.EnumValue > 0 {
if legacyEnumCutoff > 0 && m.EnumValue <= legacyEnumCutoff {
return "legacy"
}
return "cascade"
}
return "cascade"
}
var freeTierModels = []string{"gpt-4o-mini", "gemini-2.5-flash"}
func GetTierModels(tier string) []string {
switch tier {
case "pro":
keys := make([]string, 0, len(catalog))
for k := range catalog {
keys = append(keys, k)
}
return keys
case "free", "unknown":
return freeTierModels
case "expired":
return nil
default:
return freeTierModels
}
}
func ListModelsOpenAI() []ModelListEntry {
ts := time.Now().Unix()
entries := make([]ModelListEntry, 0, len(catalog))
for _, info := range catalog {
entries = append(entries, ModelListEntry{
ID: info.Name,
Object: "model",
Created: ts,
OwnedBy: info.Provider,
})
}
return entries
}
var cloudModelsMu sync.Mutex
func MergeCloudModels(configs []ModelInfo) int {
cloudModelsMu.Lock()
defer cloudModelsMu.Unlock()
ensureLookup()
providerMap := map[string]string{
"MODEL_PROVIDER_ANTHROPIC": "anthropic",
"MODEL_PROVIDER_OPENAI": "openai",
"MODEL_PROVIDER_GOOGLE": "google",
"MODEL_PROVIDER_DEEPSEEK": "deepseek",
"MODEL_PROVIDER_XAI": "xai",
"MODEL_PROVIDER_WINDSURF": "windsurf",
"MODEL_PROVIDER_MOONSHOT": "moonshot",
}
added := 0
for _, m := range configs {
if m.ModelUID == "" {
continue
}
if _, ok := lookupMap[m.ModelUID]; ok {
continue
}
if _, ok := lookupMap[strings.ToLower(m.ModelUID)]; ok {
continue
}
key := strings.ToLower(strings.ReplaceAll(m.ModelUID, "_", "-"))
if _, exists := catalog[key]; exists {
continue
}
provider := providerMap[m.Label]
if provider == "" {
provider = "unknown"
}
credit := m.CreditMultiplier
if credit == 0 {
credit = 1
}
catalog[key] = ModelMeta{
Name: key,
Provider: provider,
ModelUID: m.ModelUID,
Credit: credit,
}
lookupMap[key] = key
lookupMap[m.ModelUID] = key
lookupMap[strings.ToLower(m.ModelUID)] = key
added++
}
return added
}
func MapTeamsTier(t int) string {
if t == 0 || t == 6 || t == 19 {
return "free"
}
if t > 0 {
return "pro"
}
return "unknown"
}
func TeamsTierLabel(t int) string {
labels := map[int]string{
0: "Unspecified", 1: "Teams", 2: "Pro", 3: "Enterprise (SaaS)",
4: "Hybrid", 5: "Enterprise (Self-Hosted)", 6: "Waitlist Pro",
7: "Teams Ultimate", 8: "Pro Ultimate", 9: "Trial",
10: "Enterprise (Self-Serve)", 11: "Enterprise (SaaS Pooled)",
12: "Devin Enterprise", 14: "Devin Teams", 15: "Devin Teams V2",
16: "Devin Pro", 17: "Devin Max", 18: "Max",
19: "Devin Free", 20: "Devin Trial",
}
if l, ok := labels[t]; ok {
return l
}
return "unknown"
}