Skip to content

Commit 436aefc

Browse files
author
Mark Pollack
committed
Spring Boot: honour spring.acp.agent.enabled=false; fail on WebFlux with type=http
AcpAgentAutoConfiguration now carries the enabled condition its two sibling autoconfigurations already had. With enabled=false it no longer builds the AcpAgentFactory or serves the agent on an application-defined AcpAgentTransport, and two @AcpAgent beans no longer fail the startup. spring.acp.agent.transport.type=http in a reactive (WebFlux) web application matched neither the servlet nor the non-web branch and served nothing silently; it now fails the startup with a clear message. spring-webflux is added as a test dependency so the test runs a real reactive context.
1 parent c48b5bf commit 436aefc

7 files changed

Lines changed: 82 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10431043

10441044
### Fixed
10451045

1046+
- **Spring Boot: `spring.acp.agent.enabled=false` turns the agent off on every transport.** The
1047+
stdio transport and the HTTP endpoint already backed off, but `AcpAgentAutoConfiguration` did
1048+
not: it still built the `AcpAgentFactory` from the `@AcpAgent` bean and still served the agent on
1049+
an `AcpAgentTransport` bean the application defined itself. With `enabled=false` it now creates
1050+
no agent beans and serves nothing, as Micronaut and Quarkus already did.
1051+
1052+
- **Spring Boot: two `@AcpAgent` beans no longer fail the startup when
1053+
`spring.acp.agent.enabled=false`.** The startup failed with the error naming both beans, whose
1054+
advice was to set `spring.acp.agent.enabled=false`. Same cause and fix as the entry above.
1055+
1056+
- **Spring Boot: `spring.acp.agent.transport.type=http` in a reactive (WebFlux) web application
1057+
fails the startup.** It served no endpoint and gave no error. The startup now fails with "The
1058+
ACP HTTP transport needs a servlet web application or the standalone listener
1059+
(acp-streamable-http-jetty); WebFlux is not supported".
1060+
10461061
- **A prompt answers `cancelled` once `session/cancel` was received, whatever its handler
10471062
returns.** ACP requires an agent to answer a cancelled prompt with stop reason `cancelled`. A
10481063
handler that noticed the cancel and returned another stop reason within the grace period had

‎acp-spring-boot-autoconfigure/pom.xml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@
153153
<artifactId>spring-boot-starter-web</artifactId>
154154
<scope>test</scope>
155155
</dependency>
156+
<!-- A reactive web application: Boot detects one by spring-webflux on the classpath -->
157+
<dependency>
158+
<groupId>org.springframework</groupId>
159+
<artifactId>spring-webflux</artifactId>
160+
<scope>test</scope>
161+
</dependency>
156162
<dependency>
157163
<groupId>com.agentclientprotocol</groupId>
158164
<artifactId>acp-test</artifactId>

