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
ad326beb
authored
Mar 02, 2026
by
Seefs
Committed by
GitHub
Mar 02, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Merge pull request #3066 from seefs001/fix/aws-header-override
Fix/aws header override
parent
4b61c54c
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
288 additions
and
11 deletions
+288
-11
dto/claude.go
+1
-1
go.mod
+7
-7
go.sum
+16
-0
relay/channel/api_request.go
+4
-0
relay/channel/aws/dto.go
+1
-0
relay/channel/aws/relay-aws.go
+8
-0
relay/channel/aws/relay_aws_test.go
+55
-0
relay/channel/vertex/dto.go
+1
-0
relay/common/override.go
+25
-1
relay/common/override_test.go
+80
-0
relay/helper/valid_request.go
+1
-1
service/channel_affinity.go
+35
-0
service/channel_affinity_template_test.go
+43
-1
web/src/components/table/channels/modals/EditChannelModal.jsx
+11
-0
No files found.
dto/claude.go
View file @
ad326beb
...
@@ -434,7 +434,7 @@ func ProcessTools(tools []any) ([]*Tool, []*ClaudeWebSearchTool) {
...
@@ -434,7 +434,7 @@ func ProcessTools(tools []any) ([]*Tool, []*ClaudeWebSearchTool) {
}
}
type
Thinking
struct
{
type
Thinking
struct
{
Type
string
`json:"type"`
Type
string
`json:"type
,omitempty
"`
BudgetTokens
*
int
`json:"budget_tokens,omitempty"`
BudgetTokens
*
int
`json:"budget_tokens,omitempty"`
}
}
...
...
go.mod
View file @
ad326beb
...
@@ -8,10 +8,10 @@ require (
...
@@ -8,10 +8,10 @@ require (
github.com/abema/go-mp4 v1.4.1
github.com/abema/go-mp4 v1.4.1
github.com/andybalholm/brotli v1.1.1
github.com/andybalholm/brotli v1.1.1
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0
github.com/aws/aws-sdk-go-v2 v1.
37
.2
github.com/aws/aws-sdk-go-v2 v1.
41
.2
github.com/aws/aws-sdk-go-v2/credentials v1.1
7.11
github.com/aws/aws-sdk-go-v2/credentials v1.1
9.10
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.
33
.0
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.
50
.0
github.com/aws/smithy-go v1.2
2.5
github.com/aws/smithy-go v1.2
4.2
github.com/bytedance/gopkg v0.1.3
github.com/bytedance/gopkg v0.1.3
github.com/gin-contrib/cors v1.7.2
github.com/gin-contrib/cors v1.7.2
github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/gzip v0.0.6
...
@@ -62,9 +62,9 @@ require (
...
@@ -62,9 +62,9 @@ require (
require (
require (
github.com/DmitriyVTitov/size v1.5.0 // indirect
github.com/DmitriyVTitov/size v1.5.0 // indirect
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 // indirect
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.
0
// indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.
5
// indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.
2
// indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.
18
// indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.
2
// indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.
18
// indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/sonic v1.14.1 // indirect
github.com/bytedance/sonic v1.14.1 // indirect
...
...
go.sum
View file @
ad326beb
...
@@ -12,18 +12,34 @@ github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 h1:HblK3eJHq54yET63q
...
@@ -12,18 +12,34 @@ github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 h1:HblK3eJHq54yET63q
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6/go.mod h1:pbiaLIeYLUbgMY1kwEAdwO6UKD5ZNwdPGQlwokS9fe8=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6/go.mod h1:pbiaLIeYLUbgMY1kwEAdwO6UKD5ZNwdPGQlwokS9fe8=
github.com/aws/aws-sdk-go-v2 v1.37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo=
github.com/aws/aws-sdk-go-v2 v1.37.2 h1:xkW1iMYawzcmYFYEV0UCMxc8gSsjCGEhBXQkdQywVbo=
github.com/aws/aws-sdk-go-v2 v1.37.2/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2 v1.37.2/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11 h1:YuIB1dJNf1Re822rriUOTxopaHHvIq0l/pX3fwO+Tzs=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
github.com/aws/aws-sdk-go-v2/credentials v1.17.11/go.mod h1:AQtFPsDH9bI2O+71anW6EKL+NcD7LG3dpKGMV4SShgo=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 h1:sPiRHLVUIIQcoVZTNwqQcdtjkqkPopyYmIX0M5ElRf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2 h1:sPiRHLVUIIQcoVZTNwqQcdtjkqkPopyYmIX0M5ElRf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2/go.mod h1:ik86P3sgV+Bk7c1tBFCwI3VxMoSEwl4YkRB9xn1s340=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.2/go.mod h1:ik86P3sgV+Bk7c1tBFCwI3VxMoSEwl4YkRB9xn1s340=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 h1:ZdzDAg075H6stMZtbD2o+PyB933M/f20e9WmCBC17wA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2 h1:ZdzDAg075H6stMZtbD2o+PyB933M/f20e9WmCBC17wA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2/go.mod h1:eE1IIzXG9sdZCB0pNNpMpsYTLl4YdOQD3njiVN1e/E4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.2/go.mod h1:eE1IIzXG9sdZCB0pNNpMpsYTLl4YdOQD3njiVN1e/E4=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.33.0 h1:JzidOz4Hcn2RbP5fvIS1iAP+DcRv5VJtgixbEYDsI5g=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.33.0 h1:JzidOz4Hcn2RbP5fvIS1iAP+DcRv5VJtgixbEYDsI5g=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.33.0/go.mod h1:9A4/PJYlWjvjEzzoOLGQjkLt4bYK9fRWi7uz1GSsAcA=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.33.0/go.mod h1:9A4/PJYlWjvjEzzoOLGQjkLt4bYK9fRWi7uz1GSsAcA=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.0 h1:TDKR8ACRw7G+GFaQlhoy6biu+8q6ZtSddQCy9avMdMI=
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.50.0/go.mod h1:XlhOh5Ax/lesqN4aZCUgj9vVJed5VoXYHHFYGAlJEwU=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
...
...
relay/channel/api_request.go
View file @
ad326beb
...
@@ -267,6 +267,10 @@ func processHeaderOverride(info *common.RelayInfo, c *gin.Context) (map[string]s
...
@@ -267,6 +267,10 @@ func processHeaderOverride(info *common.RelayInfo, c *gin.Context) (map[string]s
return
headerOverride
,
nil
return
headerOverride
,
nil
}
}
func
ResolveHeaderOverride
(
info
*
common
.
RelayInfo
,
c
*
gin
.
Context
)
(
map
[
string
]
string
,
error
)
{
return
processHeaderOverride
(
info
,
c
)
}
func
applyHeaderOverrideToRequest
(
req
*
http
.
Request
,
headerOverride
map
[
string
]
string
)
{
func
applyHeaderOverrideToRequest
(
req
*
http
.
Request
,
headerOverride
map
[
string
]
string
)
{
if
req
==
nil
{
if
req
==
nil
{
return
return
...
...
relay/channel/aws/dto.go
View file @
ad326beb
...
@@ -27,6 +27,7 @@ type AwsClaudeRequest struct {
...
@@ -27,6 +27,7 @@ type AwsClaudeRequest struct {
ToolChoice
any
`json:"tool_choice,omitempty"`
ToolChoice
any
`json:"tool_choice,omitempty"`
Thinking
*
dto
.
Thinking
`json:"thinking,omitempty"`
Thinking
*
dto
.
Thinking
`json:"thinking,omitempty"`
OutputConfig
json
.
RawMessage
`json:"output_config,omitempty"`
OutputConfig
json
.
RawMessage
`json:"output_config,omitempty"`
//Metadata json.RawMessage `json:"metadata,omitempty"`
}
}
func
formatRequest
(
requestBody
io
.
Reader
,
requestHeader
http
.
Header
)
(
*
AwsClaudeRequest
,
error
)
{
func
formatRequest
(
requestBody
io
.
Reader
,
requestHeader
http
.
Header
)
(
*
AwsClaudeRequest
,
error
)
{
...
...
relay/channel/aws/relay-aws.go
View file @
ad326beb
...
@@ -11,6 +11,7 @@ import (
...
@@ -11,6 +11,7 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/relay/channel"
"github.com/QuantumNous/new-api/relay/channel/claude"
"github.com/QuantumNous/new-api/relay/channel/claude"
relaycommon
"github.com/QuantumNous/new-api/relay/common"
relaycommon
"github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relay/helper"
"github.com/QuantumNous/new-api/relay/helper"
...
@@ -106,6 +107,13 @@ func doAwsClientRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor,
...
@@ -106,6 +107,13 @@ func doAwsClientRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor,
// init empty request.header
// init empty request.header
requestHeader
:=
http
.
Header
{}
requestHeader
:=
http
.
Header
{}
a
.
SetupRequestHeader
(
c
,
&
requestHeader
,
info
)
a
.
SetupRequestHeader
(
c
,
&
requestHeader
,
info
)
headerOverride
,
err
:=
channel
.
ResolveHeaderOverride
(
info
,
c
)
if
err
!=
nil
{
return
nil
,
err
}
for
key
,
value
:=
range
headerOverride
{
requestHeader
.
Set
(
key
,
value
)
}
if
isNovaModel
(
awsModelId
)
{
if
isNovaModel
(
awsModelId
)
{
var
novaReq
*
NovaRequest
var
novaReq
*
NovaRequest
...
...
relay/channel/aws/relay_aws_test.go
0 → 100644
View file @
ad326beb
package
aws
import
(
"bytes"
"net/http"
"net/http/httptest"
"testing"
"github.com/QuantumNous/new-api/common"
relaycommon
"github.com/QuantumNous/new-api/relay/common"
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
func
TestDoAwsClientRequest_AppliesRuntimeHeaderOverrideToAnthropicBeta
(
t
*
testing
.
T
)
{
t
.
Parallel
()
gin
.
SetMode
(
gin
.
TestMode
)
recorder
:=
httptest
.
NewRecorder
()
ctx
,
_
:=
gin
.
CreateTestContext
(
recorder
)
ctx
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/v1/messages"
,
nil
)
info
:=
&
relaycommon
.
RelayInfo
{
OriginModelName
:
"claude-3-5-sonnet-20240620"
,
IsStream
:
false
,
UseRuntimeHeadersOverride
:
true
,
RuntimeHeadersOverride
:
map
[
string
]
any
{
"anthropic-beta"
:
"computer-use-2025-01-24"
,
},
ChannelMeta
:
&
relaycommon
.
ChannelMeta
{
ApiKey
:
"access-key|secret-key|us-east-1"
,
UpstreamModelName
:
"claude-3-5-sonnet-20240620"
,
},
}
requestBody
:=
bytes
.
NewBufferString
(
`{"messages":[{"role":"user","content":"hello"}],"max_tokens":128}`
)
adaptor
:=
&
Adaptor
{}
_
,
err
:=
doAwsClientRequest
(
ctx
,
info
,
adaptor
,
requestBody
)
require
.
NoError
(
t
,
err
)
awsReq
,
ok
:=
adaptor
.
AwsReq
.
(
*
bedrockruntime
.
InvokeModelInput
)
require
.
True
(
t
,
ok
)
var
payload
map
[
string
]
any
require
.
NoError
(
t
,
common
.
Unmarshal
(
awsReq
.
Body
,
&
payload
))
anthropicBeta
,
exists
:=
payload
[
"anthropic_beta"
]
require
.
True
(
t
,
exists
)
values
,
ok
:=
anthropicBeta
.
([]
any
)
require
.
True
(
t
,
ok
)
require
.
Equal
(
t
,
[]
any
{
"computer-use-2025-01-24"
},
values
)
}
relay/channel/vertex/dto.go
View file @
ad326beb
...
@@ -20,6 +20,7 @@ type VertexAIClaudeRequest struct {
...
@@ -20,6 +20,7 @@ type VertexAIClaudeRequest struct {
ToolChoice
any
`json:"tool_choice,omitempty"`
ToolChoice
any
`json:"tool_choice,omitempty"`
Thinking
*
dto
.
Thinking
`json:"thinking,omitempty"`
Thinking
*
dto
.
Thinking
`json:"thinking,omitempty"`
OutputConfig
json
.
RawMessage
`json:"output_config,omitempty"`
OutputConfig
json
.
RawMessage
`json:"output_config,omitempty"`
//Metadata json.RawMessage `json:"metadata,omitempty"`
}
}
func
copyRequest
(
req
*
dto
.
ClaudeRequest
,
version
string
)
*
VertexAIClaudeRequest
{
func
copyRequest
(
req
*
dto
.
ClaudeRequest
,
version
string
)
*
VertexAIClaudeRequest
{
...
...
relay/common/override.go
View file @
ad326beb
...
@@ -120,8 +120,18 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
...
@@ -120,8 +120,18 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
// 尝试断言为操作格式
// 尝试断言为操作格式
if
operations
,
ok
:=
tryParseOperations
(
paramOverride
);
ok
{
if
operations
,
ok
:=
tryParseOperations
(
paramOverride
);
ok
{
legacyOverride
:=
buildLegacyParamOverride
(
paramOverride
)
workingJSON
:=
jsonData
var
err
error
if
len
(
legacyOverride
)
>
0
{
workingJSON
,
err
=
applyOperationsLegacy
(
workingJSON
,
legacyOverride
)
if
err
!=
nil
{
return
nil
,
err
}
}
// 使用新方法
// 使用新方法
result
,
err
:=
applyOperations
(
string
(
jsonData
),
operations
,
conditionContext
)
result
,
err
:=
applyOperations
(
string
(
workingJSON
),
operations
,
conditionContext
)
return
[]
byte
(
result
),
err
return
[]
byte
(
result
),
err
}
}
...
@@ -129,6 +139,20 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
...
@@ -129,6 +139,20 @@ func ApplyParamOverride(jsonData []byte, paramOverride map[string]interface{}, c
return
applyOperationsLegacy
(
jsonData
,
paramOverride
)
return
applyOperationsLegacy
(
jsonData
,
paramOverride
)
}
}
func
buildLegacyParamOverride
(
paramOverride
map
[
string
]
interface
{})
map
[
string
]
interface
{}
{
if
len
(
paramOverride
)
==
0
{
return
nil
}
legacy
:=
make
(
map
[
string
]
interface
{},
len
(
paramOverride
))
for
key
,
value
:=
range
paramOverride
{
if
strings
.
EqualFold
(
strings
.
TrimSpace
(
key
),
"operations"
)
{
continue
}
legacy
[
key
]
=
value
}
return
legacy
}
func
ApplyParamOverrideWithRelayInfo
(
jsonData
[]
byte
,
info
*
RelayInfo
)
([]
byte
,
error
)
{
func
ApplyParamOverrideWithRelayInfo
(
jsonData
[]
byte
,
info
*
RelayInfo
)
([]
byte
,
error
)
{
paramOverride
:=
getParamOverrideMap
(
info
)
paramOverride
:=
getParamOverrideMap
(
info
)
if
len
(
paramOverride
)
==
0
{
if
len
(
paramOverride
)
==
0
{
...
...
relay/common/override_test.go
View file @
ad326beb
...
@@ -74,6 +74,48 @@ func TestApplyParamOverrideTrimNoop(t *testing.T) {
...
@@ -74,6 +74,48 @@ func TestApplyParamOverrideTrimNoop(t *testing.T) {
assertJSONEqual
(
t
,
`{"model":"gpt-4","temperature":0.7}`
,
string
(
out
))
assertJSONEqual
(
t
,
`{"model":"gpt-4","temperature":0.7}`
,
string
(
out
))
}
}
func
TestApplyParamOverrideMixedLegacyAndOperations
(
t
*
testing
.
T
)
{
input
:=
[]
byte
(
`{"model":"openai/gpt-4","temperature":0.7}`
)
override
:=
map
[
string
]
interface
{}{
"temperature"
:
0.2
,
"top_p"
:
0.95
,
"operations"
:
[]
interface
{}{
map
[
string
]
interface
{}{
"path"
:
"model"
,
"mode"
:
"trim_prefix"
,
"value"
:
"openai/"
,
},
},
}
out
,
err
:=
ApplyParamOverride
(
input
,
override
,
nil
)
if
err
!=
nil
{
t
.
Fatalf
(
"ApplyParamOverride returned error: %v"
,
err
)
}
assertJSONEqual
(
t
,
`{"model":"gpt-4","temperature":0.2,"top_p":0.95}`
,
string
(
out
))
}
func
TestApplyParamOverrideMixedLegacyAndOperationsConflictPrefersOperations
(
t
*
testing
.
T
)
{
input
:=
[]
byte
(
`{"model":"openai/gpt-4","temperature":0.7}`
)
override
:=
map
[
string
]
interface
{}{
"model"
:
"legacy-model"
,
"temperature"
:
0.2
,
"operations"
:
[]
interface
{}{
map
[
string
]
interface
{}{
"path"
:
"model"
,
"mode"
:
"set"
,
"value"
:
"op-model"
,
},
},
}
out
,
err
:=
ApplyParamOverride
(
input
,
override
,
nil
)
if
err
!=
nil
{
t
.
Fatalf
(
"ApplyParamOverride returned error: %v"
,
err
)
}
assertJSONEqual
(
t
,
`{"model":"op-model","temperature":0.2}`
,
string
(
out
))
}
func
TestApplyParamOverrideTrimRequiresValue
(
t
*
testing
.
T
)
{
func
TestApplyParamOverrideTrimRequiresValue
(
t
*
testing
.
T
)
{
// trim_prefix requires value example:
// trim_prefix requires value example:
// {"operations":[{"path":"model","mode":"trim_prefix"}]}
// {"operations":[{"path":"model","mode":"trim_prefix"}]}
...
@@ -1429,6 +1471,44 @@ func TestApplyParamOverrideWithRelayInfoSyncRuntimeHeaders(t *testing.T) {
...
@@ -1429,6 +1471,44 @@ func TestApplyParamOverrideWithRelayInfoSyncRuntimeHeaders(t *testing.T) {
}
}
}
}
func
TestApplyParamOverrideWithRelayInfoMixedLegacyAndOperations
(
t
*
testing
.
T
)
{
info
:=
&
RelayInfo
{
RequestHeaders
:
map
[
string
]
string
{
"Originator"
:
"Codex CLI"
,
},
ChannelMeta
:
&
ChannelMeta
{
ParamOverride
:
map
[
string
]
interface
{}{
"temperature"
:
0.2
,
"operations"
:
[]
interface
{}{
map
[
string
]
interface
{}{
"mode"
:
"pass_headers"
,
"value"
:
[]
interface
{}{
"Originator"
},
},
},
},
HeadersOverride
:
map
[
string
]
interface
{}{
"X-Static"
:
"legacy-static"
,
},
},
}
out
,
err
:=
ApplyParamOverrideWithRelayInfo
([]
byte
(
`{"model":"gpt-5","temperature":0.7}`
),
info
)
if
err
!=
nil
{
t
.
Fatalf
(
"ApplyParamOverrideWithRelayInfo returned error: %v"
,
err
)
}
assertJSONEqual
(
t
,
`{"model":"gpt-5","temperature":0.2}`
,
string
(
out
))
if
!
info
.
UseRuntimeHeadersOverride
{
t
.
Fatalf
(
"expected runtime header override to be enabled"
)
}
if
info
.
RuntimeHeadersOverride
[
"x-static"
]
!=
"legacy-static"
{
t
.
Fatalf
(
"expected x-static to be preserved, got: %v"
,
info
.
RuntimeHeadersOverride
[
"x-static"
])
}
if
info
.
RuntimeHeadersOverride
[
"originator"
]
!=
"Codex CLI"
{
t
.
Fatalf
(
"expected originator header to be passed, got: %v"
,
info
.
RuntimeHeadersOverride
[
"originator"
])
}
}
func
TestApplyParamOverrideWithRelayInfoMoveAndCopyHeaders
(
t
*
testing
.
T
)
{
func
TestApplyParamOverrideWithRelayInfoMoveAndCopyHeaders
(
t
*
testing
.
T
)
{
info
:=
&
RelayInfo
{
info
:=
&
RelayInfo
{
ChannelMeta
:
&
ChannelMeta
{
ChannelMeta
:
&
ChannelMeta
{
...
...
relay/helper/valid_request.go
View file @
ad326beb
...
@@ -229,7 +229,7 @@ func GetAndValidOpenAIImageRequest(c *gin.Context, relayMode int) (*dto.ImageReq
...
@@ -229,7 +229,7 @@ func GetAndValidOpenAIImageRequest(c *gin.Context, relayMode int) (*dto.ImageReq
func
GetAndValidateClaudeRequest
(
c
*
gin
.
Context
)
(
textRequest
*
dto
.
ClaudeRequest
,
err
error
)
{
func
GetAndValidateClaudeRequest
(
c
*
gin
.
Context
)
(
textRequest
*
dto
.
ClaudeRequest
,
err
error
)
{
textRequest
=
&
dto
.
ClaudeRequest
{}
textRequest
=
&
dto
.
ClaudeRequest
{}
err
=
c
.
ShouldBindJSON
(
textRequest
)
err
=
c
ommon
.
UnmarshalBodyReusable
(
c
,
textRequest
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
...
service/channel_affinity.go
View file @
ad326beb
...
@@ -436,11 +436,46 @@ func mergeChannelOverride(base map[string]interface{}, tpl map[string]interface{
...
@@ -436,11 +436,46 @@ func mergeChannelOverride(base map[string]interface{}, tpl map[string]interface{
}
}
out
:=
cloneStringAnyMap
(
base
)
out
:=
cloneStringAnyMap
(
base
)
for
k
,
v
:=
range
tpl
{
for
k
,
v
:=
range
tpl
{
if
strings
.
EqualFold
(
strings
.
TrimSpace
(
k
),
"operations"
)
{
baseOps
,
hasBaseOps
:=
extractParamOperations
(
out
[
k
])
tplOps
,
hasTplOps
:=
extractParamOperations
(
v
)
if
hasTplOps
{
if
hasBaseOps
{
out
[
k
]
=
append
(
tplOps
,
baseOps
...
)
}
else
{
out
[
k
]
=
tplOps
}
continue
}
}
if
_
,
exists
:=
out
[
k
];
exists
{
continue
}
out
[
k
]
=
v
out
[
k
]
=
v
}
}
return
out
return
out
}
}
func
extractParamOperations
(
value
interface
{})
([]
interface
{},
bool
)
{
switch
ops
:=
value
.
(
type
)
{
case
[]
interface
{}
:
if
len
(
ops
)
==
0
{
return
[]
interface
{}{},
true
}
cloned
:=
make
([]
interface
{},
0
,
len
(
ops
))
cloned
=
append
(
cloned
,
ops
...
)
return
cloned
,
true
case
[]
map
[
string
]
interface
{}
:
cloned
:=
make
([]
interface
{},
0
,
len
(
ops
))
for
_
,
op
:=
range
ops
{
cloned
=
append
(
cloned
,
op
)
}
return
cloned
,
true
default
:
return
nil
,
false
}
}
func
appendChannelAffinityTemplateAdminInfo
(
c
*
gin
.
Context
,
meta
channelAffinityMeta
)
{
func
appendChannelAffinityTemplateAdminInfo
(
c
*
gin
.
Context
,
meta
channelAffinityMeta
)
{
if
c
==
nil
{
if
c
==
nil
{
return
return
...
...
service/channel_affinity_template_test.go
View file @
ad326beb
...
@@ -56,7 +56,7 @@ func TestApplyChannelAffinityOverrideTemplate_MergeTemplate(t *testing.T) {
...
@@ -56,7 +56,7 @@ func TestApplyChannelAffinityOverrideTemplate_MergeTemplate(t *testing.T) {
merged
,
applied
:=
ApplyChannelAffinityOverrideTemplate
(
ctx
,
base
)
merged
,
applied
:=
ApplyChannelAffinityOverrideTemplate
(
ctx
,
base
)
require
.
True
(
t
,
applied
)
require
.
True
(
t
,
applied
)
require
.
Equal
(
t
,
0.
2
,
merged
[
"temperature"
])
require
.
Equal
(
t
,
0.
7
,
merged
[
"temperature"
])
require
.
Equal
(
t
,
0.95
,
merged
[
"top_p"
])
require
.
Equal
(
t
,
0.95
,
merged
[
"top_p"
])
require
.
Equal
(
t
,
2000
,
merged
[
"max_tokens"
])
require
.
Equal
(
t
,
2000
,
merged
[
"max_tokens"
])
require
.
Equal
(
t
,
0.7
,
base
[
"temperature"
])
require
.
Equal
(
t
,
0.7
,
base
[
"temperature"
])
...
@@ -74,6 +74,48 @@ func TestApplyChannelAffinityOverrideTemplate_MergeTemplate(t *testing.T) {
...
@@ -74,6 +74,48 @@ func TestApplyChannelAffinityOverrideTemplate_MergeTemplate(t *testing.T) {
require
.
EqualValues
(
t
,
2
,
overrideInfo
[
"param_override_keys"
])
require
.
EqualValues
(
t
,
2
,
overrideInfo
[
"param_override_keys"
])
}
}
func
TestApplyChannelAffinityOverrideTemplate_MergeOperations
(
t
*
testing
.
T
)
{
ctx
:=
buildChannelAffinityTemplateContextForTest
(
channelAffinityMeta
{
RuleName
:
"rule-with-ops-template"
,
ParamTemplate
:
map
[
string
]
interface
{}{
"operations"
:
[]
map
[
string
]
interface
{}{
{
"mode"
:
"pass_headers"
,
"value"
:
[]
string
{
"Originator"
},
},
},
},
})
base
:=
map
[
string
]
interface
{}{
"temperature"
:
0.7
,
"operations"
:
[]
map
[
string
]
interface
{}{
{
"path"
:
"model"
,
"mode"
:
"trim_prefix"
,
"value"
:
"openai/"
,
},
},
}
merged
,
applied
:=
ApplyChannelAffinityOverrideTemplate
(
ctx
,
base
)
require
.
True
(
t
,
applied
)
require
.
Equal
(
t
,
0.7
,
merged
[
"temperature"
])
opsAny
,
ok
:=
merged
[
"operations"
]
require
.
True
(
t
,
ok
)
ops
,
ok
:=
opsAny
.
([]
interface
{})
require
.
True
(
t
,
ok
)
require
.
Len
(
t
,
ops
,
2
)
firstOp
,
ok
:=
ops
[
0
]
.
(
map
[
string
]
interface
{})
require
.
True
(
t
,
ok
)
require
.
Equal
(
t
,
"pass_headers"
,
firstOp
[
"mode"
])
secondOp
,
ok
:=
ops
[
1
]
.
(
map
[
string
]
interface
{})
require
.
True
(
t
,
ok
)
require
.
Equal
(
t
,
"trim_prefix"
,
secondOp
[
"mode"
])
}
func
TestChannelAffinityHitCodexTemplatePassHeadersEffective
(
t
*
testing
.
T
)
{
func
TestChannelAffinityHitCodexTemplatePassHeadersEffective
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
gin
.
SetMode
(
gin
.
TestMode
)
...
...
web/src/components/table/channels/modals/EditChannelModal.jsx
View file @
ad326beb
...
@@ -759,6 +759,10 @@ const EditChannelModal = (props) => {
...
@@ -759,6 +759,10 @@ const EditChannelModal = (props) => {
}
}
};
};
const
clearParamOverride
=
()
=>
{
handleInputChange
(
'param_override'
,
''
);
};
const
loadChannel
=
async
()
=>
{
const
loadChannel
=
async
()
=>
{
setLoading
(
true
);
setLoading
(
true
);
let
res
=
await
API
.
get
(
`/api/channel/
${
channelId
}
`
);
let
res
=
await
API
.
get
(
`/api/channel/
${
channelId
}
`
);
...
@@ -3356,6 +3360,13 @@ const EditChannelModal = (props) => {
...
@@ -3356,6 +3360,13 @@ const EditChannelModal = (props) => {
>
>
{
t
(
'填充旧模板'
)
}
{
t
(
'填充旧模板'
)
}
</
Button
>
</
Button
>
<
Button
size=
'small'
type=
'tertiary'
onClick=
{
clearParamOverride
}
>
{
t
(
'清空'
)
}
</
Button
>
</
Space
>
</
Space
>
</
div
>
</
div
>
<
Text
type=
'tertiary'
size=
'small'
>
<
Text
type=
'tertiary'
size=
'small'
>
...
...
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