Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions orchestrator/provider_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"slices"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -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()
types := make([]string, 0, len(r.factories))
for providerType := range r.factories {
types = append(types, providerType)
}
r.mu.RUnlock()
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) {
Expand Down
34 changes: 34 additions & 0 deletions orchestrator/provider_registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package orchestrator
import (
"context"
"database/sql"
"slices"
"testing"

"github.com/GoCodeAlone/workflow-plugin-agent/provider"
Expand Down Expand Up @@ -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{}}
Expand Down
Loading