‎acp-spring-boot-autoconfigure/src/main/java/com/agentclientprotocol/sdk/spring/boot/autoconfigure/agent/AcpAgentAutoConfiguration.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3132
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3233
import org.springframework.context.ApplicationContext;
3334
import org.springframework.context.ConfigurableApplicationContext;
@@ -56,9 +57,13 @@
5657
* the handler executor come from {@link AcpAgentProperties}. More than one {@code @AcpAgent} bean
5758
* fails the startup with an error naming them. On the HTTP endpoint every connection's agent calls
5859
* the same bean, so its handler methods must be thread-safe.
60+
*
61+
* <p>With {@code spring.acp.agent.enabled=false} it creates nothing: no factory and no agent, on
62+
* any transport, however many {@code @AcpAgent} beans there are.
5963
*/
6064
@AutoConfiguration(after = AcpAgentTransportAutoConfiguration.class)
6165
@ConditionalOnClass(AcpAgentSupport.class)
66+
@ConditionalOnProperty(prefix = "spring.acp.agent", name = "enabled", havingValue = "true", matchIfMissing = true)
6267
@EnableConfigurationProperties(AcpAgentProperties.class)
6368
public class AcpAgentAutoConfiguration {
6469

‎acp-spring-boot-autoconfigure/src/main/java/com/agentclientprotocol/sdk/spring/boot/autoconfigure/agent/AcpAgentHttpAutoConfiguration.java‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
* HTTP/2 and WebSocket upgrades on the same path. It starts with the context and stops with it,
4949
* waiting at most 30 seconds. A {@code StreamableHttpAcpAgentTransport} bean of the application's
5050
* own replaces it.</li>
51-
* <li>A reactive web application gets neither.</li>
51+
* <li>A reactive (WebFlux) web application fails the startup: the endpoint needs a servlet web
52+
* application or the standalone listener.</li>
5253
* </ul>
5354
* The endpoint's limits come from {@code spring.acp.agent.transport.http.*}
5455
* ({@link AcpAgentProperties.AgentHttpProperties}). Nothing here applies when
@@ -89,6 +90,17 @@ AcpServletLifecycle acpServletLifecycle(
8990

9091
}
9192

93+
@Configuration(proxyBeanMethods = false)
94+
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
95+
static class ReactiveConfiguration {
96+
97+
ReactiveConfiguration() {
98+
throw new IllegalStateException("The ACP HTTP transport needs a servlet web application or the "
99+
+ "standalone listener (acp-streamable-http-jetty); WebFlux is not supported");
100+
}
101+
102+
}
103+
92104
@Configuration(proxyBeanMethods = false)
93105
@ConditionalOnNotWebApplication
94106
static class ListenerConfiguration {

‎acp-spring-boot-autoconfigure/src/main/java/com/agentclientprotocol/sdk/spring/boot/autoconfigure/agent/AcpAgentProperties.java‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,9 @@ public class AcpAgentProperties {
7979
* ({@code spring.acp.agent.enabled}). Default {@code true}; maps to
8080
* {@link AcpAgentSettings#enabled()}.
8181
*
82-
* <p>With {@code false} the autoconfiguration creates no stdio transport and no HTTP endpoint.
83-
* {@link AcpAgentAutoConfiguration} still builds its agent factory from the {@code @AcpAgent}
84-
* bean, and still serves the bean on an {@code AcpAgentTransport} bean the application defines
85-
* itself.
82+
* <p>With {@code false} the agent autoconfiguration creates nothing: no agent factory, no stdio
83+
* transport, no HTTP endpoint, and no agent on an {@code AcpAgentTransport} bean the application
84+
* defines itself. More than one {@code @AcpAgent} bean is then no error.
8685
* @return whether the agent is served
8786
*/
8887
public boolean isEnabled() {

‎acp-spring-boot-autoconfigure/src/test/java/com/agentclientprotocol/sdk/spring/boot/autoconfigure/agent/AcpAgentAutoConfigurationTests.java‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.UUID;
1010
import java.util.concurrent.atomic.AtomicInteger;
1111

12+
import com.agentclientprotocol.sdk.agent.AcpAgentFactory;
1213
import com.agentclientprotocol.sdk.agent.SyncPromptContext;
1314
import com.agentclientprotocol.sdk.agent.support.AcpAgentSupport;
1415
import com.agentclientprotocol.sdk.agent.support.interceptor.AcpInterceptor;
@@ -106,6 +107,31 @@ void noAgentWhenDisabled() {
106107
});
107108
}
108109

110+
@Test
111+
void disabledServesNothingOnTheApplicationsOwnTransport() {
112+
new ApplicationContextRunner()
113+
.withConfiguration(AutoConfigurations.of(AcpAgentTransportAutoConfiguration.class,
114+
AcpAgentAutoConfiguration.class, AcpAgentHttpAutoConfiguration.class))
115+
.withUserConfiguration(SingleAgentWithInMemoryTransportConfiguration.class)
116+
.withPropertyValues("spring.acp.agent.enabled=false")
117+
.run(context -> {
118+
assertThat(context).hasNotFailed();
119+
assertThat(context).doesNotHaveBean(AcpAgentFactory.class);
120+
assertThat(context).doesNotHaveBean("acpAgentLifecycle");
121+
});
122+
}
123+
124+
@Test
125+
void disabledToleratesTwoAgentBeans() {
126+
this.runner.withUserConfiguration(MultipleAgentConfiguration.class)
127+
.withPropertyValues("spring.acp.agent.enabled=false")
128+
.run(context -> {
129+
assertThat(context).hasNotFailed();
130+
assertThat(context).doesNotHaveBean(AcpAgentFactory.class);
131+
assertThat(context).doesNotHaveBean("acpAgentLifecycle");
132+
});
133+
}
134+
109135
@Test
110136
void noAgentWithoutTransportBean() {
111137
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(AcpAgentAutoConfiguration.class))

‎acp-spring-boot-autoconfigure/src/test/java/com/agentclientprotocol/sdk/spring/boot/autoconfigure/agent/AcpAgentHttpAutoConfigurationTests.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import org.springframework.boot.autoconfigure.AutoConfigurations;
3636
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
37+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
3738
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
3839
import org.springframework.boot.web.servlet.ServletRegistrationBean;
3940
import org.springframework.context.annotation.Bean;
@@ -197,6 +198,19 @@ void backsOffWhenAgentDisabled() {
197198
.run(context -> assertThat(context).doesNotHaveBean(StreamableHttpAcpAgentTransport.class));
198199
}
199200

201+
@Test
202+
void reactiveWebApplicationFailsTheStartup() {
203+
new ReactiveWebApplicationContextRunner().withConfiguration(AGENT_AUTO_CONFIGURATIONS)
204+
.withUserConfiguration(EchoAgentConfiguration.class)
205+
.withPropertyValues("spring.acp.agent.transport.type=http", "spring.acp.agent.transport.http.listener.port=0")
206+
.run(context -> {
207+
assertThat(context).hasFailed();
208+
assertThat(context.getStartupFailure()).rootCause()
209+
.hasMessageContaining("The ACP HTTP transport needs a servlet web application or the "
210+
+ "standalone listener (acp-streamable-http-jetty); WebFlux is not supported");
211+
});
212+
}
213+
200214
@Test
201215
void servletWebApplicationMountsServletInsteadOfListener() {
202216
new WebApplicationContextRunner().withConfiguration(AGENT_AUTO_CONFIGURATIONS)

0 commit comments

Comments
 (0)