Repository: kubernetes-sigs/gateway-api-inference-extension

Issue:
EPP: Destination Endpoint Keys Should Not Be Flags
Here are the flags in question:

```go
	destinationEndpointHintKey = flag.String(
		"destinationEndpointHintKey",
		runserver.DefaultDestinationEndpointHintKey,
		"Header and response metadata key used by Envoy to route to the appropriate pod. This must match Envoy configuration.")
	destinationEndpointHintMetadataNamespace = flag.String(
		"DestinationEndpointHintMetadataNamespace",
		runserver.DefaultDestinationEndpointHintMetadataNamespace,
		"The key for the outer namespace struct in the metadata field of the extproc response that is used to wrap the"+
			"target endpoint. If not set, then an outer namespace struct should not be created.")
```

These values **must** match on the Gateway. Therefore, they should not be configurable or the config should be exposed by InferencePool `ExtensionConnection`.

+1 on simply removing the flags until enough users demand to make them configurable.

Thoughts @ahg-g @kfswain @nirrozenbaum @robscott 

Discussion:
@ahg-g: sounds good to me@nirrozenbaum: SGTM@danehans: Note that https://github.com/kubernetes-sigs/gateway-api-inference-extension/pull/1282 added more flags that must be removed.@kfswain: Yeah, this sounds good. 

For posterity: Since the GW implementation is not user controlled but the EPP is, having a set protocol makes interaction easier. If we made these headers configurable that requires coordination that _all_ GW implementations would need to support. As @danehans mentions, best not to do this until there is strong demand.@danehans: Since InferencePool is being GA'd, we should discuss how these flags would be included in the API if they need to be configurable in the future. IMO, extending `ExtensionConnection` would make sense. For example:

```yaml
apiVersion: inference.networking.x-k8s.io/v1alpha2
kind: InferencePool
metadata:
  name: gateway-pool
  namespace: gwtest
spec:
  extensionRef:
    failureMode: FailOpen
    metadataKeys:
      destEndpoint: "foo" # Defaults to "x-gateway-destination-endpoint"
      destEndpointNamespace: "bar" # Defaults to "envoy.lb"
      destEndpointServed: "baz" # Defaults to"x-gateway-destination-endpoint-served"
)
```

__Note:__ Currently, `ExtensionConnection` is not present in the above example since the struct is being inlined:

```go
// Extension specifies how to configure an extension that runs the endpoint picker.
type Extension struct {
...
	// ExtensionConnection configures the connection between the gateway and the extension.
	ExtensionConnection `json:",inline"`
}
```@kfswain: That makes sense, I think the config looks clean like that too.@nirrozenbaum: @danehans your suggestion make sense to me.

Solution patch:
diff --git a/cmd/epp/runner/runner.go b/cmd/epp/runner/runner.go
index c5c5f45..d8e21c4 100644
--- a/cmd/epp/runner/runner.go
+++ b/cmd/epp/runner/runner.go
@@ -109,20 +109,6 @@ var (
 		"The path to the certificate for secure serving. The certificate and private key files "+
 			"are assumed to be named tls.crt and tls.key, respectively. If not set, and secureServing is enabled, "+
 			"then a self-signed certificate is used.")
