initial commit

This commit is contained in:
2026-06-11 22:55:35 +01:00
parent eabebcf89b
commit 391828d892
14 changed files with 694 additions and 8 deletions
+19 -1
View File
@@ -19,7 +19,10 @@ type Config struct {
APIKey string
Model string
AudioModel string
HTTPPath string
LogLevel string
Transport string
HTTPAddr string
Timeout time.Duration
MaxTokens int
}
@@ -28,7 +31,11 @@ var validProviders = map[string]bool{
"anthropic": true, "openai": true, "openai-compatible": true, "gemini": true,
}
// Load reads CAPTCHA_LLM_* env vars and returns a validated Config.
var validTransports = map[string]bool{
"stdio": true, "http": true,
}
// Load reads CAPTCHA_LLM_* and CAPTCHA_TRANSPORT_* env vars and returns a validated Config.
func Load() (Config, error) {
c := Config{
Provider: os.Getenv("CAPTCHA_LLM_PROVIDER"),
@@ -37,18 +44,29 @@ func Load() (Config, error) {
Model: os.Getenv("CAPTCHA_LLM_MODEL"),
AudioModel: os.Getenv("CAPTCHA_LLM_AUDIO_MODEL"),
LogLevel: envOr("CAPTCHA_LOG_LEVEL", "info"),
Transport: envOr("CAPTCHA_TRANSPORT", "stdio"),
HTTPAddr: envOr("CAPTCHA_HTTP_ADDR", ":8080"),
HTTPPath: envOr("CAPTCHA_HTTP_PATH", "/mcp"),
MaxTokens: 1024,
Timeout: 60 * time.Second,
}
if !validProviders[c.Provider] {
return Config{}, fmt.Errorf("CAPTCHA_LLM_PROVIDER must be one of anthropic|openai|openai-compatible|gemini, got %q", c.Provider)
}
if !validTransports[c.Transport] {
return Config{}, fmt.Errorf("CAPTCHA_TRANSPORT must be one of stdio|http, got %q", c.Transport)
}
if c.APIKey == "" && c.BaseURL == "" {
return Config{}, fmt.Errorf("CAPTCHA_LLM_API_KEY is required (or set CAPTCHA_LLM_BASE_URL for a keyless local endpoint)")
}
if c.Model == "" {
return Config{}, fmt.Errorf("CAPTCHA_LLM_MODEL is required")
}
if c.Transport == "http" {
if c.HTTPPath == "" || c.HTTPPath[0] != '/' {
return Config{}, fmt.Errorf("CAPTCHA_HTTP_PATH must be an absolute path beginning with '/', got %q", c.HTTPPath)
}
}
if v := os.Getenv("CAPTCHA_LLM_MAX_TOKENS"); v != "" {
n, err := strconv.Atoi(v)
if err != nil {
+36 -1
View File
@@ -21,7 +21,25 @@ func TestLoad(t *testing.T) {
},
want: Config{
Provider: "anthropic", APIKey: "k", Model: "claude-opus-4-8",
LogLevel: "info", MaxTokens: 1024, Timeout: 60 * time.Second,
LogLevel: "info", Transport: "stdio", HTTPAddr: ":8080", HTTPPath: "/mcp",
MaxTokens: 1024, Timeout: 60 * time.Second,
},
},
{
name: "valid http transport",
env: map[string]string{
"CAPTCHA_LLM_PROVIDER": "openai-compatible",
"CAPTCHA_LLM_BASE_URL": "http://localhost:11434/v1",
"CAPTCHA_LLM_MODEL": "qwen2.5vl",
"CAPTCHA_TRANSPORT": "http",
"CAPTCHA_HTTP_ADDR": "127.0.0.1:9090",
"CAPTCHA_HTTP_PATH": "/mcp",
},
want: Config{
Provider: "openai-compatible", BaseURL: "http://localhost:11434/v1",
Model: "qwen2.5vl",
LogLevel: "info", Transport: "http", HTTPAddr: "127.0.0.1:9090", HTTPPath: "/mcp",
MaxTokens: 1024, Timeout: 60 * time.Second,
},
},
{
@@ -34,6 +52,23 @@ func TestLoad(t *testing.T) {
env: map[string]string{"CAPTCHA_LLM_PROVIDER": "nope", "CAPTCHA_LLM_API_KEY": "k", "CAPTCHA_LLM_MODEL": "m"},
wantErr: true,
},
{
name: "bad transport",
env: map[string]string{
"CAPTCHA_LLM_PROVIDER": "openai", "CAPTCHA_LLM_API_KEY": "k", "CAPTCHA_LLM_MODEL": "m",
"CAPTCHA_TRANSPORT": "quic",
},
wantErr: true,
},
{
name: "http transport with relative path",
env: map[string]string{
"CAPTCHA_LLM_PROVIDER": "openai", "CAPTCHA_LLM_API_KEY": "k", "CAPTCHA_LLM_MODEL": "m",
"CAPTCHA_TRANSPORT": "http",
"CAPTCHA_HTTP_PATH": "mcp",
},
wantErr: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {