summaryrefslogtreecommitdiff
path: root/internal/llm
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-23 09:04:17 +0200
committerPaul Buetow <paul@buetow.org>2026-03-23 09:04:17 +0200
commit462184dff3eef32f01f06634305da1355ac1bec2 (patch)
tree026ffaaeacfe152957298c985e1df77ff661b723 /internal/llm
parent667f2d3384643aa877de2eefcbad3923965bad09 (diff)
chore: bump version to v0.25.9v0.25.9
Code quality fixes from audit: - Log silently discarded errors in status sinks and stats.Update call sites - Fix json.Marshal errors silently ignored in LSP handlers - Replace time.Sleep in tests with channel signaling (mcp) and fake clock (stats) - Make context cancellation work in production time.Sleep sites (handlers_document, cmdentry) - Remove init()-based provider registration from llm package; use explicit RegisterAllProviders() - Add WaitGroup goroutine tracking to MCP server Run() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/llm')
-rw-r--r--internal/llm/anthropic.go4
-rw-r--r--internal/llm/ollama.go4
-rw-r--r--internal/llm/openai.go4
-rw-r--r--internal/llm/openrouter.go4
-rw-r--r--internal/llm/provider.go29
-rw-r--r--internal/llm/test_helpers_test.go12
6 files changed, 31 insertions, 26 deletions
diff --git a/internal/llm/anthropic.go b/internal/llm/anthropic.go
index 7da72b3..17c40ad 100644
--- a/internal/llm/anthropic.go
+++ b/internal/llm/anthropic.go
@@ -85,10 +85,6 @@ var (
_ Streamer = (*anthropicClient)(nil)
)
-func init() {
- RegisterProvider("anthropic", anthropicProviderFactory)
-}
-
func anthropicProviderFactory(cfg Config, keys ProviderKeys) (Client, error) {
if strings.TrimSpace(keys.AnthropicAPIKey) == "" {
return nil, missingAPIKeyError("anthropic", "ANTHROPIC_API_KEY", "HEXAI_ANTHROPIC_API_KEY")
diff --git a/internal/llm/ollama.go b/internal/llm/ollama.go
index e212466..b2cecfa 100644
--- a/internal/llm/ollama.go
+++ b/internal/llm/ollama.go
@@ -45,10 +45,6 @@ type ollamaChatResponse struct {
Error string `json:"error,omitempty"`
}
-func init() {
- RegisterProvider("ollama", ollamaProviderFactory)
-}
-
func ollamaProviderFactory(cfg Config, _ ProviderKeys) (Client, error) {
return newOllamaWithTimeout(
cfg.OllamaBaseURL,
diff --git a/internal/llm/openai.go b/internal/llm/openai.go
index cf18d9b..a119fe7 100644
--- a/internal/llm/openai.go
+++ b/internal/llm/openai.go
@@ -78,10 +78,6 @@ type oaStreamChunk struct {
} `json:"error,omitempty"`
}
-func init() {
- RegisterProvider("openai", openAIProviderFactory)
-}
-
func openAIProviderFactory(cfg Config, keys ProviderKeys) (Client, error) {
if strings.TrimSpace(keys.OpenAIAPIKey) == "" {
return nil, missingAPIKeyError("openai", "OPENAI_API_KEY", "HEXAI_OPENAI_API_KEY")
diff --git a/internal/llm/openrouter.go b/internal/llm/openrouter.go
index 451e9ad..aa8a4d4 100644
--- a/internal/llm/openrouter.go
+++ b/internal/llm/openrouter.go
@@ -27,10 +27,6 @@ var (
_ Streamer = openRouterClient{}
)
-func init() {
- RegisterProvider("openrouter", openRouterProviderFactory)
-}
-
func openRouterProviderFactory(cfg Config, keys ProviderKeys) (Client, error) {
if strings.TrimSpace(keys.OpenRouterAPIKey) == "" {
return nil, missingAPIKeyError("openrouter", "OPENROUTER_API_KEY", "HEXAI_OPENROUTER_API_KEY")
diff --git a/internal/llm/provider.go b/internal/llm/provider.go
index 3c72181..6c0c04b 100644
--- a/internal/llm/provider.go
+++ b/internal/llm/provider.go
@@ -103,20 +103,17 @@ type ProviderKeys struct {
// ProviderFactory builds an LLM client for a named provider.
type ProviderFactory func(cfg Config, keys ProviderKeys) (Client, error)
-// providerRegistry is a package-level singleton populated by init() calls in
-// each provider file (anthropic.go, openai.go, etc.). It must be a
-// package-level var — rather than a constructor argument — because Go's
-// init() mechanism runs before any application code, and the alternative
-// (an explicit RegisterAll() in main) would require every binary that uses
-// the llm package to manually enumerate all providers. The RWMutex makes the
-// map safe for the rare case where RegisterProvider is called from a test
-// goroutine after init() has completed.
+// providerRegistry is a package-level singleton populated via RegisterAllProviders.
+// Callers (binaries and tests) must call RegisterAllProviders before creating any
+// clients. The RWMutex makes the map safe for concurrent reads once populated.
var (
- providerRegistryMu sync.RWMutex
- providerRegistry = map[string]ProviderFactory{}
+ providerRegistryMu sync.RWMutex
+ providerRegistry = map[string]ProviderFactory{}
+ registerProvidersOnce sync.Once
)
// RegisterProvider registers a provider factory by normalized name.
+// Panics on empty name, nil factory, or duplicate registration.
func RegisterProvider(name string, factory ProviderFactory) {
normalized := normalizeProvider(name)
if normalized == "" {
@@ -133,6 +130,18 @@ func RegisterProvider(name string, factory ProviderFactory) {
providerRegistry[normalized] = factory
}
+// RegisterAllProviders registers all built-in LLM providers (anthropic, openai,
+// openrouter, ollama). It is safe to call from multiple entry points because the
+// actual registration runs only once via sync.Once.
+func RegisterAllProviders() {
+ registerProvidersOnce.Do(func() {
+ RegisterProvider("anthropic", anthropicProviderFactory)
+ RegisterProvider("openai", openAIProviderFactory)
+ RegisterProvider("openrouter", openRouterProviderFactory)
+ RegisterProvider("ollama", ollamaProviderFactory)
+ })
+}
+
// NewFromConfig creates an LLM client using only the supplied configuration.
// The OpenAI API key is supplied separately and may be read from the environment
// by the caller; other environment-based configuration is not used.
diff --git a/internal/llm/test_helpers_test.go b/internal/llm/test_helpers_test.go
index 051747a..b6553bf 100644
--- a/internal/llm/test_helpers_test.go
+++ b/internal/llm/test_helpers_test.go
@@ -1,3 +1,15 @@
package llm
+import (
+ "os"
+ "testing"
+)
+
+// TestMain registers all built-in providers before any test runs, mirroring
+// the explicit registration that happens in production binaries.
+func TestMain(m *testing.M) {
+ RegisterAllProviders()
+ os.Exit(m.Run())
+}
+
func f64p(v float64) *float64 { return &v }