diff --git a/cmd/schema/main.go b/cmd/schema/main.go index 34939f1e84b9f3df04c7419a9ac4d7dfdc76386a..43f361662cd5d357e4d3b736ba7b4f3af2222724 100644 --- a/cmd/schema/main.go +++ b/cmd/schema/main.go @@ -4,67 +4,31 @@ import ( "encoding/json" "fmt" "os" - "reflect" - "slices" - "strings" "github.com/charmbracelet/crush/internal/config" + "github.com/invopop/jsonschema" ) -// JSONSchema represents a JSON Schema -type JSONSchema struct { - Schema string `json:"$schema,omitempty"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - Type string `json:"type,omitempty"` - Properties map[string]*JSONSchema `json:"properties,omitempty"` - Items *JSONSchema `json:"items,omitempty"` - Required []string `json:"required,omitempty"` - AdditionalProperties any `json:"additionalProperties,omitempty"` - Enum []any `json:"enum,omitempty"` - Default any `json:"default,omitempty"` - Definitions map[string]*JSONSchema `json:"definitions,omitempty"` - Ref string `json:"$ref,omitempty"` - OneOf []*JSONSchema `json:"oneOf,omitempty"` - AnyOf []*JSONSchema `json:"anyOf,omitempty"` - AllOf []*JSONSchema `json:"allOf,omitempty"` - Not *JSONSchema `json:"not,omitempty"` - Format string `json:"format,omitempty"` - Pattern string `json:"pattern,omitempty"` - MinLength *int `json:"minLength,omitempty"` - MaxLength *int `json:"maxLength,omitempty"` - Minimum *float64 `json:"minimum,omitempty"` - Maximum *float64 `json:"maximum,omitempty"` - ExclusiveMinimum *float64 `json:"exclusiveMinimum,omitempty"` - ExclusiveMaximum *float64 `json:"exclusiveMaximum,omitempty"` - MultipleOf *float64 `json:"multipleOf,omitempty"` - MinItems *int `json:"minItems,omitempty"` - MaxItems *int `json:"maxItems,omitempty"` - UniqueItems *bool `json:"uniqueItems,omitempty"` - MinProperties *int `json:"minProperties,omitempty"` - MaxProperties *int `json:"maxProperties,omitempty"` -} - -// SchemaGenerator generates JSON schemas from Go types -type SchemaGenerator struct { - definitions map[string]*JSONSchema - visited map[reflect.Type]bool -} - -// NewSchemaGenerator creates a new schema generator -func NewSchemaGenerator() *SchemaGenerator { - return &SchemaGenerator{ - definitions: make(map[string]*JSONSchema), - visited: make(map[reflect.Type]bool), +func main() { + // Create a new reflector + r := &jsonschema.Reflector{ + // Use anonymous schemas to avoid ID conflicts + Anonymous: true, + // Expand the root struct instead of referencing it + ExpandedStruct: true, + AllowAdditionalProperties: true, } -} -func main() { - // Enable mock providers to avoid API calls during schema generation - config.UseMockProviders = true + // Generate schema for the main Config struct + schema := r.Reflect(&config.Config{}) + + // Enhance the schema with additional information + enhanceSchema(schema) - generator := NewSchemaGenerator() - schema := generator.GenerateSchema() + // Set the schema metadata + schema.Version = "https://json-schema.org/draft/2020-12/schema" + schema.Title = "Crush Configuration" + schema.Description = "Configuration schema for the Crush application" // Pretty print the schema encoder := json.NewEncoder(os.Stdout) @@ -75,226 +39,23 @@ func main() { } } -// GenerateSchema generates the complete JSON schema for the Crush configuration -func (g *SchemaGenerator) GenerateSchema() *JSONSchema { - // Generate schema for the main Config struct - configType := reflect.TypeOf(config.Config{}) - configSchema := g.generateTypeSchema(configType) - - // Create the root schema - schema := &JSONSchema{ - Schema: "http://json-schema.org/draft-07/schema#", - Title: "Crush Configuration", - Description: "Configuration schema for the Crush application", - Type: configSchema.Type, - Properties: configSchema.Properties, - Required: configSchema.Required, - Definitions: g.definitions, - } - - // Add custom enhancements - g.enhanceSchema(schema) - - return schema -} - -// generateTypeSchema generates a JSON schema for a given Go type -func (g *SchemaGenerator) generateTypeSchema(t reflect.Type) *JSONSchema { - // Handle pointers - if t.Kind() == reflect.Ptr { - return g.generateTypeSchema(t.Elem()) - } - - // Check if we've already processed this type - if g.visited[t] { - // Return a reference to avoid infinite recursion - return &JSONSchema{ - Ref: fmt.Sprintf("#/definitions/%s", t.Name()), - } - } - - switch t.Kind() { - case reflect.String: - return &JSONSchema{Type: "string"} - case reflect.Bool: - return &JSONSchema{Type: "boolean"} - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return &JSONSchema{Type: "integer"} - case reflect.Float32, reflect.Float64: - return &JSONSchema{Type: "number"} - case reflect.Slice, reflect.Array: - itemSchema := g.generateTypeSchema(t.Elem()) - return &JSONSchema{ - Type: "array", - Items: itemSchema, - } - case reflect.Map: - valueSchema := g.generateTypeSchema(t.Elem()) - return &JSONSchema{ - Type: "object", - AdditionalProperties: valueSchema, - } - case reflect.Struct: - return g.generateStructSchema(t) - case reflect.Interface: - // For interface{} types, allow any value - return &JSONSchema{} - default: - // Fallback for unknown types - return &JSONSchema{} - } -} - -// generateStructSchema generates a JSON schema for a struct type -func (g *SchemaGenerator) generateStructSchema(t reflect.Type) *JSONSchema { - // Mark as visited to prevent infinite recursion - g.visited[t] = true - - schema := &JSONSchema{ - Type: "object", - Properties: make(map[string]*JSONSchema), - } - - var required []string - - for i := range t.NumField() { - field := t.Field(i) - - // Skip unexported fields - if !field.IsExported() { - continue - } - - // Get JSON tag - jsonTag := field.Tag.Get("json") - if jsonTag == "-" { - continue - } - - // Parse JSON tag - jsonName, options := parseJSONTag(jsonTag) - if jsonName == "" { - jsonName = strings.ToLower(field.Name) - } - - // Generate field schema - fieldSchema := g.generateTypeSchema(field.Type) - - // Add description from field name if not present - if fieldSchema.Description == "" { - fieldSchema.Description = generateFieldDescription(field.Name, field.Type) - } - - // Check if field is required (not omitempty and not a pointer) - if !slices.Contains(options, "omitempty") && field.Type.Kind() != reflect.Ptr { - required = append(required, jsonName) - } - - schema.Properties[jsonName] = fieldSchema - } - - if len(required) > 0 { - schema.Required = required - } - - // Store in definitions if it's a named type - if t.Name() != "" { - g.definitions[t.Name()] = schema - } - - return schema -} - -// parseJSONTag parses a JSON struct tag -func parseJSONTag(tag string) (name string, options []string) { - if tag == "" { - return "", nil - } - - parts := strings.Split(tag, ",") - name = parts[0] - if len(parts) > 1 { - options = parts[1:] - } - return name, options -} - -// generateFieldDescription generates a description for a field based on its name and type -func generateFieldDescription(fieldName string, fieldType reflect.Type) string { - // Convert camelCase to words - words := camelCaseToWords(fieldName) - description := strings.Join(words, " ") - - // Add type-specific information - switch fieldType.Kind() { - case reflect.Bool: - if !strings.Contains(strings.ToLower(description), "enable") && - !strings.Contains(strings.ToLower(description), "disable") { - description = "Enable " + strings.ToLower(description) - } - case reflect.Slice: - if !strings.HasSuffix(description, "s") { - description = description + " list" - } - case reflect.Map: - description = description + " configuration" - } - - return description -} - -// camelCaseToWords converts camelCase to separate words -func camelCaseToWords(s string) []string { - var words []string - var currentWord strings.Builder - - for i, r := range s { - if i > 0 && r >= 'A' && r <= 'Z' { - if currentWord.Len() > 0 { - words = append(words, currentWord.String()) - currentWord.Reset() - } - } - currentWord.WriteRune(r) - } - - if currentWord.Len() > 0 { - words = append(words, currentWord.String()) - } - - return words -} - -// enhanceSchema adds custom enhancements to the generated schema -func (g *SchemaGenerator) enhanceSchema(schema *JSONSchema) { +// enhanceSchema adds additional enhancements to the generated schema +func enhanceSchema(schema *jsonschema.Schema) { // Add provider enums - g.addProviderEnums(schema) + addProviderEnums(schema) // Add model enums - g.addModelEnums(schema) - - // Add agent enums - g.addAgentEnums(schema) + addModelEnums(schema) // Add tool enums - g.addToolEnums(schema) - - // Add MCP type enums - g.addMCPTypeEnums(schema) - - // Add model type enums - g.addModelTypeEnums(schema) - - // Add default values - g.addDefaultValues(schema) + addToolEnums(schema) - // Add custom descriptions - g.addCustomDescriptions(schema) + // Add default context paths + addDefaultContextPaths(schema) } // addProviderEnums adds provider enums to the schema -func (g *SchemaGenerator) addProviderEnums(schema *JSONSchema) { +func addProviderEnums(schema *jsonschema.Schema) { providers := config.Providers() var providerIDs []any for _, p := range providers { @@ -302,22 +63,24 @@ func (g *SchemaGenerator) addProviderEnums(schema *JSONSchema) { } // Add to PreferredModel provider field - if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists { - if providerProp, exists := preferredModelDef.Properties["provider"]; exists { - providerProp.Enum = providerIDs + if schema.Definitions != nil { + if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists { + if providerProp, exists := preferredModelDef.Properties.Get("provider"); exists { + providerProp.Enum = providerIDs + } } - } - // Add to ProviderConfig ID field - if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists { - if idProp, exists := providerConfigDef.Properties["id"]; exists { - idProp.Enum = providerIDs + // Add to ProviderConfig ID field + if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists { + if idProp, exists := providerConfigDef.Properties.Get("id"); exists { + idProp.Enum = providerIDs + } } } } // addModelEnums adds model enums to the schema -func (g *SchemaGenerator) addModelEnums(schema *JSONSchema) { +func addModelEnums(schema *jsonschema.Schema) { providers := config.Providers() var modelIDs []any for _, p := range providers { @@ -327,205 +90,66 @@ func (g *SchemaGenerator) addModelEnums(schema *JSONSchema) { } // Add to PreferredModel model_id field - if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists { - if modelIDProp, exists := preferredModelDef.Properties["model_id"]; exists { - modelIDProp.Enum = modelIDs - } - } -} - -// addAgentEnums adds agent ID enums to the schema -func (g *SchemaGenerator) addAgentEnums(schema *JSONSchema) { - agentIDs := []any{ - string(config.AgentCoder), - string(config.AgentTask), - } - - if agentDef, exists := schema.Definitions["Agent"]; exists { - if idProp, exists := agentDef.Properties["id"]; exists { - idProp.Enum = agentIDs + if schema.Definitions != nil { + if preferredModelDef, exists := schema.Definitions["PreferredModel"]; exists { + if modelIDProp, exists := preferredModelDef.Properties.Get("model_id"); exists { + modelIDProp.Enum = modelIDs + } } } } // addToolEnums adds tool enums to the schema -func (g *SchemaGenerator) addToolEnums(schema *JSONSchema) { +func addToolEnums(schema *jsonschema.Schema) { tools := []any{ "bash", "edit", "fetch", "glob", "grep", "ls", "sourcegraph", "view", "write", "agent", } - if agentDef, exists := schema.Definitions["Agent"]; exists { - if allowedToolsProp, exists := agentDef.Properties["allowed_tools"]; exists { - if allowedToolsProp.Items != nil { - allowedToolsProp.Items.Enum = tools + if schema.Definitions != nil { + if agentDef, exists := schema.Definitions["Agent"]; exists { + if allowedToolsProp, exists := agentDef.Properties.Get("allowed_tools"); exists { + if allowedToolsProp.Items != nil { + allowedToolsProp.Items.Enum = tools + } } } } } -// addMCPTypeEnums adds MCP type enums to the schema -func (g *SchemaGenerator) addMCPTypeEnums(schema *JSONSchema) { - mcpTypes := []any{ - string(config.MCPStdio), - string(config.MCPSse), +// addDefaultContextPaths adds default context paths to the schema +func addDefaultContextPaths(schema *jsonschema.Schema) { + defaultContextPaths := []any{ + ".github/copilot-instructions.md", + ".cursorrules", + ".cursor/rules/", + "CLAUDE.md", + "CLAUDE.local.md", + "GEMINI.md", + "gemini.md", + "crush.md", + "crush.local.md", + "Crush.md", + "Crush.local.md", + "CRUSH.md", + "CRUSH.local.md", } - if mcpDef, exists := schema.Definitions["MCP"]; exists { - if typeProp, exists := mcpDef.Properties["type"]; exists { - typeProp.Enum = mcpTypes - } - } -} - -// addModelTypeEnums adds model type enums to the schema -func (g *SchemaGenerator) addModelTypeEnums(schema *JSONSchema) { - modelTypes := []any{ - string(config.LargeModel), - string(config.SmallModel), - } - - if agentDef, exists := schema.Definitions["Agent"]; exists { - if modelProp, exists := agentDef.Properties["model"]; exists { - modelProp.Enum = modelTypes - } - } -} - -// addDefaultValues adds default values to the schema -func (g *SchemaGenerator) addDefaultValues(schema *JSONSchema) { - // Add default context paths - if optionsDef, exists := schema.Definitions["Options"]; exists { - if contextPathsProp, exists := optionsDef.Properties["context_paths"]; exists { - contextPathsProp.Default = []any{ - ".github/copilot-instructions.md", - ".cursorrules", - ".cursor/rules/", - "CLAUDE.md", - "CLAUDE.local.md", - "GEMINI.md", - "gemini.md", - "crush.md", - "crush.local.md", - "Crush.md", - "Crush.local.md", - "CRUSH.md", - "CRUSH.local.md", + if schema.Definitions != nil { + if optionsDef, exists := schema.Definitions["Options"]; exists { + if contextPathsProp, exists := optionsDef.Properties.Get("context_paths"); exists { + contextPathsProp.Default = defaultContextPaths } } - if dataDirProp, exists := optionsDef.Properties["data_directory"]; exists { - dataDirProp.Default = ".crush" - } - if debugProp, exists := optionsDef.Properties["debug"]; exists { - debugProp.Default = false - } - if debugLSPProp, exists := optionsDef.Properties["debug_lsp"]; exists { - debugLSPProp.Default = false - } - if disableAutoSummarizeProp, exists := optionsDef.Properties["disable_auto_summarize"]; exists { - disableAutoSummarizeProp.Default = false - } - } - - // Add default MCP type - if mcpDef, exists := schema.Definitions["MCP"]; exists { - if typeProp, exists := mcpDef.Properties["type"]; exists { - typeProp.Default = string(config.MCPStdio) - } - } - - // Add default TUI options - if tuiOptionsDef, exists := schema.Definitions["TUIOptions"]; exists { - if compactModeProp, exists := tuiOptionsDef.Properties["compact_mode"]; exists { - compactModeProp.Default = false - } - } - - // Add default provider disabled - if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists { - if disabledProp, exists := providerConfigDef.Properties["disabled"]; exists { - disabledProp.Default = false - } - } - - // Add default agent disabled - if agentDef, exists := schema.Definitions["Agent"]; exists { - if disabledProp, exists := agentDef.Properties["disabled"]; exists { - disabledProp.Default = false - } - } - - // Add default LSP disabled - if lspConfigDef, exists := schema.Definitions["LSPConfig"]; exists { - if disabledProp, exists := lspConfigDef.Properties["enabled"]; exists { - disabledProp.Default = true - } } -} -// addCustomDescriptions adds custom descriptions to improve the schema -func (g *SchemaGenerator) addCustomDescriptions(schema *JSONSchema) { - // Enhance main config descriptions + // Also add to root properties if they exist if schema.Properties != nil { - if modelsProp, exists := schema.Properties["models"]; exists { - modelsProp.Description = "Preferred model configurations for large and small model types" - } - if providersProp, exists := schema.Properties["providers"]; exists { - providersProp.Description = "LLM provider configurations" - } - if agentsProp, exists := schema.Properties["agents"]; exists { - agentsProp.Description = "Agent configurations for different tasks" - } - if mcpProp, exists := schema.Properties["mcp"]; exists { - mcpProp.Description = "Model Control Protocol server configurations" - } - if lspProp, exists := schema.Properties["lsp"]; exists { - lspProp.Description = "Language Server Protocol configurations" - } - if optionsProp, exists := schema.Properties["options"]; exists { - optionsProp.Description = "General application options and settings" - } - } - - // Enhance specific field descriptions - if providerConfigDef, exists := schema.Definitions["ProviderConfig"]; exists { - if apiKeyProp, exists := providerConfigDef.Properties["api_key"]; exists { - apiKeyProp.Description = "API key for authenticating with the provider" - } - if baseURLProp, exists := providerConfigDef.Properties["base_url"]; exists { - baseURLProp.Description = "Base URL for the provider API (required for custom providers)" - } - if extraHeadersProp, exists := providerConfigDef.Properties["extra_headers"]; exists { - extraHeadersProp.Description = "Additional HTTP headers to send with requests" - } - if extraParamsProp, exists := providerConfigDef.Properties["extra_params"]; exists { - extraParamsProp.Description = "Additional provider-specific parameters" - } - } - - if agentDef, exists := schema.Definitions["Agent"]; exists { - if allowedToolsProp, exists := agentDef.Properties["allowed_tools"]; exists { - allowedToolsProp.Description = "List of tools this agent is allowed to use (if nil, all tools are allowed)" - } - if allowedMCPProp, exists := agentDef.Properties["allowed_mcp"]; exists { - allowedMCPProp.Description = "Map of MCP servers this agent can use and their allowed tools" - } - if allowedLSPProp, exists := agentDef.Properties["allowed_lsp"]; exists { - allowedLSPProp.Description = "List of LSP servers this agent can use (if nil, all LSPs are allowed)" - } - if contextPathsProp, exists := agentDef.Properties["context_paths"]; exists { - contextPathsProp.Description = "Custom context paths for this agent (additive to global context paths)" - } - } - - if mcpDef, exists := schema.Definitions["MCP"]; exists { - if commandProp, exists := mcpDef.Properties["command"]; exists { - commandProp.Description = "Command to execute for stdio MCP servers" - } - if urlProp, exists := mcpDef.Properties["url"]; exists { - urlProp.Description = "URL for SSE MCP servers" - } - if headersProp, exists := mcpDef.Properties["headers"]; exists { - headersProp.Description = "HTTP headers for SSE MCP servers" + if optionsProp, exists := schema.Properties.Get("options"); exists { + if optionsProp.Properties != nil { + if contextPathsProp, exists := optionsProp.Properties.Get("context_paths"); exists { + contextPathsProp.Default = defaultContextPaths + } + } } } } diff --git a/crush-schema.json b/crush-schema.json index f5fa562c5aff42972eb2308c3374969e5d42cac8..35bd388ceca5b0aca5401d66db963b77f4f584f8 100644 --- a/crush-schema.json +++ b/crush-schema.json @@ -1,492 +1,47 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Crush Configuration", - "description": "Configuration schema for the Crush application", - "type": "object", - "properties": { - "agents": { - "description": "Agent configurations for different tasks", - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "allowed_lsp": { - "description": "List of LSP servers this agent can use (if nil, all LSPs are allowed)", - "type": "array", - "items": { - "type": "string" - } - }, - "allowed_mcp": { - "description": "Map of MCP servers this agent can use and their allowed tools", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "allowed_tools": { - "description": "List of tools this agent is allowed to use (if nil, all tools are allowed)", - "type": "array", - "items": { - "type": "string", - "enum": [ - "bash", - "edit", - "fetch", - "glob", - "grep", - "ls", - "sourcegraph", - "view", - "write", - "agent" - ] - } - }, - "context_paths": { - "description": "Custom context paths for this agent (additive to global context paths)", - "type": "array", - "items": { - "type": "string" - } - }, - "description": { - "description": "Description", - "type": "string" - }, - "disabled": { - "description": "Disabled", - "type": "boolean", - "default": false - }, - "id": { - "description": "I D", - "type": "string", - "enum": [ - "coder", - "task" - ] - }, - "model": { - "description": "Model", - "type": "string", - "enum": [ - "large", - "small" - ] - }, - "name": { - "description": "Name", - "type": "string" - } - }, - "required": [ - "id", - "name", - "disabled", - "model", - "allowed_tools", - "allowed_mcp", - "allowed_lsp", - "context_paths" - ] - } - }, - "lsp": { - "description": "Language Server Protocol configurations", - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "args": { - "description": "Args", - "type": "array", - "items": { - "type": "string" - } - }, - "command": { - "description": "Command", - "type": "string" - }, - "enabled": { - "description": "Disabled", - "type": "boolean", - "default": true - }, - "options": { - "description": "Options" - } - }, - "required": [ - "enabled", - "command", - "args", - "options" - ] - } - }, - "mcp": { - "description": "Model Control Protocol server configurations", - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "args": { - "description": "Args", - "type": "array", - "items": { - "type": "string" - } - }, - "command": { - "description": "Command to execute for stdio MCP servers", - "type": "string" - }, - "env": { - "description": "Env list", - "type": "array", - "items": { - "type": "string" - } - }, - "headers": { - "description": "HTTP headers for SSE MCP servers", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "type": { - "description": "Type", - "type": "string", - "enum": [ - "stdio", - "sse" - ], - "default": "stdio" - }, - "url": { - "description": "URL for SSE MCP servers", - "type": "string" - } - }, - "required": [ - "command", - "env", - "args", - "type", - "url", - "headers" - ] - } - }, - "models": { - "description": "Preferred model configurations for large and small model types", - "type": "object", - "properties": { - "large": { - "description": "Large", - "type": "object", - "properties": { - "max_tokens": { - "description": "Max Tokens", - "type": "integer" - }, - "model_id": { - "description": "Model I D", - "type": "string", - "enum": [ - "claude-3-opus", - "claude-3-haiku", - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "gpt-4", - "gpt-3.5-turbo", - "gpt-4-turbo", - "gpt-4o", - "gpt-4o-mini", - "o1-preview", - "o1-mini", - "gemini-2.5-pro", - "gemini-2.5-flash", - "grok-beta", - "anthropic/claude-3.5-sonnet", - "anthropic/claude-3.5-haiku" - ] - }, - "provider": { - "description": "Provider", - "type": "string", - "enum": [ - "anthropic", - "openai", - "gemini", - "xai", - "openrouter" - ] - }, - "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" - }, - "think": { - "description": "Enable think", - "type": "boolean" - } - }, - "required": [ - "model_id", - "provider" - ] - }, - "small": { - "description": "Small", - "$ref": "#/definitions/PreferredModel" - } - }, - "required": [ - "large", - "small" - ] - }, - "options": { - "description": "General application options and settings", - "type": "object", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "Agent": { "properties": { - "context_paths": { - "description": "Context Paths", - "type": "array", - "items": { - "type": "string" - }, - "default": [ - ".github/copilot-instructions.md", - ".cursorrules", - ".cursor/rules/", - "CLAUDE.md", - "CLAUDE.local.md", - "GEMINI.md", - "gemini.md", - "crush.md", - "crush.local.md", - "Crush.md", - "Crush.local.md", - "CRUSH.md", - "CRUSH.local.md" - ] - }, - "data_directory": { - "description": "Data Directory", + "id": { "type": "string", - "default": ".crush" + "enum": [ + "coder", + "task", + "coder", + "task" + ], + "title": "Agent ID", + "description": "Unique identifier for the agent" }, - "debug": { - "description": "Enable debug", - "type": "boolean", - "default": false + "name": { + "type": "string", + "title": "Name", + "description": "Display name of the agent" }, - "debug_lsp": { - "description": "Enable debug l s p", - "type": "boolean", - "default": false + "description": { + "type": "string", + "title": "Description", + "description": "Description of what the agent does" }, - "disable_auto_summarize": { - "description": "Disable Auto Summarize", + "disabled": { "type": "boolean", + "title": "Disabled", + "description": "Whether this agent is disabled", "default": false }, - "tui": { - "description": "T U I", - "type": "object", - "properties": { - "compact_mode": { - "description": "Enable compact mode", - "type": "boolean", - "default": false - } - }, - "required": [ - "compact_mode" - ] - } - }, - "required": [ - "context_paths", - "tui", - "debug", - "debug_lsp", - "disable_auto_summarize", - "data_directory" - ] - }, - "providers": { - "description": "LLM provider configurations", - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "api_key": { - "description": "API key for authenticating with the provider", - "type": "string" - }, - "base_url": { - "description": "Base URL for the provider API (required for custom providers)", - "type": "string" - }, - "default_large_model": { - "description": "Default Large Model", - "type": "string" - }, - "default_small_model": { - "description": "Default Small Model", - "type": "string" - }, - "disabled": { - "description": "Disabled", - "type": "boolean", - "default": false - }, - "extra_headers": { - "description": "Additional HTTP headers to send with requests", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "extra_params": { - "description": "Additional provider-specific parameters", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "id": { - "description": "I D", - "type": "string", - "enum": [ - "anthropic", - "openai", - "gemini", - "xai", - "openrouter" - ] - }, - "models": { - "description": "Models", - "type": "array", - "items": { - "type": "object", - "properties": { - "can_reason": { - "description": "Enable can reason", - "type": "boolean" - }, - "context_window": { - "description": "Context Window", - "type": "integer" - }, - "cost_per_1m_in": { - "description": "Cost Per1 M In", - "type": "number" - }, - "cost_per_1m_in_cached": { - "description": "Cost Per1 M In Cached", - "type": "number" - }, - "cost_per_1m_out": { - "description": "Cost Per1 M Out", - "type": "number" - }, - "cost_per_1m_out_cached": { - "description": "Cost Per1 M Out Cached", - "type": "number" - }, - "default_max_tokens": { - "description": "Default Max Tokens", - "type": "integer" - }, - "has_reasoning_effort": { - "description": "Enable has reasoning effort", - "type": "boolean" - }, - "id": { - "description": "I D", - "type": "string" - }, - "model": { - "description": "Name", - "type": "string" - }, - "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" - }, - "supports_attachments": { - "description": "Enable supports images", - "type": "boolean" - } - }, - "required": [ - "id", - "model", - "cost_per_1m_in", - "cost_per_1m_out", - "cost_per_1m_in_cached", - "cost_per_1m_out_cached", - "context_window", - "default_max_tokens", - "can_reason", - "reasoning_effort", - "has_reasoning_effort", - "supports_attachments" - ] - } - }, - "provider_type": { - "description": "Provider Type", - "type": "string" - } - }, - "required": [ - "id", - "provider_type", - "disabled" - ] - } - } - }, - "required": [ - "models", - "options" - ], - "definitions": { - "Agent": { - "type": "object", - "properties": { - "allowed_lsp": { - "description": "List of LSP servers this agent can use (if nil, all LSPs are allowed)", - "type": "array", - "items": { - "type": "string" - } - }, - "allowed_mcp": { - "description": "Map of MCP servers this agent can use and their allowed tools", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } + "model": { + "type": "string", + "enum": [ + "large", + "small", + "large", + "small" + ], + "title": "Model Type", + "description": "Type of model to use (large or small)" }, "allowed_tools": { - "description": "List of tools this agent is allowed to use (if nil, all tools are allowed)", - "type": "array", "items": { "type": "string", "enum": [ @@ -501,652 +56,200 @@ "write", "agent" ] - } - }, - "context_paths": { - "description": "Custom context paths for this agent (additive to global context paths)", + }, "type": "array", - "items": { - "type": "string" - } - }, - "description": { - "description": "Description", - "type": "string" - }, - "disabled": { - "description": "Disabled", - "type": "boolean", - "default": false - }, - "id": { - "description": "I D", - "type": "string", - "enum": [ - "coder", - "task" - ] - }, - "model": { - "description": "Model", - "type": "string", - "enum": [ - "large", - "small" - ] + "title": "Allowed Tools", + "description": "List of tools this agent is allowed to use (if nil all tools are allowed)" }, - "name": { - "description": "Name", - "type": "string" - } - }, - "required": [ - "id", - "name", - "disabled", - "model", - "allowed_tools", - "allowed_mcp", - "allowed_lsp", - "context_paths" - ] - }, - "Config": { - "type": "object", - "properties": { - "agents": { - "description": "Agent configurations for different tasks", - "type": "object", + "allowed_mcp": { "additionalProperties": { - "type": "object", - "properties": { - "allowed_lsp": { - "description": "List of LSP servers this agent can use (if nil, all LSPs are allowed)", - "type": "array", - "items": { - "type": "string" - } - }, - "allowed_mcp": { - "description": "Map of MCP servers this agent can use and their allowed tools", - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "allowed_tools": { - "description": "List of tools this agent is allowed to use (if nil, all tools are allowed)", - "type": "array", - "items": { - "type": "string", - "enum": [ - "bash", - "edit", - "fetch", - "glob", - "grep", - "ls", - "sourcegraph", - "view", - "write", - "agent" - ] - } - }, - "context_paths": { - "description": "Custom context paths for this agent (additive to global context paths)", - "type": "array", - "items": { - "type": "string" - } - }, - "description": { - "description": "Description", - "type": "string" - }, - "disabled": { - "description": "Disabled", - "type": "boolean", - "default": false - }, - "id": { - "description": "I D", - "type": "string", - "enum": [ - "coder", - "task" - ] - }, - "model": { - "description": "Model", - "type": "string", - "enum": [ - "large", - "small" - ] - }, - "name": { - "description": "Name", - "type": "string" - } + "items": { + "type": "string" }, - "required": [ - "id", - "name", - "disabled", - "model", - "allowed_tools", - "allowed_mcp", - "allowed_lsp", - "context_paths" - ] - } - }, - "lsp": { - "description": "Language Server Protocol configurations", + "type": "array" + }, "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "args": { - "description": "Args", - "type": "array", - "items": { - "type": "string" - } - }, - "command": { - "description": "Command", - "type": "string" - }, - "enabled": { - "description": "Disabled", - "type": "boolean", - "default": true - }, - "options": { - "description": "Options" - } - }, - "required": [ - "enabled", - "command", - "args", - "options" - ] - } + "title": "Allowed MCP", + "description": "Map of MCP servers this agent can use and their allowed tools" }, - "mcp": { - "description": "Model Control Protocol server configurations", - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "args": { - "description": "Args", - "type": "array", - "items": { - "type": "string" - } - }, - "command": { - "description": "Command to execute for stdio MCP servers", - "type": "string" - }, - "env": { - "description": "Env list", - "type": "array", - "items": { - "type": "string" - } - }, - "headers": { - "description": "HTTP headers for SSE MCP servers", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "type": { - "description": "Type", - "type": "string", - "enum": [ - "stdio", - "sse" - ], - "default": "stdio" - }, - "url": { - "description": "URL for SSE MCP servers", - "type": "string" - } - }, - "required": [ - "command", - "env", - "args", - "type", - "url", - "headers" - ] - } - }, - "models": { - "description": "Preferred model configurations for large and small model types", - "type": "object", - "properties": { - "large": { - "description": "Large", - "type": "object", - "properties": { - "max_tokens": { - "description": "Max Tokens", - "type": "integer" - }, - "model_id": { - "description": "Model I D", - "type": "string", - "enum": [ - "claude-3-opus", - "claude-3-haiku", - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "gpt-4", - "gpt-3.5-turbo", - "gpt-4-turbo", - "gpt-4o", - "gpt-4o-mini", - "o1-preview", - "o1-mini", - "gemini-2.5-pro", - "gemini-2.5-flash", - "grok-beta", - "anthropic/claude-3.5-sonnet", - "anthropic/claude-3.5-haiku" - ] - }, - "provider": { - "description": "Provider", - "type": "string", - "enum": [ - "anthropic", - "openai", - "gemini", - "xai", - "openrouter" - ] - }, - "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" - }, - "think": { - "description": "Enable think", - "type": "boolean" - } - }, - "required": [ - "model_id", - "provider" - ] - }, - "small": { - "description": "Small", - "$ref": "#/definitions/PreferredModel" - } + "allowed_lsp": { + "items": { + "type": "string" }, - "required": [ - "large", - "small" - ] + "type": "array", + "title": "Allowed LSP", + "description": "List of LSP servers this agent can use (if nil all LSPs are allowed)" }, - "options": { - "description": "General application options and settings", - "type": "object", - "properties": { - "context_paths": { - "description": "Context Paths", - "type": "array", - "items": { - "type": "string" - }, - "default": [ - ".github/copilot-instructions.md", - ".cursorrules", - ".cursor/rules/", - "CLAUDE.md", - "CLAUDE.local.md", - "GEMINI.md", - "gemini.md", - "crush.md", - "crush.local.md", - "Crush.md", - "Crush.local.md", - "CRUSH.md", - "CRUSH.local.md" - ] - }, - "data_directory": { - "description": "Data Directory", - "type": "string", - "default": ".crush" - }, - "debug": { - "description": "Enable debug", - "type": "boolean", - "default": false - }, - "debug_lsp": { - "description": "Enable debug l s p", - "type": "boolean", - "default": false - }, - "disable_auto_summarize": { - "description": "Disable Auto Summarize", - "type": "boolean", - "default": false - }, - "tui": { - "description": "T U I", - "type": "object", - "properties": { - "compact_mode": { - "description": "Enable compact mode", - "type": "boolean", - "default": false - } - }, - "required": [ - "compact_mode" - ] - } + "context_paths": { + "items": { + "type": "string" }, - "required": [ - "context_paths", - "tui", - "debug", - "debug_lsp", - "disable_auto_summarize", - "data_directory" - ] - }, - "providers": { - "description": "LLM provider configurations", - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "api_key": { - "description": "API key for authenticating with the provider", - "type": "string" - }, - "base_url": { - "description": "Base URL for the provider API (required for custom providers)", - "type": "string" - }, - "default_large_model": { - "description": "Default Large Model", - "type": "string" - }, - "default_small_model": { - "description": "Default Small Model", - "type": "string" - }, - "disabled": { - "description": "Disabled", - "type": "boolean", - "default": false - }, - "extra_headers": { - "description": "Additional HTTP headers to send with requests", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "extra_params": { - "description": "Additional provider-specific parameters", - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "id": { - "description": "I D", - "type": "string", - "enum": [ - "anthropic", - "openai", - "gemini", - "xai", - "openrouter" - ] - }, - "models": { - "description": "Models", - "type": "array", - "items": { - "type": "object", - "properties": { - "can_reason": { - "description": "Enable can reason", - "type": "boolean" - }, - "context_window": { - "description": "Context Window", - "type": "integer" - }, - "cost_per_1m_in": { - "description": "Cost Per1 M In", - "type": "number" - }, - "cost_per_1m_in_cached": { - "description": "Cost Per1 M In Cached", - "type": "number" - }, - "cost_per_1m_out": { - "description": "Cost Per1 M Out", - "type": "number" - }, - "cost_per_1m_out_cached": { - "description": "Cost Per1 M Out Cached", - "type": "number" - }, - "default_max_tokens": { - "description": "Default Max Tokens", - "type": "integer" - }, - "has_reasoning_effort": { - "description": "Enable has reasoning effort", - "type": "boolean" - }, - "id": { - "description": "I D", - "type": "string" - }, - "model": { - "description": "Name", - "type": "string" - }, - "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" - }, - "supports_attachments": { - "description": "Enable supports images", - "type": "boolean" - } - }, - "required": [ - "id", - "model", - "cost_per_1m_in", - "cost_per_1m_out", - "cost_per_1m_in_cached", - "cost_per_1m_out_cached", - "context_window", - "default_max_tokens", - "can_reason", - "reasoning_effort", - "has_reasoning_effort", - "supports_attachments" - ] - } - }, - "provider_type": { - "description": "Provider Type", - "type": "string" - } - }, - "required": [ - "id", - "provider_type", - "disabled" - ] - } + "type": "array", + "title": "Context Paths", + "description": "Custom context paths for this agent (additive to global context paths)" } }, + "type": "object", "required": [ - "models", - "options" + "model" ] }, "LSPConfig": { - "type": "object", "properties": { - "args": { - "description": "Args", - "type": "array", - "items": { - "type": "string" - } - }, - "command": { - "description": "Command", - "type": "string" - }, "enabled": { - "description": "Disabled", "type": "boolean", + "title": "Enabled", + "description": "Whether this LSP server is enabled", "default": true }, + "command": { + "type": "string", + "title": "Command", + "description": "Command to execute for the LSP server" + }, + "args": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Arguments", + "description": "Command line arguments for the LSP server" + }, "options": { - "description": "Options" + "title": "Options", + "description": "LSP server specific options" } }, + "type": "object", "required": [ - "enabled", - "command", - "args", - "options" + "command" ] }, "MCP": { - "type": "object", "properties": { - "args": { - "description": "Args", - "type": "array", - "items": { - "type": "string" - } - }, "command": { - "description": "Command to execute for stdio MCP servers", - "type": "string" + "type": "string", + "title": "Command", + "description": "Command to execute for stdio MCP servers" }, "env": { - "description": "Env list", - "type": "array", "items": { "type": "string" - } + }, + "type": "array", + "title": "Environment", + "description": "Environment variables for the MCP server" }, - "headers": { - "description": "HTTP headers for SSE MCP servers", - "type": "object", - "additionalProperties": { + "args": { + "items": { "type": "string" - } + }, + "type": "array", + "title": "Arguments", + "description": "Command line arguments for the MCP server" }, "type": { - "description": "Type", "type": "string", "enum": [ + "stdio", + "sse", "stdio", "sse" ], + "title": "Type", + "description": "Type of MCP connection", "default": "stdio" }, "url": { - "description": "URL for SSE MCP servers", - "type": "string" + "type": "string", + "title": "URL", + "description": "URL for SSE MCP servers" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object", + "title": "Headers", + "description": "HTTP headers for SSE MCP servers" } }, + "type": "object", "required": [ "command", - "env", - "args", - "type", - "url", - "headers" + "type" ] }, "Model": { - "type": "object", "properties": { - "can_reason": { - "description": "Enable can reason", - "type": "boolean" + "id": { + "type": "string", + "title": "Model ID", + "description": "Unique identifier for the model" }, - "context_window": { - "description": "Context Window", - "type": "integer" + "model": { + "type": "string", + "title": "Model Name", + "description": "Display name of the model" }, "cost_per_1m_in": { - "description": "Cost Per1 M In", - "type": "number" - }, - "cost_per_1m_in_cached": { - "description": "Cost Per1 M In Cached", - "type": "number" + "type": "number", + "minimum": 0, + "title": "Input Cost", + "description": "Cost per 1 million input tokens" }, "cost_per_1m_out": { - "description": "Cost Per1 M Out", - "type": "number" + "type": "number", + "minimum": 0, + "title": "Output Cost", + "description": "Cost per 1 million output tokens" }, - "cost_per_1m_out_cached": { - "description": "Cost Per1 M Out Cached", - "type": "number" + "cost_per_1m_in_cached": { + "type": "number", + "minimum": 0, + "title": "Cached Input Cost", + "description": "Cost per 1 million cached input tokens" }, - "default_max_tokens": { - "description": "Default Max Tokens", - "type": "integer" + "cost_per_1m_out_cached": { + "type": "number", + "minimum": 0, + "title": "Cached Output Cost", + "description": "Cost per 1 million cached output tokens" }, - "has_reasoning_effort": { - "description": "Enable has reasoning effort", - "type": "boolean" + "context_window": { + "type": "integer", + "minimum": 1, + "title": "Context Window", + "description": "Maximum context window size in tokens" }, - "id": { - "description": "I D", - "type": "string" + "default_max_tokens": { + "type": "integer", + "minimum": 1, + "title": "Default Max Tokens", + "description": "Default maximum tokens for responses" }, - "model": { - "description": "Name", - "type": "string" + "can_reason": { + "type": "boolean", + "title": "Can Reason", + "description": "Whether the model supports reasoning capabilities" }, "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" + "type": "string", + "title": "Reasoning Effort", + "description": "Default reasoning effort level for reasoning models" + }, + "has_reasoning_effort": { + "type": "boolean", + "title": "Has Reasoning Effort", + "description": "Whether the model supports reasoning effort configuration" }, "supports_attachments": { - "description": "Enable supports images", - "type": "boolean" + "type": "boolean", + "title": "Supports Images", + "description": "Whether the model supports image attachments" } }, + "type": "object", "required": [ "id", "model", @@ -1163,15 +266,14 @@ ] }, "Options": { - "description": "General application options and settings", - "type": "object", "properties": { "context_paths": { - "description": "Context Paths", - "type": "array", "items": { "type": "string" }, + "type": "array", + "title": "Context Paths", + "description": "List of paths to search for context files", "default": [ ".github/copilot-instructions.md", ".cursorrules", @@ -1188,318 +290,419 @@ "CRUSH.local.md" ] }, - "data_directory": { - "description": "Data Directory", - "type": "string", - "default": ".crush" + "tui": { + "$ref": "#/$defs/TUIOptions", + "title": "TUI Options", + "description": "Terminal UI configuration options" }, "debug": { - "description": "Enable debug", "type": "boolean", + "title": "Debug", + "description": "Enable debug logging", "default": false }, "debug_lsp": { - "description": "Enable debug l s p", "type": "boolean", + "title": "Debug LSP", + "description": "Enable LSP debug logging", "default": false }, "disable_auto_summarize": { - "description": "Disable Auto Summarize", "type": "boolean", + "title": "Disable Auto Summarize", + "description": "Disable automatic conversation summarization", "default": false }, - "tui": { - "description": "T U I", - "type": "object", - "properties": { - "compact_mode": { - "description": "Enable compact mode", - "type": "boolean", - "default": false - } - }, - "required": [ - "compact_mode" - ] + "data_directory": { + "type": "string", + "title": "Data Directory", + "description": "Directory for storing application data", + "default": ".crush" } }, - "required": [ - "context_paths", - "tui", - "debug", - "debug_lsp", - "disable_auto_summarize", - "data_directory" - ] + "type": "object" }, "PreferredModel": { - "description": "Large", - "type": "object", "properties": { - "max_tokens": { - "description": "Max Tokens", - "type": "integer" - }, "model_id": { - "description": "Model I D", "type": "string", "enum": [ - "claude-3-opus", - "claude-3-haiku", - "claude-3-5-sonnet-20241022", + "claude-opus-4-20250514", + "claude-sonnet-4-20250514", + "claude-3-7-sonnet-20250219", "claude-3-5-haiku-20241022", - "gpt-4", - "gpt-3.5-turbo", - "gpt-4-turbo", + "claude-3-5-sonnet-20240620", + "claude-3-5-sonnet-20241022", + "codex-mini-latest", + "o4-mini", + "o3", + "o3-pro", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.5-preview", + "o3-mini", + "gpt-4o", + "gpt-4o-mini", + "gemini-2.5-pro", + "gemini-2.5-flash", + "codex-mini-latest", + "o4-mini", + "o3", + "o3-pro", + "gpt-4.1", + "gpt-4.1-mini", + "gpt-4.1-nano", + "gpt-4.5-preview", + "o3-mini", "gpt-4o", "gpt-4o-mini", - "o1-preview", - "o1-mini", + "anthropic.claude-opus-4-20250514-v1:0", + "anthropic.claude-sonnet-4-20250514-v1:0", + "anthropic.claude-3-7-sonnet-20250219-v1:0", + "anthropic.claude-3-5-haiku-20241022-v1:0", "gemini-2.5-pro", "gemini-2.5-flash", - "grok-beta", + "grok-3-mini", + "grok-3", + "mistralai/mistral-small-3.2-24b-instruct:free", + "mistralai/mistral-small-3.2-24b-instruct", + "minimax/minimax-m1:extended", + "minimax/minimax-m1", + "google/gemini-2.5-flash-lite-preview-06-17", + "google/gemini-2.5-flash", + "google/gemini-2.5-pro", + "openai/o3-pro", + "x-ai/grok-3-mini", + "x-ai/grok-3", + "mistralai/magistral-small-2506", + "mistralai/magistral-medium-2506", + "mistralai/magistral-medium-2506:thinking", + "google/gemini-2.5-pro-preview", + "deepseek/deepseek-r1-0528", + "anthropic/claude-opus-4", + "anthropic/claude-sonnet-4", + "mistralai/devstral-small:free", + "mistralai/devstral-small", + "google/gemini-2.5-flash-preview-05-20", + "google/gemini-2.5-flash-preview-05-20:thinking", + "openai/codex-mini", + "mistralai/mistral-medium-3", + "google/gemini-2.5-pro-preview-05-06", + "arcee-ai/caller-large", + "arcee-ai/virtuoso-large", + "arcee-ai/virtuoso-medium-v2", + "qwen/qwen3-30b-a3b", + "qwen/qwen3-14b", + "qwen/qwen3-32b", + "qwen/qwen3-235b-a22b", + "google/gemini-2.5-flash-preview", + "google/gemini-2.5-flash-preview:thinking", + "openai/o4-mini-high", + "openai/o3", + "openai/o4-mini", + "openai/gpt-4.1", + "openai/gpt-4.1-mini", + "openai/gpt-4.1-nano", + "x-ai/grok-3-mini-beta", + "x-ai/grok-3-beta", + "meta-llama/llama-4-maverick", + "meta-llama/llama-4-scout", + "all-hands/openhands-lm-32b-v0.1", + "google/gemini-2.5-pro-exp-03-25", + "deepseek/deepseek-chat-v3-0324:free", + "deepseek/deepseek-chat-v3-0324", + "mistralai/mistral-small-3.1-24b-instruct:free", + "mistralai/mistral-small-3.1-24b-instruct", + "ai21/jamba-1.6-large", + "ai21/jamba-1.6-mini", + "openai/gpt-4.5-preview", + "google/gemini-2.0-flash-lite-001", + "anthropic/claude-3.7-sonnet", + "anthropic/claude-3.7-sonnet:beta", + "anthropic/claude-3.7-sonnet:thinking", + "mistralai/mistral-saba", + "openai/o3-mini-high", + "google/gemini-2.0-flash-001", + "qwen/qwen-turbo", + "qwen/qwen-plus", + "qwen/qwen-max", + "openai/o3-mini", + "mistralai/mistral-small-24b-instruct-2501", + "deepseek/deepseek-r1-distill-llama-70b", + "deepseek/deepseek-r1", + "mistralai/codestral-2501", + "deepseek/deepseek-chat", + "openai/o1", + "x-ai/grok-2-1212", + "meta-llama/llama-3.3-70b-instruct", + "amazon/nova-lite-v1", + "amazon/nova-micro-v1", + "amazon/nova-pro-v1", + "openai/gpt-4o-2024-11-20", + "mistralai/mistral-large-2411", + "mistralai/mistral-large-2407", + "mistralai/pixtral-large-2411", + "thedrummer/unslopnemo-12b", + "anthropic/claude-3.5-haiku:beta", + "anthropic/claude-3.5-haiku", + "anthropic/claude-3.5-haiku-20241022:beta", + "anthropic/claude-3.5-haiku-20241022", + "anthropic/claude-3.5-sonnet:beta", "anthropic/claude-3.5-sonnet", - "anthropic/claude-3.5-haiku" - ] + "x-ai/grok-beta", + "mistralai/ministral-8b", + "mistralai/ministral-3b", + "nvidia/llama-3.1-nemotron-70b-instruct", + "google/gemini-flash-1.5-8b", + "meta-llama/llama-3.2-11b-vision-instruct", + "meta-llama/llama-3.2-3b-instruct", + "qwen/qwen-2.5-72b-instruct", + "mistralai/pixtral-12b", + "cohere/command-r-plus-08-2024", + "cohere/command-r-08-2024", + "microsoft/phi-3.5-mini-128k-instruct", + "nousresearch/hermes-3-llama-3.1-70b", + "openai/gpt-4o-2024-08-06", + "meta-llama/llama-3.1-405b-instruct", + "meta-llama/llama-3.1-70b-instruct", + "meta-llama/llama-3.1-8b-instruct", + "mistralai/mistral-nemo", + "openai/gpt-4o-mini", + "openai/gpt-4o-mini-2024-07-18", + "anthropic/claude-3.5-sonnet-20240620:beta", + "anthropic/claude-3.5-sonnet-20240620", + "mistralai/mistral-7b-instruct-v0.3", + "mistralai/mistral-7b-instruct:free", + "mistralai/mistral-7b-instruct", + "microsoft/phi-3-mini-128k-instruct", + "microsoft/phi-3-medium-128k-instruct", + "google/gemini-flash-1.5", + "openai/gpt-4o-2024-05-13", + "openai/gpt-4o", + "openai/gpt-4o:extended", + "meta-llama/llama-3-8b-instruct", + "meta-llama/llama-3-70b-instruct", + "mistralai/mixtral-8x22b-instruct", + "openai/gpt-4-turbo", + "google/gemini-pro-1.5", + "cohere/command-r-plus", + "cohere/command-r-plus-04-2024", + "cohere/command-r", + "anthropic/claude-3-haiku:beta", + "anthropic/claude-3-haiku", + "anthropic/claude-3-opus:beta", + "anthropic/claude-3-opus", + "anthropic/claude-3-sonnet:beta", + "anthropic/claude-3-sonnet", + "cohere/command-r-03-2024", + "mistralai/mistral-large", + "openai/gpt-3.5-turbo-0613", + "openai/gpt-4-turbo-preview", + "mistralai/mistral-small", + "mistralai/mistral-tiny", + "mistralai/mixtral-8x7b-instruct", + "openai/gpt-4-1106-preview", + "mistralai/mistral-7b-instruct-v0.1", + "openai/gpt-3.5-turbo-16k", + "openai/gpt-4", + "openai/gpt-4-0314" + ], + "title": "Model ID", + "description": "ID of the preferred model" }, "provider": { - "description": "Provider", "type": "string", "enum": [ "anthropic", "openai", "gemini", + "azure", + "bedrock", + "vertex", "xai", "openrouter" - ] + ], + "title": "Provider", + "description": "Provider for the preferred model" }, "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" + "type": "string", + "title": "Reasoning Effort", + "description": "Override reasoning effort for this model" + }, + "max_tokens": { + "type": "integer", + "minimum": 1, + "title": "Max Tokens", + "description": "Override max tokens for this model" }, "think": { - "description": "Enable think", - "type": "boolean" + "type": "boolean", + "title": "Think", + "description": "Enable thinking for reasoning models", + "default": false } }, + "type": "object", "required": [ "model_id", "provider" ] }, "PreferredModels": { - "description": "Preferred model configurations for large and small model types", - "type": "object", "properties": { "large": { - "description": "Large", - "type": "object", - "properties": { - "max_tokens": { - "description": "Max Tokens", - "type": "integer" - }, - "model_id": { - "description": "Model I D", - "type": "string", - "enum": [ - "claude-3-opus", - "claude-3-haiku", - "claude-3-5-sonnet-20241022", - "claude-3-5-haiku-20241022", - "gpt-4", - "gpt-3.5-turbo", - "gpt-4-turbo", - "gpt-4o", - "gpt-4o-mini", - "o1-preview", - "o1-mini", - "gemini-2.5-pro", - "gemini-2.5-flash", - "grok-beta", - "anthropic/claude-3.5-sonnet", - "anthropic/claude-3.5-haiku" - ] - }, - "provider": { - "description": "Provider", - "type": "string", - "enum": [ - "anthropic", - "openai", - "gemini", - "xai", - "openrouter" - ] - }, - "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" - }, - "think": { - "description": "Enable think", - "type": "boolean" - } - }, - "required": [ - "model_id", - "provider" - ] + "$ref": "#/$defs/PreferredModel", + "title": "Large Model", + "description": "Preferred model configuration for large model type" }, "small": { - "description": "Small", - "$ref": "#/definitions/PreferredModel" + "$ref": "#/$defs/PreferredModel", + "title": "Small Model", + "description": "Preferred model configuration for small model type" } }, - "required": [ - "large", - "small" - ] + "type": "object" }, "ProviderConfig": { - "type": "object", "properties": { - "api_key": { - "description": "API key for authenticating with the provider", - "type": "string" + "id": { + "type": "string", + "enum": [ + "anthropic", + "openai", + "gemini", + "azure", + "bedrock", + "vertex", + "xai", + "openrouter" + ], + "title": "Provider ID", + "description": "Unique identifier for the provider" }, "base_url": { - "description": "Base URL for the provider API (required for custom providers)", - "type": "string" + "type": "string", + "title": "Base URL", + "description": "Base URL for the provider API (required for custom providers)" }, - "default_large_model": { - "description": "Default Large Model", - "type": "string" + "provider_type": { + "type": "string", + "title": "Provider Type", + "description": "Type of the provider (openai" }, - "default_small_model": { - "description": "Default Small Model", - "type": "string" + "api_key": { + "type": "string", + "title": "API Key", + "description": "API key for authenticating with the provider" }, "disabled": { - "description": "Disabled", "type": "boolean", + "title": "Disabled", + "description": "Whether this provider is disabled", "default": false }, "extra_headers": { - "description": "Additional HTTP headers to send with requests", - "type": "object", "additionalProperties": { "type": "string" - } + }, + "type": "object", + "title": "Extra Headers", + "description": "Additional HTTP headers to send with requests" }, "extra_params": { - "description": "Additional provider-specific parameters", - "type": "object", "additionalProperties": { "type": "string" - } + }, + "type": "object", + "title": "Extra Parameters", + "description": "Additional provider-specific parameters" }, - "id": { - "description": "I D", + "default_large_model": { "type": "string", - "enum": [ - "anthropic", - "openai", - "gemini", - "xai", - "openrouter" - ] + "title": "Default Large Model", + "description": "Default model ID for large model type" + }, + "default_small_model": { + "type": "string", + "title": "Default Small Model", + "description": "Default model ID for small model type" }, "models": { - "description": "Models", - "type": "array", "items": { - "type": "object", - "properties": { - "can_reason": { - "description": "Enable can reason", - "type": "boolean" - }, - "context_window": { - "description": "Context Window", - "type": "integer" - }, - "cost_per_1m_in": { - "description": "Cost Per1 M In", - "type": "number" - }, - "cost_per_1m_in_cached": { - "description": "Cost Per1 M In Cached", - "type": "number" - }, - "cost_per_1m_out": { - "description": "Cost Per1 M Out", - "type": "number" - }, - "cost_per_1m_out_cached": { - "description": "Cost Per1 M Out Cached", - "type": "number" - }, - "default_max_tokens": { - "description": "Default Max Tokens", - "type": "integer" - }, - "has_reasoning_effort": { - "description": "Enable has reasoning effort", - "type": "boolean" - }, - "id": { - "description": "I D", - "type": "string" - }, - "model": { - "description": "Name", - "type": "string" - }, - "reasoning_effort": { - "description": "Reasoning Effort", - "type": "string" - }, - "supports_attachments": { - "description": "Enable supports images", - "type": "boolean" - } - }, - "required": [ - "id", - "model", - "cost_per_1m_in", - "cost_per_1m_out", - "cost_per_1m_in_cached", - "cost_per_1m_out_cached", - "context_window", - "default_max_tokens", - "can_reason", - "reasoning_effort", - "has_reasoning_effort", - "supports_attachments" - ] - } - }, - "provider_type": { - "description": "Provider Type", - "type": "string" + "$ref": "#/$defs/Model" + }, + "type": "array", + "title": "Models", + "description": "List of available models for this provider" } }, + "type": "object", "required": [ - "id", - "provider_type", - "disabled" + "provider_type" ] }, "TUIOptions": { - "description": "T U I", - "type": "object", "properties": { "compact_mode": { - "description": "Enable compact mode", "type": "boolean", + "title": "Compact Mode", + "description": "Enable compact mode for the TUI", "default": false } }, + "type": "object", "required": [ "compact_mode" ] } - } + }, + "properties": { + "models": { + "$ref": "#/$defs/PreferredModels", + "title": "Models", + "description": "Preferred model configurations for large and small model types" + }, + "providers": { + "additionalProperties": { + "$ref": "#/$defs/ProviderConfig" + }, + "type": "object", + "title": "Providers", + "description": "LLM provider configurations" + }, + "agents": { + "additionalProperties": { + "$ref": "#/$defs/Agent" + }, + "type": "object", + "title": "Agents", + "description": "Agent configurations for different tasks" + }, + "mcp": { + "additionalProperties": { + "$ref": "#/$defs/MCP" + }, + "type": "object", + "title": "MCP", + "description": "Model Control Protocol server configurations" + }, + "lsp": { + "additionalProperties": { + "$ref": "#/$defs/LSPConfig" + }, + "type": "object", + "title": "LSP", + "description": "Language Server Protocol configurations" + }, + "options": { + "$ref": "#/$defs/Options", + "title": "Options", + "description": "General application options and settings" + } + }, + "type": "object", + "title": "Crush Configuration", + "description": "Configuration schema for the Crush application" } diff --git a/crush.json b/crush.json index 1b04ea6c24f8b64a3a12ceb47551f3177fa66302..4937665c513258840f1efb4f88fb2bdd73f6ff68 100644 --- a/crush.json +++ b/crush.json @@ -1,6 +1,7 @@ { + "$schema": "./crush-schema.json", "lsp": { - "Go": { + "go": { "command": "gopls" } } diff --git a/cspell.json b/cspell.json index d62c817e8c8699e6172e576eb0f91602dd8417a3..d98b1326e54c8b62c7ad700fe19b4cbbe3e4f672 100644 --- a/cspell.json +++ b/cspell.json @@ -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"]} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/go.mod b/go.mod index 29bcf5e483cb43dc7038d9220bc8b5259719e96d..0e3f432a3e3262516cc910f1e3c0c309b05d0e8a 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,14 @@ require ( mvdan.cc/sh/v3 v3.11.0 ) +require ( + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect + github.com/invopop/jsonschema v0.13.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect +) + require ( cloud.google.com/go v0.116.0 // indirect cloud.google.com/go/auth v0.13.0 // indirect diff --git a/go.sum b/go.sum index ddbea31f43ce1c6ec4235f3c7af740cca67795fe..234b9663cab709b9babde6e324374fa7022cb1bc 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,12 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/charlievieth/fastwalk v1.0.11 h1:5sLT/q9+d9xMdpKExawLppqvXFZCVKf6JHnr2u/ufj8= github.com/charlievieth/fastwalk v1.0.11/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI= github.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 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -158,6 +165,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930= github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE= github.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= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= diff --git a/internal/config/config.go b/internal/config/config.go index 3caf9f01c4afdba4dd2c29c43fc690dd360173ef..0cccbdfffe5dc1c42e21b03bd9a20d7112005bee 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,6 +14,7 @@ import ( "github.com/charmbracelet/crush/internal/fur/provider" "github.com/charmbracelet/crush/internal/logging" + "github.com/invopop/jsonschema" ) const ( @@ -55,18 +56,18 @@ const ( ) type Model struct { - ID string `json:"id"` - Name string `json:"model"` - CostPer1MIn float64 `json:"cost_per_1m_in"` - CostPer1MOut float64 `json:"cost_per_1m_out"` - CostPer1MInCached float64 `json:"cost_per_1m_in_cached"` - CostPer1MOutCached float64 `json:"cost_per_1m_out_cached"` - ContextWindow int64 `json:"context_window"` - DefaultMaxTokens int64 `json:"default_max_tokens"` - CanReason bool `json:"can_reason"` - ReasoningEffort string `json:"reasoning_effort"` - HasReasoningEffort bool `json:"has_reasoning_effort"` - SupportsImages bool `json:"supports_attachments"` + ID string `json:"id" jsonschema:"title=Model ID,description=Unique identifier for the model"` + Name string `json:"model" jsonschema:"title=Model Name,description=Display name of the model"` + CostPer1MIn float64 `json:"cost_per_1m_in" jsonschema:"title=Input Cost,description=Cost per 1 million input tokens,minimum=0"` + CostPer1MOut float64 `json:"cost_per_1m_out" jsonschema:"title=Output Cost,description=Cost per 1 million output tokens,minimum=0"` + CostPer1MInCached float64 `json:"cost_per_1m_in_cached" jsonschema:"title=Cached Input Cost,description=Cost per 1 million cached input tokens,minimum=0"` + CostPer1MOutCached float64 `json:"cost_per_1m_out_cached" jsonschema:"title=Cached Output Cost,description=Cost per 1 million cached output tokens,minimum=0"` + ContextWindow int64 `json:"context_window" jsonschema:"title=Context Window,description=Maximum context window size in tokens,minimum=1"` + DefaultMaxTokens int64 `json:"default_max_tokens" jsonschema:"title=Default Max Tokens,description=Default maximum tokens for responses,minimum=1"` + CanReason bool `json:"can_reason" jsonschema:"title=Can Reason,description=Whether the model supports reasoning capabilities"` + ReasoningEffort string `json:"reasoning_effort" jsonschema:"title=Reasoning Effort,description=Default reasoning effort level for reasoning models"` + HasReasoningEffort bool `json:"has_reasoning_effort" jsonschema:"title=Has Reasoning Effort,description=Whether the model supports reasoning effort configuration"` + SupportsImages bool `json:"supports_attachments" jsonschema:"title=Supports Images,description=Whether the model supports image attachments"` } type VertexAIOptions struct { @@ -76,46 +77,46 @@ type VertexAIOptions struct { } type ProviderConfig struct { - ID provider.InferenceProvider `json:"id"` - BaseURL string `json:"base_url,omitempty"` - ProviderType provider.Type `json:"provider_type"` - APIKey string `json:"api_key,omitempty"` - Disabled bool `json:"disabled"` - ExtraHeaders map[string]string `json:"extra_headers,omitempty"` + ID provider.InferenceProvider `json:"id,omitempty" jsonschema:"title=Provider ID,description=Unique identifier for the provider"` + BaseURL string `json:"base_url,omitempty" jsonschema:"title=Base URL,description=Base URL for the provider API (required for custom providers)"` + ProviderType provider.Type `json:"provider_type" jsonschema:"title=Provider Type,description=Type of the provider (openai, anthropic, etc.)"` + APIKey string `json:"api_key,omitempty" jsonschema:"title=API Key,description=API key for authenticating with the provider"` + Disabled bool `json:"disabled,omitempty" jsonschema:"title=Disabled,description=Whether this provider is disabled,default=false"` + ExtraHeaders map[string]string `json:"extra_headers,omitempty" jsonschema:"title=Extra Headers,description=Additional HTTP headers to send with requests"` // used for e.x for vertex to set the project - ExtraParams map[string]string `json:"extra_params,omitempty"` + ExtraParams map[string]string `json:"extra_params,omitempty" jsonschema:"title=Extra Parameters,description=Additional provider-specific parameters"` - DefaultLargeModel string `json:"default_large_model,omitempty"` - DefaultSmallModel string `json:"default_small_model,omitempty"` + DefaultLargeModel string `json:"default_large_model,omitempty" jsonschema:"title=Default Large Model,description=Default model ID for large model type"` + DefaultSmallModel string `json:"default_small_model,omitempty" jsonschema:"title=Default Small Model,description=Default model ID for small model type"` - Models []Model `json:"models,omitempty"` + Models []Model `json:"models,omitempty" jsonschema:"title=Models,description=List of available models for this provider"` } type Agent struct { - ID AgentID `json:"id"` - Name string `json:"name"` - Description string `json:"description,omitempty"` + ID AgentID `json:"id,omitempty" jsonschema:"title=Agent ID,description=Unique identifier for the agent,enum=coder,enum=task"` + Name string `json:"name,omitempty" jsonschema:"title=Name,description=Display name of the agent"` + Description string `json:"description,omitempty" jsonschema:"title=Description,description=Description of what the agent does"` // This is the id of the system prompt used by the agent - Disabled bool `json:"disabled"` + Disabled bool `json:"disabled,omitempty" jsonschema:"title=Disabled,description=Whether this agent is disabled,default=false"` - Model ModelType `json:"model"` + Model ModelType `json:"model" jsonschema:"title=Model Type,description=Type of model to use (large or small),enum=large,enum=small"` // The available tools for the agent // if this is nil, all tools are available - AllowedTools []string `json:"allowed_tools"` + 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)"` // this tells us which MCPs are available for this agent // if this is empty all mcps are available // the string array is the list of tools from the AllowedMCP the agent has available // if the string array is nil, all tools from the AllowedMCP are available - AllowedMCP map[string][]string `json:"allowed_mcp"` + 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"` // The list of LSPs that this agent can use // if this is nil, all LSPs are available - AllowedLSP []string `json:"allowed_lsp"` + 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)"` // Overrides the context paths for this agent - ContextPaths []string `json:"context_paths"` + ContextPaths []string `json:"context_paths,omitempty" jsonschema:"title=Context Paths,description=Custom context paths for this agent (additive to global context paths)"` } type MCPType string @@ -126,69 +127,70 @@ const ( ) type MCP struct { - Command string `json:"command"` - Env []string `json:"env"` - Args []string `json:"args"` - Type MCPType `json:"type"` - URL string `json:"url"` - Headers map[string]string `json:"headers"` + Command string `json:"command" jsonschema:"title=Command,description=Command to execute for stdio MCP servers"` + Env []string `json:"env,omitempty" jsonschema:"title=Environment,description=Environment variables for the MCP server"` + Args []string `json:"args,omitempty" jsonschema:"title=Arguments,description=Command line arguments for the MCP server"` + Type MCPType `json:"type" jsonschema:"title=Type,description=Type of MCP connection,enum=stdio,enum=sse,default=stdio"` + URL string `json:"url,omitempty" jsonschema:"title=URL,description=URL for SSE MCP servers"` + // TODO: maybe make it possible to get the value from the env + Headers map[string]string `json:"headers,omitempty" jsonschema:"title=Headers,description=HTTP headers for SSE MCP servers"` } type LSPConfig struct { - Disabled bool `json:"enabled"` - Command string `json:"command"` - Args []string `json:"args"` - Options any `json:"options"` + Disabled bool `json:"enabled,omitempty" jsonschema:"title=Enabled,description=Whether this LSP server is enabled,default=true"` + Command string `json:"command" jsonschema:"title=Command,description=Command to execute for the LSP server"` + Args []string `json:"args,omitempty" jsonschema:"title=Arguments,description=Command line arguments for the LSP server"` + Options any `json:"options,omitempty" jsonschema:"title=Options,description=LSP server specific options"` } type TUIOptions struct { - CompactMode bool `json:"compact_mode"` + CompactMode bool `json:"compact_mode" jsonschema:"title=Compact Mode,description=Enable compact mode for the TUI,default=false"` // Here we can add themes later or any TUI related options } type Options struct { - ContextPaths []string `json:"context_paths"` - TUI TUIOptions `json:"tui"` - Debug bool `json:"debug"` - DebugLSP bool `json:"debug_lsp"` - DisableAutoSummarize bool `json:"disable_auto_summarize"` + ContextPaths []string `json:"context_paths,omitempty" jsonschema:"title=Context Paths,description=List of paths to search for context files"` + TUI TUIOptions `json:"tui,omitempty" jsonschema:"title=TUI Options,description=Terminal UI configuration options"` + Debug bool `json:"debug,omitempty" jsonschema:"title=Debug,description=Enable debug logging,default=false"` + DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"title=Debug LSP,description=Enable LSP debug logging,default=false"` + DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"title=Disable Auto Summarize,description=Disable automatic conversation summarization,default=false"` // Relative to the cwd - DataDirectory string `json:"data_directory"` + DataDirectory string `json:"data_directory,omitempty" jsonschema:"title=Data Directory,description=Directory for storing application data,default=.crush"` } type PreferredModel struct { - ModelID string `json:"model_id"` - Provider provider.InferenceProvider `json:"provider"` + ModelID string `json:"model_id" jsonschema:"title=Model ID,description=ID of the preferred model"` + Provider provider.InferenceProvider `json:"provider" jsonschema:"title=Provider,description=Provider for the preferred model"` // ReasoningEffort overrides the default reasoning effort for this model - ReasoningEffort string `json:"reasoning_effort,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty" jsonschema:"title=Reasoning Effort,description=Override reasoning effort for this model"` // MaxTokens overrides the default max tokens for this model - MaxTokens int64 `json:"max_tokens,omitempty"` + MaxTokens int64 `json:"max_tokens,omitempty" jsonschema:"title=Max Tokens,description=Override max tokens for this model,minimum=1"` // Think indicates if the model should think, only applicable for anthropic reasoning models - Think bool `json:"think,omitempty"` + Think bool `json:"think,omitempty" jsonschema:"title=Think,description=Enable thinking for reasoning models,default=false"` } type PreferredModels struct { - Large PreferredModel `json:"large"` - Small PreferredModel `json:"small"` + Large PreferredModel `json:"large,omitempty" jsonschema:"title=Large Model,description=Preferred model configuration for large model type"` + Small PreferredModel `json:"small,omitempty" jsonschema:"title=Small Model,description=Preferred model configuration for small model type"` } type Config struct { - Models PreferredModels `json:"models"` + Models PreferredModels `json:"models,omitempty" jsonschema:"title=Models,description=Preferred model configurations for large and small model types"` // List of configured providers - Providers map[provider.InferenceProvider]ProviderConfig `json:"providers,omitempty"` + Providers map[provider.InferenceProvider]ProviderConfig `json:"providers,omitempty" jsonschema:"title=Providers,description=LLM provider configurations"` // List of configured agents - Agents map[AgentID]Agent `json:"agents,omitempty"` + Agents map[AgentID]Agent `json:"agents,omitempty" jsonschema:"title=Agents,description=Agent configurations for different tasks"` // List of configured MCPs - MCP map[string]MCP `json:"mcp,omitempty"` + MCP map[string]MCP `json:"mcp,omitempty" jsonschema:"title=MCP,description=Model Control Protocol server configurations"` // List of configured LSPs - LSP map[string]LSPConfig `json:"lsp,omitempty"` + LSP map[string]LSPConfig `json:"lsp,omitempty" jsonschema:"title=LSP,description=Language Server Protocol configurations"` // Miscellaneous options - Options Options `json:"options"` + Options Options `json:"options,omitempty" jsonschema:"title=Options,description=General application options and settings"` } var ( @@ -502,27 +504,23 @@ func mergeAgents(base, global, local *Config) { } for agentID, newAgent := range cfg.Agents { if _, ok := base.Agents[agentID]; !ok { - // New agent - apply defaults - newAgent.ID = agentID // Ensure the ID is set correctly + newAgent.ID = agentID if newAgent.Model == "" { - newAgent.Model = LargeModel // Default model type + newAgent.Model = LargeModel } - // Context paths are always additive - start with global, then add custom if len(newAgent.ContextPaths) > 0 { newAgent.ContextPaths = append(base.Options.ContextPaths, newAgent.ContextPaths...) } else { - newAgent.ContextPaths = base.Options.ContextPaths // Use global context paths only + newAgent.ContextPaths = base.Options.ContextPaths } base.Agents[agentID] = newAgent } else { baseAgent := base.Agents[agentID] - // Special handling for known agents - only allow model changes if agentID == AgentCoder || agentID == AgentTask { if newAgent.Model != "" { baseAgent.Model = newAgent.Model } - // For known agents, only allow MCP and LSP configuration if newAgent.AllowedMCP != nil { baseAgent.AllowedMCP = newAgent.AllowedMCP } @@ -534,7 +532,6 @@ func mergeAgents(base, global, local *Config) { baseAgent.ContextPaths = append(baseAgent.ContextPaths, newAgent.ContextPaths...) } } else { - // Custom agents - allow full merging if newAgent.Name != "" { baseAgent.Name = newAgent.Name } @@ -544,13 +541,11 @@ func mergeAgents(base, global, local *Config) { if newAgent.Model != "" { baseAgent.Model = newAgent.Model } else if baseAgent.Model == "" { - baseAgent.Model = LargeModel // Default fallback + baseAgent.Model = LargeModel } - // Boolean fields - always update (including false values) baseAgent.Disabled = newAgent.Disabled - // Slice/Map fields - update if provided (including empty slices/maps) if newAgent.AllowedTools != nil { baseAgent.AllowedTools = newAgent.AllowedTools } @@ -560,7 +555,6 @@ func mergeAgents(base, global, local *Config) { if newAgent.AllowedLSP != nil { baseAgent.AllowedLSP = newAgent.AllowedLSP } - // Context paths are additive for custom agents too if len(newAgent.ContextPaths) > 0 { baseAgent.ContextPaths = append(baseAgent.ContextPaths, newAgent.ContextPaths...) } @@ -596,6 +590,7 @@ func mergeProviderConfigs(base, global, local *Config) { continue } for providerName, p := range cfg.Providers { + p.ID = providerName if _, ok := base.Providers[providerName]; !ok { base.Providers[providerName] = p } else { @@ -616,36 +611,36 @@ func mergeProviderConfigs(base, global, local *Config) { base.Providers = finalProviders } -func providerDefaultConfig(providerId provider.InferenceProvider) ProviderConfig { - switch providerId { +func providerDefaultConfig(providerID provider.InferenceProvider) ProviderConfig { + switch providerID { case provider.InferenceProviderAnthropic: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeAnthropic, } case provider.InferenceProviderOpenAI: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeOpenAI, } case provider.InferenceProviderGemini: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeGemini, } case provider.InferenceProviderBedrock: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeBedrock, } case provider.InferenceProviderAzure: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeAzure, } case provider.InferenceProviderOpenRouter: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeOpenAI, BaseURL: "https://openrouter.ai/api/v1", ExtraHeaders: map[string]string{ @@ -655,18 +650,18 @@ func providerDefaultConfig(providerId provider.InferenceProvider) ProviderConfig } case provider.InferenceProviderXAI: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeXAI, BaseURL: "https://api.x.ai/v1", } case provider.InferenceProviderVertexAI: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeVertexAI, } default: return ProviderConfig{ - ID: providerId, + ID: providerID, ProviderType: provider.TypeOpenAI, } } @@ -1433,3 +1428,27 @@ func (c *Config) validateCompleteness(errors *ValidationErrors) { } } } + +// JSONSchemaExtend adds custom schema properties for AgentID +func (AgentID) JSONSchemaExtend(schema *jsonschema.Schema) { + schema.Enum = []any{ + string(AgentCoder), + string(AgentTask), + } +} + +// JSONSchemaExtend adds custom schema properties for ModelType +func (ModelType) JSONSchemaExtend(schema *jsonschema.Schema) { + schema.Enum = []any{ + string(LargeModel), + string(SmallModel), + } +} + +// JSONSchemaExtend adds custom schema properties for MCPType +func (MCPType) JSONSchemaExtend(schema *jsonschema.Schema) { + schema.Enum = []any{ + string(MCPStdio), + string(MCPSse), + } +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b48a9eba0a92a9f9239d6f6e3526c24cc8790ac9..2b4764489dd7795df0473c22eb529bbc65fc9a2b 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1051,7 +1051,6 @@ func TestProviderValidation_KnownProviderValid(t *testing.T) { DefaultMaxTokens: 2048, }, }, - }, }, } diff --git a/internal/config/provider_mock.go b/internal/config/provider_mock.go index 73d39d761b15ae682b272f356c62234aaa3ca0ad..801afdd8d6c9891eb47fa53294c047917b031637 100644 --- a/internal/config/provider_mock.go +++ b/internal/config/provider_mock.go @@ -126,28 +126,28 @@ func MockProviders() []provider.Provider { DefaultSmallModelID: "grok-3-mini", Models: []provider.Model{ { - ID: "grok-3", - Name: "Grok 3", - CostPer1MIn: 3.0, - CostPer1MOut: 15.0, - CostPer1MInCached: 0.0, + ID: "grok-3", + Name: "Grok 3", + CostPer1MIn: 3.0, + CostPer1MOut: 15.0, + CostPer1MInCached: 0.0, CostPer1MOutCached: 0.75, - ContextWindow: 131072, - DefaultMaxTokens: 20000, - CanReason: false, - SupportsImages: false, + ContextWindow: 131072, + DefaultMaxTokens: 20000, + CanReason: false, + SupportsImages: false, }, { - ID: "grok-3-mini", - Name: "Grok 3 Mini", - CostPer1MIn: 0.3, - CostPer1MOut: 0.5, - CostPer1MInCached: 0.0, + ID: "grok-3-mini", + Name: "Grok 3 Mini", + CostPer1MIn: 0.3, + CostPer1MOut: 0.5, + CostPer1MInCached: 0.0, CostPer1MOutCached: 0.075, - ContextWindow: 131072, - DefaultMaxTokens: 20000, - CanReason: true, - SupportsImages: false, + ContextWindow: 131072, + DefaultMaxTokens: 20000, + CanReason: true, + SupportsImages: false, }, }, }, diff --git a/internal/config/provider_test.go b/internal/config/provider_test.go index 8f2a31f06ab121fa049e7ca8bed159976cb2e92f..53a084d244c2d48538a514e8c72530a3850782d7 100644 --- a/internal/config/provider_test.go +++ b/internal/config/provider_test.go @@ -78,4 +78,4 @@ func TestProviders_ModelCapabilities(t *testing.T) { assert.True(t, foundReasoning) assert.True(t, foundNonReasoning) -} \ No newline at end of file +}