-	// header/metadata flags
-	destinationEndpointHintKey = flag.String(
-		"destination-endpoint-hint-key",
-		runserver.DefaultDestinationEndpointHintKey,
-		"Header and response metadata key used by Envoy to route to the appropriate pod. This must match Envoy configuration.")
-	destinationEndpointHintMetadataNamespace = flag.String(
-		"destination-endpoint-hint-metadata-namespace",
-		runserver.DefaultDestinationEndpointHintMetadataNamespace,
-		"The key for the outer namespace struct in the metadata field of the extproc response that is used to wrap the"+
-			"target endpoint. If not set, then an outer namespace struct should not be created.")
-	fairnessIDHeaderKey = flag.String(
-		"fairness-id-header-key",
-		runserver.DefaultFairnessIDHeaderKey,
-		"The header key used to pass the fairness ID to be used in Flow Control.")
 	// metric flags
 	totalQueuedRequestsMetric = flag.String(
 		"total-queued-requests-metric",
@@ -201,7 +187,6 @@ func bindEnvToFlags() {
 		"MODEL_SERVER_METRICS_PATH":                       "model-server-metrics-path",
 		"MODEL_SERVER_METRICS_SCHEME":                     "model-server-metrics-scheme",
 		"MODEL_SERVER_METRICS_HTTPS_INSECURE_SKIP_VERIFY": "model-server-metrics-https-insecure-skip-verify",
-		"DESTINATION_ENDPOINT_HINT_KEY":                   "destination-endpoint-hint-key",
 		"POOL_NAME":                                       "pool-name",
 		"POOL_NAMESPACE":                                  "pool-namespace",
 		// durations & bools work too; flag.Set expects the *string* form
@@ -352,20 +337,17 @@ func (r *Runner) Run(ctx context.Context) error {
 
 	// --- Setup ExtProc Server Runner ---
 	serverRunner := &runserver.ExtProcServerRunner{
-		GrpcPort:                                 *grpcPort,
-		DestinationEndpointHintMetadataNamespace: *destinationEndpointHintMetadataNamespace,
-		DestinationEndpointHintKey:               *destinationEndpointHintKey,
-		FairnessIDHeaderKey:                      *fairnessIDHeaderKey,
-		PoolNamespacedName:                       poolNamespacedName,
-		PoolGKNN:                                 poolGKNN,
-		Datastore:                                datastore,
-		SecureServing:                            *secureServing,
-		HealthChecking:                           *healthChecking,
-		CertPath:                                 *certPath,
-		RefreshPrometheusMetricsInterval:         *refreshPrometheusMetricsInterval,
-		MetricsStalenessThreshold:                *metricsStalenessThreshold,
-		Director:                                 director,
-		SaturationDetector:                       saturationDetector,
+		GrpcPort:                         *grpcPort,
+		PoolNamespacedName:               poolNamespacedName,
+		PoolGKNN:                         poolGKNN,
+		Datastore:                        datastore,
+		SecureServing:                    *secureServing,
+		HealthChecking:                   *healthChecking,
+		CertPath:                         *certPath,
+		RefreshPrometheusMetricsInterval: *refreshPrometheusMetricsInterval,
+		MetricsStalenessThreshold:        *metricsStalenessThreshold,
+		Director:                         director,
+		SaturationDetector:               saturationDetector,
 	}
 	if err := serverRunner.SetupWithManager(ctx, mgr); err != nil {
 		setupLog.Error(err, "Failed to setup EPP controllers")
diff --git a/pkg/epp/handlers/request.go b/pkg/epp/handlers/request.go
index 997e215..096f0c8 100644
--- a/pkg/epp/handlers/request.go
+++ b/pkg/epp/handlers/request.go
@@ -24,13 +24,10 @@ import (
 	extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
 	"google.golang.org/protobuf/types/known/structpb"
 
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 	errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error"
 )
 
-const (
-	ObjectiveKey = "x-gateway-inference-objective"
-)
-
 func (s *StreamingServer) HandleRequestHeaders(reqCtx *RequestContext, req *extProcPb.ProcessingRequest_RequestHeaders) error {
 	reqCtx.RequestReceivedTimestamp = time.Now()
 
@@ -61,13 +58,13 @@ func (s *StreamingServer) HandleRequestHeaders(reqCtx *RequestContext, req *extP
 			reqCtx.Request.Headers[header.Key] = header.Value
 		}
 		switch header.Key {
-		case s.fairnessIDHeaderKey:
+		case metadata.FlowFairnessIDKey:
 			reqCtx.FairnessID = reqCtx.Request.Headers[header.Key]
 			// remove the fairness ID header from the request headers,
 			// this is not data that should be manipulated or sent to the backend.
 			// It is only used for flow control.
 			delete(reqCtx.Request.Headers, header.Key)
-		case ObjectiveKey:
+		case metadata.ObjectiveKey:
 			reqCtx.ObjectiveKey = reqCtx.Request.Headers[header.Key]
 			// remove the objective header from the request headers,
 			// this is not data that should be manipulated or sent to the backend.
@@ -117,7 +114,7 @@ func (s *StreamingServer) generateHeaders(reqCtx *RequestContext) []*configPb.He
 	headers := []*configPb.HeaderValueOption{
 		{
 			Header: &configPb.HeaderValue{
-				Key:      s.destinationEndpointHintKey,
+				Key:      metadata.DestinationEndpointKey,
 				RawValue: []byte(reqCtx.TargetEndpoint),
 			},
 		},
@@ -146,27 +143,21 @@ func (s *StreamingServer) generateHeaders(reqCtx *RequestContext) []*configPb.He
 }
 
 func (s *StreamingServer) generateMetadata(endpoint string) *structpb.Struct {
-	targetEndpointValue := &structpb.Struct{
+	return &structpb.Struct{
 		Fields: map[string]*structpb.Value{
-			s.destinationEndpointHintKey: {
-				Kind: &structpb.Value_StringValue{
-					StringValue: endpoint,
-				},
-			},
-		},
-	}
-	dynamicMetadata := targetEndpointValue
-	if s.destinationEndpointHintMetadataNamespace != "" {
-		// If a namespace is defined, wrap the selected endpoint with that.
-		dynamicMetadata = &structpb.Struct{
-			Fields: map[string]*structpb.Value{
-				s.destinationEndpointHintMetadataNamespace: {
-					Kind: &structpb.Value_StructValue{
-						StructValue: targetEndpointValue,
+			metadata.DestinationEndpointNamespace: {
+				Kind: &structpb.Value_StructValue{
+					StructValue: &structpb.Struct{
+						Fields: map[string]*structpb.Value{
+							metadata.DestinationEndpointKey: {
+								Kind: &structpb.Value_StringValue{
+									StringValue: endpoint,
+								},
+							},
+						},
 					},
 				},
 			},
-		}
+		},
 	}
-	return dynamicMetadata
 }
diff --git a/pkg/epp/handlers/server.go b/pkg/epp/handlers/server.go
index ca02aa0..89e09f9 100644
--- a/pkg/epp/handlers/server.go
+++ b/pkg/epp/handlers/server.go
@@ -44,13 +44,10 @@ const (
 	bodyByteLimit = 62000
 )
 
-func NewStreamingServer(destinationEndpointHintMetadataNamespace, destinationEndpointHintKey, fairnessIDHeaderKey string, datastore Datastore, director Director) *StreamingServer {
+func NewStreamingServer(datastore Datastore, director Director) *StreamingServer {
 	return &StreamingServer{
-		destinationEndpointHintMetadataNamespace: destinationEndpointHintMetadataNamespace,
-		destinationEndpointHintKey:               destinationEndpointHintKey,
-		fairnessIDHeaderKey:                      fairnessIDHeaderKey,
-		director:                                 director,
-		datastore:                                datastore,
+		director:  director,
+		datastore: datastore,
 	}
 }
 
@@ -67,15 +64,8 @@ type Datastore interface {
 // Server implements the Envoy external processing server.
 // https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ext_proc/v3/external_processor.proto
 type StreamingServer struct {
-	// The key of the header to specify the target pod address. This value needs to match Envoy
-	// configuration.
-	destinationEndpointHintKey string
-	// The key acting as the outer namespace struct in the metadata extproc response to communicate
-	// back the picked endpoints.
-	destinationEndpointHintMetadataNamespace string
-	fairnessIDHeaderKey                      string
-	datastore                                Datastore
-	director                                 Director
+	datastore Datastore
+	director  Director
 }
 
 // RequestContext stores context information during the life time of an HTTP request.
diff --git a/pkg/epp/metadata/consts.go b/pkg/epp/metadata/consts.go
new file mode 100644
index 0000000..faf9015
--- /dev/null
+++ b/pkg/epp/metadata/consts.go
@@ -0,0 +1,33 @@
+/*
+Copyright 2025 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package metadata
+
+const (
+	// SubsetFilterNamespace is the key for the outer namespace struct in the metadata field of the extproc request that is used to wrap the subset filter.
+	SubsetFilterNamespace = "envoy.lb.subset_hint"
+	// SubsetFilterKey is the metadata key used by Envoy to specify an array candidate pods for serving the request.
+	// If not specified, all the pods that are associated with the pool are candidates.
+	SubsetFilterKey = "x-gateway-destination-endpoint-subset"
+	// DestinationEndpointNamespace is the key for the outer namespace struct in the metadata field of the extproc response that is used to wrap the target endpoint.
+	DestinationEndpointNamespace = "envoy.lb"
+	// DestinationEndpointKey is the header and response metadata key used by Envoy to route to the appropriate pod.
+	DestinationEndpointKey = "x-gateway-destination-endpoint"
+	// FlowFairnessIDKey is the header key used to pass the fairness ID to be used in Flow Control.
+	FlowFairnessIDKey = "x-gateway-inference-fairness-id"
+	// ObjectiveKey is the header key used to specify the objective of an incoming request.
+	ObjectiveKey = "x-gateway-inference-objective"
+)
diff --git a/pkg/epp/requestcontrol/director.go b/pkg/epp/requestcontrol/director.go
index 2972b7d..3ba2818 100644
--- a/pkg/epp/requestcontrol/director.go
+++ b/pkg/epp/requestcontrol/director.go
@@ -35,6 +35,7 @@ import (
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
 	schedulingtypes "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 	errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error"
@@ -42,11 +43,6 @@ import (
 	requtil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/request"
 )
 
-const (
-	subsetHintNamespace = "envoy.lb.subset_hint"
-	subsetHintKey       = "x-gateway-destination-endpoint-subset"
-)
-
 // Scheduler defines the interface required by the Director for scheduling.
 type Scheduler interface {
 	Schedule(ctx context.Context, request *schedulingtypes.LLMRequest, candidatePods []schedulingtypes.Pod) (result *schedulingtypes.SchedulingResult, err error)
@@ -196,13 +192,13 @@ func (d *Director) admitRequest(ctx context.Context, requestCriticality v1alpha2
 func (d *Director) getCandidatePodsForScheduling(ctx context.Context, requestMetadata map[string]any) []schedulingtypes.Pod {
 	loggerTrace := log.FromContext(ctx).V(logutil.TRACE)
 
-	subsetMap, found := requestMetadata[subsetHintNamespace].(map[string]any)
+	subsetMap, found := requestMetadata[metadata.SubsetFilterNamespace].(map[string]any)
 	if !found {
 		return d.toSchedulerPodMetrics(d.datastore.PodList(backendmetrics.AllPodPredicate))
 	}
 
 	// Check if endpoint key is present in the subset map and ensure there is at least one value
-	endpointSubsetList, found := subsetMap[subsetHintKey].([]any)
+	endpointSubsetList, found := subsetMap[metadata.SubsetFilterKey].([]any)
 	if !found {
 		return d.toSchedulerPodMetrics(d.datastore.PodList(backendmetrics.AllPodPredicate))
 	} else if len(endpointSubsetList) == 0 {
diff --git a/pkg/epp/server/runserver.go b/pkg/epp/server/runserver.go
index 629af48..1b84bd7 100644
--- a/pkg/epp/server/runserver.go
+++ b/pkg/epp/server/runserver.go
@@ -45,20 +45,17 @@ import (
 
 // ExtProcServerRunner provides methods to manage an external process server.
 type ExtProcServerRunner struct {
-	GrpcPort                                 int
-	DestinationEndpointHintMetadataNamespace string
-	DestinationEndpointHintKey               string
-	FairnessIDHeaderKey                      string
-	PoolNamespacedName                       types.NamespacedName
-	PoolGKNN                                 common.GKNN
-	Datastore                                datastore.Datastore
-	SecureServing                            bool
-	HealthChecking                           bool
-	CertPath                                 string
-	RefreshPrometheusMetricsInterval         time.Duration
-	MetricsStalenessThreshold                time.Duration
-	Director                                 *requestcontrol.Director
-	SaturationDetector                       requestcontrol.SaturationDetector
+	GrpcPort                         int
+	PoolNamespacedName               types.NamespacedName
+	PoolGKNN                         common.GKNN
+	Datastore                        datastore.Datastore
+	SecureServing                    bool
+	HealthChecking                   bool
+	CertPath                         string
+	RefreshPrometheusMetricsInterval time.Duration
+	MetricsStalenessThreshold        time.Duration
+	Director                         *requestcontrol.Director
+	SaturationDetector               requestcontrol.SaturationDetector
 
 	// This should only be used in tests. We won't need this once we do not inject metrics in the tests.
 	// TODO:(https://github.com/kubernetes-sigs/gateway-api-inference-extension/issues/432) Cleanup
@@ -67,27 +64,24 @@ type ExtProcServerRunner struct {
 
 // Default values for CLI flags in main
 const (
-	DefaultGrpcPort                                 = 9002                              // default for --grpc-port
-	DefaultGrpcHealthPort                           = 9003                              // default for --grpc-health-port
-	DefaultMetricsPort                              = 9090                              // default for --metrics-port
-	DefaultDestinationEndpointHintMetadataNamespace = "envoy.lb"                        // default for --destinationEndpointHintMetadataNamespace
-	DefaultDestinationEndpointHintKey               = "x-gateway-destination-endpoint"  // default for --destination-endpoint-hint-key
-	DefaultFairnessIDHeaderKey                      = "x-gateway-inference-fairness-id" // default for --fairness-id-header-key
-	DefaultPoolName                                 = ""                                // required but no default
-	DefaultPoolNamespace                            = "default"                         // default for --pool-namespace
-	DefaultRefreshMetricsInterval                   = 50 * time.Millisecond             // default for --refresh-metrics-interval
-	DefaultRefreshPrometheusMetricsInterval         = 5 * time.Second                   // default for --refresh-prometheus-metrics-interval
-	DefaultSecureServing                            = true                              // default for --secure-serving
-	DefaultHealthChecking                           = false                             // default for --health-checking
-	DefaultEnablePprof                              = true                              // default for --enable-pprof
-	DefaultTotalQueuedRequestsMetric                = "vllm:num_requests_waiting"       // default for --total-queued-requests-metric
-	DefaultKvCacheUsagePercentageMetric             = "vllm:gpu_cache_usage_perc"       // default for --kv-cache-usage-percentage-metric
-	DefaultLoraInfoMetric                           = "vllm:lora_requests_info"         // default for --lora-info-metric
-	DefaultCertPath                                 = ""                                // default for --cert-path
-	DefaultConfigFile                               = ""                                // default for --config-file
-	DefaultConfigText                               = ""                                // default for --config-text
-	DefaultPoolGroup                                = "inference.networking.k8s.io"     // default for --pool-group
-	DefaultMetricsStalenessThreshold                = 2 * time.Second
+	DefaultGrpcPort                         = 9002                          // default for --grpc-port
+	DefaultGrpcHealthPort                   = 9003                          // default for --grpc-health-port
+	DefaultMetricsPort                      = 9090                          // default for --metrics-port
+	DefaultPoolName                         = ""                            // required but no default
+	DefaultPoolNamespace                    = "default"                     // default for --pool-namespace
+	DefaultRefreshMetricsInterval           = 50 * time.Millisecond         // default for --refresh-metrics-interval
+	DefaultRefreshPrometheusMetricsInterval = 5 * time.Second               // default for --refresh-prometheus-metrics-interval
+	DefaultSecureServing                    = true                          // default for --secure-serving
+	DefaultHealthChecking                   = false                         // default for --health-checking
+	DefaultEnablePprof                      = true                          // default for --enable-pprof
+	DefaultTotalQueuedRequestsMetric        = "vllm:num_requests_waiting"   // default for --total-queued-requests-metric
+	DefaultKvCacheUsagePercentageMetric     = "vllm:gpu_cache_usage_perc"   // default for --kv-cache-usage-percentage-metric
+	DefaultLoraInfoMetric                   = "vllm:lora_requests_info"     // default for --lora-info-metric
+	DefaultCertPath                         = ""                            // default for --cert-path
+	DefaultConfigFile                       = ""                            // default for --config-file
+	DefaultConfigText                       = ""                            // default for --config-text
+	DefaultPoolGroup                        = "inference.networking.k8s.io" // default for --pool-group
+	DefaultMetricsStalenessThreshold        = 2 * time.Second
 )
 
 // NewDefaultExtProcServerRunner creates a runner with default values.
@@ -101,16 +95,13 @@ func NewDefaultExtProcServerRunner() *ExtProcServerRunner {
 		},
 	}
 	return &ExtProcServerRunner{
-		GrpcPort:                                 DefaultGrpcPort,
-		DestinationEndpointHintKey:               DefaultDestinationEndpointHintKey,
-		DestinationEndpointHintMetadataNamespace: DefaultDestinationEndpointHintMetadataNamespace,
-		FairnessIDHeaderKey:                      DefaultFairnessIDHeaderKey,
-		PoolNamespacedName:                       types.NamespacedName{Name: DefaultPoolName, Namespace: DefaultPoolNamespace},
-		PoolGKNN:                                 poolGKNN,
-		SecureServing:                            DefaultSecureServing,
-		HealthChecking:                           DefaultHealthChecking,
-		RefreshPrometheusMetricsInterval:         DefaultRefreshPrometheusMetricsInterval,
-		MetricsStalenessThreshold:                DefaultMetricsStalenessThreshold,
+		GrpcPort:                         DefaultGrpcPort,
+		PoolNamespacedName:               types.NamespacedName{Name: DefaultPoolName, Namespace: DefaultPoolNamespace},
+		PoolGKNN:                         poolGKNN,
+		SecureServing:                    DefaultSecureServing,
+		HealthChecking:                   DefaultHealthChecking,
+		RefreshPrometheusMetricsInterval: DefaultRefreshPrometheusMetricsInterval,
+		MetricsStalenessThreshold:        DefaultMetricsStalenessThreshold,
 		// Dependencies can be assigned later.
 	}
 }
@@ -172,17 +163,8 @@ func (r *ExtProcServerRunner) AsRunnable(logger logr.Logger) manager.Runnable {
 			srv = grpc.NewServer()
 		}
 
-		extProcServer := handlers.NewStreamingServer(
-			r.DestinationEndpointHintMetadataNamespace,
-			r.DestinationEndpointHintKey,
-			r.FairnessIDHeaderKey,
-			r.Datastore,
-			r.Director,
-		)
-		extProcPb.RegisterExternalProcessorServer(
-			srv,
-			extProcServer,
-		)
+		extProcServer := handlers.NewStreamingServer(r.Datastore, r.Director)
+		extProcPb.RegisterExternalProcessorServer(srv, extProcServer)
 
 		if r.HealthChecking {
 			healthcheck := health.NewServer()
diff --git a/site-src/implementations/gateways.md b/site-src/implementations/gateways.md
index 950c083..53e6f92 100644
--- a/site-src/implementations/gateways.md
+++ b/site-src/implementations/gateways.md
@@ -31,13 +31,11 @@ Issue](https://github.com/envoyproxy/ai-gateway/issues/423).
 
 ## Kgateway
 
-[Kgateway](https://kgateway.dev/) is a feature-rich, Kubernetes-native
-ingress controller and next-generation API gateway. Kgateway brings the
-full power and community support of Gateway API to its existing control-plane
-implementation.
-
-Progress towards supporting this project is tracked with a [GitHub
-Issue](https://github.com/kgateway-dev/kgateway/issues/10411).
+[Kgateway](https://kgateway.dev/) is a Gateway API Inference Extension
+[conformant](https://github.com/kubernetes-sigs/gateway-api-inference-extension/tree/main/conformance/reports/v0.5.1/gateway/kgateway)
+gateway that can run [independently](https://gateway-api-inference-extension.sigs.k8s.io/guides/#__tabbed_3_3), as an [Istio waypoint](https://kgateway.dev/blog/extend-istio-ambient-kgateway-waypoint/),
+or within your [llm-d infrastructure](https://github.com/llm-d-incubation/llm-d-infra) to improve accelerator (GPU)
+utilization for AI inference workloads.
 
 ## Google Kubernetes Engine
 



Test patch:
diff --git a/pkg/epp/handlers/request_test.go b/pkg/epp/handlers/request_test.go
index fc1a7ca..4ae2078 100644
--- a/pkg/epp/handlers/request_test.go
+++ b/pkg/epp/handlers/request_test.go
@@ -21,15 +21,14 @@ import (
 
 	configPb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	extProcPb "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 )
 
 func TestHandleRequestHeaders(t *testing.T) {
 	t.Parallel()
 
 	// Setup a mock server and request context
-	server := &StreamingServer{
-		fairnessIDHeaderKey: "test-fairness-id",
-	}
+	server := &StreamingServer{}
 
 	reqCtx := &RequestContext{
 		Request: &Request{
@@ -46,7 +45,7 @@ func TestHandleRequestHeaders(t *testing.T) {
 						Value: "test-value",
 					},
 					{
-						Key:   "test-fairness-id",
+						Key:   metadata.FlowFairnessIDKey,
 						Value: "test-fairness-id-value",
 					},
 				},
@@ -63,7 +62,7 @@ func TestHandleRequestHeaders(t *testing.T) {
 	if reqCtx.FairnessID != "test-fairness-id-value" {
 		t.Errorf("expected fairness ID to be 'test-fairness-id-value', got %s", reqCtx.FairnessID)
 	}
-	if reqCtx.Request.Headers["test-fairness-id"] == "test-fairness-id-value" {
+	if reqCtx.Request.Headers[metadata.FlowFairnessIDKey] == "test-fairness-id-value" {
 		t.Errorf("expected fairness ID header to be removed from request headers, but it was not")
 	}
 }
diff --git a/pkg/epp/requestcontrol/director_test.go b/pkg/epp/requestcontrol/director_test.go
index 975f75c..3a98b28 100644
--- a/pkg/epp/requestcontrol/director_test.go
+++ b/pkg/epp/requestcontrol/director_test.go
@@ -39,6 +39,7 @@ import (
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/plugins"
 	schedulingtypes "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/scheduling/types"
 	errutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/error"
@@ -435,8 +436,8 @@ func TestDirector_HandleRequest(t *testing.T) {
 func TestGetCandidatePodsForScheduling(t *testing.T) {
 	var makeFilterMetadata = func(data []any) map[string]any {
 		return map[string]any{
-			"envoy.lb.subset_hint": map[string]any{
-				"x-gateway-destination-endpoint-subset": data,
+			metadata.SubsetFilterNamespace: map[string]any{
+				metadata.SubsetFilterKey: data,
 			},
 		}
 	}
@@ -493,7 +494,7 @@ func TestGetCandidatePodsForScheduling(t *testing.T) {
 		},
 		{
 			name:     "SubsetFilter, namespace present filter not present — return all pods",
-			metadata: map[string]any{"envoy.lb.subset_hint": map[string]any{}},
+			metadata: map[string]any{metadata.SubsetFilterNamespace: map[string]any{}},
 			output: []schedulingtypes.Pod{
 				&schedulingtypes.PodMetrics{
 					Pod:          outputPod1,
diff --git a/pkg/epp/server/server_test.go b/pkg/epp/server/server_test.go
index 28b6433..dfcc4bb 100644
--- a/pkg/epp/server/server_test.go
+++ b/pkg/epp/server/server_test.go
@@ -28,25 +28,24 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 	testutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/testing"
 	"sigs.k8s.io/gateway-api-inference-extension/test/utils"
 )
 
 const (
-	bufSize                    = 1024 * 1024
-	podName                    = "pod1"
-	podAddress                 = "1.2.3.4"
-	poolPort                   = int32(5678)
-	destinationEndpointHintKey = "test-target"
-	fairnessIDHeaderKey        = "x-fairness-id"
-	namespace                  = "ns1"
+	bufSize    = 1024 * 1024
+	podName    = "pod1"
+	podAddress = "1.2.3.4"
+	poolPort   = int32(5678)
+	namespace  = "ns1"
 )
 
 func TestServer(t *testing.T) {
 	theHeaderValue := "body"
 	requestHeader := "x-test"
 
-	expectedRequestHeaders := map[string]string{destinationEndpointHintKey: fmt.Sprintf("%s:%d", podAddress, poolPort),
+	expectedRequestHeaders := map[string]string{metadata.DestinationEndpointKey: fmt.Sprintf("%s:%d", podAddress, poolPort),
 		"Content-Length": "42", ":method": "POST", requestHeader: theHeaderValue}
 	expectedResponseHeaders := map[string]string{"x-went-into-resp-headers": "true", ":method": "POST", requestHeader: theHeaderValue}
 	expectedSchedulerHeaders := map[string]string{":method": "POST", requestHeader: theHeaderValue}
@@ -59,7 +58,7 @@ func TestServer(t *testing.T) {
 		ctx, cancel, ds, _ := utils.PrepareForTestStreamingServer([]*v1alpha2.InferenceObjective{model},
 			[]*v1.Pod{{ObjectMeta: metav1.ObjectMeta{Name: podName}}}, "test-pool1", namespace, poolPort)
 
-		streamingServer := handlers.NewStreamingServer(namespace, destinationEndpointHintKey, fairnessIDHeaderKey, ds, director)
+		streamingServer := handlers.NewStreamingServer(ds, director)
 
 		testListener, errChan := utils.SetupTestStreamingServer(t, ctx, ds, streamingServer)
 		process, conn := utils.GetStreamingServerClient(ctx, t)
@@ -67,9 +66,9 @@ func TestServer(t *testing.T) {
 
 		// Send request headers - no response expected
 		headers := utils.BuildEnvoyGRPCHeaders(map[string]string{
-			requestHeader:       theHeaderValue,
-			":method":           "POST",
-			fairnessIDHeaderKey: "a-very-interesting-fairness-id",
+			requestHeader:              theHeaderValue,
+			":method":                  "POST",
+			metadata.FlowFairnessIDKey: "a-very-interesting-fairness-id",
 		}, true)
 		request := &pb.ProcessingRequest{
 			Request: &pb.ProcessingRequest_RequestHeaders{
diff --git a/test/e2e/epp/e2e_test.go b/test/e2e/epp/e2e_test.go
index f94ca45..5e9b408 100644
--- a/test/e2e/epp/e2e_test.go
+++ b/test/e2e/epp/e2e_test.go
@@ -35,7 +35,7 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	"sigs.k8s.io/gateway-api-inference-extension/apix/v1alpha2"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 	testutils "sigs.k8s.io/gateway-api-inference-extension/test/utils"
 )
 
@@ -288,7 +288,7 @@ func getCurlCommand(name, ns, port, model string, timeout time.Duration, api str
 		"-H",
 		"Content-Type: application/json",
 		"-H",
-		fmt.Sprintf("%v: inferenceobjective-sample", handlers.ObjectiveKey),
+		fmt.Sprintf("%v: inferenceobjective-sample", metadata.ObjectiveKey),
 		"-d",
 		string(b),
 	}
diff --git a/test/integration/epp/hermetic_test.go b/test/integration/epp/hermetic_test.go
index 6b2f058..c82a702 100644
--- a/test/integration/epp/hermetic_test.go
+++ b/test/integration/epp/hermetic_test.go
@@ -64,7 +64,7 @@ import (
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend"
 	backendmetrics "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/backend/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/datastore"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metrics"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/requestcontrol"
 	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/saturationdetector"
@@ -339,7 +339,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 										Value: "mom",
 									},
 									{
-										Key:   handlers.ObjectiveKey,
+										Key:   metadata.ObjectiveKey,
 										Value: "sql-lora-sheddable",
 									},
 								},
@@ -395,7 +395,7 @@ func TestFullDuplexStreamed_KubeInferenceObjectiveRequest(t *testing.T) {
 										Value: "mom",
 									},
 									{
-										Key:   handlers.ObjectiveKey,
+										Key:   metadata.ObjectiveKey,
 										Value: modelDirect,
 									},
 								},
diff --git a/test/integration/util.go b/test/integration/util.go
index 46c73ed..38ef0a1 100644
--- a/test/integration/util.go
+++ b/test/integration/util.go
@@ -29,13 +29,11 @@ import (
 	"github.com/go-logr/logr"
 	"google.golang.org/protobuf/types/known/structpb"
 
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/handlers"
-	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/server"
+	"sigs.k8s.io/gateway-api-inference-extension/pkg/epp/metadata"
 	logutil "sigs.k8s.io/gateway-api-inference-extension/pkg/epp/util/logging"
 )
 
 const (
-	headerKeyDestination   = "x-gateway-destination-endpoint"
 	headerKeyContentLength = "Content-Length"
 )
 
@@ -125,7 +123,7 @@ func GenerateStreamedRequestSet(logger logr.Logger, prompt, model string, filter
 							Value: "mom",
 						},
 						{
-							Key:   handlers.ObjectiveKey,
+							Key:   metadata.ObjectiveKey,
 							Value: model,
 						},
 					},
@@ -144,32 +142,28 @@ func GenerateStreamedRequestSet(logger logr.Logger, prompt, model string, filter
 }
 
 func GenerateRequestMetadata(filterMetadata []string) map[string]*structpb.Struct {
-	metadata := make(map[string]*structpb.Struct)
+	requestMetadata := make(map[string]*structpb.Struct)
 	interfaceList := make([]any, len(filterMetadata))
 	for i, val := range filterMetadata {
 		interfaceList[i] = val
 	}
 	if filterMetadata != nil {
 		structVal, _ := structpb.NewStruct(map[string]any{
-			"x-gateway-destination-endpoint-subset": interfaceList,
+			metadata.SubsetFilterKey: interfaceList,
 		})
-		metadata["envoy.lb.subset_hint"] = structVal
+		requestMetadata[metadata.SubsetFilterNamespace] = structVal
 	}
-	return metadata
+	return requestMetadata
 }
 
 // NewRequestBufferedResponse creates a complete set of responses for the request phase.
 // It modifies request headers (e.g., for routing) and replaces the entire request body.
 // It returns a slice of two messages, representing the complete buffered action.
-func NewRequestBufferedResponse(
-	destinationEndpoint string,
-	rewrittenBody string,
-	otherHeaders ...*envoyCorev3.HeaderValueOption,
-) []*extProcPb.ProcessingResponse {
+func NewRequestBufferedResponse(destinationEndpoint string, rewrittenBody string, otherHeaders ...*envoyCorev3.HeaderValueOption) []*extProcPb.ProcessingResponse {
 	setHeaders := []*envoyCorev3.HeaderValueOption{
 		{
 			Header: &envoyCorev3.HeaderValue{
-				Key:      headerKeyDestination,
+				Key:      metadata.DestinationEndpointKey,
 				RawValue: []byte(destinationEndpoint),
 			},
 		},
@@ -219,10 +213,7 @@ func NewRequestBufferedResponse(
 // NewResponseBufferedResponse creates a complete set of responses for the response phase.
 // It modifies response headers and replaces the entire response body.
 // It is used when the processor buffers the upstream response before sending its own.
-func NewResponseBufferedResponse(
-	rewrittenBody string,
-	headersToSet ...*envoyCorev3.HeaderValueOption,
-) []*extProcPb.ProcessingResponse {
+func NewResponseBufferedResponse(rewrittenBody string, headersToSet ...*envoyCorev3.HeaderValueOption) []*extProcPb.ProcessingResponse {
 	return []*extProcPb.ProcessingResponse{
 		NewResponseHeaders(headersToSet...),
 		NewResponseStreamChunk(rewrittenBody, true),
@@ -286,11 +277,11 @@ func NewImmediateErrorResponse(code envoyTypePb.StatusCode, body string) []*extP
 func makeMetadata(endpoint string) *structpb.Struct {
 	return &structpb.Struct{
 		Fields: map[string]*structpb.Value{
-			server.DefaultDestinationEndpointHintMetadataNamespace: {
+			metadata.DestinationEndpointNamespace: {
 				Kind: &structpb.Value_StructValue{
 					StructValue: &structpb.Struct{
 						Fields: map[string]*structpb.Value{
-							server.DefaultDestinationEndpointHintKey: {
+							metadata.DestinationEndpointKey: {
 								Kind: &structpb.Value_StringValue{
 									StringValue: endpoint,
 								},