返回 DeepSeek-Reasonix
official_provider_access_install.go
根目录 / desktop / official_provider_access_install.go
1 package main
2
3 import (
4 "fmt"
5 "slices"
6 "strings"
7
8 "reasonix/internal/config"
9 )
10
11 // AddOfficialProviderAccess adds one curated desktop provider template to the
12 // Settings > Model > Access list. The runtime default providers still exist
13 // independently; this only records the user's explicit access setup.
14 func (a *App) AddOfficialProviderAccess(kind, key string) (string, error) {
15 return a.applyModelConfigChangeWithWarning("provider access", func(c *config.Config) error { return addOfficialProviderAccessConfig(c, kind, key) })
16 }
17
18 func addOfficialProviderAccessConfig(c *config.Config, kind, key string) error {
19 entries, keyEnv, err := officialProviderTemplate(kind, c.DeepSeekOfficialPricingLanguage())
20 if err != nil {
21 return err
22 }
23 if _, err := validateOfficialProviderAccessInstall(c, kind, entries, keyEnv); err != nil {
24 return err
25 }
26 names, err := installOfficialProviderAccess(c, kind, entries)
27 if err != nil {
28 return err
29 }
30 if strings.TrimSpace(key) != "" {
31 env, err := c.StageModelCredentialLocked(key)
32 if err != nil {
33 return err
34 }
35 for i := range c.Providers {
36 if slices.Contains(names, c.Providers[i].Name) {
37 c.Providers[i].APIKeyEnv = env
38 }
39 }
40 }
41 addProviderAccess(c, names...)
42 return nil
43 }
44
45 func validateOfficialProviderAccessInstall(c *config.Config, kind string, entries []config.ProviderEntry, fallbackKeyEnv string) (string, error) {
46 keyEnv := strings.TrimSpace(fallbackKeyEnv)
47 wantKind := strings.ToLower(strings.TrimSpace(kind))
48 for _, entry := range entries {
49 existing, ok := c.Provider(strings.TrimSpace(entry.Name))
50 if !ok {
51 continue
52 }
53 if officialProviderKindFromEntry(*existing) != wantKind {
54 return "", fmt.Errorf("official provider %q cannot be added because provider name %q already belongs to a custom endpoint; edit, rename, or remove the existing provider first", kind, entry.Name)
55 }
56 if env := strings.TrimSpace(existing.APIKeyEnv); env != "" {
57 keyEnv = env
58 }
59 }
60 return keyEnv, nil
61 }
62
63 func installOfficialProviderAccess(c *config.Config, kind string, entries []config.ProviderEntry) ([]string, error) {
64 if c == nil {
65 return nil, fmt.Errorf("config is nil")
66 }
67 wantKind := strings.ToLower(strings.TrimSpace(kind))
68 names := make([]string, 0, len(entries))
69 for _, entry := range entries {
70 name := strings.TrimSpace(entry.Name)
71 existing, ok := c.Provider(name)
72 if ok {
73 if officialProviderKindFromEntry(*existing) != wantKind {
74 return nil, fmt.Errorf("official provider %q cannot be added because provider name %q already belongs to a custom endpoint; edit, rename, or remove the existing provider first", kind, name)
75 }
76 // Re-adding access must not reset customized official transport fields.
77 // Only repair an unusable legacy entry that has no model declaration.
78 if len(existing.ModelList()) == 0 {
79 repaired := repairOfficialProviderCatalog(*existing, entry)
80 if err := c.UpsertProvider(repaired); err != nil {
81 return nil, err
82 }
83 }
84 } else if err := c.UpsertProvider(entry); err != nil {
85 return nil, err
86 }
87 names = append(names, name)
88 }
89 if wantKind == "deepseek" && slices.Contains(names, "deepseek") {
90 retargetDeepSeekOfficialReferences(c)
91 }
92 return names, nil
93 }
94
95 func repairOfficialProviderCatalog(existing, template config.ProviderEntry) config.ProviderEntry {
96 existing.Model = ""
97 existing.Models = append([]string(nil), template.ModelList()...)
98 existing.Default = template.DefaultModel()
99 if existing.ContextWindow == 0 {
100 existing.ContextWindow = template.ContextWindow
101 }
102 if existing.MaxOutputTokens == 0 {
103 existing.MaxOutputTokens = template.MaxOutputTokens
104 }
105 if strings.TrimSpace(existing.BalanceURL) == "" {
106 existing.BalanceURL = template.BalanceURL
107 }
108 if strings.TrimSpace(existing.Thinking) == "" {
109 existing.Thinking = template.Thinking
110 }
111 if existing.WebSearch == nil && template.WebSearch != nil {
112 enabled := *template.WebSearch
113 existing.WebSearch = &enabled
114 }
115 if existing.Prices == nil {
116 existing.Prices = template.Prices
117 }
118 if existing.Price == nil {
119 existing.Price = template.Price
120 }
121 if existing.ModelOverrides == nil {
122 existing.ModelOverrides = map[string]config.ProviderModelOverride{}
123 }
124 for model, override := range template.ModelOverrides {
125 if _, ok := existing.ModelOverrides[model]; !ok {
126 existing.ModelOverrides[model] = override
127 }
128 }
129 return existing
130 }
131
132 func retargetDeepSeekOfficialReferences(c *config.Config) {
133 if c == nil {
134 return
135 }
136 retarget := func(ref string) string {
137 ref = strings.TrimSpace(ref)
138 providerName, model, hasModel := strings.Cut(ref, "/")
139 switch providerName {
140 case "deepseek-flash":
141 if !hasModel || strings.TrimSpace(model) == "" {
142 model = "deepseek-v4-flash"
143 }
144 case "deepseek-pro":
145 if !hasModel || strings.TrimSpace(model) == "" {
146 model = "deepseek-v4-pro"
147 }
148 default:
149 return ref
150 }
151 return "deepseek/" + model
152 }
153 c.DefaultModel = retarget(c.DefaultModel)
154 c.Agent.PlannerModel = retarget(c.Agent.PlannerModel)
155 c.Agent.SubagentModel = retarget(c.Agent.SubagentModel)
156 for skillName, ref := range c.Agent.SubagentModels {
157 c.Agent.SubagentModels[skillName] = retarget(ref)
158 }
159 }
160
160 lines GO