on main help sign in
chore: change to the small/large model conceptg
commit5721e7b493696ed85723f5dabbc14dc01b29badf authorKujtim Hoxha <kujtimii.h@gmail.com> dateFri 27 Jun 2025, 14:05 (1 year ago) parent565ab85
11 files changed+190−142
internal/app/app.go+3−3 internal/config/config.go+95−16 internal/llm/agent/agent.go+25−84 internal/llm/provider/anthropic.go+6−1 internal/llm/provider/bedrock.go+17−5 internal/llm/provider/gemini.go+8−2 internal/llm/provider/openai.go+7−3 internal/llm/provider/provider.go+16−10 internal/tui/components/dialogs/init/init.go+3−3 internal/tui/components/dialogs/models/models.go+7−7 internal/tui/tui.go+3−8
internal/app/app.go+3−3
@@ -9,7 +9,7 @@ import (
99 "sync"
1010 "time"
1111
12− configv2 "github.com/charmbracelet/crush/internal/config"
12+ "github.com/charmbracelet/crush/internal/config"
1313 "github.com/charmbracelet/crush/internal/db"
1414 "github.com/charmbracelet/crush/internal/format"
1515 "github.com/charmbracelet/crush/internal/history"
@@ -55,9 +55,9 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
5555 // Initialize LSP clients in the background
5656 go app.initLSPClients(ctx)
5757
58− cfg := configv2.Get()
58+ cfg := config.Get()
5959
60− coderAgentCfg := cfg.Agents[configv2.AgentCoder]
60+ coderAgentCfg := cfg.Agents[config.AgentCoder]
6161 if coderAgentCfg.ID == "" {
6262 return nil, fmt.Errorf("coder agent configuration is missing")
6363 }
internal/config/config.go+95−16
@@ -47,6 +47,13 @@ const (
4747 AgentTask AgentID = "task"
4848)
4949
50+type ModelType string
51+
52+const (
53+ LargeModel ModelType = "large"
54+ SmallModel ModelType = "small"
55+)
56+
5057type Model struct {
5158 ID string `json:"id"`
5259 Name string `json:"model"`
@@ -90,8 +97,7 @@ type Agent struct {
9097 // This is the id of the system prompt used by the agent
9198 Disabled bool `json:"disabled"`
9299
93− Provider provider.InferenceProvider `json:"provider"`
94− Model string `json:"model"`
100+ Model ModelType `json:"model"`
95101
96102 // The available tools for the agent
97103 // if this is nil, all tools are available
@@ -291,8 +297,7 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
291297 ID: AgentCoder,
292298 Name: "Coder",
293299 Description: "An agent that helps with executing coding tasks.",
294− Provider: cfg.Models.Large.Provider,
295− Model: cfg.Models.Large.ModelID,
300+ Model: LargeModel,
296301 ContextPaths: cfg.Options.ContextPaths,
297302 // All tools allowed
298303 },
@@ -300,8 +305,7 @@ func loadConfig(cwd string, debug bool) (*Config, error) {
300305 ID: AgentTask,
301306 Name: "Task",
302307 Description: "An agent that helps with searching for context and finding implementation details.",
303− Provider: cfg.Models.Large.Provider,
304− Model: cfg.Models.Large.ModelID,
308+ Model: LargeModel,
305309 ContextPaths: cfg.Options.ContextPaths,
306310 AllowedTools: []string{
307311 "glob",
@@ -490,9 +494,8 @@ func mergeAgents(base, global, local *Config) {
490494 switch agentID {
491495 case AgentCoder:
492496 baseAgent := base.Agents[agentID]
493− if newAgent.Model != "" && newAgent.Provider != "" {
497+ if newAgent.Model != "" {
494498 baseAgent.Model = newAgent.Model
495− baseAgent.Provider = newAgent.Provider
496499 }
497500 baseAgent.AllowedMCP = newAgent.AllowedMCP
498501 baseAgent.AllowedLSP = newAgent.AllowedLSP
@@ -502,9 +505,8 @@ func mergeAgents(base, global, local *Config) {
502505 baseAgent.Name = newAgent.Name
503506 baseAgent.Description = newAgent.Description
504507 baseAgent.Disabled = newAgent.Disabled
505− if newAgent.Model == "" || newAgent.Provider == "" {
506− baseAgent.Provider = base.Models.Large.Provider
507− baseAgent.Model = base.Models.Large.ModelID
508+ if newAgent.Model == "" {
509+ baseAgent.Model = LargeModel
508510 }
509511 baseAgent.AllowedTools = newAgent.AllowedTools
510512 baseAgent.AllowedMCP = newAgent.AllowedMCP
@@ -709,6 +711,8 @@ func WorkingDirectory() string {
709711 return cwd
710712}
711713
714+// TODO: Handle error state
715+
712716func GetAgentModel(agentID AgentID) Model {
713717 cfg := Get()
714718 agent, ok := cfg.Agents[agentID]
@@ -717,15 +721,25 @@ func GetAgentModel(agentID AgentID) Model {
717721 return Model{}
718722 }
719723
720− providerConfig, ok := cfg.Providers[agent.Provider]
724+ var model PreferredModel
725+ switch agent.Model {
726+ case LargeModel:
727+ model = cfg.Models.Large
728+ case SmallModel:
729+ model = cfg.Models.Small
730+ default:
731+ logging.Warn("Unknown model type for agent", "agent_id", agentID, "model_type", agent.Model)
732+ model = cfg.Models.Large // Fallback to large model
733+ }
734+ providerConfig, ok := cfg.Providers[model.Provider]
721735 if !ok {
722− logging.Error("Provider not found for agent", "agent_id", agentID, "provider", agent.Provider)
736+ logging.Error("Provider not found for agent", "agent_id", agentID, "provider", model.Provider)
723737 return Model{}
724738 }
725739
726− for _, model := range providerConfig.Models {
727− if model.ID == agent.Model {
728− return model
740+ for _, m := range providerConfig.Models {
741+ if m.ID == model.ModelID {
742+ return m
729743 }
730744 }
731745
@@ -733,6 +747,34 @@ func GetAgentModel(agentID AgentID) Model {
733747 return Model{}
734748}
735749
750+func GetAgentProvider(agentID AgentID) ProviderConfig {
751+ cfg := Get()
752+ agent, ok := cfg.Agents[agentID]
753+ if !ok {
754+ logging.Error("Agent not found", "agent_id", agentID)
755+ return ProviderConfig{}
756+ }
757+
758+ var model PreferredModel
759+ switch agent.Model {
760+ case LargeModel:
761+ model = cfg.Models.Large
762+ case SmallModel:
763+ model = cfg.Models.Small
764+ default:
765+ logging.Warn("Unknown model type for agent", "agent_id", agentID, "model_type", agent.Model)
766+ model = cfg.Models.Large // Fallback to large model
767+ }
768+
769+ providerConfig, ok := cfg.Providers[model.Provider]
770+ if !ok {
771+ logging.Error("Provider not found for agent", "agent_id", agentID, "provider", model.Provider)
772+ return ProviderConfig{}
773+ }
774+
775+ return providerConfig
776+}
777+
736778func GetProviderModel(provider provider.InferenceProvider, modelID string) Model {
737779 cfg := Get()
738780 providerConfig, ok := cfg.Providers[provider]
@@ -750,3 +792,40 @@ func GetProviderModel(provider provider.InferenceProvider, modelID string) Model
750792 logging.Error("Model not found for provider", "provider", provider, "model_id", modelID)
751793 return Model{}
752794}
795+
796+func GetModel(modelType ModelType) Model {
797+ cfg := Get()
798+ var model PreferredModel
799+ switch modelType {
800+ case LargeModel:
801+ model = cfg.Models.Large
802+ case SmallModel:
803+ model = cfg.Models.Small
804+ default:
805+ model = cfg.Models.Large // Fallback to large model
806+ }
807+ providerConfig, ok := cfg.Providers[model.Provider]
808+ if !ok {
809+ return Model{}
810+ }
811+
812+ for _, m := range providerConfig.Models {
813+ if m.ID == model.ModelID {
814+ return m
815+ }
816+ }
817+ return Model{}
818+}
819+
820+func UpdatePreferredModel(modelType ModelType, model PreferredModel) error {
821+ cfg := Get()
822+ switch modelType {
823+ case LargeModel:
824+ cfg.Models.Large = model
825+ case SmallModel:
826+ cfg.Models.Small = model
827+ default:
828+ return fmt.Errorf("unknown model type: %s", modelType)
829+ }
830+ return nil
831+}
internal/llm/agent/agent.go+25−84
@@ -9,7 +9,7 @@ import (
99 "sync"
1010 "time"
1111
12− configv2 "github.com/charmbracelet/crush/internal/config"
12+ "github.com/charmbracelet/crush/internal/config"
1313 "github.com/charmbracelet/crush/internal/history"
1414 "github.com/charmbracelet/crush/internal/llm/prompt"
1515 "github.com/charmbracelet/crush/internal/llm/provider"
@@ -49,19 +49,18 @@ type AgentEvent struct {
4949
5050type Service interface {
5151 pubsub.Suscriber[AgentEvent]
52− Model() configv2.Model
52+ Model() config.Model
5353 Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error)
5454 Cancel(sessionID string)
5555 CancelAll()
5656 IsSessionBusy(sessionID string) bool
5757 IsBusy() bool
58− Update(model configv2.PreferredModel) (configv2.Model, error)
5958 Summarize(ctx context.Context, sessionID string) error
6059}
6160
6261type agent struct {
6362 *pubsub.Broker[AgentEvent]
64− agentCfg configv2.Agent
63+ agentCfg config.Agent
6564 sessions session.Service
6665 messages message.Service
6766
@@ -76,13 +75,13 @@ type agent struct {
7675 activeRequests sync.Map
7776}
7877
79−var agentPromptMap = map[configv2.AgentID]prompt.PromptID{
80− configv2.AgentCoder: prompt.PromptCoder,
81− configv2.AgentTask: prompt.PromptTask,
78+var agentPromptMap = map[config.AgentID]prompt.PromptID{
79+ config.AgentCoder: prompt.PromptCoder,
80+ config.AgentTask: prompt.PromptTask,
8281}
8382
8483func NewAgent(
85− agentCfg configv2.Agent,
84+ agentCfg config.Agent,
8685 // These services are needed in the tools
8786 permissions permission.Service,
8887 sessions session.Service,
@@ -91,7 +90,7 @@ func NewAgent(
9190 lspClients map[string]*lsp.Client,
9291) (Service, error) {
9392 ctx := context.Background()
94− cfg := configv2.Get()
93+ cfg := config.Get()
9594 otherTools := GetMcpTools(ctx, permissions)
9695 if len(lspClients) > 0 {
9796 otherTools = append(otherTools, tools.NewDiagnosticsTool(lspClients))
@@ -109,8 +108,8 @@ func NewAgent(
109108 tools.NewWriteTool(lspClients, permissions, history),
110109 }
111110
112− if agentCfg.ID == configv2.AgentCoder {
113− taskAgentCfg := configv2.Get().Agents[configv2.AgentTask]
111+ if agentCfg.ID == config.AgentCoder {
112+ taskAgentCfg := config.Get().Agents[config.AgentTask]
114113 if taskAgentCfg.ID == "" {
115114 return nil, fmt.Errorf("task agent not found in config")
116115 }
@@ -130,26 +129,14 @@ func NewAgent(
130129 }
131130
132131 allTools = append(allTools, otherTools...)
133− var providerCfg configv2.ProviderConfig
134− for _, p := range cfg.Providers {
135− if p.ID == agentCfg.Provider {
136− providerCfg = p
137− break
138− }
139− }
132+ providerCfg := config.GetAgentProvider(agentCfg.ID)
140133 if providerCfg.ID == "" {
141− return nil, fmt.Errorf("provider %s not found in config", agentCfg.Provider)
134+ return nil, fmt.Errorf("provider for agent %s not found in config", agentCfg.Name)
142135 }
136+ model := config.GetAgentModel(agentCfg.ID)
143137
144− var model configv2.Model
145− for _, m := range providerCfg.Models {
146− if m.ID == agentCfg.Model {
147− model = m
148− break
149− }
150− }
151138 if model.ID == "" {
152− return nil, fmt.Errorf("model %s not found in provider %s", agentCfg.Model, agentCfg.Provider)
139+ return nil, fmt.Errorf("model not found for agent %s", agentCfg.Name)
153140 }
154141
155142 promptID := agentPromptMap[agentCfg.ID]
@@ -157,7 +144,7 @@ func NewAgent(
157144 promptID = prompt.PromptDefault
158145 }
159146 opts := []provider.ProviderClientOption{
160− provider.WithModel(model),
147+ provider.WithModel(agentCfg.Model),
161148 provider.WithSystemMessage(prompt.GetPrompt(promptID, providerCfg.ID)),
162149 provider.WithMaxTokens(model.DefaultMaxTokens),
163150 }
@@ -167,9 +154,9 @@ func NewAgent(
167154 }
168155
169156 smallModelCfg := cfg.Models.Small
170− var smallModel configv2.Model
157+ var smallModel config.Model
171158
172− var smallModelProviderCfg configv2.ProviderConfig
159+ var smallModelProviderCfg config.ProviderConfig
173160 if smallModelCfg.Provider == providerCfg.ID {
174161 smallModelProviderCfg = providerCfg
175162 } else {
@@ -194,7 +181,7 @@ func NewAgent(
194181 }
195182
196183 titleOpts := []provider.ProviderClientOption{
197− provider.WithModel(smallModel),
184+ provider.WithModel(config.SmallModel),
198185 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptTitle, smallModelProviderCfg.ID)),
199186 provider.WithMaxTokens(40),
200187 }
@@ -203,7 +190,7 @@ func NewAgent(
203190 return nil, err
204191 }
205192 summarizeOpts := []provider.ProviderClientOption{
206− provider.WithModel(smallModel),
193+ provider.WithModel(config.SmallModel),
207194 provider.WithSystemMessage(prompt.GetPrompt(prompt.PromptSummarizer, smallModelProviderCfg.ID)),
208195 provider.WithMaxTokens(smallModel.DefaultMaxTokens),
209196 }
@@ -240,8 +227,8 @@ func NewAgent(
240227 return agent, nil
241228}
242229
243−func (a *agent) Model() configv2.Model {
244− return a.provider.Model()
230+func (a *agent) Model() config.Model {
231+ return config.GetAgentModel(a.agentCfg.ID)
245232}
246233
247234func (a *agent) Cancel(sessionID string) {
@@ -336,7 +323,7 @@ func (a *agent) err(err error) AgentEvent {
336323}
337324
338325func (a *agent) Run(ctx context.Context, sessionID string, content string, attachments ...message.Attachment) (<-chan AgentEvent, error) {
339− if !a.provider.Model().SupportsImages && attachments != nil {
326+ if !a.Model().SupportsImages && attachments != nil {
340327 attachments = nil
341328 }
342329 events := make(chan AgentEvent)
@@ -458,7 +445,7 @@ func (a *agent) streamAndHandleEvents(ctx context.Context, sessionID string, msg
458445 assistantMsg, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
459446 Role: message.Assistant,
460447 Parts: []message.ContentPart{},
461− Model: a.provider.Model().ID,
448+ Model: a.Model().ID,
462449 Provider: a.providerID,
463450 })
464451 if err != nil {
@@ -609,13 +596,13 @@ func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg
609596 if err := a.messages.Update(ctx, *assistantMsg); err != nil {
610597 return fmt.Errorf("failed to update message: %w", err)
611598 }
612− return a.TrackUsage(ctx, sessionID, a.provider.Model(), event.Response.Usage)
599+ return a.TrackUsage(ctx, sessionID, a.Model(), event.Response.Usage)
613600 }
614601
615602 return nil
616603}
617604
618−func (a *agent) TrackUsage(ctx context.Context, sessionID string, model configv2.Model, usage provider.TokenUsage) error {
605+func (a *agent) TrackUsage(ctx context.Context, sessionID string, model config.Model, usage provider.TokenUsage) error {
619606 sess, err := a.sessions.Get(ctx, sessionID)
620607 if err != nil {
621608 return fmt.Errorf("failed to get session: %w", err)
@@ -637,52 +624,6 @@ func (a *agent) TrackUsage(ctx context.Context, sessionID string, model configv2
637624 return nil
638625}
639626
640−func (a *agent) Update(modelCfg configv2.PreferredModel) (configv2.Model, error) {
641− if a.IsBusy() {
642− return configv2.Model{}, fmt.Errorf("cannot change model while processing requests")
643− }
644−
645− cfg := configv2.Get()
646− var providerCfg configv2.ProviderConfig
647− for _, p := range cfg.Providers {
648− if p.ID == modelCfg.Provider {
649− providerCfg = p
650− break
651− }
652− }
653− if providerCfg.ID == "" {
654− return configv2.Model{}, fmt.Errorf("provider %s not found in config", modelCfg.Provider)
655− }
656−
657− var model configv2.Model
658− for _, m := range providerCfg.Models {
659− if m.ID == modelCfg.ModelID {
660− model = m
661− break
662− }
663− }
664− if model.ID == "" {
665− return configv2.Model{}, fmt.Errorf("model %s not found in provider %s", modelCfg.ModelID, modelCfg.Provider)
666− }
667−
668− promptID := agentPromptMap[a.agentCfg.ID]
669− if promptID == "" {
670− promptID = prompt.PromptDefault
671− }
672− opts := []provider.ProviderClientOption{
673− provider.WithModel(model),
674− provider.WithSystemMessage(prompt.GetPrompt(promptID, providerCfg.ID)),
675− provider.WithMaxTokens(model.DefaultMaxTokens),
676− }
677− agentProvider, err := provider.NewProviderV2(providerCfg, opts...)
678− if err != nil {
679− return configv2.Model{}, err
680− }
681− a.provider = agentProvider
682−
683− return a.provider.Model(), nil
684−}
685−
686627func (a *agent) Summarize(ctx context.Context, sessionID string) error {
687628 if a.summarizeProvider == nil {
688629 return fmt.Errorf("summarize provider not available")
internal/llm/provider/anthropic.go+6−1
@@ -145,6 +145,7 @@ func (a *anthropicClient) finishReason(reason string) message.FinishReason {
145145}
146146
147147func (a *anthropicClient) preparedMessages(messages []anthropic.MessageParam, tools []anthropic.ToolUnionParam) anthropic.MessageNewParams {
148+ model := a.providerOptions.model(a.providerOptions.modelType)
148149 var thinkingParam anthropic.ThinkingConfigParamUnion
149150 // TODO: Implement a proper thinking function
150151 // lastMessage := messages[len(messages)-1]
@@ -164,7 +165,7 @@ func (a *anthropicClient) preparedMessages(messages []anthropic.MessageParam, to
164165 // }
165166
166167 return anthropic.MessageNewParams{
167− Model: anthropic.Model(a.providerOptions.model.ID),
168+ Model: anthropic.Model(model.ID),
168169 MaxTokens: a.providerOptions.maxTokens,
169170 Temperature: temperature,
170171 Messages: messages,
@@ -425,6 +426,10 @@ func (a *anthropicClient) usage(msg anthropic.Message) TokenUsage {
425426 }
426427}
427428
429+func (a *anthropicClient) Model() config.Model {
430+ return a.providerOptions.model(a.providerOptions.modelType)
431+}
432+
428433// TODO: check if we need
429434func DefaultShouldThinkFn(s string) bool {
430435 return strings.Contains(strings.ToLower(s), "think")
internal/llm/provider/bedrock.go+17−5
@@ -6,6 +6,7 @@ import (
66 "fmt"
77 "strings"
88
9+ "github.com/charmbracelet/crush/internal/config"
910 "github.com/charmbracelet/crush/internal/llm/tools"
1011 "github.com/charmbracelet/crush/internal/message"
1112)
@@ -30,13 +31,20 @@ func newBedrockClient(opts providerClientOptions) BedrockClient {
3031 }
3132 }
3233
33− // Prefix the model name with region
34− regionPrefix := region[:2]
35− modelName := opts.model.ID
36− opts.model.ID = fmt.Sprintf("%s.%s", regionPrefix, modelName)
34+ opts.model = func(modelType config.ModelType) config.Model {
35+ model := config.GetModel(modelType)
36+
37+ // Prefix the model name with region
38+ regionPrefix := region[:2]
39+ modelName := model.ID
40+ model.ID = fmt.Sprintf("%s.%s", regionPrefix, modelName)
41+ return model
42+ }
43+
44+ model := opts.model(opts.modelType)
3745
3846 // Determine which provider to use based on the model
39− if strings.Contains(string(opts.model.ID), "anthropic") {
47+ if strings.Contains(string(model.ID), "anthropic") {
4048 // Create Anthropic client with Bedrock configuration
4149 anthropicOpts := opts
4250 // TODO: later find a way to check if the AWS account has caching enabled
@@ -78,3 +86,7 @@ func (b *bedrockClient) stream(ctx context.Context, messages []message.Message,
7886
7987 return b.childProvider.stream(ctx, messages, tools)
8088}
89+
90+func (b *bedrockClient) Model() config.Model {
91+ return b.providerOptions.model(b.providerOptions.modelType)
92+}
internal/llm/provider/gemini.go+8−2
@@ -173,7 +173,8 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
173173 if len(tools) > 0 {
174174 config.Tools = g.convertTools(tools)
175175 }
176− chat, _ := g.client.Chats.Create(ctx, g.providerOptions.model.ID, config, history)
176+ model := g.providerOptions.model(g.providerOptions.modelType)
177+ chat, _ := g.client.Chats.Create(ctx, model.ID, config, history)
177178
178179 attempts := 0
179180 for {
@@ -261,7 +262,8 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
261262 if len(tools) > 0 {
262263 config.Tools = g.convertTools(tools)
263264 }
264− chat, _ := g.client.Chats.Create(ctx, g.providerOptions.model.ID, config, history)
265+ model := g.providerOptions.model(g.providerOptions.modelType)
266+ chat, _ := g.client.Chats.Create(ctx, model.ID, config, history)
265267
266268 attempts := 0
267269 eventChan := make(chan ProviderEvent)
@@ -439,6 +441,10 @@ func (g *geminiClient) usage(resp *genai.GenerateContentResponse) TokenUsage {
439441 }
440442}
441443
444+func (g *geminiClient) Model() config.Model {
445+ return g.providerOptions.model(g.providerOptions.modelType)
446+}
447+
442448// Helper functions
443449func parseJsonToMap(jsonStr string) (map[string]any, error) {
444450 var result map[string]any
internal/llm/provider/openai.go+7−3
@@ -152,13 +152,13 @@ func (o *openaiClient) finishReason(reason string) message.FinishReason {
152152}
153153
154154func (o *openaiClient) preparedParams(messages []openai.ChatCompletionMessageParamUnion, tools []openai.ChatCompletionToolParam) openai.ChatCompletionNewParams {
155+ model := o.providerOptions.model(o.providerOptions.modelType)
155156 params := openai.ChatCompletionNewParams{
156− Model: openai.ChatModel(o.providerOptions.model.ID),
157+ Model: openai.ChatModel(model.ID),
157158 Messages: messages,
158159 Tools: tools,
159160 }
160−
161− if o.providerOptions.model.CanReason {
161+ if model.CanReason {
162162 params.MaxCompletionTokens = openai.Int(o.providerOptions.maxTokens)
163163 switch o.options.reasoningEffort {
164164 case "low":
@@ -384,3 +384,7 @@ func (o *openaiClient) usage(completion openai.ChatCompletion) TokenUsage {
384384 CacheReadTokens: cachedTokens,
385385 }
386386}
387+
388+func (a *openaiClient) Model() config.Model {
389+ return a.providerOptions.model(a.providerOptions.modelType)
390+}
internal/llm/provider/provider.go+16−10
@@ -4,7 +4,7 @@ import (
44 "context"
55 "fmt"
66
7− configv2 "github.com/charmbracelet/crush/internal/config"
7+ "github.com/charmbracelet/crush/internal/config"
88 "github.com/charmbracelet/crush/internal/fur/provider"
99 "github.com/charmbracelet/crush/internal/llm/tools"
1010 "github.com/charmbracelet/crush/internal/message"
@@ -55,13 +55,14 @@ type Provider interface {
5555
5656 StreamResponse(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent
5757
58− Model() configv2.Model
58+ Model() config.Model
5959}
6060
6161type providerClientOptions struct {
6262 baseURL string
6363 apiKey string
64− model configv2.Model
64+ modelType config.ModelType
65+ model func(config.ModelType) config.Model
6566 disableCache bool
6667 maxTokens int64
6768 systemMessage string
@@ -74,6 +75,8 @@ type ProviderClientOption func(*providerClientOptions)
7475type ProviderClient interface {
7576 send(ctx context.Context, messages []message.Message, tools []tools.BaseTool) (*ProviderResponse, error)
7677 stream(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent
78+
79+ Model() config.Model
7780}
7881
7982type baseProvider[C ProviderClient] struct {
@@ -97,18 +100,18 @@ func (p *baseProvider[C]) SendMessages(ctx context.Context, messages []message.M
97100 return p.client.send(ctx, messages, tools)
98101}
99102
100−func (p *baseProvider[C]) Model() configv2.Model {
101− return p.options.model
102−}
103−
104103func (p *baseProvider[C]) StreamResponse(ctx context.Context, messages []message.Message, tools []tools.BaseTool) <-chan ProviderEvent {
105104 messages = p.cleanMessages(messages)
106105 return p.client.stream(ctx, messages, tools)
107106}
108107
109−func WithModel(model configv2.Model) ProviderClientOption {
108+func (p *baseProvider[C]) Model() config.Model {
109+ return p.client.Model()
110+}
111+
112+func WithModel(model config.ModelType) ProviderClientOption {
110113 return func(options *providerClientOptions) {
111− options.model = model
114+ options.modelType = model
112115 }
113116}
114117
@@ -130,11 +133,14 @@ func WithSystemMessage(systemMessage string) ProviderClientOption {
130133 }
131134}
132135
133−func NewProviderV2(cfg configv2.ProviderConfig, opts ...ProviderClientOption) (Provider, error) {
136+func NewProviderV2(cfg config.ProviderConfig, opts ...ProviderClientOption) (Provider, error) {
134137 clientOptions := providerClientOptions{
135138 baseURL: cfg.BaseURL,
136139 apiKey: cfg.APIKey,
137140 extraHeaders: cfg.ExtraHeaders,
141+ model: func(tp config.ModelType) config.Model {
142+ return config.GetModel(tp)
143+ },
138144 }
139145 for _, o := range opts {
140146 o(&clientOptions)
internal/tui/components/dialogs/init/init.go+3−3
@@ -5,7 +5,7 @@ import (
55 tea "github.com/charmbracelet/bubbletea/v2"
66 "github.com/charmbracelet/lipgloss/v2"
77
8− configv2 "github.com/charmbracelet/crush/internal/config"
8+ "github.com/charmbracelet/crush/internal/config"
99 cmpChat "github.com/charmbracelet/crush/internal/tui/components/chat"
1010 "github.com/charmbracelet/crush/internal/tui/components/core"
1111 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
@@ -184,7 +184,7 @@ If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (
184184Add the .crush directory to the .gitignore file if it's not already there.`
185185
186186 // Mark the project as initialized
187− if err := configv2.MarkProjectInitialized(); err != nil {
187+ if err := config.MarkProjectInitialized(); err != nil {
188188 return util.ReportError(err)
189189 }
190190
@@ -196,7 +196,7 @@ Add the .crush directory to the .gitignore file if it's not already there.`
196196 )
197197 } else {
198198 // Mark the project as initialized without running the command
199− if err := configv2.MarkProjectInitialized(); err != nil {
199+ if err := config.MarkProjectInitialized(); err != nil {
200200 return util.ReportError(err)
201201 }
202202 }
internal/tui/components/dialogs/models/models.go+7−7
@@ -4,7 +4,7 @@ import (
44 "github.com/charmbracelet/bubbles/v2/help"
55 "github.com/charmbracelet/bubbles/v2/key"
66 tea "github.com/charmbracelet/bubbletea/v2"
7− configv2 "github.com/charmbracelet/crush/internal/config"
7+ "github.com/charmbracelet/crush/internal/config"
88 "github.com/charmbracelet/crush/internal/fur/provider"
99 "github.com/charmbracelet/crush/internal/tui/components/completions"
1010 "github.com/charmbracelet/crush/internal/tui/components/core"
@@ -24,7 +24,7 @@ const (
2424
2525// ModelSelectedMsg is sent when a model is selected
2626type ModelSelectedMsg struct {
27− Model configv2.PreferredModel
27+ Model config.PreferredModel
2828}
2929
3030// CloseModelDialogMsg is sent when a model is selected
@@ -84,12 +84,12 @@ func NewModelDialogCmp() ModelDialog {
8484}
8585
8686func (m *modelDialogCmp) Init() tea.Cmd {
87− providers := configv2.Providers()
88− cfg := configv2.Get()
87+ providers := config.Providers()
8988
90− coderAgent := cfg.Agents[configv2.AgentCoder]
9189 modelItems := []util.Model{}
9290 selectIndex := 0
91+ agentModel := config.GetAgentModel(config.AgentCoder)
92+ agentProvider := config.GetAgentProvider(config.AgentCoder)
9393 for _, provider := range providers {
9494 name := provider.Name
9595 if name == "" {
@@ -97,7 +97,7 @@ func (m *modelDialogCmp) Init() tea.Cmd {
9797 }
9898 modelItems = append(modelItems, commands.NewItemSection(name))
9999 for _, model := range provider.Models {
100− if model.ID == coderAgent.Model && provider.ID == coderAgent.Provider {
100+ if model.ID == agentModel.ID && provider.ID == agentProvider.ID {
101101 selectIndex = len(modelItems) // Set the selected index to the current model
102102 }
103103 modelItems = append(modelItems, completions.NewCompletionItem(model.Name, ModelOption{
@@ -128,7 +128,7 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
128128
129129 return m, tea.Sequence(
130130 util.CmdHandler(dialogs.CloseDialogMsg{}),
131− util.CmdHandler(ModelSelectedMsg{Model: configv2.PreferredModel{
131+ util.CmdHandler(ModelSelectedMsg{Model: config.PreferredModel{
132132 ModelID: selectedItem.Model.ID,
133133 Provider: selectedItem.Provider.ID,
134134 }}),
internal/tui/tui.go+3−8
@@ -8,7 +8,6 @@ import (
88 tea "github.com/charmbracelet/bubbletea/v2"
99 "github.com/charmbracelet/crush/internal/app"
1010 "github.com/charmbracelet/crush/internal/config"
11− configv2 "github.com/charmbracelet/crush/internal/config"
1211 "github.com/charmbracelet/crush/internal/llm/agent"
1312 "github.com/charmbracelet/crush/internal/logging"
1413 "github.com/charmbracelet/crush/internal/permission"
@@ -70,7 +69,7 @@ func (a appModel) Init() tea.Cmd {
7069
7170 // Check if we should show the init dialog
7271 cmds = append(cmds, func() tea.Msg {
73− shouldShow, err := configv2.ProjectNeedsInitialization()
72+ shouldShow, err := config.ProjectNeedsInitialization()
7473 if err != nil {
7574 return util.InfoMsg{
7675 Type: util.InfoTypeError,
@@ -173,12 +172,8 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
173172
174173 // Model Switch
175174 case models.ModelSelectedMsg:
176− model, err := a.app.CoderAgent.Update(msg.Model)
177− if err != nil {
178− return a, util.ReportError(err)
179− }
180−
181− return a, util.ReportInfo(fmt.Sprintf("Model changed to %s", model.Name))
175+ config.UpdatePreferredModel(config.LargeModel, msg.Model)
176+ return a, util.ReportInfo(fmt.Sprintf("Model changed to %s", msg.Model.ModelID))
182177
183178 // File Picker
184179 case chat.OpenFilePickerMsg: