Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
d087cc50
authored
Mar 09, 2026
by
Seefs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat:support $keep_only_declared and deduped $append for header token overrides
parent
d67f446b
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
178 additions
and
10 deletions
+178
-10
relay/common/override.go
+35
-8
relay/common/override_test.go
+135
-0
web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx
+8
-2
No files found.
relay/common/override.go
View file @
d087cc50
...
@@ -847,24 +847,30 @@ func resolveHeaderOverrideValueByMapping(context map[string]interface{}, headerN
...
@@ -847,24 +847,30 @@ func resolveHeaderOverrideValueByMapping(context map[string]interface{}, headerN
return
""
,
false
,
fmt
.
Errorf
(
"header value mapping cannot be empty"
)
return
""
,
false
,
fmt
.
Errorf
(
"header value mapping cannot be empty"
)
}
}
sourceValue
,
exists
:=
getHeaderValueFromContext
(
context
,
headerName
)
appendTokens
,
err
:=
parseHeaderAppendTokens
(
mapping
)
if
!
exists
{
if
err
!=
nil
{
return
""
,
false
,
nil
return
""
,
false
,
err
}
}
sourceTokens
:=
splitHeaderListValue
(
sourceValue
)
keepOnlyDeclared
:=
parseHeaderKeepOnlyDeclared
(
mapping
)
if
len
(
sourceTokens
)
==
0
{
return
""
,
false
,
nil
sourceValue
,
exists
:=
getHeaderValueFromContext
(
context
,
headerName
)
sourceTokens
:=
make
([]
string
,
0
)
if
exists
{
sourceTokens
=
splitHeaderListValue
(
sourceValue
)
}
}
wildcardValue
,
hasWildcard
:=
mapping
[
"*"
]
wildcardValue
,
hasWildcard
:=
mapping
[
"*"
]
resultTokens
:=
make
([]
string
,
0
,
len
(
sourceTokens
))
resultTokens
:=
make
([]
string
,
0
,
len
(
sourceTokens
)
+
len
(
appendTokens
)
)
for
_
,
token
:=
range
sourceTokens
{
for
_
,
token
:=
range
sourceTokens
{
replacementRaw
,
hasReplacement
:=
mapping
[
token
]
replacementRaw
,
hasReplacement
:=
mapping
[
token
]
if
!
hasReplacement
&&
hasWildcard
{
if
!
hasReplacement
&&
hasWildcard
&&
!
keepOnlyDeclared
{
replacementRaw
=
wildcardValue
replacementRaw
=
wildcardValue
hasReplacement
=
true
hasReplacement
=
true
}
}
if
!
hasReplacement
{
if
!
hasReplacement
{
if
keepOnlyDeclared
{
continue
}
resultTokens
=
append
(
resultTokens
,
token
)
resultTokens
=
append
(
resultTokens
,
token
)
continue
continue
}
}
...
@@ -875,6 +881,7 @@ func resolveHeaderOverrideValueByMapping(context map[string]interface{}, headerN
...
@@ -875,6 +881,7 @@ func resolveHeaderOverrideValueByMapping(context map[string]interface{}, headerN
resultTokens
=
append
(
resultTokens
,
replacementTokens
...
)
resultTokens
=
append
(
resultTokens
,
replacementTokens
...
)
}
}
resultTokens
=
append
(
resultTokens
,
appendTokens
...
)
resultTokens
=
lo
.
Uniq
(
resultTokens
)
resultTokens
=
lo
.
Uniq
(
resultTokens
)
if
len
(
resultTokens
)
==
0
{
if
len
(
resultTokens
)
==
0
{
return
""
,
false
,
nil
return
""
,
false
,
nil
...
@@ -882,6 +889,26 @@ func resolveHeaderOverrideValueByMapping(context map[string]interface{}, headerN
...
@@ -882,6 +889,26 @@ func resolveHeaderOverrideValueByMapping(context map[string]interface{}, headerN
return
strings
.
Join
(
resultTokens
,
","
),
true
,
nil
return
strings
.
Join
(
resultTokens
,
","
),
true
,
nil
}
}
func
parseHeaderAppendTokens
(
mapping
map
[
string
]
interface
{})
([]
string
,
error
)
{
appendRaw
,
ok
:=
mapping
[
"$append"
]
if
!
ok
{
return
nil
,
nil
}
return
parseHeaderReplacementTokens
(
appendRaw
)
}
func
parseHeaderKeepOnlyDeclared
(
mapping
map
[
string
]
interface
{})
bool
{
keepOnlyDeclaredRaw
,
ok
:=
mapping
[
"$keep_only_declared"
]
if
!
ok
{
return
false
}
keepOnlyDeclared
,
ok
:=
keepOnlyDeclaredRaw
.
(
bool
)
if
!
ok
{
return
false
}
return
keepOnlyDeclared
}
func
parseHeaderReplacementTokens
(
value
interface
{})
([]
string
,
error
)
{
func
parseHeaderReplacementTokens
(
value
interface
{})
([]
string
,
error
)
{
switch
raw
:=
value
.
(
type
)
{
switch
raw
:=
value
.
(
type
)
{
case
nil
:
case
nil
:
...
...
relay/common/override_test.go
View file @
d087cc50
...
@@ -1653,6 +1653,141 @@ func TestApplyParamOverrideSetHeaderMapDeleteWholeHeaderWhenAllTokensCleared(t *
...
@@ -1653,6 +1653,141 @@ func TestApplyParamOverrideSetHeaderMapDeleteWholeHeaderWhenAllTokensCleared(t *
}
}
}
}
func
TestApplyParamOverrideSetHeaderMapAppendsTokens
(
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
{}{
"$append"
:
[]
interface
{}{
"context-1m-2025-08-07"
,
"computer-use-2025-01-24"
},
},
},
},
}
ctx
:=
map
[
string
]
interface
{}{
"header_override"
:
map
[
string
]
interface
{}{
"anthropic-beta"
:
"computer-use-2025-01-24"
,
},
}
out
,
err
:=
ApplyParamOverride
(
input
,
override
,
ctx
)
if
err
!=
nil
{
t
.
Fatalf
(
"ApplyParamOverride returned error: %v"
,
err
)
}
assertJSONEqual
(
t
,
`{"temperature":0.7}`
,
string
(
out
))
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,context-1m-2025-08-07"
{
t
.
Fatalf
(
"expected anthropic-beta to append new token without duplicates, got: %v"
,
headers
[
"anthropic-beta"
])
}
}
func
TestApplyParamOverrideSetHeaderMapAppendsTokensWhenHeaderMissing
(
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
{}{
"$append"
:
[]
interface
{}{
"context-1m-2025-08-07"
,
"computer-use-2025-01-24"
},
},
},
},
}
ctx
:=
map
[
string
]
interface
{}{}
out
,
err
:=
ApplyParamOverride
(
input
,
override
,
ctx
)
if
err
!=
nil
{
t
.
Fatalf
(
"ApplyParamOverride returned error: %v"
,
err
)
}
assertJSONEqual
(
t
,
`{"temperature":0.7}`
,
string
(
out
))
headers
,
ok
:=
ctx
[
"header_override"
]
.
(
map
[
string
]
interface
{})
if
!
ok
{
t
.
Fatalf
(
"expected header_override context map"
)
}
if
headers
[
"anthropic-beta"
]
!=
"context-1m-2025-08-07,computer-use-2025-01-24"
{
t
.
Fatalf
(
"expected anthropic-beta to be created from appended tokens, got: %v"
,
headers
[
"anthropic-beta"
])
}
}
func
TestApplyParamOverrideSetHeaderMapKeepOnlyDeclaredDropsUndeclaredTokens
(
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
{}{
"computer-use-2025-01-24"
:
"computer-use-2025-01-24"
,
"$append"
:
[]
interface
{}{
"context-1m-2025-08-07"
},
"$keep_only_declared"
:
true
,
},
},
},
}
ctx
:=
map
[
string
]
interface
{}{
"header_override"
:
map
[
string
]
interface
{}{
"anthropic-beta"
:
"advanced-tool-use-2025-11-20,computer-use-2025-01-24"
,
},
}
out
,
err
:=
ApplyParamOverride
(
input
,
override
,
ctx
)
if
err
!=
nil
{
t
.
Fatalf
(
"ApplyParamOverride returned error: %v"
,
err
)
}
assertJSONEqual
(
t
,
`{"temperature":0.7}`
,
string
(
out
))
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,context-1m-2025-08-07"
{
t
.
Fatalf
(
"expected anthropic-beta to keep only declared tokens, got: %v"
,
headers
[
"anthropic-beta"
])
}
}
func
TestApplyParamOverrideSetHeaderMapKeepOnlyDeclaredDeletesHeaderWhenNothingDeclaredMatches
(
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
{}{
"computer-use-2025-01-24"
:
"computer-use-2025-01-24"
,
"$keep_only_declared"
:
true
,
},
},
},
}
ctx
:=
map
[
string
]
interface
{}{
"header_override"
:
map
[
string
]
interface
{}{
"anthropic-beta"
:
"advanced-tool-use-2025-11-20"
,
},
}
out
,
err
:=
ApplyParamOverride
(
input
,
override
,
ctx
)
if
err
!=
nil
{
t
.
Fatalf
(
"ApplyParamOverride returned error: %v"
,
err
)
}
assertJSONEqual
(
t
,
`{"temperature":0.7}`
,
string
(
out
))
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 no declared tokens remain, got: %v"
,
headers
[
"anthropic-beta"
])
}
}
func
TestApplyParamOverrideConditionsObjectShorthand
(
t
*
testing
.
T
)
{
func
TestApplyParamOverrideConditionsObjectShorthand
(
t
*
testing
.
T
)
{
input
:=
[]
byte
(
`{"temperature":0.7}`
)
input
:=
[]
byte
(
`{"temperature":0.7}`
)
override
:=
map
[
string
]
interface
{}{
override
:=
map
[
string
]
interface
{}{
...
...
web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx
View file @
d087cc50
...
@@ -163,7 +163,7 @@ const MODE_DESCRIPTIONS = {
...
@@ -163,7 +163,7 @@ const MODE_DESCRIPTIONS = {
prune_objects
:
'按条件清理对象中的子项'
,
prune_objects
:
'按条件清理对象中的子项'
,
pass_headers
:
'把指定请求头透传到上游请求'
,
pass_headers
:
'把指定请求头透传到上游请求'
,
sync_fields
:
'在一个字段有值、另一个缺失时自动补齐'
,
sync_fields
:
'在一个字段有值、另一个缺失时自动补齐'
,
set_header
:
'设置运行期请求头(支持整值覆盖,或用 JSON 映射按逗号 token 替换/删除)'
,
set_header
:
'设置运行期请求头(支持整值覆盖,或用 JSON 映射按逗号 token 替换/删除
/追加/白名单保留
)'
,
delete_header
:
'删除运行期请求头'
,
delete_header
:
'删除运行期请求头'
,
copy_header
:
'复制请求头'
,
copy_header
:
'复制请求头'
,
move_header
:
'移动请求头'
,
move_header
:
'移动请求头'
,
...
@@ -241,6 +241,12 @@ const getModeValuePlaceholder = (mode) => {
...
@@ -241,6 +241,12 @@ const getModeValuePlaceholder = (mode) => {
''
,
''
,
'JSON map wildcard:'
,
'JSON map wildcard:'
,
'{"*": null, "computer-use-2025-11-24": "computer-use-2025-11-24"}'
,
'{"*": null, "computer-use-2025-11-24": "computer-use-2025-11-24"}'
,
''
,
'JSON append example:'
,
'{"$append": ["context-1m-2025-08-07"]}'
,
''
,
'JSON strict keep example:'
,
'{"computer-use-2025-01-24": "computer-use-2025-01-24", "$append": ["context-1m-2025-08-07"], "$keep_only_declared": true}'
,
].
join
(
'\n'
);
].
join
(
'\n'
);
}
}
if
(
mode
===
'pass_headers'
)
return
'Authorization, X-Request-Id'
;
if
(
mode
===
'pass_headers'
)
return
'Authorization, X-Request-Id'
;
...
@@ -260,7 +266,7 @@ const getModeValuePlaceholder = (mode) => {
...
@@ -260,7 +266,7 @@ const getModeValuePlaceholder = (mode) => {
const
getModeValueHelp
=
(
mode
)
=>
{
const
getModeValueHelp
=
(
mode
)
=>
{
if
(
mode
!==
'set_header'
)
return
''
;
if
(
mode
!==
'set_header'
)
return
''
;
return
'字符串:整条请求头直接覆盖。JSON 映射:按逗号分隔 token 逐项处理,null 表示删除,string/array 表示替换,* 表示兜底规则。'
;
return
'字符串:整条请求头直接覆盖。JSON 映射:按逗号分隔 token 逐项处理,null 表示删除,string/array 表示替换,* 表示兜底规则
,$append 可在末尾追加新 token,$keep_only_declared=true 时会丢弃未声明 token
。'
;
};
};
const
SYNC_TARGET_TYPE_OPTIONS
=
[
const
SYNC_TARGET_TYPE_OPTIONS
=
[
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment