@@ -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
+ }
+ }
}
}
}
@@ -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",
@@ -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),
+ }
+}