refactor: replace snakeToTitle with explicit label struct tags

Use label:"..." tags on all exported config fields so that schema labels
are defined alongside the structs instead of being derived at runtime.
Remove snakeToTitle, the acronyms map, and config.example.json (now
generated by DefaultConfig via onboard).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kohei 2026-02-28 00:29:09 +09:00
parent df3035aa14
commit 598971e282
6 changed files with 94 additions and 250 deletions

View file

@ -510,7 +510,6 @@ clawdroid/
│ ├── core/ # 共有コア (data, domain, model, ui) │ ├── core/ # 共有コア (data, domain, model, ui)
│ └── feature/ # 機能モジュール (chat, settings) │ └── feature/ # 機能モジュール (chat, settings)
├── workspace/ # テンプレートファイル (IDENTITY.md, SOUL.md 等) ├── workspace/ # テンプレートファイル (IDENTITY.md, SOUL.md 等)
├── config/ # config.example.json
├── Makefile ├── Makefile
├── go.mod ├── go.mod
└── .goreleaser.yaml └── .goreleaser.yaml

View file

@ -512,7 +512,6 @@ clawdroid/
│ ├── core/ # Shared core (data, domain, model, ui) │ ├── core/ # Shared core (data, domain, model, ui)
│ └── feature/ # Feature modules (chat, settings) │ └── feature/ # Feature modules (chat, settings)
├── workspace/ # Template files (IDENTITY.md, SOUL.md, etc.) ├── workspace/ # Template files (IDENTITY.md, SOUL.md, etc.)
├── config/ # config.example.json
├── Makefile ├── Makefile
├── go.mod ├── go.mod
└── .goreleaser.yaml └── .goreleaser.yaml

View file

@ -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
}
}

View file

