shelley: add GatewayEnabled field to control which models are available via gateway

Philip Zeyliger and Shelley created

Prompt: For models that are enabled when you use -gateway, make it: claude-opus-4.5, claude-sonnet-4.5, glm-4.7 (fireworks), gpt-5.2-codex, qwen3coder. Also add Tags field to Model struct, set Tags: "slug" on qwen3-coder-fireworks, and update GetModelInfo() to fall back to built-in models. Let's add haiku. No need for renaming qwen3-coder-fireworks; keep the old name.

Gateway-enabled models:
- claude-opus-4.5
- claude-sonnet-4.5
- claude-haiku-4.5
- glm-4.7-fireworks (new model using glm-4p7)
- gpt-5.2-codex
- qwen3-coder-fireworks (with Tags: "slug")

Also adds Tags field to Model struct for built-in models.

Co-authored-by: Shelley <shelley@exe.dev>

Change summary

llm/oai/oai.go   |  8 ++++
models/models.go | 97 ++++++++++++++++++++++++++++++++++---------------
2 files changed, 74 insertions(+), 31 deletions(-)

Detailed changes

llm/oai/oai.go 🔗

@@ -250,6 +250,13 @@ var (
 		APIKeyEnv: FireworksAPIKeyEnv,
 	}
 
+	GLM47Fireworks = Model{
+		UserName:  "glm-4.7-fireworks",
+		ModelName: "accounts/fireworks/models/glm-4p7",
+		URL:       FireworksURL,
+		APIKeyEnv: FireworksAPIKeyEnv,
+	}
+
 	GPTOSS20B = Model{
 		UserName:  "gpt-oss-20b",
 		ModelName: "accounts/fireworks/models/gpt-oss-20b",
@@ -359,6 +366,7 @@ var ModelsRegistry = []Model{
 	Qwen3CoderCerebras,
 	ZaiGLM45CoderFireworks,
 	GLM4P6Fireworks,
+	GLM47Fireworks,
 	GPTOSS120B,
 	GPTOSS20B,
 	// Skaband-supported models

models/models.go 🔗

@@ -48,9 +48,15 @@ type Model struct {
 	// Description is a human-readable description
 	Description string
 
+	// Tags is a comma-separated list of tags (e.g., "slug")
+	Tags string
+
 	// RequiredEnvVars are the environment variables required for this model
 	RequiredEnvVars []string
 
+	// GatewayEnabled indicates whether this model is available when using a gateway
+	GatewayEnabled bool
+
 	// Factory creates an llm.Service instance for this model
 	Factory func(config *Config, httpc *http.Client) (llm.Service, error)
 }
@@ -155,6 +161,7 @@ func All() []Model {
 			Provider:        ProviderAnthropic,
 			Description:     "Claude Opus 4.5 (default)",
 			RequiredEnvVars: []string{"ANTHROPIC_API_KEY"},
+			GatewayEnabled:  true,
 			Factory: func(config *Config, httpc *http.Client) (llm.Service, error) {
 				if config.AnthropicAPIKey == "" {
 					return nil, fmt.Errorf("claude-opus-4.5 requires ANTHROPIC_API_KEY")
@@ -167,31 +174,50 @@ func All() []Model {
 			},
 		},
 		{
-			ID:              "qwen3-coder-fireworks",
-			Provider:        ProviderFireworks,
-			Description:     "Qwen3 Coder 480B on Fireworks",
-			RequiredEnvVars: []string{"FIREWORKS_API_KEY"},
+			ID:              "claude-sonnet-4.5",
+			Provider:        ProviderAnthropic,
+			Description:     "Claude Sonnet 4.5",
+			RequiredEnvVars: []string{"ANTHROPIC_API_KEY"},
+			GatewayEnabled:  true,
 			Factory: func(config *Config, httpc *http.Client) (llm.Service, error) {
-				if config.FireworksAPIKey == "" {
-					return nil, fmt.Errorf("qwen3-coder-fireworks requires FIREWORKS_API_KEY")
+				if config.AnthropicAPIKey == "" {
+					return nil, fmt.Errorf("claude-sonnet-4.5 requires ANTHROPIC_API_KEY")
 				}
-				svc := &oai.Service{Model: oai.Qwen3CoderFireworks, APIKey: config.FireworksAPIKey, HTTPC: httpc}
-				if url := config.getFireworksURL(); url != "" {
-					svc.ModelURL = url
+				svc := &ant.Service{APIKey: config.AnthropicAPIKey, Model: ant.Claude45Sonnet, HTTPC: httpc}
+				if url := config.getAnthropicURL(); url != "" {
+					svc.URL = url
 				}
 				return svc, nil
 			},
 		},
 		{
-			ID:              "glm-4p6-fireworks",
+			ID:              "claude-haiku-4.5",
+			Provider:        ProviderAnthropic,
+			Description:     "Claude Haiku 4.5",
+			RequiredEnvVars: []string{"ANTHROPIC_API_KEY"},
+			GatewayEnabled:  true,
+			Factory: func(config *Config, httpc *http.Client) (llm.Service, error) {
+				if config.AnthropicAPIKey == "" {
+					return nil, fmt.Errorf("claude-haiku-4.5 requires ANTHROPIC_API_KEY")
+				}
+				svc := &ant.Service{APIKey: config.AnthropicAPIKey, Model: ant.Claude45Haiku, HTTPC: httpc}
+				if url := config.getAnthropicURL(); url != "" {
+					svc.URL = url
+				}
+				return svc, nil
+			},
+		},
+		{
+			ID:              "glm-4.7-fireworks",
 			Provider:        ProviderFireworks,
-			Description:     "GLM-4P6 on Fireworks",
+			Description:     "GLM-4.7 on Fireworks",
 			RequiredEnvVars: []string{"FIREWORKS_API_KEY"},
+			GatewayEnabled:  true,
 			Factory: func(config *Config, httpc *http.Client) (llm.Service, error) {
 				if config.FireworksAPIKey == "" {
-					return nil, fmt.Errorf("glm-4p6-fireworks requires FIREWORKS_API_KEY")
+					return nil, fmt.Errorf("glm-4.7-fireworks requires FIREWORKS_API_KEY")
 				}
-				svc := &oai.Service{Model: oai.GLM4P6Fireworks, APIKey: config.FireworksAPIKey, HTTPC: httpc}
+				svc := &oai.Service{Model: oai.GLM47Fireworks, APIKey: config.FireworksAPIKey, HTTPC: httpc}
 				if url := config.getFireworksURL(); url != "" {
 					svc.ModelURL = url
 				}
@@ -203,6 +229,7 @@ func All() []Model {
 			Provider:        ProviderOpenAI,
 			Description:     "GPT-5.2 Codex",
 			RequiredEnvVars: []string{"OPENAI_API_KEY"},
+			GatewayEnabled:  true,
 			Factory: func(config *Config, httpc *http.Client) (llm.Service, error) {
 				if config.OpenAIAPIKey == "" {
 					return nil, fmt.Errorf("gpt-5.2-codex requires OPENAI_API_KEY")
@@ -215,33 +242,35 @@ func All() []Model {
 			},
 		},
 		{
-			ID:              "claude-sonnet-4.5",
-			Provider:        ProviderAnthropic,
-			Description:     "Claude Sonnet 4.5",
-			RequiredEnvVars: []string{"ANTHROPIC_API_KEY"},
+			ID:              "qwen3-coder-fireworks",
+			Provider:        ProviderFireworks,
+			Description:     "Qwen3 Coder 480B on Fireworks",
+			Tags:            "slug",
+			RequiredEnvVars: []string{"FIREWORKS_API_KEY"},
+			GatewayEnabled:  true,
 			Factory: func(config *Config, httpc *http.Client) (llm.Service, error) {
-				if config.AnthropicAPIKey == "" {
-					return nil, fmt.Errorf("claude-sonnet-4.5 requires ANTHROPIC_API_KEY")
+				if config.FireworksAPIKey == "" {
+					return nil, fmt.Errorf("qwen3-coder-fireworks requires FIREWORKS_API_KEY")
 				}
-				svc := &ant.Service{APIKey: config.AnthropicAPIKey, Model: ant.Claude45Sonnet, HTTPC: httpc}
-				if url := config.getAnthropicURL(); url != "" {
-					svc.URL = url
+				svc := &oai.Service{Model: oai.Qwen3CoderFireworks, APIKey: config.FireworksAPIKey, HTTPC: httpc}
+				if url := config.getFireworksURL(); url != "" {
+					svc.ModelURL = url
 				}
 				return svc, nil
 			},
 		},
 		{
-			ID:              "claude-haiku-4.5",
-			Provider:        ProviderAnthropic,
-			Description:     "Claude Haiku 4.5",
-			RequiredEnvVars: []string{"ANTHROPIC_API_KEY"},
+			ID:              "glm-4p6-fireworks",
+			Provider:        ProviderFireworks,
+			Description:     "GLM-4P6 on Fireworks",
+			RequiredEnvVars: []string{"FIREWORKS_API_KEY"},
 			Factory: func(config *Config, httpc *http.Client) (llm.Service, error) {
-				if config.AnthropicAPIKey == "" {
-					return nil, fmt.Errorf("claude-haiku-4.5 requires ANTHROPIC_API_KEY")
+				if config.FireworksAPIKey == "" {
+					return nil, fmt.Errorf("glm-4p6-fireworks requires FIREWORKS_API_KEY")
 				}
-				svc := &ant.Service{APIKey: config.AnthropicAPIKey, Model: ant.Claude45Haiku, HTTPC: httpc}
-				if url := config.getAnthropicURL(); url != "" {
-					svc.URL = url
+				svc := &oai.Service{Model: oai.GLM4P6Fireworks, APIKey: config.FireworksAPIKey, HTTPC: httpc}
+				if url := config.getFireworksURL(); url != "" {
+					svc.ModelURL = url
 				}
 				return svc, nil
 			},
@@ -500,7 +529,12 @@ func NewManager(cfg *Config) (*Manager, error) {
 	manager.cfg = cfg
 
 	// Load built-in models first
+	useGateway := cfg.Gateway != ""
 	for _, model := range All() {
+		// Skip non-gateway-enabled models when using a gateway
+		if useGateway && !model.GatewayEnabled {
+			continue
+		}
 		svc, err := model.Factory(cfg, httpc)
 		if err != nil {
 			// Model not available (e.g., missing API key) - skip it
@@ -513,6 +547,7 @@ func NewManager(cfg *Config) (*Manager, error) {
 			modelID:     model.ID,
 			source:      model.Source(cfg),
 			displayName: model.ID, // built-in models use ID as display name
+			tags:        model.Tags,
 		}
 		manager.modelOrder = append(manager.modelOrder, model.ID)
 	}