package proxy import ( "net/http" "github.com/gin-gonic/gin" "github.com/openteam/server/internal/proxy/convert" ) // chatCompletions POST /v1/chat/completions func (g *Gateway) chatCompletions(c *gin.Context) { u, ok := g.resolveUser(c) if !ok { return } if !g.checkBalance(c, u) { return } br, body, err := parseBody(c) if err != nil { apiError(c, http.StatusBadRequest, "invalid_request_error", "failed to read request body") return } c.Set("protocol", convert.ProtoChat) c.Set("model_name", br.Model) ch, err := g.selectChannel(c, br.Model) if err != nil { apiError(c, http.StatusServiceUnavailable, "no_channel", "No available upstream channel") g.recordError(c, nil, nil, now(), "no_channel") return } plan, err := prepareUpstream(ch.Provider, convert.ProtoChat, body) if err != nil { apiError(c, http.StatusInternalServerError, "conversion_error", "Failed to convert request: "+err.Error()) return } sink := &usageSink{} c.Set("usage_raw", &sinkHolder{sink: sink}) g.doProxy(c, ch, plan, br.Stream, sink) } // responses POST /v1/responses(OpenAI Responses API) func (g *Gateway) responses(c *gin.Context) { u, ok := g.resolveUser(c) if !ok { return } if !g.checkBalance(c, u) { return } br, body, err := parseBody(c) if err != nil { apiError(c, http.StatusBadRequest, "invalid_request_error", "failed to read request body") return } c.Set("protocol", convert.ProtoResponses) c.Set("model_name", br.Model) ch, err := g.selectChannel(c, br.Model) if err != nil { apiError(c, http.StatusServiceUnavailable, "no_channel", "No available upstream channel") g.recordError(c, nil, nil, now(), "no_channel") return } plan, err := prepareUpstream(ch.Provider, convert.ProtoResponses, body) if err != nil { apiError(c, http.StatusInternalServerError, "conversion_error", "Failed to convert request: "+err.Error()) return } sink := &usageSink{} c.Set("usage_raw", &sinkHolder{sink: sink}) g.doProxy(c, ch, plan, br.Stream, sink) } // messages POST /v1/messages(Anthropic Messages API) func (g *Gateway) messages(c *gin.Context) { u, ok := g.resolveUser(c) if !ok { return } if !g.checkBalance(c, u) { return } br, body, err := parseBody(c) if err != nil { apiError(c, http.StatusBadRequest, "invalid_request_error", "failed to read request body") return } c.Set("protocol", convert.ProtoMessages) c.Set("model_name", br.Model) ch, err := g.selectChannel(c, br.Model) if err != nil { apiError(c, http.StatusServiceUnavailable, "no_channel", "No available upstream channel") g.recordError(c, nil, nil, now(), "no_channel") return } plan, err := prepareUpstream(ch.Provider, convert.ProtoMessages, body) if err != nil { apiError(c, http.StatusInternalServerError, "conversion_error", "Failed to convert request: "+err.Error()) return } sink := &usageSink{} c.Set("usage_raw", &sinkHolder{sink: sink}) g.doProxy(c, ch, plan, br.Stream, sink) } // usageSinkHolder 桥接:gin context 里保存 sink 引用,供 finishUsage 读取最终 usage。 type sinkHolder struct { sink *usageSink } // apiError 按客户端协议返回错误体(PLANNING §5.1.4)。 func apiError(c *gin.Context, status int, code, message string) { if p, _ := c.Get("protocol"); p == convert.ProtoMessages { // Anthropic 格式 c.AbortWithStatusJSON(status, gin.H{ "type": "error", "error": gin.H{"type": errorTypeFor(status), "message": message}, }) return } // OpenAI 格式 c.AbortWithStatusJSON(status, gin.H{ "error": gin.H{ "message": message, "type": errorTypeFor(status), "param": nil, "code": code, }, }) } func errorTypeFor(status int) string { switch status { case http.StatusUnauthorized: return "authentication_error" case http.StatusForbidden, http.StatusPaymentRequired: return "permission_error" case http.StatusNotFound, http.StatusBadRequest: return "invalid_request_error" case http.StatusTooManyRequests: return "rate_limit_error" default: return "api_error" } }