first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,419 @@
package handlers
import (
"encoding/json"
"fmt"
"mime"
"strings"
"time"
"github.com/fasthttp/router"
bifrost "github.com/maximhq/bifrost/core"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/plugins/governance"
"github.com/maximhq/bifrost/transports/bifrost-http/integrations"
"github.com/maximhq/bifrost/transports/bifrost-http/lib"
"github.com/valyala/fasthttp"
)
// RealtimeClientSecretsHandler exposes OpenAI-compatible HTTP routes for
// minting short-lived Realtime client secrets.
type RealtimeClientSecretsHandler struct {
client *bifrost.Bifrost
config *lib.Config
handlerStore lib.HandlerStore
routeSpecs map[string]schemas.RealtimeSessionRoute
}
func NewRealtimeClientSecretsHandler(client *bifrost.Bifrost, config *lib.Config) *RealtimeClientSecretsHandler {
return &RealtimeClientSecretsHandler{
client: client,
config: config,
handlerStore: config,
routeSpecs: make(map[string]schemas.RealtimeSessionRoute),
}
}
func (h *RealtimeClientSecretsHandler) RegisterRoutes(r *router.Router, middlewares ...schemas.BifrostHTTPMiddleware) {
handler := lib.ChainMiddlewares(h.handleRequest, middlewares...)
for _, route := range h.realtimeSessionRoutes() {
h.routeSpecs[route.Path] = route
r.POST(route.Path, handler)
}
}
func (h *RealtimeClientSecretsHandler) findGovernancePlugin() governance.BaseGovernancePlugin {
basePlugins := h.config.BasePlugins.Load()
if basePlugins == nil {
return nil
}
for _, plugin := range *basePlugins {
if governancePlugin, ok := plugin.(governance.BaseGovernancePlugin); ok {
return governancePlugin
}
}
return nil
}
func (h *RealtimeClientSecretsHandler) handleRequest(ctx *fasthttp.RequestCtx) {
if !isJSONContentType(string(ctx.Request.Header.ContentType())) {
SendBifrostError(ctx, newRealtimeClientSecretHandlerError(
fasthttp.StatusBadRequest,
"invalid_request_error",
"Content-Type must be application/json",
nil,
))
return
}
body := append([]byte(nil), ctx.Request.Body()...)
route, ok := h.routeSpecs[string(ctx.Path())]
if !ok {
SendBifrostError(ctx, newRealtimeClientSecretHandlerError(
fasthttp.StatusNotFound,
"invalid_request_error",
"unsupported realtime client secret route",
nil,
))
return
}
providerKey, model, normalizedBody, err := resolveRealtimeClientSecretTarget(route, body)
if err != nil {
SendBifrostError(ctx, err)
return
}
bifrostCtx, cancel := lib.ConvertToBifrostContext(
ctx,
h.handlerStore.ShouldAllowDirectKeys(),
h.config.GetHeaderMatcher(),
h.config.GetMCPHeaderCombinedAllowlist(),
)
defer cancel()
bifrostCtx.SetValue(schemas.BifrostContextKeyHTTPRequestType, schemas.RealtimeRequest)
if route.DefaultProvider == schemas.OpenAI {
bifrostCtx.SetValue(schemas.BifrostContextKeyIntegrationType, "openai")
}
if governanceUserID, ok := ctx.UserValue(schemas.BifrostContextKeyUserID).(string); ok && governanceUserID != "" {
bifrostCtx.SetValue(schemas.BifrostContextKeyUserID, governanceUserID)
}
if userName, ok := ctx.UserValue(schemas.BifrostContextKeyUserName).(string); ok && userName != "" {
bifrostCtx.SetValue(schemas.BifrostContextKeyUserName, userName)
}
if bifrostErr := h.evaluateMintingGovernance(bifrostCtx, providerKey, model); bifrostErr != nil {
SendBifrostError(ctx, bifrostErr)
return
}
provider := h.client.GetProviderByKey(providerKey)
if provider == nil {
SendBifrostError(ctx, newRealtimeClientSecretHandlerError(
fasthttp.StatusBadRequest,
"invalid_request_error",
"provider not found: "+string(providerKey),
nil,
))
return
}
key, keyErr := h.client.SelectKeyForProviderRequestType(bifrostCtx, schemas.RealtimeRequest, providerKey, model)
if keyErr != nil {
SendBifrostError(ctx, newRealtimeClientSecretHandlerError(
fasthttp.StatusBadRequest,
"invalid_request_error",
keyErr.Error(),
keyErr,
))
return
}
// Resolve model aliases now that the key is selected so the forwarded body
// carries the provider's canonical model, matching wsrealtime/webrtc flows.
if resolved := key.Aliases.Resolve(model); resolved != "" && resolved != model {
model = resolved
reparsed, parseErr := schemas.ParseRealtimeClientSecretBody(normalizedBody)
if parseErr != nil {
SendBifrostError(ctx, parseErr)
return
}
rewritten, normalizeErr := normalizeRealtimeClientSecretBody(reparsed, model)
if normalizeErr != nil {
SendBifrostError(ctx, normalizeErr)
return
}
normalizedBody = rewritten
}
sessionProvider, ok := provider.(schemas.RealtimeSessionProvider)
if !ok {
SendBifrostError(ctx, realtimeSessionNotSupportedError(providerKey, provider))
return
}
resp, bifrostErr := sessionProvider.CreateRealtimeClientSecret(bifrostCtx, key, route.EndpointType, normalizedBody)
if bifrostErr != nil {
SendBifrostError(ctx, bifrostErr)
return
}
cacheRealtimeEphemeralKeyMapping(
h.handlerStore.GetKVStore(),
resp.Body,
key.ID,
bifrost.GetStringFromContext(bifrostCtx, schemas.BifrostContextKeyVirtualKey),
)
writeRealtimeClientSecretResponse(ctx, resp)
}
func (h *RealtimeClientSecretsHandler) evaluateMintingGovernance(
bifrostCtx *schemas.BifrostContext,
providerKey schemas.ModelProvider,
model string,
) *schemas.BifrostError {
governancePlugin := h.findGovernancePlugin()
if governancePlugin == nil {
return nil
}
_, bifrostErr := governancePlugin.EvaluateGovernanceRequest(bifrostCtx, &governance.EvaluationRequest{
VirtualKey: bifrost.GetStringFromContext(bifrostCtx, schemas.BifrostContextKeyVirtualKey),
Provider: providerKey,
Model: model,
UserID: bifrost.GetStringFromContext(bifrostCtx, schemas.BifrostContextKeyUserID),
}, schemas.RealtimeRequest)
return bifrostErr
}
func (h *RealtimeClientSecretsHandler) realtimeSessionRoutes() []schemas.RealtimeSessionRoute {
routes := []schemas.RealtimeSessionRoute{
{
Path: "/v1/realtime/client_secrets",
EndpointType: schemas.RealtimeSessionEndpointClientSecrets,
},
{
Path: "/v1/realtime/sessions",
EndpointType: schemas.RealtimeSessionEndpointSessions,
},
}
for _, path := range integrations.OpenAIRealtimeClientSecretPaths("/openai") {
endpointType := schemas.RealtimeSessionEndpointClientSecrets
if strings.HasSuffix(path, "/realtime/sessions") {
endpointType = schemas.RealtimeSessionEndpointSessions
}
routes = append(routes, schemas.RealtimeSessionRoute{
Path: path,
EndpointType: endpointType,
DefaultProvider: schemas.OpenAI,
})
}
return routes
}
func resolveRealtimeClientSecretTarget(route schemas.RealtimeSessionRoute, body []byte) (schemas.ModelProvider, string, []byte, *schemas.BifrostError) {
root, err := schemas.ParseRealtimeClientSecretBody(body)
if err != nil {
return "", "", nil, err
}
rawModel, err := schemas.ExtractRealtimeClientSecretModel(root)
if err != nil {
return "", "", nil, err
}
defaultProvider := route.DefaultProvider
providerKey, model := schemas.ParseModelString(rawModel, defaultProvider)
if defaultProvider == "" && providerKey == "" {
return "", "", nil, newRealtimeClientSecretHandlerError(
fasthttp.StatusBadRequest,
"invalid_request_error",
"session.model must use provider/model on /v1 realtime client secret routes",
nil,
)
}
if providerKey == "" || model == "" {
return "", "", nil, newRealtimeClientSecretHandlerError(
fasthttp.StatusBadRequest,
"invalid_request_error",
"session.model is required",
nil,
)
}
// Normalize the forwarded body so the upstream provider sees the bare model
// (strip provider prefix). Mirrors resolveRealtimeSDPTarget normalization.
normalizedBody, normalizeErr := normalizeRealtimeClientSecretBody(root, model)
if normalizeErr != nil {
return "", "", nil, normalizeErr
}
return providerKey, model, normalizedBody, nil
}
func normalizeRealtimeClientSecretBody(root map[string]json.RawMessage, bareModel string) ([]byte, *schemas.BifrostError) {
normalizedModel, marshalErr := json.Marshal(bareModel)
if marshalErr != nil {
return nil, newRealtimeClientSecretHandlerError(fasthttp.StatusInternalServerError, "server_error", "failed to encode normalized model", marshalErr)
}
// Normalize session.model if present
if sessionJSON, ok := root["session"]; ok && len(sessionJSON) > 0 {
var session map[string]json.RawMessage
if err := json.Unmarshal(sessionJSON, &session); err == nil {
if _, hasModel := session["model"]; hasModel {
session["model"] = normalizedModel
rewritten, err := json.Marshal(session)
if err != nil {
return nil, newRealtimeClientSecretHandlerError(fasthttp.StatusInternalServerError, "server_error", "failed to re-encode session", err)
}
root["session"] = rewritten
}
}
}
// Normalize top-level model if present
if _, ok := root["model"]; ok {
root["model"] = normalizedModel
}
normalized, marshalErr := json.Marshal(root)
if marshalErr != nil {
return nil, newRealtimeClientSecretHandlerError(fasthttp.StatusInternalServerError, "server_error", "failed to re-encode body", marshalErr)
}
return normalized, nil
}
const realtimeEphemeralKeyMappingPrefix = "realtime:ephemeral-key:"
type realtimeEphemeralKeyMapping struct {
KeyID string `json:"key_id,omitempty"`
VirtualKey string `json:"virtual_key,omitempty"`
}
func cacheRealtimeEphemeralKeyMapping(kv schemas.KVStore, body []byte, keyID string, virtualKey string) {
if kv == nil || len(body) == 0 || strings.TrimSpace(keyID) == "" {
return
}
token, ttl, ok := parseRealtimeEphemeralKeyMapping(body)
if !ok || strings.TrimSpace(token) == "" || ttl <= 0 {
return
}
payload, err := json.Marshal(realtimeEphemeralKeyMapping{
KeyID: strings.TrimSpace(keyID),
VirtualKey: strings.TrimSpace(virtualKey),
})
if err != nil {
logger.Warn("failed to encode realtime ephemeral key mapping for key_id=%s: %v", keyID, err)
return
}
if err := kv.SetWithTTL(buildRealtimeEphemeralKeyMappingKey(token), payload, ttl); err != nil {
logger.Warn("failed to cache realtime ephemeral key mapping for key_id=%s: %v", keyID, err)
}
}
func parseRealtimeEphemeralKeyMapping(body []byte) (string, time.Duration, bool) {
var root map[string]json.RawMessage
if err := json.Unmarshal(body, &root); err != nil {
return "", 0, false
}
var clientSecret struct {
Value string `json:"value"`
ExpiresAt int64 `json:"expires_at"`
}
// OpenAI client_secrets responses expose the ephemeral token at the top level.
// Keep accepting the nested shape too so the mapping logic stays compatible
// with any provider/session endpoint variants that wrap the secret object.
if err := json.Unmarshal(body, &clientSecret); err != nil || strings.TrimSpace(clientSecret.Value) == "" || clientSecret.ExpiresAt <= 0 {
clientSecretRaw, ok := root["client_secret"]
if !ok || len(clientSecretRaw) == 0 || string(clientSecretRaw) == "null" {
return "", 0, false
}
if err := json.Unmarshal(clientSecretRaw, &clientSecret); err != nil {
return "", 0, false
}
}
if strings.TrimSpace(clientSecret.Value) == "" || clientSecret.ExpiresAt <= 0 {
return "", 0, false
}
ttl := time.Until(time.Unix(clientSecret.ExpiresAt, 0))
if ttl <= 0 {
return "", 0, false
}
return clientSecret.Value, ttl, true
}
func buildRealtimeEphemeralKeyMappingKey(token string) string {
return realtimeEphemeralKeyMappingPrefix + strings.TrimSpace(token)
}
func realtimeSessionNotSupportedError(providerKey schemas.ModelProvider, provider schemas.Provider) *schemas.BifrostError {
if rtProvider, ok := provider.(schemas.RealtimeProvider); ok && rtProvider.SupportsRealtimeAPI() {
return newRealtimeClientSecretHandlerError(
fasthttp.StatusBadRequest,
"invalid_request_error",
fmt.Sprintf("provider %s supports realtime websocket connections but not realtime client secret creation", providerKey),
nil,
)
}
return newRealtimeClientSecretHandlerError(
fasthttp.StatusBadRequest,
"invalid_request_error",
fmt.Sprintf("provider %s does not support realtime client secret creation", providerKey),
nil,
)
}
func newRealtimeClientSecretHandlerError(status int, errorType, message string, err error) *schemas.BifrostError {
return &schemas.BifrostError{
IsBifrostError: false,
StatusCode: schemas.Ptr(status),
Error: &schemas.ErrorField{
Type: schemas.Ptr(errorType),
Message: message,
Error: err,
},
ExtraFields: schemas.BifrostErrorExtraFields{
RequestType: schemas.RealtimeRequest,
},
}
}
func writeRealtimeClientSecretResponse(ctx *fasthttp.RequestCtx, resp *schemas.BifrostPassthroughResponse) {
if resp == nil {
SendBifrostError(ctx, newRealtimeClientSecretHandlerError(
fasthttp.StatusInternalServerError,
"server_error",
"provider returned an empty realtime client secret response",
nil,
))
return
}
for key, value := range resp.Headers {
ctx.Response.Header.Set(key, value)
}
if len(ctx.Response.Header.ContentType()) == 0 {
ctx.SetContentType("application/json")
}
ctx.SetStatusCode(resp.StatusCode)
ctx.SetBody(resp.Body)
}
func isJSONContentType(contentType string) bool {
mediaType, _, err := mime.ParseMediaType(contentType)
if err != nil {
return false
}
mediaType = strings.ToLower(mediaType)
return mediaType == "application/json" || strings.HasSuffix(mediaType, "+json")
}