Files
sub2api/backend/internal/handler/openai_models_handler.go

110 lines
3.4 KiB
Go

package handler
import (
"encoding/json"
"errors"
"fmt"
"net/http"
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
)
func (h *GatewayHandler) pinnedOpenAIModels(c *gin.Context, group *service.Group) {
if c.Request.Context().Err() != nil {
return
}
if h.openAIGatewayService == nil {
writeOpenAIModelsError(c, http.StatusInternalServerError, "api_error", "OpenAI model discovery is not configured")
return
}
etag := c.GetHeader("If-None-Match")
if c.Param("model") != "" {
etag = "" // A collection ETag cannot validate a single-model representation.
}
response, account, err := h.openAIGatewayService.FetchPinnedOpenAIModelsList(
c.Request.Context(), group, h.maxAccountSwitches, etag,
)
if c.Request.Context().Err() != nil {
return
}
if err != nil {
if errors.Is(err, service.ErrNoPinnedCodexModelsAccounts) {
writeOpenAIModelsError(c, http.StatusServiceUnavailable, "upstream_error", "No available OpenAI model discovery accounts")
return
}
writeOpenAIModelsError(c, infraerrors.Code(err), "upstream_error", infraerrors.Message(err))
return
}
setOpsSelectedAccount(c, account.ID, account.Platform)
writeOpenAIModelsResponse(c, response)
}
func writeOpenAIModelsError(c *gin.Context, status int, errorType, message string) {
c.JSON(status, gin.H{"error": gin.H{"type": errorType, "message": message}})
}
func writeOpenAIModelsResponse(c *gin.Context, manifest *service.OpenAIModelsResponse) {
if c.Param("model") != "" {
writeRetrievedModel(c, manifest.Body)
return
}
if manifest.ETag != "" {
c.Header("ETag", manifest.ETag)
}
if manifest.NotModified {
c.Status(http.StatusNotModified)
c.Writer.WriteHeaderNow()
return
}
c.Data(http.StatusOK, "application/json", manifest.Body)
}
// Both discovery endpoints consume the same final catalogue, after group/platform
// selection and allowlist filtering. Preserve every field on the selected entry.
func writeModelsListResponse(c *gin.Context, models any) {
response := gin.H{"object": "list", "data": models}
if c.Param("model") == "" {
c.JSON(http.StatusOK, response)
return
}
body, err := json.Marshal(response)
if err != nil {
writeOpenAIModelsError(c, http.StatusInternalServerError, "api_error", "Failed to encode model catalogue")
return
}
writeRetrievedModel(c, body)
}
func writeRetrievedModel(c *gin.Context, body []byte) {
var catalog struct {
Data []json.RawMessage `json:"data"`
}
if err := json.Unmarshal(body, &catalog); err != nil {
writeOpenAIModelsError(c, http.StatusBadGateway, "upstream_error", "Invalid model catalogue")
return
}
modelID := c.Param("model")
for _, raw := range catalog.Data {
var model map[string]json.RawMessage
if err := json.Unmarshal(raw, &model); err != nil {
writeOpenAIModelsError(c, http.StatusBadGateway, "upstream_error", "Invalid model catalogue entry")
return
}
var id string
if err := json.Unmarshal(model["id"], &id); err != nil {
writeOpenAIModelsError(c, http.StatusBadGateway, "upstream_error", "Invalid model catalogue ID")
return
}
if id == modelID {
c.Data(http.StatusOK, "application/json", raw)
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": gin.H{
"type": "invalid_request_error", "code": "model_not_found", "param": "model",
"message": fmt.Sprintf("Model %q does not exist or is not available for this group", modelID),
}})
}