@ -44,123 +44,123 @@ func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
} }
type LLMConfig struct { type LLMConfig struct {
Model string `json:"model" env:"CLAWDROID_LLM_MODEL"` Model string `json:"model" label:"Model" env:"CLAWDROID_LLM_MODEL"`
APIKey string `json:"api_key" env:"CLAWDROID_LLM_API_KEY"` APIKey string `json:"api_key" label:"API Key" env:"CLAWDROID_LLM_API_KEY"`
BaseURL string `json:"base_url" env:"CLAWDROID_LLM_BASE_URL"` BaseURL string `json:"base_url" label:"Base URL" env:"CLAWDROID_LLM_BASE_URL"`
} }
type Config struct { type Config struct {
LLM LLMConfig `json:"llm"` LLM LLMConfig `json:"llm" label:"LLM"`
Agents AgentsConfig `json:"agents"` Agents AgentsConfig `json:"agents" label:"Agent Defaults"`
Channels ChannelsConfig `json:"channels"` Channels ChannelsConfig `json:"channels" label:"Messaging Channels"`
Gateway GatewayConfig `json:"gateway"` Gateway GatewayConfig `json:"gateway" label:"Gateway"`
Tools ToolsConfig `json:"tools"` Tools ToolsConfig `json:"tools" label:"Tool Settings"`
Heartbeat HeartbeatConfig `json:"heartbeat"` Heartbeat HeartbeatConfig `json:"heartbeat" label:"Heartbeat"`
RateLimits RateLimitsConfig `json:"rate_limits"` RateLimits RateLimitsConfig `json:"rate_limits" label:"Rate Limits"`
mu sync.RWMutex mu sync.RWMutex
} }
type AgentsConfig struct { type AgentsConfig struct {
Defaults AgentDefaults `json:"defaults"` Defaults AgentDefaults `json:"defaults" label:"Defaults"`
} }
type AgentDefaults struct { type AgentDefaults struct {
Workspace string `json:"workspace" env:"CLAWDROID_AGENTS_DEFAULTS_WORKSPACE"` Workspace string `json:"workspace" label:"Workspace" env:"CLAWDROID_AGENTS_DEFAULTS_WORKSPACE"`
DataDir string `json:"data_dir" env:"CLAWDROID_AGENTS_DEFAULTS_DATA_DIR"` DataDir string `json:"data_dir" label:"Data Directory" env:"CLAWDROID_AGENTS_DEFAULTS_DATA_DIR"`
RestrictToWorkspace bool `json:"restrict_to_workspace" env:"CLAWDROID_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"` RestrictToWorkspace bool `json:"restrict_to_workspace" label:"Restrict to Workspace" env:"CLAWDROID_AGENTS_DEFAULTS_RESTRICT_TO_WORKSPACE"`
MaxTokens int `json:"max_tokens" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOKENS"` MaxTokens int `json:"max_tokens" label:"Max Tokens" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOKENS"`
ContextWindow int `json:"context_window" env:"CLAWDROID_AGENTS_DEFAULTS_CONTEXT_WINDOW"` ContextWindow int `json:"context_window" label:"Context Window" env:"CLAWDROID_AGENTS_DEFAULTS_CONTEXT_WINDOW"`
Temperature float64 `json:"temperature" env:"CLAWDROID_AGENTS_DEFAULTS_TEMPERATURE"` Temperature float64 `json:"temperature" label:"Temperature" env:"CLAWDROID_AGENTS_DEFAULTS_TEMPERATURE"`
MaxToolIterations int `json:"max_tool_iterations" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"` MaxToolIterations int `json:"max_tool_iterations" label:"Max Tool Iterations" env:"CLAWDROID_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
} }
type ChannelsConfig struct { type ChannelsConfig struct {
WhatsApp WhatsAppConfig `json:"whatsapp"` WhatsApp WhatsAppConfig `json:"whatsapp" label:"WhatsApp"`
Telegram TelegramConfig `json:"telegram"` Telegram TelegramConfig `json:"telegram" label:"Telegram"`
Discord DiscordConfig `json:"discord"` Discord DiscordConfig `json:"discord" label:"Discord"`
Slack SlackConfig `json:"slack"` Slack SlackConfig `json:"slack" label:"Slack"`
LINE LINEConfig `json:"line"` LINE LINEConfig `json:"line" label:"LINE"`
WebSocket WebSocketConfig `json:"websocket"` WebSocket WebSocketConfig `json:"websocket" label:"WebSocket"`
} }
type WhatsAppConfig struct { type WhatsAppConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_WHATSAPP_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_WHATSAPP_ENABLED"`
BridgeURL string `json:"bridge_url" env:"CLAWDROID_CHANNELS_WHATSAPP_BRIDGE_URL"` BridgeURL string `json:"bridge_url" label:"Bridge URL" env:"CLAWDROID_CHANNELS_WHATSAPP_BRIDGE_URL"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_WHATSAPP_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_WHATSAPP_ALLOW_FROM"`
} }
type TelegramConfig struct { type TelegramConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_TELEGRAM_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_TELEGRAM_ENABLED"`
Token string `json:"token" env:"CLAWDROID_CHANNELS_TELEGRAM_TOKEN"` Token string `json:"token" label:"Token" env:"CLAWDROID_CHANNELS_TELEGRAM_TOKEN"`
Proxy string `json:"proxy" env:"CLAWDROID_CHANNELS_TELEGRAM_PROXY"` Proxy string `json:"proxy" label:"Proxy" env:"CLAWDROID_CHANNELS_TELEGRAM_PROXY"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_TELEGRAM_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_TELEGRAM_ALLOW_FROM"`
} }
type DiscordConfig struct { type DiscordConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_DISCORD_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_DISCORD_ENABLED"`
Token string `json:"token" env:"CLAWDROID_CHANNELS_DISCORD_TOKEN"` Token string `json:"token" label:"Token" env:"CLAWDROID_CHANNELS_DISCORD_TOKEN"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_DISCORD_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_DISCORD_ALLOW_FROM"`
} }
type SlackConfig struct { type SlackConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_SLACK_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_SLACK_ENABLED"`
BotToken string `json:"bot_token" env:"CLAWDROID_CHANNELS_SLACK_BOT_TOKEN"` BotToken string `json:"bot_token" label:"Bot Token" env:"CLAWDROID_CHANNELS_SLACK_BOT_TOKEN"`
AppToken string `json:"app_token" env:"CLAWDROID_CHANNELS_SLACK_APP_TOKEN"` AppToken string `json:"app_token" label:"App Token" env:"CLAWDROID_CHANNELS_SLACK_APP_TOKEN"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_SLACK_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_SLACK_ALLOW_FROM"`
} }
type LINEConfig struct { type LINEConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_LINE_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_LINE_ENABLED"`
ChannelSecret string `json:"channel_secret" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_SECRET"` ChannelSecret string `json:"channel_secret" label:"Channel Secret" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_SECRET"`
ChannelAccessToken string `json:"channel_access_token" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_ACCESS_TOKEN"` ChannelAccessToken string `json:"channel_access_token" label:"Channel Access Token" env:"CLAWDROID_CHANNELS_LINE_CHANNEL_ACCESS_TOKEN"`
WebhookHost string `json:"webhook_host" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_HOST"` WebhookHost string `json:"webhook_host" label:"Webhook Host" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_HOST"`
WebhookPort int `json:"webhook_port" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PORT"` WebhookPort int `json:"webhook_port" label:"Webhook Port" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PORT"`
WebhookPath string `json:"webhook_path" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PATH"` WebhookPath string `json:"webhook_path" label:"Webhook Path" env:"CLAWDROID_CHANNELS_LINE_WEBHOOK_PATH"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_LINE_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_LINE_ALLOW_FROM"`
} }
type WebSocketConfig struct { type WebSocketConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_CHANNELS_WEBSOCKET_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_CHANNELS_WEBSOCKET_ENABLED"`
Host string `json:"host" env:"CLAWDROID_CHANNELS_WEBSOCKET_HOST"` Host string `json:"host" label:"Host" env:"CLAWDROID_CHANNELS_WEBSOCKET_HOST"`
Port int `json:"port" env:"CLAWDROID_CHANNELS_WEBSOCKET_PORT"` Port int `json:"port" label:"Port" env:"CLAWDROID_CHANNELS_WEBSOCKET_PORT"`
Path string `json:"path" env:"CLAWDROID_CHANNELS_WEBSOCKET_PATH"` Path string `json:"path" label:"Path" env:"CLAWDROID_CHANNELS_WEBSOCKET_PATH"`
AllowFrom FlexibleStringSlice `json:"allow_from" env:"CLAWDROID_CHANNELS_WEBSOCKET_ALLOW_FROM"` AllowFrom FlexibleStringSlice `json:"allow_from" label:"Allow From" env:"CLAWDROID_CHANNELS_WEBSOCKET_ALLOW_FROM"`
} }
type HeartbeatConfig struct { type HeartbeatConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_HEARTBEAT_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_HEARTBEAT_ENABLED"`
Interval int `json:"interval" env:"CLAWDROID_HEARTBEAT_INTERVAL"` // minutes, min 5 Interval int `json:"interval" label:"Interval" env:"CLAWDROID_HEARTBEAT_INTERVAL"` // minutes, min 5
} }
type RateLimitsConfig struct { type RateLimitsConfig struct {
MaxToolCallsPerMinute int `json:"max_tool_calls_per_minute" env:"CLAWDROID_RATE_LIMITS_MAX_TOOL_CALLS_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" env:"CLAWDROID_RATE_LIMITS_MAX_REQUESTS_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 { type GatewayConfig struct {
Host string `json:"host" env:"CLAWDROID_GATEWAY_HOST"` Host string `json:"host" label:"Host" env:"CLAWDROID_GATEWAY_HOST"`
Port int `json:"port" env:"CLAWDROID_GATEWAY_PORT"` Port int `json:"port" label:"Port" env:"CLAWDROID_GATEWAY_PORT"`
APIKey string `json:"api_key" env:"CLAWDROID_GATEWAY_API_KEY"` APIKey string `json:"api_key" label:"API Key" env:"CLAWDROID_GATEWAY_API_KEY"`
} }
type BraveConfig struct { type BraveConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_TOOLS_WEB_BRAVE_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_TOOLS_WEB_BRAVE_ENABLED"`
APIKey string `json:"api_key" env:"CLAWDROID_TOOLS_WEB_BRAVE_API_KEY"` APIKey string `json:"api_key" label:"API Key" env:"CLAWDROID_TOOLS_WEB_BRAVE_API_KEY"`
MaxResults int `json:"max_results" env:"CLAWDROID_TOOLS_WEB_BRAVE_MAX_RESULTS"` MaxResults int `json:"max_results" label:"Max Results" env:"CLAWDROID_TOOLS_WEB_BRAVE_MAX_RESULTS"`
} }
type DuckDuckGoConfig struct { type DuckDuckGoConfig struct {
Enabled bool `json:"enabled" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_ENABLED"` Enabled bool `json:"enabled" label:"Enabled" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_ENABLED"`
MaxResults int `json:"max_results" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"` MaxResults int `json:"max_results" label:"Max Results" env:"CLAWDROID_TOOLS_WEB_DUCKDUCKGO_MAX_RESULTS"`
} }
type WebToolsConfig struct { type WebToolsConfig struct {
Brave BraveConfig `json:"brave"` Brave BraveConfig `json:"brave" label:"Brave Search"`
DuckDuckGo DuckDuckGoConfig `json:"duckduckgo"` DuckDuckGo DuckDuckGoConfig `json:"duckduckgo" label:"DuckDuckGo"`
} }
type ExecToolsConfig struct { 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 { type MCPServerConfig struct {
@ -178,19 +178,19 @@ type MCPServerConfig struct {
} }
type AndroidToolsConfig 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 { 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 { type ToolsConfig struct {
Web WebToolsConfig `json:"web"` Web WebToolsConfig `json:"web" label:"Web Search"`
Exec ExecToolsConfig `json:"exec"` Exec ExecToolsConfig `json:"exec" label:"Shell Exec"`
Android AndroidToolsConfig `json:"android"` Android AndroidToolsConfig `json:"android" label:"Android"`
Memory MemoryToolsConfig `json:"memory"` Memory MemoryToolsConfig `json:"memory" label:"Memory"`
MCP map[string]MCPServerConfig `json:"mcp,omitempty"` MCP map[string]MCPServerConfig `json:"mcp,omitempty" label:"MCP Servers"`
} }
func DefaultConfig() *Config { func DefaultConfig() *Config {

View file

@ -28,12 +28,6 @@ type SchemaResponse struct {
Sections []SchemaSection `json:"sections"` 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. // secretKeys lists JSON keys that contain sensitive values.
var secretKeys = map[string]bool{ var secretKeys = map[string]bool{
"api_key": true, "api_key": true,
@ -72,7 +66,7 @@ func BuildSchema(defaultCfg *config.Config) SchemaResponse {
section := SchemaSection{ section := SchemaSection{
Key: jsonTag, Key: jsonTag,
Label: snakeToTitle(jsonTag), Label: labelTag(field),
} }
fieldVal := cfgVal.Field(i) fieldVal := cfgVal.Field(i)
@ -145,7 +139,7 @@ func buildFields(t reflect.Type, v reflect.Value, prefix string) []SchemaField {
fields = append(fields, SchemaField{ fields = append(fields, SchemaField{
Key: fullKey, Key: fullKey,
Label: snakeToTitle(jk), Label: labelTag(sf),
Type: schemaType, Type: schemaType,
Secret: secretKeys[jk], Secret: secretKeys[jk],
Default: defVal, Default: defVal,
@ -195,15 +189,7 @@ func jsonKey(f reflect.StructField) string {
return parts[0] return parts[0]
} }
// snakeToTitle converts a snake_case string to Title Case, applying acronym rules. // labelTag reads the "label" struct tag from a field.
func snakeToTitle(s string) string { func labelTag(f reflect.StructField) string {
parts := strings.Split(s, "_") return f.Tag.Get("label")
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, " ")
} }

View file

@ -196,20 +196,22 @@ func TestBuildSchema_SecretFlag(t *testing.T) {
} }
func TestBuildSchema_Labels(t *testing.T) { func TestBuildSchema_Labels(t *testing.T) {
tests := []struct { schema := BuildSchema(config.DefaultConfig())
input string
want string wantLabels := map[string]string{
}{ "api_key": "API Key",
{"api_key", "API Key"}, "base_url": "Base URL",
{"base_url", "Base URL"}, "defaults.max_tokens": "Max Tokens",
{"max_tokens", "Max Tokens"}, "slack.bot_token": "Bot Token",
{"bot_token", "Bot Token"},
} }
for _, tc := range tests { for _, sec := range schema.Sections {
got := snakeToTitle(tc.input) for _, f := range sec.Fields {
if got != tc.want { if want, ok := wantLabels[f.Key]; ok {
t.Errorf("snakeToTitle(%q) = %q, want %q", tc.input, got, tc.want) 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{ wantLabels := map[string]string{
"llm": "LLM", "llm": "LLM",
"agents": "Agent Defaults",
"channels": "Messaging Channels",
"gateway": "Gateway", "gateway": "Gateway",
"tools": "Tool Settings",
"heartbeat": "Heartbeat", "heartbeat": "Heartbeat",
"rate_limits": "Rate Limits", "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 --- // --- #34: goTypeToSchema unknown type ---