summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/llm/anthropic.go11
-rw-r--r--internal/llm/anthropic_test.go20
-rw-r--r--internal/llm/ollama.go7
-rw-r--r--internal/llm/ollama_test.go26
-rw-r--r--internal/llm/openai.go7
-rw-r--r--internal/llm/openai_http_test.go18
-rw-r--r--internal/llm/openai_sse_negative_test.go2
-rw-r--r--internal/llm/openrouter.go8
-rw-r--r--internal/llm/openrouter_test.go8
9 files changed, 61 insertions, 46 deletions
diff --git a/internal/llm/anthropic.go b/internal/llm/anthropic.go
index 17c40ad..3f7a616 100644
--- a/internal/llm/anthropic.go
+++ b/internal/llm/anthropic.go
@@ -81,8 +81,8 @@ type anthropicStreamError struct {
// Ensure anthropicClient implements Client and Streamer.
var (
- _ Client = (*anthropicClient)(nil)
- _ Streamer = (*anthropicClient)(nil)
+ _ Client = anthropicClient{}
+ _ Streamer = anthropicClient{}
)
func anthropicProviderFactory(cfg Config, keys ProviderKeys) (Client, error) {
@@ -101,11 +101,14 @@ func anthropicProviderFactory(cfg Config, keys ProviderKeys) (Client, error) {
// Constructor
// newAnthropic constructs an Anthropic client using explicit configuration values.
// The apiKey may be empty; calls will fail until a valid key is supplied.
-func newAnthropic(baseURL, model, apiKey string, defaultTemp *float64) Client {
+// Following the Go idiom "return concrete types, accept interfaces", this
+// returns anthropicClient directly; the provider registry wraps it back into a
+// Client at registration time.
+func newAnthropic(baseURL, model, apiKey string, defaultTemp *float64) anthropicClient {
return newAnthropicWithTimeout(baseURL, model, apiKey, defaultTemp, 0)
}
-func newAnthropicWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) Client {
+func newAnthropicWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) anthropicClient {
if strings.TrimSpace(baseURL) == "" {
baseURL = "https://api.anthropic.com/v1"
}
diff --git a/internal/llm/anthropic_test.go b/internal/llm/anthropic_test.go
index 2459064..4e66d48 100644
--- a/internal/llm/anthropic_test.go
+++ b/internal/llm/anthropic_test.go
@@ -59,7 +59,7 @@ func TestAnthropicChat_Success(t *testing.T) {
}))
defer srv.Close()
- c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil)
response, err := c.Chat(context.Background(), []Message{
{Role: "user", Content: "Hello"},
})
@@ -100,7 +100,7 @@ func TestAnthropicChat_APIError(t *testing.T) {
}))
defer srv.Close()
- c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "invalid-key", nil).(anthropicClient)
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "invalid-key", nil)
_, err := c.Chat(context.Background(), []Message{
{Role: "user", Content: "Hello"},
})
@@ -128,7 +128,7 @@ func TestAnthropicChat_EmptyResponse(t *testing.T) {
}))
defer srv.Close()
- c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil)
_, err := c.Chat(context.Background(), []Message{
{Role: "user", Content: "Hello"},
})
@@ -163,7 +163,7 @@ func TestAnthropicChat_WithTemperature(t *testing.T) {
}))
defer srv.Close()
- c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil)
_, err := c.Chat(context.Background(), []Message{
{Role: "user", Content: "Hello"},
}, WithTemperature(0.5))
@@ -190,7 +190,9 @@ func TestAnthropicStream_Success(t *testing.T) {
}))
defer srv.Close()
- c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil)
+ // newAnthropic now returns the concrete anthropicClient; assert via a Client
+ // interface value to keep verifying the optional Streamer capability.
+ var c Client = newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil)
streamer, ok := c.(Streamer)
if !ok {
t.Fatalf("Anthropic client does not implement Streamer interface")
@@ -213,7 +215,7 @@ func TestAnthropicStream_Success(t *testing.T) {
}
func TestAnthropicStream_NoAPIKey(t *testing.T) {
- c := newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "", nil)
+ var c Client = newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "", nil)
streamer, ok := c.(Streamer)
if !ok {
t.Fatalf("Anthropic client does not implement Streamer interface")
@@ -238,21 +240,21 @@ func TestAnthropicClient_Name(t *testing.T) {
func TestAnthropicClient_DefaultModel(t *testing.T) {
model := "claude-3-opus-20250219"
- c := newAnthropic("https://api.anthropic.com/v1", model, "test-key", nil).(anthropicClient)
+ c := newAnthropic("https://api.anthropic.com/v1", model, "test-key", nil)
if c.DefaultModel() != model {
t.Fatalf("expected '%s', got '%s'", model, c.DefaultModel())
}
}
func TestAnthropicClient_DefaultBaseURL(t *testing.T) {
- c := newAnthropic("", "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ c := newAnthropic("", "claude-3-5-sonnet-20241022", "test-key", nil)
if c.baseURL != "https://api.anthropic.com/v1" {
t.Fatalf("expected default base URL, got '%s'", c.baseURL)
}
}
func TestAnthropicClient_DefaultModel_Empty(t *testing.T) {
- c := newAnthropic("https://api.anthropic.com/v1", "", "test-key", nil).(anthropicClient)
+ c := newAnthropic("https://api.anthropic.com/v1", "", "test-key", nil)
if c.defaultModel != "claude-3-5-sonnet-20240620" {
t.Fatalf("expected default model, got '%s'", c.defaultModel)
}
diff --git a/internal/llm/ollama.go b/internal/llm/ollama.go
index 98e5dce..d8f911f 100644
--- a/internal/llm/ollama.go
+++ b/internal/llm/ollama.go
@@ -61,11 +61,14 @@ func ollamaProviderFactory(cfg Config, keys ProviderKeys) (Client, error) {
// Constructor (kept among the first functions by convention).
// apiKey may be empty for local Ollama; pass a non-empty key for Ollama Cloud.
-func newOllama(baseURL, model string, defaultTemp *float64, apiKey string) Client {
+// Following the Go idiom "return concrete types, accept interfaces", this
+// returns ollamaClient directly; the provider registry wraps it back into a
+// Client at registration time.
+func newOllama(baseURL, model string, defaultTemp *float64, apiKey string) ollamaClient {
return newOllamaWithTimeout(baseURL, model, apiKey, defaultTemp, 0)
}
-func newOllamaWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) Client {
+func newOllamaWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) ollamaClient {
// Defaults target Ollama Cloud (ollama.ai); a local server is opted into
// by setting base_url = "http://localhost:11434" (or HEXAI_OLLAMA_BASE_URL)
// and an appropriate model.
diff --git a/internal/llm/ollama_test.go b/internal/llm/ollama_test.go
index 2216e21..e319617 100644
--- a/internal/llm/ollama_test.go
+++ b/internal/llm/ollama_test.go
@@ -49,7 +49,7 @@ func TestBuildOllamaRequest_TempOverride(t *testing.T) {
}
func TestOllama_NameAndModel(t *testing.T) {
- c := newOllama("http://x", "model-x", nil, "").(ollamaClient)
+ c := newOllama("http://x", "model-x", nil, "")
if c.Name() != "ollama" {
t.Fatalf("name: %q", c.Name())
}
@@ -71,7 +71,7 @@ func TestOllamaChat_NoAuthHeaderWhenKeyEmpty(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"message": map[string]string{"role": "assistant", "content": "ok"}, "done": true})
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", nil, "").(ollamaClient)
+ c := newOllama(ts.URL, "m", nil, "")
c.httpClient = ts.Client()
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}}); err != nil {
t.Fatalf("unexpected: %v", err)
@@ -95,7 +95,7 @@ func TestOllamaChat_AuthHeaderWhenKeySet(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"message": map[string]string{"role": "assistant", "content": "ok"}, "done": true})
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", f64p(0.1), key).(ollamaClient)
+ c := newOllama(ts.URL, "m", f64p(0.1), key)
c.httpClient = ts.Client()
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}}); err != nil {
t.Fatalf("unexpected: %v", err)
@@ -110,7 +110,7 @@ func TestOllamaChat_AuthHeaderWhenKeySet(t *testing.T) {
_, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"ok"},"done":true}`))
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", nil, key).(ollamaClient)
+ c := newOllama(ts.URL, "m", nil, key)
c.httpClient = ts.Client()
if err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "hi"}}, func(string) {}); err != nil {
t.Fatalf("unexpected: %v", err)
@@ -130,7 +130,7 @@ func TestOllamaChat_Success(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"message": map[string]string{"role": "assistant", "content": "Hello"}, "done": true})
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", f64p(0.1), "").(ollamaClient)
+ c := newOllama(ts.URL, "m", f64p(0.1), "")
c.httpClient = ts.Client()
out, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}})
if err != nil {
@@ -149,7 +149,7 @@ func TestOllamaChat_EmptyContent(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"message": map[string]string{"role": "assistant", "content": ""}, "done": true})
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", nil, "").(ollamaClient)
+ c := newOllama(ts.URL, "m", nil, "")
c.httpClient = ts.Client()
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "x"}}); err == nil {
t.Fatalf("expected error for empty content")
@@ -166,7 +166,7 @@ func TestOllamaChat_Non2xx(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"error": "bad"})
}))
defer ts1.Close()
- c1 := newOllama(ts1.URL, "m", nil, "").(ollamaClient)
+ c1 := newOllama(ts1.URL, "m", nil, "")
c1.httpClient = ts1.Client()
if _, err := c1.Chat(context.Background(), []Message{{Role: "user", Content: "x"}}); err == nil {
t.Fatalf("expected error for 400 with api body")
@@ -177,7 +177,7 @@ func TestOllamaChat_Non2xx(t *testing.T) {
_, _ = w.Write([]byte("{}"))
}))
defer ts2.Close()
- c2 := newOllama(ts2.URL, "m", nil, "").(ollamaClient)
+ c2 := newOllama(ts2.URL, "m", nil, "")
c2.httpClient = ts2.Client()
if _, err := c2.Chat(context.Background(), []Message{{Role: "user", Content: "x"}}); err == nil {
t.Fatalf("expected error for 500")
@@ -189,7 +189,7 @@ type rtFunc func(*http.Request) (*http.Response, error)
func (f rtFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
func TestOllamaChat_HTTPError(t *testing.T) {
- c := newOllama("http://127.0.0.1:0", "m", nil, "").(ollamaClient)
+ c := newOllama("http://127.0.0.1:0", "m", nil, "")
c.httpClient = &http.Client{Transport: rtFunc(func(*http.Request) (*http.Response, error) { return nil, fmt.Errorf("boom") })}
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "x"}}); err == nil {
t.Fatalf("expected http error path")
@@ -204,7 +204,7 @@ func TestOllamaChat_DecodeError(t *testing.T) {
_, _ = w.Write([]byte("{bad json}"))
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", nil, "").(ollamaClient)
+ c := newOllama(ts.URL, "m", nil, "")
c.httpClient = ts.Client()
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "x"}}); err == nil {
t.Fatalf("expected decode error")
@@ -229,7 +229,7 @@ func TestOllamaChatStream_Success(t *testing.T) {
_, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"!"},"done":true}`))
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", nil, "").(ollamaClient)
+ c := newOllama(ts.URL, "m", nil, "")
c.httpClient = ts.Client()
var got strings.Builder
if err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "x"}}, func(s string) { got.WriteString(s) }); err != nil {
@@ -248,7 +248,7 @@ func TestOllamaChatStream_ErrorEvent(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"error": "oops"})
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", nil, "").(ollamaClient)
+ c := newOllama(ts.URL, "m", nil, "")
c.httpClient = ts.Client()
if err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "x"}}, func(string) {}); err == nil {
t.Fatalf("expected stream error")
@@ -263,7 +263,7 @@ func TestOllamaChatStream_DecodeError(t *testing.T) {
_, _ = w.Write([]byte("{not json}"))
}))
defer ts.Close()
- c := newOllama(ts.URL, "m", nil, "").(ollamaClient)
+ c := newOllama(ts.URL, "m", nil, "")
c.httpClient = ts.Client()
if err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "x"}}, func(string) {}); err == nil {
t.Fatalf("expected decode error")
diff --git a/internal/llm/openai.go b/internal/llm/openai.go
index a119fe7..d475de0 100644
--- a/internal/llm/openai.go
+++ b/internal/llm/openai.go
@@ -110,11 +110,14 @@ func resolveOpenAITemperature(model string, configured *float64) *float64 {
// Constructor (kept among the first functions by convention)
// newOpenAI constructs an OpenAI client using explicit configuration values.
// The apiKey may be empty; calls will fail until a valid key is supplied.
-func newOpenAI(baseURL, model, apiKey string, defaultTemp *float64) Client {
+// Following the Go idiom "return concrete types, accept interfaces", this
+// returns openAIClient directly so callers keep full type information; the
+// provider registry wraps it back into a Client at registration time.
+func newOpenAI(baseURL, model, apiKey string, defaultTemp *float64) openAIClient {
return newOpenAIWithTimeout(baseURL, model, apiKey, defaultTemp, 0)
}
-func newOpenAIWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) Client {
+func newOpenAIWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) openAIClient {
if strings.TrimSpace(baseURL) == "" {
baseURL = "https://api.openai.com/v1"
}
diff --git a/internal/llm/openai_http_test.go b/internal/llm/openai_http_test.go
index d0fc828..400ac18 100644
--- a/internal/llm/openai_http_test.go
+++ b/internal/llm/openai_http_test.go
@@ -23,7 +23,7 @@ func TestOpenAI_Chat_Success(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []map[string]any{{"index": 0, "message": map[string]string{"role": "assistant", "content": "OK"}}}})
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
out, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}})
if err != nil || out != "OK" {
@@ -32,7 +32,7 @@ func TestOpenAI_Chat_Success(t *testing.T) {
}
func TestOpenAI_Chat_MissingKey(t *testing.T) {
- c := newOpenAI("http://x", "g", "", f64p(0.2)).(openAIClient)
+ c := newOpenAI("http://x", "g", "", f64p(0.2))
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}}); err == nil {
t.Fatalf("expected error for missing key")
}
@@ -49,7 +49,7 @@ func TestOpenAI_ChatStream_SSE(t *testing.T) {
_, _ = io.WriteString(w, "data: [DONE]\n")
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
var got string
err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "hi"}}, func(s string) { got += s })
@@ -75,7 +75,7 @@ func TestOpenAI_ChatStream_SSE_ErrorChunk(t *testing.T) {
_, _ = io.WriteString(w, "data: [DONE]\n")
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
var got string
if err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "hi"}}, func(s string) { got += s }); err == nil {
@@ -91,7 +91,7 @@ func TestOpenAI_Chat_NoChoices_Error(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{}})
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}}); err == nil {
t.Fatalf("expected error when choices empty")
@@ -108,7 +108,7 @@ func TestOpenAI_ChatStream_SSE_EmptyDelta_NoError(t *testing.T) {
_, _ = io.WriteString(w, "data: [DONE]\\n")
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
var got string
if err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "hi"}}, func(s string) { got += s }); err != nil {
@@ -129,7 +129,7 @@ func TestOpenAI_Chat_DecodeError_StatusOK(t *testing.T) {
_, _ = io.WriteString(w, "{invalid")
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}}); err == nil {
t.Fatalf("expected decode error for invalid JSON body")
@@ -150,7 +150,7 @@ func TestOpenAI_Chat_MultiChoiceAndErrorBody(t *testing.T) {
})
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
out, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}})
if err != nil || out != "FIRST" {
@@ -163,7 +163,7 @@ func TestOpenAI_Chat_MultiChoiceAndErrorBody(t *testing.T) {
_ = json.NewEncoder(w).Encode(map[string]any{"error": map[string]any{"message": "bad", "type": "invalid"}})
}))
defer srv2.Close()
- c2 := newOpenAI(srv2.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c2 := newOpenAI(srv2.URL, "g", "KEY", f64p(0.2))
c2.httpClient = srv2.Client()
if _, err := c2.Chat(context.Background(), []Message{{Role: "user", Content: "hi"}}); err == nil {
t.Fatalf("expected error from non-2xx with error body")
diff --git a/internal/llm/openai_sse_negative_test.go b/internal/llm/openai_sse_negative_test.go
index 7f4f7db..3ef9320 100644
--- a/internal/llm/openai_sse_negative_test.go
+++ b/internal/llm/openai_sse_negative_test.go
@@ -20,7 +20,7 @@ func TestOpenAI_ChatStream_SSE_MalformedChunk(t *testing.T) {
_, _ = io.WriteString(w, "data: [DONE]\n")
}))
defer srv.Close()
- c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2)).(openAIClient)
+ c := newOpenAI(srv.URL, "g", "KEY", f64p(0.2))
c.httpClient = srv.Client()
var got string
if err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "hi"}}, func(s string) { got += s }); err != nil {
diff --git a/internal/llm/openrouter.go b/internal/llm/openrouter.go
index aa8a4d4..d728275 100644
--- a/internal/llm/openrouter.go
+++ b/internal/llm/openrouter.go
@@ -40,11 +40,15 @@ func openRouterProviderFactory(cfg Config, keys ProviderKeys) (Client, error) {
), nil
}
-func newOpenRouter(baseURL, model, apiKey string, defaultTemp *float64) Client {
+// newOpenRouter constructs an OpenRouter client using explicit configuration
+// values. Following the Go idiom "return concrete types, accept interfaces",
+// this returns openRouterClient directly; the provider registry wraps it back
+// into a Client at registration time.
+func newOpenRouter(baseURL, model, apiKey string, defaultTemp *float64) openRouterClient {
return newOpenRouterWithTimeout(baseURL, model, apiKey, defaultTemp, 0)
}
-func newOpenRouterWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) Client {
+func newOpenRouterWithTimeout(baseURL, model, apiKey string, defaultTemp *float64, timeoutSec int) openRouterClient {
if strings.TrimSpace(baseURL) == "" {
baseURL = "https://openrouter.ai/api/v1"
}
diff --git a/internal/llm/openrouter_test.go b/internal/llm/openrouter_test.go
index 07d6e0f..f42459e 100644
--- a/internal/llm/openrouter_test.go
+++ b/internal/llm/openrouter_test.go
@@ -35,7 +35,7 @@ func TestOpenRouter_Chat_SendsHeadersAndBody(t *testing.T) {
}))
defer srv.Close()
- c := newOpenRouter(srv.URL, "anthropic/claude-test", "KEY", f64p(0.2)).(openRouterClient)
+ c := newOpenRouter(srv.URL, "anthropic/claude-test", "KEY", f64p(0.2))
c.httpClient = srv.Client()
out, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "ping"}})
if err != nil {
@@ -81,7 +81,7 @@ func TestOpenRouter_ChatStream_SendsHeaders(t *testing.T) {
}))
defer srv.Close()
- c := newOpenRouter(srv.URL, "anthropic/claude-test", "KEY", f64p(0.2)).(openRouterClient)
+ c := newOpenRouter(srv.URL, "anthropic/claude-test", "KEY", f64p(0.2))
c.httpClient = srv.Client()
var got string
err := c.ChatStream(context.Background(), []Message{{Role: "user", Content: "ping"}}, func(s string) { got += s })
@@ -100,7 +100,7 @@ func TestOpenRouter_ChatStream_SendsHeaders(t *testing.T) {
}
func TestOpenRouter_Chat_MissingKey(t *testing.T) {
- c := newOpenRouter("http://example", "anthropic/claude-test", "", f64p(0.2)).(openRouterClient)
+ c := newOpenRouter("http://example", "anthropic/claude-test", "", f64p(0.2))
if _, err := c.Chat(context.Background(), []Message{{Role: "user", Content: "ping"}}); err == nil {
t.Fatalf("expected error for missing api key")
} else if !strings.Contains(err.Error(), "OPENROUTER_API_KEY") || !strings.Contains(err.Error(), "HEXAI_OPENROUTER_API_KEY") {
@@ -111,7 +111,7 @@ func TestOpenRouter_Chat_MissingKey(t *testing.T) {
func TestOpenRouter_DefaultsAndMetadata(t *testing.T) {
logger := log.New(io.Discard, "", 0)
logging.Bind(logger)
- c := newOpenRouter("", "", "KEY", nil).(openRouterClient)
+ c := newOpenRouter("", "", "KEY", nil)
if c.baseURL != "https://openrouter.ai/api/v1" {
t.Fatalf("default baseURL mismatch: %s", c.baseURL)
}