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:
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
Reference in New Issue
Block a user