diff --git a/internal/llm/gemini.go b/internal/llm/gemini.go new file mode 100644 index 0000000..2b82a54 --- /dev/null +++ b/internal/llm/gemini.go @@ -0,0 +1,24 @@ +package llm + +import ( + "context" + "fmt" + "net/http" +) + +type geminiClient struct { + http *http.Client + base string + key string + model string + audioModel string + maxTokens int +} + +func (c *geminiClient) Vision(_ context.Context, _ string, _ []Image, _ Options) (string, error) { + return "", fmt.Errorf("gemini provider not yet implemented") +} + +func (c *geminiClient) Transcribe(_ context.Context, _ []byte, _ string, _ Options) (string, error) { + return "", fmt.Errorf("gemini provider not yet implemented") +} diff --git a/internal/llm/new.go b/internal/llm/new.go new file mode 100644 index 0000000..e793082 --- /dev/null +++ b/internal/llm/new.go @@ -0,0 +1,40 @@ +package llm + +import ( + "fmt" + "net/http" + + "github.com/lukaszraczylo/captcha-solver-mcp/internal/config" +) + +// New constructs the configured provider Client. +func New(cfg config.Config) (Client, error) { + hc := &http.Client{Timeout: cfg.Timeout} + switch cfg.Provider { + case "anthropic": + base := cfg.BaseURL + if base == "" { + base = "https://api.anthropic.com" + } + return &anthropicClient{http: hc, base: base, key: cfg.APIKey, model: cfg.Model, maxTokens: cfg.MaxTokens}, nil + case "openai": + base := cfg.BaseURL + if base == "" { + base = "https://api.openai.com" + } + return &openAIClient{http: hc, base: base, key: cfg.APIKey, model: cfg.Model, audioModel: cfg.AudioModel, maxTokens: cfg.MaxTokens}, nil + case "openai-compatible": + if cfg.BaseURL == "" { + return nil, fmt.Errorf("openai-compatible requires CAPTCHA_LLM_BASE_URL") + } + return &openAIClient{http: hc, base: cfg.BaseURL, key: cfg.APIKey, model: cfg.Model, audioModel: cfg.AudioModel, maxTokens: cfg.MaxTokens}, nil + case "gemini": + base := cfg.BaseURL + if base == "" { + base = "https://generativelanguage.googleapis.com" + } + return &geminiClient{http: hc, base: base, key: cfg.APIKey, model: cfg.Model, audioModel: cfg.AudioModel, maxTokens: cfg.MaxTokens}, nil + default: + return nil, fmt.Errorf("unknown provider %q", cfg.Provider) + } +} diff --git a/internal/llm/new_test.go b/internal/llm/new_test.go new file mode 100644 index 0000000..c2a94f9 --- /dev/null +++ b/internal/llm/new_test.go @@ -0,0 +1,39 @@ +package llm + +import ( + "testing" + + "github.com/lukaszraczylo/captcha-solver-mcp/internal/config" +) + +func TestNew(t *testing.T) { + tests := []struct { + name string + provider string + wantBase string + wantErr bool + }{ + {"anthropic default base", "anthropic", "https://api.anthropic.com", false}, + {"openai default base", "openai", "https://api.openai.com", false}, + {"compatible needs base", "openai-compatible", "", true}, + {"unknown", "zzz", "", true}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cfg := config.Config{Provider: tc.provider, APIKey: "k", Model: "m"} + c, err := New(cfg) + if tc.wantErr { + if err == nil { + t.Fatal("want error") + } + return + } + if err != nil { + t.Fatalf("unexpected err: %v", err) + } + if c == nil { + t.Fatal("nil client") + } + }) + } +}