feat: implement AI gateway phase one runtime

This commit is contained in:
2026-05-09 21:18:32 +08:00
parent a5e66e79cd
commit fdcdcd477b
46 changed files with 5678 additions and 768 deletions
+81 -27
View File
@@ -164,6 +164,32 @@ func (s *Server) createPlatform(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusCreated, platform)
}
func (s *Server) createPlatformModel(w http.ResponseWriter, r *http.Request) {
var input store.CreatePlatformModelInput
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
if pathPlatformID := r.PathValue("platformID"); pathPlatformID != "" {
input.PlatformID = pathPlatformID
}
if input.PlatformID == "" {
writeError(w, http.StatusBadRequest, "platformId is required")
return
}
model, err := s.store.CreatePlatformModel(r.Context(), input)
if err != nil {
if store.IsNotFound(err) {
writeError(w, http.StatusNotFound, "base model not found")
return
}
s.logger.Error("create platform model failed", "error", err)
writeError(w, http.StatusInternalServerError, "create platform model failed")
return
}
writeJSON(w, http.StatusCreated, model)
}
func (s *Server) listModels(w http.ResponseWriter, r *http.Request) {
models, err := s.store.ListModels(r.Context())
if err != nil {
@@ -174,26 +200,6 @@ func (s *Server) listModels(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": models})
}
func (s *Server) listCatalogProviders(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListCatalogProviders(r.Context())
if err != nil {
s.logger.Error("list catalog providers failed", "error", err)
writeError(w, http.StatusInternalServerError, "list catalog providers failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) listBaseModels(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListBaseModels(r.Context())
if err != nil {
s.logger.Error("list base models failed", "error", err)
writeError(w, http.StatusInternalServerError, "list base models failed")
return
}
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) listPricingRules(w http.ResponseWriter, r *http.Request) {
items, err := s.store.ListPricingRules(r.Context())
if err != nil {
@@ -285,16 +291,32 @@ func (s *Server) disableAPIKey(w http.ResponseWriter, r *http.Request) {
}
func (s *Server) estimatePricing(w http.ResponseWriter, r *http.Request) {
user, _ := auth.UserFromContext(r.Context())
var body map[string]any
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeError(w, http.StatusBadRequest, "invalid json body")
return
}
writeJSON(w, http.StatusOK, map[string]any{
"items": []any{},
"resolver": "effective-pricing-placeholder",
"request": body,
})
model, _ := body["model"].(string)
kind, _ := body["kind"].(string)
if kind == "" {
kind = "chat.completions"
}
if model == "" {
writeError(w, http.StatusBadRequest, "model is required")
return
}
estimate, err := s.runner.Estimate(r.Context(), kind, model, body, user)
if err != nil {
if errors.Is(err, store.ErrNoModelCandidate) {
writeError(w, http.StatusNotFound, "no enabled platform model matches request")
return
}
s.logger.Error("estimate pricing failed", "error", err)
writeError(w, http.StatusInternalServerError, "estimate pricing failed")
return
}
writeJSON(w, http.StatusOK, estimate)
}
func (s *Server) listRateLimitWindows(w http.ResponseWriter, r *http.Request) {
@@ -307,7 +329,7 @@ func (s *Server) listRateLimitWindows(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"items": items})
}
func (s *Server) createTask(kind string) http.Handler {
func (s *Server) createTask(kind string, compatible bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, ok := auth.UserFromContext(r.Context())
if !ok {
@@ -337,9 +359,25 @@ func (s *Server) createTask(kind string) http.Handler {
writeError(w, http.StatusInternalServerError, "create task failed")
return
}
result, runErr := s.runner.Execute(r.Context(), task, user)
if compatible {
if runErr != nil {
writeError(w, statusFromRunError(runErr), runErr.Error())
return
}
if boolValue(body, "stream") {
writeCompatibleStream(w, kind, model, result.Output)
return
}
writeJSON(w, http.StatusOK, result.Output)
return
}
if runErr != nil {
s.logger.Warn("task completed with failure", "kind", kind, "taskId", task.ID, "error", runErr)
}
writeJSON(w, http.StatusAccepted, map[string]any{
"task": task,
"task": result.Task,
"next": map[string]string{
"events": fmt.Sprintf("/api/v1/tasks/%s/events", task.ID),
"detail": fmt.Sprintf("/api/v1/tasks/%s", task.ID),
@@ -348,6 +386,22 @@ func (s *Server) createTask(kind string) http.Handler {
})
}
func statusFromRunError(err error) int {
switch {
case errors.Is(err, store.ErrNoModelCandidate):
return http.StatusNotFound
case errors.Is(err, store.ErrRateLimited):
return http.StatusTooManyRequests
default:
return http.StatusBadGateway
}
}
func boolValue(body map[string]any, key string) bool {
value, _ := body[key].(bool)
return value
}
func (s *Server) getTask(w http.ResponseWriter, r *http.Request) {
task, err := s.store.GetTask(r.Context(), r.PathValue("taskID"))
if err == nil {