1package config
2
3import (
4 "slices"
5 "strings"
6
7 "github.com/charmbracelet/crush/internal/fur/provider"
8)
9
10const (
11 appName = "crush"
12 defaultDataDirectory = ".crush"
13 defaultLogLevel = "info"
14)
15
16var defaultContextPaths = []string{
17 ".github/copilot-instructions.md",
18 ".cursorrules",
19 ".cursor/rules/",
20 "CLAUDE.md",
21 "CLAUDE.local.md",
22 "GEMINI.md",
23 "gemini.md",
24 "crush.md",
25 "crush.local.md",
26 "Crush.md",
27 "Crush.local.md",
28 "CRUSH.md",
29 "CRUSH.local.md",
30}
31
32type SelectedModelType string
33
34const (
35 SelectedModelTypeLarge SelectedModelType = "large"
36 SelectedModelTypeSmall SelectedModelType = "small"
37)
38
39type SelectedModel struct {
40 // The model id as used by the provider API.
41 // Required.
42 Model string `json:"model"`
43 // The model provider, same as the key/id used in the providers config.
44 // Required.
45 Provider string `json:"provider"`
46
47 // Only used by models that use the openai provider and need this set.
48 ReasoningEffort string `json:"reasoning_effort,omitempty"`
49
50 // Overrides the default model configuration.
51 MaxTokens int64 `json:"max_tokens,omitempty"`
52
53 // Used by anthropic models that can reason to indicate if the model should think.
54 Think bool `json:"think,omitempty"`
55}
56
57type ProviderConfig struct {
58 // The provider's id.
59 ID string `json:"id,omitempty"`
60 // The provider's API endpoint.
61 BaseURL string `json:"base_url,omitempty"`
62 // The provider type, e.g. "openai", "anthropic", etc. if empty it defaults to openai.
63 Type provider.Type `json:"type,omitempty"`
64 // The provider's API key.
65 APIKey string `json:"api_key,omitempty"`
66 // Marks the provider as disabled.
67 Disable bool `json:"disable,omitempty"`
68
69 // Extra headers to send with each request to the provider.
70 ExtraHeaders map[string]string
71
72 // Used to pass extra parameters to the provider.
73 ExtraParams map[string]string `json:"-"`
74
75 // The provider models
76 Models []provider.Model `json:"models,omitempty"`
77}
78
79type MCPType string
80
81const (
82 MCPStdio MCPType = "stdio"
83 MCPSse MCPType = "sse"
84 MCPHttp MCPType = "http"
85)
86
87type MCPConfig struct {
88 Command string `json:"command,omitempty" `
89 Env []string `json:"env,omitempty"`
90 Args []string `json:"args,omitempty"`
91 Type MCPType `json:"type"`
92 URL string `json:"url,omitempty"`
93
94 // TODO: maybe make it possible to get the value from the env
95 Headers map[string]string `json:"headers,omitempty"`
96}
97
98type LSPConfig struct {
99 Disabled bool `json:"enabled,omitempty"`
100 Command string `json:"command"`
101 Args []string `json:"args,omitempty"`
102 Options any `json:"options,omitempty"`
103}
104
105type TUIOptions struct {
106 CompactMode bool `json:"compact_mode,omitempty"`
107 // Here we can add themes later or any TUI related options
108}
109
110type Options struct {
111 ContextPaths []string `json:"context_paths,omitempty"`
112 TUI *TUIOptions `json:"tui,omitempty"`
113 Debug bool `json:"debug,omitempty"`
114 DebugLSP bool `json:"debug_lsp,omitempty"`
115 DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty"`
116 // Relative to the cwd
117 DataDirectory string `json:"data_directory,omitempty"`
118}
119
120type MCPs map[string]MCPConfig
121
122type MCP struct {
123 Name string `json:"name"`
124 MCP MCPConfig `json:"mcp"`
125}
126
127func (m MCPs) Sorted() []MCP {
128 sorted := make([]MCP, 0, len(m))
129 for k, v := range m {
130 sorted = append(sorted, MCP{
131 Name: k,
132 MCP: v,
133 })
134 }
135 slices.SortFunc(sorted, func(a, b MCP) int {
136 return strings.Compare(a.Name, b.Name)
137 })
138 return sorted
139}
140
141type LSPs map[string]LSPConfig
142
143type LSP struct {
144 Name string `json:"name"`
145 LSP LSPConfig `json:"lsp"`
146}
147
148func (l LSPs) Sorted() []LSP {
149 sorted := make([]LSP, 0, len(l))
150 for k, v := range l {
151 sorted = append(sorted, LSP{
152 Name: k,
153 LSP: v,
154 })
155 }
156 slices.SortFunc(sorted, func(a, b LSP) int {
157 return strings.Compare(a.Name, b.Name)
158 })
159 return sorted
160}
161
162// Config holds the configuration for crush.
163type Config struct {
164 // We currently only support large/small as values here.
165 Models map[SelectedModelType]SelectedModel `json:"models,omitempty"`
166
167 // The providers that are configured
168 Providers map[string]ProviderConfig `json:"providers,omitempty"`
169
170 MCP MCPs `json:"mcp,omitempty"`
171
172 LSP LSPs `json:"lsp,omitempty"`
173
174 Options *Options `json:"options,omitempty"`
175
176 // Internal
177 workingDir string `json:"-"`
178}
179
180func (c *Config) WorkingDir() string {
181 return c.workingDir
182}
183
184func (c *Config) EnabledProviders() []ProviderConfig {
185 enabled := make([]ProviderConfig, 0, len(c.Providers))
186 for _, p := range c.Providers {
187 if !p.Disable {
188 enabled = append(enabled, p)
189 }
190 }
191 return enabled
192}
193
194// IsConfigured return true if at least one provider is configured
195func (c *Config) IsConfigured() bool {
196 return len(c.EnabledProviders()) > 0
197}
198
199func (c *Config) GetModel(provider, model string) *provider.Model {
200 if providerConfig, ok := c.Providers[provider]; ok {
201 for _, m := range providerConfig.Models {
202 if m.ID == model {
203 return &m
204 }
205 }
206 }
207 return nil
208}
209
210func (c *Config) LargeModel() *provider.Model {
211 model, ok := c.Models[SelectedModelTypeLarge]
212 if !ok {
213 return nil
214 }
215 return c.GetModel(model.Provider, model.Model)
216}
217
218func (c *Config) SmallModel() *provider.Model {
219 model, ok := c.Models[SelectedModelTypeSmall]
220 if !ok {
221 return nil
222 }
223 return c.GetModel(model.Provider, model.Model)
224}