refactor: advanced custom channel route editor (#6865)

* refactor: advanced custom channel route editor

* fix(channels): show raw balance response from balance cell
This commit is contained in:
Seefs
2026-08-18 17:31:21 +08:00
committed by GitHub
parent 3dda1d50c6
commit 2b0efd8484
21 changed files with 1551 additions and 506 deletions
+36 -9
View File
@@ -145,8 +145,12 @@ const (
advancedCustomEndpointPathEmbeddings = "/v1/embeddings"
)
// AdvancedCustomModelListPath identifies the optional OpenAI Models discovery route.
const AdvancedCustomModelListPath = "/v1/models"
const (
// AdvancedCustomModelListPath identifies the optional OpenAI Models discovery route.
AdvancedCustomModelListPath = "/v1/models"
// AdvancedCustomBalancePath identifies the optional balance lookup route used by channel management.
AdvancedCustomBalancePath = "/v1/dashboard/billing/credit_grants"
)
// MatchPath returns the first route whose IncomingPath matches requestPath.
// Matching mirrors the relay adaptor: exact match, {model} placeholder, and
@@ -193,6 +197,19 @@ func (c *AdvancedCustomConfig) ModelListRoute() (AdvancedCustomRoute, bool) {
return AdvancedCustomRoute{}, false
}
// BalanceRoute returns the explicitly configured channel-management balance route.
func (c *AdvancedCustomConfig) BalanceRoute() (AdvancedCustomRoute, bool) {
if c == nil {
return AdvancedCustomRoute{}, false
}
for _, route := range c.Routes {
if strings.TrimSpace(route.IncomingPath) == AdvancedCustomBalancePath {
return route, true
}
}
return AdvancedCustomRoute{}, false
}
// SupportsPath reports whether any route matches requestPath.
func (c *AdvancedCustomConfig) SupportsPath(requestPath string) bool {
_, ok := c.MatchPath(requestPath)
@@ -360,6 +377,7 @@ func (c *AdvancedCustomConfig) Validate() error {
paths := make(map[string]*advancedCustomPathModelState, len(c.Routes))
modelListRouteIndex := -1
balanceRouteIndex := -1
for i := range c.Routes {
route := c.Routes[i]
route.IncomingPath = strings.TrimSpace(route.IncomingPath)
@@ -378,19 +396,28 @@ func (c *AdvancedCustomConfig) Validate() error {
if strings.Contains(route.IncomingPath, "?") {
return fmt.Errorf("advanced_custom.advanced_routes[%d].incoming_path must not include query", i)
}
if route.IncomingPath == AdvancedCustomModelListPath {
if modelListRouteIndex >= 0 {
return fmt.Errorf("advanced_custom.advanced_routes[%d] duplicates the /v1/models route at advanced_routes[%d]", i, modelListRouteIndex)
if route.IncomingPath == AdvancedCustomModelListPath || route.IncomingPath == AdvancedCustomBalancePath {
managementRouteName := route.IncomingPath
previousIndex := modelListRouteIndex
if route.IncomingPath == AdvancedCustomBalancePath {
previousIndex = balanceRouteIndex
}
if previousIndex >= 0 {
return fmt.Errorf("advanced_custom.advanced_routes[%d] duplicates the %s route at advanced_routes[%d]", i, managementRouteName, previousIndex)
}
if route.IncomingPath == AdvancedCustomModelListPath {
modelListRouteIndex = i
} else {
balanceRouteIndex = i
}
modelListRouteIndex = i
if len(normalizeAdvancedCustomRouteModels(route.Models)) > 0 {
return fmt.Errorf("advanced_custom.advanced_routes[%d].models must be empty for /v1/models", i)
return fmt.Errorf("advanced_custom.advanced_routes[%d].models must be empty for %s", i, managementRouteName)
}
if route.Converter != advancedCustomConverterNone {
return fmt.Errorf("advanced_custom.advanced_routes[%d].converter must be none for /v1/models", i)
return fmt.Errorf("advanced_custom.advanced_routes[%d].converter must be none for %s", i, managementRouteName)
}
if strings.Contains(upstreamPath, advancedCustomModelPlaceholder) {
return fmt.Errorf("advanced_custom.advanced_routes[%d].upstream_path must not contain %s for /v1/models", i, advancedCustomModelPlaceholder)
return fmt.Errorf("advanced_custom.advanced_routes[%d].upstream_path must not contain %s for %s", i, advancedCustomModelPlaceholder, managementRouteName)
}
}
if err := validateAdvancedCustomRouteModels(i, route.IncomingPath, route.Models, paths); err != nil {
+64
View File
@@ -147,6 +147,70 @@ func TestAdvancedCustomModelListRouteRequiresExactIncomingPath(t *testing.T) {
assert.Equal(t, "/provider/models", route.UpstreamPath)
}
func TestAdvancedCustomValidateBalanceRouteConstraints(t *testing.T) {
valid := &AdvancedCustomConfig{
Routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/balance",
Converter: advancedCustomConverterNone,
}},
}
require.NoError(t, valid.Validate())
route, ok := valid.BalanceRoute()
require.True(t, ok)
assert.Equal(t, "/provider/balance", route.UpstreamPath)
tests := []struct {
name string
routes []AdvancedCustomRoute
want string
}{
{
name: "model matching rules",
routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/balance",
Models: []string{"gpt-4o"},
}},
want: "models must be empty",
},
{
name: "converter",
routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/balance",
Converter: advancedCustomConverterOpenAIChatToOpenAIResponses,
}},
want: "converter must be none",
},
{
name: "model placeholder",
routes: []AdvancedCustomRoute{{
IncomingPath: AdvancedCustomBalancePath,
UpstreamPath: "/provider/{model}/balance",
}},
want: "upstream_path must not contain {model}",
},
{
name: "duplicate routes",
routes: []AdvancedCustomRoute{
{IncomingPath: AdvancedCustomBalancePath, UpstreamPath: "/provider/balance"},
{IncomingPath: AdvancedCustomBalancePath, UpstreamPath: "/provider/credits"},
},
want: "duplicates the /v1/dashboard/billing/credit_grants route",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := (&AdvancedCustomConfig{Routes: tt.routes}).Validate()
require.Error(t, err)
assert.Contains(t, err.Error(), tt.want)
})
}
}
func TestAdvancedCustomValidateDuplicateIncomingPathWithDisjointModels(t *testing.T) {
config := &AdvancedCustomConfig{
Routes: []AdvancedCustomRoute{