Skip to content

Commit 949f6e0

Browse files
authored
Catalog refactor and security/quality hardening pass (external/eyrie snapshot) (#60)
* feat: add Poolside provider (constant, profile, detection order, env keys) * feat: wire Groq and Poolside providers end-to-end (config, registry, deployment, onboarding) * refactor: consolidate version into root version.go with //go:embed VERSION - Remove broken internal/version/ package (embedding stale 0.1.0 copy) - Add root version.go with //go:embed VERSION (resolves to root VERSION) - Revert client/client.go to SetVersion pattern (version set by root init) - Remove legacy migration code (migrate.go, EnsureDeploymentConfigV2, etc.) - Add deploymentOwnerProviderID for poolside/groq-direct routing * fix: update Poolside default base URL to inference.poolside.ai api.poolside.ai does not resolve — Poolside runs on Baseten (inference.poolside.ai). Verified with curl that models endpoint returns available models (laguna-m.1, laguna-xs.2). * fix: add live fetcher functions for Poolside and Groq Both providers were missing FetchPoolside and FetchGroq functions and were not registered in the live.Registry map, causing 'live: unknown fetcher' errors during catalog discovery. Also updates the parity test assertion from 19 to 21 providers. * test: add live fetcher tests for Poolside and Groq Adds mock HTTP server tests, no-key tests, and JSON parsing tests for both FetchPoolside and FetchGroq, following the same pattern as existing provider tests (grok, kimi, etc.). * feat: add ClinePass provider Adds ClinePass as an OpenAI-compatible provider using the https://api.cline.bot/api/v1 endpoint with CLINE_API_KEY auth. Wires through all layers: provider spec, env config, profile, runtime profile, routing, live fetcher, client registry, compat config, deployment, and tests. * fix: set ClinePass probe to ProbeNone, use curated model list Cline API does not expose a GET /models endpoint, so credential probing fails with HTTP 404. Changed ProbeKind to ProbeNone (key is saved without validation). Live fetcher returns a curated static list of 10 known ClinePass models instead of hitting the API. * feat: add Poolside Laguna M.1 free model to ClinePass curated list * feat: set OpenRouter pricing on ClinePass models (poolside free) * feat: use ClinePass reference pricing instead of OpenRouter * test: update ClinePass pricing assertion to match reference pricing * fix: add missing legacyDeploymentAndOwner entries for ClinePass, Poolside, Groq, MiniMax * Catalog refactor and security/quality hardening pass - Catalog v1 spec parsing, provider discovery/refresh, live enrichment, and deployment routing refactor - Remove deprecated legacy catalog shim - Fix stale ProtocolID field references in default_catalog_test.go and model_policy_test.go to match the renamed Deployment.APIProtocolID field * chore: apply formatting and security-hardening fixes across catalog, client and config
1 parent 57950ff commit 949f6e0

104 files changed

Lines changed: 2067 additions & 2007 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

catalog/bootstrap.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,28 @@ func BootstrapSource() string {
99
return bootstrapSource
1010
}
1111

12-
// BootstrapCatalogV1 returns deployment/provider wiring only — no chat models.
12+
// BootstrapCatalog returns deployment/provider wiring only — no chat models.
1313
// Chat models come from the published catalog cache and live provider discovery.
14-
func BootstrapCatalogV1() CatalogV1 {
14+
func BootstrapCatalog() Catalog {
1515
generatedAt := time.Now().UTC().Truncate(time.Second)
16-
c := CatalogV1{
17-
SchemaVersion: CatalogV1SchemaVersion,
16+
c := Catalog{
17+
SchemaVersion: CatalogSchemaVersion,
1818
GeneratedAt: generatedAt,
1919
StaleAfter: generatedAt.Add(24 * time.Hour),
20-
Providers: defaultProvidersV1(),
21-
APIProtocols: defaultAPIProtocolsV1(),
22-
Deployments: defaultDeploymentsV1(),
23-
Models: map[string]ModelV1{},
20+
Providers: defaultProviders(),
21+
Protocols: defaultProtocols(),
22+
Deployments: defaultDeployments(),
23+
Models: map[string]Model{},
2424
Aliases: map[string]string{},
2525
Offerings: nil,
26-
Provenance: &CatalogProvenanceV1{Source: bootstrapSource, ObservedAt: generatedAt},
26+
Provenance: &Provenance{Source: bootstrapSource, ObservedAt: generatedAt},
2727
}
2828
EnsureDeploymentEnvFallbacks(&c)
2929
EnsureCredentialRegistryInCatalog(&c)
3030
return c
3131
}
3232

3333
// IsBootstrapCatalog reports whether c is the empty wiring-only catalog.
34-
func IsBootstrapCatalog(c *CatalogV1) bool {
34+
func IsBootstrapCatalog(c *Catalog) bool {
3535
return c != nil && c.Provenance != nil && c.Provenance.Source == bootstrapSource
3636
}

catalog/catalog_list_test.go

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -89,36 +89,36 @@ func TestServerToolsFromOffering(t *testing.T) {
8989
t.Parallel()
9090
tests := []struct {
9191
name string
92-
offering ModelOfferingV1
92+
offering ModelOffering
9393
want []string
9494
}{
9595
{
9696
name: "nil_server_tools",
97-
offering: ModelOfferingV1{},
97+
offering: ModelOffering{},
9898
want: nil,
9999
},
100100
{
101101
name: "supported_tool",
102-
offering: ModelOfferingV1{
103-
Capabilities: CapabilitySetV1{
102+
offering: ModelOffering{
103+
Capabilities: CapabilitySet{
104104
ServerTools: map[string]CapabilityState{"web_search": CapabilitySupported},
105105
},
106106
},
107107
want: []string{"web_search"},
108108
},
109109
{
110110
name: "unsupported_tool_filtered",
111-
offering: ModelOfferingV1{
112-
Capabilities: CapabilitySetV1{
111+
offering: ModelOffering{
112+
Capabilities: CapabilitySet{
113113
ServerTools: map[string]CapabilityState{"web_search": CapabilityUnsupported},
114114
},
115115
},
116116
want: nil,
117117
},
118118
{
119119
name: "mixed_tools",
120-
offering: ModelOfferingV1{
121-
Capabilities: CapabilitySetV1{
120+
offering: ModelOffering{
121+
Capabilities: CapabilitySet{
122122
ServerTools: map[string]CapabilityState{
123123
"web_search": CapabilitySupported,
124124
"code_interp": CapabilityUnsupported,
@@ -130,8 +130,8 @@ func TestServerToolsFromOffering(t *testing.T) {
130130
},
131131
{
132132
name: "empty_tool_name_filtered",
133-
offering: ModelOfferingV1{
134-
Capabilities: CapabilitySetV1{
133+
offering: ModelOffering{
134+
Capabilities: CapabilitySet{
135135
ServerTools: map[string]CapabilityState{
136136
"": CapabilitySupported,
137137
"web_search": CapabilitySupported,
@@ -162,8 +162,8 @@ func TestModelEntryFromOffering(t *testing.T) {
162162
t.Parallel()
163163
tests := []struct {
164164
name string
165-
model ModelV1
166-
offering ModelOfferingV1
165+
model Model
166+
offering ModelOffering
167167
wantID string
168168
wantContext int
169169
wantMaxOut int
@@ -172,28 +172,28 @@ func TestModelEntryFromOffering(t *testing.T) {
172172
}{
173173
{
174174
name: "uses_native_model_id",
175-
model: ModelV1{ID: "anthropic/claude-sonnet-4-6", Name: "Sonnet 4.6", ContextWindow: 200000, MaxOutput: 32000},
176-
offering: ModelOfferingV1{
175+
model: Model{ID: "anthropic/claude-sonnet-4-6", Name: "Sonnet 4.6", ContextWindow: 200000, MaxOutput: 32000},
176+
offering: ModelOffering{
177177
NativeModelID: "claude-sonnet-4-6",
178-
Pricing: PricingV1{RatesPer1M: map[string]float64{"input_tokens": 3, "output_tokens": 15}},
178+
Pricing: Pricing{RatesPer1M: map[string]float64{"input_tokens": 3, "output_tokens": 15}},
179179
},
180180
wantID: "claude-sonnet-4-6", wantContext: 200000, wantMaxOut: 32000, wantInPrice: 3, wantOutPrice: 15,
181181
},
182182
{
183183
name: "empty_native_falls_back_to_model_id",
184-
model: ModelV1{ID: "openai/gpt-4o", Name: "GPT-4o"},
185-
offering: ModelOfferingV1{
184+
model: Model{ID: "openai/gpt-4o", Name: "GPT-4o"},
185+
offering: ModelOffering{
186186
NativeModelID: "",
187-
Pricing: PricingV1{Status: PricingUnknown},
187+
Pricing: Pricing{Status: PricingUnknown},
188188
},
189189
wantID: "openai/gpt-4o",
190190
},
191191
{
192192
name: "nil_rates_zeroes_prices",
193-
model: ModelV1{ID: "x/model", Name: "Model"},
194-
offering: ModelOfferingV1{
193+
model: Model{ID: "x/model", Name: "Model"},
194+
offering: ModelOffering{
195195
NativeModelID: "model",
196-
Pricing: PricingV1{Status: PricingUnknown},
196+
Pricing: Pricing{Status: PricingUnknown},
197197
},
198198
wantID: "model", wantInPrice: 0, wantOutPrice: 0,
199199
},
@@ -232,8 +232,8 @@ func TestModelEntriesForProvider_NilCompiled(t *testing.T) {
232232

233233
func TestModelEntriesForProvider_EmptyProvider(t *testing.T) {
234234
t.Parallel()
235-
compiled := &CompiledCatalogV1{
236-
ModelsByID: map[string]ModelV1{
235+
compiled := &CompiledCatalog{
236+
ModelsByID: map[string]Model{
237237
"anthropic/claude-sonnet-4-6": {ID: "anthropic/claude-sonnet-4-6", Name: "Sonnet", ProviderID: "anthropic"},
238238
},
239239
}
@@ -245,11 +245,11 @@ func TestModelEntriesForProvider_EmptyProvider(t *testing.T) {
245245

246246
func TestModelEntriesForProvider_DeduplicatesByNativeID(t *testing.T) {
247247
t.Parallel()
248-
compiled := &CompiledCatalogV1{
249-
ModelsByID: map[string]ModelV1{
248+
compiled := &CompiledCatalog{
249+
ModelsByID: map[string]Model{
250250
"anthropic/claude-sonnet-4-6": {ID: "anthropic/claude-sonnet-4-6", Name: "Sonnet", ProviderID: "anthropic"},
251251
},
252-
OfferingsByDeployment: map[string][]ModelOfferingV1{
252+
OfferingsByDeployment: map[string][]ModelOffering{
253253
"anthropic-direct": {
254254
{CanonicalModelID: "anthropic/claude-sonnet-4-6", DeploymentID: "anthropic-direct", NativeModelID: "claude-sonnet-4-6"},
255255
},
@@ -267,7 +267,7 @@ func TestDiscoveryEnvKeysFromCatalog(t *testing.T) {
267267
t.Parallel()
268268
tests := []struct {
269269
name string
270-
compiled *CompiledCatalogV1
270+
compiled *CompiledCatalog
271271
wantNil bool
272272
}{
273273
{
@@ -277,7 +277,7 @@ func TestDiscoveryEnvKeysFromCatalog(t *testing.T) {
277277
},
278278
{
279279
name: "nil_catalog",
280-
compiled: &CompiledCatalogV1{},
280+
compiled: &CompiledCatalog{},
281281
wantNil: true,
282282
},
283283
}
@@ -293,8 +293,8 @@ func TestDiscoveryEnvKeysFromCatalog(t *testing.T) {
293293

294294
func TestDiscoveryEnvKeysFromCatalog_ReturnsUniqueKeys(t *testing.T) {
295295
t.Parallel()
296-
c := testLegacyCatalogV1()
297-
compiled, err := CompileCatalogV1(&c)
296+
c := SeedCatalog()
297+
compiled, err := CompileCatalog(&c)
298298
if err != nil {
299299
t.Fatal(err)
300300
}
@@ -318,8 +318,8 @@ func TestDiscoveryEnvKeysFromCatalog_ReturnsUniqueKeys(t *testing.T) {
318318

319319
func TestAPIKeyEnvsForProvider(t *testing.T) {
320320
t.Parallel()
321-
c := testLegacyCatalogV1()
322-
compiled, err := CompileCatalogV1(&c)
321+
c := SeedCatalog()
322+
compiled, err := CompileCatalog(&c)
323323
if err != nil {
324324
t.Fatal(err)
325325
}
@@ -378,8 +378,8 @@ func TestPrimaryAPIKeyEnvForDeployment(t *testing.T) {
378378

379379
func TestPrimaryAPIKeyEnvForDeployment_WithCompiled(t *testing.T) {
380380
t.Parallel()
381-
c := testLegacyCatalogV1()
382-
compiled, err := CompileCatalogV1(&c)
381+
c := SeedCatalog()
382+
compiled, err := CompileCatalog(&c)
383383
if err != nil {
384384
t.Fatal(err)
385385
}

catalog/catalog_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestGetModelDeprecationWarning(t *testing.T) {
6464

6565
func TestModelsForProvider(t *testing.T) {
6666
t.Parallel()
67-
cat := testLegacyModelCatalog()
67+
cat := testModelCatalog()
6868
models := cat.Providers["anthropic"]
6969
if len(models) == 0 {
7070
t.Error("expected anthropic models in default catalog")

catalog/compiled_list.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
// ModelEntriesForProvider lists models from a compiled v1 catalog for one provider.
1212
// New models appear here automatically when the eyrie catalog is updated — hosts must not hardcode IDs.
13-
func ModelEntriesForProvider(compiled *CompiledCatalogV1, provider string) []ModelCatalogEntry {
13+
func ModelEntriesForProvider(compiled *CompiledCatalog, provider string) []ModelCatalogEntry {
1414
if compiled == nil {
1515
return nil
1616
}
@@ -24,7 +24,7 @@ func ModelEntriesForProvider(compiled *CompiledCatalogV1, provider string) []Mod
2424
return modelEntriesByProviderID(compiled, provider)
2525
}
2626

27-
func modelEntriesByProviderID(compiled *CompiledCatalogV1, provider string) []ModelCatalogEntry {
27+
func modelEntriesByProviderID(compiled *CompiledCatalog, provider string) []ModelCatalogEntry {
2828
seen := map[string]bool{}
2929
var out []ModelCatalogEntry
3030
ids := make([]string, 0, len(compiled.ModelsByID))
@@ -48,7 +48,7 @@ func modelEntriesByProviderID(compiled *CompiledCatalogV1, provider string) []Mo
4848

4949
// CanonicalModelForProviderNative maps a picker native id to the canonical model for that provider's
5050
// deployment, without using global catalog aliases (e.g. mimo-v2.5-pro → xiaomi, not opencodego).
51-
func CanonicalModelForProviderNative(compiled *CompiledCatalogV1, providerID, modelID string) (string, bool) {
51+
func CanonicalModelForProviderNative(compiled *CompiledCatalog, providerID, modelID string) (string, bool) {
5252
if compiled == nil {
5353
return "", false
5454
}
@@ -70,7 +70,7 @@ func CanonicalModelForProviderNative(compiled *CompiledCatalogV1, providerID, mo
7070
return "", false
7171
}
7272

73-
func modelEntriesForDeployment(compiled *CompiledCatalogV1, deploymentID string) []ModelCatalogEntry {
73+
func modelEntriesForDeployment(compiled *CompiledCatalog, deploymentID string) []ModelCatalogEntry {
7474
if compiled == nil || deploymentID == "" {
7575
return nil
7676
}
@@ -95,7 +95,7 @@ func modelEntriesForDeployment(compiled *CompiledCatalogV1, deploymentID string)
9595
return out
9696
}
9797

98-
func modelEntryFromOffering(model ModelV1, offering ModelOfferingV1) ModelCatalogEntry {
98+
func modelEntryFromOffering(model Model, offering ModelOffering) ModelCatalogEntry {
9999
id := strings.TrimSpace(model.ID)
100100
if native := strings.TrimSpace(offering.NativeModelID); native != "" {
101101
id = native
@@ -132,14 +132,14 @@ func descriptionFromLiveMetadata(raw json.RawMessage) string {
132132
return strings.TrimSpace(meta.Description)
133133
}
134134

135-
func modelOwnerFromOffering(offering ModelOfferingV1) string {
135+
func modelOwnerFromOffering(offering ModelOffering) string {
136136
if o := ownerFromLiveMetadata(offering.LiveMetadata); o != "" {
137137
return o
138138
}
139139
return ownerFromModelID(offering.NativeModelID)
140140
}
141141

142-
func serverToolsFromOffering(offering ModelOfferingV1) []string {
142+
func serverToolsFromOffering(offering ModelOffering) []string {
143143
if offering.Capabilities.ServerTools == nil {
144144
return nil
145145
}
@@ -153,10 +153,10 @@ func serverToolsFromOffering(offering ModelOfferingV1) []string {
153153
return out
154154
}
155155

156-
func firstOfferingForModel(compiled *CompiledCatalogV1, canonicalModelID string) ModelOfferingV1 {
156+
func firstOfferingForModel(compiled *CompiledCatalog, canonicalModelID string) ModelOffering {
157157
offerings := compiled.OfferingsByCanonicalModel[canonicalModelID]
158158
if len(offerings) == 0 {
159-
return ModelOfferingV1{}
159+
return ModelOffering{}
160160
}
161161
sort.SliceStable(offerings, func(i, j int) bool {
162162
return offerings[i].DeploymentID < offerings[j].DeploymentID

catalog/compiled_list_test.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import "testing"
55
func TestModelEntriesForProvider_OpenRouterUsesOfferings(t *testing.T) {
66
t.Parallel()
77
raw := []byte(`{"id":"anthropic/claude-sonnet-4-6","architecture":{"modality":"text"}}`)
8-
compiled := &CompiledCatalogV1{
9-
ModelsByID: map[string]ModelV1{
8+
compiled := &CompiledCatalog{
9+
ModelsByID: map[string]Model{
1010
"anthropic/claude-sonnet-4-6": {ID: "anthropic/claude-sonnet-4-6", Name: "Sonnet", ProviderID: "anthropic"},
1111
},
12-
OfferingsByDeployment: map[string][]ModelOfferingV1{
12+
OfferingsByDeployment: map[string][]ModelOffering{
1313
"openrouter": {{
1414
CanonicalModelID: "anthropic/claude-sonnet-4-6",
1515
DeploymentID: "openrouter",
@@ -30,11 +30,11 @@ func TestModelEntriesForProvider_OpenRouterUsesOfferings(t *testing.T) {
3030
func TestModelEntriesForProvider_CanopyWaveUsesDeploymentOfferings(t *testing.T) {
3131
t.Parallel()
3232
raw := []byte(`{"id":"moonshotai/kimi-k2.6","name":"Kimi K2.6","owned_by":"moonshotai"}`)
33-
compiled := &CompiledCatalogV1{
34-
ModelsByID: map[string]ModelV1{
33+
compiled := &CompiledCatalog{
34+
ModelsByID: map[string]Model{
3535
"moonshotai/kimi-k2.6": {ID: "moonshotai/kimi-k2.6", Name: "Kimi K2.6", ProviderID: "moonshotai"},
3636
},
37-
OfferingsByDeployment: map[string][]ModelOfferingV1{
37+
OfferingsByDeployment: map[string][]ModelOffering{
3838
"canopywave": {{
3939
CanonicalModelID: "moonshotai/kimi-k2.6",
4040
DeploymentID: "canopywave",
@@ -54,13 +54,13 @@ func TestModelEntriesForProvider_CanopyWaveUsesDeploymentOfferings(t *testing.T)
5454

5555
func TestModelEntriesForProvider_GeminiUsesDirectDeploymentOfferings(t *testing.T) {
5656
t.Parallel()
57-
compiled := &CompiledCatalogV1{
58-
ModelsByID: map[string]ModelV1{
57+
compiled := &CompiledCatalog{
58+
ModelsByID: map[string]Model{
5959
"gemini-flash": {ID: "gemini-flash", Name: "Flash", ProviderID: "google"},
6060
"gemini-pro": {ID: "gemini-pro", Name: "Pro", ProviderID: "google"},
6161
"other-model": {ID: "other-model", Name: "Other", ProviderID: "google"},
6262
},
63-
OfferingsByDeployment: map[string][]ModelOfferingV1{
63+
OfferingsByDeployment: map[string][]ModelOffering{
6464
"gemini-direct": {
6565
{CanonicalModelID: "gemini-flash", DeploymentID: "gemini-direct", NativeModelID: "gemini-flash"},
6666
{CanonicalModelID: "gemini-pro", DeploymentID: "gemini-direct", NativeModelID: "gemini-pro"},
@@ -75,17 +75,17 @@ func TestModelEntriesForProvider_GeminiUsesDirectDeploymentOfferings(t *testing.
7575

7676
func TestCanonicalModelForProviderNative_PrefersDeploymentOverGlobalAlias(t *testing.T) {
7777
t.Parallel()
78-
compiled := &CompiledCatalogV1{
79-
Catalog: &CatalogV1{
78+
compiled := &CompiledCatalog{
79+
Catalog: &Catalog{
8080
Aliases: map[string]string{
8181
"mimo-v2.5-pro": "opencodego/mimo-v2.5-pro",
8282
},
8383
},
84-
ModelsByID: map[string]ModelV1{
84+
ModelsByID: map[string]Model{
8585
"opencodego/mimo-v2.5-pro": {ID: "opencodego/mimo-v2.5-pro", ProviderID: "opencodego"},
8686
"xiaomi_mimo_token_plan/mimo-v2.5-pro": {ID: "xiaomi_mimo_token_plan/mimo-v2.5-pro", ProviderID: "xiaomi_mimo_token_plan"},
8787
},
88-
OfferingsByDeployment: map[string][]ModelOfferingV1{
88+
OfferingsByDeployment: map[string][]ModelOffering{
8989
"xiaomi_mimo_token_plan-direct": {{
9090
CanonicalModelID: "xiaomi_mimo_token_plan/mimo-v2.5-pro",
9191
DeploymentID: "xiaomi_mimo_token_plan-direct",
@@ -101,12 +101,12 @@ func TestCanonicalModelForProviderNative_PrefersDeploymentOverGlobalAlias(t *tes
101101

102102
func TestModelEntriesForProvider_AnthropicUsesDirectDeploymentOfferings(t *testing.T) {
103103
t.Parallel()
104-
compiled := &CompiledCatalogV1{
105-
ModelsByID: map[string]ModelV1{
104+
compiled := &CompiledCatalog{
105+
ModelsByID: map[string]Model{
106106
"anthropic/claude-sonnet-4-6": {ID: "anthropic/claude-sonnet-4-6", Name: "Sonnet", ProviderID: "anthropic"},
107107
"openai/gpt-4o": {ID: "openai/gpt-4o", Name: "GPT-4o", ProviderID: "openai"},
108108
},
109-
OfferingsByDeployment: map[string][]ModelOfferingV1{
109+
OfferingsByDeployment: map[string][]ModelOffering{
110110
"anthropic-direct": {{
111111
CanonicalModelID: "anthropic/claude-sonnet-4-6",
112112
DeploymentID: "anthropic-direct",

0 commit comments

Comments
 (0)