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
29 changes: 23 additions & 6 deletions internal/server/handlers_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,19 +173,23 @@ func (s *Server) handleModelOptions(w http.ResponseWriter, r *http.Request) {

// Every provider from the catalogue (configured or not), so the new kinds
// can be set up from here — then any custom providers only in the config.
// The catalogue's "custom" entry belongs to the first-run wizard only: it
// never renders here, so the providers page shows solely built-ins and
// providers the user created (named, manageable, deletable).
// The catalogue's "custom" entry belongs to the first-run wizard, but older
// installations may still use that id for their real custom provider.
seen := map[string]bool{}
providerList := make([]providerInfo, 0)
for _, sp := range setupProviderCatalogue(cfg) {
if sp.Custom {
p := cfg.Providers[sp.ID]
if sp.Custom && !legacyCustomProviderInUse(cfg, p) {
seen[sp.ID] = true
continue
}
p := cfg.Providers[sp.ID]
label, kind := sp.Label, sp.Kind
if sp.Custom {
label = firstNonEmpty(p.Label, sp.Label)
kind = firstNonEmpty(p.Kind, sp.Kind)
}
providerList = append(providerList, providerInfo{
ID: sp.ID, Label: sp.Label, Kind: sp.Kind,
ID: sp.ID, Label: label, Kind: kind,
Enabled: p.Enabled, HasKey: p.APIKey != "", Local: sp.Local,
BaseURL: firstNonEmpty(p.BaseURL, sp.BaseURL), Active: sp.ID == cfg.Model.Provider,
Hint: sp.Hint, KeyHint: sp.KeyHint, KeyURL: sp.KeyURL, KeyLabel: sp.KeyLabel,
Expand Down Expand Up @@ -217,6 +221,19 @@ func (s *Server) handleModelOptions(w http.ResponseWriter, r *http.Request) {
})
}

func legacyCustomProviderInUse(cfg *config.Config, p config.Provider) bool {
return cfg.Model.Provider == "custom" ||
p.Enabled ||
strings.TrimSpace(p.BaseURL) != "" ||
strings.TrimSpace(p.APIKey) != "" ||
strings.TrimSpace(p.APIKeyEnv) != "" ||
strings.TrimSpace(p.APIVersion) != "" ||
strings.TrimSpace(p.Region) != "" ||
len(p.Headers) > 0 ||
len(p.Models) > 0 ||
len(p.ModelMeta) > 0
}

func (s *Server) handleModelList(w http.ResponseWriter, r *http.Request) {
provider := r.URL.Query().Get("provider")
cfg := s.config()
Expand Down
92 changes: 92 additions & 0 deletions internal/server/handlers_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package server

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/enowdev/antares/internal/config"
)

type modelOptionsProvider struct {
ID string `json:"id"`
Label string `json:"label"`
Kind string `json:"kind"`
Enabled bool `json:"enabled"`
HasKey bool `json:"has_key"`
BaseURL string `json:"base_url"`
Active bool `json:"active"`
Custom bool `json:"custom"`
NeedsBaseURL bool `json:"needs_base_url"`
}

type modelOptionsResponse struct {
Providers []modelOptionsProvider `json:"providers"`
}

func modelOptions(t *testing.T, cfg *config.Config) modelOptionsResponse {
t.Helper()
s := &Server{cfg: cfg}
rr := httptest.NewRecorder()
s.handleModelOptions(rr, httptest.NewRequest(http.MethodGet, "/api/model/options", nil))
if rr.Code != http.StatusOK {
t.Fatalf("model options: status = %d (body=%s)", rr.Code, rr.Body.String())
}
var response modelOptionsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Fatalf("decode model options: %v", err)
}
return response
}

func customProviderCard(response modelOptionsResponse) (modelOptionsProvider, int) {
var match modelOptionsProvider
count := 0
for _, provider := range response.Providers {
if provider.ID != "custom" {
continue
}
match = provider
count++
}
return match, count
}

func TestModelOptionsShowsConfiguredLegacyCustomProvider(t *testing.T) {
cfg := config.Default()
cfg.Providers["custom"] = config.Provider{
Kind: "openai-compatible", BaseURL: "https://legacy.example/v1",
APIKey: "legacy-key", Enabled: true, Label: "Something else",
}
cfg.Model.Provider = "custom"

got, count := customProviderCard(modelOptions(t, cfg))
if count != 1 {
t.Fatalf("legacy custom provider cards = %d, want 1", count)
}
if got.Label != "Something else" {
t.Errorf("label = %q, want the stored legacy label", got.Label)
}
if got.Kind != "openai-compatible" {
t.Errorf("kind = %q, want the stored legacy kind", got.Kind)
}
if got.BaseURL != "https://legacy.example/v1" {
t.Errorf("base_url = %q, want the stored legacy endpoint", got.BaseURL)
}
if !got.Enabled || !got.HasKey || !got.Active || !got.Custom || !got.NeedsBaseURL {
t.Errorf("legacy card flags = %+v, want enabled, keyed, active, custom, and needs_base_url", got)
}
}

func TestModelOptionsHidesUnusedLegacyCustomPlaceholder(t *testing.T) {
cfg := config.Default()
cfg.Providers["custom"] = config.Provider{
Kind: "custom", Label: "Custom endpoint", TimeoutSecs: 300,
}

_, count := customProviderCard(modelOptions(t, cfg))
if count != 0 {
t.Fatalf("unused legacy custom provider cards = %d, want 0", count)
}
}
Loading