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))