diff options
| -rw-r--r-- | internal/llm/anthropic.go | 11 | ||||
| -rw-r--r-- | internal/llm/anthropic_test.go | 20 | ||||
| -rw-r--r-- | internal/llm/ollama.go | 7 | ||||
| -rw-r--r-- | internal/llm/ollama_test.go | 26 | ||||
| -rw-r--r-- | internal/llm/openai.go | 7 | ||||
| -rw-r--r-- | internal/llm/openai_http_test.go | 18 | ||||
| -rw-r--r-- | internal/llm/openai_sse_negative_test.go | 2 | ||||
| -rw-r--r-- | internal/llm/openrouter.go | 8 | ||||
| -rw-r--r-- | internal/llm/openrouter_test.go | 8 |
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) } |
