返回 DeepSeek-Reasonix
reasoning_entry.go
根目录 / internal / config / reasoning_entry.go
1 package config
2
3 import (
4 "slices"
5 "strings"
6 )
7
8 // ResolveReasoningEntry overlays current built-in facts on a private model
9 // entry. Only the user's declarations live in Config. Missing declarations
10 // therefore follow catalog updates without rewriting a saved connection.
11 // Explicit model settings win over connection settings, then built-in facts.
12 func ResolveReasoningEntry(entry *ProviderEntry) *ProviderEntry {
13 if entry == nil {
14 return nil
15 }
16 e := *entry
17 if e.Model == "" {
18 e.Model = e.DefaultModel()
19 }
20 e.applyModelOverride()
21 RepairProviderEndpointContract(&e)
22 defaults, ok := builtinReasoningDefaults(&e)
23 if !ok {
24 return &e
25 }
26 protocol := explicitReasoningProtocol(&e)
27 // A user-selected dialect owns its vocabulary. Never graft the preset's
28 // DeepSeek levels onto an explicitly selected GLM/OpenAI/none protocol.
29 if protocol != "" && protocol != defaults.ReasoningProtocol {
30 return &e
31 }
32 if protocol == "" {
33 e.ReasoningProtocol = defaults.ReasoningProtocol
34 e.reasoningProtocolAutomatic = true
35 }
36 if len(e.SupportedEfforts) == 0 {
37 e.SupportedEfforts = append([]string(nil), defaults.SupportedEfforts...)
38 e.reasoningAutomatic = true
39 if e.DefaultEffort == "" {
40 e.DefaultEffort = defaults.DefaultEffort
41 e.reasoningDefaultAutomatic = true
42 }
43 }
44 return &e
45 }
46
47 // stripRuntimeReasoningDefaults prevents callers saving a resolved runtime
48 // entry (for example /effort materializing a default provider) from freezing
49 // inherited facts. Values changed since resolution remain explicit edits.
50 func stripRuntimeReasoningDefaults(e *ProviderEntry) {
51 if !e.reasoningAutomatic && !e.reasoningProtocolAutomatic && !e.reasoningDefaultAutomatic {
52 return
53 }
54 if defaults, ok := builtinReasoningDefaults(e); ok {
55 if e.reasoningAutomatic && slices.Equal(e.SupportedEfforts, defaults.SupportedEfforts) {
56 e.SupportedEfforts = nil
57 }
58 if e.reasoningProtocolAutomatic && e.ReasoningProtocol == defaults.ReasoningProtocol {
59 e.ReasoningProtocol = ""
60 }
61 if e.reasoningDefaultAutomatic && e.DefaultEffort == defaults.DefaultEffort {
62 e.DefaultEffort = ""
63 }
64 }
65 e.reasoningAutomatic, e.reasoningProtocolAutomatic, e.reasoningDefaultAutomatic = false, false, false
66 }
67
68 // PresetReasoningForEdit removes generated reasoning metadata from a newly
69 // installed template. Existing saved declarations are never guessed to be
70 // generated: their provenance is unavailable, so they remain user-owned.
71 func presetReasoningForEdit(e *ProviderEntry) {
72 e.ReasoningProtocol, e.DefaultEffort, e.SupportedEfforts = "", "", nil
73 for model, ov := range e.ModelOverrides {
74 ov.ReasoningProtocol, ov.DefaultEffort, ov.SupportedEfforts = "", "", nil
75 if modelOverrideEmpty(ov) {
76 delete(e.ModelOverrides, model)
77 } else {
78 e.ModelOverrides[model] = ov
79 }
80 }
81 if len(e.ModelOverrides) == 0 {
82 e.ModelOverrides = nil
83 }
84 }
85
86 func builtinReasoningDefaults(e *ProviderEntry) (ProviderModelOverride, bool) {
87 // Names and preset IDs are editable labels, not proof of endpoint identity.
88 // Exact endpoint/protocol matching also keeps custom gateways and paths from
89 // accidentally receiving a first-party contract.
90 request, valid := normalizedExactProviderRequestURL(ProviderEffectiveRequestURL(e))
91 if !valid {
92 return ProviderModelOverride{}, false
93 }
94 for _, preset := range curatedProviderPresets {
95 for _, template := range preset.Entries {
96 if template.Kind != e.Kind || !template.HasModel(e.Model) {
97 continue
98 }
99 candidate, valid := normalizedExactProviderRequestURL(ProviderEffectiveRequestURL(&template))
100 if !valid || candidate != request {
101 continue
102 }
103 template.Model = e.Model
104 template.applyModelOverride()
105 return ProviderModelOverride{
106 ReasoningProtocol: reasoningProtocolForResolvedEntry(&template),
107 SupportedEfforts: template.SupportedEfforts,
108 DefaultEffort: template.DefaultEffort,
109 }, true
110 }
111 }
112 return ProviderModelOverride{}, false
113 }
114
115 // RebindSessionEffort binds the existing model/effort pair as one selection.
116 // A route change clears inherited intent even when both models spell a level
117 // "high". Same-route values are preserved for strict validation, never clamped.
118 // Existing persisted model and effort fields remain the storage contract.
119 func RebindSessionEffort(cfg *Config, previous, next string, effort *string) *string {
120 if effort == nil {
121 return nil
122 }
123 canonical := func(ref string) string {
124 ref = strings.TrimSpace(ref)
125 if cfg != nil {
126 if e, ok := cfg.ResolveModel(ref); ok {
127 return e.Name + "/" + e.Model
128 }
129 }
130 return ref
131 }
132 if canonical(previous) != canonical(next) {
133 return nil
134 }
135 value := *effort
136 return &value
137 }
138
138 lines GO