返回 DeepSeek-Reasonix
model_settings_edit.go
根目录 / internal / config / model_settings_edit.go
1 package config
2
3 import (
4 "bytes"
5 "fmt"
6 "os"
7 "reflect"
8
9 "github.com/BurntSushi/toml"
10 fileencoding "reasonix/internal/fileutil/encoding"
11 )
12
13 // ModelSettingsBaseline must be captured before editing under the config lock.
14 func (c *Config) ModelSettingsBaseline() string { return RenderTOMLForScope(c, RenderScopeUser) }
15
16 // SaveModelSettingsTo applies only the typed changes to the original document.
17 // Unknown top-level and provider fields survive, including nested future fields.
18 // A new file still receives the standard annotated template.
19 func (c *Config) SaveModelSettingsTo(path, baseline string) error {
20 if c == nil {
21 return fmt.Errorf("save model settings: nil config")
22 }
23 if c.editLoadErr != nil {
24 return c.editLoadErr
25 }
26 userConfig := IsUserConfigPath(path)
27 if userConfig {
28 if err := currentUserConfigEditLockError(); err != nil {
29 return err
30 }
31 }
32 resolved, err := resolveConfigAccessPath(path, userConfig)
33 if err != nil {
34 return err
35 }
36 raw, err := fileencoding.ReadFileUTF8(resolved)
37 if os.IsNotExist(err) {
38 return c.SaveTo(path)
39 }
40 if err != nil {
41 return err
42 }
43 doc, before, after := map[string]any{}, map[string]any{}, map[string]any{}
44 for _, input := range []struct {
45 body string
46 dest *map[string]any
47 }{{string(raw), &doc}, {baseline, &before}, {c.ModelSettingsBaseline(), &after}} {
48 if _, err := toml.Decode(input.body, input.dest); err != nil {
49 return err
50 }
51 }
52 mergeModelSettingsDelta(doc, before, after)
53 var encoded bytes.Buffer
54 if err := toml.NewEncoder(&encoded).Encode(doc); err != nil {
55 return err
56 }
57 return c.writeModelConfigResolved(resolved, encoded.String(), configFilePerm(path))
58 }
59
60 func mergeModelSettingsDelta(doc, before, after map[string]any) {
61 for key, previous := range before {
62 next, exists := after[key]
63 if !exists {
64 delete(doc, key)
65 continue
66 }
67 if reflect.DeepEqual(previous, next) {
68 continue
69 }
70 oldTable, oldOK := previous.(map[string]any)
71 newTable, newOK := next.(map[string]any)
72 if oldOK && newOK {
73 target, ok := doc[key].(map[string]any)
74 if !ok {
75 target = map[string]any{}
76 }
77 mergeModelSettingsDelta(target, oldTable, newTable)
78 doc[key] = target
79 continue
80 }
81 if key == "providers" || key == "hosts" || key == "projects" {
82 oldEntries, oldOK := previous.([]map[string]any)
83 newEntries, newOK := next.([]map[string]any)
84 if oldOK && newOK {
85 rawEntries, _ := doc[key].([]map[string]any)
86 doc[key] = mergeModelProviderEntries(rawEntries, oldEntries, newEntries)
87 continue
88 }
89 }
90 doc[key] = next
91 }
92 for key, next := range after {
93 if _, exists := before[key]; !exists {
94 doc[key] = next
95 }
96 }
97 }
98
99 func mergeModelProviderEntries(raw, before, after []map[string]any) []map[string]any {
100 identity := func(entry map[string]any) string {
101 if host, ok := entry["host_id"].(string); ok {
102 workspace, _ := entry["workspace"].(string)
103 return host + "\x00" + workspace
104 }
105 name, _ := entry["name"].(string)
106 return name
107 }
108 index := func(entries []map[string]any) map[string]map[string]any {
109 result := map[string]map[string]any{}
110 for _, entry := range entries {
111 if name := identity(entry); name != "" {
112 result[name] = entry
113 }
114 }
115 return result
116 }
117 rawByName, beforeByName := index(raw), index(before)
118 result := make([]map[string]any, 0, len(after))
119 for _, entry := range after {
120 name := identity(entry)
121 target, exists := rawByName[name]
122 if !exists {
123 target = map[string]any{}
124 }
125 mergeModelSettingsDelta(target, beforeByName[name], entry)
126 // A default provider newly materialized by an edit needs its identity.
127 if !exists {
128 target = entry
129 }
130 result = append(result, target)
131 }
132 return result
133 }
134
134 lines GO