config.go

  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}