Files
sub2api/backend/internal/service/openai_guardian_affinity.go
T

134 lines
3.9 KiB
Go

package service
import (
"context"
"strings"
"github.com/gin-gonic/gin"
"github.com/tidwall/gjson"
)
const (
codexAutoReviewModel = "codex-auto-review"
openAISubagentHeader = "x-openai-subagent"
codexParentThreadIDHeader = "x-codex-parent-thread-id"
codexTurnMetadataHeader = "x-codex-turn-metadata"
)
type openAIGuardianParentAffinityContextKey struct{}
type openAIGuardianParentAffinity struct {
currentSessionHash string
legacySessionHash string
}
// WithOpenAIGuardianParentAffinity records a Codex review request's parent
// thread as a routing hint. The hint is resolved against the current group's
// sticky-session namespace later; client headers never carry an account ID.
func WithOpenAIGuardianParentAffinity(ctx context.Context, c *gin.Context, body []byte, requestedModel string) context.Context {
if ctx == nil || c == nil || !strings.EqualFold(strings.TrimSpace(requestedModel), codexAutoReviewModel) {
return ctx
}
headerMetadata := c.GetHeader(codexTurnMetadataHeader)
bodyMetadata := openAIRequestPayloadView(body).Get("client_metadata.x-codex-turn-metadata").String()
if !hasUnambiguousOpenAICodexReviewSubagent(
c.GetHeader(openAISubagentHeader),
codexSubagentKindFromMetadata(headerMetadata),
codexSubagentKindFromMetadata(bodyMetadata),
) {
return ctx
}
parentID := ""
for _, candidate := range []string{
strings.TrimSpace(c.GetHeader(codexParentThreadIDHeader)),
codexParentThreadIDFromMetadata(headerMetadata),
codexParentThreadIDFromMetadata(bodyMetadata),
} {
if candidate == "" {
continue
}
if parentID != "" && parentID != candidate {
return ctx
}
parentID = candidate
}
if parentID == "" {
return ctx
}
currentHash, legacyHash := deriveOpenAISessionHashes(parentID)
if currentHash == "" {
return ctx
}
return context.WithValue(ctx, openAIGuardianParentAffinityContextKey{}, openAIGuardianParentAffinity{
currentSessionHash: currentHash,
legacySessionHash: legacyHash,
})
}
func codexParentThreadIDFromMetadata(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" || !gjson.Valid(raw) {
return ""
}
return strings.TrimSpace(gjson.Get(raw, "parent_thread_id").String())
}
func codexSubagentKindFromMetadata(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" || !gjson.Valid(raw) {
return ""
}
return strings.TrimSpace(gjson.Get(raw, "subagent_kind").String())
}
func hasUnambiguousOpenAICodexReviewSubagent(candidates ...string) bool {
subagent := ""
for _, candidate := range candidates {
candidate = strings.ToLower(strings.TrimSpace(candidate))
if candidate == "" {
continue
}
if subagent != "" && subagent != candidate {
return false
}
subagent = candidate
}
return subagent == "guardian" || subagent == "review"
}
func openAIGuardianParentAffinityFromContext(ctx context.Context) (openAIGuardianParentAffinity, bool) {
if ctx == nil {
return openAIGuardianParentAffinity{}, false
}
affinity, ok := ctx.Value(openAIGuardianParentAffinityContextKey{}).(openAIGuardianParentAffinity)
return affinity, ok && affinity.currentSessionHash != ""
}
func preserveOpenAIGuardianParentBinding(ctx context.Context, sessionHash string) bool {
affinity, ok := openAIGuardianParentAffinityFromContext(ctx)
if !ok {
return false
}
sessionHash = strings.TrimSpace(sessionHash)
return sessionHash != "" && (sessionHash == affinity.currentSessionHash || sessionHash == affinity.legacySessionHash)
}
func (s *OpenAIGatewayService) resolveOpenAIGuardianParentAccountID(ctx context.Context, groupID *int64) int64 {
if s == nil || s.cache == nil {
return 0
}
affinity, ok := openAIGuardianParentAffinityFromContext(ctx)
if !ok {
return 0
}
lookupCtx := withOpenAILegacySessionHash(ctx, affinity.legacySessionHash)
accountID, err := s.getStickySessionAccountID(lookupCtx, groupID, affinity.currentSessionHash)
if err != nil || accountID <= 0 {
return 0
}
return accountID
}