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

hengyuss pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 819b92a63a refactor(ai-proxy): introduce protocol conversion 
abstraction (#7910)
819b92a63a is described below

commit 819b92a63ad514b0508382f657a17def4b1dd9d3
Author: hengyuss <[email protected]>
AuthorDate: Thu Oct 22 09:58:41 2026 +0800

    refactor(ai-proxy): introduce protocol conversion abstraction (#7910)
---
 .../enhanced/conversion/AiProtocolConversion.java  |  51 ++++
 .../enhanced/conversion/AiProtocolConverter.java   |  49 ++++
 .../conversion/AiProtocolConverterFactory.java     | 132 +++++++++++
 .../ConnectResponsesToChatConverter.java}          | 117 ++++++----
 .../conversion/IdentityAiProtocolConverter.java    |  63 +++++
 .../enhanced/provider/AiProviderCapabilities.java  |   9 +-
 .../proxy/enhanced/provider/AiProxyProvider.java   |  15 +-
 .../provider/ConnectProviderDialectAdapter.java    |  95 ++++++++
 .../plugin/ai/proxy/enhanced/provider/OpenAi.java  |  24 +-
 .../ai/proxy/enhanced/service/AiProxyEngine.java   |  67 ++++--
 .../enhanced/AiProxyHttpCompatibilityTest.java     |   3 +-
 .../ai/proxy/enhanced/AiProxyPluginTest.java       |   3 +-
 .../plugin/ai/proxy/enhanced/AiProxyTestUtils.java |  41 ++++
 .../conversion/AiProtocolConverterFactoryTest.java | 147 ++++++++++++
 .../ConnectResponsesToChatConverterTest.java       | 227 ++++++++++++++++++
 .../protocol/ConnectProtocolAdapterTest.java       | 258 ---------------------
 .../enhanced/provider/AiProxyProviderHttpTest.java |   4 +-
 .../ConnectProviderDialectAdapterTest.java         |  86 +++++++
 .../provider/OpenAiProviderOptionsTest.java        |  20 +-
 .../service/AiProxyEngineOrchestrationTest.java    |  21 +-
 .../proxy/enhanced/service/AiProxyEngineTest.java  |  26 ++-
 .../service/AiProxyStreamCancellationTest.java     |   3 +-
 .../ai/proxy/AiProxyPluginConfiguration.java       |  31 ++-
 .../ai/proxy/AiProxyPluginConfigurationTest.java   |   9 +-
 24 files changed, 1105 insertions(+), 396 deletions(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConversion.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConversion.java
new file mode 100644
index 0000000000..6f59854172
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConversion.java
@@ -0,0 +1,51 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.conversion;
+
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
+
+import java.util.Objects;
+
+/**
+ * Request-scoped protocol conversion binding a target request to its reverse 
response adapter.
+ *
+ * @param request converted target protocol request
+ * @param responseAdapter reverse response adapter for this conversion
+ */
+public record AiProtocolConversion(
+        ShenyuAiRequest request,
+        AiResponseAdapter responseAdapter) {
+
+    /**
+     * Reject incomplete conversion results.
+     */
+    public AiProtocolConversion {
+        Objects.requireNonNull(request, "Converted AI request is required");
+        Objects.requireNonNull(responseAdapter, "AI response adapter is 
required");
+    }
+
+    /**
+     * Create a conversion that leaves both request and response unchanged.
+     *
+     * @param request request to retain
+     * @return identity protocol conversion
+     */
+    public static AiProtocolConversion identity(final ShenyuAiRequest request) 
{
+        return new AiProtocolConversion(request, AiResponseAdapter.identity());
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverter.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverter.java
new file mode 100644
index 0000000000..32d2965cf4
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverter.java
@@ -0,0 +1,49 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.conversion;
+
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
+
+/**
+ * Converts a client protocol request to a target protocol and binds reverse 
response conversion.
+ */
+public interface AiProtocolConverter {
+
+    /**
+     * Get the source client protocol identifier.
+     *
+     * @return source protocol identifier
+     */
+    String sourceProtocol();
+
+    /**
+     * Get the target upstream protocol identifier.
+     *
+     * @return target protocol identifier
+     */
+    String targetProtocol();
+
+    /**
+     * Create one request-scoped bidirectional conversion.
+     *
+     * @param request source protocol request
+     * @param stream effective streaming mode
+     * @return converted request and its reverse response adapter
+     */
+    AiProtocolConversion convert(ShenyuAiRequest request, boolean stream);
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverterFactory.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverterFactory.java
new file mode 100644
index 0000000000..53646589ea
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverterFactory.java
@@ -0,0 +1,132 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.conversion;
+
+import org.apache.shenyu.common.exception.ShenyuException;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Registry for directional AI protocol converters.
+ */
+public final class AiProtocolConverterFactory {
+
+    private final Map<ConversionKey, AiProtocolConverter> converters;
+
+    /**
+     * Create a converter registry and reject duplicate directional routes.
+     *
+     * @param converters protocol converters
+     */
+    public AiProtocolConverterFactory(final Collection<AiProtocolConverter> 
converters) {
+        this.converters = new LinkedHashMap<>();
+        for (AiProtocolConverter converter : converters) {
+            Objects.requireNonNull(converter, "AI protocol converter is 
required");
+            ConversionKey key = new ConversionKey(converter.sourceProtocol(), 
converter.targetProtocol());
+            AiProtocolConverter previous = this.converters.putIfAbsent(key, 
converter);
+            if (Objects.nonNull(previous)) {
+                throw new IllegalArgumentException("Duplicate AI protocol 
converter: "
+                        + key.sourceProtocol() + " -> " + 
key.targetProtocol());
+            }
+        }
+    }
+
+    /**
+     * Get a converter for an exact directional route.
+     *
+     * @param sourceProtocol source protocol identifier
+     * @param targetProtocol target protocol identifier
+     * @return resolved protocol converter
+     */
+    public AiProtocolConverter getConverter(final String sourceProtocol, final 
String targetProtocol) {
+        ConversionKey key = new ConversionKey(sourceProtocol, targetProtocol);
+        if (sourceProtocol.equals(targetProtocol)) {
+            return new IdentityAiProtocolConverter(sourceProtocol);
+        }
+        AiProtocolConverter converter = converters.get(key);
+        if (Objects.isNull(converter)) {
+            throw new ShenyuException("Unsupported AI protocol conversion: "
+                    + sourceProtocol + " -> " + targetProtocol);
+        }
+        return converter;
+    }
+
+    /**
+     * Resolve a converter from a client protocol to one of the protocols 
natively accepted by a provider.
+     * Native support takes precedence over registered conversion routes. When 
multiple routes are available,
+     * converter registration order determines the selected target.
+     *
+     * @param sourceProtocol client protocol identifier
+     * @param targetProtocols protocols natively accepted by the provider
+     * @return identity or directional protocol converter
+     */
+    public AiProtocolConverter resolveConverter(final String sourceProtocol, 
final Collection<String> targetProtocols) {
+        Objects.requireNonNull(sourceProtocol, "Source AI protocol is 
required");
+        Objects.requireNonNull(targetProtocols, "Target AI protocols are 
required");
+        if (sourceProtocol.isBlank()) {
+            throw new IllegalArgumentException("Source AI protocol must not be 
blank");
+        }
+        for (String targetProtocol : targetProtocols) {
+            Objects.requireNonNull(targetProtocol, "Target AI protocol is 
required");
+            if (targetProtocol.isBlank()) {
+                throw new IllegalArgumentException("Target AI protocol must 
not be blank");
+            }
+        }
+        if (targetProtocols.contains(sourceProtocol)) {
+            return new IdentityAiProtocolConverter(sourceProtocol);
+        }
+        for (Map.Entry<ConversionKey, AiProtocolConverter> entry : 
converters.entrySet()) {
+            if (entry.getKey().sourceProtocol().equals(sourceProtocol)
+                    && 
targetProtocols.contains(entry.getKey().targetProtocol())) {
+                return entry.getValue();
+            }
+        }
+        throw new ShenyuException("Unsupported AI protocol conversion from " + 
sourceProtocol
+                + " to provider protocols " + targetProtocols);
+    }
+
+    /**
+     * Determine whether an identity or registered directional route is 
supported.
+     *
+     * @param sourceProtocol source protocol identifier
+     * @param targetProtocol target protocol identifier
+     * @return whether the route is supported
+     */
+    public boolean supports(final String sourceProtocol, final String 
targetProtocol) {
+        if (Objects.isNull(sourceProtocol) || Objects.isNull(targetProtocol)
+                || sourceProtocol.isBlank() || targetProtocol.isBlank()) {
+            return false;
+        }
+        return sourceProtocol.equals(targetProtocol)
+                || converters.containsKey(new ConversionKey(sourceProtocol, 
targetProtocol));
+    }
+
+    private record ConversionKey(String sourceProtocol, String targetProtocol) 
{
+
+        private ConversionKey {
+            Objects.requireNonNull(sourceProtocol, "Source AI protocol is 
required");
+            Objects.requireNonNull(targetProtocol, "Target AI protocol is 
required");
+            if (sourceProtocol.isBlank() || targetProtocol.isBlank()) {
+                throw new IllegalArgumentException("AI protocol identifiers 
must not be blank");
+            }
+        }
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/protocol/ConnectProtocolAdapter.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/ConnectResponsesToChatConverter.java
similarity index 63%
rename from 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/protocol/ConnectProtocolAdapter.java
rename to 
shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/ConnectResponsesToChatConverter.java
index ef257f6999..ea6409b29d 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/protocol/ConnectProtocolAdapter.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/ConnectResponsesToChatConverter.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.plugin.ai.proxy.enhanced.protocol;
+package org.apache.shenyu.plugin.ai.proxy.enhanced.conversion;
 
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -29,11 +29,13 @@ import io.github.agenticai.connect.Diagnostic;
 import io.github.agenticai.connect.Format;
 import io.github.agenticai.connect.Json;
 import io.github.agenticai.connect.Options;
-import io.github.agenticai.connect.ProviderProfile;
 import io.github.agenticai.connect.ResponseStreamState;
 import io.github.agenticai.connect.StreamOptions;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamResponse;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiChat;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiResponses;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import reactor.core.publisher.Flux;
@@ -46,12 +48,11 @@ import java.util.Map;
 import java.util.Objects;
 
 /**
- * Request-scoped bridge between ShenYu's reactive HTTP boundary and Connect's 
JSON codecs.
- * No transport, routing, credentials, retry or billing policy is delegated to 
Connect.
+ * Converts OpenAI Responses requests to Chat Completions and binds the 
reverse response conversion.
  */
-public final class ConnectProtocolAdapter {
+public final class ConnectResponsesToChatConverter implements 
AiProtocolConverter {
 
-    private static final Logger LOG = 
LoggerFactory.getLogger(ConnectProtocolAdapter.class);
+    private static final Logger LOG = 
LoggerFactory.getLogger(ConnectResponsesToChatConverter.class);
 
     private static final ObjectMapper MAPPER = new 
ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
 
@@ -59,90 +60,101 @@ public final class ConnectProtocolAdapter {
 
     private static final int MAX_CONVERSION_BYTES = 5 * 1024 * 1024;
 
-    private final Format clientFormat;
+    @Override
+    public String sourceProtocol() {
+        return OpenAiResponses.NAME;
+    }
+
+    @Override
+    public String targetProtocol() {
+        return OpenAiChat.NAME;
+    }
 
-    private final ConversionContext context;
+    @Override
+    public AiProtocolConversion convert(final ShenyuAiRequest request, final 
boolean stream) {
+        Objects.requireNonNull(request, "AI request is required");
+        if (!sourceProtocol().equals(request.getProtocol())) {
+            throw new AiProxyException(AiProxyException.Kind.REQUEST,
+                    "Expected OpenAI Responses request", null);
+        }
 
-    /**
-     * Create an isolated context for a request and its response, never a 
singleton context.
-     *
-     * @param protocol client protocol identifier
-     * @param upstreamModel authoritative routed model, or null to retain the 
client model
-     * @param profile explicitly selected provider dialect, or null for 
generic Chat
-     */
-    public ConnectProtocolAdapter(final String protocol, final String 
upstreamModel, final ProviderProfile profile) {
-        clientFormat = OpenAiResponses.NAME.equals(protocol) ? 
Format.OPENAI_RESPONSES : Format.OPENAI_CHAT;
+        ConversionContext context = createContext(request);
+        ObjectNode convertedPayload = convertRequest(request.getRawPayload(), 
context);
+        ShenyuAiRequest convertedRequest = new OpenAiChat().decodeRequest(
+                convertedPayload.toString(), convertedPayload);
+        AiResponseAdapter responseAdapter = response -> 
convertResponse(response, stream, context);
+        return new AiProtocolConversion(convertedRequest, responseAdapter);
+    }
+
+    private ConversionContext createContext(final ShenyuAiRequest request) {
         Options options = new Options();
         options.toolLossPolicy = Options.LossPolicy.SAFE;
-        options.providerProfile = profile;
-        context = new ConversionContext(options);
-        context.upstreamModel = Objects.isNull(upstreamModel) ? "" : 
upstreamModel;
+        ConversionContext context = new ConversionContext(options);
+        context.upstreamModel = Objects.requireNonNullElse(request.getModel(), 
"");
+        return context;
     }
 
-    /**
-     * Convert the final gateway-configured payload to Chat and apply the 
provider profile once.
-     *
-     * @param payload copied client payload with gateway overrides applied
-     * @return converted payload
-     */
-    public ObjectNode convertRequest(final ObjectNode payload) {
+    private ObjectNode convertRequest(final ObjectNode payload, final 
ConversionContext context) {
+        if (Objects.isNull(payload)) {
+            throw new AiProxyException(AiProxyException.Kind.REQUEST, "AI 
request payload is required", null);
+        }
         try {
-            ConversionResult result = CONNECT.convertRequest(clientFormat, 
Format.OPENAI_CHAT, Json.object(payload.toString()), context);
+            ConversionResult result = 
CONNECT.convertRequest(Format.OPENAI_RESPONSES, Format.OPENAI_CHAT,
+                    Json.object(payload.toString()), context);
             reportDiagnostics(result.diagnostics());
             return (ObjectNode) MAPPER.readTree(result.json());
         } catch (Exception error) {
-            if (error instanceof ConversionException) {
-                reportDiagnostics(((ConversionException) error).diagnostics());
+            if (error instanceof ConversionException conversionException) {
+                reportDiagnostics(conversionException.diagnostics());
             }
             throw new AiProxyException(AiProxyException.Kind.REQUEST, "AI 
request protocol conversion failed", error);
         }
     }
 
-    /**
-     * Convert successful Responses bodies; preserve Chat and upstream HTTP 
errors unchanged.
-     *
-     * @param response upstream response with an unconsumed body
-     * @param stream effective streaming mode
-     * @return adapted response without subscribing or blocking
-     */
-    public AiUpstreamResponse convertResponse(final AiUpstreamResponse 
response, final boolean stream) {
-        if (clientFormat == Format.OPENAI_CHAT || response.getStatusCode() < 
200 || response.getStatusCode() >= 300) {
+    private AiUpstreamResponse convertResponse(final AiUpstreamResponse 
response, final boolean stream,
+                                               final ConversionContext 
context) {
+        if (response.getStatusCode() < 200 || response.getStatusCode() >= 300) 
{
             return response;
         }
         if (stream) {
             // Reuse ShenYu's incremental SSE framing and content-type 
validation, not a library HTTP client.
             AiUpstreamResponse framed = new 
OpenAiChat().encodeResponse(response, true);
-            return new AiUpstreamResponse(response.getStatusCode(), 
responseHeaders(response, "text/event-stream"), 
convertStream(framed.getBody()));
+            return new AiUpstreamResponse(response.getStatusCode(), 
responseHeaders(response, "text/event-stream"),
+                    convertStream(framed.getBody(), context));
         }
         Flux<byte[]> body = 
response.getBody().reduceWith(ByteArrayOutputStream::new, (buffer, bytes) -> {
             if (bytes.length > MAX_CONVERSION_BYTES - buffer.size()) {
-                throw new AiProxyException(AiProxyException.Kind.PROTOCOL, "AI 
response exceeds the conversion size limit", null);
+                throw new AiProxyException(AiProxyException.Kind.PROTOCOL,
+                        "AI response exceeds the conversion size limit", null);
             }
             buffer.writeBytes(bytes);
             return buffer;
-        }).map(buffer -> 
convertCompleteResponse(buffer.toString(StandardCharsets.UTF_8))).flux();
+        }).map(buffer -> 
convertCompleteResponse(buffer.toString(StandardCharsets.UTF_8), 
context)).flux();
         return new AiUpstreamResponse(response.getStatusCode(), 
responseHeaders(response, "application/json"), body);
     }
 
-    private byte[] convertCompleteResponse(final String body) {
+    private byte[] convertCompleteResponse(final String body, final 
ConversionContext context) {
         try {
             JsonNode payload = MAPPER.readTree(body);
             if (Objects.isNull(payload) || !payload.isObject()) {
                 throw new IllegalArgumentException("Expected an AI response 
object");
             }
-            ConversionResult result = 
CONNECT.convertResponse(Format.OPENAI_CHAT, clientFormat, 
Json.object(payload.toString()), context);
+            ConversionResult result = 
CONNECT.convertResponse(Format.OPENAI_CHAT, Format.OPENAI_RESPONSES,
+                    Json.object(payload.toString()), context);
             reportDiagnostics(result.diagnostics());
             return result.json().getBytes(StandardCharsets.UTF_8);
         } catch (Exception error) {
-            throw new AiProxyException(AiProxyException.Kind.PROTOCOL, "AI 
response protocol conversion failed", error);
+            throw new AiProxyException(AiProxyException.Kind.PROTOCOL,
+                    "AI response protocol conversion failed", error);
         }
     }
 
-    private Flux<byte[]> convertStream(final Flux<byte[]> framed) {
+    private Flux<byte[]> convertStream(final Flux<byte[]> framed, final 
ConversionContext context) {
         return Flux.defer(() -> {
             StreamOptions options = new StreamOptions();
             options.emitSequenceNumber = true;
-            ResponseStreamState state = 
CONNECT.newResponseStream(Format.OPENAI_CHAT, clientFormat, options, context);
+            ResponseStreamState state = CONNECT.newResponseStream(
+                    Format.OPENAI_CHAT, Format.OPENAI_RESPONSES, options, 
context);
             int[] size = {0};
             return framed.concatMapIterable(bytes -> {
                 // The framer produces exactly one validated event, retaining 
multiline JSON.
@@ -152,7 +164,8 @@ public final class ConnectProtocolAdapter {
                     return List.<ConversionResult>of();
                 }
                 if (bytes.length > MAX_CONVERSION_BYTES - size[0]) {
-                    throw new AiProxyException(AiProxyException.Kind.PROTOCOL, 
"AI stream exceeds the conversion size limit", null);
+                    throw new AiProxyException(AiProxyException.Kind.PROTOCOL,
+                            "AI stream exceeds the conversion size limit", 
null);
                 }
                 size[0] += bytes.length;
                 return state.accept(payload);
@@ -162,7 +175,8 @@ public final class ConnectProtocolAdapter {
                 return Flux.fromIterable(terminal);
             })).map(this::frameEvent)
                     .onErrorMap(error -> !(error instanceof AiProxyException),
-                            error -> new 
AiProxyException(AiProxyException.Kind.PROTOCOL, "AI stream protocol conversion 
failed", error));
+                            error -> new 
AiProxyException(AiProxyException.Kind.PROTOCOL,
+                                    "AI stream protocol conversion failed", 
error));
         });
     }
 
@@ -173,8 +187,9 @@ public final class ConnectProtocolAdapter {
 
     private Map<String, List<String>> responseHeaders(final AiUpstreamResponse 
response, final String contentType) {
         Map<String, List<String>> headers = new 
LinkedHashMap<>(response.getHeaders());
-        headers.keySet().removeIf(name -> 
"Content-Length".equalsIgnoreCase(name) || "Content-Type".equalsIgnoreCase(name)
-                || "ETag".equalsIgnoreCase(name) || 
"Content-MD5".equalsIgnoreCase(name) || "Digest".equalsIgnoreCase(name));
+        headers.keySet().removeIf(name -> 
"Content-Length".equalsIgnoreCase(name)
+                || "Content-Type".equalsIgnoreCase(name) || 
"ETag".equalsIgnoreCase(name)
+                || "Content-MD5".equalsIgnoreCase(name) || 
"Digest".equalsIgnoreCase(name));
         headers.put("Content-Type", List.of(contentType));
         return headers;
     }
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/IdentityAiProtocolConverter.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/IdentityAiProtocolConverter.java
new file mode 100644
index 0000000000..6ad971d2ed
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/IdentityAiProtocolConverter.java
@@ -0,0 +1,63 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.conversion;
+
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
+
+import java.util.Objects;
+
+/**
+ * Identity conversion for requests whose source and target protocols are the 
same.
+ */
+public final class IdentityAiProtocolConverter implements AiProtocolConverter {
+
+    private final String protocol;
+
+    /**
+     * Create an identity converter for one protocol.
+     *
+     * @param protocol protocol identifier
+     */
+    public IdentityAiProtocolConverter(final String protocol) {
+        this.protocol = Objects.requireNonNull(protocol, "AI protocol is 
required");
+        if (protocol.isBlank()) {
+            throw new IllegalArgumentException("AI protocol must not be 
blank");
+        }
+    }
+
+    @Override
+    public String sourceProtocol() {
+        return protocol;
+    }
+
+    @Override
+    public String targetProtocol() {
+        return protocol;
+    }
+
+    @Override
+    public AiProtocolConversion convert(final ShenyuAiRequest request, final 
boolean stream) {
+        Objects.requireNonNull(request, "AI request is required");
+        if (!protocol.equals(request.getProtocol())) {
+            throw new AiProxyException(AiProxyException.Kind.REQUEST,
+                    "Expected AI protocol: " + protocol, null);
+        }
+        return AiProtocolConversion.identity(request);
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProviderCapabilities.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProviderCapabilities.java
index 20d3d1dfe0..8ef056a22d 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProviderCapabilities.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProviderCapabilities.java
@@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.JsonNode;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiChat;
-import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiResponses;
 
 import java.util.EnumSet;
 import java.util.Objects;
@@ -30,7 +29,7 @@ import java.util.Set;
 /**
  * Declared provider capabilities, independent of HTTP execution and model 
discovery.
  *
- * @param protocols supported client protocols
+ * @param protocols protocols accepted by the provider-facing request builder
  * @param operations supported AI operations
  * @param features supported request features
  */
@@ -46,13 +45,13 @@ public record AiProviderCapabilities(Set<String> protocols, 
Set<ShenyuAiRequest.
     }
 
     /**
-     * General Chat upstream capabilities, including conversion from Responses 
clients.
+     * General OpenAI Chat upstream capabilities.
      *
      * @return general capabilities
      */
     public static AiProviderCapabilities chat() {
-        return new AiProviderCapabilities(Set.of(OpenAiChat.NAME, 
OpenAiResponses.NAME),
-                EnumSet.of(ShenyuAiRequest.Operation.CHAT_COMPLETION, 
ShenyuAiRequest.Operation.RESPONSE),
+        return new AiProviderCapabilities(Set.of(OpenAiChat.NAME),
+                EnumSet.of(ShenyuAiRequest.Operation.CHAT_COMPLETION),
                 EnumSet.of(Feature.STREAMING, Feature.TOOL_CALLING, 
Feature.CUSTOM_TOOLS, Feature.JSON_MODE,
                         Feature.JSON_SCHEMA, Feature.STREAM_USAGE, 
Feature.AUDIO_OUTPUT));
     }
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProvider.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProvider.java
index 5bec34746f..2f9276fee5 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProvider.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProvider.java
@@ -20,7 +20,6 @@ package org.apache.shenyu.plugin.ai.proxy.enhanced.provider;
 import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiResponseAdapter;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamRequest;
-import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamResponse;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.PreparedAiRequest;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
 
@@ -58,25 +57,15 @@ public interface AiProxyProvider {
     }
 
     /**
-     * Check whether the provider supports a client protocol.
+     * Check whether the provider natively accepts an upstream protocol.
      *
-     * @param protocol client protocol identifier
+     * @param protocol upstream protocol identifier
      * @return whether supported
      */
     default boolean supports(final String protocol) {
         return Objects.nonNull(protocol) && 
capabilities().protocols().contains(protocol);
     }
 
-    /**
-     * Adapt provider-specific response details before protocol encoding.
-     *
-     * @param response raw upstream response
-     * @return adapted response
-     */
-    default AiUpstreamResponse adaptResponse(final AiUpstreamResponse 
response) {
-        return response;
-    }
-
     /**
      * Prepare an upstream request and the response conversion bound to it.
      * Existing providers receive an identity response adapter by default.
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/ConnectProviderDialectAdapter.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/ConnectProviderDialectAdapter.java
new file mode 100644
index 0000000000..8783112196
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/ConnectProviderDialectAdapter.java
@@ -0,0 +1,95 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.provider;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.github.agenticai.connect.Connect;
+import io.github.agenticai.connect.ConversionContext;
+import io.github.agenticai.connect.ConversionException;
+import io.github.agenticai.connect.ConversionResult;
+import io.github.agenticai.connect.Diagnostic;
+import io.github.agenticai.connect.Format;
+import io.github.agenticai.connect.Json;
+import io.github.agenticai.connect.Options;
+import io.github.agenticai.connect.ProviderProfile;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Applies a provider-specific dialect to an OpenAI Chat request.
+ */
+public final class ConnectProviderDialectAdapter {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ConnectProviderDialectAdapter.class);
+
+    private static final ObjectMapper MAPPER = new 
ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);
+
+    private static final Connect CONNECT = new Connect();
+
+    private final ConversionContext context;
+
+    /**
+     * Create a request-scoped provider dialect adapter.
+     *
+     * @param upstreamModel authoritative routed model, or null to retain the 
request model
+     * @param profile explicitly selected provider dialect, or null for 
generic Chat
+     */
+    public ConnectProviderDialectAdapter(final String upstreamModel, final 
ProviderProfile profile) {
+        Options options = new Options();
+        options.toolLossPolicy = Options.LossPolicy.SAFE;
+        options.providerProfile = profile;
+        context = new ConversionContext(options);
+        context.upstreamModel = Objects.requireNonNullElse(upstreamModel, "");
+    }
+
+    /**
+     * Apply the provider dialect without changing the Chat protocol.
+     *
+     * @param payload standard OpenAI Chat payload
+     * @return provider-specific Chat payload
+     */
+    public ObjectNode adapt(final ObjectNode payload) {
+        Objects.requireNonNull(payload, "AI provider payload is required");
+        try {
+            ConversionResult result = 
CONNECT.convertRequest(Format.OPENAI_CHAT, Format.OPENAI_CHAT,
+                    Json.object(payload.toString()), context);
+            reportDiagnostics(result.diagnostics());
+            return (ObjectNode) MAPPER.readTree(result.json());
+        } catch (Exception error) {
+            if (error instanceof ConversionException conversionException) {
+                reportDiagnostics(conversionException.diagnostics());
+            }
+            throw new AiProxyException(AiProxyException.Kind.REQUEST,
+                    "AI provider dialect adaptation failed", error);
+        }
+    }
+
+    private void reportDiagnostics(final List<Diagnostic> diagnostics) {
+        for (Diagnostic diagnostic : diagnostics) {
+            // Never log payloads, credentials, private reasoning or library 
diagnostic messages.
+            LOG.warn("AI provider dialect diagnostic: code={}, severity={}, 
from={}, to={}",
+                    diagnostic.code(), diagnostic.severity(), 
diagnostic.from(), diagnostic.to());
+        }
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAi.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAi.java
index ac8e52cb2d..efa224af38 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAi.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAi.java
@@ -27,8 +27,6 @@ import 
org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamRequest;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.PreparedAiRequest;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
-import 
org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.ConnectProtocolAdapter;
-import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiResponses;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
@@ -132,21 +130,17 @@ public final class OpenAi implements AiProxyProvider {
             if (config.getMaxTokens() <= 0) {
                 throw new IllegalArgumentException("AI maxTokens must be 
positive");
             }
-            if (OpenAiResponses.NAME.equals(request.getProtocol())) {
-                payload.put("max_output_tokens", config.getMaxTokens());
-            } else {
-                String field = options.maxTokensField().isEmpty()
-                        ? (payload.hasNonNull("max_completion_tokens") ? 
"max_completion_tokens" : "max_tokens") : options.maxTokensField();
-                payload.remove("max_tokens");
-                payload.remove("max_completion_tokens");
-                payload.put(field, config.getMaxTokens());
-            }
+            String field = options.maxTokensField().isEmpty()
+                    ? (payload.hasNonNull("max_completion_tokens") ? 
"max_completion_tokens" : "max_tokens") : options.maxTokensField();
+            payload.remove("max_tokens");
+            payload.remove("max_completion_tokens");
+            payload.put(field, config.getMaxTokens());
         }
         if (!request.hasStream() && Objects.nonNull(config.getStream())) {
             payload.put("stream", stream);
         }
-        ConnectProtocolAdapter adapter = new 
ConnectProtocolAdapter(request.getProtocol(), mappedModel, 
options.connectProfile());
-        ObjectNode converted = adapter.convertRequest(payload);
+        ConnectProviderDialectAdapter adapter = new 
ConnectProviderDialectAdapter(mappedModel, options.connectProfile());
+        ObjectNode converted = adapter.adapt(payload);
         JsonNode convertedStream = converted.get("stream");
         if (Objects.nonNull(convertedStream) && !convertedStream.isNull() && 
!convertedStream.isBoolean()
                 || converted.path("stream").asBoolean() != stream) {
@@ -162,9 +156,7 @@ public final class OpenAi implements AiProxyProvider {
         upstream.setHeaders(headers(config, stream));
         String body = request.getRawPayload().equals(converted) ? 
request.getRawBody() : converted.toString();
         upstream.setBody(body.getBytes(StandardCharsets.UTF_8));
-        AiResponseAdapter responseAdapter =
-                response -> adapter.convertResponse(response, stream);
-        return new PreparedAiRequest(upstream, responseAdapter);
+        return new PreparedAiRequest(upstream, AiResponseAdapter.identity());
     }
 
     private Map<String, List<String>> headers(final AiCommonConfig config, 
final boolean stream) {
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngine.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngine.java
index 401c2b2f80..2fdc3055e0 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngine.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/main/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngine.java
@@ -24,6 +24,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.shenyu.common.exception.ShenyuException;
 import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConversion;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConverter;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConverterFactory;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiResponseAdapter;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamRequest;
@@ -58,6 +61,8 @@ public final class AiProxyEngine {
 
     private final AiProxyProviderFactory aiProxyProviderFactory;
 
+    private final AiProtocolConverterFactory aiProtocolConverterFactory;
+
     private final AiProxyTransport aiProxyTransport;
 
     private final AiProxyRetryProperties retryProperties;
@@ -65,25 +70,30 @@ public final class AiProxyEngine {
     public AiProxyEngine(
             final AiProxyProtocolFactory protocols,
             final AiProxyProviderFactory providers,
+            final AiProtocolConverterFactory converters,
             final AiProxyTransport transport) {
         this(
                 protocols,
                 providers,
+                converters,
                 transport,
                 new AiProxyRetryProperties());
     }
 
     public AiProxyEngine(final AiProxyProtocolFactory protocols, final 
AiProxyProviderFactory providers,
-                         final AiProxyTransport transport, final 
AiProxyRetryProperties retryProperties) {
-        this.aiProxyProtocolFactory = protocols;
-        this.aiProxyProviderFactory = providers;
-        this.aiProxyTransport = transport;
+                         final AiProtocolConverterFactory converters, final 
AiProxyTransport transport,
+                         final AiProxyRetryProperties retryProperties) {
+        this.aiProxyProtocolFactory = Objects.requireNonNull(protocols, 
"protocols must not be null");
+        this.aiProxyProviderFactory = Objects.requireNonNull(providers, 
"providers must not be null");
+        this.aiProtocolConverterFactory = Objects.requireNonNull(converters, 
"converters must not be null");
+        this.aiProxyTransport = Objects.requireNonNull(transport, "transport 
must not be null");
         this.retryProperties = Objects.requireNonNull(retryProperties, 
"retryProperties must not be null");
     }
 
     public AiProxyEngine(final AiProxyProtocolFactory protocols, final 
AiProxyProviderFactory providers,
-                        final AiProxyTransport transport, final Duration 
retryBackoff) {
-        this(protocols, providers, transport, new AiProxyRetryProperties(3, 1, 
retryBackoff));
+                         final AiProtocolConverterFactory converters, final 
AiProxyTransport transport,
+                         final Duration retryBackoff) {
+        this(protocols, providers, converters, transport, new 
AiProxyRetryProperties(3, 1, retryBackoff));
     }
 
     /**
@@ -105,7 +115,7 @@ public final class AiProxyEngine {
                 AiCommonConfig copy = new AiCommonConfig(config);
                 copy.setStream(primary.stream());
                 // Fallback changes the upstream, never the already selected 
client protocol.
-                copy.setProtocol(primary.protocol().name());
+                copy.setProtocol(primary.clientProtocol());
                 return copy;
             });
             final int maxRetries = 
retryProperties.maxRetries(primary.stream());
@@ -138,10 +148,8 @@ public final class AiProxyEngine {
                         }
                         return response.getBody().then(Mono.error(new 
UpstreamStatusFailure(status)));
                     }
-                    AiUpstreamResponse providerAdapted = 
prepared.provider().adaptResponse(response);
-                    AiUpstreamResponse requestAdapted = 
prepared.responseAdapter().adapt(providerAdapted);
-                    AiUpstreamResponse encoded = 
prepared.protocol().encodeResponse(requestAdapted, prepared.stream());
-                    return consumeResponse(encoded, consumer, emitted);
+                    AiUpstreamResponse adapted = 
prepared.responseAdapter().adapt(response);
+                    return consumeResponse(adapted, consumer, emitted);
                 })
                 .onErrorResume(error -> {
                     final AiProxyRetryPolicy.Decision decision;
@@ -224,28 +232,45 @@ public final class AiProxyEngine {
         if (!protocol.matches(payload)) {
             throw new ShenyuException("Request body does not match AI 
protocol: " + protocol.name());
         }
-        ShenyuAiRequest decoded = protocol.decodeRequest(upstreamBody, 
payload);
+        ShenyuAiRequest decoded = 
applyModelOverride(protocol.decodeRequest(upstreamBody, payload), protocol, 
config);
         AiProxyProvider provider = aiProxyProviderFactory.getProvider(
                 Objects.isNull(config.getProvider()) || 
config.getProvider().isBlank() ? "openai" : config.getProvider());
-        if (!provider.supports(protocol.name())) {
-            throw new ShenyuException("Unsupported AI protocol/provider 
combination");
-        }
         boolean stream = decoded.hasStream() ? decoded.isStream() : 
Boolean.TRUE.equals(config.getStream());
-        provider.capabilities().validate(decoded, stream);
+        AiProtocolConverter converter = 
aiProtocolConverterFactory.resolveConverter(
+                decoded.getProtocol(), provider.capabilities().protocols());
+        AiProtocolConversion conversion = converter.convert(decoded, stream);
+        ShenyuAiRequest providerRequest = conversion.request();
+        if (!provider.supports(providerRequest.getProtocol())) {
+            throw new ShenyuException("Unsupported AI upstream 
protocol/provider combination: "
+                    + providerRequest.getProtocol() + " -> " + 
provider.name());
+        }
+        provider.capabilities().validate(providerRequest, stream);
         try {
-            PreparedAiRequest preparedRequest = 
provider.prepareRequest(decoded, config);
-            return new PreparedExchange(protocol, provider, 
preparedRequest.request(),
-                    preparedRequest.responseAdapter(), stream);
+            PreparedAiRequest preparedRequest = 
provider.prepareRequest(providerRequest, config);
+            AiResponseAdapter responseAdapter = response -> 
protocol.encodeResponse(
+                    
conversion.responseAdapter().adapt(preparedRequest.responseAdapter().adapt(response)),
 stream);
+            return new PreparedExchange(preparedRequest.request(), 
responseAdapter, protocol.name(), stream);
         } catch (IllegalArgumentException error) {
             throw new AiProxyException(AiProxyException.Kind.PROVIDER, 
"Invalid AI provider configuration", error);
         }
     }
 
+    private ShenyuAiRequest applyModelOverride(final ShenyuAiRequest request, 
final AiProxyProtocol protocol,
+                                               final AiCommonConfig config) {
+        String configuredModel = config.getModel();
+        if (Objects.isNull(configuredModel) || configuredModel.isBlank()
+                || configuredModel.equals(request.getModel())) {
+            return request;
+        }
+        ObjectNode effectivePayload = request.getRawPayload().deepCopy();
+        effectivePayload.put("model", configuredModel);
+        return protocol.decodeRequest(effectivePayload.toString(), 
effectivePayload);
+    }
+
     private record PreparedExchange(
-            AiProxyProtocol protocol,
-            AiProxyProvider provider,
             AiUpstreamRequest request,
             AiResponseAdapter responseAdapter,
+            String clientProtocol,
             boolean stream) {
     }
 
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyHttpCompatibilityTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyHttpCompatibilityTest.java
index 475c2196f0..ba9cd6940e 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyHttpCompatibilityTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyHttpCompatibilityTest.java
@@ -54,6 +54,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static 
org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyTestUtils.protocolConverterFactory;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 
@@ -258,7 +259,7 @@ class AiProxyHttpCompatibilityTest {
 
     private AiProxyPlugin plugin(final AiProxyPluginHandler handler) {
         AiProxyEngine engine = new AiProxyEngine(new 
AiProxyProtocolFactory(List.of(new OpenAiChat(), new OpenAiResponses())), new 
AiProxyProviderFactory(List.of(new OpenAi())),
-                new WebClientAiProxyTransport(WebClient.builder().build()), 
Duration.ZERO);
+                protocolConverterFactory(), new 
WebClientAiProxyTransport(WebClient.builder().build()), Duration.ZERO);
         return new AiProxyPlugin(new AiProxyConfigService(handler), engine, 
handler);
     }
 
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyPluginTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyPluginTest.java
index 4431de85c7..416839bf9e 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyPluginTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyPluginTest.java
@@ -61,6 +61,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static 
org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyTestUtils.protocolConverterFactory;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -279,7 +280,7 @@ class AiProxyPluginTest {
     private AiProxyPlugin plugin(final AiProxyTransport transport) {
         
handler.getSelectorCachedHandle().cachedHandle(CacheKeyUtils.INST.getKey(SELECTOR_ID,
 Constants.DEFAULT_RULE), handle);
         AiProxyEngine engine = new AiProxyEngine(new 
AiProxyProtocolFactory(List.of(new OpenAiChat())),
-                new AiProxyProviderFactory(List.of(new OpenAi(), new 
OpenAiCompatible("DeepSeek"))), request -> {
+                new AiProxyProviderFactory(List.of(new OpenAi(), new 
OpenAiCompatible("DeepSeek"))), protocolConverterFactory(), request -> {
                     requests.add(request);
                     return transport.execute(request);
                 }, Duration.ZERO);
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyTestUtils.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyTestUtils.java
new file mode 100644
index 0000000000..bba2f6be8f
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/AiProxyTestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced;
+
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConverterFactory;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.ConnectResponsesToChatConverter;
+
+import java.util.List;
+
+/**
+ * Test utilities for AI proxy fixtures.
+ */
+public final class AiProxyTestUtils {
+
+    private AiProxyTestUtils() {
+    }
+
+    /**
+     * Creates the protocol converter factory used by the default Spring 
configuration.
+     *
+     * @return protocol converter factory
+     */
+    public static AiProtocolConverterFactory protocolConverterFactory() {
+        return new AiProtocolConverterFactory(List.of(new 
ConnectResponsesToChatConverter()));
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverterFactoryTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverterFactoryTest.java
new file mode 100644
index 0000000000..3e4de4db2d
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/AiProtocolConverterFactoryTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.conversion;
+
+import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class AiProtocolConverterFactoryTest {
+
+    @Test
+    void testFindsConverterByDirectionalRoute() {
+        AiProtocolConverter converter = new TestConverter("openai-responses", 
"openai-chat");
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of(converter));
+
+        assertSame(converter, factory.getConverter("openai-responses", 
"openai-chat"));
+        assertThrows(ShenyuException.class,
+                () -> factory.getConverter("openai-chat", "openai-responses"));
+    }
+
+    @Test
+    void testRejectsDuplicateRoute() {
+        assertThrows(IllegalArgumentException.class, () -> new 
AiProtocolConverterFactory(List.of(
+                new TestConverter("source", "target"),
+                new TestConverter("source", "target"))));
+    }
+
+    @Test
+    void testRejectsUnsupportedRoute() {
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of());
+
+        assertThrows(ShenyuException.class, () -> 
factory.getConverter("source", "target"));
+    }
+
+    @Test
+    void testSupportsIdentityWithoutRegistration() {
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of());
+
+        assertTrue(factory.supports("openai-chat", "openai-chat"));
+        assertFalse(factory.supports("openai-responses", "openai-chat"));
+    }
+
+    @Test
+    void testCreatesIdentityConverterWithoutRegistration() {
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of());
+        ShenyuAiRequest request = new ShenyuAiRequest();
+        request.setProtocol("openai-chat");
+
+        AiProtocolConverter converter = factory.getConverter("openai-chat", 
"openai-chat");
+        AiProtocolConversion conversion = converter.convert(request, false);
+
+        assertTrue(converter instanceof IdentityAiProtocolConverter);
+        assertSame(request, conversion.request());
+    }
+
+    @Test
+    void testPrefersNativeProviderProtocolOverConversion() {
+        AiProtocolConverter converter = new TestConverter("openai-responses", 
"openai-chat");
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of(converter));
+
+        AiProtocolConverter resolved = factory.resolveConverter(
+                "openai-responses", Set.of("openai-chat", "openai-responses"));
+
+        assertTrue(resolved instanceof IdentityAiProtocolConverter);
+    }
+
+    @Test
+    void testSelectsRegisteredRouteToAnyProviderProtocol() {
+        AiProtocolConverter first = new TestConverter("source", "target-b");
+        AiProtocolConverter second = new TestConverter("source", "target-a");
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of(first, second));
+
+        AiProtocolConverter resolved = factory.resolveConverter(
+                "source", Set.of("target-a", "target-b"));
+
+        assertSame(first, resolved);
+    }
+
+    @Test
+    void testRejectsSourceWithoutRouteToProviderProtocols() {
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of(
+                new TestConverter("source", "other")));
+
+        assertThrows(ShenyuException.class,
+                () -> factory.resolveConverter("source", Set.of("target-a", 
"target-b")));
+    }
+
+    @Test
+    void testRejectsInvalidProtocolIdentifiers() {
+        AiProtocolConverterFactory factory = new 
AiProtocolConverterFactory(List.of());
+
+        assertFalse(factory.supports(null, null));
+        assertFalse(factory.supports("", ""));
+        assertThrows(NullPointerException.class, () -> 
factory.getConverter(null, null));
+        assertThrows(IllegalArgumentException.class, () -> 
factory.getConverter("", ""));
+    }
+
+    private static final class TestConverter implements AiProtocolConverter {
+
+        private final String source;
+
+        private final String target;
+
+        private TestConverter(final String source, final String target) {
+            this.source = source;
+            this.target = target;
+        }
+
+        @Override
+        public String sourceProtocol() {
+            return source;
+        }
+
+        @Override
+        public String targetProtocol() {
+            return target;
+        }
+
+        @Override
+        public AiProtocolConversion convert(final ShenyuAiRequest request, 
final boolean stream) {
+            return AiProtocolConversion.identity(request);
+        }
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/ConnectResponsesToChatConverterTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/ConnectResponsesToChatConverterTest.java
new file mode 100644
index 0000000000..11134ba8b8
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/conversion/ConnectResponsesToChatConverterTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.conversion;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamResponse;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.ShenyuAiRequest;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiChat;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiResponses;
+import org.junit.jupiter.api.Test;
+import reactor.core.publisher.Flux;
+import reactor.test.StepVerifier;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class ConnectResponsesToChatConverterTest {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static final String RESPONSE = 
"{\"id\":\"chatcmpl-test\",\"model\":\"upstream\",\"choices\":[{\"index\":0,"
+            + 
"\"message\":{\"role\":\"assistant\",\"content\":\"hello\"},\"finish_reason\":\"stop\"}],"
+            + 
"\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2,\"total_tokens\":5}}";
+
+    private static final String CHUNK = 
"{\"id\":\"chatcmpl-test\",\"model\":\"upstream\",\"choices\":[{\"index\":0,"
+            + 
"\"delta\":{\"role\":\"assistant\",\"content\":\"hello\"},\"finish_reason\":null}]}";
+
+    @Test
+    void testConvertsResponsesRequestWithoutMutatingSource() throws Exception {
+        ShenyuAiRequest source = 
request("{\"model\":\"test\",\"instructions\":\"be helpful\","
+                + "\"input\":\"hello\",\"max_output_tokens\":12}");
+        final ObjectNode before = source.getRawPayload().deepCopy();
+
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(source, false);
+
+        assertEquals(OpenAiChat.NAME, conversion.request().getProtocol());
+        assertEquals("system", 
conversion.request().getRawPayload().at("/messages/0/role").asText());
+        assertEquals("hello", 
conversion.request().getRawPayload().at("/messages/1/content").asText());
+        assertEquals(12, 
conversion.request().getRawPayload().get("max_completion_tokens").asInt());
+        assertEquals(before, source.getRawPayload());
+    }
+
+    @Test
+    void testUsesRequestContextForReverseResponseConversion() throws Exception 
{
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(request(
+                
"{\"model\":\"test\",\"input\":\"hi\",\"tools\":[{\"type\":\"custom\",\"name\":\"execute\"}]}"),
 false);
+        String mappedName = 
conversion.request().getRawPayload().at("/tools/0/function/name").asText();
+        String body = 
"{\"model\":\"test\",\"choices\":[{\"index\":0,\"finish_reason\":\"tool_calls\","
+                + 
"\"message\":{\"role\":\"assistant\",\"tool_calls\":[{\"id\":\"call_one\",\"type\":\"function\","
+                + "\"function\":{\"name\":\"" + mappedName
+                + 
"\",\"arguments\":\"{\\\"input\\\":\\\"print(1)\\\"}\"}}]}}]}";
+
+        AiUpstreamResponse converted = 
conversion.responseAdapter().adapt(response(200, Flux.just(bytes(body))));
+        JsonNode result = 
MAPPER.readTree(converted.getBody().single().block(Duration.ofSeconds(3)));
+
+        assertEquals("custom_tool_call", result.at("/output/0/type").asText());
+        assertEquals("execute", result.at("/output/0/name").asText());
+        assertEquals("print(1)", result.at("/output/0/input").asText());
+    }
+
+    @Test
+    void testConvertsCompleteResponseAndRepresentationHeaders() throws 
Exception {
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(
+                request("{\"model\":\"test\",\"input\":\"hello\"}"), false);
+        AiUpstreamResponse upstream = new AiUpstreamResponse(200,
+                Map.of("content-length", List.of("123"), "ETag", 
List.of("old"),
+                        "X-Request-ID", List.of("kept")), 
Flux.just(bytes(RESPONSE)));
+
+        AiUpstreamResponse converted = 
conversion.responseAdapter().adapt(upstream);
+        JsonNode result = 
MAPPER.readTree(converted.getBody().single().block(Duration.ofSeconds(3)));
+
+        assertEquals(Map.of("Content-Type", List.of("application/json"),
+                "X-Request-ID", List.of("kept")), converted.getHeaders());
+        assertEquals("response", result.get("object").asText());
+        assertEquals("hello", result.at("/output/0/content/0/text").asText());
+    }
+
+    @Test
+    void testPreservesHttpErrorsWithoutConsumingBody() throws Exception {
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(
+                request("{\"model\":\"test\",\"input\":\"hello\"}"), false);
+        AiUpstreamResponse upstream = response(400, Flux.error(new 
AssertionError("Body must not be consumed")));
+
+        assertSame(upstream, conversion.responseAdapter().adapt(upstream));
+    }
+
+    @Test
+    void testIsolatesStreamingStateOnEachSubscription() throws Exception {
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(
+                
request("{\"model\":\"test\",\"input\":\"hello\",\"stream\":true}"), true);
+        AiUpstreamResponse upstream = response(200, Flux.just(bytes("data: " + 
CHUNK + "\n\ndata: [DONE]\n\n")));
+        Flux<String> converted = 
conversion.responseAdapter().adapt(upstream).getBody()
+                .map(bytes -> new String(bytes, StandardCharsets.UTF_8));
+
+        for (int attempt = 0; attempt < 2; attempt++) {
+            List<String> events = 
converted.collectList().block(Duration.ofSeconds(3));
+            assertEquals(1, events.stream()
+                    .filter(event -> event.startsWith("event: 
response.completed\n")).count());
+            assertEquals(1, events.stream()
+                    .filter(event -> event.startsWith("event: 
response.output_text.delta\n")
+                            && event.contains("hello")).count());
+        }
+    }
+
+    @Test
+    void testRejectsMalformedAndOversizedCompleteResponses() throws Exception {
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(
+                request("{\"model\":\"test\",\"input\":\"hello\"}"), false);
+        for (String body : List.of("invalid", "[]", "", "null", RESPONSE + " 
{}",
+                "x".repeat(5 * 1024 * 1024 + 1))) {
+            
StepVerifier.create(conversion.responseAdapter().adapt(response(200, 
Flux.just(bytes(body)))).getBody())
+                    .expectErrorMatches(error -> error instanceof 
AiProxyException
+                            && ((AiProxyException) error).getKind() == 
AiProxyException.Kind.PROTOCOL)
+                    .verify();
+        }
+        StepVerifier.create(conversion.responseAdapter().adapt(response(200, 
Flux.empty())).getBody())
+                .expectError(AiProxyException.class)
+                .verify();
+    }
+
+    @Test
+    void testConvertsFragmentedMultilineStreamAndProducesOneTerminalEvent() 
throws Exception {
+        String wire = ": heartbeat\r\ndata: {\r\ndata: " + CHUNK.substring(1) 
+ "\r\n\r\n"
+                + "data: 
{\"choices\":[],\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2}}\r\n\r\n"
+                + "data: [DONE]\r\n\r\n";
+        List<byte[]> fragments = new ArrayList<>();
+        for (byte value : bytes(wire)) {
+            fragments.add(new byte[] {value});
+        }
+
+        List<String> events = 
stream(Flux.fromIterable(fragments)).collectList().block(Duration.ofSeconds(3));
+
+        assertTrue(events.stream().anyMatch(event -> event.startsWith("event: 
response.output_text.delta\n")
+                && event.contains("hello")));
+        assertEquals(1, events.stream().filter(event -> 
event.startsWith("event: response.completed\n")).count());
+        assertTrue(events.get(events.size() - 
1).contains("\"input_tokens\":3"));
+    }
+
+    @Test
+    void testDoesNotCompleteAfterMalformedEventOrTransportFailure() throws 
Exception {
+        for (Flux<byte[]> body : List.of(
+                Flux.just(bytes("data: " + CHUNK + "\n\ndata: invalid\n\n")),
+                Flux.just(bytes("data: " + CHUNK + "\n\n"))
+                        .concatWith(Flux.error(new 
IllegalStateException("socket closed"))))) {
+            List<String> events = new ArrayList<>();
+            StepVerifier.create(stream(body).doOnNext(events::add))
+                    .thenConsumeWhile(event -> !event.startsWith("event: 
response.completed\n"))
+                    .expectError(AiProxyException.class)
+                    .verify();
+            assertTrue(events.stream().noneMatch(event -> 
event.startsWith("event: response.completed\n")));
+        }
+    }
+
+    @Test
+    void testCancelsUpstreamAtDone() throws Exception {
+        AtomicBoolean cancelled = new AtomicBoolean();
+
+        List<String> events = stream(Flux.just(bytes("data: [DONE]\n\n"))
+                .concatWith(Flux.never()).doOnCancel(() -> 
cancelled.set(true)))
+                .collectList().block(Duration.ofSeconds(3));
+
+        assertTrue(cancelled.get());
+        assertEquals(1, events.stream().filter(event -> 
event.startsWith("event: response.completed\n")).count());
+    }
+
+    @Test
+    void testRejectsWrongSourceProtocol() throws Exception {
+        ObjectNode payload = 
object("{\"model\":\"test\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}");
+        ShenyuAiRequest request = new 
OpenAiChat().decodeRequest(payload.toString(), payload);
+
+        AiProxyException error = assertThrows(AiProxyException.class,
+                () -> new ConnectResponsesToChatConverter().convert(request, 
false));
+
+        assertEquals(AiProxyException.Kind.REQUEST, error.getKind());
+    }
+
+    private ShenyuAiRequest request(final String body) throws Exception {
+        ObjectNode payload = object(body);
+        return new OpenAiResponses().decodeRequest(body, payload);
+    }
+
+    private ObjectNode object(final String value) throws Exception {
+        return (ObjectNode) MAPPER.readTree(value);
+    }
+
+    private AiUpstreamResponse response(final int status, final Flux<byte[]> 
body) {
+        return new AiUpstreamResponse(status, Map.of(), body);
+    }
+
+    private Flux<String> stream(final Flux<byte[]> body) throws Exception {
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(
+                
request("{\"model\":\"test\",\"input\":\"hello\",\"stream\":true}"), true);
+        return conversion.responseAdapter().adapt(response(200, 
body)).getBody()
+                .map(bytes -> new String(bytes, StandardCharsets.UTF_8));
+    }
+
+    private byte[] bytes(final String value) {
+        return value.getBytes(StandardCharsets.UTF_8);
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/protocol/ConnectProtocolAdapterTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/protocol/ConnectProtocolAdapterTest.java
deleted file mode 100644
index dab3b60d1f..0000000000
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/protocol/ConnectProtocolAdapterTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.protocol;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import io.github.agenticai.connect.ProviderProfile;
-import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
-import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamResponse;
-import org.junit.jupiter.api.Test;
-import reactor.core.publisher.Flux;
-import reactor.test.StepVerifier;
-
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Library contracts at ShenYu's JSON/reactive boundary, without live-provider 
calls.
- */
-class ConnectProtocolAdapterTest {
-
-    private static final ObjectMapper MAPPER = new ObjectMapper();
-
-    private static final String RESPONSE = 
"{\"id\":\"chatcmpl-test\",\"model\":\"upstream\",\"choices\":[{\"index\":0,"
-            + 
"\"message\":{\"role\":\"assistant\",\"content\":\"hello\"},\"finish_reason\":\"stop\"}],"
-            + 
"\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2,\"total_tokens\":5}}";
-
-    private static final String CHUNK = 
"{\"id\":\"chatcmpl-test\",\"model\":\"upstream\",\"choices\":[{\"index\":0,"
-            + 
"\"delta\":{\"role\":\"assistant\",\"content\":\"hello\"},\"finish_reason\":null}]}";
-
-    @Test
-    void delegatesRequestConversionAndProviderAdaptationExactlyOnce() throws 
Exception {
-        AtomicInteger calls = new AtomicInteger();
-        ProviderProfile profile = (payload, adaptation) -> {
-            calls.incrementAndGet();
-            assertEquals("routed", payload.get("model").getAsString());
-            payload.addProperty("custom", true);
-            adaptation.warning("custom_profile", "custom", "Test profile");
-        };
-        ObjectNode input = object("{\"model\":\"alias\",\"instructions\":\"be 
helpful\",\"input\":\"hello\",\"max_output_tokens\":12}");
-        ObjectNode before = input.deepCopy();
-        ObjectNode result = new ConnectProtocolAdapter(OpenAiResponses.NAME, 
"routed", profile).convertRequest(input);
-        assertEquals(1, calls.get());
-        assertEquals(before, input);
-        assertEquals("system", result.at("/messages/0/role").asText());
-        assertEquals("hello", result.at("/messages/1/content").asText());
-        assertEquals(12, result.get("max_completion_tokens").asInt());
-        assertTrue(result.get("custom").asBoolean());
-    }
-
-    @Test
-    void keepsGenericIdentityFieldsAndRejectsUnrepresentableResponsesState() 
throws Exception {
-        ObjectNode input = 
object("{\"messages\":[{\"role\":\"user\"}],\"vendor\":true,\"content\":null}");
-        assertEquals(input, new ConnectProtocolAdapter(OpenAiChat.NAME, null, 
null).convertRequest(input));
-        ConnectProtocolAdapter adapter = adapter();
-        for (String body : 
List.of("{\"model\":\"x\",\"input\":\"hi\",\"previous_response_id\":\"resp_old\"}",
 "{\"input\":\"hi\"}")) {
-            AiProxyException error = assertThrows(AiProxyException.class, () 
-> adapter.convertRequest(object(body)));
-            assertEquals(AiProxyException.Kind.REQUEST, error.getKind());
-        }
-    }
-
-    @Test
-    void adaptsDeepSeekTokensAndReasoningWithoutGuessingFromAModel() throws 
Exception {
-        ObjectNode input = 
object("{\"model\":\"deepseek-model\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],"
-                + 
"\"max_completion_tokens\":20,\"max_tokens\":10,\"reasoning_effort\":\"medium\"}");
-        assertEquals(input, new ConnectProtocolAdapter(OpenAiChat.NAME, null, 
null).convertRequest(input));
-        ObjectNode result = new ConnectProtocolAdapter(OpenAiChat.NAME, null, 
ProviderProfile.deepSeekChat()).convertRequest(input);
-        assertEquals(20, result.get("max_tokens").asInt());
-        assertFalse(result.has("max_completion_tokens"));
-        assertEquals("high", result.get("reasoning_effort").asText());
-        assertEquals("enabled", result.at("/thinking/type").asText());
-        input.put("tool_choice", "required");
-        assertThrows(AiProxyException.class,
-                () -> new ConnectProtocolAdapter(OpenAiChat.NAME, null, 
ProviderProfile.deepSeekChat()).convertRequest(input));
-    }
-
-    @Test
-    void convertsCompleteResponsesAndRemovesStaleRepresentationHeaders() 
throws Exception {
-        AiUpstreamResponse upstream = new AiUpstreamResponse(200, 
Map.of("content-length", List.of("123"), "ETag", List.of("old"),
-                "Content-MD5", List.of("old"), "Digest", List.of("old"), 
"X-Request-ID", List.of("kept")),
-                Flux.just(bytes(RESPONSE.substring(0, 20)), 
bytes(RESPONSE.substring(20))));
-        AiUpstreamResponse converted = adapter().convertResponse(upstream, 
false);
-        assertEquals(Map.of("Content-Type", List.of("application/json"), 
"X-Request-ID", List.of("kept")), converted.getHeaders());
-        JsonNode result = 
MAPPER.readTree(converted.getBody().single().block(Duration.ofSeconds(3)));
-        assertEquals("response", result.get("object").asText());
-        assertEquals("hello", result.at("/output/0/content/0/text").asText());
-        assertEquals(3, result.at("/usage/input_tokens").asInt());
-        assertEquals(2, result.at("/usage/output_tokens").asInt());
-    }
-
-    @Test
-    void restoresCustomToolsUsingTheOriginalRequestContext() throws Exception {
-        ConnectProtocolAdapter adapter = adapter();
-        ObjectNode converted = 
adapter.convertRequest(object("{\"model\":\"x\",\"input\":\"hi\",\"tools\":[{\"type\":\"custom\",\"name\":\"execute\"}]}"));
-        String name = converted.at("/tools/0/function/name").asText();
-        String body = 
"{\"model\":\"x\",\"choices\":[{\"index\":0,\"finish_reason\":\"tool_calls\",\"message\":{\"role\":\"assistant\","
-                + 
"\"tool_calls\":[{\"id\":\"call_one\",\"type\":\"function\",\"function\":{\"name\":\""
 + name + "\",\"arguments\":\"{\\\"input\\\":\\\"print(1)\\\"}\"}}]}}]}";
-        JsonNode result = 
MAPPER.readTree(adapter.convertResponse(response(200, Flux.just(bytes(body))), 
false).getBody().single().block());
-        assertEquals("custom_tool_call", result.at("/output/0/type").asText());
-        assertEquals("execute", result.at("/output/0/name").asText());
-        assertEquals("print(1)", result.at("/output/0/input").asText());
-        JsonNode unrelated = 
MAPPER.readTree(adapter().convertResponse(response(200, 
Flux.just(bytes(body))), false).getBody().single().block());
-        assertEquals("function_call", unrelated.at("/output/0/type").asText());
-    }
-
-    @Test
-    void restoresFragmentedCustomToolStreamsWithoutCrossRequestState() throws 
Exception {
-        ConnectProtocolAdapter custom = adapter();
-        ObjectNode request = 
custom.convertRequest(object("{\"model\":\"x\",\"input\":\"hi\",\"tools\":[{\"type\":\"custom\",\"name\":\"execute\"}]}"));
-        String name = request.at("/tools/0/function/name").asText();
-        ObjectNode start = 
object("{\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_one\",\"type\":\"function\",\"function\":{}}]}}]}");
-        ObjectNode function = (ObjectNode) 
start.at("/choices/0/delta/tool_calls/0/function");
-        function.put("name", name);
-        function.put("arguments", "{\"input\":\"print(");
-        ObjectNode next = start.deepCopy();
-        ObjectNode continuation = (ObjectNode) 
next.at("/choices/0/delta/tool_calls/0");
-        continuation.remove(List.of("id", "type"));
-        ((ObjectNode) continuation.get("function")).remove("name");
-        ((ObjectNode) continuation.get("function")).put("arguments", "1)\"}");
-        Flux<byte[]> chunks = Flux.just(bytes("data: " + start + "\n\n"), 
bytes("data: " + next + "\n\n"),
-                bytes("data: 
{\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}]}\n\n"));
-        Flux<String> restored = custom.convertResponse(response(200, chunks), 
true).getBody().map(bytes -> new String(bytes, StandardCharsets.UTF_8));
-        Flux<String> isolated = adapter().convertResponse(response(200, 
chunks), true).getBody().map(bytes -> new String(bytes, 
StandardCharsets.UTF_8));
-        List<List<String>> results = 
reactor.core.publisher.Mono.zip(restored.collectList(), isolated.collectList(), 
List::of).block(Duration.ofSeconds(3));
-        JsonNode customResult = terminalResponse(results.get(0));
-        JsonNode genericResult = terminalResponse(results.get(1));
-        assertEquals("custom_tool_call", 
customResult.at("/response/output/0/type").asText());
-        assertEquals("print(1)", 
customResult.at("/response/output/0/input").asText());
-        assertEquals("function_call", 
genericResult.at("/response/output/0/type").asText());
-        assertEquals("{\"input\":\"print(1)\"}", 
genericResult.at("/response/output/0/arguments").asText());
-    }
-
-    private JsonNode terminalResponse(final List<String> events) throws 
Exception {
-        String terminal = events.get(events.size() - 1);
-        return MAPPER.readTree(terminal.substring(terminal.indexOf("data: ") + 
6).trim());
-    }
-
-    @Test
-    void preservesHttpErrorsAndChatResponsesWithoutConsumingThem() {
-        for (int status : List.of(199, 400, 500)) {
-            AiUpstreamResponse upstream = response(status, Flux.error(new 
AssertionError("Body must not be consumed")));
-            assertSame(upstream, adapter().convertResponse(upstream, false));
-            assertSame(upstream, adapter().convertResponse(upstream, true));
-        }
-        AiUpstreamResponse upstream = response(200, Flux.empty());
-        assertSame(upstream, new ConnectProtocolAdapter(OpenAiChat.NAME, null, 
null).convertResponse(upstream, true));
-    }
-
-    @Test
-    void rejectsMalformedEmptyTrailingAndOversizedCompleteResponses() {
-        for (String body : List.of("invalid", "[]", "", "null", RESPONSE + " 
{}", "x".repeat(5 * 1024 * 1024 + 1))) {
-            StepVerifier.create(adapter().convertResponse(response(200, 
Flux.just(bytes(body))), false).getBody())
-                    .expectErrorMatches(error -> error instanceof 
AiProxyException && ((AiProxyException) error).getKind() == 
AiProxyException.Kind.PROTOCOL).verify();
-        }
-        StepVerifier.create(adapter().convertResponse(response(200, 
Flux.empty()), false).getBody()).expectError(AiProxyException.class).verify();
-    }
-
-    @Test
-    void convertsFragmentedMultilineSseAndProducesOneTerminalEvent() {
-        String wire = ": heartbeat\r\ndata: {\r\ndata: " + CHUNK.substring(1) 
+ "\r\n\r\n"
-                + "data: 
{\"choices\":[],\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":2}}\r\n\r\ndata:
 [DONE]\r\n\r\n";
-        List<byte[]> fragments = new ArrayList<>();
-        for (byte value : bytes(wire)) {
-            fragments.add(new byte[] {value});
-        }
-        List<String> events = 
stream(Flux.fromIterable(fragments)).collectList().block(Duration.ofSeconds(3));
-        assertTrue(events.stream().anyMatch(event -> event.startsWith("event: 
response.output_text.delta\n") && event.contains("hello")));
-        assertEquals(1, events.stream().filter(event -> 
event.startsWith("event: response.completed\n")).count());
-        assertTrue(events.get(events.size() - 
1).contains("\"input_tokens\":3"));
-        assertTrue(events.stream().allMatch(event -> 
event.contains("\"sequence_number\":") && !event.contains("[DONE]")));
-    }
-
-    @Test
-    void isolatesStreamStateOnResubscriptionAndCancelsWithoutATerminalEvent() {
-        Flux<String> reusable = stream(Flux.just(bytes("data: " + CHUNK + 
"\n\n")));
-        for (int attempt = 0; attempt < 2; attempt++) {
-            List<String> events = reusable.collectList().block();
-            assertEquals(1, events.stream().filter(event -> 
event.startsWith("event: response.completed\n")).count());
-        }
-        AtomicBoolean cancelled = new AtomicBoolean();
-        Flux<String> live = stream(Flux.just(bytes("data: " + CHUNK + 
"\n\n")).concatWith(Flux.never()).doOnCancel(() -> cancelled.set(true)));
-        StepVerifier.create(live, 0).thenRequest(1).expectNextMatches(event -> 
event.startsWith("event: response.created\n"))
-                .thenCancel().verify(Duration.ofSeconds(3));
-        assertTrue(cancelled.get());
-    }
-
-    @Test
-    void doesNotSynthesizeSuccessAfterMalformedEventsOrTransportFailure() {
-        for (Flux<byte[]> body : List.of(Flux.just(bytes("data: " + CHUNK + 
"\n\ndata: invalid\n\n")),
-                Flux.just(bytes("data: " + CHUNK + 
"\n\n")).concatWith(Flux.error(new IllegalStateException("socket closed"))))) {
-            List<String> events = new ArrayList<>();
-            
StepVerifier.create(stream(body).doOnNext(events::add)).thenConsumeWhile(event 
-> !event.startsWith("event: response.completed\n"))
-                    .expectError(AiProxyException.class).verify();
-            assertTrue(events.stream().noneMatch(event -> 
event.startsWith("event: response.completed\n")));
-        }
-        AiUpstreamResponse json = new AiUpstreamResponse(200, 
Map.of("Content-Type", List.of("application/json")), 
Flux.just(bytes(RESPONSE)));
-        StepVerifier.create(adapter().convertResponse(json, 
true).getBody()).expectError(AiProxyException.class).verify();
-    }
-
-    @Test
-    void boundsCumulativeConversionStateAndCancelsAtDone() {
-        String large = "data: " + CHUNK.replace("hello", "x".repeat(1024 * 
1024)) + "\n\n";
-        StepVerifier.create(stream(Flux.range(0, 6).map(index -> 
bytes(large))))
-                .thenConsumeWhile(event -> !event.startsWith("event: 
response.completed\n")).expectError(AiProxyException.class).verify();
-        AtomicBoolean cancelled = new AtomicBoolean();
-        List<String> events = stream(Flux.just(bytes("data: 
[DONE]\n\n")).concatWith(Flux.never()).doOnCancel(() -> cancelled.set(true)))
-                .collectList().block(Duration.ofSeconds(3));
-        assertTrue(cancelled.get());
-        assertEquals(1, events.stream().filter(event -> 
event.startsWith("event: response.completed\n")).count());
-    }
-
-    private ConnectProtocolAdapter adapter() {
-        return new ConnectProtocolAdapter(OpenAiResponses.NAME, null, null);
-    }
-
-    private ObjectNode object(final String json) throws Exception {
-        return (ObjectNode) MAPPER.readTree(json);
-    }
-
-    private AiUpstreamResponse response(final int status, final Flux<byte[]> 
body) {
-        return new AiUpstreamResponse(status, Map.of(), body);
-    }
-
-    private Flux<String> stream(final Flux<byte[]> body) {
-        return adapter().convertResponse(response(200, body), 
true).getBody().map(bytes -> new String(bytes, StandardCharsets.UTF_8));
-    }
-
-    private byte[] bytes(final String value) {
-        return value.getBytes(StandardCharsets.UTF_8);
-    }
-}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProviderHttpTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProviderHttpTest.java
index 7453e95ee6..092c9fc97c 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProviderHttpTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/AiProxyProviderHttpTest.java
@@ -41,6 +41,7 @@ import java.util.concurrent.atomic.AtomicReference;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static 
org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyTestUtils.protocolConverterFactory;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -129,7 +130,8 @@ class AiProxyProviderHttpTest {
 
     private AiProxyEngine engine(final AiProxyProvider provider) {
         return new AiProxyEngine(new AiProxyProtocolFactory(List.of(new 
OpenAiChat(), new OpenAiResponses())),
-                new AiProxyProviderFactory(List.of(provider)), new 
WebClientAiProxyTransport(WebClient.builder().build()));
+                new AiProxyProviderFactory(List.of(provider)), 
protocolConverterFactory(),
+                new WebClientAiProxyTransport(WebClient.builder().build()));
     }
 
     private AiCommonConfig config(final DisposableServer server, final String 
provider, final String protocol) {
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/ConnectProviderDialectAdapterTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/ConnectProviderDialectAdapterTest.java
new file mode 100644
index 0000000000..cbfaab862f
--- /dev/null
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/ConnectProviderDialectAdapterTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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 org.apache.shenyu.plugin.ai.proxy.enhanced.provider;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.github.agenticai.connect.ProviderProfile;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests provider-specific Chat dialect adaptation independently of protocol 
conversion.
+ */
+class ConnectProviderDialectAdapterTest {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @Test
+    void appliesProviderProfileExactlyOnceWithoutMutatingSource() throws 
Exception {
+        AtomicInteger calls = new AtomicInteger();
+        ProviderProfile profile = (payload, adaptation) -> {
+            calls.incrementAndGet();
+            assertEquals("routed", payload.get("model").getAsString());
+            payload.addProperty("custom", true);
+            adaptation.warning("custom_profile", "custom", "Test profile");
+        };
+        ObjectNode input = 
object("{\"model\":\"alias\",\"messages\":[{\"role\":\"user\",\"content\":\"hello\"}]}");
+        ObjectNode before = input.deepCopy();
+
+        ObjectNode result = new ConnectProviderDialectAdapter("routed", 
profile).adapt(input);
+
+        assertEquals(1, calls.get());
+        assertEquals(before, input);
+        assertEquals("routed", result.path("model").asText());
+        assertTrue(result.path("custom").asBoolean());
+    }
+
+    @Test
+    void keepsGenericChatPayloadUnchanged() throws Exception {
+        ObjectNode input = 
object("{\"messages\":[{\"role\":\"user\"}],\"vendor\":true,\"content\":null}");
+
+        assertEquals(input, new ConnectProviderDialectAdapter(null, 
null).adapt(input));
+    }
+
+    @Test
+    void adaptsDeepSeekTokensAndReasoning() throws Exception {
+        ObjectNode input = 
object("{\"model\":\"deepseek-model\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],"
+                + 
"\"max_completion_tokens\":20,\"max_tokens\":10,\"reasoning_effort\":\"medium\"}");
+        ObjectNode result = new ConnectProviderDialectAdapter(null, 
ProviderProfile.deepSeekChat()).adapt(input);
+
+        assertEquals(20, result.path("max_tokens").asInt());
+        assertFalse(result.has("max_completion_tokens"));
+        assertEquals("high", result.path("reasoning_effort").asText());
+        assertEquals("enabled", result.at("/thinking/type").asText());
+
+        input.put("tool_choice", "required");
+        assertThrows(AiProxyException.class,
+                () -> new ConnectProviderDialectAdapter(null, 
ProviderProfile.deepSeekChat()).adapt(input));
+    }
+
+    private ObjectNode object(final String json) throws Exception {
+        return (ObjectNode) MAPPER.readTree(json);
+    }
+}
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAiProviderOptionsTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAiProviderOptionsTest.java
index 0800e25581..e8481a7876 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAiProviderOptionsTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/provider/OpenAiProviderOptionsTest.java
@@ -21,6 +21,8 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.shenyu.plugin.ai.common.config.AiCommonConfig;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConversion;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.ConnectResponsesToChatConverter;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiProxyException;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamRequest;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.model.AiUpstreamResponse;
@@ -45,6 +47,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static 
org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyTestUtils.protocolConverterFactory;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -191,7 +194,9 @@ class OpenAiProviderOptionsTest {
     void compatibleTokenFieldChoiceRunsInConnectAfterResponsesConversion() 
throws Exception {
         OpenAiCompatible provider = new OpenAiCompatible("legacy", 
OpenAiProviderOptions.generic(null).withMaxTokensField("max_tokens"));
         config.setMaxTokens(17);
-        AiUpstreamRequest upstream = 
provider.buildRequest(responses("{\"model\":\"m\",\"input\":\"hi\",\"max_output_tokens\":7}"),
 config);
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(
+                
responses("{\"model\":\"m\",\"input\":\"hi\",\"max_output_tokens\":7}"), false);
+        AiUpstreamRequest upstream = 
provider.buildRequest(conversion.request(), config);
         JsonNode payload = MAPPER.readTree(upstream.getBody());
         assertEquals(17, payload.get("max_tokens").asInt());
         assertFalse(payload.has("max_completion_tokens"));
@@ -237,8 +242,12 @@ class OpenAiProviderOptionsTest {
         AiUpstreamResponse response = new AiUpstreamResponse(200, 
Map.of("Content-Type", List.of("application/json")), Flux.just(bytes(reply)));
         PreparedAiRequest chat = provider.prepareRequest(chat(CHAT), config);
         assertEquals(reply, 
text(chat.responseAdapter().adapt(response).getBody().single().block(Duration.ofSeconds(3))));
-        PreparedAiRequest responses = 
provider.prepareRequest(responses("{\"model\":\"deepseek-flash\",\"input\":\"hi\"}"),
 config);
-        JsonNode converted = 
MAPPER.readTree(responses.responseAdapter().adapt(response).getBody().single().block(Duration.ofSeconds(3)));
+        AiProtocolConversion conversion = new 
ConnectResponsesToChatConverter().convert(
+                responses("{\"model\":\"deepseek-flash\",\"input\":\"hi\"}"), 
false);
+        PreparedAiRequest prepared = 
provider.prepareRequest(conversion.request(), config);
+        AiUpstreamResponse providerAdapted = 
prepared.responseAdapter().adapt(response);
+        JsonNode converted = 
MAPPER.readTree(conversion.responseAdapter().adapt(providerAdapted)
+                .getBody().single().block(Duration.ofSeconds(3)));
         assertEquals("response", converted.get("object").asText());
         assertTrue(converted.toString().contains("think"));
         assertTrue(converted.toString().contains("answer"));
@@ -248,12 +257,13 @@ class OpenAiProviderOptionsTest {
     @Test
     void unsupportedOperationsFailBeforeTransportWithoutEngineChanges() {
         AiProviderCapabilities all = AiProviderCapabilities.chat();
-        AiProviderCapabilities restricted = new 
AiProviderCapabilities(all.protocols(), 
Set.of(ShenyuAiRequest.Operation.CHAT_COMPLETION), all.features());
+        AiProviderCapabilities restricted = new AiProviderCapabilities(
+                all.protocols(), Set.of(ShenyuAiRequest.Operation.RESPONSE), 
all.features());
         OpenAiCompatible custom = new OpenAiCompatible("restricted", 
options("/generate", OpenAiProviderOptions.Authentication.NONE, "", "",
                 Map.of(), Map.of(), Map.of(), MAPPER.createObjectNode(), 
restricted, ""));
         AtomicInteger calls = new AtomicInteger();
         AiProxyEngine engine = new AiProxyEngine(new 
AiProxyProtocolFactory(List.of(new OpenAiResponses())),
-                new AiProxyProviderFactory(List.of(custom)), request -> {
+                new AiProxyProviderFactory(List.of(custom)), 
protocolConverterFactory(), request -> {
                     calls.incrementAndGet();
                     return Mono.empty();
                 });
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineOrchestrationTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineOrchestrationTest.java
index fe77d269f7..9297d904dd 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineOrchestrationTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineOrchestrationTest.java
@@ -57,6 +57,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static 
org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyTestUtils.protocolConverterFactory;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -417,7 +418,7 @@ class AiProxyEngineOrchestrationTest {
     }
 
     @Test
-    void providerRequestAdapterAndProtocolConvertTheResponseInOrder() {
+    void providerResponseAdapterAndProtocolConvertTheResponseInOrder() {
         List<String> conversions = new ArrayList<>();
         AtomicBoolean supported = new AtomicBoolean(true);
         AiProxyProvider provider = new AiProxyProvider() {
@@ -439,16 +440,10 @@ class AiProxyEngineOrchestrationTest {
             @Override
             public PreparedAiRequest prepareRequest(final ShenyuAiRequest 
request, final AiCommonConfig config) {
                 return new PreparedAiRequest(buildRequest(request, config), 
response -> {
-                    conversions.add("request");
+                    conversions.add("provider");
                     return response;
                 });
             }
-
-            @Override
-            public AiUpstreamResponse adaptResponse(final AiUpstreamResponse 
response) {
-                conversions.add("provider");
-                return response;
-            }
         };
         OpenAiChat delegate = new OpenAiChat();
         AiProxyProtocol protocol = new AiProxyProtocol() {
@@ -476,9 +471,9 @@ class AiProxyEngineOrchestrationTest {
         AiCommonConfig config = config(false, "model");
         config.setProvider("custom");
         AiProxyEngine engine = new AiProxyEngine(new 
AiProxyProtocolFactory(List.of(protocol)), new 
AiProxyProviderFactory(List.of(provider)),
-                request -> Mono.just(response(200, Flux.just(bytes("ok")))), 
Duration.ZERO);
+                protocolConverterFactory(), request -> Mono.just(response(200, 
Flux.just(bytes("ok")))), Duration.ZERO);
         StepVerifier.create(engine.execute(BODY, config, Optional.empty(), 
response -> response.getBody().then())).verifyComplete();
-        assertEquals(List.of("provider", "request", "protocol"), conversions);
+        assertEquals(List.of("provider", "protocol"), conversions);
         supported.set(false);
         conversions.clear();
         StepVerifier.create(engine.execute(BODY, config, Optional.empty(), 
response -> 
response.getBody().then())).expectError(ShenyuException.class).verify();
@@ -504,12 +499,14 @@ class AiProxyEngineOrchestrationTest {
 
     private AiProxyEngine engine(final AiProxyTransport transport) {
         return new AiProxyEngine(new AiProxyProtocolFactory(List.of(new 
OpenAiChat(), new OpenAiResponses())),
-                new AiProxyProviderFactory(List.of(new OpenAi(), new 
OpenAiCompatible("DeepSeek"))), transport, Duration.ZERO);
+                new AiProxyProviderFactory(List.of(new OpenAi(), new 
OpenAiCompatible("DeepSeek"))),
+                protocolConverterFactory(), transport, Duration.ZERO);
     }
 
     private AiProxyEngine engine(final AiProxyTransport transport, final 
AiProxyRetryProperties retryProperties) {
         return new AiProxyEngine(new AiProxyProtocolFactory(List.of(new 
OpenAiChat())),
-                new AiProxyProviderFactory(List.of(new OpenAi(), new 
OpenAiCompatible("DeepSeek"))), transport, retryProperties);
+                new AiProxyProviderFactory(List.of(new OpenAi(), new 
OpenAiCompatible("DeepSeek"))),
+                protocolConverterFactory(), transport, retryProperties);
     }
 
     private AiCommonConfig config(final boolean stream, final String model) {
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineTest.java
index cdcc7c502c..fc6817ba86 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyEngineTest.java
@@ -54,6 +54,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static 
org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyTestUtils.protocolConverterFactory;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class AiProxyEngineTest {
@@ -79,6 +80,7 @@ class AiProxyEngineTest {
         engine = new AiProxyEngine(
                 new AiProxyProtocolFactory(List.of(new OpenAiChat())),
                 new AiProxyProviderFactory(List.of(new OpenAi())),
+                protocolConverterFactory(),
                 transport);
         config = new AiCommonConfig();
         config.setProvider("OpenAI");
@@ -153,25 +155,31 @@ class AiProxyEngineTest {
     }
 
     @Test
-    void testCustomProviderProtocolRejectedBeforeBuildAndTransport() {
+    void testCustomChatProviderAcceptsResponsesThroughConversion() throws 
Exception {
         final AiProviderCapabilities all = AiProviderCapabilities.chat();
         final AtomicInteger builds = new AtomicInteger();
         useCustomProvider(new AiProviderCapabilities(Set.of(OpenAiChat.NAME), 
all.operations(), all.features()), builds);
         config.setProtocol(OpenAiResponses.NAME);
 
-        StepVerifier.create(engine.execute("{\"input\":\"hello\"}", config, 
Optional.empty(), response -> response.getBody().then()))
-                .expectError(ShenyuException.class).verify();
+        AiUpstreamRequest request = 
engine.buildRequest("{\"model\":\"test\",\"input\":\"hello\"}", config);
+        JsonNode payload = OBJECT_MAPPER.readTree(request.getBody());
 
-        assertEquals(0, builds.get());
+        assertEquals(1, builds.get());
+        assertEquals("hello", payload.at("/messages/0/content").asText());
         assertNull(transportedRequest.get());
     }
 
     @Test
-    void testCustomProviderOperationRejectedBeforeBuildAndTransport() {
+    void testCustomChatProviderValidatesConvertedOperation() {
         final AiProviderCapabilities all = AiProviderCapabilities.chat();
+        final AtomicInteger builds = new AtomicInteger();
+        useCustomProvider(new AiProviderCapabilities(all.protocols(),
+                Set.of(ShenyuAiRequest.Operation.CHAT_COMPLETION), 
all.features()), builds);
         config.setProtocol(OpenAiResponses.NAME);
-        assertCapabilityRejected("{\"input\":\"hello\"}",
-                new AiProviderCapabilities(all.protocols(), 
Set.of(ShenyuAiRequest.Operation.CHAT_COMPLETION), all.features()), 
"operation");
+
+        engine.buildRequest("{\"model\":\"test\",\"input\":\"hello\"}", 
config);
+
+        assertEquals(1, builds.get());
     }
 
     @Test
@@ -211,7 +219,7 @@ class AiProxyEngineTest {
         final AtomicInteger transports = new AtomicInteger();
         final AiProxyProvider fallback = customProvider(new 
AiProviderCapabilities(all.protocols(), all.operations(), Set.of()), builds);
         engine = new AiProxyEngine(new AiProxyProtocolFactory(List.of(new 
OpenAiChat())),
-                new AiProxyProviderFactory(List.of(new OpenAi(), fallback)), 
request -> {
+                new AiProxyProviderFactory(List.of(new OpenAi(), fallback)), 
protocolConverterFactory(), request -> {
                     transports.incrementAndGet();
                     return Mono.error(new 
AiTransportException(AiTransportException.Kind.IO_FAILURE, "unavailable", 
null));
                 }, Duration.ZERO);
@@ -241,7 +249,7 @@ class AiProxyEngineTest {
 
     private void useCustomProvider(final AiProviderCapabilities capabilities, 
final AtomicInteger builds) {
         engine = new AiProxyEngine(new AiProxyProtocolFactory(List.of(new 
OpenAiChat(), new OpenAiResponses())),
-                new 
AiProxyProviderFactory(List.of(customProvider(capabilities, builds))), request 
-> {
+                new 
AiProxyProviderFactory(List.of(customProvider(capabilities, builds))), 
protocolConverterFactory(), request -> {
                     transportedRequest.set(request);
                     return Mono.just(upstreamResponse);
                 });
diff --git 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyStreamCancellationTest.java
 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyStreamCancellationTest.java
index bd6b3ce523..bb6497f58f 100644
--- 
a/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyStreamCancellationTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-ai/shenyu-plugin-ai-proxy/src/test/java/org/apache/shenyu/plugin/ai/proxy/enhanced/service/AiProxyStreamCancellationTest.java
@@ -43,6 +43,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static 
org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyTestUtils.protocolConverterFactory;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -110,7 +111,7 @@ class AiProxyStreamCancellationTest {
         WebClient client = WebClient.builder().exchangeFunction(request -> 
Mono.just(ClientResponse.create(HttpStatus.OK)
                 .body(events.map(event -> 
DefaultDataBufferFactory.sharedInstance.wrap(event.getBytes(StandardCharsets.UTF_8)))).build())).build();
         return new AiProxyEngine(new AiProxyProtocolFactory(List.of(new 
OpenAiChat())), new AiProxyProviderFactory(List.of(new OpenAi())),
-                new WebClientAiProxyTransport(client), Duration.ZERO);
+                protocolConverterFactory(), new 
WebClientAiProxyTransport(client), Duration.ZERO);
     }
 
     private Mono<Void> stream(final AiProxyEngine engine, final AtomicInteger 
received) {
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfiguration.java
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfiguration.java
index d84e90d965..2500ceb6da 100644
--- 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfiguration.java
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/main/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfiguration.java
@@ -19,6 +19,9 @@ package org.apache.shenyu.springboot.starter.plugin.ai.proxy;
 
 import io.netty.channel.ChannelOption;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyPlugin;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConverter;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConverterFactory;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.ConnectResponsesToChatConverter;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.handler.AiProxyPluginHandler;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.AiProxyProtocol;
 import 
org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.AiProxyProtocolFactory;
@@ -107,6 +110,7 @@ public class AiProxyPluginConfiguration {
      *
      * @param aiProxyProtocolFactory protocol factory
      * @param aiProxyProviderFactory provider factory
+     * @param aiProtocolConverterFactory protocol converter factory
      * @param aiProxyTransport       transport
      * @param retryProperties        retry properties
      * @return the AI proxy engine
@@ -114,9 +118,34 @@ public class AiProxyPluginConfiguration {
     @Bean
     public AiProxyEngine aiProxyEngine(final AiProxyProtocolFactory 
aiProxyProtocolFactory,
                                        final AiProxyProviderFactory 
aiProxyProviderFactory,
+                                       final AiProtocolConverterFactory 
aiProtocolConverterFactory,
                                        final AiProxyTransport aiProxyTransport,
                                        final AiProxyRetryProperties 
retryProperties) {
-        return new AiProxyEngine(aiProxyProtocolFactory, 
aiProxyProviderFactory, aiProxyTransport, retryProperties);
+        return new AiProxyEngine(aiProxyProtocolFactory, 
aiProxyProviderFactory,
+                aiProtocolConverterFactory, aiProxyTransport, retryProperties);
+    }
+
+    /**
+     * Responses-to-Chat protocol converter.
+     *
+     * @return the Responses-to-Chat protocol converter
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public ConnectResponsesToChatConverter connectResponsesToChatConverter() {
+        return new ConnectResponsesToChatConverter();
+    }
+
+    /**
+     * AI protocol converter factory.
+     *
+     * @param converters protocol converters
+     * @return the AI protocol converter factory
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    public AiProtocolConverterFactory aiProtocolConverterFactory(final 
List<AiProtocolConverter> converters) {
+        return new AiProtocolConverterFactory(converters);
     }
 
     /**
diff --git 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/test/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfigurationTest.java
 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/test/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfigurationTest.java
index 271edb3cf6..618b7aa5b4 100644
--- 
a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/test/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfigurationTest.java
+++ 
b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-plugin/shenyu-spring-boot-starter-plugin-ai-proxy/src/test/java/org/apache/shenyu/springboot/starter/plugin/ai/proxy/AiProxyPluginConfigurationTest.java
@@ -28,7 +28,10 @@ import 
org.apache.shenyu.plugin.ai.proxy.enhanced.provider.AiProxyProvider;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.provider.OpenAiCompatible;
 import 
org.apache.shenyu.plugin.ai.proxy.enhanced.provider.OpenAiProviderOptions;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.AiProxyPlugin;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.AiProtocolConverterFactory;
+import 
org.apache.shenyu.plugin.ai.proxy.enhanced.conversion.ConnectResponsesToChatConverter;
 import 
org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.AiProxyProtocolFactory;
+import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiChat;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.protocol.OpenAiResponses;
 import 
org.apache.shenyu.plugin.ai.proxy.enhanced.provider.AiProxyProviderFactory;
 import org.apache.shenyu.plugin.ai.proxy.enhanced.service.AiProxyEngine;
@@ -61,12 +64,16 @@ class AiProxyPluginConfigurationTest {
     void createsTheMigratedPluginAndAllExistingProviderIdentifiers() {
         new 
ApplicationContextRunner().withUserConfiguration(AiProxyPluginConfiguration.class).run(context
 -> {
             
org.assertj.core.api.Assertions.assertThat(context).hasNotFailed().hasSingleBean(AiProxyPlugin.class).hasSingleBean(AiProxyEngine.class)
+                    
.hasSingleBean(AiProtocolConverterFactory.class).hasSingleBean(ConnectResponsesToChatConverter.class)
                     .doesNotHaveBean(AiModelFactoryRegistry.class);
             AiProxyProviderFactory factory = 
context.getBean(AiProxyProviderFactory.class);
+            
org.junit.jupiter.api.Assertions.assertTrue(context.getBean(AiProtocolConverterFactory.class)
+                    .supports(OpenAiResponses.NAME, OpenAiChat.NAME));
             assertSame(context.getBean(OpenAiResponses.class), 
context.getBean(AiProxyProtocolFactory.class).getProtocol(OpenAiResponses.NAME));
             for (String provider : List.of("OpenAI", "DeepSeek", "ALiYun", 
"OpenAPI", "Moonshot", "OpenAICompatible")) {
                 assertEquals(provider.toLowerCase(java.util.Locale.ROOT), 
factory.getProvider(provider).name().toLowerCase(java.util.Locale.ROOT));
-                
org.junit.jupiter.api.Assertions.assertTrue(factory.getProvider(provider).supports(OpenAiResponses.NAME));
+                
org.junit.jupiter.api.Assertions.assertTrue(factory.getProvider(provider).supports(OpenAiChat.NAME));
+                
org.junit.jupiter.api.Assertions.assertFalse(factory.getProvider(provider).supports(OpenAiResponses.NAME));
             }
         });
     }

Reply via email to