on main help sign in
chore: change how we generate the json schema
commit21719ee394b679b3b5522d3a128b1b6148c65353 authorKujtim Hoxha <kujtimii.h@gmail.com> dateMon 30 Jun 2025, 13:43 (1 year ago) parent28c85ea
10 files changed+702−1837
cmd/schema/main.go+76−452 crush-schema.json+482−1279 crush.json+2−1 cspell.json+1−1 go.mod+8 go.sum+11 internal/config/config.go+103−84 internal/config/config_test.go−1 internal/config/provider_mock.go+18−18 internal/config/provider_test.go+1−1
cmd/schema/main.go+76−452
@@ -4,67 +4,31 @@ import (
44 "encoding/json"
55 "fmt"
66 "os"
7− "reflect"
8− "slices"
9− "strings"
107
118 "github.com/charmbracelet/crush/internal/config"
9+ "github.com/invopop/jsonschema"
1210)
1311
14−// JSONSchema represents a JSON Schema
15−type JSONSchema struct {
16− Schema string `json:"$schema,omitempty"`
17− Title string `json:"title,omitempty"`
18− Description string `json:"description,omitempty"`
19− Type string `json:"type,omitempty"`
20− Properties map[string]*JSONSchema `json:"properties,omitempty"`
21− Items *JSONSchema `json:"items,omitempty"`
22− Required []string `json:"required,omitempty"`
23− AdditionalProperties any `json:"additionalProperties,omitempty"`
24− Enum []any `json:"enum,omitempty"`
25− Default any `json:"default,omitempty"`
26− Definitions map[string]*JSONSchema `json:"definitions,omitempty"`
27− Ref string `json:"$ref,omitempty"`
28− OneOf []*JSONSchema `json:"oneOf,omitempty"`
29− AnyOf []*JSONSchema `json:"anyOf,omitempty"`
30− AllOf []*JSONSchema `json:"allOf,omitempty"`
31− Not *JSONSchema `json:"not,omitempty"`
32− Format string `json:"format,omitempty"`
33− Pattern string `json:"pattern,omitempty"`
34− MinLength *int `json:"minLength,omitempty"`
35− MaxLength *int `json:"maxLength,omitempty"`
36− Minimum *float64 `json:"minimum,omitempty"`
37− Maximum *float64 `json:"maximum,omitempty"`
38− ExclusiveMinimum *float64 `json:"exclusiveMinimum,omitempty"`
39− ExclusiveMaximum *float64 `json:"exclusiveMaximum,omitempty"`
40− MultipleOf *float64 `json:"multipleOf,omitempty"`
41− MinItems *int `json:"minItems,omitempty"`
42− MaxItems *int `json:"maxItems,omitempty"`
43− UniqueItems *bool `json:"uniqueItems,omitempty"`
44− MinProperties *int `json:"minProperties,omitempty"`
45− MaxProperties *int `json:"maxProperties,omitempty"`
46−}
47−
48−// SchemaGenerator generates JSON schemas from Go types
49−type SchemaGenerator struct {
50− definitions map[string]*JSONSchema
51− visited map[reflect.Type]bool
52−}
53−
54−// NewSchemaGenerator creates a new schema generator
55−func NewSchemaGenerator() *SchemaGenerator {
56− return &SchemaGenerator{
57− definitions: make(map[string]*JSONSchema),
58− visited: make(map[reflect.Type]bool),
12+func main() {
13+ // Create a new reflector
14+ r := &jsonschema.Reflector{
15+ // Use anonymous schemas to avoid ID conflicts
16+ Anonymous: true,
17+ // Expand the root struct instead of referencing it
18+ ExpandedStruct: true,
19+ AllowAdditionalProperties: true,
5920 }
60−}
6121
62−func main() {
63− // Enable mock providers to avoid API calls during schema generation
64− config.UseMockProviders = true
22+ // Generate schema for the main Config struct
23+ schema := r.Reflect(&config.Config{})
24+
25+ // Enhance the schema with additional information
26+ enhanceSchema(schema)
6527
66− generator := NewSchemaGenerator()
67− schema := generator.GenerateSchema()
28+ // Set the schema metadata
29+ schema.Version = "https://json-schema.org/draft/2020-12/schema"
30+ schema.Title = "Crush Configuration"
31+ schema.Description = "Configuration schema for the Crush application"
6832
6933 // Pretty print the schema
7034 encoder := json.NewEncoder(os.Stdout)
@@ -75,226 +39,23 @@ func main() {
7539 }
7640}
7741
78−// GenerateSchema generates the complete JSON schema for the Crush configuration
79−func (g *SchemaGenerator) GenerateSchema() *JSONSchema {
80− // Generate schema for the main Config struct
81− configType := reflect.TypeOf(config.Config{})
82− configSchema := g.generateTypeSchema(configType)
83−
84− // Create the root schema
85− schema := &JSONSchema{
86− Schema: "http://json-schema.org/draft-07/schema#",
87− Title: "Crush Configuration",
88− Description: "Configuration schema for the Crush application",
89− Type: configSchema.Type,
90− Properties: configSchema.Properties,
91− Required: configSchema.Required,
92− Definitions: g.definitions,
93− }
94−
95− // Add custom enhancements
96− g.enhanceSchema(schema)
97−
98− return schema
99−}
100−
101−// generateTypeSchema generates a JSON schema for a given Go type
102−func (g *SchemaGenerator) generateTypeSchema(t reflect.Type) *JSONSchema {
103− // Handle pointers
104− if t.Kind() == reflect.Ptr {
105− return g.generateTypeSchema(t.Elem())
106− }
107−
108− // Check if we've already processed this type
109− if g.visited[t] {
110− // Return a reference to avoid infinite recursion
111− return &JSONSchema{
112− Ref: fmt.Sprintf("#/definitions/%s", t.Name()),
113− }
114− }
115−
116− switch t.Kind() {
117− case reflect.String:
118− return &JSONSchema{Type: "string"}
119− case reflect.Bool:
120− return &JSONSchema{Type: "boolean"}
121− case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
122− reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
123− return &JSONSchema{Type: "integer"}
124− case reflect.Float32, reflect.Float64:
125− return &JSONSchema{Type: "number"}
126− case reflect.Slice, reflect.Array:
127− itemSchema := g.generateTypeSchema(t.Elem())
128− return &JSONSchema{
129− Type: "array",
130− Items: itemSchema,
131− }
132− case reflect.Map:
133− valueSchema := g.generateTypeSchema(t.Elem())
134− return &JSONSchema{
135− Type: "object",
136− AdditionalProperties: valueSchema,
137− }
138− case reflect.Struct:
139− return g.generateStructSchema(t)
140− case reflect.Interface:
141− // For interface{} types, allow any value
142− return &JSONSchema{}
143− default:
144− // Fallback for unknown types
145− return &JSONSchema{}
146− }
147−}
148−
149−// generateStructSchema generates a JSON schema for a struct type
150−func (g *SchemaGenerator) generateStructSchema(t reflect.Type) *JSONSchema {
151− // Mark as visited to prevent infinite recursion
152− g.visited[t] = true
153−
154− schema := &JSONSchema{
155− Type: "object",
156− Properties: make(map[string]*JSONSchema),
157− }
158−
159− var required []string
160−
161− for i := range t.NumField() {
162− field := t.Field(i)
163−
164− // Skip unexported fields
165− if !field.IsExported() {
166− continue
167− }
168−
169− // Get JSON tag
170− jsonTag := field.Tag.Get("json")
171− if jsonTag == "-" {
172− continue
173− }
174−
175− // Parse JSON tag
176− jsonName, options := parseJSONTag(jsonTag)
177− if jsonName == "" {
178− jsonName = strings.ToLower(field.Name)
179− }
180−
181− // Generate field schema
182− fieldSchema := g.generateTypeSchema(field.Type)
183−
184− // Add description from field name if not present
185− if fieldSchema.Description == "" {
186− fieldSchema.Description = generateFieldDescription(field.Name, field.Type)
187− }
188−
189− // Check if field is required (not omitempty and not a pointer)
190− if !slices.Contains(options, "omitempty") && field.Type.Kind() != reflect.Ptr {
191− required = append(required, jsonName)
192− }
193−
194− schema.Properties[jsonName] = fieldSchema
195− }
196−
197− if len(required) > 0 {
198− schema.Required = required
199− }
200−
201− // Store in definitions if it's a named type
202− if t.Name() != "" {
203− g.definitions[t.Name()] = schema
204− }
205−
206− return schema
207−}
208−
209−// parseJSONTag parses a JSON struct tag
210−func parseJSONTag(tag string) (name string, options []string) {
211− if tag == "" {
212− return "", nil
213− }
214−
215− parts := strings.Split(tag, ",")
216− name = parts[0]
217− if len(parts) > 1 {
218− options = parts[1:]
219− }
220− return name, options
221−}
222−
223−// generateFieldDescription generates a description for a field based on its name and type
224−func generateFieldDescription(fieldName string, fieldType reflect.Type) string {
225− // Convert camelCase to words
226− words := camelCaseToWords(fieldName)
227− description := strings.Join(words, " ")
228−
229− // Add type-specific information
230− switch fieldType.Kind() {
231− case reflect.Bool:
232− if !strings.Contains(strings.ToLower(description), "enable") &&
233− !strings.Contains(strings.ToLower(description), "disable") {
234− description = "Enable " + strings.ToLower(description)
235− }
236− case reflect.Slice:
237− if !strings.HasSuffix(description, "s") {
238− description = description + " list"
239− }
240− case reflect.Map:
241− description = description + " configuration"
242− }
243−
244− return description
245−}
246−
247−// camelCaseToWords converts camelCase to separate words
248−func camelCaseToWords(s string) []string {
249− var words []string
250− var currentWord strings.Builder
251−
252− for i, r := range s {
253− if i > 0 && r >= 'A' && r <= 'Z' {
254− if currentWord.Len() > 0 {
255− words = append(words, currentWord.String())
256− currentWord.Reset()
257− }
258− }
259− currentWord.WriteRune(r)
260− }
261−
262− if currentWord.Len() > 0 {
263− words = append(words, currentWord.String())
264− }
265−
266− return words
267−}
268−
269−// enhanceSchema adds custom enhancements to the generated schema
270−func (g *SchemaGenerator) enhanceSchema(schema *JSONSchema) {
42+// enhanceSchema adds additional enhancements to the generated schema
43+func enhanceSchema(schema *jsonschema.Schema) {
27144 // Add provider enums
272− g.addProviderEnums(schema)
45+ addProviderEnums(schema)
27346
27447 // Add model enums
275− g.addModelEnums(schema)
276−
277− // Add agent enums
278− g.addAgentEnums(schema)
48+ addModelEnums(schema)
27949
28050 // Add tool enums
281− g.addToolEnums(schema)
282−
283− // Add MCP type enums
284− g.addMCPTypeEnums(schema)
285−
286− // Add model type enums
287− g.addModelTypeEnums(schema)
288−
289− // Add default values
290− g.addDefaultValues(schema)
51+ addToolEnums(schema)
29152
292− // Add custom descriptions
293− g.addCustomDescriptions(schema)
53+ // Add default context paths
54+ addDefaultContextPaths(schema)
29455}
29556
29657// addProviderEnums adds provider enums to the schema
297−func (g *SchemaGenerator) addProviderEnums(schema *JSONSchema) {
58+func addProviderEnums(schema *jsonschema.Schema) {
29859 providers := config.Providers()
29960 var providerIDs []any
30061 for _, p := range providers {
@@ -302,22 +63,24 @@ func (g *SchemaGenerator) addProviderEnums(schema *JSONSchema) {
30263 }
30364
30465 // Add to PreferredModel provider field
305− if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists {
306− if providerProp, exists := preferredModelDef.Properties["provider"]; exists {
307− providerProp.Enum = providerIDs
66+ if schema.Definitions != nil {
67+ if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists {
68+ if providerProp, exists := preferredModelDef.Properties.Get("provider"); exists {
69+ providerProp.Enum = providerIDs
70+ }
30871 }
309− }
31072
311− // Add to ProviderConfig ID field
312− if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists {
313− if idProp, exists := providerConfigDef.Properties["id"]; exists {
314− idProp.Enum = providerIDs
73+ // Add to ProviderConfig ID field
74+ if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists {
75+ if idProp, exists := providerConfigDef.Properties.Get("id"); exists {
76+ idProp.Enum = providerIDs
77+ }
31578 }
31679 }
31780}
31881
31982// addModelEnums adds model enums to the schema
320−func (g *SchemaGenerator) addModelEnums(schema *JSONSchema) {
83+func addModelEnums(schema *jsonschema.Schema) {
32184 providers := config.Providers()
32285 var modelIDs []any
32386 for _, p := range providers {
@@ -327,205 +90,66 @@ func (g *SchemaGenerator) addModelEnums(schema *JSONSchema) {
32790 }
32891
32992 // Add to PreferredModel model_id field
330− if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists {
331− if modelIDProp, exists := preferredModelDef.Properties["model_id"]; exists {
332− modelIDProp.Enum = modelIDs
333− }
334− }
335−}
336−
337−// addAgentEnums adds agent ID enums to the schema
338−func (g *SchemaGenerator) addAgentEnums(schema *JSONSchema) {
339− agentIDs := []any{
340− string(config.AgentCoder),
341− string(config.AgentTask),
342− }
343−
344− if agentDef, exists := schema.Definitions["Agent"]; exists {
345− if idProp, exists := agentDef.Properties["id"]; exists {
346− idProp.Enum = agentIDs
93+ if schema.Definitions != nil {
94+ if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists {
95+ if modelIDProp, exists := preferredModelDef.Properties.Get("model_id"); exists {
96+ modelIDProp.Enum = modelIDs
97+ }
34798 }
34899 }
349100}
350101
351102// addToolEnums adds tool enums to the schema
352−func (g *SchemaGenerator) addToolEnums(schema *JSONSchema) {
103+func addToolEnums(schema *jsonschema.Schema) {
353104 tools := []any{
354105 "bash", "edit", "fetch", "glob", "grep", "ls", "sourcegraph", "view", "write", "agent",
355106 }
356107
357− if agentDef, exists := schema.Definitions["Agent"]; exists {
358− if allowedToolsProp, exists := agentDef.Properties["allowed_tools"]; exists {
359− if allowedToolsProp.Items != nil {
360− allowedToolsProp.Items.Enum = tools
108+ if schema.Definitions != nil {
109+ if agentDef, exists := schema.Definitions["Agent"]; exists {
110+ if allowedToolsProp, exists := agentDef.Properties.Get("allowed_tools"); exists {
111+ if allowedToolsProp.Items != nil {
112+ allowedToolsProp.Items.Enum = tools
113+ }
361114 }
362115 }
363116 }
364117}
365118
366−// addMCPTypeEnums adds MCP type enums to the schema
367−func (g *SchemaGenerator) addMCPTypeEnums(schema *JSONSchema) {
368− mcpTypes := []any{
369− string(config.MCPStdio),
370− string(config.MCPSse),
119+// addDefaultContextPaths adds default context paths to the schema
120+func addDefaultContextPaths(schema *jsonschema.Schema) {
121+ defaultContextPaths := []any{
122+ ".github/copilot-instructions.md",
123+ ".cursorrules",
124+ ".cursor/rules/",
125+ "CLAUDE.md",
126+ "CLAUDE.local.md",
127+ "GEMINI.md",
128+ "gemini.md",
129+ "crush.md",
130+ "crush.local.md",
131+ "Crush.md",
132+ "Crush.local.md",
133+ "CRUSH.md",
134+ "CRUSH.local.md",
371135 }
372136
373− if mcpDef, exists := schema.Definitions["MCP"]; exists {
374− if typeProp, exists := mcpDef.Properties["type"]; exists {
375− typeProp.Enum = mcpTypes
376− }
377− }
378−}
379−
380−// addModelTypeEnums adds model type enums to the schema
381−func (g *SchemaGenerator) addModelTypeEnums(schema *JSONSchema) {
382− modelTypes := []any{
383− string(config.LargeModel),
384− string(config.SmallModel),
385− }
386−
387− if agentDef, exists := schema.Definitions["Agent"]; exists {
388− if modelProp, exists := agentDef.Properties["model"]; exists {
389− modelProp.Enum = modelTypes
390− }
391− }
392−}
393−
394−// addDefaultValues adds default values to the schema
395−func (g *SchemaGenerator) addDefaultValues(schema *JSONSchema) {
396− // Add default context paths
397− if optionsDef, exists := schema.Definitions["Options"]; exists {
398− if contextPathsProp, exists := optionsDef.Properties["context_paths"]; exists {
399− contextPathsProp.Default = []any{
400− ".github/copilot-instructions.md",
401− ".cursorrules",
402− ".cursor/rules/",
403− "CLAUDE.md",
404− "CLAUDE.local.md",
405− "GEMINI.md",
406− "gemini.md",
407− "crush.md",
408− "crush.local.md",
409− "Crush.md",
410− "Crush.local.md",
411− "CRUSH.md",
412− "CRUSH.local.md",
137+ if schema.Definitions != nil {
138+ if optionsDef, exists := schema.Definitions["Options"]; exists {
139+ if contextPathsProp, exists := optionsDef.Properties.Get("context_paths"); exists {
140+ contextPathsProp.Default = defaultContextPaths
413141 }
414142 }
415− if dataDirProp, exists := optionsDef.Properties["data_directory"]; exists {
416− dataDirProp.Default = ".crush"
417− }
418− if debugProp, exists := optionsDef.Properties["debug"]; exists {
419− debugProp.Default = false
420− }
421− if debugLSPProp, exists := optionsDef.Properties["debug_lsp"]; exists {
422− debugLSPProp.Default = false
423− }
424− if disableAutoSummarizeProp, exists := optionsDef.Properties["disable_auto_summarize"]; exists {
425− disableAutoSummarizeProp.Default = false
426− }
427− }
428−
429− // Add default MCP type
430− if mcpDef, exists := schema.Definitions["MCP"]; exists {
431− if typeProp, exists := mcpDef.Properties["type"]; exists {
432− typeProp.Default = string(config.MCPStdio)
433− }
434− }
435−
436− // Add default TUI options
437− if tuiOptionsDef, exists := schema.Definitions["TUIOptions"]; exists {
438− if compactModeProp, exists := tuiOptionsDef.Properties["compact_mode"]; exists {
439− compactModeProp.Default = false
440− }
441− }
442−
443− // Add default provider disabled
444− if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists {
445− if disabledProp, exists := providerConfigDef.Properties["disabled"]; exists {
446− disabledProp.Default = false
447− }
448− }
449−
450− // Add default agent disabled
451− if agentDef, exists := schema.Definitions["Agent"]; exists {
452− if disabledProp, exists := agentDef.Properties["disabled"]; exists {
453− disabledProp.Default = false
454− }
455− }
456−
457− // Add default LSP disabled
458− if lspConfigDef, exists := schema.Definitions["LSPConfig"]; exists {
459− if disabledProp, exists := lspConfigDef.Properties["enabled"]; exists {
460− disabledProp.Default = true
461− }
462143 }
463−}
464144
465−// addCustomDescriptions adds custom descriptions to improve the schema
466−func (g *SchemaGenerator) addCustomDescriptions(schema *JSONSchema) {
467− // Enhance main config descriptions
145+ // Also add to root properties if they exist
468146 if schema.Properties != nil {
469− if modelsProp, exists := schema.Properties["models"]; exists {
470− modelsProp.Description = "Preferred model configurations for large and small model types"
471− }
472− if providersProp, exists := schema.Properties["providers"]; exists {
473− providersProp.Description = "LLM provider configurations"
474− }
475− if agentsProp, exists := schema.Properties["agents"]; exists {
476− agentsProp.Description = "Agent configurations for different tasks"
477− }
478− if mcpProp, exists := schema.Properties["mcp"]; exists {
479− mcpProp.Description = "Model Control Protocol server configurations"
480− }
481− if lspProp, exists := schema.Properties["lsp"]; exists {
482− lspProp.Description = "Language Server Protocol configurations"
483− }
484− if optionsProp, exists := schema.Properties["options"]; exists {
485− optionsProp.Description = "General application options and settings"
486− }
487− }
488−
489− // Enhance specific field descriptions
490− if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists {
491− if apiKeyProp, exists := providerConfigDef.Properties["api_key"]; exists {
492− apiKeyProp.Description = "API key for authenticating with the provider"
493− }
494− if baseURLProp, exists := providerConfigDef.Properties["base_url"]; exists {
495− baseURLProp.Description = "Base URL for the provider API (required for custom providers)"
496− }
497− if extraHeadersProp, exists := providerConfigDef.Properties["extra_headers"]; exists {
498− extraHeadersProp.Description = "Additional HTTP headers to send with requests"
499− }
500− if extraParamsProp, exists := providerConfigDef.Properties["extra_params"]; exists {
501− extraParamsProp.Description = "Additional provider-specific parameters"
502− }
503− }
504−
505− if agentDef, exists := schema.Definitions["Agent"]; exists {
506− if allowedToolsProp, exists := agentDef.Properties["allowed_tools"]; exists {
507− allowedToolsProp.Description = "List of tools this agent is allowed to use (if nil, all tools are allowed)"
508− }
509− if allowedMCPProp, exists := agentDef.Properties["allowed_mcp"]; exists {
510− allowedMCPProp.Description = "Map of MCP servers this agent can use and their allowed tools"
511− }
512− if allowedLSPProp, exists := agentDef.Properties["allowed_lsp"]; exists {
513− allowedLSPProp.Description = "List of LSP servers this agent can use (if nil, all LSPs are allowed)"
514− }
515− if contextPathsProp, exists := agentDef.Properties["context_paths"]; exists {
516− contextPathsProp.Description = "Custom context paths for this agent (additive to global context paths)"
517− }
518− }
519−
520− if mcpDef, exists := schema.Definitions["MCP"]; exists {
521− if commandProp, exists := mcpDef.Properties["command"]; exists {
522− commandProp.Description = "Command to execute for stdio MCP servers"
523− }
524− if urlProp, exists := mcpDef.Properties["url"]; exists {
525− urlProp.Description = "URL for SSE MCP servers"
526− }
527− if headersProp, exists := mcpDef.Properties["headers"]; exists {
528− headersProp.Description = "HTTP headers for SSE MCP servers"
147+ if optionsProp, exists := schema.Properties.Get("options"); exists {
148+ if optionsProp.Properties != nil {
149+ if contextPathsProp, exists := optionsProp.Properties.Get("context_paths"); exists {
150+ contextPathsProp.Default = defaultContextPaths
151+ }
152+ }
529153 }
530154 }
531155}
crush-schema.json+482−1279
@@ -1,492 +1,47 @@
11{
2− "$schema": "http://json-schema.org/draft-07/schema#",
3− "title": "Crush Configuration",
4− "description": "Configuration schema for the Crush application",
5− "type": "object",
6− "properties": {
7− "agents": {
8− "description": "Agent configurations for different tasks",
9− "type": "object",
10− "additionalProperties": {
11− "type": "object",
12− "properties": {
13− "allowed_lsp": {
14− "description": "List of LSP servers this agent can use (if nil, all LSPs are allowed)",
15− "type": "array",
16− "items": {
17− "type": "string"
18− }
19− },
20− "allowed_mcp": {
21− "description": "Map of MCP servers this agent can use and their allowed tools",
22− "type": "object",
23− "additionalProperties": {
24− "type": "array",
25− "items": {
26− "type": "string"
27− }
28− }
29− },
30− "allowed_tools": {
31− "description": "List of tools this agent is allowed to use (if nil, all tools are allowed)",
32− "type": "array",
33− "items": {
34− "type": "string",
35− "enum": [
36− "bash",
37− "edit",
38− "fetch",
39− "glob",
40− "grep",
41− "ls",
42− "sourcegraph",
43− "view",
44− "write",
45− "agent"
46− ]
47− }
48− },
49− "context_paths": {
50− "description": "Custom context paths for this agent (additive to global context paths)",
51− "type": "array",
52− "items": {
53− "type": "string"
54− }
55− },
56− "description": {
57− "description": "Description",
58− "type": "string"
59− },
60− "disabled": {
61− "description": "Disabled",
62− "type": "boolean",
63− "default": false
64− },
65− "id": {
66− "description": "I D",
67− "type": "string",
68− "enum": [
69− "coder",
70− "task"
71− ]
72− },
73− "model": {
74− "description": "Model",
75− "type": "string",
76− "enum": [
77− "large",
78− "small"
79− ]
80− },
81− "name": {
82− "description": "Name",
83− "type": "string"
84− }
85− },
86− "required": [
87− "id",
88− "name",
89− "disabled",
90− "model",
91− "allowed_tools",
92− "allowed_mcp",
93− "allowed_lsp",
94− "context_paths"
95− ]
96− }
97− },
98− "lsp": {
99− "description": "Language Server Protocol configurations",
100− "type": "object",
101− "additionalProperties": {
102− "type": "object",
103− "properties": {
104− "args": {
105− "description": "Args",
106− "type": "array",
107− "items": {
108− "type": "string"
109− }
110− },
111− "command": {
112− "description": "Command",
113− "type": "string"
114− },
115− "enabled": {
116− "description": "Disabled",
117− "type": "boolean",
118− "default": true
119− },
120− "options": {
121− "description": "Options"
122− }
123− },
124− "required": [
125− "enabled",
126− "command",
127− "args",
128− "options"
129− ]
130− }
131− },
132− "mcp": {
133− "description": "Model Control Protocol server configurations",
134− "type": "object",
135− "additionalProperties": {
136− "type": "object",
137− "properties": {
138− "args": {
139− "description": "Args",
140− "type": "array",
141− "items": {
142− "type": "string"
143− }
144− },
145− "command": {
146− "description": "Command to execute for stdio MCP servers",
147− "type": "string"
148− },
149− "env": {
150− "description": "Env list",
151− "type": "array",
152− "items": {
153− "type": "string"
154− }
155− },
156− "headers": {
157− "description": "HTTP headers for SSE MCP servers",
158− "type": "object",
159− "additionalProperties": {
160− "type": "string"
161− }
162− },
163− "type": {
164− "description": "Type",
165− "type": "string",
166− "enum": [
167− "stdio",
168− "sse"
169− ],
170− "default": "stdio"
171− },
172− "url": {
173− "description": "URL for SSE MCP servers",
174− "type": "string"
175− }
176− },
177− "required": [
178− "command",
179− "env",
180− "args",
181− "type",
182− "url",
183− "headers"
184− ]
185− }
186− },
187− "models": {
188− "description": "Preferred model configurations for large and small model types",
189− "type": "object",
190− "properties": {
191− "large": {
192− "description": "Large",
193− "type": "object",
194− "properties": {
195− "max_tokens": {
196− "description": "Max Tokens",
197− "type": "integer"
198− },
199− "model_id": {
200− "description": "Model I D",
201− "type": "string",
202− "enum": [
203− "claude-3-opus",
204− "claude-3-haiku",
205− "claude-3-5-sonnet-20241022",
206− "claude-3-5-haiku-20241022",
207− "gpt-4",
208− "gpt-3.5-turbo",
209− "gpt-4-turbo",
210− "gpt-4o",
211− "gpt-4o-mini",
212− "o1-preview",
213− "o1-mini",
214− "gemini-2.5-pro",
215− "gemini-2.5-flash",
216− "grok-beta",
217− "anthropic/claude-3.5-sonnet",
218− "anthropic/claude-3.5-haiku"
219− ]
220− },
221− "provider": {
222− "description": "Provider",
223− "type": "string",
224− "enum": [
225− "anthropic",
226− "openai",
227− "gemini",
228− "xai",
229− "openrouter"
230− ]
231− },
232− "reasoning_effort": {
233− "description": "Reasoning Effort",
234− "type": "string"
235− },
236− "think": {
237− "description": "Enable think",
238− "type": "boolean"
239− }
240− },
241− "required": [
242− "model_id",
243− "provider"
244− ]
245− },
246− "small": {
247− "description": "Small",
248− "$ref": "#/definitions/PreferredModel"
249− }
250− },
251− "required": [
252− "large",
253− "small"
254− ]
255− },
256− "options": {
257− "description": "General application options and settings",
258− "type": "object",
2+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3+ "$defs": {
4+ "Agent": {
2595 "properties": {
260− "context_paths": {
261− "description": "Context Paths",
262− "type": "array",
263− "items": {
264− "type": "string"
265− },
266− "default": [
267− ".github/copilot-instructions.md",
268− ".cursorrules",
269− ".cursor/rules/",
270− "CLAUDE.md",
271− "CLAUDE.local.md",
272− "GEMINI.md",
273− "gemini.md",
274− "crush.md",
275− "crush.local.md",
276− "Crush.md",
277− "Crush.local.md",
278− "CRUSH.md",
279− "CRUSH.local.md"
280− ]
281− },
282− "data_directory": {
283− "description": "Data Directory",
6+ "id": {
2847 "type": "string",
285− "default": ".crush"
8+ "enum": [
9+ "coder",
10+ "task",
11+ "coder",
12+ "task"
13+ ],
14+ "title": "Agent ID",
15+ "description": "Unique identifier for the agent"
28616 },
287− "debug": {
288− "description": "Enable debug",
289− "type": "boolean",
290− "default": false
17+ "name": {
18+ "type": "string",
19+ "title": "Name",
20+ "description": "Display name of the agent"
29121 },
292− "debug_lsp": {
293− "description": "Enable debug l s p",
294− "type": "boolean",
295− "default": false
22+ "description": {
23+ "type": "string",
24+ "title": "Description",
25+ "description": "Description of what the agent does"
29626 },
297− "disable_auto_summarize": {
298− "description": "Disable Auto Summarize",
27+ "disabled": {
29928 "type": "boolean",
29+ "title": "Disabled",
30+ "description": "Whether this agent is disabled",
30031 "default": false
30132 },
302− "tui": {
303− "description": "T U I",
304− "type": "object",
305− "properties": {
306− "compact_mode": {
307− "description": "Enable compact mode",
308− "type": "boolean",
309− "default": false
310− }
311− },
312− "required": [
313− "compact_mode"
314− ]
315− }
316− },
317− "required": [
318− "context_paths",
319− "tui",
320− "debug",
321− "debug_lsp",
322− "disable_auto_summarize",
323− "data_directory"
324− ]
325− },
326− "providers": {
327− "description": "LLM provider configurations",
328− "type": "object",
329− "additionalProperties": {
330− "type": "object",
331− "properties": {
332− "api_key": {
333− "description": "API key for authenticating with the provider",
334− "type": "string"
335− },
336− "base_url": {
337− "description": "Base URL for the provider API (required for custom providers)",
338− "type": "string"
339− },
340− "default_large_model": {
341− "description": "Default Large Model",
342− "type": "string"
343− },
344− "default_small_model": {
345− "description": "Default Small Model",
346− "type": "string"
347− },
348− "disabled": {
349− "description": "Disabled",
350− "type": "boolean",
351− "default": false
352− },
353− "extra_headers": {
354− "description": "Additional HTTP headers to send with requests",
355− "type": "object",
356− "additionalProperties": {
357− "type": "string"
358− }
359− },
360− "extra_params": {
361− "description": "Additional provider-specific parameters",
362− "type": "object",
363− "additionalProperties": {
364− "type": "string"
365− }
366− },
367− "id": {
368− "description": "I D",
369− "type": "string",
370− "enum": [
371− "anthropic",
372− "openai",
373− "gemini",
374− "xai",
375− "openrouter"
376− ]
377− },
378− "models": {
379− "description": "Models",
380− "type": "array",
381− "items": {
382− "type": "object",
383− "properties": {
384− "can_reason": {
385− "description": "Enable can reason",
386− "type": "boolean"
387− },
388− "context_window": {
389− "description": "Context Window",
390− "type": "integer"
391− },
392− "cost_per_1m_in": {
393− "description": "Cost Per1 M In",
394− "type": "number"
395− },
396− "cost_per_1m_in_cached": {
397− "description": "Cost Per1 M In Cached",
398− "type": "number"
399− },
400− "cost_per_1m_out": {
401− "description": "Cost Per1 M Out",
402− "type": "number"
403− },
404− "cost_per_1m_out_cached": {
405− "description": "Cost Per1 M Out Cached",
406− "type": "number"
407− },
408− "default_max_tokens": {
409− "description": "Default Max Tokens",
410− "type": "integer"
411− },
412− "has_reasoning_effort": {
413− "description": "Enable has reasoning effort",
414− "type": "boolean"
415− },
416− "id": {
417− "description": "I D",
418− "type": "string"
419− },
420− "model": {
421− "description": "Name",
422− "type": "string"
423− },
424− "reasoning_effort": {
425− "description": "Reasoning Effort",
426− "type": "string"
427− },
428− "supports_attachments": {
429− "description": "Enable supports images",
430− "type": "boolean"
431− }
432− },
433− "required": [
434− "id",
435− "model",
436− "cost_per_1m_in",
437− "cost_per_1m_out",
438− "cost_per_1m_in_cached",
439− "cost_per_1m_out_cached",
440− "context_window",
441− "default_max_tokens",
442− "can_reason",
443− "reasoning_effort",
444− "has_reasoning_effort",
445− "supports_attachments"
446− ]
447− }
448− },
449− "provider_type": {
450− "description": "Provider Type",
451− "type": "string"
452− }
453− },
454− "required": [
455− "id",
456− "provider_type",
457− "disabled"
458− ]
459− }
460− }
461− },
462− "required": [
463− "models",
464− "options"
465− ],
466− "definitions": {
467− "Agent": {
468− "type": "object",
469− "properties": {
470− "allowed_lsp": {
471− "description": "List of LSP servers this agent can use (if nil, all LSPs are allowed)",
472− "type": "array",
473− "items": {
474− "type": "string"
475− }
476− },
477− "allowed_mcp": {
478− "description": "Map of MCP servers this agent can use and their allowed tools",
479− "type": "object",
480− "additionalProperties": {
481− "type": "array",
482− "items": {
483− "type": "string"
484− }
485− }
33+ "model": {
34+ "type": "string",
35+ "enum": [
36+ "large",
37+ "small",
38+ "large",
39+ "small"
40+ ],
41+ "title": "Model Type",
42+ "description": "Type of model to use (large or small)"
48643 },
48744 "allowed_tools": {
488− "description": "List of tools this agent is allowed to use (if nil, all tools are allowed)",
489− "type": "array",
49045 "items": {
49146 "type": "string",
49247 "enum": [
@@ -501,652 +56,200 @@
50156 "write",
50257 "agent"
50358 ]
504− }
505− },
506− "context_paths": {
507− "description": "Custom context paths for this agent (additive to global context paths)",
59+ },
50860 "type": "array",
509− "items": {
510− "type": "string"
511− }
512− },
513− "description": {
514− "description": "Description",
515− "type": "string"
516− },
517− "disabled": {
518− "description": "Disabled",
519− "type": "boolean",
520− "default": false
521− },
522− "id": {
523− "description": "I D",
524− "type": "string",
525− "enum": [
526− "coder",
527− "task"
528− ]
529− },
530− "model": {
531− "description": "Model",
532− "type": "string",
533− "enum": [
534− "large",
535− "small"
536− ]
61+ "title": "Allowed Tools",
62+ "description": "List of tools this agent is allowed to use (if nil all tools are allowed)"
53763 },
538− "name": {
539− "description": "Name",
540− "type": "string"
541− }
542− },
543− "required": [
544− "id",
545− "name",
546− "disabled",
547− "model",
548− "allowed_tools",
549− "allowed_mcp",
550− "allowed_lsp",
551− "context_paths"
552− ]
553− },
554− "Config": {
555− "type": "object",
556− "properties": {
557− "agents": {
558− "description": "Agent configurations for different tasks",
559− "type": "object",
64+ "allowed_mcp": {
56065 "additionalProperties": {
561− "type": "object",
562− "properties": {
563− "allowed_lsp": {
564− "description": "List of LSP servers this agent can use (if nil, all LSPs are allowed)",
565− "type": "array",
566− "items": {
567− "type": "string"
568− }
569− },
570− "allowed_mcp": {
571− "description": "Map of MCP servers this agent can use and their allowed tools",
572− "type": "object",
573− "additionalProperties": {
574− "type": "array",
575− "items": {
576− "type": "string"
577− }
578− }
579− },
580− "allowed_tools": {
581− "description": "List of tools this agent is allowed to use (if nil, all tools are allowed)",
582− "type": "array",
583− "items": {
584− "type": "string",
585− "enum": [
586− "bash",
587− "edit",
588− "fetch",
589− "glob",
590− "grep",
591− "ls",
592− "sourcegraph",
593− "view",
594− "write",
595− "agent"
596− ]
597− }
598− },
599− "context_paths": {
600− "description": "Custom context paths for this agent (additive to global context paths)",
601− "type": "array",
602− "items": {
603− "type": "string"
604− }
605− },
606− "description": {
607− "description": "Description",
608− "type": "string"
609− },
610− "disabled": {
611− "description": "Disabled",
612− "type": "boolean",
613− "default": false
614− },
615− "id": {
616− "description": "I D",
617− "type": "string",
618− "enum": [
619− "coder",
620− "task"
621− ]
622− },
623− "model": {
624− "description": "Model",
625− "type": "string",
626− "enum": [
627− "large",
628− "small"
629− ]
630− },
631− "name": {
632− "description": "Name",
633− "type": "string"
634− }
66+ "items": {
67+ "type": "string"
63568 },
636− "required": [
637− "id",
638− "name",
639− "disabled",
640− "model",
641− "allowed_tools",
642− "allowed_mcp",
643− "allowed_lsp",
644− "context_paths"
645− ]
646− }
647− },
648− "lsp": {
649− "description": "Language Server Protocol configurations",
69+ "type": "array"
70+ },
65071 "type": "object",
651− "additionalProperties": {
652− "type": "object",
653− "properties": {
654− "args": {
655− "description": "Args",
656− "type": "array",
657− "items": {
658− "type": "string"
659− }
660− },
661− "command": {
662− "description": "Command",
663− "type": "string"
664− },
665− "enabled": {
666− "description": "Disabled",
667− "type": "boolean",
668− "default": true
669− },
670− "options": {
671− "description": "Options"
672− }
673− },
674− "required": [
675− "enabled",
676− "command",
677− "args",
678− "options"
679− ]
680− }
72+ "title": "Allowed MCP",
73+ "description": "Map of MCP servers this agent can use and their allowed tools"
68174 },
682− "mcp": {
683− "description": "Model Control Protocol server configurations",
684− "type": "object",
685− "additionalProperties": {
686− "type": "object",
687− "properties": {
688− "args": {
689− "description": "Args",
690− "type": "array",
691− "items": {
692− "type": "string"
693− }
694− },
695− "command": {
696− "description": "Command to execute for stdio MCP servers",
697− "type": "string"
698− },
699− "env": {
700− "description": "Env list",
701− "type": "array",
702− "items": {
703− "type": "string"
704− }
705− },
706− "headers": {
707− "description": "HTTP headers for SSE MCP servers",
708− "type": "object",
709− "additionalProperties": {
710− "type": "string"
711− }
712− },
713− "type": {
714− "description": "Type",
715− "type": "string",
716− "enum": [
717− "stdio",
718− "sse"
719− ],
720− "default": "stdio"
721− },
722− "url": {
723− "description": "URL for SSE MCP servers",
724− "type": "string"
725− }
726− },
727− "required": [
728− "command",
729− "env",
730− "args",
731− "type",
732− "url",
733− "headers"
734− ]
735− }
736− },
737− "models": {
738− "description": "Preferred model configurations for large and small model types",
739− "type": "object",
740− "properties": {
741− "large": {
742− "description": "Large",
743− "type": "object",
744− "properties": {
745− "max_tokens": {
746− "description": "Max Tokens",
747− "type": "integer"
748− },
749− "model_id": {
750− "description": "Model I D",
751− "type": "string",
752− "enum": [
753− "claude-3-opus",
754− "claude-3-haiku",
755− "claude-3-5-sonnet-20241022",
756− "claude-3-5-haiku-20241022",
757− "gpt-4",
758− "gpt-3.5-turbo",
759− "gpt-4-turbo",
760− "gpt-4o",
761− "gpt-4o-mini",
762− "o1-preview",
763− "o1-mini",
764− "gemini-2.5-pro",
765− "gemini-2.5-flash",
766− "grok-beta",
767− "anthropic/claude-3.5-sonnet",
768− "anthropic/claude-3.5-haiku"
769− ]
770− },
771− "provider": {
772− "description": "Provider",
773− "type": "string",
774− "enum": [
775− "anthropic",
776− "openai",
777− "gemini",
778− "xai",
779− "openrouter"
780− ]
781− },
782− "reasoning_effort": {
783− "description": "Reasoning Effort",
784− "type": "string"
785− },
786− "think": {
787− "description": "Enable think",
788− "type": "boolean"
789− }
790− },
791− "required": [
792− "model_id",
793− "provider"
794− ]
795− },
796− "small": {
797− "description": "Small",
798− "$ref": "#/definitions/PreferredModel"
799− }
75+ "allowed_lsp": {
76+ "items": {
77+ "type": "string"
80078 },
801− "required": [
802− "large",
803− "small"
804− ]
79+ "type": "array",
80+ "title": "Allowed LSP",
81+ "description": "List of LSP servers this agent can use (if nil all LSPs are allowed)"
80582 },
806− "options": {
807− "description": "General application options and settings",
808− "type": "object",
809− "properties": {
810− "context_paths": {
811− "description": "Context Paths",
812− "type": "array",
813− "items": {
814− "type": "string"
815− },
816− "default": [
817− ".github/copilot-instructions.md",
818− ".cursorrules",
819− ".cursor/rules/",
820− "CLAUDE.md",
821− "CLAUDE.local.md",
822− "GEMINI.md",
823− "gemini.md",
824− "crush.md",
825− "crush.local.md",
826− "Crush.md",
827− "Crush.local.md",
828− "CRUSH.md",
829− "CRUSH.local.md"
830− ]
831− },
832− "data_directory": {
833− "description": "Data Directory",
834− "type": "string",
835− "default": ".crush"
836− },
837− "debug": {
838− "description": "Enable debug",
839− "type": "boolean",
840− "default": false
841− },
842− "debug_lsp": {
843− "description": "Enable debug l s p",
844− "type": "boolean",
845− "default": false
846− },
847− "disable_auto_summarize": {
848− "description": "Disable Auto Summarize",
849− "type": "boolean",
850− "default": false
851− },
852− "tui": {
853− "description": "T U I",
854− "type": "object",
855− "properties": {
856− "compact_mode": {
857− "description": "Enable compact mode",
858− "type": "boolean",
859− "default": false
860− }
861− },
862− "required": [
863− "compact_mode"
864− ]
865− }
83+ "context_paths": {
84+ "items": {
85+ "type": "string"
86686 },
867− "required": [
868− "context_paths",
869− "tui",
870− "debug",
871− "debug_lsp",
872− "disable_auto_summarize",
873− "data_directory"
874− ]
875− },
876− "providers": {
877− "description": "LLM provider configurations",
878− "type": "object",
879− "additionalProperties": {
880− "type": "object",
881− "properties": {
882− "api_key": {
883− "description": "API key for authenticating with the provider",
884− "type": "string"
885− },
886− "base_url": {
887− "description": "Base URL for the provider API (required for custom providers)",
888− "type": "string"
889− },
890− "default_large_model": {
891− "description": "Default Large Model",
892− "type": "string"
893− },
894− "default_small_model": {
895− "description": "Default Small Model",
896− "type": "string"
897− },
898− "disabled": {
899− "description": "Disabled",
900− "type": "boolean",
901− "default": false
902− },
903− "extra_headers": {
904− "description": "Additional HTTP headers to send with requests",
905− "type": "object",
906− "additionalProperties": {
907− "type": "string"
908− }
909− },
910− "extra_params": {
911− "description": "Additional provider-specific parameters",
912− "type": "object",
913− "additionalProperties": {
914− "type": "string"
915− }
916− },
917− "id": {
918− "description": "I D",
919− "type": "string",
920− "enum": [
921− "anthropic",
922− "openai",
923− "gemini",
924− "xai",
925− "openrouter"
926− ]
927− },
928− "models": {
929− "description": "Models",
930− "type": "array",
931− "items": {
932− "type": "object",
933− "properties": {
934− "can_reason": {
935− "description": "Enable can reason",
936− "type": "boolean"
937− },
938− "context_window": {
939− "description": "Context Window",
940− "type": "integer"
941− },
942− "cost_per_1m_in": {
943− "description": "Cost Per1 M In",
944− "type": "number"
945− },
946− "cost_per_1m_in_cached": {
947− "description": "Cost Per1 M In Cached",
948− "type": "number"
949− },
950− "cost_per_1m_out": {
951− "description": "Cost Per1 M Out",
952− "type": "number"
953− },
954− "cost_per_1m_out_cached": {
955− "description": "Cost Per1 M Out Cached",
956− "type": "number"
957− },
958− "default_max_tokens": {
959− "description": "Default Max Tokens",
960− "type": "integer"
961− },
962− "has_reasoning_effort": {
963− "description": "Enable has reasoning effort",
964− "type": "boolean"
965− },
966− "id": {
967− "description": "I D",
968− "type": "string"
969− },
970− "model": {
971− "description": "Name",
972− "type": "string"
973− },
974− "reasoning_effort": {
975− "description": "Reasoning Effort",
976− "type": "string"
977− },
978− "supports_attachments": {
979− "description": "Enable supports images",
980− "type": "boolean"
981− }
982− },
983− "required": [
984− "id",
985− "model",
986− "cost_per_1m_in",
987− "cost_per_1m_out",
988− "cost_per_1m_in_cached",
989− "cost_per_1m_out_cached",
990− "context_window",
991− "default_max_tokens",
992− "can_reason",
993− "reasoning_effort",
994− "has_reasoning_effort",
995− "supports_attachments"
996− ]
997− }
998− },
999− "provider_type": {
1000− "description": "Provider Type",
1001− "type": "string"
1002− }
1003− },
1004− "required": [
1005− "id",
1006− "provider_type",
1007− "disabled"
1008− ]
1009− }
87+ "type": "array",
88+ "title": "Context Paths",
89+ "description": "Custom context paths for this agent (additive to global context paths)"
101090 }
101191 },
92+ "type": "object",
101293 "required": [
1013− "models",
1014− "options"
94+ "model"
101595 ]
101696 },
101797 "LSPConfig": {
1018− "type": "object",
101998 "properties": {
1020− "args": {
1021− "description": "Args",
1022− "type": "array",
1023− "items": {
1024− "type": "string"
1025− }
1026− },
1027− "command": {
1028− "description": "Command",
1029− "type": "string"
1030− },
103199 "enabled": {
1032− "description": "Disabled",
1033100 "type": "boolean",
101+ "title": "Enabled",
102+ "description": "Whether this LSP server is enabled",
1034103 "default": true
1035104 },
105+ "command": {
106+ "type": "string",
107+ "title": "Command",
108+ "description": "Command to execute for the LSP server"
109+ },
110+ "args": {
111+ "items": {
112+ "type": "string"
113+ },
114+ "type": "array",
115+ "title": "Arguments",
116+ "description": "Command line arguments for the LSP server"
117+ },
1036118 "options": {
1037− "description": "Options"
119+ "title": "Options",
120+ "description": "LSP server specific options"
1038121 }
1039122 },
123+ "type": "object",
1040124 "required": [
1041− "enabled",
1042− "command",
1043− "args",
1044− "options"
125+ "command"
1045126 ]
1046127 },
1047128 "MCP": {
1048− "type": "object",
1049129 "properties": {
1050− "args": {
1051− "description": "Args",
1052− "type": "array",
1053− "items": {
1054− "type": "string"
1055− }
1056− },
1057130 "command": {
1058− "description": "Command to execute for stdio MCP servers",
1059− "type": "string"
131+ "type": "string",
132+ "title": "Command",
133+ "description": "Command to execute for stdio MCP servers"
1060134 },
1061135 "env": {
1062− "description": "Env list",
1063− "type": "array",
1064136 "items": {
1065137 "type": "string"
1066− }
138+ },
139+ "type": "array",
140+ "title": "Environment",
141+ "description": "Environment variables for the MCP server"
1067142 },
1068− "headers": {
1069− "description": "HTTP headers for SSE MCP servers",
1070− "type": "object",
1071− "additionalProperties": {
143+ "args": {
144+ "items": {
1072145 "type": "string"
1073− }
146+ },
147+ "type": "array",
148+ "title": "Arguments",
149+ "description": "Command line arguments for the MCP server"
1074150 },
1075151 "type": {
1076− "description": "Type",
1077152 "type": "string",
1078153 "enum": [
154+ "stdio",
155+ "sse",
1079156 "stdio",
1080157 "sse"
1081158 ],
159+ "title": "Type",
160+ "description": "Type of MCP connection",
1082161 "default": "stdio"
1083162 },
1084163 "url": {
1085− "description": "URL for SSE MCP servers",
1086− "type": "string"
164+ "type": "string",
165+ "title": "URL",
166+ "description": "URL for SSE MCP servers"
167+ },
168+ "headers": {
169+ "additionalProperties": {
170+ "type": "string"
171+ },
172+ "type": "object",
173+ "title": "Headers",
174+ "description": "HTTP headers for SSE MCP servers"
1087175 }
1088176 },
177+ "type": "object",
1089178 "required": [
1090179 "command",
1091− "env",
1092− "args",
1093− "type",
1094− "url",
1095− "headers"
180+ "type"
1096181 ]
1097182 },
1098183 "Model": {
1099− "type": "object",
1100184 "properties": {
1101− "can_reason": {
1102− "description": "Enable can reason",
1103− "type": "boolean"
185+ "id": {
186+ "type": "string",
187+ "title": "Model ID",
188+ "description": "Unique identifier for the model"
1104189 },
1105− "context_window": {
1106− "description": "Context Window",
1107− "type": "integer"
190+ "model": {
191+ "type": "string",
192+ "title": "Model Name",
193+ "description": "Display name of the model"
1108194 },
1109195 "cost_per_1m_in": {
1110− "description": "Cost Per1 M In",
1111− "type": "number"
1112− },
1113− "cost_per_1m_in_cached": {
1114− "description": "Cost Per1 M In Cached",
1115− "type": "number"
196+ "type": "number",
197+ "minimum": 0,
198+ "title": "Input Cost",
199+ "description": "Cost per 1 million input tokens"
1116200 },
1117201 "cost_per_1m_out": {
1118− "description": "Cost Per1 M Out",
1119− "type": "number"
202+ "type": "number",
203+ "minimum": 0,
204+ "title": "Output Cost",
205+ "description": "Cost per 1 million output tokens"
1120206 },
1121− "cost_per_1m_out_cached": {
1122− "description": "Cost Per1 M Out Cached",
1123− "type": "number"
207+ "cost_per_1m_in_cached": {
208+ "type": "number",
209+ "minimum": 0,
210+ "title": "Cached Input Cost",
211+ "description": "Cost per 1 million cached input tokens"
1124212 },
1125− "default_max_tokens": {
1126− "description": "Default Max Tokens",
1127− "type": "integer"
213+ "cost_per_1m_out_cached": {
214+ "type": "number",
215+ "minimum": 0,
216+ "title": "Cached Output Cost",
217+ "description": "Cost per 1 million cached output tokens"
1128218 },
1129− "has_reasoning_effort": {
1130− "description": "Enable has reasoning effort",
1131− "type": "boolean"
219+ "context_window": {
220+ "type": "integer",
221+ "minimum": 1,
222+ "title": "Context Window",
223+ "description": "Maximum context window size in tokens"
1132224 },
1133− "id": {
1134− "description": "I D",
1135− "type": "string"
225+ "default_max_tokens": {
226+ "type": "integer",
227+ "minimum": 1,
228+ "title": "Default Max Tokens",
229+ "description": "Default maximum tokens for responses"
1136230 },
1137− "model": {
1138− "description": "Name",
1139− "type": "string"
231+ "can_reason": {
232+ "type": "boolean",
233+ "title": "Can Reason",
234+ "description": "Whether the model supports reasoning capabilities"
1140235 },
1141236 "reasoning_effort": {
1142− "description": "Reasoning Effort",
1143− "type": "string"
237+ "type": "string",
238+ "title": "Reasoning Effort",
239+ "description": "Default reasoning effort level for reasoning models"
240+ },
241+ "has_reasoning_effort": {
242+ "type": "boolean",
243+ "title": "Has Reasoning Effort",
244+ "description": "Whether the model supports reasoning effort configuration"
1144245 },
1145246 "supports_attachments": {
1146− "description": "Enable supports images",
1147− "type": "boolean"
247+ "type": "boolean",
248+ "title": "Supports Images",
249+ "description": "Whether the model supports image attachments"
1148250 }
1149251 },
252+ "type": "object",
1150253 "required": [
1151254 "id",
1152255 "model",
@@ -1163,15 +266,14 @@
1163266 ]
1164267 },
1165268 "Options": {
1166− "description": "General application options and settings",
1167− "type": "object",
1168269 "properties": {
1169270 "context_paths": {
1170− "description": "Context Paths",
1171− "type": "array",
1172271 "items": {
1173272 "type": "string"
1174273 },
274+ "type": "array",
275+ "title": "Context Paths",
276+ "description": "List of paths to search for context files",
1175277 "default": [
1176278 ".github/copilot-instructions.md",
1177279 ".cursorrules",
@@ -1188,318 +290,419 @@
1188290 "CRUSH.local.md"
1189291 ]
1190292 },
1191− "data_directory": {
1192− "description": "Data Directory",
1193− "type": "string",
1194− "default": ".crush"
293+ "tui": {
294+ "$ref": "#/$defs/TUIOptions",
295+ "title": "TUI Options",
296+ "description": "Terminal UI configuration options"
1195297 },
1196298 "debug": {
1197− "description": "Enable debug",
1198299 "type": "boolean",
300+ "title": "Debug",
301+ "description": "Enable debug logging",
1199302 "default": false
1200303 },
1201304 "debug_lsp": {
1202− "description": "Enable debug l s p",
1203305 "type": "boolean",
306+ "title": "Debug LSP",
307+ "description": "Enable LSP debug logging",
1204308 "default": false
1205309 },
1206310 "disable_auto_summarize": {
1207− "description": "Disable Auto Summarize",
1208311 "type": "boolean",
312+ "title": "Disable Auto Summarize",
313+ "description": "Disable automatic conversation summarization",
1209314 "default": false
1210315 },
1211− "tui": {
1212− "description": "T U I",
1213− "type": "object",
1214− "properties": {
1215− "compact_mode": {
1216− "description": "Enable compact mode",
1217− "type": "boolean",
1218− "default": false
1219− }
1220− },
1221− "required": [
1222− "compact_mode"
1223− ]
316+ "data_directory": {
317+ "type": "string",
318+ "title": "Data Directory",
319+ "description": "Directory for storing application data",
320+ "default": ".crush"
1224321 }
1225322 },
1226− "required": [
1227− "context_paths",
1228− "tui",
1229− "debug",
1230− "debug_lsp",
1231− "disable_auto_summarize",
1232− "data_directory"
1233− ]
323+ "type": "object"
1234324 },
1235325 "PreferredModel": {
1236− "description": "Large",
1237− "type": "object",
1238326 "properties": {
1239− "max_tokens": {
1240− "description": "Max Tokens",
1241− "type": "integer"
1242− },
1243327 "model_id": {
1244− "description": "Model I D",
1245328 "type": "string",
1246329 "enum": [
1247− "claude-3-opus",
1248− "claude-3-haiku",
1249− "claude-3-5-sonnet-20241022",
330+ "claude-opus-4-20250514",
331+ "claude-sonnet-4-20250514",
332+ "claude-3-7-sonnet-20250219",
1250333 "claude-3-5-haiku-20241022",
1251− "gpt-4",
1252− "gpt-3.5-turbo",
1253− "gpt-4-turbo",
334+ "claude-3-5-sonnet-20240620",
335+ "claude-3-5-sonnet-20241022",
336+ "codex-mini-latest",
337+ "o4-mini",
338+ "o3",
339+ "o3-pro",
340+ "gpt-4.1",
341+ "gpt-4.1-mini",
342+ "gpt-4.1-nano",
343+ "gpt-4.5-preview",
344+ "o3-mini",
345+ "gpt-4o",
346+ "gpt-4o-mini",
347+ "gemini-2.5-pro",
348+ "gemini-2.5-flash",
349+ "codex-mini-latest",
350+ "o4-mini",
351+ "o3",
352+ "o3-pro",
353+ "gpt-4.1",
354+ "gpt-4.1-mini",
355+ "gpt-4.1-nano",
356+ "gpt-4.5-preview",
357+ "o3-mini",
1254358 "gpt-4o",
1255359 "gpt-4o-mini",
1256− "o1-preview",
1257− "o1-mini",
360+ "anthropic.claude-opus-4-20250514-v1:0",
361+ "anthropic.claude-sonnet-4-20250514-v1:0",
362+ "anthropic.claude-3-7-sonnet-20250219-v1:0",
363+ "anthropic.claude-3-5-haiku-20241022-v1:0",
1258364 "gemini-2.5-pro",
1259365 "gemini-2.5-flash",
1260− "grok-beta",
366+ "grok-3-mini",
367+ "grok-3",
368+ "mistralai/mistral-small-3.2-24b-instruct:free",
369+ "mistralai/mistral-small-3.2-24b-instruct",
370+ "minimax/minimax-m1:extended",
371+ "minimax/minimax-m1",
372+ "google/gemini-2.5-flash-lite-preview-06-17",
373+ "google/gemini-2.5-flash",
374+ "google/gemini-2.5-pro",
375+ "openai/o3-pro",
376+ "x-ai/grok-3-mini",
377+ "x-ai/grok-3",
378+ "mistralai/magistral-small-2506",
379+ "mistralai/magistral-medium-2506",
380+ "mistralai/magistral-medium-2506:thinking",
381+ "google/gemini-2.5-pro-preview",
382+ "deepseek/deepseek-r1-0528",
383+ "anthropic/claude-opus-4",
384+ "anthropic/claude-sonnet-4",
385+ "mistralai/devstral-small:free",
386+ "mistralai/devstral-small",
387+ "google/gemini-2.5-flash-preview-05-20",
388+ "google/gemini-2.5-flash-preview-05-20:thinking",
389+ "openai/codex-mini",
390+ "mistralai/mistral-medium-3",
391+ "google/gemini-2.5-pro-preview-05-06",
392+ "arcee-ai/caller-large",
393+ "arcee-ai/virtuoso-large",
394+ "arcee-ai/virtuoso-medium-v2",
395+ "qwen/qwen3-30b-a3b",
396+ "qwen/qwen3-14b",
397+ "qwen/qwen3-32b",
398+ "qwen/qwen3-235b-a22b",
399+ "google/gemini-2.5-flash-preview",
400+ "google/gemini-2.5-flash-preview:thinking",
401+ "openai/o4-mini-high",
402+ "openai/o3",
403+ "openai/o4-mini",
404+ "openai/gpt-4.1",
405+ "openai/gpt-4.1-mini",
406+ "openai/gpt-4.1-nano",
407+ "x-ai/grok-3-mini-beta",
408+ "x-ai/grok-3-beta",
409+ "meta-llama/llama-4-maverick",
410+ "meta-llama/llama-4-scout",
411+ "all-hands/openhands-lm-32b-v0.1",
412+ "google/gemini-2.5-pro-exp-03-25",
413+ "deepseek/deepseek-chat-v3-0324:free",
414+ "deepseek/deepseek-chat-v3-0324",
415+ "mistralai/mistral-small-3.1-24b-instruct:free",
416+ "mistralai/mistral-small-3.1-24b-instruct",
417+ "ai21/jamba-1.6-large",
418+ "ai21/jamba-1.6-mini",
419+ "openai/gpt-4.5-preview",
420+ "google/gemini-2.0-flash-lite-001",
421+ "anthropic/claude-3.7-sonnet",
422+ "anthropic/claude-3.7-sonnet:beta",
423+ "anthropic/claude-3.7-sonnet:thinking",
424+ "mistralai/mistral-saba",
425+ "openai/o3-mini-high",
426+ "google/gemini-2.0-flash-001",
427+ "qwen/qwen-turbo",
428+ "qwen/qwen-plus",
429+ "qwen/qwen-max",
430+ "openai/o3-mini",
431+ "mistralai/mistral-small-24b-instruct-2501",
432+ "deepseek/deepseek-r1-distill-llama-70b",
433+ "deepseek/deepseek-r1",
434+ "mistralai/codestral-2501",
435+ "deepseek/deepseek-chat",
436+ "openai/o1",
437+ "x-ai/grok-2-1212",
438+ "meta-llama/llama-3.3-70b-instruct",
439+ "amazon/nova-lite-v1",
440+ "amazon/nova-micro-v1",
441+ "amazon/nova-pro-v1",
442+ "openai/gpt-4o-2024-11-20",
443+ "mistralai/mistral-large-2411",
444+ "mistralai/mistral-large-2407",
445+ "mistralai/pixtral-large-2411",
446+ "thedrummer/unslopnemo-12b",
447+ "anthropic/claude-3.5-haiku:beta",
448+ "anthropic/claude-3.5-haiku",
449+ "anthropic/claude-3.5-haiku-20241022:beta",
450+ "anthropic/claude-3.5-haiku-20241022",
451+ "anthropic/claude-3.5-sonnet:beta",
1261452 "anthropic/claude-3.5-sonnet",
1262− "anthropic/claude-3.5-haiku"
1263− ]
453+ "x-ai/grok-beta",
454+ "mistralai/ministral-8b",
455+ "mistralai/ministral-3b",
456+ "nvidia/llama-3.1-nemotron-70b-instruct",
457+ "google/gemini-flash-1.5-8b",
458+ "meta-llama/llama-3.2-11b-vision-instruct",
459+ "meta-llama/llama-3.2-3b-instruct",
460+ "qwen/qwen-2.5-72b-instruct",
461+ "mistralai/pixtral-12b",
462+ "cohere/command-r-plus-08-2024",
463+ "cohere/command-r-08-2024",
464+ "microsoft/phi-3.5-mini-128k-instruct",
465+ "nousresearch/hermes-3-llama-3.1-70b",
466+ "openai/gpt-4o-2024-08-06",
467+ "meta-llama/llama-3.1-405b-instruct",
468+ "meta-llama/llama-3.1-70b-instruct",
469+ "meta-llama/llama-3.1-8b-instruct",
470+ "mistralai/mistral-nemo",
471+ "openai/gpt-4o-mini",
472+ "openai/gpt-4o-mini-2024-07-18",
473+ "anthropic/claude-3.5-sonnet-20240620:beta",
474+ "anthropic/claude-3.5-sonnet-20240620",
475+ "mistralai/mistral-7b-instruct-v0.3",
476+ "mistralai/mistral-7b-instruct:free",
477+ "mistralai/mistral-7b-instruct",
478+ "microsoft/phi-3-mini-128k-instruct",
479+ "microsoft/phi-3-medium-128k-instruct",
480+ "google/gemini-flash-1.5",
481+ "openai/gpt-4o-2024-05-13",
482+ "openai/gpt-4o",
483+ "openai/gpt-4o:extended",
484+ "meta-llama/llama-3-8b-instruct",
485+ "meta-llama/llama-3-70b-instruct",
486+ "mistralai/mixtral-8x22b-instruct",
487+ "openai/gpt-4-turbo",
488+ "google/gemini-pro-1.5",
489+ "cohere/command-r-plus",
490+ "cohere/command-r-plus-04-2024",
491+ "cohere/command-r",
492+ "anthropic/claude-3-haiku:beta",
493+ "anthropic/claude-3-haiku",
494+ "anthropic/claude-3-opus:beta",
495+ "anthropic/claude-3-opus",
496+ "anthropic/claude-3-sonnet:beta",
497+ "anthropic/claude-3-sonnet",
498+ "cohere/command-r-03-2024",
499+ "mistralai/mistral-large",
500+ "openai/gpt-3.5-turbo-0613",
501+ "openai/gpt-4-turbo-preview",
502+ "mistralai/mistral-small",
503+ "mistralai/mistral-tiny",
504+ "mistralai/mixtral-8x7b-instruct",
505+ "openai/gpt-4-1106-preview",
506+ "mistralai/mistral-7b-instruct-v0.1",
507+ "openai/gpt-3.5-turbo-16k",
508+ "openai/gpt-4",
509+ "openai/gpt-4-0314"
510+ ],
511+ "title": "Model ID",
512+ "description": "ID of the preferred model"
1264513 },
1265514 "provider": {
1266− "description": "Provider",
1267515 "type": "string",
1268516 "enum": [
1269517 "anthropic",
1270518 "openai",
1271519 "gemini",
520+ "azure",
521+ "bedrock",
522+ "vertex",
1272523 "xai",
1273524 "openrouter"
1274− ]
525+ ],
526+ "title": "Provider",
527+ "description": "Provider for the preferred model"
1275528 },
1276529 "reasoning_effort": {
1277− "description": "Reasoning Effort",
1278− "type": "string"
530+ "type": "string",
531+ "title": "Reasoning Effort",
532+ "description": "Override reasoning effort for this model"
533+ },
534+ "max_tokens": {
535+ "type": "integer",
536+ "minimum": 1,
537+ "title": "Max Tokens",
538+ "description": "Override max tokens for this model"
1279539 },
1280540 "think": {
1281− "description": "Enable think",
1282− "type": "boolean"
541+ "type": "boolean",
542+ "title": "Think",
543+ "description": "Enable thinking for reasoning models",
544+ "default": false
1283545 }
1284546 },
547+ "type": "object",
1285548 "required": [
1286549 "model_id",
1287550 "provider"
1288551 ]
1289552 },
1290553 "PreferredModels": {
1291− "description": "Preferred model configurations for large and small model types",
1292− "type": "object",
1293554 "properties": {
1294555 "large": {
1295− "description": "Large",
1296− "type": "object",
1297− "properties": {
1298− "max_tokens": {
1299− "description": "Max Tokens",
1300− "type": "integer"
1301− },
1302− "model_id": {
1303− "description": "Model I D",
1304− "type": "string",
1305− "enum": [
1306− "claude-3-opus",
1307− "claude-3-haiku",
1308− "claude-3-5-sonnet-20241022",
1309− "claude-3-5-haiku-20241022",
1310− "gpt-4",
1311− "gpt-3.5-turbo",
1312− "gpt-4-turbo",
1313− "gpt-4o",
1314− "gpt-4o-mini",
1315− "o1-preview",
1316− "o1-mini",
1317− "gemini-2.5-pro",
1318− "gemini-2.5-flash",
1319− "grok-beta",
1320− "anthropic/claude-3.5-sonnet",
1321− "anthropic/claude-3.5-haiku"
1322− ]
1323− },
1324− "provider": {
1325− "description": "Provider",
1326− "type": "string",
1327− "enum": [
1328− "anthropic",
1329− "openai",
1330− "gemini",
1331− "xai",
1332− "openrouter"
1333− ]
1334− },
1335− "reasoning_effort": {
1336− "description": "Reasoning Effort",
1337− "type": "string"
1338− },
1339− "think": {
1340− "description": "Enable think",
1341− "type": "boolean"
1342− }
1343− },
1344− "required": [
1345− "model_id",
1346− "provider"
1347− ]
556+ "$ref": "#/$defs/PreferredModel",
557+ "title": "Large Model",
558+ "description": "Preferred model configuration for large model type"
1348559 },
1349560 "small": {
1350− "description": "Small",
1351− "$ref": "#/definitions/PreferredModel"
561+ "$ref": "#/$defs/PreferredModel",
562+ "title": "Small Model",
563+ "description": "Preferred model configuration for small model type"
1352564 }
1353565 },
1354− "required": [
1355− "large",
1356− "small"
1357− ]
566+ "type": "object"
1358567 },
1359568 "ProviderConfig": {
1360− "type": "object",
1361569 "properties": {
1362− "api_key": {
1363− "description": "API key for authenticating with the provider",
1364− "type": "string"
570+ "id": {
571+ "type": "string",
572+ "enum": [
573+ "anthropic",
574+ "openai",
575+ "gemini",
576+ "azure",
577+ "bedrock",
578+ "vertex",
579+ "xai",
580+ "openrouter"
581+ ],
582+ "title": "Provider ID",
583+ "description": "Unique identifier for the provider"
1365584 },
1366585 "base_url": {
1367− "description": "Base URL for the provider API (required for custom providers)",
1368− "type": "string"
586+ "type": "string",
587+ "title": "Base URL",
588+ "description": "Base URL for the provider API (required for custom providers)"
1369589 },
1370− "default_large_model": {
1371− "description": "Default Large Model",
1372− "type": "string"
590+ "provider_type": {
591+ "type": "string",
592+ "title": "Provider Type",
593+ "description": "Type of the provider (openai"
1373594 },
1374− "default_small_model": {
1375− "description": "Default Small Model",
1376− "type": "string"
595+ "api_key": {
596+ "type": "string",
597+ "title": "API Key",
598+ "description": "API key for authenticating with the provider"
1377599 },
1378600 "disabled": {
1379− "description": "Disabled",
1380601 "type": "boolean",
602+ "title": "Disabled",
603+ "description": "Whether this provider is disabled",
1381604 "default": false
1382605 },
1383606 "extra_headers": {
1384− "description": "Additional HTTP headers to send with requests",
1385− "type": "object",
1386607 "additionalProperties": {
1387608 "type": "string"
1388− }
609+ },
610+ "type": "object",
611+ "title": "Extra Headers",
612+ "description": "Additional HTTP headers to send with requests"
1389613 },
1390614 "extra_params": {
1391− "description": "Additional provider-specific parameters",
1392− "type": "object",
1393615 "additionalProperties": {
1394616 "type": "string"
1395− }
617+ },
618+ "type": "object",
619+ "title": "Extra Parameters",
620+ "description": "Additional provider-specific parameters"
1396621 },
1397− "id": {
1398− "description": "I D",
622+ "default_large_model": {
1399623 "type": "string",
1400− "enum": [
1401− "anthropic",
1402− "openai",
1403− "gemini",
1404− "xai",
1405− "openrouter"
1406− ]
624+ "title": "Default Large Model",
625+ "description": "Default model ID for large model type"
626+ },
627+ "default_small_model": {
628+ "type": "string",
629+ "title": "Default Small Model",
630+ "description": "Default model ID for small model type"
1407631 },
1408632 "models": {
1409− "description": "Models",
1410− "type": "array",
1411633 "items": {
1412− "type": "object",
1413− "properties": {
1414− "can_reason": {
1415− "description": "Enable can reason",
1416− "type": "boolean"
1417− },
1418− "context_window": {
1419− "description": "Context Window",
1420− "type": "integer"
1421− },
1422− "cost_per_1m_in": {
1423− "description": "Cost Per1 M In",
1424− "type": "number"
1425− },
1426− "cost_per_1m_in_cached": {
1427− "description": "Cost Per1 M In Cached",
1428− "type": "number"
1429− },
1430− "cost_per_1m_out": {
1431− "description": "Cost Per1 M Out",
1432− "type": "number"
1433− },
1434− "cost_per_1m_out_cached": {
1435− "description": "Cost Per1 M Out Cached",
1436− "type": "number"
1437− },
1438− "default_max_tokens": {
1439− "description": "Default Max Tokens",
1440− "type": "integer"
1441− },
1442− "has_reasoning_effort": {
1443− "description": "Enable has reasoning effort",
1444− "type": "boolean"
1445− },
1446− "id": {
1447− "description": "I D",
1448− "type": "string"
1449− },
1450− "model": {
1451− "description": "Name",
1452− "type": "string"
1453− },
1454− "reasoning_effort": {
1455− "description": "Reasoning Effort",
1456− "type": "string"
1457− },
1458− "supports_attachments": {
1459− "description": "Enable supports images",
1460− "type": "boolean"
1461− }
1462− },
1463− "required": [
1464− "id",
1465− "model",
1466− "cost_per_1m_in",
1467− "cost_per_1m_out",
1468− "cost_per_1m_in_cached",
1469− "cost_per_1m_out_cached",
1470− "context_window",
1471− "default_max_tokens",
1472− "can_reason",
1473− "reasoning_effort",
1474− "has_reasoning_effort",
1475− "supports_attachments"
1476− ]
1477− }
1478− },
1479− "provider_type": {
1480− "description": "Provider Type",
1481− "type": "string"
634+ "$ref": "#/$defs/Model"
635+ },
636+ "type": "array",
637+ "title": "Models",
638+ "description": "List of available models for this provider"
1482639 }
1483640 },
641+ "type": "object",
1484642 "required": [
1485− "id",
1486− "provider_type",
1487− "disabled"
643+ "provider_type"
1488644 ]
1489645 },
1490646 "TUIOptions": {
1491− "description": "T U I",
1492− "type": "object",
1493647 "properties": {
1494648 "compact_mode": {
1495− "description": "Enable compact mode",
1496649 "type": "boolean",
650+ "title": "Compact Mode",
651+ "description": "Enable compact mode for the TUI",
1497652 "default": false
1498653 }
1499654 },
655+ "type": "object",
1500656 "required": [
1501657 "compact_mode"
1502658 ]
1503659 }
1504− }
660+ },
661+ "properties": {
662+ "models": {
663+ "$ref": "#/$defs/PreferredModels",
664+ "title": "Models",
665+ "description": "Preferred model configurations for large and small model types"
666+ },
667+ "providers": {
668+ "additionalProperties": {
669+ "$ref": "#/$defs/ProviderConfig"
670+ },
671+ "type": "object",
672+ "title": "Providers",
673+ "description": "LLM provider configurations"
674+ },
675+ "agents": {
676+ "additionalProperties": {
677+ "$ref": "#/$defs/Agent"
678+ },
679+ "type": "object",
680+ "title": "Agents",
681+ "description": "Agent configurations for different tasks"
682+ },
683+ "mcp": {
684+ "additionalProperties": {
685+ "$ref": "#/$defs/MCP"
686+ },
687+ "type": "object",
688+ "title": "MCP",
689+ "description": "Model Control Protocol server configurations"
690+ },
691+ "lsp": {
692+ "additionalProperties": {
693+ "$ref": "#/$defs/LSPConfig"
694+ },
695+ "type": "object",
696+ "title": "LSP",
697+ "description": "Language Server Protocol configurations"
698+ },
699+ "options": {
700+ "$ref": "#/$defs/Options",
701+ "title": "Options",
702+ "description": "General application options and settings"
703+ }
704+ },
705+ "type": "object",
706+ "title": "Crush Configuration",
707+ "description": "Configuration schema for the Crush application"
1505708}
crush.json+2−1
@@ -1,6 +1,7 @@
11{
2+ "$schema": "./crush-schema.json",
23 "lsp": {
3− "Go": {
4+ "go": {
45 "command": "gopls"
56 }
67 }
cspell.json+1−1
@@ -1 +1 @@
1−{"language":"en","flagWords":[],"version":"0.2","words":["afero","alecthomas","bubbletea","charmbracelet","charmtone","Charple","crush","diffview","Emph","filepicker","Focusable","fsext","GROQ","Guac","imageorient","Lanczos","lipgloss","lsps","lucasb","nfnt","oksvg","Preproc","rasterx","rivo","Sourcegraph","srwiley","Strikethrough","termenv","textinput","trashhalo","uniseg","Unticked","genai"]}
1+{"flagWords":[],"words":["afero","alecthomas","bubbletea","charmbracelet","charmtone","Charple","crush","diffview","Emph","filepicker","Focusable","fsext","GROQ","Guac","imageorient","Lanczos","lipgloss","lsps","lucasb","nfnt","oksvg","Preproc","rasterx","rivo","Sourcegraph","srwiley","Strikethrough","termenv","textinput","trashhalo","uniseg","Unticked","genai","jsonschema"],"version":"0.2","language":"en"}
go.mod+8
@@ -39,6 +39,14 @@ require (
3939 mvdan.cc/sh/v3 v3.11.0
4040)
4141
42+require (
43+ github.com/bahlo/generic-list-go v0.2.0 // indirect
44+ github.com/buger/jsonparser v1.1.1 // indirect
45+ github.com/invopop/jsonschema v0.13.0 // indirect
46+ github.com/mailru/easyjson v0.7.7 // indirect
47+ github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
48+)
49+
4250require (
4351 cloud.google.com/go v0.116.0 // indirect
4452 cloud.google.com/go/auth v0.13.0 // indirect
go.sum+11
@@ -64,8 +64,12 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v
6464github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
6565github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
6666github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
67+github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
68+github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
6769github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
6870github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
71+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
72+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
6973github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8=
7074github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
7175github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250607113720-eb5e1cf3b09e h1:99Ugtt633rqauFsXjZobZmtkNpeaWialfj8dl6COC6A=
@@ -147,6 +151,9 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
147151github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
148152github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
149153github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
154+github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
155+github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
156+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
150157github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
151158github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
152159github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -158,6 +165,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
158165github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
159166github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
160167github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
168+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
169+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
161170github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930=
162171github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
163172github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -243,6 +252,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
243252github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
244253github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
245254github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
255+github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
256+github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
246257github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
247258github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
248259github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
internal/config/config.go+103−84
@@ -14,6 +14,7 @@ import (
1414
1515 "github.com/charmbracelet/crush/internal/fur/provider"
1616 "github.com/charmbracelet/crush/internal/logging"
17+ "github.com/invopop/jsonschema"
1718)
1819
1920const (
@@ -55,18 +56,18 @@ const (
5556)
5657
5758type Model struct {
58− ID string `json:"id"`
59− Name string `json:"model"`
60− CostPer1MIn float64 `json:"cost_per_1m_in"`
61− CostPer1MOut float64 `json:"cost_per_1m_out"`
62− CostPer1MInCached float64 `json:"cost_per_1m_in_cached"`
63− CostPer1MOutCached float64 `json:"cost_per_1m_out_cached"`
64− ContextWindow int64 `json:"context_window"`
65− DefaultMaxTokens int64 `json:"default_max_tokens"`
66− CanReason bool `json:"can_reason"`
67− ReasoningEffort string `json:"reasoning_effort"`
68− HasReasoningEffort bool `json:"has_reasoning_effort"`
69− SupportsImages bool `json:"supports_attachments"`
59+ ID string `json:"id" jsonschema:"title=Model ID,description=Unique identifier for the model"`
60+ Name string `json:"model" jsonschema:"title=Model Name,description=Display name of the model"`
61+ CostPer1MIn float64 `json:"cost_per_1m_in" jsonschema:"title=Input Cost,description=Cost per 1 million input tokens,minimum=0"`
62+ CostPer1MOut float64 `json:"cost_per_1m_out" jsonschema:"title=Output Cost,description=Cost per 1 million output tokens,minimum=0"`
63+ CostPer1MInCached float64 `json:"cost_per_1m_in_cached" jsonschema:"title=Cached Input Cost,description=Cost per 1 million cached input tokens,minimum=0"`
64+ CostPer1MOutCached float64 `json:"cost_per_1m_out_cached" jsonschema:"title=Cached Output Cost,description=Cost per 1 million cached output tokens,minimum=0"`
65+ ContextWindow int64 `json:"context_window" jsonschema:"title=Context Window,description=Maximum context window size in tokens,minimum=1"`
66+ DefaultMaxTokens int64 `json:"default_max_tokens" jsonschema:"title=Default Max Tokens,description=Default maximum tokens for responses,minimum=1"`
67+ CanReason bool `json:"can_reason" jsonschema:"title=Can Reason,description=Whether the model supports reasoning capabilities"`
68+ ReasoningEffort string `json:"reasoning_effort" jsonschema:"title=Reasoning Effort,description=Default reasoning effort level for reasoning models"`
69+ HasReasoningEffort bool `json:"has_reasoning_effort" jsonschema:"title=Has Reasoning Effort,description=Whether the model supports reasoning effort configuration"`
70+ SupportsImages bool `json:"supports_attachments" jsonschema:"title=Supports Images,description=Whether the model supports image attachments"`
7071}
7172
7273type VertexAIOptions struct {
@@ -76,46 +77,46 @@ type VertexAIOptions struct {
7677}
7778
7879type ProviderConfig struct {
79− ID provider.InferenceProvider `json:"id"`
80− BaseURL string `json:"base_url,omitempty"`
81− ProviderType provider.Type `json:"provider_type"`
82− APIKey string `json:"api_key,omitempty"`
83− Disabled bool `json:"disabled"`
84− ExtraHeaders map[string]string `json:"extra_headers,omitempty"`
80+ ID provider.InferenceProvider `json:"id,omitempty" jsonschema:"title=Provider ID,description=Unique identifier for the provider"`
81+ BaseURL string `json:"base_url,omitempty" jsonschema:"title=Base URL,description=Base URL for the provider API (required for custom providers)"`
82+ ProviderType provider.Type `json:"provider_type" jsonschema:"title=Provider Type,description=Type of the provider (openai, anthropic, etc.)"`
83+ APIKey string `json:"api_key,omitempty" jsonschema:"title=API Key,description=API key for authenticating with the provider"`
84+ Disabled bool `json:"disabled,omitempty" jsonschema:"title=Disabled,description=Whether this provider is disabled,default=false"`
85+ ExtraHeaders map[string]string `json:"extra_headers,omitempty" jsonschema:"title=Extra Headers,description=Additional HTTP headers to send with requests"`
8586 // used for e.x for vertex to set the project
86− ExtraParams map[string]string `json:"extra_params,omitempty"`
87+ ExtraParams map[string]string `json:"extra_params,omitempty" jsonschema:"title=Extra Parameters,description=Additional provider-specific parameters"`
8788
88− DefaultLargeModel string `json:"default_large_model,omitempty"`
89− DefaultSmallModel string `json:"default_small_model,omitempty"`
89+ DefaultLargeModel string `json:"default_large_model,omitempty" jsonschema:"title=Default Large Model,description=Default model ID for large model type"`
90+ DefaultSmallModel string `json:"default_small_model,omitempty" jsonschema:"title=Default Small Model,description=Default model ID for small model type"`
9091
91− Models []Model `json:"models,omitempty"`
92+ Models []Model `json:"models,omitempty" jsonschema:"title=Models,description=List of available models for this provider"`
9293}
9394
9495type Agent struct {
95− ID AgentID `json:"id"`
96− Name string `json:"name"`
97− Description string `json:"description,omitempty"`
96+ ID AgentID `json:"id,omitempty" jsonschema:"title=Agent ID,description=Unique identifier for the agent,enum=coder,enum=task"`
97+ Name string `json:"name,omitempty" jsonschema:"title=Name,description=Display name of the agent"`
98+ Description string `json:"description,omitempty" jsonschema:"title=Description,description=Description of what the agent does"`
9899 // This is the id of the system prompt used by the agent
99− Disabled bool `json:"disabled"`
100+ Disabled bool `json:"disabled,omitempty" jsonschema:"title=Disabled,description=Whether this agent is disabled,default=false"`
100101
101− Model ModelType `json:"model"`
102+ Model ModelType `json:"model" jsonschema:"title=Model Type,description=Type of model to use (large or small),enum=large,enum=small"`
102103
103104 // The available tools for the agent
104105 // if this is nil, all tools are available
105− AllowedTools []string `json:"allowed_tools"`
106+ AllowedTools []string `json:"allowed_tools,omitempty" jsonschema:"title=Allowed Tools,description=List of tools this agent is allowed to use (if nil all tools are allowed)"`
106107
107108 // this tells us which MCPs are available for this agent
108109 // if this is empty all mcps are available
109110 // the string array is the list of tools from the AllowedMCP the agent has available
110111 // if the string array is nil, all tools from the AllowedMCP are available
111− AllowedMCP map[string][]string `json:"allowed_mcp"`
112+ AllowedMCP map[string][]string `json:"allowed_mcp,omitempty" jsonschema:"title=Allowed MCP,description=Map of MCP servers this agent can use and their allowed tools"`
112113
113114 // The list of LSPs that this agent can use
114115 // if this is nil, all LSPs are available
115− AllowedLSP []string `json:"allowed_lsp"`
116+ AllowedLSP []string `json:"allowed_lsp,omitempty" jsonschema:"title=Allowed LSP,description=List of LSP servers this agent can use (if nil all LSPs are allowed)"`
116117
117118 // Overrides the context paths for this agent
118− ContextPaths []string `json:"context_paths"`
119+ ContextPaths []string `json:"context_paths,omitempty" jsonschema:"title=Context Paths,description=Custom context paths for this agent (additive to global context paths)"`
119120}
120121
121122type MCPType string
@@ -126,69 +127,70 @@ const (
126127)
127128
128129type MCP struct {
129− Command string `json:"command"`
130− Env []string `json:"env"`
131− Args []string `json:"args"`
132− Type MCPType `json:"type"`
133− URL string `json:"url"`
134− Headers map[string]string `json:"headers"`
130+ Command string `json:"command" jsonschema:"title=Command,description=Command to execute for stdio MCP servers"`
131+ Env []string `json:"env,omitempty" jsonschema:"title=Environment,description=Environment variables for the MCP server"`
132+ Args []string `json:"args,omitempty" jsonschema:"title=Arguments,description=Command line arguments for the MCP server"`
133+ Type MCPType `json:"type" jsonschema:"title=Type,description=Type of MCP connection,enum=stdio,enum=sse,default=stdio"`
134+ URL string `json:"url,omitempty" jsonschema:"title=URL,description=URL for SSE MCP servers"`
135+ // TODO: maybe make it possible to get the value from the env
136+ Headers map[string]string `json:"headers,omitempty" jsonschema:"title=Headers,description=HTTP headers for SSE MCP servers"`
135137}
136138
137139type LSPConfig struct {
138− Disabled bool `json:"enabled"`
139− Command string `json:"command"`
140− Args []string `json:"args"`
141− Options any `json:"options"`
140+ Disabled bool `json:"enabled,omitempty" jsonschema:"title=Enabled,description=Whether this LSP server is enabled,default=true"`
141+ Command string `json:"command" jsonschema:"title=Command,description=Command to execute for the LSP server"`
142+ Args []string `json:"args,omitempty" jsonschema:"title=Arguments,description=Command line arguments for the LSP server"`
143+ Options any `json:"options,omitempty" jsonschema:"title=Options,description=LSP server specific options"`
142144}
143145
144146type TUIOptions struct {
145− CompactMode bool `json:"compact_mode"`
147+ CompactMode bool `json:"compact_mode" jsonschema:"title=Compact Mode,description=Enable compact mode for the TUI,default=false"`
146148 // Here we can add themes later or any TUI related options
147149}
148150
149151type Options struct {
150− ContextPaths []string `json:"context_paths"`
151− TUI TUIOptions `json:"tui"`
152− Debug bool `json:"debug"`
153− DebugLSP bool `json:"debug_lsp"`
154− DisableAutoSummarize bool `json:"disable_auto_summarize"`
152+ ContextPaths []string `json:"context_paths,omitempty" jsonschema:"title=Context Paths,description=List of paths to search for context files"`
153+ TUI TUIOptions `json:"tui,omitempty" jsonschema:"title=TUI Options,description=Terminal UI configuration options"`
154+ Debug bool `json:"debug,omitempty" jsonschema:"title=Debug,description=Enable debug logging,default=false"`
155+ DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"title=Debug LSP,description=Enable LSP debug logging,default=false"`
156+ DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"title=Disable Auto Summarize,description=Disable automatic conversation summarization,default=false"`
155157 // Relative to the cwd
156− DataDirectory string `json:"data_directory"`
158+ DataDirectory string `json:"data_directory,omitempty" jsonschema:"title=Data Directory,description=Directory for storing application data,default=.crush"`
157159}
158160
159161type PreferredModel struct {
160− ModelID string `json:"model_id"`
161− Provider provider.InferenceProvider `json:"provider"`
162+ ModelID string `json:"model_id" jsonschema:"title=Model ID,description=ID of the preferred model"`
163+ Provider provider.InferenceProvider `json:"provider" jsonschema:"title=Provider,description=Provider for the preferred model"`
162164 // ReasoningEffort overrides the default reasoning effort for this model
163− ReasoningEffort string `json:"reasoning_effort,omitempty"`
165+ ReasoningEffort string `json:"reasoning_effort,omitempty" jsonschema:"title=Reasoning Effort,description=Override reasoning effort for this model"`
164166 // MaxTokens overrides the default max tokens for this model
165− MaxTokens int64 `json:"max_tokens,omitempty"`
167+ MaxTokens int64 `json:"max_tokens,omitempty" jsonschema:"title=Max Tokens,description=Override max tokens for this model,minimum=1"`
166168
167169 // Think indicates if the model should think, only applicable for anthropic reasoning models
168− Think bool `json:"think,omitempty"`
170+ Think bool `json:"think,omitempty" jsonschema:"title=Think,description=Enable thinking for reasoning models,default=false"`
169171}
170172
171173type PreferredModels struct {
172− Large PreferredModel `json:"large"`
173− Small PreferredModel `json:"small"`
174+ Large PreferredModel `json:"large,omitempty" jsonschema:"title=Large Model,description=Preferred model configuration for large model type"`
175+ Small PreferredModel `json:"small,omitempty" jsonschema:"title=Small Model,description=Preferred model configuration for small model type"`
174176}
175177
176178type Config struct {
177− Models PreferredModels `json:"models"`
179+ Models PreferredModels `json:"models,omitempty" jsonschema:"title=Models,description=Preferred model configurations for large and small model types"`
178180 // List of configured providers
179− Providers map[provider.InferenceProvider]ProviderConfig `json:"providers,omitempty"`
181+ Providers map[provider.InferenceProvider]ProviderConfig `json:"providers,omitempty" jsonschema:"title=Providers,description=LLM provider configurations"`
180182
181183 // List of configured agents
182− Agents map[AgentID]Agent `json:"agents,omitempty"`
184+ Agents map[AgentID]Agent `json:"agents,omitempty" jsonschema:"title=Agents,description=Agent configurations for different tasks"`
183185
184186 // List of configured MCPs
185− MCP map[string]MCP `json:"mcp,omitempty"`
187+ MCP map[string]MCP `json:"mcp,omitempty" jsonschema:"title=MCP,description=Model Control Protocol server configurations"`
186188
187189 // List of configured LSPs
188− LSP map[string]LSPConfig `json:"lsp,omitempty"`
190+ LSP map[string]LSPConfig `json:"lsp,omitempty" jsonschema:"title=LSP,description=Language Server Protocol configurations"`
189191
190192 // Miscellaneous options
191− Options Options `json:"options"`
193+ Options Options `json:"options,omitempty" jsonschema:"title=Options,description=General application options and settings"`
192194}
193195
194196var (
@@ -502,27 +504,23 @@ func mergeAgents(base, global, local *Config) {
502504 }
503505 for agentID, newAgent := range cfg.Agents {
504506 if _, ok := base.Agents[agentID]; !ok {
505− // New agent - apply defaults
506− newAgent.ID = agentID // Ensure the ID is set correctly
507+ newAgent.ID = agentID
507508 if newAgent.Model == "" {
508− newAgent.Model = LargeModel // Default model type
509+ newAgent.Model = LargeModel
509510 }
510− // Context paths are always additive - start with global, then add custom
511511 if len(newAgent.ContextPaths) > 0 {
512512 newAgent.ContextPaths = append(base.Options.ContextPaths, newAgent.ContextPaths...)
513513 } else {
514− newAgent.ContextPaths = base.Options.ContextPaths // Use global context paths only
514+ newAgent.ContextPaths = base.Options.ContextPaths
515515 }
516516 base.Agents[agentID] = newAgent
517517 } else {
518518 baseAgent := base.Agents[agentID]
519519
520− // Special handling for known agents - only allow model changes
521520 if agentID == AgentCoder || agentID == AgentTask {
522521 if newAgent.Model != "" {
523522 baseAgent.Model = newAgent.Model
524523 }
525− // For known agents, only allow MCP and LSP configuration
526524 if newAgent.AllowedMCP != nil {
527525 baseAgent.AllowedMCP = newAgent.AllowedMCP
528526 }
@@ -534,7 +532,6 @@ func mergeAgents(base, global, local *Config) {
534532 baseAgent.ContextPaths = append(baseAgent.ContextPaths, newAgent.ContextPaths...)
535533 }
536534 } else {
537− // Custom agents - allow full merging
538535 if newAgent.Name != "" {
539536 baseAgent.Name = newAgent.Name
540537 }
@@ -544,13 +541,11 @@ func mergeAgents(base, global, local *Config) {
544541 if newAgent.Model != "" {
545542 baseAgent.Model = newAgent.Model
546543 } else if baseAgent.Model == "" {
547− baseAgent.Model = LargeModel // Default fallback
544+ baseAgent.Model = LargeModel
548545 }
549546
550− // Boolean fields - always update (including false values)
551547 baseAgent.Disabled = newAgent.Disabled
552548
553− // Slice/Map fields - update if provided (including empty slices/maps)
554549 if newAgent.AllowedTools != nil {
555550 baseAgent.AllowedTools = newAgent.AllowedTools
556551 }
@@ -560,7 +555,6 @@ func mergeAgents(base, global, local *Config) {
560555 if newAgent.AllowedLSP != nil {
561556 baseAgent.AllowedLSP = newAgent.AllowedLSP
562557 }
563− // Context paths are additive for custom agents too
564558 if len(newAgent.ContextPaths) > 0 {
565559 baseAgent.ContextPaths = append(baseAgent.ContextPaths, newAgent.ContextPaths...)
566560 }
@@ -596,6 +590,7 @@ func mergeProviderConfigs(base, global, local *Config) {
596590 continue
597591 }
598592 for providerName, p := range cfg.Providers {
593+ p.ID = providerName
599594 if _, ok := base.Providers[providerName]; !ok {
600595 base.Providers[providerName] = p
601596 } else {
@@ -616,36 +611,36 @@ func mergeProviderConfigs(base, global, local *Config) {
616611 base.Providers = finalProviders
617612}
618613
619−func providerDefaultConfig(providerId provider.InferenceProvider) ProviderConfig {
620− switch providerId {
614+func providerDefaultConfig(providerID provider.InferenceProvider) ProviderConfig {
615+ switch providerID {
621616 case provider.InferenceProviderAnthropic:
622617 return ProviderConfig{
623− ID: providerId,
618+ ID: providerID,
624619 ProviderType: provider.TypeAnthropic,
625620 }
626621 case provider.InferenceProviderOpenAI:
627622 return ProviderConfig{
628− ID: providerId,
623+ ID: providerID,
629624 ProviderType: provider.TypeOpenAI,
630625 }
631626 case provider.InferenceProviderGemini:
632627 return ProviderConfig{
633− ID: providerId,
628+ ID: providerID,
634629 ProviderType: provider.TypeGemini,
635630 }
636631 case provider.InferenceProviderBedrock:
637632 return ProviderConfig{
638− ID: providerId,
633+ ID: providerID,
639634 ProviderType: provider.TypeBedrock,
640635 }
641636 case provider.InferenceProviderAzure:
642637 return ProviderConfig{
643− ID: providerId,
638+ ID: providerID,
644639 ProviderType: provider.TypeAzure,
645640 }
646641 case provider.InferenceProviderOpenRouter:
647642 return ProviderConfig{
648− ID: providerId,
643+ ID: providerID,
649644 ProviderType: provider.TypeOpenAI,
650645 BaseURL: "https://openrouter.ai/api/v1",
651646 ExtraHeaders: map[string]string{
@@ -655,18 +650,18 @@ func providerDefaultConfig(providerId provider.InferenceProvider) ProviderConfig
655650 }
656651 case provider.InferenceProviderXAI:
657652 return ProviderConfig{
658− ID: providerId,
653+ ID: providerID,
659654 ProviderType: provider.TypeXAI,
660655 BaseURL: "https://api.x.ai/v1",
661656 }
662657 case provider.InferenceProviderVertexAI:
663658 return ProviderConfig{
664− ID: providerId,
659+ ID: providerID,
665660 ProviderType: provider.TypeVertexAI,
666661 }
667662 default:
668663 return ProviderConfig{
669− ID: providerId,
664+ ID: providerID,
670665 ProviderType: provider.TypeOpenAI,
671666 }
672667 }
@@ -1433,3 +1428,27 @@ func (c *Config) validateCompleteness(errors *ValidationErrors) {
14331428 }
14341429 }
14351430}
1431+
1432+// JSONSchemaExtend adds custom schema properties for AgentID
1433+func (AgentID) JSONSchemaExtend(schema *jsonschema.Schema) {
1434+ schema.Enum = []any{
1435+ string(AgentCoder),
1436+ string(AgentTask),
1437+ }
1438+}
1439+
1440+// JSONSchemaExtend adds custom schema properties for ModelType
1441+func (ModelType) JSONSchemaExtend(schema *jsonschema.Schema) {
1442+ schema.Enum = []any{
1443+ string(LargeModel),
1444+ string(SmallModel),
1445+ }
1446+}
1447+
1448+// JSONSchemaExtend adds custom schema properties for MCPType
1449+func (MCPType) JSONSchemaExtend(schema *jsonschema.Schema) {
1450+ schema.Enum = []any{
1451+ string(MCPStdio),
1452+ string(MCPSse),
1453+ }
1454+}
internal/config/config_test.go−1
@@ -1051,7 +1051,6 @@ func TestProviderValidation_KnownProviderValid(t *testing.T) {
10511051 DefaultMaxTokens: 2048,
10521052 },
10531053 },
1054−
10551054 },
10561055 },
10571056 }
internal/config/provider_mock.go+18−18
@@ -126,28 +126,28 @@ func MockProviders() []provider.Provider {
126126 DefaultSmallModelID: "grok-3-mini",
127127 Models: []provider.Model{
128128 {
129− ID: "grok-3",
130− Name: "Grok 3",
131− CostPer1MIn: 3.0,
132− CostPer1MOut: 15.0,
133− CostPer1MInCached: 0.0,
129+ ID: "grok-3",
130+ Name: "Grok 3",
131+ CostPer1MIn: 3.0,
132+ CostPer1MOut: 15.0,
133+ CostPer1MInCached: 0.0,
134134 CostPer1MOutCached: 0.75,
135− ContextWindow: 131072,
136− DefaultMaxTokens: 20000,
137− CanReason: false,
138− SupportsImages: false,
135+ ContextWindow: 131072,
136+ DefaultMaxTokens: 20000,
137+ CanReason: false,
138+ SupportsImages: false,
139139 },
140140 {
141− ID: "grok-3-mini",
142− Name: "Grok 3 Mini",
143− CostPer1MIn: 0.3,
144− CostPer1MOut: 0.5,
145− CostPer1MInCached: 0.0,
141+ ID: "grok-3-mini",
142+ Name: "Grok 3 Mini",
143+ CostPer1MIn: 0.3,
144+ CostPer1MOut: 0.5,
145+ CostPer1MInCached: 0.0,
146146 CostPer1MOutCached: 0.075,
147− ContextWindow: 131072,
148− DefaultMaxTokens: 20000,
149− CanReason: true,
150− SupportsImages: false,
147+ ContextWindow: 131072,
148+ DefaultMaxTokens: 20000,
149+ CanReason: true,
150+ SupportsImages: false,
151151 },
152152 },
153153 },
internal/config/provider_test.go+1−1
@@ -78,4 +78,4 @@ func TestProviders_ModelCapabilities(t *testing.T) {
7878
7979 assert.True(t, foundReasoning)
8080 assert.True(t, foundNonReasoning)
81−}
81+}