package server import ( "encoding/json" "io" "net/http" "strings" "time" "github.com/uptrace/bunrouter" "github.com/Warky-Devs/vecna.git/pkg/forwardproxy" ) // passthroughRoute describes one OpenAI-compatible endpoint forwarded verbatim. type passthroughRoute struct { method string path string // route pattern registered with bunrouter (":model" etc.) } // passthroughRoutes lists the OpenAI-compatible endpoints vecna forwards without // any body processing, beyond /v1/embeddings (which gets dimension adaptation). var passthroughRoutes = []passthroughRoute{ {http.MethodGet, "/v1/models"}, {http.MethodGet, "/v1/models/:model"}, {http.MethodPost, "/v1/chat/completions"}, {http.MethodPost, "/v1/completions"}, {http.MethodPost, "/v1/moderations"}, {http.MethodPost, "/v1/images/generations"}, {http.MethodPost, "/v1/images/edits"}, {http.MethodPost, "/v1/images/variations"}, {http.MethodPost, "/v1/audio/speech"}, {http.MethodPost, "/v1/audio/transcriptions"}, {http.MethodPost, "/v1/audio/translations"}, {http.MethodPost, "/v1/rerank"}, } // registerPassthroughRoutes wires up both the unmapped and /map/:mapping variants // of every entry in passthroughRoutes. func registerPassthroughRoutes(authed *bunrouter.Group, h *handler) { for _, r := range passthroughRoutes { upstreamPath := strings.Replace(r.path, ":model", "{model}", 1) authed.Handle(r.method, r.path, h.proxyHandler(r.method, upstreamPath)) authed.Handle(r.method, "/map/:mapping"+r.path, h.proxyHandlerMapped(r.method, upstreamPath)) } } // proxyHandler returns a handler that forwards method/path verbatim to the // target resolved from the request body's "model" field (or forward.default). // No vecna-specific processing (adaptation, etc.) is applied to the body. func (h *handler) proxyHandler(method, path string) bunrouter.HandlerFunc { return func(w http.ResponseWriter, req bunrouter.Request) error { return h.doProxy(w, req, method, path, "") } } // proxyHandlerMapped is the /map/:mapping variant: the target is always the // extra_map's forward_target, regardless of the body's "model" field. func (h *handler) proxyHandlerMapped(method, path string) bunrouter.HandlerFunc { return func(w http.ResponseWriter, req bunrouter.Request) error { em, err := h.resolveExtraMap(req.Param("mapping")) if err != nil { return writeError(w, http.StatusNotFound, err.Error()) } return h.doProxy(w, req, method, path, em.ForwardTarget) } } func (h *handler) doProxy(w http.ResponseWriter, req bunrouter.Request, method, pathTemplate, targetOverride string) error { model := req.Param("model") upstreamPath := strings.Replace(pathTemplate, "{model}", model, 1) var body []byte if method != http.MethodGet && method != http.MethodDelete { b, err := io.ReadAll(req.Body) if err != nil { return writeError(w, http.StatusBadRequest, "invalid request body") } body = b if model == "" && strings.HasPrefix(req.Header.Get("Content-Type"), "application/json") { model = modelPeek(body) } } fwd, targetName, err := h.resolveForwarderOverride(targetOverride, model) if err != nil { return writeError(w, http.StatusBadGateway, err.Error()) } trace := TraceFromContext(req.Context()) trace.ForwardTarget = targetName if isStreaming(body) { return h.streamProxy(w, req, fwd, method, upstreamPath, body, trace) } t0 := time.Now() res, err := fwd.Do(req.Context(), method, upstreamPath, body, req.Header) trace.ForwardDuration = time.Since(t0) if err != nil { return writeError(w, http.StatusBadGateway, err.Error()) } trace.ForwardURL = res.Target writeTraceHeaders(w, trace) writeUpstreamResult(w, res) return nil } // streamProxy relays a single-attempt streaming response (e.g. SSE chat // completions) directly to the client as bytes arrive. func (h *handler) streamProxy(w http.ResponseWriter, req bunrouter.Request, fwd *forwardproxy.Router, method, path string, body []byte, trace *RequestTrace) error { t0 := time.Now() status, header, respBody, err := fwd.DoStream(req.Context(), method, path, body, req.Header) trace.ForwardDuration = time.Since(t0) if err != nil { return writeError(w, http.StatusBadGateway, err.Error()) } defer func() { _ = respBody.Close() }() dst := w.Header() for k, vv := range header { if forwardproxy.SkipResponseHeader(k) { continue } for _, v := range vv { dst.Add(k, v) } } w.WriteHeader(status) flusher, _ := w.(http.Flusher) buf := make([]byte, 4096) for { n, rerr := respBody.Read(buf) if n > 0 { if _, werr := w.Write(buf[:n]); werr != nil { return nil } if flusher != nil { flusher.Flush() } } if rerr != nil { break } } return nil } func writeUpstreamResult(w http.ResponseWriter, res forwardproxy.Result) { dst := w.Header() for k, vv := range res.Header { if forwardproxy.SkipResponseHeader(k) { continue } for _, v := range vv { dst.Add(k, v) } } w.WriteHeader(res.Status) _, _ = w.Write(res.Body) } // modelPeek extracts the "model" field from a JSON body without full decoding, // used to select a forward target the same way embeddings does. func modelPeek(body []byte) string { var v struct { Model string `json:"model"` } _ = json.Unmarshal(body, &v) return v.Model } // isStreaming reports whether a JSON request body sets "stream": true. func isStreaming(body []byte) bool { var v struct { Stream bool `json:"stream"` } _ = json.Unmarshal(body, &v) return v.Stream }