OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress, ingress key governance, monitoring, and security controls. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rose_cat707/luminary/internal/model"
|
||||
"github.com/rose_cat707/luminary/internal/provider"
|
||||
"github.com/rose_cat707/luminary/internal/store/cache"
|
||||
)
|
||||
|
||||
func (h *Handler) ListEndpointMeta(c *gin.Context) {
|
||||
category := model.ProviderCategory(c.Query("category"))
|
||||
if category == "" {
|
||||
category = model.CategoryLLM
|
||||
}
|
||||
providerType := model.ProviderType(c.Query("type"))
|
||||
if providerType == "" {
|
||||
providerType = provider.DefaultProviderType(category)
|
||||
}
|
||||
endpoints := provider.EndpointsForCategory(category)
|
||||
providerTypes := provider.TypesForCategory(category)
|
||||
type item struct {
|
||||
provider.EndpointMeta
|
||||
DefaultPath string `json:"default_path"`
|
||||
}
|
||||
out := make([]item, 0, len(endpoints))
|
||||
for _, e := range endpoints {
|
||||
out = append(out, item{
|
||||
EndpointMeta: e,
|
||||
DefaultPath: provider.DefaultPath(providerType, e.Key),
|
||||
})
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"categories": []gin.H{
|
||||
{"key": model.CategoryLLM, "label": provider.CategoryLabel(model.CategoryLLM), "supported": true},
|
||||
{"key": model.CategoryEmbedding, "label": provider.CategoryLabel(model.CategoryEmbedding), "supported": true},
|
||||
{"key": model.CategoryRerank, "label": provider.CategoryLabel(model.CategoryRerank), "supported": true},
|
||||
{"key": model.CategorySpeech, "label": provider.CategoryLabel(model.CategorySpeech), "supported": false},
|
||||
{"key": model.CategoryImage, "label": provider.CategoryLabel(model.CategoryImage), "supported": true},
|
||||
{"key": model.CategoryAudio, "label": provider.CategoryLabel(model.CategoryAudio), "supported": false},
|
||||
},
|
||||
"provider_types": providerTypes,
|
||||
"all_provider_types": provider.AllProviderTypes(),
|
||||
"endpoints": out,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) ListProviders(c *gin.Context) {
|
||||
category := c.Query("category")
|
||||
providers := h.cache.ListProviders()
|
||||
if category != "" {
|
||||
filtered := providers[:0]
|
||||
for _, p := range providers {
|
||||
if string(p.Category) == category {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
providers = filtered
|
||||
}
|
||||
out := make([]model.Provider, 0, len(providers))
|
||||
for _, p := range providers {
|
||||
p.Models = h.cache.GetModels(p.ID)
|
||||
out = append(out, p)
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": out})
|
||||
}
|
||||
|
||||
func (h *Handler) CreateProvider(c *gin.Context) {
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Type model.ProviderType `json:"type"`
|
||||
Category model.ProviderCategory `json:"category"`
|
||||
BaseURL string `json:"base_url"`
|
||||
AllowedEndpoints []string `json:"allowed_endpoints"`
|
||||
EndpointPaths map[string]string `json:"endpoint_paths"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil || req.Name == "" || req.Type == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
if req.Category == "" {
|
||||
req.Category = model.CategoryLLM
|
||||
}
|
||||
if !model.IsCategorySupported(req.Category) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "category not supported"})
|
||||
return
|
||||
}
|
||||
if !provider.IsTypeValidForCategory(req.Category, req.Type) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "protocol type not supported for this category"})
|
||||
return
|
||||
}
|
||||
if h.providerNameTaken(req.Name, 0) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "provider name already exists"})
|
||||
return
|
||||
}
|
||||
allowed := req.AllowedEndpoints
|
||||
if len(allowed) == 0 {
|
||||
allowed = provider.DefaultEndpointsForCategory(req.Category)
|
||||
}
|
||||
if err := provider.ValidateCategoryEndpoints(req.Category, allowed); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
pathsJSON, _ := json.Marshal(req.EndpointPaths)
|
||||
if req.EndpointPaths == nil {
|
||||
pathsJSON = []byte("{}")
|
||||
}
|
||||
p := model.Provider{
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Category: req.Category,
|
||||
BaseURL: req.BaseURL,
|
||||
AllowedEndpointsJSON: cache.ModelsToJSON(allowed),
|
||||
EndpointPathsJSON: string(pathsJSON),
|
||||
Status: model.ProviderUnknown,
|
||||
}
|
||||
if err := h.db.DB().Create(&p).Error; err != nil {
|
||||
if h.providerNameTaken(req.Name, 0) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "provider name already exists"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
h.cache.SetProvider(p)
|
||||
c.JSON(http.StatusCreated, p)
|
||||
}
|
||||
|
||||
func (h *Handler) GetProvider(c *gin.Context) {
|
||||
id := parseUint(c.Param("id"))
|
||||
p, ok := h.cache.GetProvider(id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
p.Keys = h.cache.ListProviderKeys(id)
|
||||
p.Models = h.cache.GetModels(id)
|
||||
c.JSON(http.StatusOK, p)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateProvider(c *gin.Context) {
|
||||
id := parseUint(c.Param("id"))
|
||||
p, ok := h.cache.GetProvider(id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
BaseURL string `json:"base_url"`
|
||||
Category model.ProviderCategory `json:"category"`
|
||||
AllowedEndpoints []string `json:"allowed_endpoints"`
|
||||
EndpointPaths map[string]string `json:"endpoint_paths"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
if req.Name != "" && req.Name != p.Name {
|
||||
if h.providerNameTaken(req.Name, p.ID) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "provider name already exists"})
|
||||
return
|
||||
}
|
||||
p.Name = req.Name
|
||||
}
|
||||
if req.BaseURL != "" {
|
||||
p.BaseURL = req.BaseURL
|
||||
}
|
||||
if req.Category != "" {
|
||||
if !model.IsCategorySupported(req.Category) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "category not supported yet"})
|
||||
return
|
||||
}
|
||||
p.Category = req.Category
|
||||
}
|
||||
if req.AllowedEndpoints != nil {
|
||||
if err := provider.ValidateCategoryEndpoints(p.Category, req.AllowedEndpoints); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
p.AllowedEndpointsJSON = cache.ModelsToJSON(req.AllowedEndpoints)
|
||||
}
|
||||
if req.EndpointPaths != nil {
|
||||
b, _ := json.Marshal(req.EndpointPaths)
|
||||
p.EndpointPathsJSON = string(b)
|
||||
}
|
||||
if req.Enabled != nil {
|
||||
p.Enabled = *req.Enabled
|
||||
}
|
||||
h.db.DB().Save(&p)
|
||||
h.cache.SetProvider(p)
|
||||
c.JSON(http.StatusOK, p)
|
||||
}
|
||||
Reference in New Issue
Block a user