From b0daf0e9496f59578d26ff516cb1d379dfac9232 Mon Sep 17 00:00:00 2001 From: Kujtim Hoxha Date: Fri, 6 Feb 2026 13:29:57 +0100 Subject: [PATCH] refactor: remove Config() escape hatch, eliminate all direct Config access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove Service.Config() method entirely. Remove Config.Agents field. Add Provider, SetProvider, AllProviders, SetAttribution, SetSkillsPaths, SetLSP to Service. Migrate all remaining callers (chat, coordinator, app, UI, commands, MCP, prompt, init) to use Service methods exclusively. No code outside the config package touches Config directly. 🐾 Generated with Crush Assisted-by: Claude Opus 4.6 via Crush --- internal/agent/agent_tool.go | 2 +- internal/agent/agentic_fetch_tool.go | 6 ++--- internal/agent/common_test.go | 14 +++++----- internal/agent/coordinator.go | 23 +++++++--------- internal/app/app.go | 18 ++++++------- internal/cmd/models.go | 2 +- internal/cmd/stats.go | 2 +- internal/config/config.go | 2 -- internal/config/load.go | 2 +- internal/config/load_test.go | 21 ++++++++------- internal/config/provider.go | 4 +-- internal/config/provider_test.go | 2 +- internal/config/service.go | 40 ++++++++++++++++++++++------ internal/ui/chat/messages.go | 6 ++--- internal/ui/dialog/models.go | 12 ++++----- internal/ui/model/onboarding.go | 4 +-- internal/ui/model/sidebar.go | 2 +- internal/ui/model/ui.go | 24 ++++++++--------- 18 files changed, 103 insertions(+), 83 deletions(-) diff --git a/internal/agent/agent_tool.go b/internal/agent/agent_tool.go index 64e95f5f403dad02502864cd39d1209b252c0621..b64cdda9131ad9c4a412bc23d8d1297a2dfa1ea8 100644 --- a/internal/agent/agent_tool.go +++ b/internal/agent/agent_tool.go @@ -67,7 +67,7 @@ func (c *coordinator) agentTool(ctx context.Context) (fantasy.AgentTool, error) maxTokens = model.ModelCfg.MaxTokens } - providerCfg, ok := c.cfgSvc.Config().Providers[model.ModelCfg.Provider] + providerCfg, ok := c.cfgSvc.Provider(model.ModelCfg.Provider) if !ok { return fantasy.ToolResponse{}, errors.New("model provider not configured") } diff --git a/internal/agent/agentic_fetch_tool.go b/internal/agent/agentic_fetch_tool.go index 4a8a4aac74ebd0aea83b42ed8e3953529550f622..9d8103b2a2f3744afb28daa122766889ed53a24e 100644 --- a/internal/agent/agentic_fetch_tool.go +++ b/internal/agent/agentic_fetch_tool.go @@ -98,7 +98,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) ( return fantasy.ToolResponse{}, permission.ErrorPermissionDenied } - tmpDir, err := os.MkdirTemp(c.cfgSvc.Config().Options.DataDirectory, "crush-fetch-*") + tmpDir, err := os.MkdirTemp(c.cfgSvc.DataDirectory(), "crush-fetch-*") if err != nil { return fantasy.NewTextErrorResponse(fmt.Sprintf("Failed to create temporary directory: %s", err)), nil } @@ -156,7 +156,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) ( return fantasy.ToolResponse{}, fmt.Errorf("error building system prompt: %s", err) } - smallProviderCfg, ok := c.cfgSvc.Config().Providers[small.ModelCfg.Provider] + smallProviderCfg, ok := c.cfgSvc.Provider(small.ModelCfg.Provider) if !ok { return fantasy.ToolResponse{}, errors.New("small model provider not configured") } @@ -177,7 +177,7 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) ( SmallModel: small, SystemPromptPrefix: smallProviderCfg.SystemPromptPrefix, SystemPrompt: systemPrompt, - DisableAutoSummarize: c.cfgSvc.Config().Options.DisableAutoSummarize, + DisableAutoSummarize: c.cfgSvc.DisableAutoSummarize(), IsYolo: c.permissions.SkipRequests(), Sessions: c.sessions, Messages: c.messages, diff --git a/internal/agent/common_test.go b/internal/agent/common_test.go index 446c979df9db48611e8d25b931c7b72717cca673..e2d6ca97d548cc0395da270d3866ea00319195cb 100644 --- a/internal/agent/common_test.go +++ b/internal/agent/common_test.go @@ -177,18 +177,18 @@ func coderAgent(r *vcr.Recorder, env fakeEnv, large, small fantasy.LanguageModel // NOTE(@andreynering): Set a fixed config to ensure cassettes match // independently of user config on `$HOME/.config/crush/crush.json`. - cfg.Config().Options.Attribution = &config.Attribution{ + cfg.SetAttribution(&config.Attribution{ TrailerStyle: "co-authored-by", GeneratedWith: true, - } + }) // Clear skills paths to ensure test reproducibility - user's skills // would be included in prompt and break VCR cassette matching. - cfg.Config().Options.SkillsPaths = []string{} + cfg.SetSkillsPaths([]string{}) // Clear LSP config to ensure test reproducibility - user's LSP config // would be included in prompt and break VCR cassette matching. - cfg.Config().LSP = nil + cfg.SetLSP(nil) systemPrompt, err := prompt.Build(context.TODO(), large.Provider(), large.Model(), cfg) if err != nil { @@ -197,19 +197,19 @@ func coderAgent(r *vcr.Recorder, env fakeEnv, large, small fantasy.LanguageModel // Get the model name for the bash tool modelName := large.Model() // fallback to ID if Name not available - if model := cfg.Config().GetModel(large.Provider(), large.Model()); model != nil { + if model := cfg.GetModel(large.Provider(), large.Model()); model != nil { modelName = model.Name } allTools := []fantasy.AgentTool{ - tools.NewBashTool(env.permissions, env.workingDir, cfg.Config().Options.Attribution, modelName), + tools.NewBashTool(env.permissions, env.workingDir, cfg.Attribution(), modelName), tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()), tools.NewEditTool(env.lspClients, env.permissions, env.history, *env.filetracker, env.workingDir), tools.NewMultiEditTool(env.lspClients, env.permissions, env.history, *env.filetracker, env.workingDir), tools.NewFetchTool(env.permissions, env.workingDir, r.GetDefaultClient()), tools.NewGlobTool(env.workingDir), tools.NewGrepTool(env.workingDir), - tools.NewLsTool(env.permissions, env.workingDir, cfg.Config().Tools.Ls), + tools.NewLsTool(env.permissions, env.workingDir, cfg.ToolLsConfig()), tools.NewSourcegraphTool(r.GetDefaultClient()), tools.NewViewTool(env.lspClients, env.permissions, *env.filetracker, env.workingDir), tools.NewWriteTool(env.lspClients, env.permissions, env.history, *env.filetracker, env.workingDir), diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index dfc88f490dc7f594f5d859b7b5b608518b47d9bc..6c07e84d6f84e1e2ef040aeffc5c61eb5b54c6a7 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -61,7 +61,6 @@ type Coordinator interface { } type coordinator struct { - cfg *config.Config cfgSvc *config.Service sessions session.Service messages message.Service @@ -86,9 +85,7 @@ func NewCoordinator( filetracker filetracker.Service, lspClients *csync.Map[string, *lsp.Client], ) (Coordinator, error) { - cfg := cfgSvc.Config() c := &coordinator{ - cfg: cfg, cfgSvc: cfgSvc, sessions: sessions, messages: messages, @@ -99,7 +96,7 @@ func NewCoordinator( agents: make(map[string]SessionAgent), } - agentCfg, ok := cfg.Agents[config.AgentCoder] + agentCfg, ok := cfgSvc.Agent(config.AgentCoder) if !ok { return nil, errors.New("coder agent not configured") } @@ -147,7 +144,7 @@ func (c *coordinator) Run(ctx context.Context, sessionID string, prompt string, attachments = filteredAttachments } - providerCfg, ok := c.cfgSvc.Config().Providers[model.ModelCfg.Provider] + providerCfg, ok := c.cfgSvc.Provider(model.ModelCfg.Provider) if !ok { return nil, errors.New("model provider not configured") } @@ -360,7 +357,7 @@ func (c *coordinator) buildAgent(ctx context.Context, prompt *prompt.Prompt, age return nil, err } - largeProviderCfg, _ := c.cfgSvc.Config().Providers[large.ModelCfg.Provider] + largeProviderCfg, _ := c.cfgSvc.Provider(large.ModelCfg.Provider) result := NewSessionAgent(SessionAgentOptions{ large, small, @@ -415,7 +412,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan // Get the model name for the agent modelName := "" - if modelCfg, ok := c.cfgSvc.Config().Models[agent.Model]; ok { + if modelCfg, ok := c.cfgSvc.SelectedModel(agent.Model); ok { if model := c.cfgSvc.GetModel(modelCfg.Provider, modelCfg.Model); model != nil { modelName = model.Name } @@ -479,16 +476,16 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan // TODO: when we support multiple agents we need to change this so that we pass in the agent specific model config func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Model, Model, error) { - largeModelCfg, ok := c.cfgSvc.Config().Models[config.SelectedModelTypeLarge] + largeModelCfg, ok := c.cfgSvc.SelectedModel(config.SelectedModelTypeLarge) if !ok { return Model{}, Model{}, errors.New("large model not selected") } - smallModelCfg, ok := c.cfgSvc.Config().Models[config.SelectedModelTypeSmall] + smallModelCfg, ok := c.cfgSvc.SelectedModel(config.SelectedModelTypeSmall) if !ok { return Model{}, Model{}, errors.New("small model not selected") } - largeProviderCfg, ok := c.cfgSvc.Config().Providers[largeModelCfg.Provider] + largeProviderCfg, ok := c.cfgSvc.Provider(largeModelCfg.Provider) if !ok { return Model{}, Model{}, errors.New("large model provider not configured") } @@ -498,7 +495,7 @@ func (c *coordinator) buildAgentModels(ctx context.Context, isSubAgent bool) (Mo return Model{}, Model{}, err } - smallProviderCfg, ok := c.cfgSvc.Config().Providers[smallModelCfg.Provider] + smallProviderCfg, ok := c.cfgSvc.Provider(smallModelCfg.Provider) if !ok { return Model{}, Model{}, errors.New("large model provider not configured") } @@ -881,7 +878,7 @@ func (c *coordinator) QueuedPromptsList(sessionID string) []string { } func (c *coordinator) Summarize(ctx context.Context, sessionID string) error { - providerCfg, ok := c.cfgSvc.Config().Providers[c.currentAgent.Model().ModelCfg.Provider] + providerCfg, ok := c.cfgSvc.Provider(c.currentAgent.Model().ModelCfg.Provider) if !ok { return errors.New("model provider not configured") } @@ -912,7 +909,7 @@ func (c *coordinator) refreshApiKeyTemplate(ctx context.Context, providerCfg con } providerCfg.APIKey = newAPIKey - c.cfgSvc.Config().Providers[providerCfg.ID] = providerCfg + c.cfgSvc.SetProvider(providerCfg.ID, providerCfg) if err := c.UpdateModels(ctx); err != nil { return err diff --git a/internal/app/app.go b/internal/app/app.go index 08cf8703ffb83d8c1cb7a26a287de2d0c45bc8fc..aa92bc18b17b63ceb33daec401022f0ed7ca1808 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -75,15 +75,15 @@ type App struct { // New initializes a new application instance. func New(ctx context.Context, conn *sql.DB, cfgSvc *config.Service) (*App, error) { - cfg := cfgSvc.Config() q := db.New(conn) sessions := session.NewService(q, conn) messages := message.NewService(q) files := history.NewService(q, conn) - skipPermissionsRequests := cfg.Permissions != nil && cfg.Permissions.SkipRequests + perms := cfgSvc.Permissions() + skipPermissionsRequests := perms != nil && perms.SkipRequests var allowedTools []string - if cfg.Permissions != nil && cfg.Permissions.AllowedTools != nil { - allowedTools = cfg.Permissions.AllowedTools + if perms != nil && perms.AllowedTools != nil { + allowedTools = perms.AllowedTools } app := &App{ @@ -322,7 +322,7 @@ func (app *App) UpdateAgentModel(ctx context.Context) error { // If largeModel is provided but smallModel is not, the small model defaults to // the provider's default small model. func (app *App) overrideModelsForNonInteractive(ctx context.Context, largeModel, smallModel string) error { - providers := maps.Clone(app.configService.Config().Providers) + providers := maps.Clone(app.configService.AllProviders()) largeMatches, smallMatches, err := findModels(providers, largeModel, smallModel) if err != nil { @@ -370,11 +370,11 @@ func (app *App) overrideModelsForNonInteractive(ctx context.Context, largeModel, // GetDefaultSmallModel returns the default small model for the given // provider. Falls back to the large model if no default is found. func (app *App) GetDefaultSmallModel(providerID string) config.SelectedModel { - cfg := app.configService.Config() - largeModelCfg := cfg.Models[config.SelectedModelTypeLarge] + svc := app.configService + largeModelCfg, _ := svc.SelectedModel(config.SelectedModelTypeLarge) // Find the provider in the known providers list to get its default small model. - knownProviders, _ := config.Providers(cfg) + knownProviders, _ := config.Providers(svc) var knownProvider *catwalk.Provider for _, p := range knownProviders { if string(p.ID) == providerID { @@ -390,7 +390,7 @@ func (app *App) GetDefaultSmallModel(providerID string) config.SelectedModel { } defaultSmallModelID := knownProvider.DefaultSmallModelID - model := cfg.GetModel(providerID, defaultSmallModelID) + model := svc.GetModel(providerID, defaultSmallModelID) if model == nil { slog.Warn("Default small model not found, using large model", "provider", providerID, "model", largeModelCfg.Model) return largeModelCfg diff --git a/internal/cmd/models.go b/internal/cmd/models.go index bdfee5e1b61186ef3b09e0ef4e381ecdd4b6c5e9..7d38ad15b8e7ec68cbca375778df8101d68c7e43 100644 --- a/internal/cmd/models.go +++ b/internal/cmd/models.go @@ -55,7 +55,7 @@ crush models gpt5`, var providerIDs []string providerModels := make(map[string][]string) - for providerID, provider := range cfg.Config().Providers { + for providerID, provider := range cfg.AllProviders() { if provider.Disable { continue } diff --git a/internal/cmd/stats.go b/internal/cmd/stats.go index 4f655f9b755223fc22b3914d3f5504d04275002c..f9877256a54791311fc603fe6513a910b0c7863b 100644 --- a/internal/cmd/stats.go +++ b/internal/cmd/stats.go @@ -128,7 +128,7 @@ func runStats(cmd *cobra.Command, _ []string) error { if err != nil { return fmt.Errorf("failed to initialize config: %w", err) } - dataDir = svc.Config().Options.DataDirectory + dataDir = svc.DataDirectory() } conn, err := db.Connect(ctx, dataDir) diff --git a/internal/config/config.go b/internal/config/config.go index 7995139ca9e0002955818978d9beac7609b1e0bf..4a3080741ee4e9e39986c173b77bb7503a8df354 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -376,8 +376,6 @@ type Config struct { Permissions *Permissions `json:"permissions,omitempty" jsonschema:"description=Permission settings for tool usage"` Tools Tools `json:"tools,omitempty" jsonschema:"description=Tool configurations"` - - Agents map[string]Agent `json:"-"` } func (c *Config) EnabledProviders() []ProviderConfig { diff --git a/internal/config/load.go b/internal/config/load.go index 22442f1558ea1bd7cb4f8d2a61d81fc5f8fed375..4b5874037ee80f024e5501783503ef8162a8cac9 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -73,7 +73,7 @@ func Load(workingDir, dataDir string, debug bool) (*Service, error) { } // Load known providers, this loads the config from catwalk - providers, err := Providers(cfg) + providers, err := Providers(svc) if err != nil { return nil, err } diff --git a/internal/config/load_test.go b/internal/config/load_test.go index f1f5c4b366be4053dd3756b8e82e99a69d633e58..f2f2c6ea3b20e4bc6d4a4287b1e5bfb0efaa98cf 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -463,12 +463,13 @@ func TestConfig_setupAgentsWithNoDisabledTools(t *testing.T) { }, } - serviceFor(cfg).SetupAgents() - coderAgent, ok := cfg.Agents[AgentCoder] + svc := serviceFor(cfg) + svc.SetupAgents() + coderAgent, ok := svc.Agents()[AgentCoder] require.True(t, ok) assert.Equal(t, allToolNames(), coderAgent.AllowedTools) - taskAgent, ok := cfg.Agents[AgentTask] + taskAgent, ok := svc.Agents()[AgentTask] require.True(t, ok) assert.Equal(t, []string{"glob", "grep", "ls", "sourcegraph", "view"}, taskAgent.AllowedTools) } @@ -484,13 +485,14 @@ func TestConfig_setupAgentsWithDisabledTools(t *testing.T) { }, } - serviceFor(cfg).SetupAgents() - coderAgent, ok := cfg.Agents[AgentCoder] + svc := serviceFor(cfg) + svc.SetupAgents() + coderAgent, ok := svc.Agents()[AgentCoder] require.True(t, ok) assert.Equal(t, []string{"agent", "bash", "job_output", "job_kill", "multiedit", "lsp_diagnostics", "lsp_references", "lsp_restart", "fetch", "agentic_fetch", "glob", "ls", "sourcegraph", "todos", "view", "write"}, coderAgent.AllowedTools) - taskAgent, ok := cfg.Agents[AgentTask] + taskAgent, ok := svc.Agents()[AgentTask] require.True(t, ok) assert.Equal(t, []string{"glob", "ls", "sourcegraph", "view"}, taskAgent.AllowedTools) } @@ -508,12 +510,13 @@ func TestConfig_setupAgentsWithEveryReadOnlyToolDisabled(t *testing.T) { }, } - serviceFor(cfg).SetupAgents() - coderAgent, ok := cfg.Agents[AgentCoder] + svc := serviceFor(cfg) + svc.SetupAgents() + coderAgent, ok := svc.Agents()[AgentCoder] require.True(t, ok) assert.Equal(t, []string{"agent", "bash", "job_output", "job_kill", "download", "edit", "multiedit", "lsp_diagnostics", "lsp_references", "lsp_restart", "fetch", "agentic_fetch", "todos", "write"}, coderAgent.AllowedTools) - taskAgent, ok := cfg.Agents[AgentTask] + taskAgent, ok := svc.Agents()[AgentTask] require.True(t, ok) assert.Equal(t, []string{}, taskAgent.AllowedTools) } diff --git a/internal/config/provider.go b/internal/config/provider.go index 6ca981e5a73cbf3e3472b05f55c7b911a4a857c3..8e8c0c31b17f9ca9d1a238fffd7eb44a06b36bdf 100644 --- a/internal/config/provider.go +++ b/internal/config/provider.go @@ -139,12 +139,12 @@ var ( // 2. load the cached providers // 3. try to get the fresh list of providers, and return either this new list, // the cached list, or the embedded list if all others fail. -func Providers(cfg *Config) ([]catwalk.Provider, error) { +func Providers(svc *Service) ([]catwalk.Provider, error) { providerOnce.Do(func() { var wg sync.WaitGroup var errs []error providers := csync.NewSlice[catwalk.Provider]() - autoupdate := !cfg.Options.DisableProviderAutoUpdate + autoupdate := !svc.DisableProviderAutoUpdate() ctx, cancel := context.WithTimeout(context.Background(), 45*time.Second) defer cancel() diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index 283c18c8ab68c013dadf6f4fc8174f4947210f3a..a31808299507c0fcfa48c3a05a7914b383e36683 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -46,7 +46,7 @@ func TestProviders_Integration_AutoUpdateDisabled(t *testing.T) { }, } - providers, err := Providers(cfg) + providers, err := Providers(serviceFor(cfg)) require.NoError(t, err) require.NotNil(t, providers) require.Greater(t, len(providers), 5, "Expected embedded providers") diff --git a/internal/config/service.go b/internal/config/service.go index 1f3107243648cb2967eee40b4a954ac8add2313c..0197ed4925087eb6028b3bce5d49e22ff87f0caf 100644 --- a/internal/config/service.go +++ b/internal/config/service.go @@ -28,13 +28,6 @@ type Service struct { agents map[string]Agent } -// Config returns the underlying Config struct. This is a temporary -// escape hatch that will be removed once all callers migrate to -// Service getter methods. -func (s *Service) Config() *Config { - return s.cfg -} - // WorkingDir returns the working directory. func (s *Service) WorkingDir() string { return s.workingDir @@ -116,7 +109,6 @@ func (s *Service) SetupAgents() { AllowedMCP: map[string][]string{}, }, } - s.cfg.Agents = s.agents } // Agents returns the agent configuration map. @@ -184,6 +176,23 @@ func (s *Service) SelectedModel(modelType SelectedModelType) (SelectedModel, boo return m, ok } +// Provider returns the provider config for the given ID and whether +// it exists. +func (s *Service) Provider(id string) (ProviderConfig, bool) { + p, ok := s.cfg.Providers[id] + return p, ok +} + +// SetProvider sets the provider config for the given ID. +func (s *Service) SetProvider(id string, p ProviderConfig) { + s.cfg.Providers[id] = p +} + +// Providers returns all provider configs. +func (s *Service) AllProviders() map[string]ProviderConfig { + return s.cfg.Providers +} + // MCP returns the MCP configurations. func (s *Service) MCP() MCPs { return s.cfg.MCP @@ -199,6 +208,21 @@ func (s *Service) Permissions() *Permissions { return s.cfg.Permissions } +// SetAttribution sets the attribution settings. +func (s *Service) SetAttribution(a *Attribution) { + s.cfg.Options.Attribution = a +} + +// SetSkillsPaths sets the skills paths. +func (s *Service) SetSkillsPaths(paths []string) { + s.cfg.Options.SkillsPaths = paths +} + +// SetLSP sets the LSP configurations. +func (s *Service) SetLSP(lsp LSPs) { + s.cfg.LSP = lsp +} + // SetPermissions sets the permissions configuration. func (s *Service) SetPermissions(p *Permissions) { s.cfg.Permissions = p diff --git a/internal/ui/chat/messages.go b/internal/ui/chat/messages.go index 1aaccdc911fadfc41208e69c6903e8b4e9e90212..dcb5cf0a3139c98d8e563428263e6ef988f45516 100644 --- a/internal/ui/chat/messages.go +++ b/internal/ui/chat/messages.go @@ -186,12 +186,12 @@ type AssistantInfoItem struct { id string message *message.Message sty *styles.Styles - cfg *config.Config + cfg *config.Service lastUserMessageTime time.Time } // NewAssistantInfoItem creates a new AssistantInfoItem. -func NewAssistantInfoItem(sty *styles.Styles, message *message.Message, cfg *config.Config, lastUserMessageTime time.Time) MessageItem { +func NewAssistantInfoItem(sty *styles.Styles, message *message.Message, cfg *config.Service, lastUserMessageTime time.Time) MessageItem { return &AssistantInfoItem{ cachedMessageItem: &cachedMessageItem{}, id: AssistantInfoID(message.ID), @@ -239,7 +239,7 @@ func (a *AssistantInfoItem) renderContent(width int) string { } modelFormatted := a.sty.Chat.Message.AssistantInfoModel.Render(model.Name) providerName := a.message.Provider - if providerConfig, ok := a.cfg.Providers[a.message.Provider]; ok { + if providerConfig, ok := a.cfg.Provider(a.message.Provider); ok { providerName = providerConfig.Name } provider := a.sty.Chat.Message.AssistantInfoProvider.Render(fmt.Sprintf("via %s", providerName)) diff --git a/internal/ui/dialog/models.go b/internal/ui/dialog/models.go index f96e547ccdddb6441762f968db79b62fd27d6ff7..3347348f2b304826d02b2e199f946e407d23e16e 100644 --- a/internal/ui/dialog/models.go +++ b/internal/ui/dialog/models.go @@ -336,7 +336,6 @@ func (m *Models) FullHelp() [][]key.Binding { func (m *Models) setProviderItems() error { t := m.com.Styles svc := m.com.ConfigService() - cfg := svc.Config() var selectedItemID string selectedType := m.modelType.Config() @@ -347,7 +346,7 @@ func (m *Models) setProviderItems() error { addedProviders := make(map[string]bool) // Get a list of known providers to compare against - knownProviders, err := config.Providers(cfg) + knownProviders, err := config.Providers(svc) if err != nil { return fmt.Errorf("failed to get providers: %w", err) } @@ -361,7 +360,7 @@ func (m *Models) setProviderItems() error { // itemsMap contains the keys of added model items. itemsMap := make(map[string]*ModelItem) groups := []ModelGroup{} - for id, p := range cfg.Providers { + for id, p := range svc.AllProviders() { if p.Disable { continue } @@ -411,7 +410,7 @@ func (m *Models) setProviderItems() error { continue } - providerConfig, providerConfigured := cfg.Providers[providerID] + providerConfig, providerConfigured := svc.Provider(providerID) if providerConfigured && providerConfig.Disable { continue } @@ -507,8 +506,7 @@ func (m *Models) setProviderItems() error { } func getFilteredProviders(svc *config.Service) ([]catwalk.Provider, error) { - cfg := svc.Config() - providers, err := config.Providers(cfg) + providers, err := config.Providers(svc) if err != nil { return nil, fmt.Errorf("failed to get providers: %w", err) } @@ -519,7 +517,7 @@ func getFilteredProviders(svc *config.Service) ([]catwalk.Provider, error) { isCopilot = p.ID == catwalk.InferenceProviderCopilot isHyper = string(p.ID) == "hyper" hasAPIKeyEnv = strings.HasPrefix(p.APIKey, "$") - _, isConfigured = cfg.Providers[string(p.ID)] + _, isConfigured = svc.Provider(string(p.ID)) ) if isAzure || isCopilot || isHyper || hasAPIKeyEnv || isConfigured { filteredProviders = append(filteredProviders, p) diff --git a/internal/ui/model/onboarding.go b/internal/ui/model/onboarding.go index c5dc305cac91ceb3dc98dcc01d95f81773437ca8..0aa6925ef43d55e209bb7e31f9861f1a0fc66f0f 100644 --- a/internal/ui/model/onboarding.go +++ b/internal/ui/model/onboarding.go @@ -77,10 +77,10 @@ func (m *UI) skipInitializeProject() tea.Cmd { // initializeView renders the project initialization prompt with Yes/No buttons. func (m *UI) initializeView() string { - cfg := m.com.ConfigService().Config() + cfg := m.com.ConfigService() s := m.com.Styles.Initialize cwd := home.Short(m.com.ConfigService().WorkingDir()) - initFile := cfg.Options.InitializeAs + initFile := cfg.InitializeAs() header := s.Header.Render("Would you like to initialize this project?") path := s.Accent.PaddingLeft(2).Render(cwd) diff --git a/internal/ui/model/sidebar.go b/internal/ui/model/sidebar.go index 7c37584afb2f054f1410659b2b103056e1f42c7b..e24fa98035ee94badbdedb72914d0817f8347639 100644 --- a/internal/ui/model/sidebar.go +++ b/internal/ui/model/sidebar.go @@ -21,7 +21,7 @@ func (m *UI) modelInfo(width int) string { if model != nil { // Get provider name first - providerConfig, ok := m.com.ConfigService().Config().Providers[model.ModelCfg.Provider] + providerConfig, ok := m.com.ConfigService().Provider(model.ModelCfg.Provider) if ok { providerName = providerConfig.Name diff --git a/internal/ui/model/ui.go b/internal/ui/model/ui.go index fd32910cfed0ab95d0864d78cef417acd1830ff0..999f5d5099f63add42899f369bdf7103f04f25aa 100644 --- a/internal/ui/model/ui.go +++ b/internal/ui/model/ui.go @@ -776,7 +776,7 @@ func (m *UI) setSessionMessages(msgs []message.Message) tea.Cmd { case message.Assistant: items = append(items, chat.ExtractMessageItems(m.com.Styles, msg, toolResultMap)...) if msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonEndTurn { - infoItem := chat.NewAssistantInfoItem(m.com.Styles, msg, m.com.ConfigService().Config(), time.Unix(m.lastUserMessageTime, 0)) + infoItem := chat.NewAssistantInfoItem(m.com.Styles, msg, m.com.ConfigService(), time.Unix(m.lastUserMessageTime, 0)) items = append(items, infoItem) } default: @@ -906,7 +906,7 @@ func (m *UI) appendSessionMessage(msg message.Message) tea.Cmd { } } if msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonEndTurn { - infoItem := chat.NewAssistantInfoItem(m.com.Styles, &msg, m.com.ConfigService().Config(), time.Unix(m.lastUserMessageTime, 0)) + infoItem := chat.NewAssistantInfoItem(m.com.Styles, &msg, m.com.ConfigService(), time.Unix(m.lastUserMessageTime, 0)) m.chat.AppendMessages(infoItem) if atBottom { if cmd := m.chat.ScrollToBottomAndAnimate(); cmd != nil { @@ -977,7 +977,7 @@ func (m *UI) updateSessionMessage(msg message.Message) tea.Cmd { if shouldRenderAssistant && msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonEndTurn { if infoItem := m.chat.MessageItem(chat.AssistantInfoID(msg.ID)); infoItem == nil { - newInfoItem := chat.NewAssistantInfoItem(m.com.Styles, &msg, m.com.ConfigService().Config(), time.Unix(m.lastUserMessageTime, 0)) + newInfoItem := chat.NewAssistantInfoItem(m.com.Styles, &msg, m.com.ConfigService(), time.Unix(m.lastUserMessageTime, 0)) m.chat.AppendMessages(newInfoItem) } } @@ -1196,17 +1196,17 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd { m.dialog.CloseDialog(dialog.CommandsID) case dialog.ActionToggleThinking: cmds = append(cmds, func() tea.Msg { - cfg := m.com.ConfigService().Config() + cfg := m.com.ConfigService() if cfg == nil { return util.ReportError(errors.New("configuration not found"))() } - agentCfg, ok := cfg.Agents[config.AgentCoder] + agentCfg, ok := m.com.ConfigService().Agent(config.AgentCoder) if !ok { return util.ReportError(errors.New("agent configuration not found"))() } - currentModel := cfg.Models[agentCfg.Model] + currentModel, _ := cfg.SelectedModel(agentCfg.Model) currentModel.Think = !currentModel.Think if err := m.com.ConfigService().UpdatePreferredModel(agentCfg.Model, currentModel); err != nil { return util.ReportError(err)() @@ -1235,7 +1235,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd { break } - cfg := m.com.ConfigService().Config() + cfg := m.com.ConfigService() if cfg == nil { cmds = append(cmds, util.ReportError(errors.New("configuration not found"))) break @@ -1244,7 +1244,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd { var ( providerID = msg.Model.Provider isCopilot = providerID == string(catwalk.InferenceProviderCopilot) - isConfigured = func() bool { _, ok := cfg.Providers[providerID]; return ok } + isConfigured = func() bool { _, ok := cfg.Provider(providerID); return ok } ) // Attempt to import GitHub Copilot tokens from VSCode if available. @@ -1262,7 +1262,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd { if err := m.com.ConfigService().UpdatePreferredModel(msg.ModelType, msg.Model); err != nil { cmds = append(cmds, util.ReportError(err)) - } else if _, ok := cfg.Models[config.SelectedModelTypeSmall]; !ok { + } else if _, ok := cfg.SelectedModel(config.SelectedModelTypeSmall); !ok { // Ensure small model is set is unset. smallModel := m.com.App.GetDefaultSmallModel(providerID) if err := m.com.ConfigService().UpdatePreferredModel(config.SelectedModelTypeSmall, smallModel); err != nil { @@ -1297,19 +1297,19 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd { break } - cfg := m.com.ConfigService().Config() + cfg := m.com.ConfigService() if cfg == nil { cmds = append(cmds, util.ReportError(errors.New("configuration not found"))) break } - agentCfg, ok := cfg.Agents[config.AgentCoder] + agentCfg, ok := m.com.ConfigService().Agent(config.AgentCoder) if !ok { cmds = append(cmds, util.ReportError(errors.New("agent configuration not found"))) break } - currentModel := cfg.Models[agentCfg.Model] + currentModel, _ := cfg.SelectedModel(agentCfg.Model) currentModel.ReasoningEffort = msg.Effort if err := m.com.ConfigService().UpdatePreferredModel(agentCfg.Model, currentModel); err != nil { cmds = append(cmds, util.ReportError(err))