package server import ( "context" "encoding/json" "net/http" "strings" "time" "github.com/uptrace/bunrouter" "github.com/Warky-Devs/vecna.git/pkg/adapter" "github.com/Warky-Devs/vecna.git/pkg/embedclient" ) // googleDispatch parses the wildcard "model:action" segment and routes to the // correct Google handler. The colon is a literal method separator in the // Google embedding API, not a bunrouter parameter prefix. func (h *handler) googleDispatch(w http.ResponseWriter, req bunrouter.Request) error { return h.googleDispatchWithAdapter(w, req, h.adapter, "") } func (h *handler) googleDispatchMapped(w http.ResponseWriter, req bunrouter.Request) error { em, err := h.resolveExtraMap(req.Param("mapping")) if err != nil { return writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()}) } return h.googleDispatchWithAdapter(w, req, em.Adapter, em.ForwardTarget) } func (h *handler) googleDispatchWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error { modelaction := req.Param("modelaction") // e.g. "text-embedding-foo:embedContent" idx := strings.LastIndex(modelaction, ":") if idx < 0 { return writeJSON(w, http.StatusNotFound, map[string]string{"error": "invalid Google API path"}) } model := modelaction[:idx] action := modelaction[idx+1:] ctx := context.WithValue(req.Context(), modelKey, model) req = req.WithContext(ctx) switch action { case "embedContent": return h.googleEmbedContentWithAdapter(w, req, adp, targetOverride) case "batchEmbedContents": return h.googleBatchEmbedContentsWithAdapter(w, req, adp, targetOverride) default: return writeJSON(w, http.StatusNotFound, map[string]string{"error": "unknown Google API method: " + action}) } } // --- single embedContent --- type googleEmbedContentRequest struct { Content googleContent `json:"content"` TaskType string `json:"taskType,omitempty"` } type googleContent struct { Parts []googlePart `json:"parts"` } type googlePart struct { Text string `json:"text"` } type googleEmbedContentResponse struct { Embedding googleEmbeddingValues `json:"embedding"` } type googleEmbeddingValues struct { Values []float32 `json:"values"` } func (h *handler) googleEmbedContentWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error { model, _ := req.Context().Value(modelKey).(string) var body googleEmbedContentRequest if err := json.NewDecoder(req.Body).Decode(&body); err != nil { return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"}) } texts := make([]string, len(body.Content.Parts)) for i, p := range body.Content.Parts { texts[i] = p.Text } client, targetName, targetURL := h.resolveClientOverride(targetOverride, model) trace := TraceFromContext(req.Context()) trace.ForwardTarget = targetName trace.ForwardURL = targetURL t0 := time.Now() embedResp, err := client.Embed(req.Context(), embedclient.Request{Texts: texts, Model: model}) trace.ForwardDuration = time.Since(t0) if err != nil { return writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()}) } trace.ForwardModel = embedResp.Model trace.PromptTokens = embedResp.Usage.PromptTokens trace.TotalTokens = embedResp.Usage.TotalTokens t1 := time.Now() var adapted []float32 if len(embedResp.Embeddings) > 0 { adapted, err = adp.Adapt(embedResp.Embeddings[0]) if err != nil { return writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) } } trace.TranslateDuration = time.Since(t1) writeTraceHeaders(w, trace) return writeJSON(w, http.StatusOK, googleEmbedContentResponse{ Embedding: googleEmbeddingValues{Values: adapted}, }) } // --- batch batchEmbedContents --- type googleBatchRequest struct { Requests []googleEmbedContentRequest `json:"requests"` } type googleBatchResponse struct { Embeddings []googleEmbeddingValues `json:"embeddings"` } func (h *handler) googleBatchEmbedContentsWithAdapter(w http.ResponseWriter, req bunrouter.Request, adp adapter.Adapter, targetOverride string) error { model, _ := req.Context().Value(modelKey).(string) var body googleBatchRequest if err := json.NewDecoder(req.Body).Decode(&body); err != nil { return writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"}) } var texts []string for _, r := range body.Requests { for _, p := range r.Content.Parts { texts = append(texts, p.Text) } } client, targetName, targetURL := h.resolveClientOverride(targetOverride, model) trace := TraceFromContext(req.Context()) trace.ForwardTarget = targetName trace.ForwardURL = targetURL t0 := time.Now() embedResp, err := client.Embed(req.Context(), embedclient.Request{Texts: texts, Model: model}) trace.ForwardDuration = time.Since(t0) if err != nil { return writeJSON(w, http.StatusBadGateway, map[string]string{"error": err.Error()}) } trace.ForwardModel = embedResp.Model trace.PromptTokens = embedResp.Usage.PromptTokens trace.TotalTokens = embedResp.Usage.TotalTokens t1 := time.Now() result := make([]googleEmbeddingValues, len(embedResp.Embeddings)) for i, vec := range embedResp.Embeddings { adapted, adaptErr := adp.Adapt(vec) if adaptErr != nil { return writeJSON(w, http.StatusInternalServerError, map[string]string{"error": adaptErr.Error()}) } result[i] = googleEmbeddingValues{Values: adapted} } trace.TranslateDuration = time.Since(t1) writeTraceHeaders(w, trace) return writeJSON(w, http.StatusOK, googleBatchResponse{Embeddings: result}) }