mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 00:46:42 +00:00
feat: support token-map rewrite for comma-separated headers and add bedrock anthropic-beta preset
This commit is contained in:
@@ -690,13 +690,6 @@ func setHeaderOverrideInContext(context map[string]interface{}, headerName strin
|
||||
if headerName == "" {
|
||||
return fmt.Errorf("header name is required")
|
||||
}
|
||||
if value == nil {
|
||||
return fmt.Errorf("header value is required")
|
||||
}
|
||||
headerValue := strings.TrimSpace(fmt.Sprintf("%v", value))
|
||||
if headerValue == "" {
|
||||
return fmt.Errorf("header value is required")
|
||||
}
|
||||
|
||||
rawHeaders := ensureMapKeyInContext(context, paramOverrideContextHeaderOverride)
|
||||
if keepOrigin {
|
||||
@@ -707,10 +700,127 @@ func setHeaderOverrideInContext(context map[string]interface{}, headerName strin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headerValue, hasValue, err := resolveHeaderOverrideValue(context, headerName, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasValue {
|
||||
delete(rawHeaders, headerName)
|
||||
return nil
|
||||
}
|
||||
|
||||
rawHeaders[headerName] = headerValue
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveHeaderOverrideValue(context map[string]interface{}, headerName string, value interface{}) (string, bool, error) {
|
||||
if value == nil {
|
||||
return "", false, fmt.Errorf("header value is required")
|
||||
}
|
||||
|
||||
if mapping, ok := value.(map[string]interface{}); ok {
|
||||
return resolveHeaderOverrideValueByMapping(context, headerName, mapping)
|
||||
}
|
||||
if mapping, ok := value.(map[string]string); ok {
|
||||
converted := make(map[string]interface{}, len(mapping))
|
||||
for key, item := range mapping {
|
||||
converted[key] = item
|
||||
}
|
||||
return resolveHeaderOverrideValueByMapping(context, headerName, converted)
|
||||
}
|
||||
|
||||
headerValue := strings.TrimSpace(fmt.Sprintf("%v", value))
|
||||
if headerValue == "" {
|
||||
return "", false, nil
|
||||
}
|
||||
return headerValue, true, nil
|
||||
}
|
||||
|
||||
func resolveHeaderOverrideValueByMapping(context map[string]interface{}, headerName string, mapping map[string]interface{}) (string, bool, error) {
|
||||
if len(mapping) == 0 {
|
||||
return "", false, fmt.Errorf("header value mapping cannot be empty")
|
||||
}
|
||||
|
||||
sourceValue, exists := getHeaderValueFromContext(context, headerName)
|
||||
if !exists {
|
||||
return "", false, nil
|
||||
}
|
||||
sourceTokens := splitHeaderListValue(sourceValue)
|
||||
if len(sourceTokens) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
wildcardValue, hasWildcard := mapping["*"]
|
||||
resultTokens := make([]string, 0, len(sourceTokens))
|
||||
for _, token := range sourceTokens {
|
||||
replacementRaw, hasReplacement := mapping[token]
|
||||
if !hasReplacement && hasWildcard {
|
||||
replacementRaw = wildcardValue
|
||||
hasReplacement = true
|
||||
}
|
||||
if !hasReplacement {
|
||||
resultTokens = append(resultTokens, token)
|
||||
continue
|
||||
}
|
||||
replacementTokens, err := parseHeaderReplacementTokens(replacementRaw)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
resultTokens = append(resultTokens, replacementTokens...)
|
||||
}
|
||||
|
||||
resultTokens = lo.Uniq(resultTokens)
|
||||
if len(resultTokens) == 0 {
|
||||
return "", false, nil
|
||||
}
|
||||
return strings.Join(resultTokens, ","), true, nil
|
||||
}
|
||||
|
||||
func parseHeaderReplacementTokens(value interface{}) ([]string, error) {
|
||||
switch raw := value.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case string:
|
||||
return splitHeaderListValue(raw), nil
|
||||
case []string:
|
||||
tokens := make([]string, 0, len(raw))
|
||||
for _, item := range raw {
|
||||
tokens = append(tokens, splitHeaderListValue(item)...)
|
||||
}
|
||||
return lo.Uniq(tokens), nil
|
||||
case []interface{}:
|
||||
tokens := make([]string, 0, len(raw))
|
||||
for _, item := range raw {
|
||||
itemTokens, err := parseHeaderReplacementTokens(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokens = append(tokens, itemTokens...)
|
||||
}
|
||||
return lo.Uniq(tokens), nil
|
||||
case map[string]interface{}, map[string]string:
|
||||
return nil, fmt.Errorf("header replacement value must be string, array or null")
|
||||
default:
|
||||
token := strings.TrimSpace(fmt.Sprintf("%v", raw))
|
||||
if token == "" {
|
||||
return nil, nil
|
||||
}
|
||||
return []string{token}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func splitHeaderListValue(raw string) []string {
|
||||
items := strings.Split(raw, ",")
|
||||
return lo.FilterMap(items, func(item string, _ int) (string, bool) {
|
||||
token := strings.TrimSpace(item)
|
||||
if token == "" {
|
||||
return "", false
|
||||
}
|
||||
return token, true
|
||||
})
|
||||
}
|
||||
|
||||
func copyHeaderInContext(context map[string]interface{}, fromHeader, toHeader string, keepOrigin bool) error {
|
||||
fromHeader = normalizeHeaderContextKey(fromHeader)
|
||||
toHeader = normalizeHeaderContextKey(toHeader)
|
||||
|
||||
@@ -1287,6 +1287,74 @@ func TestApplyParamOverrideSetHeaderKeepOrigin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideSetHeaderMapRewritesCommaSeparatedHeader(t *testing.T) {
|
||||
input := []byte(`{"temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "set_header",
|
||||
"path": "anthropic-beta",
|
||||
"value": map[string]interface{}{
|
||||
"advanced-tool-use-2025-11-20": nil,
|
||||
"computer-use-2025-01-24": "computer-use-2025-01-24",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := map[string]interface{}{
|
||||
"request_headers": map[string]interface{}{
|
||||
"anthropic-beta": "advanced-tool-use-2025-11-20, computer-use-2025-01-24",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
|
||||
headers, ok := ctx["header_override"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected header_override context map")
|
||||
}
|
||||
if headers["anthropic-beta"] != "computer-use-2025-01-24" {
|
||||
t.Fatalf("expected anthropic-beta to keep only mapped value, got: %v", headers["anthropic-beta"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideSetHeaderMapDeleteWholeHeaderWhenAllTokensCleared(t *testing.T) {
|
||||
input := []byte(`{"temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "set_header",
|
||||
"path": "anthropic-beta",
|
||||
"value": map[string]interface{}{
|
||||
"advanced-tool-use-2025-11-20": nil,
|
||||
"computer-use-2025-01-24": nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := map[string]interface{}{
|
||||
"header_override": map[string]interface{}{
|
||||
"anthropic-beta": "advanced-tool-use-2025-11-20,computer-use-2025-01-24",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverride(input, override, ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverride returned error: %v", err)
|
||||
}
|
||||
|
||||
headers, ok := ctx["header_override"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("expected header_override context map")
|
||||
}
|
||||
if _, exists := headers["anthropic-beta"]; exists {
|
||||
t.Fatalf("expected anthropic-beta to be deleted when all mapped values are null")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideConditionsObjectShorthand(t *testing.T) {
|
||||
input := []byte(`{"temperature":0.7}`)
|
||||
override := map[string]interface{}{
|
||||
@@ -1400,6 +1468,40 @@ func TestApplyParamOverrideWithRelayInfoMoveAndCopyHeaders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyParamOverrideWithRelayInfoSetHeaderMapRewritesAnthropicBeta(t *testing.T) {
|
||||
info := &RelayInfo{
|
||||
ChannelMeta: &ChannelMeta{
|
||||
ParamOverride: map[string]interface{}{
|
||||
"operations": []interface{}{
|
||||
map[string]interface{}{
|
||||
"mode": "set_header",
|
||||
"path": "anthropic-beta",
|
||||
"value": map[string]interface{}{
|
||||
"advanced-tool-use-2025-11-20": nil,
|
||||
"computer-use-2025-01-24": "computer-use-2025-01-24",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
HeadersOverride: map[string]interface{}{
|
||||
"anthropic-beta": "advanced-tool-use-2025-11-20, computer-use-2025-01-24",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ApplyParamOverrideWithRelayInfo([]byte(`{"temperature":0.7}`), info)
|
||||
if err != nil {
|
||||
t.Fatalf("ApplyParamOverrideWithRelayInfo returned error: %v", err)
|
||||
}
|
||||
|
||||
if !info.UseRuntimeHeadersOverride {
|
||||
t.Fatalf("expected runtime header override to be enabled")
|
||||
}
|
||||
if info.RuntimeHeadersOverride["anthropic-beta"] != "computer-use-2025-01-24" {
|
||||
t.Fatalf("expected anthropic-beta to be rewritten, got: %v", info.RuntimeHeadersOverride["anthropic-beta"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEffectiveHeaderOverrideUsesRuntimeOverrideAsFinalResult(t *testing.T) {
|
||||
info := &RelayInfo{
|
||||
UseRuntimeHeadersOverride: true,
|
||||
|
||||
Reference in New Issue
Block a user