diff --git a/README.ja.md b/README.ja.md index 489484f90..d28f05dc2 100644 --- a/README.ja.md +++ b/README.ja.md @@ -510,7 +510,6 @@ clawdroid/ │ ├── core/ # 共有コア (data, domain, model, ui) │ └── feature/ # 機能モジュール (chat, settings) ├── workspace/ # テンプレートファイル (IDENTITY.md, SOUL.md 等) -├── config/ # config.example.json ├── Makefile ├── go.mod └── .goreleaser.yaml diff --git a/README.md b/README.md index 2ef5cd28d..420a95494 100644 --- a/README.md +++ b/README.md @@ -512,7 +512,6 @@ clawdroid/ │ ├── core/ # Shared core (data, domain, model, ui) │ └── feature/ # Feature modules (chat, settings) ├── workspace/ # Template files (IDENTITY.md, SOUL.md, etc.) -├── config/ # config.example.json ├── Makefile ├── go.mod └── .goreleaser.yaml diff --git a/config/config.example.json b/config/config.example.json deleted file mode 100644 index cb52cd983..000000000 --- a/config/config.example.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "llm": { - "model": "", - "api_key": "", - "base_url": "" - }, - "agents": { - "defaults": { - "workspace": "~/.clawdroid/workspace", - "data_dir": "~/.clawdroid/data", - "restrict_to_workspace": true, - "max_tokens": 8192, - "context_window": 128000, - "temperature": 0.7, - "max_tool_iterations": 10 - } - }, - "channels": { - "telegram": { - "enabled": false, - "token": "YOUR_TELEGRAM_BOT_TOKEN", - "proxy": "", - "allow_from": ["YOUR_USER_ID"] - }, - "discord": { - "enabled": false, - "token": "YOUR_DISCORD_BOT_TOKEN", - "allow_from": [] - }, - "whatsapp": { - "enabled": false, - "bridge_url": "ws://localhost:3001", - "allow_from": [] - }, - "slack": { - "enabled": false, - "bot_token": "xoxb-YOUR-BOT-TOKEN", - "app_token": "xapp-YOUR-APP-TOKEN", - "allow_from": [] - }, - "line": { - "enabled": false, - "channel_secret": "YOUR_LINE_CHANNEL_SECRET", - "channel_access_token": "YOUR_LINE_CHANNEL_ACCESS_TOKEN", - "webhook_host": "127.0.0.1", - "webhook_port": 18791, - "webhook_path": "/webhook/line", - "allow_from": [] - }, - "websocket": { - "enabled": true, - "host": "127.0.0.1", - "port": 18793, - "path": "/ws", - "allow_from": [] - } - }, - "tools": { - "exec": { - "enabled": false - }, - "android": { - "enabled": true - }, - "memory": { - "enabled": true - }, - "web": { - "brave": { - "enabled": false, - "api_key": "YOUR_BRAVE_API_KEY", - "max_results": 5 - }, - "duckduckgo": { - "enabled": true, - "max_results": 5 - } - }, - "mcp": { - "example-stdio": { - "command": "npx", - "args": ["-y", "@example/mcp-server"], - "description": "Example local MCP server (stdio)", - "enabled": false - }, - "example-http": { - "url": "https://mcp.example.com/mcp", - "headers": { - "Authorization": "Bearer YOUR_TOKEN" - }, - "description": "Example remote MCP server (HTTP)", - "enabled": false - } - } - }, - "heartbeat": { - "enabled": true, - "interval": 30 - }, - "gateway": { - "host": "127.0.0.1", - "port": 18790, - "api_key": "" - }, - "rate_limits": { - "max_tool_calls_per_minute": 30, - "max_requests_per_minute": 15 - } -} diff --git a/pkg/config/config.go b/pkg/config/config.go index 9a69f4d86..ef2b65eae 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -44,123 +44,123 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error { } type LLMConfig struct { - Model string `json:"model" env:"CLAWDROID_LLM_MODEL"` - APIKey string `json:"api_key" env:"CLAWDROID_LLM_API_KEY"` - BaseURL string `json:"base_url" env:"CLAWDROID_LLM_BASE_URL"` + Model string `json:"model" label:"Model" env:"CLAWDROID_LLM_MODEL"` + APIKey string `json:"api_key" label:"API Key" env:"CLAWDROID_LLM_API_KEY"` + BaseURL string `json:"base_url" label:"Base URL" env:"CLAWDROID_LLM_BASE_URL"` } type Config struct { - LLM LLMConfig `json:"llm"` - Agents AgentsConfig `json:"agents"` - Channels ChannelsConfig `json:"channels"` - Gateway GatewayConfig `json:"gateway"` - Tools ToolsConfig `json:"tools"` - Heartbeat HeartbeatConfig `json:"heartbeat"` - RateLimits RateLimitsConfig `json:"rate_limits"` + LLM LLMConfig `json:"llm" label:"LLM"` + Agents AgentsConfig `json:"agents" label:"Agent Defaults"` + Channels ChannelsConfig `json:"channels" label:"Messaging Channels"` + Gateway GatewayConfig `json:"gateway" label:"Gateway"` + Tools ToolsConfig `json:"tools" label:"Tool Settings"` + Heartbeat HeartbeatConfig `json:"heartbeat" label:"Heartbeat"` + RateLimits RateLimitsConfig `json:"rate_limits" label:"Rate Limits"` mu sync.RWMutex } type AgentsConfig struct { - Defaults AgentDefaults `json:"defaults"` + Defaults AgentDefaults `json:"defaults" label:"Defaults"` } type AgentDefaults struct { - Workspace string `json:"workspace" env:"CLAWDROID_AGENTS_DEFAULTS_WORKSPACE"` - DataDir string `json:"data_dir" env:"CLAWDROID_AGENTS_DEFAULTS_DATA_DIR"` - RestrictToWorkspace bool `json:"restrict_to_workspace" env:"CLAWDROID_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"` - MaxTokens int `json:"max_tokens" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOKENS"` - ContextWindow int `json:"context_window" env:"CLAWDROID_AGENTS_DEFAULTS_CONTEXT_WINDOW"` - Temperature float64 `json:"temperature" env:"CLAWDROID_AGENTS_DEFAULTS_TEMPERATURE"` - MaxToolIterations int `json:"max_tool_iterations" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` + Workspace string `json:"workspace" label:"Workspace" env:"CLAWDROID_AGENTS_DEFAULTS_WORKSPACE"` + DataDir string `json:"data_dir" label:"Data Directory" env:"CLAWDROID_AGENTS_DEFAULTS_DATA_DIR"` + RestrictToWorkspace bool `json:"restrict_to_workspace" label:"Restrict to Workspace" env:"CLAWDROID_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"` + MaxTokens int `json:"max_tokens" label:"Max Tokens" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOKENS"` + ContextWindow int `json:"context_window" label:"Context Window" env:"CLAWDROID_AGENTS_DEFAULTS_CONTEXT_WINDOW"` + Temperature float64 `json:"temperature" label:"Temperature" env:"CLAWDROID_AGENTS_DEFAULTS_TEMPERATURE"` + MaxToolIterations int `json:"max_tool_iterations" label:"Max Tool Iterations" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` } type ChannelsConfig struct { - WhatsApp WhatsAppConfig `json:"whatsapp"` - Telegram TelegramConfig `json:"telegram"` - Discord DiscordConfig `json:"discord"` - Slack SlackConfig `json:"slack"` - LINE LINEConfig `json:"line"` - WebSocket WebSocketConfig `json:"websocket"` + WhatsApp WhatsAppConfig `json:"whatsapp" label:"WhatsApp"` + Telegram TelegramConfig `json:"telegram" label:"Telegram"` + Discord DiscordConfig `json:"discord" label:"Discord"` + Slack SlackConfig `json:"slack" label:"Slack"` + LINE LINEConfig `json:"line" label:"LINE"` + WebSocket WebSocketConfig `json:"websocket" label:"WebSocket"` } type WhatsAppConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_WHATSAPP_ENABLED"` - BridgeURL string `json:"bridge_url" env:"CLAWDROID_CHANNELS_WHATSAPP_BRIDGE_URL"` - AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_WHATSAPP_ALLOW_FROM"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_WHATSAPP_ENABLED"` + BridgeURL string `json:"bridge_url" label:"Bridge URL" env:"CLAWDROID_CHANNELS_WHATSAPP_BRIDGE_URL"` + AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_WHATSAPP_ALLOW_FROM"` } type TelegramConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_TELEGRAM_ENABLED"` - Token string `json:"token" env:"CLAWDROID_CHANNELS_TELEGRAM_TOKEN"` - Proxy string `json:"proxy" env:"CLAWDROID_CHANNELS_TELEGRAM_PROXY"` - AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_TELEGRAM_ALLOW_FROM"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_TELEGRAM_ENABLED"` + Token string `json:"token" label:"Token" env:"CLAWDROID_CHANNELS_TELEGRAM_TOKEN"` + Proxy string `json:"proxy" label:"Proxy" env:"CLAWDROID_CHANNELS_TELEGRAM_PROXY"` + AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_TELEGRAM_ALLOW_FROM"` } type DiscordConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_DISCORD_ENABLED"` - Token string `json:"token" env:"CLAWDROID_CHANNELS_DISCORD_TOKEN"` - AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_DISCORD_ALLOW_FROM"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_DISCORD_ENABLED"` + Token string `json:"token" label:"Token" env:"CLAWDROID_CHANNELS_DISCORD_TOKEN"` + AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_DISCORD_ALLOW_FROM"` } type SlackConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_SLACK_ENABLED"` - BotToken string `json:"bot_token" env:"CLAWDROID_CHANNELS_SLACK_BOT_TOKEN"` - AppToken string `json:"app_token" env:"CLAWDROID_CHANNELS_SLACK_APP_TOKEN"` - AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_SLACK_ALLOW_FROM"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_SLACK_ENABLED"` + BotToken string `json:"bot_token" label:"Bot Token" env:"CLAWDROID_CHANNELS_SLACK_BOT_TOKEN"` + AppToken string `json:"app_token" label:"App Token" env:"CLAWDROID_CHANNELS_SLACK_APP_TOKEN"` + AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_SLACK_ALLOW_FROM"` } type LINEConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_LINE_ENABLED"` - ChannelSecret string `json:"channel_secret" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_SECRET"` - ChannelAccessToken string `json:"channel_access_token" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_ACCESS_TOKEN"` - WebhookHost string `json:"webhook_host" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_HOST"` - WebhookPort int `json:"webhook_port" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PORT"` - WebhookPath string `json:"webhook_path" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PATH"` - AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_LINE_ALLOW_FROM"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_LINE_ENABLED"` + ChannelSecret string `json:"channel_secret" label:"Channel Secret" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_SECRET"` + ChannelAccessToken string `json:"channel_access_token" label:"Channel Access Token" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_ACCESS_TOKEN"` + WebhookHost string `json:"webhook_host" label:"Webhook Host" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_HOST"` + WebhookPort int `json:"webhook_port" label:"Webhook Port" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PORT"` + WebhookPath string `json:"webhook_path" label:"Webhook Path" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PATH"` + AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_LINE_ALLOW_FROM"` } type WebSocketConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_WEBSOCKET_ENABLED"` - Host string `json:"host" env:"CLAWDROID_CHANNELS_WEBSOCKET_HOST"` - Port int `json:"port" env:"CLAWDROID_CHANNELS_WEBSOCKET_PORT"` - Path string `json:"path" env:"CLAWDROID_CHANNELS_WEBSOCKET_PATH"` - AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_WEBSOCKET_ALLOW_FROM"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_WEBSOCKET_ENABLED"` + Host string `json:"host" label:"Host" env:"CLAWDROID_CHANNELS_WEBSOCKET_HOST"` + Port int `json:"port" label:"Port" env:"CLAWDROID_CHANNELS_WEBSOCKET_PORT"` + Path string `json:"path" label:"Path" env:"CLAWDROID_CHANNELS_WEBSOCKET_PATH"` + AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_WEBSOCKET_ALLOW_FROM"` } type HeartbeatConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_HEARTBEAT_ENABLED"` - Interval int `json:"interval" env:"CLAWDROID_HEARTBEAT_INTERVAL"` // minutes, min 5 + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_HEARTBEAT_ENABLED"` + Interval int `json:"interval" label:"Interval" env:"CLAWDROID_HEARTBEAT_INTERVAL"` // minutes, min 5 } type RateLimitsConfig struct { - MaxToolCallsPerMinute int `json:"max_tool_calls_per_minute" env:"CLAWDROID_RATE_LIMITS_MAX_TOOL_CALLS_PER_MINUTE"` // 0 = unlimited - MaxRequestsPerMinute int `json:"max_requests_per_minute" env:"CLAWDROID_RATE_LIMITS_MAX_REQUESTS_PER_MINUTE"` // 0 = unlimited + MaxToolCallsPerMinute int `json:"max_tool_calls_per_minute" label:"Max Tool Calls Per Minute" env:"CLAWDROID_RATE_LIMITS_MAX_TOOL_CALLS_PER_MINUTE"` // 0 = unlimited + MaxRequestsPerMinute int `json:"max_requests_per_minute" label:"Max Requests Per Minute" env:"CLAWDROID_RATE_LIMITS_MAX_REQUESTS_PER_MINUTE"` // 0 = unlimited } type GatewayConfig struct { - Host string `json:"host" env:"CLAWDROID_GATEWAY_HOST"` - Port int `json:"port" env:"CLAWDROID_GATEWAY_PORT"` - APIKey string `json:"api_key" env:"CLAWDROID_GATEWAY_API_KEY"` + Host string `json:"host" label:"Host" env:"CLAWDROID_GATEWAY_HOST"` + Port int `json:"port" label:"Port" env:"CLAWDROID_GATEWAY_PORT"` + APIKey string `json:"api_key" label:"API Key" env:"CLAWDROID_GATEWAY_API_KEY"` } type BraveConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_TOOLS_WEB_BRAVE_ENABLED"` - APIKey string `json:"api_key" env:"CLAWDROID_TOOLS_WEB_BRAVE_API_KEY"` - MaxResults int `json:"max_results" env:"CLAWDROID_TOOLS_WEB_BRAVE_MAX_RESULTS"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_TOOLS_WEB_BRAVE_ENABLED"` + APIKey string `json:"api_key" label:"API Key" env:"CLAWDROID_TOOLS_WEB_BRAVE_API_KEY"` + MaxResults int `json:"max_results" label:"Max Results" env:"CLAWDROID_TOOLS_WEB_BRAVE_MAX_RESULTS"` } type DuckDuckGoConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_ENABLED"` - MaxResults int `json:"max_results" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_ENABLED"` + MaxResults int `json:"max_results" label:"Max Results" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"` } type WebToolsConfig struct { - Brave BraveConfig `json:"brave"` - DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"` + Brave BraveConfig `json:"brave" label:"Brave Search"` + DuckDuckGo DuckDuckGoConfig `json:"duckduckgo" label:"DuckDuckGo"` } type ExecToolsConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_TOOLS_EXEC_ENABLED"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_TOOLS_EXEC_ENABLED"` } type MCPServerConfig struct { @@ -178,19 +178,19 @@ type MCPServerConfig struct { } type AndroidToolsConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_TOOLS_ANDROID_ENABLED"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_TOOLS_ANDROID_ENABLED"` } type MemoryToolsConfig struct { - Enabled bool `json:"enabled" env:"CLAWDROID_TOOLS_MEMORY_ENABLED"` + Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_TOOLS_MEMORY_ENABLED"` } type ToolsConfig struct { - Web WebToolsConfig `json:"web"` - Exec ExecToolsConfig `json:"exec"` - Android AndroidToolsConfig `json:"android"` - Memory MemoryToolsConfig `json:"memory"` - MCP map[string]MCPServerConfig `json:"mcp,omitempty"` + Web WebToolsConfig `json:"web" label:"Web Search"` + Exec ExecToolsConfig `json:"exec" label:"Shell Exec"` + Android AndroidToolsConfig `json:"android" label:"Android"` + Memory MemoryToolsConfig `json:"memory" label:"Memory"` + MCP map[string]MCPServerConfig `json:"mcp,omitempty" label:"MCP Servers"` } func DefaultConfig() *Config { diff --git a/pkg/gateway/schema.go b/pkg/gateway/schema.go index f57a0ee65..a41e334ae 100644 --- a/pkg/gateway/schema.go +++ b/pkg/gateway/schema.go @@ -28,12 +28,6 @@ type SchemaResponse struct { Sections []SchemaSection `json:"sections"` } -// acronyms maps lowercase abbreviations to their uppercase forms for label generation. -var acronyms = map[string]string{ - "api": "API", "llm": "LLM", "url": "URL", "ws": "WS", - "id": "ID", "mcp": "MCP", -} - // secretKeys lists JSON keys that contain sensitive values. var secretKeys = map[string]bool{ "api_key": true, @@ -72,7 +66,7 @@ func BuildSchema(defaultCfg *config.Config) SchemaResponse { section := SchemaSection{ Key: jsonTag, - Label: snakeToTitle(jsonTag), + Label: labelTag(field), } fieldVal := cfgVal.Field(i) @@ -145,7 +139,7 @@ func buildFields(t reflect.Type, v reflect.Value, prefix string) []SchemaField { fields = append(fields, SchemaField{ Key: fullKey, - Label: snakeToTitle(jk), + Label: labelTag(sf), Type: schemaType, Secret: secretKeys[jk], Default: defVal, @@ -195,15 +189,7 @@ func jsonKey(f reflect.StructField) string { return parts[0] } -// snakeToTitle converts a snake_case string to Title Case, applying acronym rules. -func snakeToTitle(s string) string { - parts := strings.Split(s, "_") - for i, p := range parts { - if upper, ok := acronyms[strings.ToLower(p)]; ok { - parts[i] = upper - } else if len(p) > 0 { - parts[i] = strings.ToUpper(p[:1]) + p[1:] - } - } - return strings.Join(parts, " ") +// labelTag reads the "label" struct tag from a field. +func labelTag(f reflect.StructField) string { + return f.Tag.Get("label") } diff --git a/pkg/gateway/server_test.go b/pkg/gateway/server_test.go index 0f8970d91..4dbfd7d0c 100644 --- a/pkg/gateway/server_test.go +++ b/pkg/gateway/server_test.go @@ -196,20 +196,22 @@ func TestBuildSchema_SecretFlag(t *testing.T) { } func TestBuildSchema_Labels(t *testing.T) { - tests := []struct { - input string - want string - }{ - {"api_key", "API Key"}, - {"base_url", "Base URL"}, - {"max_tokens", "Max Tokens"}, - {"bot_token", "Bot Token"}, + schema := BuildSchema(config.DefaultConfig()) + + wantLabels := map[string]string{ + "api_key": "API Key", + "base_url": "Base URL", + "defaults.max_tokens": "Max Tokens", + "slack.bot_token": "Bot Token", } - for _, tc := range tests { - got := snakeToTitle(tc.input) - if got != tc.want { - t.Errorf("snakeToTitle(%q) = %q, want %q", tc.input, got, tc.want) + for _, sec := range schema.Sections { + for _, f := range sec.Fields { + if want, ok := wantLabels[f.Key]; ok { + if f.Label != want { + t.Errorf("field %q label = %q, want %q", f.Key, f.Label, want) + } + } } } } @@ -1177,7 +1179,10 @@ func TestBuildSchema_SectionLabels(t *testing.T) { wantLabels := map[string]string{ "llm": "LLM", + "agents": "Agent Defaults", + "channels": "Messaging Channels", "gateway": "Gateway", + "tools": "Tool Settings", "heartbeat": "Heartbeat", "rate_limits": "Rate Limits", } @@ -2367,42 +2372,6 @@ func TestBuildSchema_GatewaySectionFields(t *testing.T) { } } -// --- #31: snakeToTitle with long compound words --- - -func TestSnakeToTitle_LongCompound(t *testing.T) { - tests := []struct { - input string - want string - }{ - {"channel_access_token", "Channel Access Token"}, - {"max_tool_calls_per_minute", "Max Tool Calls Per Minute"}, - {"restrict_to_workspace", "Restrict To Workspace"}, - } - for _, tc := range tests { - got := snakeToTitle(tc.input) - if got != tc.want { - t.Errorf("snakeToTitle(%q) = %q, want %q", tc.input, got, tc.want) - } - } -} - -// --- #32: snakeToTitle with empty string --- - -func TestSnakeToTitle_EmptyString(t *testing.T) { - got := snakeToTitle("") - if got != "" { - t.Errorf("snakeToTitle(\"\") = %q, want empty", got) - } -} - -// --- #33: snakeToTitle with "mcp" acronym --- - -func TestSnakeToTitle_MCPAcronym(t *testing.T) { - got := snakeToTitle("mcp") - if got != "MCP" { - t.Errorf("snakeToTitle(\"mcp\") = %q, want %q", got, "MCP") - } -} // --- #34: goTypeToSchema unknown type ---