This is an automated email from the ASF dual-hosted git repository.

mfordjody pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/dubbo-kubernetes.git


The following commit(s) were added to refs/heads/master by this push:
     new e4fe0b73 feat: add mesh-native AI service routing (#1007)
e4fe0b73 is described below

commit e4fe0b7338564c947efc7d916d95928c29a62671
Author: mfordjody <[email protected]>
AuthorDate: Sun Aug 9 11:27:28 2026 +0800

    feat: add mesh-native AI service routing (#1007)
---
 .github/workflows/ci.yml                           |   3 +-
 dubbod/discovery/pkg/bootstrap/server.go           |  59 +--
 dubbod/discovery/pkg/bootstrap/server_test.go      |  32 ++
 .../pkg/config/kube/crdclient/types.gen.go         |  51 +++
 .../kube/gateway/deployment_controller_test.go     |  77 ++--
 dubbod/discovery/pkg/model/push_context.go         |  39 ++
 .../pkg/networking/grpcgen/agent_config.go         | 430 +++++++++++++++++++++
 dubbod/discovery/pkg/networking/grpcgen/rds.go     |  52 ++-
 .../discovery/pkg/networking/grpcgen/rds_test.go   | 170 ++++++++
 go.mod                                             |   6 +-
 go.sum                                             |  12 +-
 manifests/charts/base/files/crd-all.gen.yaml       | 365 +++++++++++++++++
 manifests/charts/dubbod/files/kube-gateway.yaml    |  36 +-
 manifests/charts/dubbod/templates/clusterrole.yaml |  12 +
 .../dubbod/templates/configmap-injector.yaml       |  17 +
 pkg/config/schema/collections/collections.gen.go   |  19 +
 pkg/config/schema/gvk/resources.gen.go             |   7 +
 pkg/config/schema/gvr/resources.gen.go             |   3 +
 pkg/config/schema/kind/resources.gen.go            |   5 +
 pkg/config/schema/kubeclient/resources.gen.go      |  13 +
 pkg/config/schema/kubetypes/resources.gen.go       |   4 +
 pkg/config/schema/metadata.yaml                    |  10 +
 pkg/config/validation/validators.go                | 151 ++++++++
 pkg/config/validation/validators_test.go           | 106 +++++
 samples/ai-mesh/README.md                          |  18 +
 samples/ai-mesh/backends.yaml                      | 119 ++++++
 samples/ai-mesh/gateway.yaml                       |  13 +
 samples/ai-mesh/routes.yaml                        |  86 +++++
 samples/ai-mesh/services.yaml                      |  80 ++++
 tests/e2e/agentmock/Dockerfile                     |   9 +
 tests/e2e/agentmock/main.go                        |  94 +++++
 tests/e2e/run.sh                                   | 134 ++++++-
 32 files changed, 2132 insertions(+), 100 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 79b5fdb0..ecc8143e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -251,7 +251,7 @@ jobs:
         uses: actions/checkout@v4
         with:
           repository: kdubbo/dxgate
-          ref: d683797f44c9ba4c6e1eb6cdb2da21460b00cbfd
+          ref: c8e423501f103c7faa08a0cee7a43680a5054138
           path: .e2e/dxgate
 
       - name: Build dxgate activation image
@@ -266,6 +266,7 @@ jobs:
       - name: Run real KEDA activation
         env:
           ACTIVATION_E2E: '1'
+          AI_MESH_E2E: '1'
           CLUSTER_NAME: dubbo-activation-ga
           DXGATE_IMAGE: kdubbo/dxgate:ga-e2e
           IMAGE: kdubbo/dubbod:activation-ga-e2e
diff --git a/dubbod/discovery/pkg/bootstrap/server.go 
b/dubbod/discovery/pkg/bootstrap/server.go
index bcb49a48..a6a2faf5 100644
--- a/dubbod/discovery/pkg/bootstrap/server.go
+++ b/dubbod/discovery/pkg/bootstrap/server.go
@@ -500,31 +500,8 @@ func (s *Server) initRegistryEventHandlers() {
                log.Infof("processing config change, schema identifier=%s, 
GVK=%v, name=%s/%s, event=%s",
                        schemaID, cfg.GroupVersionKind, cfg.Namespace, 
cfg.Name, event)
 
-               var configKind kind.Kind
-               switch schemaID {
-               case "AuthorizationPolicy":
-                       configKind = kind.AuthorizationPolicy
-               case "PeerAuthentication":
-                       configKind = kind.PeerAuthentication
-               case "RequestAuthentication":
-                       configKind = kind.RequestAuthentication
-               case "ReferenceGrant":
-                       configKind = kind.ReferenceGrant
-               case "GatewayClass":
-                       configKind = kind.GatewayClass
-               case "KubernetesGateway":
-                       configKind = kind.KubernetesGateway
-               case "HTTPRoute":
-                       configKind = kind.HTTPRoute
-               case "BackendTLSPolicy":
-                       configKind = kind.BackendTLSPolicy
-               case "CircuitBreakerPolicy":
-                       configKind = kind.CircuitBreakerPolicy
-               case "FaultInjectionPolicy":
-                       configKind = kind.FaultInjectionPolicy
-               case "ServiceActivationPolicy":
-                       configKind = kind.ServiceActivationPolicy
-               default:
+               configKind, found := configKindForSchemaIdentifier(schemaID)
+               if !found {
                        log.Debugf("unknown schema identifier %s for %v, 
skipping", schemaID, cfg.GroupVersionKind)
                        return
                }
@@ -557,6 +534,7 @@ func (s *Server) initRegistryEventHandlers() {
                        configKind == kind.ReferenceGrant ||
                        configKind == kind.CircuitBreakerPolicy ||
                        configKind == kind.FaultInjectionPolicy ||
+                       configKind == kind.DxgateService ||
                        configKind == kind.ServiceActivationPolicy
 
                // Trigger ConfigUpdate to push changes to all connected proxies
@@ -618,6 +596,37 @@ func (s *Server) initReadinessProbes() {
        }
 }
 
+func configKindForSchemaIdentifier(schemaID string) (kind.Kind, bool) {
+       switch schemaID {
+       case "AuthorizationPolicy":
+               return kind.AuthorizationPolicy, true
+       case "PeerAuthentication":
+               return kind.PeerAuthentication, true
+       case "RequestAuthentication":
+               return kind.RequestAuthentication, true
+       case "ReferenceGrant":
+               return kind.ReferenceGrant, true
+       case "GatewayClass":
+               return kind.GatewayClass, true
+       case "KubernetesGateway":
+               return kind.KubernetesGateway, true
+       case "HTTPRoute":
+               return kind.HTTPRoute, true
+       case "BackendTLSPolicy":
+               return kind.BackendTLSPolicy, true
+       case "CircuitBreakerPolicy":
+               return kind.CircuitBreakerPolicy, true
+       case "FaultInjectionPolicy":
+               return kind.FaultInjectionPolicy, true
+       case "DxgateService":
+               return kind.DxgateService, true
+       case "ServiceActivationPolicy":
+               return kind.ServiceActivationPolicy, true
+       default:
+               return 0, false
+       }
+}
+
 func (s *Server) initMulticluster(args *DubboArgs) {
        if s.kubeClient == nil {
                return
diff --git a/dubbod/discovery/pkg/bootstrap/server_test.go 
b/dubbod/discovery/pkg/bootstrap/server_test.go
new file mode 100644
index 00000000..a04ae89c
--- /dev/null
+++ b/dubbod/discovery/pkg/bootstrap/server_test.go
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 bootstrap
+
+import (
+       "testing"
+
+       "github.com/apache/dubbo-kubernetes/pkg/config/schema/kind"
+)
+
+func TestConfigKindForSchemaIdentifierIncludesDxgateService(t *testing.T) {
+       got, found := configKindForSchemaIdentifier("DxgateService")
+       if !found {
+               t.Fatal("DxgateService schema identifier was not mapped")
+       }
+       if got != kind.DxgateService {
+               t.Fatalf("kind = %v, want %v", got, kind.DxgateService)
+       }
+}
diff --git a/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go 
b/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
index 6eaf61e3..731cc4db 100755
--- a/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
+++ b/dubbod/discovery/pkg/config/kube/crdclient/types.gen.go
@@ -50,6 +50,11 @@ func create(c kube.Client, cfg config.Config, objMeta 
metav1.ObjectMeta) (metav1
                        ObjectMeta: objMeta,
                        Spec:       
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.CircuitBreakerPolicy)),
                }, metav1.CreateOptions{})
+       case gvk.DxgateService:
+               return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(cfg.Namespace).Create(context.TODO(),
 &apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService{
+                       ObjectMeta: objMeta,
+                       Spec:       
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.DxgateService)),
+               }, metav1.CreateOptions{})
        case gvk.FaultInjectionPolicy:
                return 
c.Dubbo().NetworkingV1alpha3().FaultInjectionPolicies(cfg.Namespace).Create(context.TODO(),
 &apigithubcomapachedubbokubernetesapinetworkingv1alpha3.FaultInjectionPolicy{
                        ObjectMeta: objMeta,
@@ -127,6 +132,11 @@ func update(c kube.Client, cfg config.Config, objMeta 
metav1.ObjectMeta) (metav1
                        ObjectMeta: objMeta,
                        Spec:       
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.CircuitBreakerPolicy)),
                }, metav1.UpdateOptions{})
+       case gvk.DxgateService:
+               return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(cfg.Namespace).Update(context.TODO(),
 &apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService{
+                       ObjectMeta: objMeta,
+                       Spec:       
*(cfg.Spec.(*githubcomkdubboapinetworkingv1alpha3.DxgateService)),
+               }, metav1.UpdateOptions{})
        case gvk.FaultInjectionPolicy:
                return 
c.Dubbo().NetworkingV1alpha3().FaultInjectionPolicies(cfg.Namespace).Update(context.TODO(),
 &apigithubcomapachedubbokubernetesapinetworkingv1alpha3.FaultInjectionPolicy{
                        ObjectMeta: objMeta,
@@ -204,6 +214,11 @@ func updateStatus(c kube.Client, cfg config.Config, 
objMeta metav1.ObjectMeta) (
                        ObjectMeta: objMeta,
                        Status:     
*(cfg.Status.(*githubcomkdubboapimetav1alpha1.DubboStatus)),
                }, metav1.UpdateOptions{})
+       case gvk.DxgateService:
+               return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(cfg.Namespace).UpdateStatus(context.TODO(),
 &apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService{
+                       ObjectMeta: objMeta,
+                       Status:     
*(cfg.Status.(*githubcomkdubboapimetav1alpha1.DubboStatus)),
+               }, metav1.UpdateOptions{})
        case gvk.FaultInjectionPolicy:
                return 
c.Dubbo().NetworkingV1alpha3().FaultInjectionPolicies(cfg.Namespace).UpdateStatus(context.TODO(),
 &apigithubcomapachedubbokubernetesapinetworkingv1alpha3.FaultInjectionPolicy{
                        ObjectMeta: objMeta,
@@ -309,6 +324,21 @@ func patch(c kube.Client, orig config.Config, origMeta 
metav1.ObjectMeta, mod co
                }
                return 
c.Dubbo().NetworkingV1alpha3().CircuitBreakerPolicies(orig.Namespace).
                        Patch(context.TODO(), orig.Name, typ, patchBytes, 
metav1.PatchOptions{FieldManager: "pilot-discovery"})
+       case gvk.DxgateService:
+               oldRes := 
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService{
+                       ObjectMeta: origMeta,
+                       Spec:       
*(orig.Spec.(*githubcomkdubboapinetworkingv1alpha3.DxgateService)),
+               }
+               modRes := 
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService{
+                       ObjectMeta: modMeta,
+                       Spec:       
*(mod.Spec.(*githubcomkdubboapinetworkingv1alpha3.DxgateService)),
+               }
+               patchBytes, err := genPatchBytes(oldRes, modRes, typ)
+               if err != nil {
+                       return nil, err
+               }
+               return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(orig.Namespace).
+                       Patch(context.TODO(), orig.Name, typ, patchBytes, 
metav1.PatchOptions{FieldManager: "pilot-discovery"})
        case gvk.FaultInjectionPolicy:
                oldRes := 
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.FaultInjectionPolicy{
                        ObjectMeta: origMeta,
@@ -491,6 +521,8 @@ func delete(c kube.Client, typ config.GroupVersionKind, 
name, namespace string,
                return 
c.GatewayAPI().GatewayV1().BackendTLSPolicies(namespace).Delete(context.TODO(), 
name, deleteOptions)
        case gvk.CircuitBreakerPolicy:
                return 
c.Dubbo().NetworkingV1alpha3().CircuitBreakerPolicies(namespace).Delete(context.TODO(),
 name, deleteOptions)
+       case gvk.DxgateService:
+               return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(namespace).Delete(context.TODO(), 
name, deleteOptions)
        case gvk.FaultInjectionPolicy:
                return 
c.Dubbo().NetworkingV1alpha3().FaultInjectionPolicies(namespace).Delete(context.TODO(),
 name, deleteOptions)
        case gvk.GatewayClass:
@@ -648,6 +680,25 @@ var translationMap = map[config.GroupVersionKind]func(r 
runtime.Object) config.C
                        Spec: &obj.Spec,
                }
        },
+       gvk.DxgateService: func(r runtime.Object) config.Config {
+               obj := 
r.(*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService)
+               return config.Config{
+                       Meta: config.Meta{
+                               GroupVersionKind:  gvk.DxgateService,
+                               Name:              obj.Name,
+                               Namespace:         obj.Namespace,
+                               Labels:            obj.Labels,
+                               Annotations:       obj.Annotations,
+                               ResourceVersion:   obj.ResourceVersion,
+                               CreationTimestamp: obj.CreationTimestamp.Time,
+                               OwnerReferences:   obj.OwnerReferences,
+                               UID:               string(obj.UID),
+                               Generation:        obj.Generation,
+                       },
+                       Spec:   &obj.Spec,
+                       Status: &obj.Status,
+               }
+       },
        gvk.EndpointSlice: func(r runtime.Object) config.Config {
                obj := r.(*k8sioapidiscoveryv1.EndpointSlice)
                return config.Config{
diff --git 
a/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go 
b/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go
index 826f4701..cd259594 100644
--- a/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go
+++ b/dubbod/discovery/pkg/config/kube/gateway/deployment_controller_test.go
@@ -691,8 +691,8 @@ func TestKubeGatewayTemplateRendersDxgateResources(t 
*testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       if len(rendered) != 4 {
-               t.Fatalf("expected 4 rendered resources, got %d", len(rendered))
+       if len(rendered) != 6 {
+               t.Fatalf("expected 6 rendered resources, got %d", len(rendered))
        }
        for i, doc := range rendered {
                var obj map[string]any
@@ -706,25 +706,30 @@ func TestKubeGatewayTemplateRendersDxgateResources(t 
*testing.T) {
        if !strings.Contains(rendered[0], "bootstrap.json") || 
!strings.Contains(rendered[0], `"xds_address": 
"http://dubbod.dubbo-system.svc:26010"`) {
                t.Fatalf("configmap did not render dxgate bootstrap:\n%s", 
rendered[0])
        }
-       if !strings.Contains(rendered[2], "image: kdubbo/dxgate:test") {
-               t.Fatalf("deployment did not render dxgate image:\n%s", 
rendered[2])
+       if !strings.Contains(rendered[1], "automountServiceAccountToken: true") 
||
+               !strings.Contains(rendered[2], "resources: [\"secrets\"]") ||
+               !strings.Contains(rendered[3], "kind: RoleBinding") {
+               t.Fatalf("credential Secret RBAC not rendered:\n%s", 
strings.Join(rendered[1:4], "\n---\n"))
        }
-       if !strings.Contains(rendered[2], "DXGATE_BOOTSTRAP") || 
strings.Contains(rendered[2], "DXGATE_STATIC_CONFIG") {
-               t.Fatalf("deployment did not switch from static config to 
bootstrap:\n%s", rendered[2])
+       if !strings.Contains(rendered[4], "image: kdubbo/dxgate:test") {
+               t.Fatalf("deployment did not render dxgate image:\n%s", 
rendered[4])
        }
-       if !strings.Contains(rendered[2], `proxyless.dubbo.apache.org/inject: 
"true"`) {
-               t.Fatalf("deployment pod template did not enable proxyless 
injection for dxgate mTLS certs:\n%s", rendered[2])
+       if !strings.Contains(rendered[4], "DXGATE_BOOTSTRAP") || 
strings.Contains(rendered[4], "DXGATE_STATIC_CONFIG") {
+               t.Fatalf("deployment did not switch from static config to 
bootstrap:\n%s", rendered[4])
        }
-       if !strings.Contains(rendered[2], "inject.dubbo.apache.org/templates: 
grpc-engine") {
-               t.Fatalf("deployment pod template did not request grpc-engine 
injection:\n%s", rendered[2])
+       if !strings.Contains(rendered[4], `proxyless.dubbo.apache.org/inject: 
"true"`) {
+               t.Fatalf("deployment pod template did not enable proxyless 
injection for dxgate mTLS certs:\n%s", rendered[4])
        }
-       if !strings.Contains(rendered[2], `prometheus.io/scrape: "true"`) ||
-               !strings.Contains(rendered[2], "prometheus.io/path: /metrics") 
||
-               !strings.Contains(rendered[2], `prometheus.io/port: "26021"`) {
-               t.Fatalf("deployment pod template did not render prometheus 
scrape annotations:\n%s", rendered[2])
+       if !strings.Contains(rendered[4], "inject.dubbo.apache.org/templates: 
grpc-engine") {
+               t.Fatalf("deployment pod template did not request grpc-engine 
injection:\n%s", rendered[4])
        }
-       if strings.Contains(rendered[2], "DXGATE_OTEL_ENDPOINT") {
-               t.Fatalf("deployment rendered OTEL endpoint without 
annotation:\n%s", rendered[2])
+       if !strings.Contains(rendered[4], `prometheus.io/scrape: "true"`) ||
+               !strings.Contains(rendered[4], "prometheus.io/path: /metrics") 
||
+               !strings.Contains(rendered[4], `prometheus.io/port: "26021"`) {
+               t.Fatalf("deployment pod template did not render prometheus 
scrape annotations:\n%s", rendered[4])
+       }
+       if strings.Contains(rendered[4], "DXGATE_OTEL_ENDPOINT") {
+               t.Fatalf("deployment rendered OTEL endpoint without 
annotation:\n%s", rendered[4])
        }
        for _, want := range []string{
                "DXGATE_GATEWAY_NAME",
@@ -737,21 +742,21 @@ func TestKubeGatewayTemplateRendersDxgateResources(t 
*testing.T) {
                "DXGATE_ACCESS_LOG_FORMAT",
                `value: "text"`,
        } {
-               if !strings.Contains(rendered[2], want) {
-                       t.Fatalf("deployment did not render observability env 
%q:\n%s", want, rendered[2])
+               if !strings.Contains(rendered[4], want) {
+                       t.Fatalf("deployment did not render observability env 
%q:\n%s", want, rendered[4])
                }
        }
-       if !strings.Contains(rendered[2], "app.kubernetes.io/instance: 
public-dubbo") {
-               t.Fatalf("deployment did not render stable dxgate instance 
label:\n%s", rendered[2])
+       if !strings.Contains(rendered[4], "app.kubernetes.io/instance: 
public-dubbo") {
+               t.Fatalf("deployment did not render stable dxgate instance 
label:\n%s", rendered[4])
        }
-       if !strings.Contains(rendered[3], "targetPort: 15080") {
-               t.Fatalf("service did not target grpc-inbound port:\n%s", 
rendered[3])
+       if !strings.Contains(rendered[5], "targetPort: 15080") {
+               t.Fatalf("service did not target grpc-inbound port:\n%s", 
rendered[5])
        }
-       if !strings.Contains(rendered[3], `proxyless.dubbo.apache.org/inject: 
"false"`) {
-               t.Fatalf("service did not opt out of proxyless targetPort 
rewriting:\n%s", rendered[3])
+       if !strings.Contains(rendered[5], `proxyless.dubbo.apache.org/inject: 
"false"`) {
+               t.Fatalf("service did not opt out of proxyless targetPort 
rewriting:\n%s", rendered[5])
        }
-       if !strings.Contains(rendered[3], "app.kubernetes.io/instance: 
public-dubbo") {
-               t.Fatalf("service did not render stable dxgate instance 
selector:\n%s", rendered[3])
+       if !strings.Contains(rendered[5], "app.kubernetes.io/instance: 
public-dubbo") {
+               t.Fatalf("service did not render stable dxgate instance 
selector:\n%s", rendered[5])
        }
 
        input.OtelEndpoint = "http://tracing.dubbo-system.svc:4317";
@@ -759,9 +764,9 @@ func TestKubeGatewayTemplateRendersDxgateResources(t 
*testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       if !strings.Contains(rendered[2], "DXGATE_OTEL_ENDPOINT") ||
-               !strings.Contains(rendered[2], `value: 
"http://tracing.dubbo-system.svc:4317"`) {
-               t.Fatalf("deployment did not render dxgate OTEL endpoint:\n%s", 
rendered[2])
+       if !strings.Contains(rendered[4], "DXGATE_OTEL_ENDPOINT") ||
+               !strings.Contains(rendered[4], `value: 
"http://tracing.dubbo-system.svc:4317"`) {
+               t.Fatalf("deployment did not render dxgate OTEL endpoint:\n%s", 
rendered[4])
        }
 }
 
@@ -803,8 +808,8 @@ func 
TestKubeGatewayTemplateRendersHighAvailabilityResources(t *testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       if len(rendered) != 5 {
-               t.Fatalf("rendered %d resources, want 5 including the 
disruption budget", len(rendered))
+       if len(rendered) != 7 {
+               t.Fatalf("rendered %d resources, want 7 including credential 
RBAC and disruption budget", len(rendered))
        }
        joined := strings.Join(rendered, "\n---\n")
        for _, want := range []string{
@@ -825,8 +830,8 @@ func 
TestKubeGatewayTemplateRendersHighAvailabilityResources(t *testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       if len(rendered) != 4 {
-               t.Fatalf("rendered %d resources at one replica, want 4 without 
a disruption budget", len(rendered))
+       if len(rendered) != 6 {
+               t.Fatalf("rendered %d resources at one replica, want 6 without 
a disruption budget", len(rendered))
        }
        joined = strings.Join(rendered, "\n---\n")
        if strings.Contains(joined, "kind: PodDisruptionBudget") {
@@ -934,7 +939,7 @@ func TestKubeGatewayTemplateRendersActivationEnv(t 
*testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       deployment := rendered[2]
+       deployment := rendered[4]
        if !strings.Contains(deployment, "DXGATE_ACTIVATION_CONTROL_PLANE") ||
                !strings.Contains(deployment, 
"dubbod-activation-replicas.dubbo-system.svc.cluster.local:26030") {
                t.Fatalf("deployment did not render the activation control 
plane:\n%s", deployment)
@@ -954,7 +959,7 @@ func TestKubeGatewayTemplateRendersActivationEnv(t 
*testing.T) {
        if err != nil {
                t.Fatal(err)
        }
-       if strings.Contains(off[2], "DXGATE_ACTIVATION") {
-               t.Fatalf("activation env leaked into a gateway with activation 
disabled:\n%s", off[2])
+       if strings.Contains(off[4], "DXGATE_ACTIVATION") {
+               t.Fatalf("activation env leaked into a gateway with activation 
disabled:\n%s", off[4])
        }
 }
diff --git a/dubbod/discovery/pkg/model/push_context.go 
b/dubbod/discovery/pkg/model/push_context.go
index b9bf9a9c..72742d38 100644
--- a/dubbod/discovery/pkg/model/push_context.go
+++ b/dubbod/discovery/pkg/model/push_context.go
@@ -75,6 +75,7 @@ type PushContext struct {
        ServiceIndex           serviceIndex
        virtualServiceIndex    virtualServiceIndex
        httpRouteIndex         httpRouteIndex
+       dxgateServiceIndex     dxgateServiceIndex
        backendTLSPolicyIndex  backendTLSPolicyIndex
        faultInjectionIndex    faultInjectionPolicyIndex
        serviceActivationIndex serviceActivationPolicyIndex
@@ -165,6 +166,10 @@ type httpRouteIndex struct {
        hostToRoutes map[host.Name][]config.Config
 }
 
+type dxgateServiceIndex struct {
+       byNamespace map[string]map[string]config.Config
+}
+
 type backendTLSPolicyIndex struct {
        serviceTLS map[string]BackendTLSSettings
 }
@@ -193,6 +198,7 @@ func NewPushContext() *PushContext {
        return &PushContext{
                ServiceIndex:          newServiceIndex(),
                virtualServiceIndex:   newVirtualServiceIndex(),
+               dxgateServiceIndex:    dxgateServiceIndex{byNamespace: 
map[string]map[string]config.Config{}},
                backendTLSPolicyIndex: backendTLSPolicyIndex{serviceTLS: 
map[string]BackendTLSSettings{}},
                serviceActivationIndex: serviceActivationPolicyIndex{
                        services: map[string][]string{},
@@ -628,6 +634,7 @@ func (ps *PushContext) createNewContext(env *Environment) {
        // Initialize Kubernetes Gateway API resources if the controller is 
enabled.
        ps.initKubernetesGateways(env)
        ps.initHTTPRoutes(env)
+       ps.initDxgateServices(env)
        ps.initBackendTLSPolicies(env)
        ps.initFaultInjectionPolicies(env)
        ps.initServiceActivationPolicies(env)
@@ -687,6 +694,13 @@ func (ps *PushContext) updateContext(env *Environment, 
oldPushContext *PushConte
                ps.httpRouteIndex = oldPushContext.httpRouteIndex
        }
 
+       dxgateServicesChanged := pushReq != nil && 
HasConfigsOfKind(pushReq.ConfigsUpdated, kind.DxgateService)
+       if dxgateServicesChanged {
+               ps.initDxgateServices(env)
+       } else {
+               ps.dxgateServiceIndex = oldPushContext.dxgateServiceIndex
+       }
+
        backendTLSPoliciesChanged := pushReq != nil && 
HasConfigsOfKind(pushReq.ConfigsUpdated, kind.BackendTLSPolicy)
        if backendTLSPoliciesChanged {
                log.Debugf("BackendTLSPolicies changed, re-initializing 
BackendTLSPolicy index")
@@ -866,6 +880,31 @@ func (ps *PushContext) initHTTPRoutes(env *Environment) {
        }
 }
 
+func (ps *PushContext) initDxgateServices(env *Environment) {
+       services := sortConfigByCreationTime(env.List(gvk.DxgateService, 
NamespaceAll))
+       index := make(map[string]map[string]config.Config)
+       for _, service := range services {
+               if index[service.Namespace] == nil {
+                       index[service.Namespace] = 
make(map[string]config.Config)
+               }
+               index[service.Namespace][service.Name] = service
+       }
+       ps.dxgateServiceIndex = dxgateServiceIndex{byNamespace: index}
+}
+
+// DxgateService resolves the mesh-native backend named by an HTTPRoute.
+func (ps *PushContext) DxgateService(namespace, name string) (config.Config, 
bool) {
+       if ps == nil {
+               return config.Config{}, false
+       }
+       services := ps.dxgateServiceIndex.byNamespace[namespace]
+       if services == nil {
+               return config.Config{}, false
+       }
+       service, found := services[name]
+       return service, found
+}
+
 // HTTPRouteForHost returns HTTPRoutes that match the given hostname.
 func (ps *PushContext) HTTPRouteForHost(hostname host.Name) []config.Config {
        var routes []config.Config
diff --git a/dubbod/discovery/pkg/networking/grpcgen/agent_config.go 
b/dubbod/discovery/pkg/networking/grpcgen/agent_config.go
new file mode 100644
index 00000000..86847fd3
--- /dev/null
+++ b/dubbod/discovery/pkg/networking/grpcgen/agent_config.go
@@ -0,0 +1,430 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 grpcgen
+
+import (
+       "fmt"
+       "sort"
+       "strings"
+
+       "github.com/apache/dubbo-kubernetes/dubbod/discovery/pkg/model"
+       "github.com/apache/dubbo-kubernetes/pkg/config"
+       networking "github.com/kdubbo/api/networking/v1alpha3"
+       route "github.com/kdubbo/xds-api/route/v1"
+       gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+const (
+       dxgateServiceGroup = "networking.dubbo.apache.org"
+       dxgateServiceKind  = "DxgateService"
+)
+
+func isDxgateServiceBackend(ref gatewayv1.HTTPBackendRef) bool {
+       group := ""
+       if ref.Group != nil {
+               group = string(*ref.Group)
+       }
+       kind := "Service"
+       if ref.Kind != nil {
+               kind = string(*ref.Kind)
+       }
+       return group == dxgateServiceGroup && kind == dxgateServiceKind
+}
+
+func ruleUsesDxgateService(rule gatewayv1.HTTPRouteRule) bool {
+       for _, ref := range rule.BackendRefs {
+               if isDxgateServiceBackend(ref) {
+                       return true
+               }
+       }
+       return false
+}
+
+func buildAgentConfig(push *model.PushContext, httpRoutes []config.Config) 
*route.AgentConfig {
+       if push == nil {
+               return nil
+       }
+       out := &route.AgentConfig{}
+       providers := map[string]bool{}
+       backends := map[string]bool{}
+       policies := map[string]bool{}
+
+       for _, routeConfig := range httpRoutes {
+               spec, ok := routeConfig.Spec.(*gatewayv1.HTTPRouteSpec)
+               if !ok {
+                       continue
+               }
+               for ruleIndex, rule := range spec.Rules {
+                       if !ruleUsesDxgateService(rule) {
+                               continue
+                       }
+                       if len(rule.BackendRefs) == 0 {
+                               continue
+                       }
+
+                       var protocol route.AgentProtocol
+                       var weighted []*route.WeightedBackend
+                       valid := true
+                       for _, ref := range rule.BackendRefs {
+                               if !isDxgateServiceBackend(ref) {
+                                       log.Warnf("HTTPRoute %s/%s rule[%d] 
mixes Service and DxgateService backends", routeConfig.Namespace, 
routeConfig.Name, ruleIndex)
+                                       valid = false
+                                       break
+                               }
+                               namespace := routeConfig.Namespace
+                               if ref.Namespace != nil {
+                                       namespace = string(*ref.Namespace)
+                               }
+                               if namespace != routeConfig.Namespace {
+                                       log.Warnf("HTTPRoute %s/%s rule[%d] 
cross-namespace DxgateService reference %s/%s is not supported", 
routeConfig.Namespace, routeConfig.Name, ruleIndex, namespace, ref.Name)
+                                       valid = false
+                                       break
+                               }
+                               serviceConfig, found := 
push.DxgateService(namespace, string(ref.Name))
+                               if !found {
+                                       log.Warnf("HTTPRoute %s/%s rule[%d] 
references missing DxgateService %s/%s", routeConfig.Namespace, 
routeConfig.Name, ruleIndex, namespace, ref.Name)
+                                       valid = false
+                                       break
+                               }
+                               service, ok := 
serviceConfig.Spec.(*networking.DxgateService)
+                               if !ok {
+                                       valid = false
+                                       break
+                               }
+                               compiled, serviceProtocol, err := 
compileDxgateService(serviceConfig, service)
+                               if err != nil {
+                                       log.Warnf("DxgateService %s/%s cannot 
be compiled: %v", namespace, ref.Name, err)
+                                       valid = false
+                                       break
+                               }
+                               if protocol != 
route.AgentProtocol_AGENT_PROTOCOL_UNSPECIFIED && protocol != serviceProtocol {
+                                       log.Warnf("HTTPRoute %s/%s rule[%d] 
mixes DxgateService protocols", routeConfig.Namespace, routeConfig.Name, 
ruleIndex)
+                                       valid = false
+                                       break
+                               }
+                               protocol = serviceProtocol
+                               if compiled.provider != nil && 
!providers[compiled.provider.Name] {
+                                       out.Providers = append(out.Providers, 
compiled.provider)
+                                       providers[compiled.provider.Name] = true
+                               }
+                               if compiled.policy != nil && 
!policies[compiled.policy.Name] {
+                                       out.Policies = append(out.Policies, 
compiled.policy)
+                                       policies[compiled.policy.Name] = true
+                               }
+                               weight := uint32(1)
+                               if ref.Weight != nil && *ref.Weight > 0 {
+                                       weight = uint32(*ref.Weight)
+                               }
+                               for _, backend := range compiled.backends {
+                                       if !backends[backend.Name] {
+                                               out.Backends = 
append(out.Backends, backend)
+                                               backends[backend.Name] = true
+                                       }
+                                       weighted = append(weighted, 
&route.WeightedBackend{Name: backend.Name, Weight: weight})
+                               }
+                       }
+                       if !valid || len(weighted) == 0 {
+                               continue
+                       }
+                       out.AgentRoutes = append(out.AgentRoutes, 
&route.AgentRoute{
+                               Name:             fmt.Sprintf("%s/%s/%d", 
routeConfig.Namespace, routeConfig.Name, ruleIndex),
+                               Protocol:         protocol,
+                               Matches:          
compileAgentMatches(spec.Hostnames, rule.Matches),
+                               WeightedBackends: weighted,
+                               Policies:         policyNames(weighted, 
out.Backends),
+                               Rewrite:          
compileAgentRewrite(rule.Filters),
+                       })
+               }
+       }
+       if len(out.AgentRoutes) == 0 {
+               return nil
+       }
+       sort.Slice(out.Providers, func(i, j int) bool { return 
out.Providers[i].Name < out.Providers[j].Name })
+       sort.Slice(out.Backends, func(i, j int) bool { return 
out.Backends[i].Name < out.Backends[j].Name })
+       sort.Slice(out.Policies, func(i, j int) bool { return 
out.Policies[i].Name < out.Policies[j].Name })
+       sort.Slice(out.AgentRoutes, func(i, j int) bool { return 
out.AgentRoutes[i].Name < out.AgentRoutes[j].Name })
+       return out
+}
+
+type compiledDxgateService struct {
+       provider *route.AgentProvider
+       backends []*route.AgentBackend
+       policy   *route.AgentPolicy
+}
+
+func compileDxgateService(cfg config.Config, service 
*networking.DxgateService) (compiledDxgateService, route.AgentProtocol, error) {
+       prefix := fmt.Sprintf("%s.%s", cfg.Name, cfg.Namespace)
+       policyName := prefix + ".policy"
+       policy := compileAgentPolicy(policyName, cfg.Namespace, 
service.GetPolicies())
+       policyRefs := []string(nil)
+       if policy != nil {
+               policyRefs = []string{policyName}
+       }
+
+       switch {
+       case service.GetAi() != nil:
+               ai := service.GetAi()
+               if ai.GetProvider() == nil {
+                       return compiledDxgateService{}, 0, fmt.Errorf("provider 
is not set")
+               }
+               providerName := prefix + ".provider"
+               provider := &route.AgentProvider{
+                       Name:           providerName,
+                       BaseUrl:        ai.GetEndpoint(),
+                       Credential:     compileSecretReference(cfg.Namespace, 
ai.GetProvider().GetCredential()),
+                       RequestHeaders: 
compileHeaderValues(ai.GetProvider().GetRequestHeaders()),
+               }
+               switch {
+               case ai.GetProvider().GetOpenai() != nil:
+                       provider.Kind = route.AgentProviderKind_OPENAI
+               case ai.GetProvider().GetAnthropic() != nil:
+                       provider.Kind = route.AgentProviderKind_ANTHROPIC
+               default:
+                       return compiledDxgateService{}, 0, fmt.Errorf("provider 
is not set")
+               }
+               backend := &route.AgentBackend{
+                       Name: prefix,
+                       Backend: &route.AgentBackend_Llm{Llm: &route.LLMBackend{
+                               Provider:      providerName,
+                               Models:        append([]string(nil), 
ai.GetModels()...),
+                               ModelRewrites: 
cloneStringMap(ai.GetModelRewrites()),
+                       }},
+                       Policies: policyRefs,
+               }
+               return compiledDxgateService{provider: provider, backends: 
[]*route.AgentBackend{backend}, policy: policy}, route.AgentProtocol_LLM, nil
+       case service.GetMcp() != nil:
+               backends := make([]*route.AgentBackend, 0, 
len(service.GetMcp().GetTargets()))
+               for _, target := range service.GetMcp().GetTargets() {
+                       static := target.GetStatic()
+                       if static == nil || static.GetBackendRef() == nil {
+                               continue
+                       }
+                       namespace := static.GetBackendRef().GetNamespace()
+                       if namespace == "" {
+                               namespace = cfg.Namespace
+                       }
+                       backends = append(backends, &route.AgentBackend{
+                               Name: fmt.Sprintf("%s.%s", prefix, 
target.GetName()),
+                               Backend: &route.AgentBackend_Mcp{Mcp: 
&route.MCPBackend{
+                                       Endpoint: 
serviceEndpoint(static.GetBackendRef().GetName(), namespace, static.GetPort(), 
static.GetPath()),
+                                       Tools:    append([]string(nil), 
target.GetTools()...),
+                               }},
+                               Policies: policyRefs,
+                       })
+               }
+               return compiledDxgateService{backends: backends, policy: 
policy}, route.AgentProtocol_MCP, nil
+       case service.GetA2A() != nil:
+               a2a := service.GetA2A()
+               endpoint := ""
+               if ref := a2a.GetBackendRef(); ref != nil {
+                       namespace := ref.GetNamespace()
+                       if namespace == "" {
+                               namespace = cfg.Namespace
+                       }
+                       endpoint = serviceEndpoint(ref.GetName(), namespace, 
a2a.GetPort(), a2a.GetPath())
+               } else {
+                       endpoint = hostEndpoint(a2a.GetHost(), a2a.GetPort(), 
a2a.GetPath())
+               }
+               backend := &route.AgentBackend{
+                       Name: prefix,
+                       Backend: &route.AgentBackend_A2A{A2A: &route.A2ABackend{
+                               Endpoint: endpoint,
+                               Agent:    a2a.GetAgent(),
+                       }},
+                       Policies: policyRefs,
+               }
+               return compiledDxgateService{backends: 
[]*route.AgentBackend{backend}, policy: policy}, route.AgentProtocol_A2A, nil
+       default:
+               return compiledDxgateService{}, 0, fmt.Errorf("service type is 
not set")
+       }
+}
+
+func serviceEndpoint(name, namespace string, port uint32, path string) string {
+       return hostEndpoint(fmt.Sprintf("%s.%s.svc.cluster.local", name, 
namespace), port, path)
+}
+
+func hostEndpoint(host string, port uint32, path string) string {
+       return fmt.Sprintf("http://%s:%d%s";, host, port, 
normalizeEndpointPath(path))
+}
+
+func normalizeEndpointPath(path string) string {
+       if path == "" || path == "/" {
+               return ""
+       }
+       return "/" + strings.Trim(path, "/")
+}
+
+func compileAgentMatches(hostnames []gatewayv1.Hostname, matches 
[]gatewayv1.HTTPRouteMatch) []*route.AgentRouteMatch {
+       hosts := make([]string, 0, len(hostnames))
+       for _, hostname := range hostnames {
+               hosts = append(hosts, string(hostname))
+       }
+       if len(hosts) == 0 {
+               hosts = []string{""}
+       }
+       if len(matches) == 0 {
+               matches = []gatewayv1.HTTPRouteMatch{{}}
+       }
+       out := make([]*route.AgentRouteMatch, 0, len(hosts)*len(matches))
+       for _, hostname := range hosts {
+               for _, match := range matches {
+                       compiled := &route.AgentRouteMatch{
+                               Host:    hostname,
+                               Path:    compileAgentPathMatch(match.Path),
+                               Headers: make([]*route.AgentHeaderMatch, 0, 
len(match.Headers)),
+                       }
+                       if match.Method != nil {
+                               compiled.Method = string(*match.Method)
+                       }
+                       for _, header := range match.Headers {
+                               compiled.Headers = append(compiled.Headers, 
&route.AgentHeaderMatch{
+                                       Name: string(header.Name), Value: 
header.Value,
+                               })
+                       }
+                       out = append(out, compiled)
+               }
+       }
+       return out
+}
+
+func compileAgentPathMatch(match *gatewayv1.HTTPPathMatch) 
*route.AgentPathMatch {
+       if match == nil || match.Value == nil {
+               return &route.AgentPathMatch{Match: 
&route.AgentPathMatch_Prefix{Prefix: "/"}}
+       }
+       if match.Type != nil && *match.Type == gatewayv1.PathMatchExact {
+               return &route.AgentPathMatch{Match: 
&route.AgentPathMatch_Exact{Exact: *match.Value}}
+       }
+       return &route.AgentPathMatch{Match: 
&route.AgentPathMatch_Prefix{Prefix: *match.Value}}
+}
+
+func compileAgentRewrite(filters []gatewayv1.HTTPRouteFilter) 
*route.PathRewrite {
+       for _, filter := range filters {
+               if filter.Type != gatewayv1.HTTPRouteFilterURLRewrite || 
filter.URLRewrite == nil || filter.URLRewrite.Path == nil {
+                       continue
+               }
+               if filter.URLRewrite.Path.Type == 
gatewayv1.PrefixMatchHTTPPathModifier && 
filter.URLRewrite.Path.ReplacePrefixMatch != nil {
+                       return &route.PathRewrite{ReplacePrefixMatch: 
*filter.URLRewrite.Path.ReplacePrefixMatch}
+               }
+       }
+       return nil
+}
+
+func compileAgentPolicy(name, namespace string, in 
*networking.DxgateServicePolicies) *route.AgentPolicy {
+       if in == nil {
+               return nil
+       }
+       out := &route.AgentPolicy{
+               Name:            name,
+               Timeout:         in.GetTimeout(),
+               MaxBodyBytes:    uint64(max(in.GetMaxBodyBytes(), 0)),
+               RequestHeaders:  compileHeaderTransform(in.GetRequestHeaders()),
+               ResponseHeaders: 
compileHeaderTransform(in.GetResponseHeaders()),
+       }
+       if auth := in.GetAuth(); auth != nil {
+               out.Auth = &route.ClientAuthPolicy{
+                       Header:    auth.GetHeader(),
+                       SecretRef: compileSecretReference(namespace, 
auth.GetSecretRef()),
+               }
+       }
+       if limit := in.GetRateLimit(); limit != nil {
+               out.RateLimit = &route.AgentRateLimitPolicy{
+                       Requests: limit.GetRequests(), Window: 
limit.GetWindow(),
+                       Key: compileRateLimitKey(limit.GetKey()), Header: 
limit.GetHeader(),
+               }
+       }
+       if limit := in.GetTokenLimit(); limit != nil {
+               out.TokenLimit = &route.AgentTokenLimitPolicy{
+                       Tokens: limit.GetTokens(), Window: limit.GetWindow(),
+                       Key: compileRateLimitKey(limit.GetKey()), Header: 
limit.GetHeader(),
+               }
+       }
+       if retry := in.GetRetry(); retry != nil {
+               out.Retry = &route.AgentRetryPolicy{
+                       Attempts: retry.GetAttempts(), StatusCodes: 
append([]uint32(nil), retry.GetStatusCodes()...),
+               }
+       }
+       return out
+}
+
+func compileSecretReference(namespace string, ref 
*networking.SecretKeyReference) *route.SecretKeyReference {
+       if ref == nil {
+               return nil
+       }
+       return &route.SecretKeyReference{Namespace: namespace, Name: 
ref.GetName(), Key: ref.GetKey()}
+}
+
+func compileHeaderTransform(in *networking.HeaderTransform) 
*route.AgentHeaderTransform {
+       if in == nil {
+               return nil
+       }
+       return &route.AgentHeaderTransform{
+               Add:    compileHeaderValues(in.GetAdd()),
+               Remove: append([]string(nil), in.GetRemove()...),
+       }
+}
+
+func compileHeaderValues(in []*networking.HeaderValue) 
[]*route.AgentHeaderValue {
+       out := make([]*route.AgentHeaderValue, 0, len(in))
+       for _, value := range in {
+               if value != nil {
+                       out = append(out, &route.AgentHeaderValue{Name: 
value.GetName(), Value: value.GetValue()})
+               }
+       }
+       return out
+}
+
+func compileRateLimitKey(in networking.RateLimitKey) route.AgentRateLimitKey {
+       switch in {
+       case networking.RateLimitKey_BACKEND:
+               return route.AgentRateLimitKey_BACKEND
+       case networking.RateLimitKey_HEADER:
+               return route.AgentRateLimitKey_HEADER
+       default:
+               return route.AgentRateLimitKey_ROUTE
+       }
+}
+
+func policyNames(weighted []*route.WeightedBackend, backends 
[]*route.AgentBackend) []string {
+       seen := map[string]bool{}
+       var out []string
+       for _, selected := range weighted {
+               for _, backend := range backends {
+                       if backend.Name != selected.Name {
+                               continue
+                       }
+                       for _, policy := range backend.Policies {
+                               if !seen[policy] {
+                                       out = append(out, policy)
+                                       seen[policy] = true
+                               }
+                       }
+               }
+       }
+       sort.Strings(out)
+       return out
+}
+
+func cloneStringMap(in map[string]string) map[string]string {
+       if len(in) == 0 {
+               return nil
+       }
+       out := make(map[string]string, len(in))
+       for key, value := range in {
+               out[key] = value
+       }
+       return out
+}
diff --git a/dubbod/discovery/pkg/networking/grpcgen/rds.go 
b/dubbod/discovery/pkg/networking/grpcgen/rds.go
index 7a7df64e..9a718dd8 100644
--- a/dubbod/discovery/pkg/networking/grpcgen/rds.go
+++ b/dubbod/discovery/pkg/networking/grpcgen/rds.go
@@ -241,6 +241,7 @@ func buildHTTPRoute(node *model.Proxy, push 
*model.PushContext, routeName string
 
                // Filter HTTPRoutes by parentRef to match this Gateway
                httpRoutes := filterHTTPRoutesByGateway(allHTTPRoutes, 
gatewayName, gatewayNamespace, gatewayListenerPort)
+               agentConfig := buildAgentConfig(push, httpRoutes)
                log.Debugf("Gateway Pod inbound listener, filtered to %d 
HTTPRoute(s) matching gateway %s/%s listener port %d", len(httpRoutes), 
gatewayNamespace, gatewayName, gatewayListenerPort)
 
                // For Gateway Pod, we also need to collect HTTPRoutes with 
specific hostnames
@@ -313,6 +314,7 @@ func buildHTTPRoute(node *model.Proxy, push 
*model.PushContext, routeName string
                return &route.RouteConfiguration{
                        Name:         routeName,
                        VirtualHosts: virtualHosts,
+                       AgentConfig:  agentConfig,
                }
        }
 
@@ -429,6 +431,13 @@ func buildRoutesFromGatewayHTTPRoute(httpRoutes 
[]config.Config, hostName host.N
                                log.Debugf("HTTPRoute %s/%s rule[%d] has no 
backendRefs, skipping", hrConfig.Namespace, hrConfig.Name, ruleIdx)
                                continue
                        }
+                       if ruleUsesDxgateService(rule) {
+                               // Mesh-native LLM/MCP/A2A backends are carried 
in AgentConfig.
+                               // Emitting them as ordinary clusters would 
fabricate a
+                               // Kubernetes Service with the same name and 
bypass protocol
+                               // translation.
+                               continue
+                       }
 
                        // Build weighted clusters from backendRefs
                        weights := make([]*route.WeightedCluster_ClusterWeight, 
0, len(rule.BackendRefs))
@@ -483,9 +492,6 @@ func buildRoutesFromGatewayHTTPRoute(httpRoutes 
[]config.Config, hostName host.N
                                weightedClusters.TotalWeight = 
wrapperspb.UInt32(totalWeight)
                        }
 
-                       // Build route match from HTTPRoute matches
-                       routeMatch := 
buildRouteMatchFromHTTPRouteMatches(rule.Matches)
-
                        routeAction := &route.RouteAction{
                                ClusterSpecifier: 
&route.RouteAction_WeightedClusters{
                                        WeightedClusters: weightedClusters,
@@ -499,16 +505,18 @@ func buildRoutesFromGatewayHTTPRoute(httpRoutes 
[]config.Config, hostName host.N
                        routeAction.RetryPolicy = 
gatewayAPIRetryPolicy(rule.Retry, rule.Timeouts)
                        routeAction.FaultPolicy = faultPolicy
 
-                       builtRoute := &route.Route{
-                               Match: routeMatch,
-                               Action: &route.Route_Route{
-                                       Route: routeAction,
-                               },
+                       routeMatches := 
buildRouteMatchesFromHTTPRouteMatches(rule.Matches)
+                       for _, routeMatch := range routeMatches {
+                               allRoutes = append(allRoutes, &route.Route{
+                                       Match: routeMatch,
+                                       Action: &route.Route_Route{
+                                               Route: routeAction,
+                                       },
+                               })
                        }
 
-                       log.Infof("HTTPRoute %s/%s rule[%d] -> built route with 
%d clusters, totalWeight=%d",
-                               hrConfig.Namespace, hrConfig.Name, ruleIdx, 
len(weights), totalWeight)
-                       allRoutes = append(allRoutes, builtRoute)
+                       log.Infof("HTTPRoute %s/%s rule[%d] -> built %d routes 
with %d clusters, totalWeight=%d",
+                               hrConfig.Namespace, hrConfig.Name, ruleIdx, 
len(routeMatches), len(weights), totalWeight)
                }
        }
 
@@ -701,19 +709,21 @@ func isServiceParentRef(parentRef 
sigsk8siogatewayapiapisv1.ParentReference) boo
        return parentRef.Group == nil || string(*parentRef.Group) == ""
 }
 
-// buildRouteMatchFromHTTPRouteMatches converts Gateway API HTTPRouteMatch to 
XDS RouteMatch
-func buildRouteMatchFromHTTPRouteMatches(matches 
[]sigsk8siogatewayapiapisv1.HTTPRouteMatch) *route.RouteMatch {
+// buildRouteMatchesFromHTTPRouteMatches preserves Gateway API OR semantics:
+// every HTTPRouteMatch in a rule becomes an independent xDS route.
+func buildRouteMatchesFromHTTPRouteMatches(matches 
[]sigsk8siogatewayapiapisv1.HTTPRouteMatch) []*route.RouteMatch {
        if len(matches) == 0 {
-               // No matches means match all
-               return &route.RouteMatch{
-                       PathSpecifier: &route.RouteMatch_Prefix{
-                               Prefix: "/",
-                       },
-               }
+               matches = []sigsk8siogatewayapiapisv1.HTTPRouteMatch{{}}
        }
 
-       // For now, we'll use the first match. In a full implementation, we 
might need to merge multiple matches.
-       match := matches[0]
+       out := make([]*route.RouteMatch, 0, len(matches))
+       for _, match := range matches {
+               out = append(out, buildRouteMatchFromHTTPRouteMatch(match))
+       }
+       return out
+}
+
+func buildRouteMatchFromHTTPRouteMatch(match 
sigsk8siogatewayapiapisv1.HTTPRouteMatch) *route.RouteMatch {
        routeMatch := &route.RouteMatch{}
 
        // Handle path match
diff --git a/dubbod/discovery/pkg/networking/grpcgen/rds_test.go 
b/dubbod/discovery/pkg/networking/grpcgen/rds_test.go
index d2b035c9..babdd1dd 100644
--- a/dubbod/discovery/pkg/networking/grpcgen/rds_test.go
+++ b/dubbod/discovery/pkg/networking/grpcgen/rds_test.go
@@ -17,6 +17,7 @@ package grpcgen
 
 import (
        "reflect"
+       "strings"
        "testing"
        "time"
 
@@ -37,6 +38,26 @@ import (
        gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 )
 
+func TestBuildRouteMatchesPreservesHTTPRouteORSemantics(t *testing.T) {
+       pathType := gatewayv1.PathMatchPathPrefix
+       users := "/users"
+       orders := "/orders"
+       matches := 
buildRouteMatchesFromHTTPRouteMatches([]gatewayv1.HTTPRouteMatch{
+               {Path: &gatewayv1.HTTPPathMatch{Type: &pathType, Value: 
&users}},
+               {Path: &gatewayv1.HTTPPathMatch{Type: &pathType, Value: 
&orders}},
+       })
+
+       if len(matches) != 2 {
+               t.Fatalf("matches = %d, want 2 independent xDS routes", 
len(matches))
+       }
+       if got := matches[0].GetPrefix(); got != users {
+               t.Fatalf("first prefix = %q, want %q", got, users)
+       }
+       if got := matches[1].GetPrefix(); got != orders {
+               t.Fatalf("second prefix = %q, want %q", got, orders)
+       }
+}
+
 func TestBuildHTTPRouteProxylessOutboundIgnoresGatewayAttachedHTTPRoute(t 
*testing.T) {
        push := newRDSTestPushContext(t, []config.Config{
                newWildcardHTTPRouteConfig("httpbin", "default", 8000),
@@ -335,6 +356,108 @@ func 
TestGatewayInboundTargetPortIncludesActivationRoutes(t *testing.T) {
        t.Fatalf("activation virtual host not found on Gateway targetPort: %v", 
rc.GetVirtualHosts())
 }
 
+func TestBuildAgentConfigCompilesDxgateServiceHTTPRoute(t *testing.T) {
+       routeConfig := newDxgateHTTPRouteConfig("anthropic", "app", 
"/anthropic", "/v1/chat/completions")
+       serviceConfig := config.Config{
+               Meta: config.Meta{
+                       GroupVersionKind: gvk.DxgateService,
+                       Name:             "anthropic",
+                       Namespace:        "app",
+               },
+               Spec: &networking.DxgateService{
+                       Service: &networking.DxgateService_Ai{Ai: 
&networking.AIService{
+                               Provider: &networking.AIProvider{
+                                       Provider: 
&networking.AIProvider_Anthropic{Anthropic: &networking.AnthropicProvider{
+                                               Model: "claude-test",
+                                       }},
+                                       Credential: 
&networking.SecretKeyReference{Name: "anthropic", Key: "api-key"},
+                               },
+                               Models:   []string{"claude-test"},
+                               Endpoint: "http://anthropic-mock.app.svc:8080";,
+                       }},
+                       Policies: &networking.DxgateServicePolicies{
+                               Timeout: durationpb.New(5 * time.Second),
+                               Retry:   &networking.RetryPolicy{Attempts: 2, 
StatusCodes: []uint32{503}},
+                       },
+               },
+       }
+       push := newRDSTestPushContext(t, []config.Config{routeConfig, 
serviceConfig}, nil)
+
+       got := buildAgentConfig(push, []config.Config{routeConfig})
+       if got == nil {
+               t.Fatal("AgentConfig = nil")
+       }
+       if len(got.GetProviders()) != 1 || got.GetProviders()[0].GetKind() != 
route.AgentProviderKind_ANTHROPIC {
+               t.Fatalf("providers = %v, want one Anthropic provider", 
got.GetProviders())
+       }
+       if credential := got.GetProviders()[0].GetCredential(); 
credential.GetNamespace() != "app" ||
+               credential.GetName() != "anthropic" || credential.GetKey() != 
"api-key" {
+               t.Fatalf("credential = %v", credential)
+       }
+       if len(got.GetBackends()) != 1 || got.GetBackends()[0].GetLlm() == nil {
+               t.Fatalf("backends = %v, want one LLM backend", 
got.GetBackends())
+       }
+       if len(got.GetAgentRoutes()) != 1 {
+               t.Fatalf("agent routes = %d, want 1", len(got.GetAgentRoutes()))
+       }
+       agentRoute := got.GetAgentRoutes()[0]
+       if agentRoute.GetProtocol() != route.AgentProtocol_LLM {
+               t.Fatalf("protocol = %v, want LLM", agentRoute.GetProtocol())
+       }
+       if got := agentRoute.GetMatches()[0].GetPath().GetPrefix(); got != 
"/anthropic" {
+               t.Fatalf("path prefix = %q, want /anthropic", got)
+       }
+       if got := agentRoute.GetRewrite().GetReplacePrefixMatch(); got != 
"/v1/chat/completions" {
+               t.Fatalf("rewrite prefix = %q, want /v1/chat/completions", got)
+       }
+       if got := got.GetPolicies()[0].GetRetry().GetAttempts(); got != 2 {
+               t.Fatalf("retry attempts = %d, want 2", got)
+       }
+}
+
+func TestBuildAgentConfigExpandsMCPFederationTargets(t *testing.T) {
+       routeConfig := newDxgateHTTPRouteConfig("company-tools", "app", "/mcp", 
"")
+       serviceConfig := config.Config{
+               Meta: config.Meta{
+                       GroupVersionKind: gvk.DxgateService,
+                       Name:             "company-tools",
+                       Namespace:        "app",
+               },
+               Spec: &networking.DxgateService{
+                       Service: &networking.DxgateService_Mcp{Mcp: 
&networking.MCPService{
+                               Targets: []*networking.MCPTarget{
+                                       {
+                                               Name: "orders",
+                                               Static: 
&networking.StaticBackend{
+                                                       BackendRef: 
&networking.BackendReference{Name: "orders-tools"},
+                                                       Port:       8080,
+                                               },
+                                       },
+                                       {
+                                               Name: "tickets",
+                                               Static: 
&networking.StaticBackend{
+                                                       BackendRef: 
&networking.BackendReference{Name: "tickets-tools"},
+                                                       Port:       8081,
+                                               },
+                                       },
+                               },
+                       }},
+               },
+       }
+       push := newRDSTestPushContext(t, []config.Config{routeConfig, 
serviceConfig}, nil)
+
+       got := buildAgentConfig(push, []config.Config{routeConfig})
+       if got == nil || len(got.GetBackends()) != 2 {
+               t.Fatalf("backends = %v, want 2 MCP targets", got.GetBackends())
+       }
+       if len(got.GetAgentRoutes()) != 1 || 
len(got.GetAgentRoutes()[0].GetWeightedBackends()) != 2 {
+               t.Fatalf("agent routes = %v, want one federated route", 
got.GetAgentRoutes())
+       }
+       if endpoint := got.GetBackends()[0].GetMcp().GetEndpoint(); 
!strings.Contains(endpoint, ".app.svc.cluster.local:") {
+               t.Fatalf("MCP endpoint = %q, want in-cluster Service DNS", 
endpoint)
+       }
+}
+
 func newRDSTestPushContext(t *testing.T, configs []config.Config, services 
[]*model.Service) *model.PushContext {
        t.Helper()
 
@@ -358,6 +481,53 @@ func newRDSTestPushContext(t *testing.T, configs 
[]config.Config, services []*mo
        return push
 }
 
+func newDxgateHTTPRouteConfig(backendName, namespace, path, rewrite string) 
config.Config {
+       group := gatewayv1.Group(dxgateServiceGroup)
+       kind := gatewayv1.Kind(dxgateServiceKind)
+       pathType := gatewayv1.PathMatchPathPrefix
+       method := gatewayv1.HTTPMethodPost
+       weight := int32(100)
+       rule := gatewayv1.HTTPRouteRule{
+               Matches: []gatewayv1.HTTPRouteMatch{{
+                       Path:   &gatewayv1.HTTPPathMatch{Type: &pathType, 
Value: &path},
+                       Method: &method,
+               }},
+               BackendRefs: []gatewayv1.HTTPBackendRef{{
+                       BackendRef: gatewayv1.BackendRef{
+                               BackendObjectReference: 
gatewayv1.BackendObjectReference{
+                                       Group: &group,
+                                       Kind:  &kind,
+                                       Name:  
gatewayv1.ObjectName(backendName),
+                               },
+                               Weight: &weight,
+                       },
+               }},
+       }
+       if rewrite != "" {
+               modifier := gatewayv1.PrefixMatchHTTPPathModifier
+               rule.Filters = []gatewayv1.HTTPRouteFilter{{
+                       Type: gatewayv1.HTTPRouteFilterURLRewrite,
+                       URLRewrite: &gatewayv1.HTTPURLRewriteFilter{
+                               Path: &gatewayv1.HTTPPathModifier{
+                                       Type:               modifier,
+                                       ReplacePrefixMatch: &rewrite,
+                               },
+                       },
+               }}
+       }
+       return config.Config{
+               Meta: config.Meta{
+                       GroupVersionKind: gvk.HTTPRoute,
+                       Name:             backendName,
+                       Namespace:        namespace,
+                       Domain:           "cluster.local",
+               },
+               Spec: &gatewayv1.HTTPRouteSpec{
+                       Rules: []gatewayv1.HTTPRouteRule{rule},
+               },
+       }
+}
+
 func newWildcardHTTPRouteConfig(backendName, backendNamespace string, 
backendPort int32) config.Config {
        port := backendPort
        weight := int32(1)
diff --git a/go.mod b/go.mod
index ad044fb3..d44a074f 100644
--- a/go.mod
+++ b/go.mod
@@ -52,9 +52,9 @@ require (
        github.com/hashicorp/go-multierror v1.1.1
        github.com/hashicorp/golang-lru/v2 v2.0.7
        github.com/heroku/color v0.0.6
-       github.com/kdubbo/api v0.0.0-20260808071541-7709b6f3f491
-       github.com/kdubbo/client-go v0.0.0-20260807013041-7abbf3125711
-       github.com/kdubbo/xds-api v0.0.0-20260808063945-44451fbf2da1
+       github.com/kdubbo/api v0.0.0-20260808154749-d91601e19406
+       github.com/kdubbo/client-go v0.0.0-20260808154904-3acbf91635a1
+       github.com/kdubbo/xds-api v0.0.0-20260808155041-b6b8371fcc89
        github.com/moby/moby/client v0.4.1
        github.com/moby/term v0.5.2
        github.com/ory/viper v1.7.5
diff --git a/go.sum b/go.sum
index 1c07d989..5e956c1c 100644
--- a/go.sum
+++ b/go.sum
@@ -368,12 +368,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod 
h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
 github.com/julienschmidt/httprouter v1.2.0/go.mod 
h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 
h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod 
h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
-github.com/kdubbo/api v0.0.0-20260808071541-7709b6f3f491 
h1:KukdrR5lEbLUqh45vTPJnGLTusT9dCoro/LkiSvF+dk=
-github.com/kdubbo/api v0.0.0-20260808071541-7709b6f3f491/go.mod 
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
-github.com/kdubbo/client-go v0.0.0-20260807013041-7abbf3125711 
h1:ofQz1xUC5DZyh+3lVPIFdipjKxe6jQ9uQbNMYoLInaI=
-github.com/kdubbo/client-go v0.0.0-20260807013041-7abbf3125711/go.mod 
h1:ogHgHroSROD3HwYPBKS8c2dpcfd4cZxpwZrzTSCXjXs=
-github.com/kdubbo/xds-api v0.0.0-20260808063945-44451fbf2da1 
h1:s/M95tB0/0Pf0icg9qCiK7zEWFsUt0F+8r3MYP3bUCQ=
-github.com/kdubbo/xds-api v0.0.0-20260808063945-44451fbf2da1/go.mod 
h1:o2HDUgL1ntaDbWomZ4cD2tt8jBamuG2qRtjXOa1zZ0Q=
+github.com/kdubbo/api v0.0.0-20260808154749-d91601e19406 
h1:HlD3yE/A+aHwXNIMydXMtdCTJyLukk8Znv+fo2pX8NA=
+github.com/kdubbo/api v0.0.0-20260808154749-d91601e19406/go.mod 
h1:8BtJiIovg7QCPsCxXcw3gDf922VcvYq5ihOSvj49Rq8=
+github.com/kdubbo/client-go v0.0.0-20260808154904-3acbf91635a1 
h1:ykfiDq3lqlBJp8r30xJIJC+E2amT0GQVQep62+lhdzo=
+github.com/kdubbo/client-go v0.0.0-20260808154904-3acbf91635a1/go.mod 
h1:euRf0fpvc8PfHmIwb3Oj/b42aJDhMVX7PHAYzSZjpU8=
+github.com/kdubbo/xds-api v0.0.0-20260808155041-b6b8371fcc89 
h1:HVDXIqVzomm6eHAaitmaB3JGPd7w7BhSg5ns1ObLvzo=
+github.com/kdubbo/xds-api v0.0.0-20260808155041-b6b8371fcc89/go.mod 
h1:o2HDUgL1ntaDbWomZ4cD2tt8jBamuG2qRtjXOa1zZ0Q=
 github.com/kevinburke/ssh_config v1.2.0 
h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
 github.com/kevinburke/ssh_config v1.2.0/go.mod 
h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/kisielk/errcheck v1.5.0/go.mod 
h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
diff --git a/manifests/charts/base/files/crd-all.gen.yaml 
b/manifests/charts/base/files/crd-all.gen.yaml
index 34c42e30..21f52ae6 100644
--- a/manifests/charts/base/files/crd-all.gen.yaml
+++ b/manifests/charts/base/files/crd-all.gen.yaml
@@ -152,6 +152,371 @@ spec:
 ---
 apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
+metadata:
+  annotations:
+    "helm.sh/resource-policy": keep
+  labels:
+    app: dubbo
+    chart: dubbo
+    dubbo: networking
+    heritage: Tiller
+    release: dubbo
+  name: dxgateservices.networking.dubbo.apache.org
+spec:
+  group: networking.dubbo.apache.org
+  names:
+    categories:
+    - dubbo
+    - networking
+    kind: DxgateService
+    listKind: DxgateServiceList
+    plural: dxgateservices
+    shortNames:
+    - dxsvc
+    singular: dxgateservice
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: CreationTimestamp is a timestamp representing the server 
time when
+        this object was created.
+      jsonPath: .metadata.creationTimestamp
+      name: Age
+      type: date
+    name: v1alpha3
+    schema:
+      openAPIV3Schema:
+        properties:
+          spec:
+            description: 'Mesh-native LLM, MCP, or A2A backend referenced by 
Gateway
+              API routes. See more details at: '
+            oneOf:
+            - not:
+                anyOf:
+                - required:
+                  - ai
+                - required:
+                  - mcp
+                - required:
+                  - a2a
+            - required:
+              - ai
+            - required:
+              - mcp
+            - required:
+              - a2a
+            properties:
+              a2a:
+                properties:
+                  agent:
+                    type: string
+                  backendRef:
+                    description: Prefer backend_ref for an in-cluster agent.
+                    properties:
+                      name:
+                        description: Kubernetes Service name.
+                        type: string
+                      namespace:
+                        description: Defaults to the DxgateService namespace.
+                        type: string
+                    required:
+                    - name
+                    type: object
+                  host:
+                    type: string
+                  path:
+                    type: string
+                  port:
+                    maximum: 4294967295
+                    minimum: 0
+                    type: integer
+                type: object
+              ai:
+                properties:
+                  endpoint:
+                    description: Optional backend URL.
+                    type: string
+                  modelRewrites:
+                    additionalProperties:
+                      type: string
+                    description: Client model alias to upstream model name.
+                    type: object
+                  models:
+                    description: Models accepted by this backend.
+                    items:
+                      type: string
+                    type: array
+                  provider:
+                    oneOf:
+                    - not:
+                        anyOf:
+                        - required:
+                          - openai
+                        - required:
+                          - anthropic
+                    - required:
+                      - openai
+                    - required:
+                      - anthropic
+                    properties:
+                      anthropic:
+                        properties:
+                          model:
+                            description: Default upstream model.
+                            type: string
+                        type: object
+                      credential:
+                        description: Provider API key.
+                        properties:
+                          key:
+                            type: string
+                          name:
+                            type: string
+                        required:
+                        - name
+                        - key
+                        type: object
+                      openai:
+                        type: object
+                      requestHeaders:
+                        items:
+                          properties:
+                            name:
+                              type: string
+                            value:
+                              type: string
+                          required:
+                          - name
+                          - value
+                          type: object
+                        type: array
+                    type: object
+                  routes:
+                    additionalProperties:
+                      description: |2-
+
+
+                        Valid Options: COMPLETIONS, RESPONSES, EMBEDDINGS, 
PASSTHROUGH
+                      enum:
+                      - AI_OPERATION_UNSPECIFIED
+                      - COMPLETIONS
+                      - RESPONSES
+                      - EMBEDDINGS
+                      - PASSTHROUGH
+                      type: string
+                    description: OpenAI-compatible operations explicitly 
enabled for
+                      this service.
+                    type: object
+                required:
+                - provider
+                type: object
+              mcp:
+                properties:
+                  targets:
+                    items:
+                      properties:
+                        name:
+                          type: string
+                        static:
+                          properties:
+                            backendRef:
+                              properties:
+                                name:
+                                  description: Kubernetes Service name.
+                                  type: string
+                                namespace:
+                                  description: Defaults to the DxgateService 
namespace.
+                                  type: string
+                              required:
+                              - name
+                              type: object
+                            path:
+                              type: string
+                            port:
+                              maximum: 4294967295
+                              minimum: 0
+                              type: integer
+                          required:
+                          - backendRef
+                          - port
+                          type: object
+                        tools:
+                          items:
+                            type: string
+                          type: array
+                      required:
+                      - name
+                      - static
+                      type: object
+                    type: array
+                required:
+                - targets
+                type: object
+              policies:
+                description: Policies applied to every HTTPRoute reference to 
this
+                  service.
+                properties:
+                  auth:
+                    description: Client authentication at the gateway.
+                    properties:
+                      header:
+                        type: string
+                      secretRef:
+                        properties:
+                          key:
+                            type: string
+                          name:
+                            type: string
+                        required:
+                        - name
+                        - key
+                        type: object
+                    required:
+                    - secretRef
+                    type: object
+                  maxBodyBytes:
+                    format: int64
+                    type: integer
+                  rateLimit:
+                    properties:
+                      header:
+                        type: string
+                      key:
+                        description: |2-
+
+
+                          Valid Options: ROUTE, BACKEND, HEADER
+                        enum:
+                        - RATE_LIMIT_KEY_UNSPECIFIED
+                        - ROUTE
+                        - BACKEND
+                        - HEADER
+                        type: string
+                      requests:
+                        maximum: 4294967295
+                        minimum: 0
+                        type: integer
+                      window:
+                        type: string
+                        x-kubernetes-validations:
+                        - message: must be a valid duration greater than 1ms
+                          rule: duration(self) >= duration('1ms')
+                    required:
+                    - requests
+                    - window
+                    type: object
+                  requestHeaders:
+                    properties:
+                      add:
+                        items:
+                          properties:
+                            name:
+                              type: string
+                            value:
+                              type: string
+                          required:
+                          - name
+                          - value
+                          type: object
+                        type: array
+                      remove:
+                        items:
+                          type: string
+                        type: array
+                    type: object
+                  responseHeaders:
+                    properties:
+                      add:
+                        items:
+                          properties:
+                            name:
+                              type: string
+                            value:
+                              type: string
+                          required:
+                          - name
+                          - value
+                          type: object
+                        type: array
+                      remove:
+                        items:
+                          type: string
+                        type: array
+                    type: object
+                  retry:
+                    properties:
+                      attempts:
+                        maximum: 4294967295
+                        minimum: 0
+                        type: integer
+                      statusCodes:
+                        items:
+                          maximum: 4294967295
+                          minimum: 0
+                          type: integer
+                        type: array
+                    type: object
+                  timeout:
+                    type: string
+                    x-kubernetes-validations:
+                    - message: must be a valid duration greater than 1ms
+                      rule: duration(self) >= duration('1ms')
+                  tokenLimit:
+                    properties:
+                      header:
+                        type: string
+                      key:
+                        description: |2-
+
+
+                          Valid Options: ROUTE, BACKEND, HEADER
+                        enum:
+                        - RATE_LIMIT_KEY_UNSPECIFIED
+                        - ROUTE
+                        - BACKEND
+                        - HEADER
+                        type: string
+                      tokens:
+                        minimum: 0
+                        type: integer
+                      window:
+                        type: string
+                        x-kubernetes-validations:
+                        - message: must be a valid duration greater than 1ms
+                          rule: duration(self) >= duration('1ms')
+                    required:
+                    - tokens
+                    - window
+                    type: object
+                type: object
+            type: object
+          status:
+            properties:
+              conditions:
+                items:
+                  properties:
+                    observedGeneration:
+                      anyOf:
+                      - type: integer
+                      - type: string
+                      x-kubernetes-int-or-string: true
+                    reason:
+                      type: string
+                    status:
+                      type: string
+                    type:
+                      type: string
+                  type: object
+                type: array
+            type: object
+            x-kubernetes-preserve-unknown-fields: true
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
 metadata:
   annotations:
     "helm.sh/resource-policy": keep
diff --git a/manifests/charts/dubbod/files/kube-gateway.yaml 
b/manifests/charts/dubbod/files/kube-gateway.yaml
index 3e3ab51c..c2beb796 100644
--- a/manifests/charts/dubbod/files/kube-gateway.yaml
+++ b/manifests/charts/dubbod/files/kube-gateway.yaml
@@ -37,7 +37,41 @@ metadata:
     app.kubernetes.io/instance: {{ .DeploymentName }}
     app.kubernetes.io/managed-by: dubbod
     gateway.networking.k8s.io/gateway-name: {{ .Gateway.Name }}
-automountServiceAccountToken: false
+automountServiceAccountToken: true
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: {{ .ServiceAccount }}-credentials
+  namespace: {{ .Gateway.Namespace }}
+  labels:
+    app.kubernetes.io/name: dxgate
+    app.kubernetes.io/instance: {{ .DeploymentName }}
+    app.kubernetes.io/managed-by: dubbod
+    gateway.networking.k8s.io/gateway-name: {{ .Gateway.Name }}
+rules:
+- apiGroups: [""]
+  resources: ["secrets"]
+  verbs: ["get"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: {{ .ServiceAccount }}-credentials
+  namespace: {{ .Gateway.Namespace }}
+  labels:
+    app.kubernetes.io/name: dxgate
+    app.kubernetes.io/instance: {{ .DeploymentName }}
+    app.kubernetes.io/managed-by: dubbod
+    gateway.networking.k8s.io/gateway-name: {{ .Gateway.Name }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: {{ .ServiceAccount }}-credentials
+subjects:
+- kind: ServiceAccount
+  name: {{ .ServiceAccount }}
+  namespace: {{ .Gateway.Namespace }}
 ---
 apiVersion: apps/v1
 kind: Deployment
diff --git a/manifests/charts/dubbod/templates/clusterrole.yaml 
b/manifests/charts/dubbod/templates/clusterrole.yaml
index 751e0f2b..80b23939 100644
--- a/manifests/charts/dubbod/templates/clusterrole.yaml
+++ b/manifests/charts/dubbod/templates/clusterrole.yaml
@@ -113,6 +113,18 @@ rules:
       - update
       - patch
       - delete
+  - apiGroups: ["rbac.authorization.k8s.io"]
+    resources:
+      - roles
+      - rolebindings
+    verbs:
+      - get
+      - list
+      - watch
+      - create
+      - update
+      - patch
+      - delete
   - apiGroups: ["apps"]
     resources:
       - deployments
diff --git a/manifests/charts/dubbod/templates/configmap-injector.yaml 
b/manifests/charts/dubbod/templates/configmap-injector.yaml
index 80a9b1b3..0d4a9f1a 100644
--- a/manifests/charts/dubbod/templates/configmap-injector.yaml
+++ b/manifests/charts/dubbod/templates/configmap-injector.yaml
@@ -13,6 +13,22 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+{{- $defaults := .Values._internal_default_values_not_set | default dict }}
+{{- $defaultGlobal := $defaults.global | default dict }}
+{{- $defaultProxyless := $defaultGlobal.proxyless | default dict }}
+{{- $defaultCNI := $defaultProxyless.cni | default dict }}
+{{- $global := .Values.global | default dict }}
+{{- $proxyless := $global.proxyless | default dict }}
+{{- $cni := $proxyless.cni | default dict }}
+{{- $image := coalesce $cni.image $defaultCNI.image (printf 
"ghcr.io/apache/dubbo-kubernetes/dubbod:%s" .Chart.AppVersion) }}
+{{- $injectorValues := deepCopy (omit .Values 
"_internal_default_values_not_set") }}
+{{- $injectorGlobal := get $injectorValues "global" | default dict }}
+{{- $_ := set $injectorValues "global" $injectorGlobal }}
+{{- $injectorProxyless := get $injectorGlobal "proxyless" | default dict }}
+{{- $_ = set $injectorGlobal "proxyless" $injectorProxyless }}
+{{- $injectorCNI := get $injectorProxyless "cni" | default dict }}
+{{- $_ = set $injectorProxyless "cni" $injectorCNI }}
+{{- $_ = set $injectorCNI "image" $image }}
 apiVersion: v1
 kind: ConfigMap
 metadata:
@@ -31,3 +47,4 @@ data:
       gateway: |
 {{ .Files.Get "files/kube-gateway.yaml" | trim | indent 8 }}
   values: |-
+{{ $injectorValues | toYaml | indent 4 }}
diff --git a/pkg/config/schema/collections/collections.gen.go 
b/pkg/config/schema/collections/collections.gen.go
index d1a08f35..9d82b78a 100755
--- a/pkg/config/schema/collections/collections.gen.go
+++ b/pkg/config/schema/collections/collections.gen.go
@@ -137,6 +137,21 @@ var (
                ValidateProto: validation.EmptyValidate,
        }.MustBuild()
 
+       DxgateService = resource.Builder{
+               Identifier: "DxgateService",
+               Group:      "networking.dubbo.apache.org",
+               Kind:       "DxgateService",
+               Plural:     "dxgateservices",
+               Version:    "v1alpha3",
+               Proto:      "dubbo.networking.v1alpha3.DxgateService", 
StatusProto: "dubbo.meta.v1alpha1.DubboStatus",
+               ReflectType: 
reflect.TypeOf(&githubcomkdubboapinetworkingv1alpha3.DxgateService{}).Elem(), 
StatusType: 
reflect.TypeOf(&githubcomkdubboapimetav1alpha1.DubboStatus{}).Elem(),
+               ProtoPackage: "github.com/kdubbo/api/networking/v1alpha3", 
StatusPackage: "github.com/kdubbo/api/meta/v1alpha1",
+               ClusterScoped: false,
+               Synthetic:     false,
+               Builtin:       false,
+               ValidateProto: validation.ValidateDxgateService,
+       }.MustBuild()
+
        EndpointSlice = resource.Builder{
                Identifier:    "EndpointSlice",
                Group:         "discovery.k8s.io",
@@ -548,6 +563,7 @@ var (
                MustAdd(CustomResourceDefinition).
                MustAdd(DaemonSet).
                MustAdd(Deployment).
+               MustAdd(DxgateService).
                MustAdd(EndpointSlice).
                MustAdd(Endpoints).
                MustAdd(FaultInjectionPolicy).
@@ -607,6 +623,7 @@ var (
        Dubbo = collection.NewSchemasBuilder().
                MustAdd(AuthorizationPolicy).
                MustAdd(CircuitBreakerPolicy).
+               MustAdd(DxgateService).
                MustAdd(FaultInjectionPolicy).
                MustAdd(PeerAuthentication).
                MustAdd(RequestAuthentication).
@@ -621,6 +638,7 @@ var (
                        MustAdd(AuthorizationPolicy).
                        MustAdd(BackendTLSPolicy).
                        MustAdd(CircuitBreakerPolicy).
+                       MustAdd(DxgateService).
                        MustAdd(FaultInjectionPolicy).
                        MustAdd(GatewayClass).
                        MustAdd(HTTPRoute).
@@ -639,6 +657,7 @@ var (
                                MustAdd(AuthorizationPolicy).
                                MustAdd(BackendTLSPolicy).
                                MustAdd(CircuitBreakerPolicy).
+                               MustAdd(DxgateService).
                                MustAdd(FaultInjectionPolicy).
                                MustAdd(GatewayClass).
                                MustAdd(HTTPRoute).
diff --git a/pkg/config/schema/gvk/resources.gen.go 
b/pkg/config/schema/gvk/resources.gen.go
index 90b93612..b094ac5c 100755
--- a/pkg/config/schema/gvk/resources.gen.go
+++ b/pkg/config/schema/gvk/resources.gen.go
@@ -18,6 +18,7 @@ var (
        CustomResourceDefinition       = config.GroupVersionKind{Group: 
"apiextensions.k8s.io", Version: "v1", Kind: "CustomResourceDefinition"}
        DaemonSet                      = config.GroupVersionKind{Group: "apps", 
Version: "v1", Kind: "DaemonSet"}
        Deployment                     = config.GroupVersionKind{Group: "apps", 
Version: "v1", Kind: "Deployment"}
+       DxgateService                  = config.GroupVersionKind{Group: 
"networking.dubbo.apache.org", Version: "v1alpha3", Kind: "DxgateService"}
        EndpointSlice                  = config.GroupVersionKind{Group: 
"discovery.k8s.io", Version: "v1", Kind: "EndpointSlice"}
        Endpoints                      = config.GroupVersionKind{Group: "", 
Version: "v1", Kind: "Endpoints"}
        FaultInjectionPolicy           = config.GroupVersionKind{Group: 
"networking.dubbo.apache.org", Version: "v1alpha3", Kind: 
"FaultInjectionPolicy"}
@@ -69,6 +70,8 @@ func ToGVR(g config.GroupVersionKind) 
(schema.GroupVersionResource, bool) {
                return gvr.DaemonSet, true
        case Deployment:
                return gvr.Deployment, true
+       case DxgateService:
+               return gvr.DxgateService, true
        case EndpointSlice:
                return gvr.EndpointSlice, true
        case Endpoints:
@@ -150,6 +153,8 @@ func MustToKind(g config.GroupVersionKind) kind.Kind {
                return kind.DaemonSet
        case Deployment:
                return kind.Deployment
+       case DxgateService:
+               return kind.DxgateService
        case EndpointSlice:
                return kind.EndpointSlice
        case Endpoints:
@@ -234,6 +239,8 @@ func FromGVR(g schema.GroupVersionResource) 
(config.GroupVersionKind, bool) {
                return DaemonSet, true
        case gvr.Deployment:
                return Deployment, true
+       case gvr.DxgateService:
+               return DxgateService, true
        case gvr.EndpointSlice:
                return EndpointSlice, true
        case gvr.Endpoints:
diff --git a/pkg/config/schema/gvr/resources.gen.go 
b/pkg/config/schema/gvr/resources.gen.go
index 79d7ab4f..b71d600f 100755
--- a/pkg/config/schema/gvr/resources.gen.go
+++ b/pkg/config/schema/gvr/resources.gen.go
@@ -13,6 +13,7 @@ var (
        CustomResourceDefinition       = schema.GroupVersionResource{Group: 
"apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions"}
        DaemonSet                      = schema.GroupVersionResource{Group: 
"apps", Version: "v1", Resource: "daemonsets"}
        Deployment                     = schema.GroupVersionResource{Group: 
"apps", Version: "v1", Resource: "deployments"}
+       DxgateService                  = schema.GroupVersionResource{Group: 
"networking.dubbo.apache.org", Version: "v1alpha3", Resource: "dxgateservices"}
        EndpointSlice                  = schema.GroupVersionResource{Group: 
"discovery.k8s.io", Version: "v1", Resource: "endpointslices"}
        Endpoints                      = schema.GroupVersionResource{Group: "", 
Version: "v1", Resource: "endpoints"}
        FaultInjectionPolicy           = schema.GroupVersionResource{Group: 
"networking.dubbo.apache.org", Version: "v1alpha3", Resource: 
"faultinjectionpolicies"}
@@ -63,6 +64,8 @@ func IsClusterScoped(g schema.GroupVersionResource) bool {
                return false
        case Deployment:
                return false
+       case DxgateService:
+               return false
        case EndpointSlice:
                return false
        case Endpoints:
diff --git a/pkg/config/schema/kind/resources.gen.go 
b/pkg/config/schema/kind/resources.gen.go
index 2eeffb3e..01ade2a7 100755
--- a/pkg/config/schema/kind/resources.gen.go
+++ b/pkg/config/schema/kind/resources.gen.go
@@ -13,6 +13,7 @@ const (
        DNSName
        DaemonSet
        Deployment
+       DxgateService
        EndpointSlice
        Endpoints
        FaultInjectionPolicy
@@ -61,6 +62,8 @@ func (k Kind) String() string {
                return "DaemonSet"
        case Deployment:
                return "Deployment"
+       case DxgateService:
+               return "DxgateService"
        case EndpointSlice:
                return "EndpointSlice"
        case Endpoints:
@@ -138,6 +141,8 @@ func FromString(s string) Kind {
                return DaemonSet
        case "Deployment":
                return Deployment
+       case "DxgateService":
+               return DxgateService
        case "EndpointSlice":
                return EndpointSlice
        case "Endpoints":
diff --git a/pkg/config/schema/kubeclient/resources.gen.go 
b/pkg/config/schema/kubeclient/resources.gen.go
index 217a0ec5..61e5a3ab 100755
--- a/pkg/config/schema/kubeclient/resources.gen.go
+++ b/pkg/config/schema/kubeclient/resources.gen.go
@@ -48,6 +48,8 @@ func GetWriteClient[T runtime.Object](c ClientGetter, 
namespace string) ktypes.W
                return 
c.Kube().AppsV1().DaemonSets(namespace).(ktypes.WriteAPI[T])
        case *k8sioapiappsv1.Deployment:
                return 
c.Kube().AppsV1().Deployments(namespace).(ktypes.WriteAPI[T])
+       case 
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService:
+               return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(namespace).(ktypes.WriteAPI[T])
        case *k8sioapidiscoveryv1.EndpointSlice:
                return 
c.Kube().DiscoveryV1().EndpointSlices(namespace).(ktypes.WriteAPI[T])
        case *k8sioapicorev1.Endpoints:
@@ -119,6 +121,8 @@ func GetClient[T, TL runtime.Object](c ClientGetter, 
namespace string) ktypes.Re
                return 
c.Kube().AppsV1().DaemonSets(namespace).(ktypes.ReadWriteAPI[T, TL])
        case *k8sioapiappsv1.Deployment:
                return 
c.Kube().AppsV1().Deployments(namespace).(ktypes.ReadWriteAPI[T, TL])
+       case 
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService:
+               return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(namespace).(ktypes.ReadWriteAPI[T,
 TL])
        case *k8sioapidiscoveryv1.EndpointSlice:
                return 
c.Kube().DiscoveryV1().EndpointSlices(namespace).(ktypes.ReadWriteAPI[T, TL])
        case *k8sioapicorev1.Endpoints:
@@ -190,6 +194,8 @@ func gvrToObject(g schema.GroupVersionResource) 
runtime.Object {
                return &k8sioapiappsv1.DaemonSet{}
        case gvr.Deployment:
                return &k8sioapiappsv1.Deployment{}
+       case gvr.DxgateService:
+               return 
&apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService{}
        case gvr.EndpointSlice:
                return &k8sioapidiscoveryv1.EndpointSlice{}
        case gvr.Endpoints:
@@ -299,6 +305,13 @@ func getInformerFiltered(c ClientGetter, opts 
ktypes.InformerOptions, g schema.G
                w = func(options metav1.ListOptions) (watch.Interface, error) {
                        return 
c.Kube().AppsV1().Deployments(opts.Namespace).Watch(context.Background(), 
options)
                }
+       case gvr.DxgateService:
+               l = func(options metav1.ListOptions) (runtime.Object, error) {
+                       return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(opts.Namespace).List(context.Background(),
 options)
+               }
+               w = func(options metav1.ListOptions) (watch.Interface, error) {
+                       return 
c.Dubbo().NetworkingV1alpha3().DxgateServices(opts.Namespace).Watch(context.Background(),
 options)
+               }
        case gvr.EndpointSlice:
                l = func(options metav1.ListOptions) (runtime.Object, error) {
                        return 
c.Kube().DiscoveryV1().EndpointSlices(opts.Namespace).List(context.Background(),
 options)
diff --git a/pkg/config/schema/kubetypes/resources.gen.go 
b/pkg/config/schema/kubetypes/resources.gen.go
index 00efb091..ea73932a 100755
--- a/pkg/config/schema/kubetypes/resources.gen.go
+++ b/pkg/config/schema/kubetypes/resources.gen.go
@@ -44,6 +44,10 @@ func getGvk(obj any) (config.GroupVersionKind, bool) {
                return gvk.DaemonSet, true
        case *k8sioapiappsv1.Deployment:
                return gvk.Deployment, true
+       case *githubcomkdubboapinetworkingv1alpha3.DxgateService:
+               return gvk.DxgateService, true
+       case 
*apigithubcomapachedubbokubernetesapinetworkingv1alpha3.DxgateService:
+               return gvk.DxgateService, true
        case *k8sioapidiscoveryv1.EndpointSlice:
                return gvk.EndpointSlice, true
        case *k8sioapicorev1.Endpoints:
diff --git a/pkg/config/schema/metadata.yaml b/pkg/config/schema/metadata.yaml
index 7a03f977..c238b616 100644
--- a/pkg/config/schema/metadata.yaml
+++ b/pkg/config/schema/metadata.yaml
@@ -240,6 +240,16 @@ resources:
     statusProto: "dubbo.meta.v1alpha1.DubboStatus"
     statusProtoPackage: "github.com/kdubbo/api/meta/v1alpha1"
 
+  - kind: DxgateService
+    plural: "dxgateservices"
+    group: "networking.dubbo.apache.org"
+    version: "v1alpha3"
+    proto: "dubbo.networking.v1alpha3.DxgateService"
+    protoPackage: "github.com/kdubbo/api/networking/v1alpha3"
+    validate: "validation.ValidateDxgateService"
+    statusProto: "dubbo.meta.v1alpha1.DubboStatus"
+    statusProtoPackage: "github.com/kdubbo/api/meta/v1alpha1"
+
   - kind: ServiceActivationPolicy
     plural: "serviceactivationpolicies"
     group: "networking.dubbo.apache.org"
diff --git a/pkg/config/validation/validators.go 
b/pkg/config/validation/validators.go
index b5b61739..8d5b050a 100644
--- a/pkg/config/validation/validators.go
+++ b/pkg/config/validation/validators.go
@@ -20,6 +20,7 @@ import (
        "fmt"
        "math"
        "net"
+       "net/url"
        "strings"
        "time"
 
@@ -263,6 +264,156 @@ var ValidateFaultInjectionPolicy = 
RegisterValidateFunc("ValidateFaultInjectionP
                return v.Unwrap()
        })
 
+// ValidateDxgateService checks that a mesh-native LLM, MCP, or A2A backend can
+// be compiled into one unambiguous data-plane configuration.
+var ValidateDxgateService = RegisterValidateFunc("ValidateDxgateService",
+       func(cfg config.Config) (Warning, error) {
+               spec, ok := cfg.Spec.(*networking.DxgateService)
+               if !ok {
+                       return nil, fmt.Errorf("cannot cast to DxgateService")
+               }
+               v := Validation{}
+               switch {
+               case spec.GetAi() != nil:
+                       ai := spec.GetAi()
+                       if ai.GetProvider() == nil || 
ai.GetProvider().GetProvider() == nil {
+                               v = appendValidation(v, fmt.Errorf("ai.provider 
must select openai or anthropic"))
+                       }
+                       if endpoint := ai.GetEndpoint(); endpoint != "" {
+                               parsed, err := url.ParseRequestURI(endpoint)
+                               if err != nil || (parsed.Scheme != "http" && 
parsed.Scheme != "https") || parsed.Host == "" {
+                                       v = appendValidation(v, 
fmt.Errorf("ai.endpoint %q must be an absolute HTTP(S) URL", endpoint))
+                               }
+                       }
+                       for i, model := range ai.GetModels() {
+                               if strings.TrimSpace(model) == "" {
+                                       v = appendValidation(v, 
fmt.Errorf("ai.models[%d] must not be empty", i))
+                               }
+                       }
+                       for path := range ai.GetRoutes() {
+                               if !strings.HasPrefix(path, "/") {
+                                       v = appendValidation(v, 
fmt.Errorf("ai.routes key %q must start with /", path))
+                               }
+                       }
+                       if credential := ai.GetProvider().GetCredential(); 
credential != nil {
+                               v = appendValidation(v, 
validateSecretKeyReference("ai.provider.credential", credential))
+                       }
+               case spec.GetMcp() != nil:
+                       targets := spec.GetMcp().GetTargets()
+                       if len(targets) == 0 {
+                               v = appendValidation(v, fmt.Errorf("mcp.targets 
must not be empty"))
+                       }
+                       names := make(map[string]struct{}, len(targets))
+                       for i, target := range targets {
+                               if target == nil {
+                                       v = appendValidation(v, 
fmt.Errorf("mcp.targets[%d] must not be null", i))
+                                       continue
+                               }
+                               if target.GetName() == "" {
+                                       v = appendValidation(v, 
fmt.Errorf("mcp.targets[%d].name must not be empty", i))
+                               } else if _, found := names[target.GetName()]; 
found {
+                                       v = appendValidation(v, 
fmt.Errorf("mcp.targets[%d].name %q is duplicated", i, target.GetName()))
+                               } else {
+                                       names[target.GetName()] = struct{}{}
+                               }
+                               static := target.GetStatic()
+                               if static == nil {
+                                       v = appendValidation(v, 
fmt.Errorf("mcp.targets[%d].static must be set", i))
+                                       continue
+                               }
+                               v = appendValidation(v,
+                                       
validateBackendReference(fmt.Sprintf("mcp.targets[%d].static.backendRef", i), 
static.GetBackendRef()),
+                                       
validateDxgatePort(fmt.Sprintf("mcp.targets[%d].static.port", i), 
static.GetPort()),
+                                       
validateOptionalPath(fmt.Sprintf("mcp.targets[%d].static.path", i), 
static.GetPath()),
+                               )
+                       }
+               case spec.GetA2A() != nil:
+                       a2a := spec.GetA2A()
+                       hasRef := a2a.GetBackendRef() != nil
+                       hasHost := strings.TrimSpace(a2a.GetHost()) != ""
+                       if hasRef == hasHost {
+                               v = appendValidation(v, fmt.Errorf("a2a must 
set exactly one of backendRef or host"))
+                       }
+                       if hasRef {
+                               v = appendValidation(v, 
validateBackendReference("a2a.backendRef", a2a.GetBackendRef()))
+                       }
+                       v = appendValidation(v,
+                               validateDxgatePort("a2a.port", a2a.GetPort()),
+                               validateOptionalPath("a2a.path", a2a.GetPath()),
+                       )
+               default:
+                       v = appendValidation(v, fmt.Errorf("exactly one of ai, 
mcp, or a2a must be set"))
+               }
+
+               if policies := spec.GetPolicies(); policies != nil {
+                       if auth := policies.GetAuth(); auth != nil {
+                               v = appendValidation(v, 
validateSecretKeyReference("policies.auth.secretRef", auth.GetSecretRef()))
+                       }
+                       if rate := policies.GetRateLimit(); rate != nil {
+                               if rate.GetRequests() == 0 {
+                                       v = appendValidation(v, 
fmt.Errorf("policies.rateLimit.requests must be greater than zero"))
+                               }
+                               v = appendValidation(v, 
validatePositiveDuration("policies.rateLimit.window", rate.GetWindow()))
+                       }
+                       if tokens := policies.GetTokenLimit(); tokens != nil {
+                               if tokens.GetTokens() == 0 {
+                                       v = appendValidation(v, 
fmt.Errorf("policies.tokenLimit.tokens must be greater than zero"))
+                               }
+                               v = appendValidation(v, 
validatePositiveDuration("policies.tokenLimit.window", tokens.GetWindow()))
+                       }
+                       v = appendValidation(v, 
validatePositiveDuration("policies.timeout", policies.GetTimeout()))
+                       if retry := policies.GetRetry(); retry != nil {
+                               if retry.GetAttempts() == 0 {
+                                       v = appendValidation(v, 
fmt.Errorf("policies.retry.attempts must be greater than zero"))
+                               }
+                               for i, status := range retry.GetStatusCodes() {
+                                       if status < 400 || status > 599 {
+                                               v = appendValidation(v, 
fmt.Errorf("policies.retry.statusCodes[%d] must be in range [400, 599], got 
%d", i, status))
+                                       }
+                               }
+                       }
+                       if policies.GetMaxBodyBytes() < 0 {
+                               v = appendValidation(v, 
fmt.Errorf("policies.maxBodyBytes must not be negative"))
+                       }
+               }
+               return v.Unwrap()
+       })
+
+func validateSecretKeyReference(field string, ref 
*networking.SecretKeyReference) error {
+       if ref == nil {
+               return fmt.Errorf("%s must be set", field)
+       }
+       var errs error
+       if strings.TrimSpace(ref.GetName()) == "" {
+               errs = AppendErrors(errs, fmt.Errorf("%s.name must not be 
empty", field))
+       }
+       if strings.TrimSpace(ref.GetKey()) == "" {
+               errs = AppendErrors(errs, fmt.Errorf("%s.key must not be 
empty", field))
+       }
+       return errs
+}
+
+func validateBackendReference(field string, ref *networking.BackendReference) 
error {
+       if ref == nil || strings.TrimSpace(ref.GetName()) == "" {
+               return fmt.Errorf("%s.name must not be empty", field)
+       }
+       return nil
+}
+
+func validateDxgatePort(field string, port uint32) error {
+       if port == 0 || port > 65535 {
+               return fmt.Errorf("%s must be in range [1, 65535], got %d", 
field, port)
+       }
+       return nil
+}
+
+func validateOptionalPath(field, path string) error {
+       if path != "" && !strings.HasPrefix(path, "/") {
+               return fmt.Errorf("%s must start with /", field)
+       }
+       return nil
+}
+
 // ValidateServiceEntry checks that a ServiceEntry can be converted into 
services and endpoints.
 var ValidateServiceEntry = RegisterValidateFunc("ValidateServiceEntry", 
func(cfg config.Config) (Warning, error) {
        spec, ok := cfg.Spec.(*networking.ServiceEntry)
diff --git a/pkg/config/validation/validators_test.go 
b/pkg/config/validation/validators_test.go
index 08dce1af..34cdbd71 100644
--- a/pkg/config/validation/validators_test.go
+++ b/pkg/config/validation/validators_test.go
@@ -382,6 +382,112 @@ func TestValidateFaultInjectionPolicy(t *testing.T) {
        }
 }
 
+func TestValidateDxgateService(t *testing.T) {
+       openAI := func() *networking.DxgateService {
+               return &networking.DxgateService{
+                       Service: &networking.DxgateService_Ai{Ai: 
&networking.AIService{
+                               Provider: &networking.AIProvider{
+                                       Provider: 
&networking.AIProvider_Openai{Openai: &networking.OpenAIProvider{}},
+                               },
+                               Models:   []string{"gpt-test"},
+                               Endpoint: "http://llm.default.svc:8080/v1";,
+                       }},
+               }
+       }
+       mcp := func() *networking.DxgateService {
+               return &networking.DxgateService{
+                       Service: &networking.DxgateService_Mcp{Mcp: 
&networking.MCPService{
+                               Targets: []*networking.MCPTarget{{
+                                       Name: "tools",
+                                       Static: &networking.StaticBackend{
+                                               BackendRef: 
&networking.BackendReference{Name: "tools"},
+                                               Port:       8080,
+                                       },
+                               }},
+                       }},
+               }
+       }
+       a2a := func() *networking.DxgateService {
+               return &networking.DxgateService{
+                       Service: &networking.DxgateService_A2A{A2A: 
&networking.A2AService{
+                               BackendRef: &networking.BackendReference{Name: 
"review-agent"},
+                               Port:       9090,
+                       }},
+               }
+       }
+       cases := []struct {
+               name    string
+               spec    *networking.DxgateService
+               wantErr bool
+       }{
+               {name: "openai", spec: openAI()},
+               {name: "mcp", spec: mcp()},
+               {name: "a2a", spec: a2a()},
+               {name: "missing service", spec: &networking.DxgateService{}, 
wantErr: true},
+               {
+                       name: "ai provider missing",
+                       spec: &networking.DxgateService{
+                               Service: &networking.DxgateService_Ai{Ai: 
&networking.AIService{}},
+                       },
+                       wantErr: true,
+               },
+               {
+                       name: "bad ai endpoint",
+                       spec: func() *networking.DxgateService {
+                               s := openAI()
+                               s.GetAi().Endpoint = "llm:8080"
+                               return s
+                       }(),
+                       wantErr: true,
+               },
+               {
+                       name: "empty mcp targets",
+                       spec: &networking.DxgateService{
+                               Service: &networking.DxgateService_Mcp{Mcp: 
&networking.MCPService{}},
+                       },
+                       wantErr: true,
+               },
+               {
+                       name: "duplicate mcp target",
+                       spec: func() *networking.DxgateService {
+                               s := mcp()
+                               s.GetMcp().Targets = append(s.GetMcp().Targets, 
s.GetMcp().Targets[0])
+                               return s
+                       }(),
+                       wantErr: true,
+               },
+               {
+                       name: "a2a ambiguous target",
+                       spec: func() *networking.DxgateService {
+                               s := a2a()
+                               s.GetA2A().Host = "agent.example.com"
+                               return s
+                       }(),
+                       wantErr: true,
+               },
+               {
+                       name: "bad policy",
+                       spec: func() *networking.DxgateService {
+                               s := openAI()
+                               s.Policies = &networking.DxgateServicePolicies{
+                                       Retry:   
&networking.RetryPolicy{Attempts: 0, StatusCodes: []uint32{200}},
+                                       Timeout: durationpb.New(-time.Second),
+                               }
+                               return s
+                       }(),
+                       wantErr: true,
+               },
+       }
+       for _, tc := range cases {
+               t.Run(tc.name, func(t *testing.T) {
+                       _, err := ValidateDxgateService(makeConfig(tc.spec))
+                       if (err != nil) != tc.wantErr {
+                               t.Fatalf("got err=%v, wantErr=%v", err, 
tc.wantErr)
+                       }
+               })
+       }
+}
+
 func TestValidateServiceEntry(t *testing.T) {
        valid := func() *networking.ServiceEntry {
                return &networking.ServiceEntry{
diff --git a/samples/ai-mesh/README.md b/samples/ai-mesh/README.md
new file mode 100644
index 00000000..5fca59e4
--- /dev/null
+++ b/samples/ai-mesh/README.md
@@ -0,0 +1,18 @@
+# Mesh-native AI services
+
+This sample uses one API: `networking.dubbo.apache.org/v1alpha3`
+`DxgateService`. Ordinary HTTP backends remain core Kubernetes `Service`
+objects. `dubbod` compiles both kinds of `HTTPRoute` backend into RDS and
+delivers it to dxgate over xDS.
+
+```bash
+kubectl create namespace ai-mesh
+kubectl -n ai-mesh apply -f gateway.yaml
+kubectl -n ai-mesh apply -f backends.yaml
+kubectl -n ai-mesh apply -f services.yaml
+kubectl -n ai-mesh apply -f routes.yaml
+```
+
+The sample image `kdubbo/agent-mock:latest` is the no-key E2E fixture in
+`tests/e2e/agentmock`; replace its Services and Secret values with production
+backends and credentials.
diff --git a/samples/ai-mesh/backends.yaml b/samples/ai-mesh/backends.yaml
new file mode 100644
index 00000000..ec4e6fb9
--- /dev/null
+++ b/samples/ai-mesh/backends.yaml
@@ -0,0 +1,119 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: agent-mock
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: agent-mock
+  template:
+    metadata:
+      labels:
+        app: agent-mock
+    spec:
+      containers:
+        - name: http
+          image: kdubbo/agent-mock:latest
+          imagePullPolicy: IfNotPresent
+          env:
+            - {name: MOCK_MODE, value: http}
+            - {name: PORT, value: "8080"}
+          ports:
+            - {name: http, containerPort: 8080}
+        - name: openai
+          image: kdubbo/agent-mock:latest
+          imagePullPolicy: IfNotPresent
+          env:
+            - {name: MOCK_MODE, value: openai}
+            - {name: PORT, value: "8081"}
+          ports:
+            - {name: openai, containerPort: 8081}
+        - name: anthropic
+          image: kdubbo/agent-mock:latest
+          imagePullPolicy: IfNotPresent
+          env:
+            - {name: MOCK_MODE, value: anthropic}
+            - {name: PORT, value: "8082"}
+          ports:
+            - {name: anthropic, containerPort: 8082}
+        - name: mcp-search
+          image: kdubbo/agent-mock:latest
+          imagePullPolicy: IfNotPresent
+          env:
+            - {name: MOCK_MODE, value: mcp}
+            - {name: MOCK_NAME, value: search}
+            - {name: PORT, value: "8083"}
+          ports:
+            - {name: mcp-search, containerPort: 8083}
+        - name: mcp-calendar
+          image: kdubbo/agent-mock:latest
+          imagePullPolicy: IfNotPresent
+          env:
+            - {name: MOCK_MODE, value: mcp}
+            - {name: MOCK_NAME, value: calendar}
+            - {name: PORT, value: "8084"}
+          ports:
+            - {name: mcp-calendar, containerPort: 8084}
+        - name: a2a
+          image: kdubbo/agent-mock:latest
+          imagePullPolicy: IfNotPresent
+          env:
+            - {name: MOCK_MODE, value: a2a}
+            - {name: PORT, value: "8085"}
+          ports:
+            - {name: a2a, containerPort: 8085}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mock-http
+spec:
+  selector: {app: agent-mock}
+  ports:
+    - {name: http, port: 8080, targetPort: http}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mock-openai
+spec:
+  selector: {app: agent-mock}
+  ports:
+    - {name: http, port: 8081, targetPort: openai}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mock-anthropic
+spec:
+  selector: {app: agent-mock}
+  ports:
+    - {name: http, port: 8082, targetPort: anthropic}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mock-mcp-search
+spec:
+  selector: {app: agent-mock}
+  ports:
+    - {name: http, port: 8083, targetPort: mcp-search}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mock-mcp-calendar
+spec:
+  selector: {app: agent-mock}
+  ports:
+    - {name: http, port: 8084, targetPort: mcp-calendar}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: mock-a2a
+spec:
+  selector: {app: agent-mock}
+  ports:
+    - {name: http, port: 8085, targetPort: a2a}
diff --git a/samples/ai-mesh/gateway.yaml b/samples/ai-mesh/gateway.yaml
new file mode 100644
index 00000000..86ea738f
--- /dev/null
+++ b/samples/ai-mesh/gateway.yaml
@@ -0,0 +1,13 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: public
+spec:
+  gatewayClassName: dubbo
+  listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
+      allowedRoutes:
+        namespaces:
+          from: Same
diff --git a/samples/ai-mesh/routes.yaml b/samples/ai-mesh/routes.yaml
new file mode 100644
index 00000000..ad2dd293
--- /dev/null
+++ b/samples/ai-mesh/routes.yaml
@@ -0,0 +1,86 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: ordinary-http
+spec:
+  parentRefs:
+    - {name: public, sectionName: http}
+  rules:
+    - matches:
+        - path: {type: PathPrefix, value: /users}
+        - path: {type: PathPrefix, value: /orders}
+      backendRefs:
+        - {name: mock-http, port: 8080}
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: openai
+spec:
+  parentRefs:
+    - {name: public, sectionName: http}
+  rules:
+    - matches:
+        - path: {type: PathPrefix, value: /openai}
+      filters:
+        - type: URLRewrite
+          urlRewrite:
+            path:
+              type: ReplacePrefixMatch
+              replacePrefixMatch: /v1
+      backendRefs:
+        - group: networking.dubbo.apache.org
+          kind: DxgateService
+          name: openai
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: anthropic
+spec:
+  parentRefs:
+    - {name: public, sectionName: http}
+  rules:
+    - matches:
+        - path: {type: PathPrefix, value: /anthropic}
+      filters:
+        - type: URLRewrite
+          urlRewrite:
+            path:
+              type: ReplacePrefixMatch
+              replacePrefixMatch: /v1
+      backendRefs:
+        - group: networking.dubbo.apache.org
+          kind: DxgateService
+          name: anthropic
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: tools
+spec:
+  parentRefs:
+    - {name: public, sectionName: http}
+  rules:
+    - matches:
+        - path: {type: Exact, value: /mcp}
+      backendRefs:
+        - group: networking.dubbo.apache.org
+          kind: DxgateService
+          name: tools
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: planner
+spec:
+  parentRefs:
+    - {name: public, sectionName: http}
+  rules:
+    - matches:
+        - path: {type: Exact, value: /a2a}
+        - path: {type: Exact, value: /.well-known/agent-card.json}
+      backendRefs:
+        - group: networking.dubbo.apache.org
+          kind: DxgateService
+          name: planner
diff --git a/samples/ai-mesh/services.yaml b/samples/ai-mesh/services.yaml
new file mode 100644
index 00000000..48aabb9b
--- /dev/null
+++ b/samples/ai-mesh/services.yaml
@@ -0,0 +1,80 @@
+apiVersion: v1
+kind: Secret
+metadata:
+  name: agent-credentials
+type: Opaque
+stringData:
+  openai: mock-openai-key
+  anthropic: mock-anthropic-key
+  client: mock-client-key
+---
+apiVersion: networking.dubbo.apache.org/v1alpha3
+kind: DxgateService
+metadata:
+  name: openai
+spec:
+  ai:
+    endpoint: http://mock-openai:8081/v1
+    provider:
+      openai: {}
+      credential:
+        name: agent-credentials
+        key: openai
+    models: [gpt-mock]
+    routes:
+      /v1/chat/completions: COMPLETIONS
+  policies:
+    auth:
+      header: x-client-key
+      secretRef:
+        name: agent-credentials
+        key: client
+    timeout: 5s
+    retry:
+      attempts: 2
+      statusCodes: [502, 503, 504]
+---
+apiVersion: networking.dubbo.apache.org/v1alpha3
+kind: DxgateService
+metadata:
+  name: anthropic
+spec:
+  ai:
+    endpoint: http://mock-anthropic:8082
+    provider:
+      anthropic:
+        model: claude-mock
+      credential:
+        name: agent-credentials
+        key: anthropic
+    models: [claude-mock]
+    routes:
+      /v1/chat/completions: COMPLETIONS
+---
+apiVersion: networking.dubbo.apache.org/v1alpha3
+kind: DxgateService
+metadata:
+  name: tools
+spec:
+  mcp:
+    targets:
+      - name: search
+        static:
+          backendRef: {name: mock-mcp-search}
+          port: 8083
+        tools: [search]
+      - name: calendar
+        static:
+          backendRef: {name: mock-mcp-calendar}
+          port: 8084
+        tools: [calendar]
+---
+apiVersion: networking.dubbo.apache.org/v1alpha3
+kind: DxgateService
+metadata:
+  name: planner
+spec:
+  a2a:
+    backendRef: {name: mock-a2a}
+    port: 8085
+    agent: planner
diff --git a/tests/e2e/agentmock/Dockerfile b/tests/e2e/agentmock/Dockerfile
new file mode 100644
index 00000000..d9c11d3b
--- /dev/null
+++ b/tests/e2e/agentmock/Dockerfile
@@ -0,0 +1,9 @@
+FROM golang:1.25-alpine AS builder
+WORKDIR /src
+COPY main.go .
+RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /agentmock main.go
+
+FROM scratch
+COPY --from=builder /agentmock /agentmock
+USER 65532:65532
+ENTRYPOINT ["/agentmock"]
diff --git a/tests/e2e/agentmock/main.go b/tests/e2e/agentmock/main.go
new file mode 100644
index 00000000..c7a91265
--- /dev/null
+++ b/tests/e2e/agentmock/main.go
@@ -0,0 +1,94 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0.
+
+package main
+
+import (
+       "encoding/json"
+       "fmt"
+       "log"
+       "net/http"
+       "os"
+       "time"
+)
+
+type request struct {
+       ID     any            `json:"id"`
+       Method string         `json:"method"`
+       Model  string         `json:"model"`
+       Params map[string]any `json:"params"`
+}
+
+func main() {
+       mode := env("MOCK_MODE", "http")
+       name := env("MOCK_NAME", mode)
+       port := env("PORT", "8080")
+       mux := http.NewServeMux()
+       mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) 
{
+               w.WriteHeader(http.StatusOK)
+       })
+       mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+               switch mode {
+               case "http":
+                       writeJSON(w, map[string]any{"service": "http", "path": 
r.URL.Path})
+               case "openai":
+                       var in request
+                       _ = json.NewDecoder(r.Body).Decode(&in)
+                       writeJSON(w, map[string]any{
+                               "id": "chatcmpl-mock", "object": 
"chat.completion", "model": in.Model,
+                               "provider_authorization": 
r.Header.Get("Authorization"),
+                               "choices":                
[]any{map[string]any{"index": 0, "message": map[string]any{"role": "assistant", 
"content": "openai-mock"}, "finish_reason": "stop"}},
+                               "usage":                  
map[string]any{"prompt_tokens": 2, "completion_tokens": 3, "total_tokens": 5},
+                       })
+               case "anthropic":
+                       var in request
+                       _ = json.NewDecoder(r.Body).Decode(&in)
+                       writeJSON(w, map[string]any{
+                               "id": "msg-mock", "type": "message", "role": 
"assistant", "model": in.Model,
+                               "content":     []any{map[string]any{"type": 
"text", "text": "anthropic-mock"}},
+                               "stop_reason": "end_turn",
+                               "usage":       map[string]any{"input_tokens": 
4, "output_tokens": 6},
+                       })
+               case "mcp":
+                       var in request
+                       _ = json.NewDecoder(r.Body).Decode(&in)
+                       result := map[string]any{}
+                       switch in.Method {
+                       case "initialize":
+                               result = map[string]any{"protocolVersion": 
"2025-03-26", "serverInfo": map[string]any{"name": name, "version": "1.0.0"}, 
"capabilities": map[string]any{"tools": map[string]any{}}}
+                       case "tools/list":
+                               result = map[string]any{"tools": 
[]any{map[string]any{"name": name, "description": name + " mock tool", 
"inputSchema": map[string]any{"type": "object"}}}}
+                       case "tools/call":
+                               result = map[string]any{"content": 
[]any{map[string]any{"type": "text", "text": name + "-ok"}}}
+                       }
+                       writeJSON(w, map[string]any{"jsonrpc": "2.0", "id": 
in.ID, "result": result})
+               case "a2a":
+                       if r.Method == http.MethodGet {
+                               writeJSON(w, map[string]any{"name": "planner", 
"version": "1.0.0", "url": "http://mock-a2a:8080/a2a"})
+                               return
+                       }
+                       var in request
+                       _ = json.NewDecoder(r.Body).Decode(&in)
+                       writeJSON(w, map[string]any{"jsonrpc": "2.0", "id": 
in.ID, "result": map[string]any{"id": "task-mock", "status": 
map[string]any{"state": "completed"}}})
+               default:
+                       http.Error(w, fmt.Sprintf("unknown MOCK_MODE %q", 
mode), http.StatusInternalServerError)
+               }
+       })
+       server := &http.Server{Addr: ":" + port, Handler: mux, 
ReadHeaderTimeout: 5 * time.Second}
+       log.Printf("agent mock mode=%s name=%s addr=%s", mode, name, 
server.Addr)
+       log.Fatal(server.ListenAndServe())
+}
+
+func env(name, fallback string) string {
+       if value := os.Getenv(name); value != "" {
+               return value
+       }
+       return fallback
+}
+
+func writeJSON(w http.ResponseWriter, value any) {
+       w.Header().Set("Content-Type", "application/json")
+       _ = json.NewEncoder(w).Encode(value)
+}
diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh
index 042a7de9..efb0d795 100755
--- a/tests/e2e/run.sh
+++ b/tests/e2e/run.sh
@@ -32,6 +32,7 @@
 #   KIND            path to the kind binary      (default: kind)
 #   KIND_NODE_IMAGE kind node image override     (default: kind release 
default)
 #   ACTIVATION_E2E  install KEDA and run real scale-to-zero E2E (default: 0)
+#   AI_MESH_E2E     run no-key HTTP/LLM/MCP/A2A E2E (default: 0)
 #   DXGATE_IMAGE    prebuilt dxgate image used by managed Gateways
 #   KEDA_VERSION    pinned KEDA chart/app version (default: 2.20.2)
 
@@ -52,7 +53,9 @@ PREVIOUS_CHART=""
 KIND="${KIND:-kind}"
 KIND_NODE_IMAGE="${KIND_NODE_IMAGE:-}"
 ACTIVATION_E2E="${ACTIVATION_E2E:-0}"
+AI_MESH_E2E="${AI_MESH_E2E:-0}"
 DXGATE_IMAGE="${DXGATE_IMAGE:-kdubbo/dxgate:latest}"
+AGENT_MOCK_IMAGE="${AGENT_MOCK_IMAGE:-kdubbo/agent-mock:latest}"
 ACTIVATION_APP_IMAGE="${ACTIVATION_APP_IMAGE:-kdubbo/activation-e2e:latest}"
 
ACTIVATION_CLIENT_IMAGE="${ACTIVATION_CLIENT_IMAGE:-kdubbo/activation-client:latest}"
 KEDA_VERSION="${KEDA_VERSION:-2.20.2}"
@@ -81,6 +84,7 @@ fail() {
 
 cleanup() {
   if [[ -n "${PF_PID:-}" ]]; then kill "${PF_PID}" 2>/dev/null || true; fi
+  if [[ -n "${AI_PF_PID:-}" ]]; then kill "${AI_PF_PID}" 2>/dev/null || true; 
fi
   if [[ "${KEEP_CLUSTER:-0}" != "1" ]]; then
     "${KIND}" delete cluster --name "${CLUSTER_NAME}" || true
   fi
@@ -90,6 +94,14 @@ cleanup() {
 }
 trap cleanup EXIT
 
+apply_activation_fixture() {
+  sed \
+    -e "s#kdubbo/activation-e2e:latest#${ACTIVATION_APP_IMAGE}#g" \
+    -e "s#kdubbo/activation-client:latest#${ACTIVATION_CLIENT_IMAGE}#g" \
+    "$1" \
+    | "${KUBECTL[@]}" apply -f -
+}
+
 prepare_previous_chart() {
   if [[ -n "${UPGRADE_FROM_CHART}" ]]; then
     [[ -e "${UPGRADE_FROM_CHART}" ]] || fail "previous chart not found: 
${UPGRADE_FROM_CHART}"
@@ -152,11 +164,18 @@ log "loading ${IMAGE} into kind"
 if [[ "${IMAGE}" != "${UPGRADE_FROM_IMAGE}" ]]; then
   "${KIND}" load docker-image "${UPGRADE_FROM_IMAGE}" --name "${CLUSTER_NAME}"
 fi
-if [[ "${ACTIVATION_E2E}" == "1" ]]; then
+if [[ "${ACTIVATION_E2E}" == "1" || "${AI_MESH_E2E}" == "1" ]]; then
   docker image inspect "${DXGATE_IMAGE}" >/dev/null 2>&1 \
-    || fail "ACTIVATION_E2E requires prebuilt ${DXGATE_IMAGE}"
-  log "building ${ACTIVATION_APP_IMAGE}"
-  docker build -t "${ACTIVATION_APP_IMAGE}" "${ROOT}/tests/e2e/activationapp"
+    || fail "data-plane E2E requires prebuilt ${DXGATE_IMAGE}"
+fi
+if [[ "${ACTIVATION_E2E}" == "1" ]]; then
+  if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
+    log "building ${ACTIVATION_APP_IMAGE}"
+    docker build -t "${ACTIVATION_APP_IMAGE}" "${ROOT}/tests/e2e/activationapp"
+  else
+    docker image inspect "${ACTIVATION_APP_IMAGE}" >/dev/null 2>&1 \
+      || fail "SKIP_BUILD=1 requires prebuilt ${ACTIVATION_APP_IMAGE}"
+  fi
   docker tag "${IMAGE}" "${ACTIVATION_CLIENT_IMAGE}"
   log "loading activation data-plane images into kind"
   "${KIND}" load docker-image \
@@ -165,6 +184,20 @@ if [[ "${ACTIVATION_E2E}" == "1" ]]; then
     "${ACTIVATION_CLIENT_IMAGE}" \
     --name "${CLUSTER_NAME}"
 fi
+if [[ "${AI_MESH_E2E}" == "1" ]]; then
+  if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
+    log "building ${AGENT_MOCK_IMAGE}"
+    docker build -t "${AGENT_MOCK_IMAGE}" "${ROOT}/tests/e2e/agentmock"
+  else
+    docker image inspect "${AGENT_MOCK_IMAGE}" >/dev/null 2>&1 \
+      || fail "SKIP_BUILD=1 requires prebuilt ${AGENT_MOCK_IMAGE}"
+  fi
+  log "loading AI mesh data-plane images into kind"
+  "${KIND}" load docker-image \
+    "${DXGATE_IMAGE}" \
+    "${AGENT_MOCK_IMAGE}" \
+    --name "${CLUSTER_NAME}"
+fi
 
 log "installing Gateway API CRDs"
 # Pin to the sigs.k8s.io/gateway-api version in go.mod. HTTPRoute retry is an
@@ -354,6 +387,93 @@ GATEWAY_ENV="$("${KUBECTL[@]}" -n "${APP_NS}" get deploy 
public-dubbo \
   | grep -qx metadata.name \
   || fail "gateway does not inject POD_NAME; demand reports would not be 
attributable to a replica"
 
+if [[ "${AI_MESH_E2E}" == "1" ]]; then
+  log "applying mesh-native HTTP, OpenAI, Anthropic, MCP, and A2A sample"
+  sed "s#kdubbo/agent-mock:latest#${AGENT_MOCK_IMAGE}#g" \
+    "${ROOT}/samples/ai-mesh/backends.yaml" \
+    | "${KUBECTL[@]}" -n "${APP_NS}" apply -f -
+  "${KUBECTL[@]}" -n "${APP_NS}" apply -f 
"${ROOT}/samples/ai-mesh/services.yaml"
+  "${KUBECTL[@]}" -n "${APP_NS}" apply -f "${ROOT}/samples/ai-mesh/routes.yaml"
+  "${KUBECTL[@]}" -n "${APP_NS}" rollout status deploy/agent-mock 
--timeout=300s \
+    || fail "agent mock deployment did not become ready"
+  "${KUBECTL[@]}" -n "${APP_NS}" rollout status deploy/public-dubbo 
--timeout=300s \
+    || fail "mesh gateway deployment did not become ready"
+
+  "${KUBECTL[@]}" get crd dxgateservices.networking.dubbo.apache.org 
>/dev/null \
+    || fail "DxgateService CRD is missing"
+  "${KUBECTL[@]}" -n "${APP_NS}" get role public-dubbo-credentials >/dev/null \
+    || fail "dxgate credential Role is missing"
+  "${KUBECTL[@]}" -n "${APP_NS}" get rolebinding public-dubbo-credentials 
>/dev/null \
+    || fail "dxgate credential RoleBinding is missing"
+  "${KUBECTL[@]}" auth can-i get secret/agent-credentials \
+    --as="system:serviceaccount:${APP_NS}:public-dubbo" -n "${APP_NS}" \
+    | grep -qx yes || fail "dxgate ServiceAccount cannot resolve referenced 
Secret"
+
+  log "port-forwarding mesh gateway"
+  "${KUBECTL[@]}" -n "${APP_NS}" port-forward svc/public-dubbo 18081:80 
>/dev/null 2>&1 &
+  AI_PF_PID=$!
+  ai_get() { curl -sf --max-time 10 "http://127.0.0.1:18081$1";; }
+  ai_post() {
+    local path="$1" body="$2"
+    curl -sf --max-time 10 -H 'content-type: application/json' \
+      -d "${body}" "http://127.0.0.1:18081${path}";
+  }
+  ai_openai() {
+    curl -sf --max-time 10 -H 'content-type: application/json' \
+      -H 'x-client-key: mock-client-key' \
+      -d '{"model":"gpt-mock","messages":[{"role":"user","content":"ping"}]}' \
+      http://127.0.0.1:18081/openai/chat/completions
+  }
+
+  retry "ordinary /users Service route" ai_get /users
+  [[ "$(ai_get /users | jq -r .path)" == "/users" ]] \
+    || fail "ordinary /users route returned the wrong backend response"
+  [[ "$(ai_get /orders | jq -r .path)" == "/orders" ]] \
+    || fail "ordinary /orders route returned the wrong backend response"
+  retry "OpenAI DxgateService route and Secret resolution" ai_openai
+  OPENAI_RESPONSE="$(ai_openai)"
+  [[ "$(jq -r .choices[0].message.content <<<"${OPENAI_RESPONSE}")" == 
"openai-mock" ]] \
+    || fail "OpenAI mock response was not proxied"
+  [[ "$(jq -r .provider_authorization <<<"${OPENAI_RESPONSE}")" == "Bearer 
mock-openai-key" ]] \
+    || fail "OpenAI provider credential was not resolved from the Secret"
+
+  ANTHROPIC_RESPONSE="$(ai_post /anthropic/chat/completions \
+    '{"model":"claude-mock","messages":[{"role":"user","content":"ping"}]}')"
+  [[ "$(jq -r .choices[0].message.content <<<"${ANTHROPIC_RESPONSE}")" == 
"anthropic-mock" ]] \
+    || fail "Anthropic native response was not translated to OpenAI"
+
+  MCP_RESPONSE="$(ai_post /mcp 
'{"jsonrpc":"2.0","id":1,"method":"tools/list"}')"
+  [[ "$(jq -r '[.result.tools[].name] | sort | join(",")' 
<<<"${MCP_RESPONSE}")" == "calendar,search" ]] \
+    || fail "MCP tools/list was not federated across both targets"
+  [[ "$(ai_post /mcp 
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{}}}'
 \
+      | jq -r .result.content[0].text)" == "search-ok" ]] \
+    || fail "MCP tools/call did not reach the selected target"
+
+  [[ "$(ai_get /.well-known/agent-card.json | jq -r .name)" == "planner" ]] \
+    || fail "A2A Agent Card was not proxied"
+  [[ "$(ai_post /a2a 
'{"jsonrpc":"2.0","id":3,"method":"message/send","params":{}}' \
+      | jq -r .result.status.state)" == "completed" ]] \
+    || fail "A2A task request did not complete"
+
+  check_agent_config_programmed() {
+    local pod
+    pod="$("${KUBECTL[@]}" -n "${APP_NS}" get pods \
+      -l gateway.networking.k8s.io/gateway-name=public \
+      -o jsonpath='{.items[0].metadata.name}')"
+    "${KUBECTL[@]}" get --raw \
+      "/api/v1/namespaces/${APP_NS}/pods/${pod}:26021/proxy/debug/config" \
+      | jq -e '
+          ([.providers[].name] | length) == 2 and
+          ([.backends[] | select(.type == "llm")] | length) == 2 and
+          ([.backends[] | select(.type == "mcp")] | length) == 2 and
+          ([.backends[] | select(.type == "a2a")] | length) == 1 and
+          ([.routes[] | .protocol] | sort | join(",")) == "a2a,llm,llm,mcp"
+        ' >/dev/null
+  }
+  retry "compiled AgentConfig visible in dxgate /debug/config" 
check_agent_config_programmed
+  log "mesh-native HTTP, LLM, MCP, and A2A E2E passed"
+fi
+
 if [[ "${ACTIVATION_E2E}" == "1" ]]; then
   log "asserting multiple Gateways have isolated resources"
   "${KUBECTL[@]}" -n "${APP_NS}" get deploy dxgate-gateway public-dubbo 
>/dev/null \
@@ -364,7 +484,7 @@ if [[ "${ACTIVATION_E2E}" == "1" ]]; then
     || fail "second managed gateway did not become ready"
 
   log "deploying the proxyless activation target and KEDA ScaledObject"
-  "${KUBECTL[@]}" apply -f 
"${ROOT}/tests/e2e/testdata/eastwest-activation.yaml"
+  apply_activation_fixture 
"${ROOT}/tests/e2e/testdata/eastwest-activation.yaml"
   "${KUBECTL[@]}" apply -f 
"${ROOT}/tests/e2e/testdata/eastwest-activation-scaledobject.yaml"
   "${KUBECTL[@]}" -n "${APP_NS}" wait --for=condition=Ready 
scaledobject/payment --timeout=180s \
     || fail "KEDA ScaledObject did not become ready"
@@ -426,7 +546,7 @@ if [[ "${ACTIVATION_E2E}" == "1" ]]; then
 
   log "sending one proxyless request while payment is at zero"
   "${KUBECTL[@]}" -n "${APP_NS}" delete pod payment-client --ignore-not-found
-  "${KUBECTL[@]}" apply -f 
"${ROOT}/tests/e2e/testdata/eastwest-activation-client.yaml"
+  apply_activation_fixture 
"${ROOT}/tests/e2e/testdata/eastwest-activation-client.yaml"
   activation_metrics() {
     local pod
     while read -r pod; do
@@ -475,7 +595,7 @@ if [[ "${ACTIVATION_E2E}" == "1" ]]; then
     local before after
     before="$(activation_payment_requests_total)"
     "${KUBECTL[@]}" -n "${APP_NS}" delete pod payment-client 
--ignore-not-found --wait=true >/dev/null
-    "${KUBECTL[@]}" apply -f 
"${ROOT}/tests/e2e/testdata/eastwest-activation-client.yaml" >/dev/null
+    apply_activation_fixture 
"${ROOT}/tests/e2e/testdata/eastwest-activation-client.yaml" >/dev/null
     "${KUBECTL[@]}" -n "${APP_NS}" wait \
       
--for=jsonpath='{.status.containerStatuses[?(@.name=="client")].state.terminated.exitCode}'=0
 \
       pod/payment-client --timeout=30s >/dev/null || return 1

Reply via email to