feat(api): add GET /api/aliases to list model aliases

Adds an admin-gated read-only endpoint that lists every model alias
config as {name, target} pairs, backed by the loader's existing
GetAllModelsConfigs().

Assisted-by: Claude:opus-4.8 [Claude Code]
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2026-06-20 09:50:44 +00:00
parent 882d320020
commit f7ad5074d9
3 changed files with 93 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
package localai
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/mudler/LocalAI/core/config"
)
// AliasInfo is one alias -> target pair.
type AliasInfo struct {
Name string `json:"name"`
Target string `json:"target"`
}
// ListAliasesEndpoint returns every configured model alias and its target.
//
// @Summary List model aliases
// @Tags models
// @Success 200 {array} AliasInfo
// @Router /api/aliases [get]
func ListAliasesEndpoint(cl *config.ModelConfigLoader) echo.HandlerFunc {
return func(c echo.Context) error {
// Non-nil so an empty result marshals as [] rather than null.
out := []AliasInfo{}
for _, cfg := range cl.GetAllModelsConfigs() {
if cfg.IsAlias() {
out = append(out, AliasInfo{Name: cfg.Name, Target: cfg.Alias})
}
}
return c.JSON(http.StatusOK, out)
}
}

View File

@@ -0,0 +1,57 @@
package localai_test
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"github.com/labstack/echo/v4"
"github.com/mudler/LocalAI/core/config"
. "github.com/mudler/LocalAI/core/http/endpoints/localai"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("ListAliasesEndpoint", func() {
var tempDir string
BeforeEach(func() {
var err error
tempDir, err = os.MkdirTemp("", "localai-aliases-test")
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
os.RemoveAll(tempDir)
})
It("returns only alias configs as name/target pairs", func() {
// Seed one real model and one alias pointing at it.
Expect(os.WriteFile(
filepath.Join(tempDir, "real.yaml"),
[]byte("name: real\nbackend: llama-cpp\nmodel: foo\n"),
0644,
)).To(Succeed())
Expect(os.WriteFile(
filepath.Join(tempDir, "gpt-4.yaml"),
[]byte("name: gpt-4\nalias: real\n"),
0644,
)).To(Succeed())
loader := config.NewModelConfigLoader(tempDir)
Expect(loader.LoadModelConfigsFromPath(tempDir)).To(Succeed())
app := echo.New()
app.GET("/api/aliases", ListAliasesEndpoint(loader))
req := httptest.NewRequest("GET", "/api/aliases", nil)
rec := httptest.NewRecorder()
app.ServeHTTP(rec, req)
Expect(rec.Code).To(Equal(http.StatusOK))
Expect(rec.Body.String()).To(ContainSubstring(`"name":"gpt-4"`))
Expect(rec.Body.String()).To(ContainSubstring(`"target":"real"`))
// The real model must not appear as an alias entry.
Expect(rec.Body.String()).ToNot(ContainSubstring(`"name":"real"`))
})
})

View File

@@ -80,6 +80,9 @@ func RegisterLocalAIRoutes(router *echo.Echo,
// Custom model edit endpoint
router.POST("/models/edit/:name", localai.EditModelEndpoint(cl, ml, appConfig), adminMiddleware)
// List model aliases endpoint
router.GET("/api/aliases", localai.ListAliasesEndpoint(cl), adminMiddleware)
// Toggle model enable/disable endpoint
router.PUT("/models/toggle-state/:name/:action", localai.ToggleStateModelEndpoint(cl, ml, appConfig), adminMiddleware)