Files
vecna/pkg/server/handler.go
Hein c7a3fed6e1 feat(server): add support for extra maps in adapter configuration
* Introduced ExtraMapConfig to allow multiple adapter configurations.
* Updated server and handler to utilize extra maps for routing.
* Added dashboard handler for metrics visualization.
2026-04-11 21:43:14 +02:00

103 lines
3.2 KiB
Go

package server
import (
"context"
"encoding/json"
"fmt"
"net/http"
"time"
"go.uber.org/zap"
"github.com/Warky-Devs/vecna.git/pkg/adapter"
"github.com/Warky-Devs/vecna.git/pkg/config"
"github.com/Warky-Devs/vecna.git/pkg/embedclient"
)
// ExtraMap pairs a dimension adapter with an optional forward-target override.
type ExtraMap struct {
Adapter adapter.Adapter
ForwardTarget string // named target in forward.targets; empty = model-based resolution
}
// handler holds shared dependencies for all HTTP handlers.
type handler struct {
cfg *config.Config
clients map[string]embedclient.Client
adapter adapter.Adapter
extraMaps map[string]ExtraMap
logger *zap.Logger
}
// resolveExtraMap returns the ExtraMap for the named extra_map entry.
func (h *handler) resolveExtraMap(name string) (ExtraMap, error) {
em, ok := h.extraMaps[name]
if !ok {
return ExtraMap{}, fmt.Errorf("extra_map %q not configured", name)
}
return em, nil
}
// resolveClient selects the embed client for the given model name.
// Returns the client, target name, and first endpoint URL for tracing.
func (h *handler) resolveClient(model string) (embedclient.Client, string, string) {
if c, ok := h.clients[model]; ok {
url := firstEndpointURL(h.cfg, model)
return c, model, url
}
name := h.cfg.Forward.Default
c, ok := h.clients[name]
if !ok {
return &errClient{err: fmt.Errorf("no client configured for model %q and no default", model)}, name, ""
}
return c, name, firstEndpointURL(h.cfg, name)
}
// resolveClientOverride selects the client for targetOverride when set,
// otherwise falls back to model-based resolution.
func (h *handler) resolveClientOverride(targetOverride, model string) (embedclient.Client, string, string) {
if targetOverride == "" {
return h.resolveClient(model)
}
c, ok := h.clients[targetOverride]
if !ok {
return &errClient{err: fmt.Errorf("extra_map forward_target %q not configured", targetOverride)}, targetOverride, ""
}
return c, targetOverride, firstEndpointURL(h.cfg, targetOverride)
}
func firstEndpointURL(cfg *config.Config, targetName string) string {
t, ok := cfg.Forward.Targets[targetName]
if !ok || len(t.Endpoints) == 0 {
return ""
}
return t.Endpoints[0].URL
}
// writeJSON encodes v as JSON and writes it with the given status code.
func writeJSON(w http.ResponseWriter, status int, v interface{}) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(v); err != nil {
return fmt.Errorf("writeJSON: %w", err)
}
return nil
}
// writeTraceHeaders writes X-Vecna-* timing headers from the RequestTrace.
func writeTraceHeaders(w http.ResponseWriter, t *RequestTrace) {
total := time.Since(t.Start)
w.Header().Set("X-Vecna-Forward-Ms", fmt.Sprintf("%d", t.ForwardDuration.Milliseconds()))
w.Header().Set("X-Vecna-Translate-Ms", fmt.Sprintf("%d", t.TranslateDuration.Milliseconds()))
w.Header().Set("X-Vecna-Total-Ms", fmt.Sprintf("%d", total.Milliseconds()))
}
// errClient is a Client that always returns a fixed error (used as safe fallback).
type errClient struct {
err error
}
func (e *errClient) Embed(_ context.Context, _ embedclient.Request) (embedclient.Response, error) {
return embedclient.Response{}, e.err
}