From 83f09db93d97ea3de31bcbe299aa56e3a0032825 Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Fri, 10 Jul 2026 07:19:15 -0400 Subject: [PATCH 1/2] feat(provider): expose registered types --- orchestrator/provider_registry.go | 13 ++++++++++ orchestrator/provider_registry_test.go | 34 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/orchestrator/provider_registry.go b/orchestrator/provider_registry.go index f9c5a4a..17bf10e 100644 --- a/orchestrator/provider_registry.go +++ b/orchestrator/provider_registry.go @@ -5,6 +5,7 @@ import ( "database/sql" "encoding/json" "fmt" + "slices" "strings" "sync" "time" @@ -110,6 +111,18 @@ func NewProviderRegistry(db *sql.DB, secretsProvider func() secrets.Provider) *P return r } +// ProviderTypes returns the registered provider type names in sorted order. +func (r *ProviderRegistry) ProviderTypes() []string { + r.mu.RLock() + defer r.mu.RUnlock() + types := make([]string, 0, len(r.factories)) + for providerType := range r.factories { + types = append(types, providerType) + } + slices.Sort(types) + return types +} + // GetByAlias looks up a provider by its alias. It checks the cache first, // then falls back to DB lookup, secret resolution, and factory creation. func (r *ProviderRegistry) GetByAlias(ctx context.Context, alias string) (provider.Provider, error) { diff --git a/orchestrator/provider_registry_test.go b/orchestrator/provider_registry_test.go index b9ce3aa..235e864 100644 --- a/orchestrator/provider_registry_test.go +++ b/orchestrator/provider_registry_test.go @@ -3,6 +3,7 @@ package orchestrator import ( "context" "database/sql" + "slices" "testing" "github.com/GoCodeAlone/workflow-plugin-agent/provider" @@ -328,6 +329,39 @@ func TestProviderRegistryNewProviderTypes(t *testing.T) { } } +func TestProviderRegistryProviderTypesSortedDefensiveCopy(t *testing.T) { + reg := NewProviderRegistry(nil, nil) + want := []string{ + "mock", "anthropic", "openai", "openai_compatible", + "anthropic_compatible", "custom", "openai_chatgpt", "openrouter", + "copilot", "cohere", "copilot_models", "openai_azure", + "anthropic_foundry", "anthropic_vertex", "bedrock", "anthropic_bedrock", + "gemini", "ollama", "llama_cpp", "claude_code", "copilot_cli", + "codex_cli", "gemini_cli", "cursor_cli", + } + slices.Sort(want) + + got := reg.ProviderTypes() + if !slices.Equal(got, want) { + t.Fatalf("ProviderTypes() = %v, want %v", got, want) + } + if !slices.IsSorted(got) { + t.Fatalf("ProviderTypes() is not sorted: %v", got) + } + + got[0] = "mutated" + if next := reg.ProviderTypes(); !slices.Equal(next, want) { + t.Fatalf("ProviderTypes() returned shared state: %v", next) + } + + reg.mu.Lock() + reg.factories["zzz_test"] = nil + reg.mu.Unlock() + if next := reg.ProviderTypes(); len(next) != len(want)+1 || next[len(next)-1] != "zzz_test" { + t.Fatalf("ProviderTypes() did not reflect registered factory: %v", next) + } +} + func TestProviderRegistryVertexFactoryRegistered(t *testing.T) { db := setupTestDB(t) sec := &memSecretsProvider{data: map[string]string{}} From 5f964205446e3d77729bce4903455b2e2ec6d68a Mon Sep 17 00:00:00 2001 From: Jon Langevin Date: Fri, 10 Jul 2026 07:32:05 -0400 Subject: [PATCH 2/2] perf(provider): shorten registry read lock --- orchestrator/provider_registry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orchestrator/provider_registry.go b/orchestrator/provider_registry.go index 17bf10e..56373f8 100644 --- a/orchestrator/provider_registry.go +++ b/orchestrator/provider_registry.go @@ -114,11 +114,11 @@ func NewProviderRegistry(db *sql.DB, secretsProvider func() secrets.Provider) *P // ProviderTypes returns the registered provider type names in sorted order. func (r *ProviderRegistry) ProviderTypes() []string { r.mu.RLock() - defer r.mu.RUnlock() types := make([]string, 0, len(r.factories)) for providerType := range r.factories { types = append(types, providerType) } + r.mu.RUnlock() slices.Sort(types) return types }