159 lines · 3.9 KB · Go
RawDownload
package executor import (	"strings" 	"github.com/router-for-me/CLIProxyAPI/v6/internal/config"	"github.com/tidwall/gjson"	"github.com/tidwall/sjson") // applyPayloadConfig applies payload default and override rules from configuration// to the given JSON payload for the specified model.// Defaults only fill missing fields, while overrides always overwrite existing values.func applyPayloadConfig(cfg *config.Config, model string, payload []byte) []byte {	return applyPayloadConfigWithRoot(cfg, model, "", "", payload)} // applyPayloadConfigWithRoot behaves like applyPayloadConfig but treats all parameter// paths as relative to the provided root path (for example, "request" for Gemini CLI)// and restricts matches to the given protocol when supplied.func applyPayloadConfigWithRoot(cfg *config.Config, model, protocol, root string, payload []byte) []byte {	if cfg == nil || len(payload) == 0 {		return payload	}	rules := cfg.Payload	if len(rules.Default) == 0 && len(rules.Override) == 0 {		return payload	}	model = strings.TrimSpace(model)	if model == "" {		return payload	}	out := payload	// Apply default rules: first write wins per field across all matching rules.	for i := range rules.Default {		rule := &rules.Default[i]		if !payloadRuleMatchesModel(rule, model, protocol) {			continue		}		for path, value := range rule.Params {			fullPath := buildPayloadPath(root, path)			if fullPath == "" {				continue			}			if gjson.GetBytes(out, fullPath).Exists() {				continue			}			updated, errSet := sjson.SetBytes(out, fullPath, value)			if errSet != nil {				continue			}			out = updated		}	}	// Apply override rules: last write wins per field across all matching rules.	for i := range rules.Override {		rule := &rules.Override[i]		if !payloadRuleMatchesModel(rule, model, protocol) {			continue		}		for path, value := range rule.Params {			fullPath := buildPayloadPath(root, path)			if fullPath == "" {				continue			}			updated, errSet := sjson.SetBytes(out, fullPath, value)			if errSet != nil {				continue			}			out = updated		}	}	return out} func payloadRuleMatchesModel(rule *config.PayloadRule, model, protocol string) bool {	if rule == nil {		return false	}	if len(rule.Models) == 0 {		return false	}	for _, entry := range rule.Models {		name := strings.TrimSpace(entry.Name)		if name == "" {			continue		}		if ep := strings.TrimSpace(entry.Protocol); ep != "" && protocol != "" && !strings.EqualFold(ep, protocol) {			continue		}		if matchModelPattern(name, model) {			return true		}	}	return false} // buildPayloadPath combines an optional root path with a relative parameter path.// When root is empty, the parameter path is used as-is. When root is non-empty,// the parameter path is treated as relative to root.func buildPayloadPath(root, path string) string {	r := strings.TrimSpace(root)	p := strings.TrimSpace(path)	if r == "" {		return p	}	if p == "" {		return r	}	if strings.HasPrefix(p, ".") {		p = p[1:]	}	return r + "." + p} // matchModelPattern performs simple wildcard matching where '*' matches zero or more characters.// Examples:////	"*-5" matches "gpt-5"//	"gpt-*" matches "gpt-5" and "gpt-4"//	"gemini-*-pro" matches "gemini-2.5-pro" and "gemini-3-pro".func matchModelPattern(pattern, model string) bool {	pattern = strings.TrimSpace(pattern)	model = strings.TrimSpace(model)	if pattern == "" {		return false	}	if pattern == "*" {		return true	}	// Iterative glob-style matcher supporting only '*' wildcard.	pi, si := 0, 0	starIdx := -1	matchIdx := 0	for si < len(model) {		if pi < len(pattern) && (pattern[pi] == model[si]) {			pi++			si++			continue		}		if pi < len(pattern) && pattern[pi] == '*' {			starIdx = pi			matchIdx = si			pi++			continue		}		if starIdx != -1 {			pi = starIdx + 1			matchIdx++			si = matchIdx			continue		}		return false	}	for pi < len(pattern) && pattern[pi] == '*' {		pi++	}	return pi == len(pattern)}