208 lines
5.8 KiB
Go
208 lines
5.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type pinnedAuthContextKey struct{}
|
|
|
|
type selectedAuthCallbackContextKey struct{}
|
|
|
|
type preparedModelRouteContextKey struct{}
|
|
|
|
type executionSessionContextKey struct{}
|
|
|
|
type disallowFreeAuthContextKey struct{}
|
|
|
|
type nestedExecutionTrackerKey struct{}
|
|
|
|
type nestedExecutionTracker struct {
|
|
mu sync.Mutex
|
|
called bool
|
|
}
|
|
|
|
func (t *nestedExecutionTracker) mark() {
|
|
if t == nil {
|
|
return
|
|
}
|
|
t.mu.Lock()
|
|
t.called = true
|
|
t.mu.Unlock()
|
|
}
|
|
|
|
func (t *nestedExecutionTracker) hasNestedExecution() bool {
|
|
if t == nil {
|
|
return false
|
|
}
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
return t.called
|
|
}
|
|
|
|
func withNestedExecutionTracker(ctx context.Context) (context.Context, *nestedExecutionTracker) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if existing, ok := ctx.Value(nestedExecutionTrackerKey{}).(*nestedExecutionTracker); ok && existing != nil {
|
|
return ctx, existing
|
|
}
|
|
tracker := &nestedExecutionTracker{}
|
|
return context.WithValue(ctx, nestedExecutionTrackerKey{}, tracker), tracker
|
|
}
|
|
|
|
func markNestedExecution(ctx context.Context) {
|
|
if ctx == nil {
|
|
return
|
|
}
|
|
if tracker, ok := ctx.Value(nestedExecutionTrackerKey{}).(*nestedExecutionTracker); ok && tracker != nil {
|
|
tracker.mark()
|
|
}
|
|
}
|
|
|
|
// WithPinnedAuthID returns a child context that requests execution on a specific auth ID.
|
|
func WithPinnedAuthID(ctx context.Context, authID string) context.Context {
|
|
authID = strings.TrimSpace(authID)
|
|
if authID == "" {
|
|
return ctx
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
return context.WithValue(ctx, pinnedAuthContextKey{}, authID)
|
|
}
|
|
|
|
// WithSelectedAuthIDCallback returns a child context that receives the selected auth ID.
|
|
func WithSelectedAuthIDCallback(ctx context.Context, callback func(string)) context.Context {
|
|
if callback == nil {
|
|
return ctx
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
return context.WithValue(ctx, selectedAuthCallbackContextKey{}, callback)
|
|
}
|
|
|
|
// PrepareStreamModelRoute resolves a stream route once and stores it on the returned context for execution.
|
|
// The boolean reports whether the route overrides normal model-to-provider resolution.
|
|
func (h *BaseAPIHandler) PrepareStreamModelRoute(ctx context.Context, handlerType string, modelName string, rawJSON []byte) (context.Context, bool) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
decision := h.applyModelRouter(ctx, handlerType, modelName, rawJSON, true, modelExecutionOptions{})
|
|
ctx = context.WithValue(ctx, preparedModelRouteContextKey{}, decision)
|
|
hasOverride := strings.TrimSpace(decision.ExecutorPluginID) != "" || strings.TrimSpace(decision.Provider) != ""
|
|
return ctx, hasOverride
|
|
}
|
|
|
|
func preparedModelRouteFromContext(ctx context.Context, skipRouterPluginID string) (modelRouteDecision, bool) {
|
|
// A host.model.execute_stream callback is a nested execution. Its caller is
|
|
// excluded from model routing, so an outer prepared route cannot be reused:
|
|
// it may point straight back at that caller.
|
|
if ctx == nil || strings.TrimSpace(skipRouterPluginID) != "" {
|
|
return modelRouteDecision{}, false
|
|
}
|
|
decision, ok := ctx.Value(preparedModelRouteContextKey{}).(modelRouteDecision)
|
|
return decision, ok
|
|
}
|
|
|
|
// WithExecutionSessionID returns a child context tagged with a long-lived execution session ID.
|
|
func WithExecutionSessionID(ctx context.Context, sessionID string) context.Context {
|
|
sessionID = strings.TrimSpace(sessionID)
|
|
if sessionID == "" {
|
|
return ctx
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
return context.WithValue(ctx, executionSessionContextKey{}, sessionID)
|
|
}
|
|
|
|
// WithDisallowFreeAuth returns a child context that requests skipping known free-tier credentials.
|
|
func WithDisallowFreeAuth(ctx context.Context) context.Context {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
return context.WithValue(ctx, disallowFreeAuthContextKey{}, true)
|
|
}
|
|
|
|
// headersFromContext extracts the original HTTP request headers from the gin context
|
|
// embedded in the provided context. This allows session affinity selectors to read
|
|
// client-provided session headers.
|
|
func headersFromContext(ctx context.Context) http.Header {
|
|
if ctx == nil {
|
|
return nil
|
|
}
|
|
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil {
|
|
return ginCtx.Request.Header.Clone()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// queryFromContext extracts the original HTTP request query parameters from the
|
|
// gin context embedded in the provided context. Mirrors headersFromContext so
|
|
// model routers can observe inbound query parameters for plain HTTP requests,
|
|
// where execOptions.Query is not populated by callers.
|
|
func queryFromContext(ctx context.Context) url.Values {
|
|
if ctx == nil {
|
|
return nil
|
|
}
|
|
if ginCtx, ok := ctx.Value("gin").(*gin.Context); ok && ginCtx != nil && ginCtx.Request != nil && ginCtx.Request.URL != nil {
|
|
return ginCtx.Request.URL.Query()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func pinnedAuthIDFromContext(ctx context.Context) string {
|
|
if ctx == nil {
|
|
return ""
|
|
}
|
|
raw := ctx.Value(pinnedAuthContextKey{})
|
|
switch v := raw.(type) {
|
|
case string:
|
|
return strings.TrimSpace(v)
|
|
case []byte:
|
|
return strings.TrimSpace(string(v))
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func selectedAuthIDCallbackFromContext(ctx context.Context) func(string) {
|
|
if ctx == nil {
|
|
return nil
|
|
}
|
|
raw := ctx.Value(selectedAuthCallbackContextKey{})
|
|
if callback, ok := raw.(func(string)); ok && callback != nil {
|
|
return callback
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func executionSessionIDFromContext(ctx context.Context) string {
|
|
if ctx == nil {
|
|
return ""
|
|
}
|
|
raw := ctx.Value(executionSessionContextKey{})
|
|
switch v := raw.(type) {
|
|
case string:
|
|
return strings.TrimSpace(v)
|
|
case []byte:
|
|
return strings.TrimSpace(string(v))
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func disallowFreeAuthFromContext(ctx context.Context) bool {
|
|
if ctx == nil {
|
|
return false
|
|
}
|
|
raw, ok := ctx.Value(disallowFreeAuthContextKey{}).(bool)
|
|
return ok && raw
|
|
}
|