mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-27 06:49:07 +01:00
[management,proxy] Add agentgateway integration (#7274)
* [management] Add agentgateway provider catalog entry Allow Agent Network providers to target an operator-supplied agentgateway proxy while stamping trusted NetBird identity headers. Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io> * [proxy] Allow trusted Agent Network identity headers Permit only the built-in identity injector to replace the two reserved agentgateway attribution headers while keeping them blocked for every other middleware. Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io> * [management,proxy] Add multi-vendor gateway routing Let one Agent Network route declare multiple parser surfaces while preserving the existing singular vendor wire field. Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io> * [management] Update router test for model policies Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io> * [proxy] Cover reserved header policy Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io> * [management] Add agentgateway model discovery Use agentgateway's OpenAI-compatible models endpoint and omit wildcard patterns until NetBird can authorize and price them consistently. Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io> --------- Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>
This commit is contained in:
@@ -412,6 +412,50 @@ func TestRouter_VendorKeepsOpenAIOffAnthropic(t *testing.T) {
|
||||
assert.Equal(t, "api.openai.com", out.Mutations.RewriteUpstream.Host, "openai vendor must pin to the openai route despite anthropic being declared first")
|
||||
}
|
||||
|
||||
func TestRouter_MultiVendorGatewayAcceptsBothSurfaces(t *testing.T) {
|
||||
gateway := ProviderRoute{
|
||||
ID: "agentgateway",
|
||||
Vendors: []string{"openai", "anthropic"},
|
||||
Models: nil,
|
||||
AllowedGroupIDs: []string{defaultTestGroup},
|
||||
UpstreamScheme: "https",
|
||||
UpstreamHost: "gateway.example.com",
|
||||
}
|
||||
other := ProviderRoute{
|
||||
ID: "other-vendor",
|
||||
Vendor: "mistral",
|
||||
Models: nil,
|
||||
AllowedGroupIDs: []string{defaultTestGroup},
|
||||
UpstreamScheme: "https",
|
||||
UpstreamHost: "mistral.example.com",
|
||||
}
|
||||
mw := New(Config{Providers: []ProviderRoute{other, gateway}})
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
vendor string
|
||||
model string
|
||||
path string
|
||||
}{
|
||||
{name: "OpenAI", vendor: "openai", model: "gpt-4o-mini", path: "/v1/chat/completions"},
|
||||
{name: "Anthropic", vendor: "anthropic", model: "claude-sonnet-4-5", path: "/v1/messages"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
out, err := mw.Invoke(context.Background(), newInputVendorModelURL(tc.vendor, tc.model, tc.path))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, out)
|
||||
assert.Equal(t, middleware.DecisionAllow, out.Decision,
|
||||
"supported vendor must route through the multi-surface gateway")
|
||||
require.NotNil(t, out.Mutations)
|
||||
require.NotNil(t, out.Mutations.RewriteUpstream)
|
||||
assert.Equal(t, "gateway.example.com", out.Mutations.RewriteUpstream.Host)
|
||||
|
||||
provider, _ := metaValue(t, out.Metadata, middleware.KeyLLMResolvedProviderID)
|
||||
assert.Equal(t, "agentgateway", provider)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRouter_VendorAbsentFallsBackToModelPath confirms vendor filtering is
|
||||
// inert when the request carries no detected vendor: routing then relies on
|
||||
// model/path as before.
|
||||
@@ -692,6 +736,23 @@ func TestRouter_FactoryRejectsBadJSON(t *testing.T) {
|
||||
require.Error(t, err, "malformed JSON config must be rejected at chain build time")
|
||||
}
|
||||
|
||||
func TestRouter_FactoryDecodesLegacyAndMultiVendorFields(t *testing.T) {
|
||||
raw := []byte(`{"providers":[` +
|
||||
`{"id":"legacy","vendor":"openai","models":[],"upstream_scheme":"https","upstream_host":"openai.example.com","auth_header_name":"Authorization","auth_header_value":"Bearer legacy","allowed_group_ids":["group"]},` +
|
||||
`{"id":"multi","vendors":["openai","anthropic"],"models":[],"upstream_scheme":"https","upstream_host":"gateway.example.com","auth_header_name":"Authorization","auth_header_value":"Bearer multi","allowed_group_ids":["group"]}` +
|
||||
`]}`)
|
||||
|
||||
resolved, err := Factory{}.New(raw)
|
||||
require.NoError(t, err)
|
||||
router, ok := resolved.(*Middleware)
|
||||
require.True(t, ok, "factory must return the concrete router middleware")
|
||||
require.Len(t, router.cfg.Providers, 2)
|
||||
assert.Equal(t, "openai", router.cfg.Providers[0].Vendor,
|
||||
"the legacy singular field must keep decoding")
|
||||
assert.Equal(t, []string{"openai", "anthropic"}, router.cfg.Providers[1].Vendors,
|
||||
"the multi-vendor field must decode both supported surfaces")
|
||||
}
|
||||
|
||||
func TestRouter_FactoryAcceptsEmptyShapes(t *testing.T) {
|
||||
cases := [][]byte{nil, []byte(""), []byte(" "), []byte("null"), []byte("{}"), []byte("[]")}
|
||||
for _, raw := range cases {
|
||||
|
||||
Reference in New Issue
Block a user