PatchConfig applies a JSON deep-merge to an installed model's YAML and reloads. Returns the merged config that's now in the loader. Mirrors PatchConfigEndpoint: read raw YAML from disk (not the in-memory config — which has SetDefaults applied and would persist runtime defaults like top_p/temperatur
(_ context.Context, name string, patch map[string]any)
| 90 | // like top_p/temperature/mirostat), deep-merge the patch, validate, write, |
| 91 | // reload, preload (preload errors are non-fatal — log only). |
| 92 | func (s *ConfigService) PatchConfig(_ context.Context, name string, patch map[string]any) (*config.ModelConfig, error) { |
| 93 | if name == "" { |
| 94 | return nil, ErrNameRequired |
| 95 | } |
| 96 | if len(patch) == 0 { |
| 97 | return nil, ErrEmptyBody |
| 98 | } |
| 99 | cfg, exists := s.Loader.GetModelConfig(name) |
| 100 | if !exists { |
| 101 | return nil, ErrNotFound |
| 102 | } |
| 103 | configPath := cfg.GetModelConfigFile() |
| 104 | if err := utils.VerifyPath(configPath, s.modelsPath()); err != nil { |
| 105 | return nil, fmt.Errorf("%w: %v", ErrPathNotTrusted, err) |
| 106 | } |
| 107 | diskYAML, err := os.ReadFile(configPath) |
| 108 | if err != nil { |
| 109 | return nil, fmt.Errorf("read config file: %w", err) |
| 110 | } |
| 111 | var existingMap map[string]any |
| 112 | if err := yaml.Unmarshal(diskYAML, &existingMap); err != nil { |
| 113 | return nil, fmt.Errorf("parse existing config: %w", err) |
| 114 | } |
| 115 | if existingMap == nil { |
| 116 | existingMap = map[string]any{} |
| 117 | } |
| 118 | patchMerge(existingMap, patch, mapLeafFieldPaths(), "") |
| 119 | yamlData, err := yaml.Marshal(existingMap) |
| 120 | if err != nil { |
| 121 | return nil, fmt.Errorf("marshal merged YAML: %w", err) |
| 122 | } |
| 123 | var updated config.ModelConfig |
| 124 | if err := yaml.Unmarshal(yamlData, &updated); err != nil { |
| 125 | return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err) |
| 126 | } |
| 127 | if valid, vErr := updated.Validate(); !valid { |
| 128 | if vErr != nil { |
| 129 | return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, vErr) |
| 130 | } |
| 131 | return nil, ErrInvalidConfig |
| 132 | } |
| 133 | if err := s.Loader.ValidateAliasTarget(&updated); err != nil { |
| 134 | return nil, fmt.Errorf("%w: %v", ErrInvalidConfig, err) |
| 135 | } |
| 136 | if err := writeFileAtomic(configPath, yamlData, 0644); err != nil { |
| 137 | return nil, fmt.Errorf("write config file: %w", err) |
| 138 | } |
| 139 | if err := s.Loader.LoadModelConfigsFromPath(s.modelsPath(), s.AppConfig.ToConfigLoaderOptions()...); err != nil { |
| 140 | return nil, fmt.Errorf("reload configs: %w", err) |
| 141 | } |
| 142 | // Preload is best-effort — a failure here doesn't undo the patch. |
| 143 | _ = s.Loader.Preload(s.modelsPath()) |
| 144 | return &updated, nil |
| 145 | } |
| 146 | |
| 147 | // mapLeafFieldPaths returns the set of dotted config paths whose schema type is |
| 148 | // a map that the editor edits as one complete value (e.g. |
no test coverage detected