mirror of
https://github.com/lukaszraczylo/mcp-captcharesolver.git
synced 2026-10-18 01:17:53 +00:00
feat(llm): provider factory with per-provider default base URLs
This commit is contained in:
@@ -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")
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user