Skip to content

Commit f10d705

Browse files
committed
Remove prefix-based credential inference; gateway-first setup only
Drop KeyPrefixes from provider registry, pattern matching, learned prefix files, and probe disambiguation on paste. Add InferenceForProvider for hosts that select a gateway before saving an API key. ResolveCredential lists registry gateways in sort order without inferring from key shape.
1 parent bf5c536 commit f10d705

15 files changed

Lines changed: 113 additions & 786 deletions

catalog/registry/derive.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ type CredentialSpec struct {
1111
DisplayName string
1212
DeploymentID string
1313
EnvVar string
14-
KeyPrefixes []string
1514
ProbeKind string
1615
ProbeBaseURL string
1716
RequiresKey bool
@@ -57,7 +56,6 @@ func CredentialRegistry() []CredentialSpec {
5756
DisplayName: s.DisplayName,
5857
DeploymentID: s.DeploymentID,
5958
EnvVar: s.CredentialEnv,
60-
KeyPrefixes: append([]string(nil), s.KeyPrefixes...),
6159
ProbeKind: string(s.ProbeKind),
6260
ProbeBaseURL: s.ProbeBaseURL,
6361
RequiresKey: s.RequiresKey,

catalog/registry/providers.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ func providerSpecs() []ProviderSpec {
1515
return []ProviderSpec{
1616
{
1717
ProviderID: "anthropic", DisplayName: "Anthropic", DeploymentID: "anthropic-direct", SortOrder: 1,
18-
RequiresKey: true, CredentialEnv: "ANTHROPIC_API_KEY", KeyPrefixes: []string{"sk-ant-"},
18+
RequiresKey: true, CredentialEnv: "ANTHROPIC_API_KEY",
1919
BaseURLEnv: []string{"ANTHROPIC_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"},
2020
ProbeKind: ProbeAnthropic, ModelStrategy: StrategyRemoteThenLive, PreferLiveMerge: true,
2121
LiveFetcherKey: "anthropic", LiveCatalogKey: "anthropic",
2222
APIProtocolID: "anthropic-messages", AdapterID: "anthropic",
2323
},
2424
{
2525
ProviderID: "openai", DisplayName: "OpenAI", DeploymentID: "openai-direct", SortOrder: 2,
26-
RequiresKey: true, CredentialEnv: "OPENAI_API_KEY", KeyPrefixes: []string{"sk-proj-", "sk-svcacct-"},
26+
RequiresKey: true, CredentialEnv: "OPENAI_API_KEY",
2727
BaseURLEnv: []string{"OPENAI_BASE_URL", "OPENAI_API_BASE"},
2828
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.openai.com/v1",
2929
ModelStrategy: StrategyRemoteThenLive, PreferLiveMerge: true,
@@ -32,15 +32,15 @@ func providerSpecs() []ProviderSpec {
3232
},
3333
{
3434
ProviderID: "gemini", DisplayName: "Google Gemini", DeploymentID: "gemini-direct", SortOrder: 3,
35-
RequiresKey: true, CredentialEnv: "GEMINI_API_KEY", KeyPrefixes: []string{"AIza", "AQ."},
35+
RequiresKey: true, CredentialEnv: "GEMINI_API_KEY",
3636
BaseURLEnv: []string{"GEMINI_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"},
3737
ProbeKind: ProbeGemini, ModelStrategy: StrategyRemoteThenLive, PreferLiveMerge: true,
3838
LiveFetcherKey: "gemini", LiveCatalogKey: "gemini",
3939
APIProtocolID: "gemini-generate-content", AdapterID: "gemini",
4040
},
4141
{
4242
ProviderID: "openrouter", DisplayName: "OpenRouter", DeploymentID: "openrouter", SortOrder: 4,
43-
RequiresKey: true, CredentialEnv: "OPENROUTER_API_KEY", KeyPrefixes: []string{"sk-or-v1-", "sk-or-"},
43+
RequiresKey: true, CredentialEnv: "OPENROUTER_API_KEY",
4444
BaseURLEnv: []string{"OPENROUTER_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"},
4545
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://openrouter.ai/api/v1",
4646
ModelStrategy: StrategyLiveOnly, PreferLiveMerge: true,
@@ -49,7 +49,7 @@ func providerSpecs() []ProviderSpec {
4949
},
5050
{
5151
ProviderID: "grok", DisplayName: "xAI (Grok)", DeploymentID: "grok-direct", SortOrder: 5,
52-
RequiresKey: true, CredentialEnv: "XAI_API_KEY", CredentialEnvFallbacks: []string{"GROK_API_KEY"}, KeyPrefixes: []string{"xai-"},
52+
RequiresKey: true, CredentialEnv: "XAI_API_KEY", CredentialEnvFallbacks: []string{"GROK_API_KEY"},
5353
BaseURLEnv: []string{"GROK_BASE_URL", "XAI_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"},
5454
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.x.ai/v1",
5555
ModelStrategy: StrategyRemoteThenLive, PreferLiveMerge: true,
@@ -67,7 +67,7 @@ func providerSpecs() []ProviderSpec {
6767
},
6868
{
6969
ProviderID: "canopywave", DisplayName: "CanopyWave", DeploymentID: "canopywave", SortOrder: 7,
70-
RequiresKey: true, CredentialEnv: "CANOPYWAVE_API_KEY", KeyPrefixes: []string{"cw_"},
70+
RequiresKey: true, CredentialEnv: "CANOPYWAVE_API_KEY",
7171
BaseURLEnv: []string{"CANOPYWAVE_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"},
7272
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://inference.canopywave.io/v1",
7373
ModelStrategy: StrategyLiveOnly, PreferLiveMerge: true,
@@ -76,7 +76,7 @@ func providerSpecs() []ProviderSpec {
7676
},
7777
{
7878
ProviderID: "opencodego", DisplayName: "OpenCode Go", DeploymentID: "opencodego", SortOrder: 8,
79-
RequiresKey: true, CredentialEnv: "OPENCODEGO_API_KEY", KeyPrefixes: []string{"ocg_"},
79+
RequiresKey: true, CredentialEnv: "OPENCODEGO_API_KEY",
8080
BaseURLEnv: []string{"OPENCODEGO_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"},
8181
ProbeKind: ProbeOpenAIModels,
8282
ProbeBaseURL: "https://opencode.ai/zen/go/v1",
@@ -95,7 +95,7 @@ func providerSpecs() []ProviderSpec {
9595
},
9696
{
9797
ProviderID: "xiaomi", DisplayName: "Xiaomi (MiMo)", DeploymentID: "xiaomi-direct", SortOrder: 10,
98-
RequiresKey: true, CredentialEnv: "XIAOMI_API_KEY", KeyPrefixes: []string{"tp-"},
98+
RequiresKey: true, CredentialEnv: "XIAOMI_API_KEY",
9999
BaseURLEnv: []string{"XIAOMI_BASE_URL", "MIMO_BASE_URL", "OPENAI_BASE_URL", "OPENAI_API_BASE"},
100100
ProbeKind: ProbeOpenAIModels, ProbeBaseURL: "https://api.xiaomimimo.com/v1",
101101
ModelStrategy: StrategyLiveOnly, PreferLiveMerge: true,
@@ -118,4 +118,4 @@ func providerSpecs() []ProviderSpec {
118118
},
119119
},
120120
}
121-
}
121+
}

catalog/registry/spec.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ type ProviderSpec struct {
3939
RequiresKey bool
4040
CredentialEnv string
4141
CredentialEnvFallbacks []string // additional env var names for the same credential
42-
KeyPrefixes []string
4342
BaseURLEnv []string
4443
ProbeKind ProbeKind
4544
ProbeBaseURL string

config/credential/inference.go

Lines changed: 7 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,127 +1,18 @@
11
package credential
22

3-
import (
4-
"context"
5-
"sort"
6-
"strings"
3+
import "context"
74

8-
"github.com/GrayCodeAI/eyrie/catalog"
9-
"github.com/GrayCodeAI/eyrie/catalog/registry"
10-
)
11-
12-
// CredentialInference is one provider/deployment match for a pasted API key (no secret).
5+
// CredentialInference is save metadata for a gateway chosen in setup UI (no secret).
136
type CredentialInference struct {
147
ProviderID string `json:"provider_id"`
158
DeploymentID string `json:"deployment_id"`
169
EnvVar string `json:"env_var"`
1710
DisplayName string `json:"display_name"`
1811
}
1912

20-
// InferCredentialsFromAPIKey returns prefix-inferred candidates (legacy API; prefer ResolveCredential).
13+
// InferCredentialsFromAPIKey is deprecated: setup is gateway-first (select provider, then paste key).
2114
func InferCredentialsFromAPIKey(ctx context.Context, secret string) []CredentialInference {
22-
res := ResolveCredential(ctx, secret)
23-
if !res.FormatOK {
24-
return nil
25-
}
26-
var out []CredentialInference
27-
for _, opt := range res.Providers {
28-
if !opt.Inferred {
29-
continue
30-
}
31-
out = append(out, InferenceFromOption(opt))
32-
}
33-
if len(out) > 0 {
34-
return out
35-
}
36-
// Fallback: catalog-backed inference for providers not in registry prefixes.
37-
return inferFromCatalog(ctx, secret)
38-
}
39-
40-
func inferFromCatalog(ctx context.Context, secret string) []CredentialInference {
41-
if err := ValidateKeyFormat(secret); err != nil {
42-
return nil
43-
}
44-
compiled, err := catalog.LoadCatalogForDiscovery(ctx)
45-
if err != nil || compiled == nil {
46-
return nil
47-
}
48-
providerIDs := matchedProviderIDsFromRegistry(secret)
49-
if len(providerIDs) == 0 {
50-
return nil
51-
}
52-
seen := map[string]bool{}
53-
var out []CredentialInference
54-
for _, pid := range providerIDs {
55-
for _, depID := range deploymentIDsForProvider(compiled, pid) {
56-
env := catalog.PrimaryAPIKeyEnvForDeployment(compiled, depID)
57-
if env == "" || seen[env] || !isProviderAPIKeyEnv(env) {
58-
continue
59-
}
60-
if err := ValidateCredentialSecret(env, secret); err != nil {
61-
continue
62-
}
63-
seen[env] = true
64-
out = append(out, CredentialInference{
65-
ProviderID: pid,
66-
DeploymentID: depID,
67-
EnvVar: env,
68-
DisplayName: inferenceDisplayName(compiled, depID, pid),
69-
})
70-
}
71-
}
72-
sort.Slice(out, func(i, j int) bool {
73-
if out[i].ProviderID != out[j].ProviderID {
74-
return out[i].ProviderID < out[j].ProviderID
75-
}
76-
return out[i].DeploymentID < out[j].DeploymentID
77-
})
78-
return out
79-
}
80-
81-
func deploymentIDsForProvider(compiled *catalog.CompiledCatalogV1, providerID string) []string {
82-
if compiled == nil || compiled.Catalog == nil {
83-
return []string{providerID + "-direct"}
84-
}
85-
providerID = catalog.CanonicalProviderID(providerID)
86-
preferred := []string{providerID + "-direct", providerID}
87-
var out []string
88-
seen := map[string]bool{}
89-
add := func(id string) {
90-
if id == "" || seen[id] {
91-
return
92-
}
93-
if _, ok := compiled.Catalog.Deployments[id]; !ok {
94-
return
95-
}
96-
seen[id] = true
97-
out = append(out, id)
98-
}
99-
for _, id := range preferred {
100-
add(id)
101-
}
102-
for id, dep := range compiled.Catalog.Deployments {
103-
if catalog.CanonicalProviderID(dep.ProviderID) == providerID {
104-
add(id)
105-
}
106-
}
107-
return out
108-
}
109-
110-
func isProviderAPIKeyEnv(env string) bool {
111-
return strings.Contains(strings.ToUpper(strings.TrimSpace(env)), "API_KEY") ||
112-
strings.Contains(strings.ToUpper(strings.TrimSpace(env)), "TOKEN")
113-
}
114-
115-
func inferenceDisplayName(compiled *catalog.CompiledCatalogV1, deploymentID, providerID string) string {
116-
if spec, ok := registry.DefaultRegistry.Get(providerID); ok {
117-
return spec.DisplayName
118-
}
119-
if compiled != nil && compiled.Catalog != nil {
120-
if dep, ok := compiled.Catalog.Deployments[deploymentID]; ok {
121-
if name := strings.TrimSpace(dep.Name); name != "" {
122-
return name
123-
}
124-
}
125-
}
126-
return providerID
127-
}
15+
_ = ctx
16+
_ = secret
17+
return nil
18+
}

config/credential/inference_test.go

Lines changed: 19 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,73 +5,35 @@ import (
55
"testing"
66
)
77

8-
func TestInferCredentialsFromAPIKey_Anthropic(t *testing.T) {
8+
func TestInferCredentialsFromAPIKey_ReturnsNil(t *testing.T) {
99
got := InferCredentialsFromAPIKey(context.Background(), "sk-ant-api03-test-key-1234567890")
10-
if len(got) == 0 {
11-
t.Fatal("expected anthropic inference")
12-
}
13-
if got[0].ProviderID != "anthropic" {
14-
t.Fatalf("provider = %q, want anthropic", got[0].ProviderID)
15-
}
16-
if got[0].EnvVar != "ANTHROPIC_API_KEY" {
17-
t.Fatalf("env = %q", got[0].EnvVar)
18-
}
19-
}
20-
21-
func TestInferCredentialsFromAPIKey_OpenRouter(t *testing.T) {
22-
got := InferCredentialsFromAPIKey(context.Background(), "sk-or-v1-test-key-1234567890")
23-
if len(got) == 0 {
24-
t.Fatal("expected openrouter inference")
25-
}
26-
found := false
27-
for _, c := range got {
28-
if c.ProviderID == "openrouter" {
29-
found = true
30-
break
31-
}
32-
}
33-
if !found {
34-
t.Fatalf("expected openrouter in %#v", got)
35-
}
36-
}
37-
38-
func TestInferCredentialsFromAPIKey_OpenAI(t *testing.T) {
39-
got := InferCredentialsFromAPIKey(context.Background(), "sk-proj-test-key-1234567890")
40-
if len(got) == 0 {
41-
t.Fatal("expected openai inference")
42-
}
43-
if got[0].ProviderID != "openai" {
44-
t.Fatalf("provider = %q, want openai", got[0].ProviderID)
45-
}
46-
}
47-
48-
func TestInferCredentialsFromAPIKey_GenericOpenAICompatible(t *testing.T) {
49-
got := InferCredentialsFromAPIKey(ContextWithoutProbeDisambiguation(context.Background()), "sk-test-key-that-could-belong-to-any-compatible-provider")
5010
if len(got) != 0 {
51-
t.Fatalf("generic sk- keys should not infer a provider, got %#v", got)
11+
t.Fatalf("expected no prefix inference, got %d", len(got))
5212
}
5313
}
5414

55-
func TestInferCredentialsFromAPIKey_Gemini(t *testing.T) {
56-
got := InferCredentialsFromAPIKey(context.Background(), "AIzaSyD-test-key-1234567890")
57-
if len(got) == 0 {
58-
t.Fatal("expected gemini inference")
15+
func TestInferenceForProvider_Anthropic(t *testing.T) {
16+
inf, err := InferenceForProvider("anthropic")
17+
if err != nil {
18+
t.Fatal(err)
5919
}
60-
if got[0].ProviderID != "gemini" {
61-
t.Fatalf("provider = %q, want gemini", got[0].ProviderID)
20+
if inf.ProviderID != "anthropic" || inf.DeploymentID != "anthropic-direct" || inf.EnvVar != "ANTHROPIC_API_KEY" {
21+
t.Fatalf("unexpected inference: %+v", inf)
6222
}
6323
}
6424

65-
func TestInferCredentialsFromAPIKey_Unknown(t *testing.T) {
66-
got := InferCredentialsFromAPIKey(context.Background(), "not-a-real-key-format")
67-
if len(got) != 0 {
68-
t.Fatalf("expected no inference, got %#v", got)
25+
func TestInferenceForProvider_Ollama(t *testing.T) {
26+
inf, err := InferenceForProvider("ollama")
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
if inf.EnvVar != "OLLAMA_BASE_URL" || inf.DeploymentID != "ollama-local" {
31+
t.Fatalf("unexpected ollama inference: %+v", inf)
6932
}
7033
}
7134

72-
func TestInferCredentialsFromAPIKey_Placeholder(t *testing.T) {
73-
got := InferCredentialsFromAPIKey(context.Background(), "your-api-key")
74-
if len(got) != 0 {
75-
t.Fatalf("expected no inference for placeholder, got %#v", got)
35+
func TestInferenceForProvider_Unknown(t *testing.T) {
36+
if _, err := InferenceForProvider("not-a-provider"); err == nil {
37+
t.Fatal("expected error for unknown provider")
7638
}
77-
}
39+
}

0 commit comments

Comments
 (0)