fix(config): move RemovePluginModelRefs into its own file (follow-up to #8198)

RemovePluginModelRefs pushed edit.go past its file-size budget and its doc
comment, like the two in repairPluginModelRefs, ran past the comment budget.
The method moves unchanged into plugin_model_refs.go and the three comments
are cut to state the constraint only. No behaviour change.
This commit is contained in:
yhh committed 2026-09-24 23:09:12 -07:00
1 parent 7e5643d380
commit a233e8b13f
3 files changed
+58 -72

No files matched your search

-56
View File
@@ -603,62 +603,6 @@ func (c *Config) providerRemovalFallback(name string) string {
return ""
}
// RemovePluginModelRefs migrates model references that name models of the
// plugin id off that plugin, and reports whether anything changed.
//
// Plugin-namespaced refs (plugin/<plugin>/<provider>/<model>) resolve through
// boot's merged resolver rather than the config catalog, so SetDefaultModel
// accepts them without proof. That trade means removing the plugin strands
// every ref that named it: the next launch fails with "unknown model" and no
// session can start until the file is edited by hand. Removal calls this to
// close that gap the way RemoveProvider does for a deleted provider.
//
// References move to the first remaining configured provider. Unlike provider
// removal this never refuses: the plugin is already gone by the time it runs,
// so with no fallback the refs are cleared and the keyless-default policy in
// ResolveNewSessionChatModel picks the boot model instead.
func (c *Config) RemovePluginModelRefs(pluginID string) bool {
pluginID = strings.TrimSpace(pluginID)
if pluginID == "" {
return false
}
ownedByPlugin := func(ref string) bool {
return protocol.PluginRefOwner(strings.TrimSpace(ref)) == pluginID
}
defaultOwned := ownedByPlugin(c.DefaultModel)
plannerOwned := ownedByPlugin(c.Agent.PlannerModel)
subagentOwned := ownedByPlugin(c.Agent.SubagentModel)
subagentModelsOwned := map[string]bool{}
for skill, ref := range c.Agent.SubagentModels {
if ownedByPlugin(ref) {
subagentModelsOwned[skill] = true
}
}
if !defaultOwned && !plannerOwned && !subagentOwned && len(subagentModelsOwned) == 0 {
return false
}
fallback := c.providerRemovalFallback("")
if defaultOwned {
c.DefaultModel = fallback
}
if plannerOwned {
c.Agent.PlannerModel = fallback
}
if subagentOwned {
c.Agent.SubagentModel = fallback
}
for skill := range subagentModelsOwned {
if fallback != "" {
c.Agent.SubagentModels[skill] = fallback
} else {
delete(c.Agent.SubagentModels, skill)
}
}
return true
}
// validateProvider checks the fields a provider can't function without.
func validateProvider(e ProviderEntry) error {
switch {
+53
View File
@@ -0,0 +1,53 @@
package config
import (
"strings"
"reasonix/internal/extension/protocol"
)
// RemovePluginModelRefs moves every model ref owned by pluginID to the first
// remaining configured provider, or clears it when none is left, and reports
// whether anything changed. Plugin refs resolve outside the config catalog, so
// a removed plugin would otherwise strand them and fail the next boot.
func (c *Config) RemovePluginModelRefs(pluginID string) bool {
pluginID = strings.TrimSpace(pluginID)
if pluginID == "" {
return false
}
ownedByPlugin := func(ref string) bool {
return protocol.PluginRefOwner(strings.TrimSpace(ref)) == pluginID
}
defaultOwned := ownedByPlugin(c.DefaultModel)
plannerOwned := ownedByPlugin(c.Agent.PlannerModel)
subagentOwned := ownedByPlugin(c.Agent.SubagentModel)
subagentModelsOwned := map[string]bool{}
for skill, ref := range c.Agent.SubagentModels {
if ownedByPlugin(ref) {
subagentModelsOwned[skill] = true
}
}
if !defaultOwned && !plannerOwned && !subagentOwned && len(subagentModelsOwned) == 0 {
return false
}
fallback := c.providerRemovalFallback("")
if defaultOwned {
c.DefaultModel = fallback
}
if plannerOwned {
c.Agent.PlannerModel = fallback
}
if subagentOwned {
c.Agent.SubagentModel = fallback
}
for skill := range subagentModelsOwned {
if fallback != "" {
c.Agent.SubagentModels[skill] = fallback
} else {
delete(c.Agent.SubagentModels, skill)
}
}
return true
}
+5 -16
View File
@@ -717,15 +717,9 @@ func (t *installSourceTool) applyRemovePluginPackage(_ request, act *action) err
return nil
}
// repairPluginModelRefs moves default_model and the optional agent model refs
// off a plugin that has just been removed. Its models stop resolving the
// moment the package is gone, and boot rejects an unknown default_model, so
// leaving the ref in place locks the user out of every session until they edit
// the file by hand.
//
// Only the user config is repaired: plugin packages install globally, so a
// project reasonix.toml that names a plugin model is the project's own choice
// to make and to undo.
// repairPluginModelRefs moves user-config model refs off a removed plugin,
// since boot rejects an unknown default_model. Project configs are left alone:
// a project naming a plugin model made that choice itself.
func repairPluginModelRefs(pluginID string) error {
path := config.UserConfigPath()
if strings.TrimSpace(path) == "" {
@@ -734,13 +728,8 @@ func repairPluginModelRefs(pluginID string) error {
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return nil
}
// Probe a throwaway copy first. A config with no ref to this plugin is left
// byte-for-byte alone, and a malformed one is left for the user to recover:
// rewriting it here would trade a stale ref for a lost file.
//
// Credentials load with it: the fallback provider must be one the user can
// actually reach, and Configured() reports false for every provider when
// the keys are absent.
// A strict read-only probe leaves an unaffected or malformed file untouched;
// credentials load so the fallback is a provider the user can reach.
probe, err := config.LoadForEditReadOnlyStrict(path)
if err != nil || probe == nil || !probe.RemovePluginModelRefs(pluginID) {
return nil