返回 DeepSeek-Reasonix
opencode_contract.go
根目录 / internal / provider / opencode_contract.go
1 package provider
2
3 import (
4 "maps"
5 "slices"
6 "strings"
7 )
8
9 // OpenCodeGoContract separates the recommended route from a supported alternate
10 // route. It is a local, versioned contract: discovery never performs network I/O.
11 type OpenCodeGoContract struct {
12 Model string
13 Route string
14 RecommendedRoute string
15 ReasoningProtocol string
16 Thinking string
17 Reasoning ReasoningCapability
18 }
19
20 // OpenCodeGoRequestRoute recognizes the effective request endpoint, including
21 // the exact URLs saved by Desktop. Custom overrides must never inherit facts
22 // from an unrelated base URL.
23 func OpenCodeGoRequestRoute(kind, baseURL, requestURL, chatURL string) (string, bool) {
24 kind = strings.ToLower(strings.TrimSpace(kind))
25 exact := strings.TrimSpace(requestURL)
26 if exact == "" && (kind == "openai" || kind == "chat") {
27 exact = strings.TrimSpace(chatURL)
28 }
29 if exact != "" {
30 path, ok := officialOpenCodeGoPath(exact)
31 if !ok {
32 return "", false
33 }
34 switch {
35 case (kind == "openai" || kind == "chat") && path == "/zen/go/v1/chat/completions":
36 return OpenCodeGoRouteChat, true
37 case kind == "anthropic" && path == "/zen/go/v1/messages":
38 return OpenCodeGoRouteAnthropic, true
39 case kind == "responses" && path == "/zen/go/v1/responses":
40 return OpenCodeGoRouteResponses, true
41 default:
42 return "", false
43 }
44 }
45 if kind == "anthropic" {
46 baseURL = strings.TrimSuffix(strings.TrimRight(strings.TrimSpace(baseURL), "/"), "/v1")
47 }
48 return OfficialOpenCodeGoRoute(kind, baseURL)
49 }
50
51 // OpenCodeGoRecommendedRoute uses the pinned Pi catalog first. The compatibility
52 // catalog only fills absent IDs; alternate DeepSeek routes are not recommendations.
53 func OpenCodeGoRecommendedRoute(model string) (string, bool) {
54 model = strings.TrimSpace(model)
55 if OpenCodeGoDeepSeekModel(model) {
56 return OpenCodeGoRouteChat, true
57 }
58 for _, route := range []string{OpenCodeGoRouteChat, OpenCodeGoRouteAnthropic, OpenCodeGoRouteResponses} {
59 if slices.Contains(PiCatalogOpenCodeGoModelIDs(route), model) {
60 return route, true
61 }
62 }
63 for _, item := range []struct {
64 route string
65 models map[string]OpenCodeGoModelLimits
66 }{{OpenCodeGoRouteChat, OpenCodeGoChatModels()}, {OpenCodeGoRouteAnthropic, OpenCodeGoAnthropicModels()}, {OpenCodeGoRouteResponses, OpenCodeGoResponsesModels()}} {
67 if _, ok := item.models[model]; ok {
68 return item.route, true
69 }
70 }
71 return "", false
72 }
73
74 func OpenCodeGoDeepSeekModel(model string) bool {
75 switch strings.TrimSpace(model) {
76 case "deepseek-flash", "deepseek-v4-flash", "deepseek-v4-pro", "deepseek-v4-flash-vision-exp":
77 return true
78 }
79 return false
80 }
81
82 func LookupOpenCodeGoContract(kind, baseURL, requestURL, chatURL, model string) (OpenCodeGoContract, bool) {
83 route, ok := OpenCodeGoRequestRoute(kind, baseURL, requestURL, chatURL)
84 if !ok {
85 return OpenCodeGoContract{}, false
86 }
87 return OpenCodeGoContractForRoute(route, model)
88 }
89
90 func OpenCodeGoContractForRoute(route, model string) (OpenCodeGoContract, bool) {
91 model = strings.TrimSpace(model)
92 recommended, known := OpenCodeGoRecommendedRoute(model)
93 if !known {
94 return OpenCodeGoContract{}, false
95 }
96 if route != OpenCodeGoRouteChat && route != OpenCodeGoRouteAnthropic && route != OpenCodeGoRouteResponses {
97 return OpenCodeGoContract{}, false
98 }
99 if _, supported := LookupOfficialOpenCodeGo(routeKind(route), routeURL(route), model); !supported {
100 return OpenCodeGoContract{}, false
101 }
102 c := OpenCodeGoContract{Model: model, Route: route, RecommendedRoute: recommended}
103 if OpenCodeGoDeepSeekModel(model) {
104 c.RecommendedRoute, c.ReasoningProtocol, c.Thinking = OpenCodeGoRouteChat, "deepseek", "enabled"
105 c.Reasoning = ReasoningOptions("high", "disabled", "low", "high", "max")
106 if route == OpenCodeGoRouteResponses {
107 c.Reasoning = ReasoningOptions("high", "none", "low", "high", "max")
108 }
109 return c, true
110 }
111 if route == OpenCodeGoRouteAnthropic {
112 c.Thinking = "adaptive"
113 c.Reasoning = ReasoningOptions("", "low", "medium", "high", "xhigh", "max")
114 return c, true
115 }
116 levels := map[string][]string{
117 "glm-5.3": {"low", "high", "max"}, "glm-5.2": {"high", "max"}, "glm-5.1": {"low", "high"},
118 "kimi-k3": {"high", "max"}, "kimi-k2.7-code": {"low", "medium", "high"}, "kimi-k2.6": {"low", "medium", "high"},
119 "hy3": {"none", "low", "high"}, "grok-4.5": {"low", "medium", "high"},
120 "gpt-5.6-luna": {"none", "low", "medium", "high", "xhigh", "max"},
121 "muse-spark-1.2-contributor": {"minimal", "low", "medium", "high", "xhigh"},
122 }
123 if ids, ok := levels[model]; ok {
124 c.ReasoningProtocol = "openai"
125 c.Reasoning = ReasoningOptions("high", ids...)
126 } else if cap, ok := PiCatalogOpenCodeGoReasoning(route, model); ok {
127 c.ReasoningProtocol = "openai"
128 c.Reasoning = cap
129 }
130 return c, true
131 }
132
133 // FilterOpenCodeGoRequestModels is shared by discovery and settings. The
134 // effective request URL determines the protocol, including exact URL overrides.
135 func FilterOpenCodeGoRequestModels(kind, baseURL, requestURL, chatURL string, models []string) []string {
136 route, ok := OpenCodeGoRequestRoute(kind, baseURL, requestURL, chatURL)
137 if !ok {
138 return models
139 }
140 out := make([]string, 0, len(models))
141 for _, model := range models {
142 if _, supported := OpenCodeGoContractForRoute(route, model); supported {
143 out = append(out, model)
144 }
145 }
146 return out
147 }
148
149 // ApplyOpenCodeGoContract provides identical inputs to capability validation and
150 // serialization. Explicit declarations still win; migrations repair old defaults.
151 func ApplyOpenCodeGoContract(kind string, cfg Config) Config {
152 requestURL, _ := cfg.Extra["request_url"].(string)
153 chatURL, _ := cfg.Extra["chat_url"].(string)
154 c, ok := LookupOpenCodeGoContract(kind, cfg.BaseURL, requestURL, chatURL, cfg.Model)
155 if !ok {
156 return cfg
157 }
158 cfg.Extra = maps.Clone(cfg.Extra)
159 if cfg.Extra == nil {
160 cfg.Extra = map[string]any{}
161 }
162 protocol, _ := cfg.Extra["reasoning_protocol"].(string)
163 if strings.TrimSpace(protocol) == "" || protocol == "auto" {
164 cfg.Extra["reasoning_protocol"] = c.ReasoningProtocol
165 }
166 if (protocol == "" || protocol == "auto" || protocol == c.ReasoningProtocol) && len(c.Reasoning.Options) > 0 {
167 if ids, _ := cfg.Extra["supported_efforts"].([]string); len(ids) == 0 {
168 cfg.Extra["supported_efforts"] = c.Reasoning.IDs()
169 if def, _ := cfg.Extra["default_effort"].(string); def == "" {
170 cfg.Extra["default_effort"] = c.Reasoning.Default
171 }
172 }
173 }
174 return cfg
175 }
176
176 lines GO