Skip to content

Commit 8bb2f70

Browse files
author
Mark Pollack
committed
Add unstable providers/* methods
Implement provider configuration end-to-end, all marked @UnstableAcpApi. Lets a client enumerate the model/backend providers an agent routes to and configure their protocol, base URL, and headers. - providers/list, providers/set, providers/disable across schema, agent handlers (async + sync builders), and both clients - Types: ListProviders/SetProvider/DisableProvider requests and responses, ProviderInfo, ProviderCurrentConfig, ProvidersCapabilities - AgentCapabilities.providers capability + NegotiatedCapabilities supportsProviders()/requireProviders(); preserved the existing 5-arg AgentCapabilities constructor via a convenience overload - Annotations @ListProviders/@SetProvider/@DisableProvider with resolvers, agent-support wiring, and DirectResponseHandler entries - Tests: handler invocation, serialization round-trips, capability extraction LlmProtocol is modeled as a String (open set: anthropic, openai, azure, vertex, bedrock, or custom).
1 parent 540e58c commit 8bb2f70

18 files changed

Lines changed: 864 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ Protocol currency: catching up to ACP spec v0.13.6 (June 2026).
2020
- Per-chunk `messageId` on `AgentMessageChunk`, `AgentThoughtChunk`, `UserMessageChunk`, plus
2121
`sendMessage(text, messageId)` / `sendThought(text, messageId)` convenience overloads on
2222
`PromptContext` and `SyncPromptContext`.
23+
- **Provider configuration methods** (`providers/list`, `providers/set`, `providers/disable`),
24+
marked `@UnstableAcpApi`: client methods `listProviders`/`setProvider`/`disableProvider`, agent
25+
handlers + `@ListProviders`/`@SetProvider`/`@DisableProvider`, the `ProviderInfo` /
26+
`ProviderCurrentConfig` / `ProvidersCapabilities` types, and a `providers` capability on
27+
`AgentCapabilities` surfaced via `NegotiatedCapabilities.supportsProviders()`.
2328
- `sessionCapabilities.delete` and `sessionCapabilities.additionalDirectories`, surfaced via
2429
`NegotiatedCapabilities` (`supports*`/`require*`).
2530

acp-agent-support/src/main/java/com/agentclientprotocol/sdk/agent/support/AcpAgentSupport.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@
2929
import com.agentclientprotocol.sdk.agent.support.resolver.CapabilitiesResolver;
3030
import com.agentclientprotocol.sdk.agent.support.resolver.CloseSessionRequestResolver;
3131
import com.agentclientprotocol.sdk.agent.support.resolver.DeleteSessionRequestResolver;
32+
import com.agentclientprotocol.sdk.agent.support.resolver.DisableProviderRequestResolver;
3233
import com.agentclientprotocol.sdk.agent.support.resolver.ForkSessionRequestResolver;
3334
import com.agentclientprotocol.sdk.agent.support.resolver.InitializeRequestResolver;
35+
import com.agentclientprotocol.sdk.agent.support.resolver.ListProvidersRequestResolver;
3436
import com.agentclientprotocol.sdk.agent.support.resolver.ListSessionsRequestResolver;
3537
import com.agentclientprotocol.sdk.agent.support.resolver.LoadSessionRequestResolver;
3638
import com.agentclientprotocol.sdk.agent.support.resolver.LogoutRequestResolver;
@@ -39,20 +41,24 @@
3941
import com.agentclientprotocol.sdk.agent.support.resolver.PromptRequestResolver;
4042
import com.agentclientprotocol.sdk.agent.support.resolver.ResumeSessionRequestResolver;
4143
import com.agentclientprotocol.sdk.agent.support.resolver.SessionIdResolver;
44+
import com.agentclientprotocol.sdk.agent.support.resolver.SetProviderRequestResolver;
4245
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionConfigOptionRequestResolver;
4346
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionModeRequestResolver;
4447
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionModelRequestResolver;
4548
import com.agentclientprotocol.sdk.annotation.Cancel;
4649
import com.agentclientprotocol.sdk.annotation.CloseSession;
4750
import com.agentclientprotocol.sdk.annotation.DeleteSession;
51+
import com.agentclientprotocol.sdk.annotation.DisableProvider;
4852
import com.agentclientprotocol.sdk.annotation.ForkSession;
4953
import com.agentclientprotocol.sdk.annotation.Initialize;
54+
import com.agentclientprotocol.sdk.annotation.ListProviders;
5055
import com.agentclientprotocol.sdk.annotation.ListSessions;
5156
import com.agentclientprotocol.sdk.annotation.LoadSession;
5257
import com.agentclientprotocol.sdk.annotation.Logout;
5358
import com.agentclientprotocol.sdk.annotation.NewSession;
5459
import com.agentclientprotocol.sdk.annotation.Prompt;
5560
import com.agentclientprotocol.sdk.annotation.ResumeSession;
61+
import com.agentclientprotocol.sdk.annotation.SetProvider;
5662
import com.agentclientprotocol.sdk.annotation.SetSessionConfigOption;
5763
import com.agentclientprotocol.sdk.annotation.SetSessionMode;
5864
import com.agentclientprotocol.sdk.annotation.SetSessionModel;
@@ -280,6 +286,24 @@ private void wireHandlers(AcpAgent.SyncAgentBuilder agentBuilder) {
280286
req -> invokeHandler(setConfigOptionHandler, req, req.sessionId(), null, null));
281287
}
282288

289+
// ListProviders handler (unstable)
290+
AcpHandlerMethod listProvidersHandler = handlers.get("providers/list");
291+
if (listProvidersHandler != null) {
292+
agentBuilder.listProvidersHandler(req -> invokeHandler(listProvidersHandler, req, null, null, null));
293+
}
294+
295+
// SetProvider handler (unstable)
296+
AcpHandlerMethod setProviderHandler = handlers.get("providers/set");
297+
if (setProviderHandler != null) {
298+
agentBuilder.setProviderHandler(req -> invokeHandler(setProviderHandler, req, null, null, null));
299+
}
300+
301+
// DisableProvider handler (unstable)
302+
AcpHandlerMethod disableProviderHandler = handlers.get("providers/disable");
303+
if (disableProviderHandler != null) {
304+
agentBuilder.disableProviderHandler(req -> invokeHandler(disableProviderHandler, req, null, null, null));
305+
}
306+
283307
// Cancel handler
284308
AcpHandlerMethod cancelHandler = handlers.get("session/cancel");
285309
if (cancelHandler != null) {
@@ -533,6 +557,19 @@ private void discoverHandlers(Class<?> agentClass, Supplier<Object> beanSupplier
533557
new AcpHandlerMethod(beanSupplier, method, "session/set_config_option"));
534558
log.debug("Discovered @SetSessionConfigOption handler: {}", method.getName());
535559
}
560+
if (method.isAnnotationPresent(ListProviders.class)) {
561+
handlers.put("providers/list", new AcpHandlerMethod(beanSupplier, method, "providers/list"));
562+
log.debug("Discovered @ListProviders handler: {}", method.getName());
563+
}
564+
if (method.isAnnotationPresent(SetProvider.class)) {
565+
handlers.put("providers/set", new AcpHandlerMethod(beanSupplier, method, "providers/set"));
566+
log.debug("Discovered @SetProvider handler: {}", method.getName());
567+
}
568+
if (method.isAnnotationPresent(DisableProvider.class)) {
569+
handlers.put("providers/disable",
570+
new AcpHandlerMethod(beanSupplier, method, "providers/disable"));
571+
log.debug("Discovered @DisableProvider handler: {}", method.getName());
572+
}
536573
if (method.isAnnotationPresent(Cancel.class)) {
537574
handlers.put("session/cancel", new AcpHandlerMethod(beanSupplier, method, "session/cancel"));
538575
log.debug("Discovered @Cancel handler: {}", method.getName());
@@ -557,6 +594,9 @@ private void addDefaultResolvers() {
557594
argumentResolvers.addResolver(new ResumeSessionRequestResolver());
558595
argumentResolvers.addResolver(new ForkSessionRequestResolver());
559596
argumentResolvers.addResolver(new SetSessionConfigOptionRequestResolver());
597+
argumentResolvers.addResolver(new ListProvidersRequestResolver());
598+
argumentResolvers.addResolver(new SetProviderRequestResolver());
599+
argumentResolvers.addResolver(new DisableProviderRequestResolver());
560600
argumentResolvers.addResolver(new CancelNotificationResolver());
561601
argumentResolvers.addResolver(new PromptContextResolver());
562602
argumentResolvers.addResolver(new SessionIdResolver());

acp-agent-support/src/main/java/com/agentclientprotocol/sdk/agent/support/handler/DirectResponseHandler.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
import com.agentclientprotocol.sdk.agent.support.AcpMethodParameter;
99
import com.agentclientprotocol.sdk.spec.AcpSchema.CloseSessionResponse;
1010
import com.agentclientprotocol.sdk.spec.AcpSchema.DeleteSessionResponse;
11+
import com.agentclientprotocol.sdk.spec.AcpSchema.DisableProviderResponse;
1112
import com.agentclientprotocol.sdk.spec.AcpSchema.ForkSessionResponse;
1213
import com.agentclientprotocol.sdk.spec.AcpSchema.InitializeResponse;
14+
import com.agentclientprotocol.sdk.spec.AcpSchema.ListProvidersResponse;
1315
import com.agentclientprotocol.sdk.spec.AcpSchema.ListSessionsResponse;
1416
import com.agentclientprotocol.sdk.spec.AcpSchema.LoadSessionResponse;
1517
import com.agentclientprotocol.sdk.spec.AcpSchema.LogoutResponse;
1618
import com.agentclientprotocol.sdk.spec.AcpSchema.NewSessionResponse;
1719
import com.agentclientprotocol.sdk.spec.AcpSchema.PromptResponse;
1820
import com.agentclientprotocol.sdk.spec.AcpSchema.ResumeSessionResponse;
21+
import com.agentclientprotocol.sdk.spec.AcpSchema.SetProviderResponse;
1922
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionConfigOptionResponse;
2023
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModeResponse;
2124
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModelResponse;
@@ -48,7 +51,10 @@ public boolean supportsReturnType(AcpMethodParameter returnType) {
4851
|| DeleteSessionResponse.class.isAssignableFrom(type)
4952
|| ResumeSessionResponse.class.isAssignableFrom(type)
5053
|| ForkSessionResponse.class.isAssignableFrom(type)
51-
|| SetSessionConfigOptionResponse.class.isAssignableFrom(type);
54+
|| SetSessionConfigOptionResponse.class.isAssignableFrom(type)
55+
|| ListProvidersResponse.class.isAssignableFrom(type)
56+
|| SetProviderResponse.class.isAssignableFrom(type)
57+
|| DisableProviderResponse.class.isAssignableFrom(type);
5258
}
5359

5460
@Override
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.agent.support.resolver;
6+
7+
import com.agentclientprotocol.sdk.agent.support.AcpInvocationContext;
8+
import com.agentclientprotocol.sdk.agent.support.AcpMethodParameter;
9+
import com.agentclientprotocol.sdk.spec.AcpSchema.DisableProviderRequest;
10+
11+
/**
12+
* Resolves {@link DisableProviderRequest} parameters in {@code providers/disable} handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 0.13.0
16+
*/
17+
public class DisableProviderRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return DisableProviderRequest.class.isAssignableFrom(parameter.getParameterType());
22+
}
23+
24+
@Override
25+
public Object resolveArgument(AcpMethodParameter parameter, AcpInvocationContext context) {
26+
Object request = context.getRequest();
27+
if (request instanceof DisableProviderRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException("Expected DisableProviderRequest but got: "
31+
+ (request != null ? request.getClass().getName() : "null"));
32+
}
33+
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.agent.support.resolver;
6+
7+
import com.agentclientprotocol.sdk.agent.support.AcpInvocationContext;
8+
import com.agentclientprotocol.sdk.agent.support.AcpMethodParameter;
9+
import com.agentclientprotocol.sdk.spec.AcpSchema.ListProvidersRequest;
10+
11+
/**
12+
* Resolves {@link ListProvidersRequest} parameters in {@code providers/list} handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 0.13.0
16+
*/
17+
public class ListProvidersRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return ListProvidersRequest.class.isAssignableFrom(parameter.getParameterType());
22+
}
23+
24+
@Override
25+
public Object resolveArgument(AcpMethodParameter parameter, AcpInvocationContext context) {
26+
Object request = context.getRequest();
27+
if (request instanceof ListProvidersRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException(
31+
"Expected ListProvidersRequest but got: " + (request != null ? request.getClass().getName() : "null"));
32+
}
33+
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.agent.support.resolver;
6+
7+
import com.agentclientprotocol.sdk.agent.support.AcpInvocationContext;
8+
import com.agentclientprotocol.sdk.agent.support.AcpMethodParameter;
9+
import com.agentclientprotocol.sdk.spec.AcpSchema.SetProviderRequest;
10+
11+
/**
12+
* Resolves {@link SetProviderRequest} parameters in {@code providers/set} handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 0.13.0
16+
*/
17+
public class SetProviderRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return SetProviderRequest.class.isAssignableFrom(parameter.getParameterType());
22+
}
23+
24+
@Override
25+
public Object resolveArgument(AcpMethodParameter parameter, AcpInvocationContext context) {
26+
Object request = context.getRequest();
27+
if (request instanceof SetProviderRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException(
31+
"Expected SetProviderRequest but got: " + (request != null ? request.getClass().getName() : "null"));
32+
}
33+
34+
}

acp-agent-support/src/test/java/com/agentclientprotocol/sdk/agent/support/AcpAgentSupportTest.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
import com.agentclientprotocol.sdk.annotation.CloseSession;
1515
import com.agentclientprotocol.sdk.annotation.DeleteSession;
1616
import com.agentclientprotocol.sdk.annotation.Initialize;
17+
import com.agentclientprotocol.sdk.annotation.ListProviders;
1718
import com.agentclientprotocol.sdk.annotation.ListSessions;
1819
import com.agentclientprotocol.sdk.annotation.LoadSession;
1920
import com.agentclientprotocol.sdk.annotation.Logout;
2021
import com.agentclientprotocol.sdk.annotation.NewSession;
22+
import com.agentclientprotocol.sdk.annotation.SetProvider;
2123
import com.agentclientprotocol.sdk.annotation.Prompt;
2224
import com.agentclientprotocol.sdk.annotation.ResumeSession;
2325
import com.agentclientprotocol.sdk.annotation.SetSessionMode;
@@ -31,6 +33,8 @@
3133
import com.agentclientprotocol.sdk.spec.AcpSchema.DeleteSessionResponse;
3234
import com.agentclientprotocol.sdk.spec.AcpSchema.InitializeRequest;
3335
import com.agentclientprotocol.sdk.spec.AcpSchema.InitializeResponse;
36+
import com.agentclientprotocol.sdk.spec.AcpSchema.ListProvidersRequest;
37+
import com.agentclientprotocol.sdk.spec.AcpSchema.ListProvidersResponse;
3438
import com.agentclientprotocol.sdk.spec.AcpSchema.ListSessionsRequest;
3539
import com.agentclientprotocol.sdk.spec.AcpSchema.ListSessionsResponse;
3640
import com.agentclientprotocol.sdk.spec.AcpSchema.LoadSessionRequest;
@@ -41,9 +45,12 @@
4145
import com.agentclientprotocol.sdk.spec.AcpSchema.NewSessionResponse;
4246
import com.agentclientprotocol.sdk.spec.AcpSchema.PromptRequest;
4347
import com.agentclientprotocol.sdk.spec.AcpSchema.PromptResponse;
48+
import com.agentclientprotocol.sdk.spec.AcpSchema.ProviderInfo;
4449
import com.agentclientprotocol.sdk.spec.AcpSchema.ResumeSessionRequest;
4550
import com.agentclientprotocol.sdk.spec.AcpSchema.ResumeSessionResponse;
4651
import com.agentclientprotocol.sdk.spec.AcpSchema.SessionInfo;
52+
import com.agentclientprotocol.sdk.spec.AcpSchema.SetProviderRequest;
53+
import com.agentclientprotocol.sdk.spec.AcpSchema.SetProviderResponse;
4754
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModeRequest;
4855
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModeResponse;
4956
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModelRequest;
@@ -591,6 +598,87 @@ DeleteSessionResponse deleteSession(DeleteSessionRequest req) {
591598
assertThat(resp).isNotNull();
592599
}
593600

601+
@Test
602+
void listProvidersHandlerInvoked() throws Exception {
603+
@AcpAgent
604+
class ListProvidersAgent {
605+
606+
@Initialize
607+
InitializeResponse init() {
608+
return InitializeResponse.ok();
609+
}
610+
611+
@ListProviders
612+
ListProvidersResponse listProviders(ListProvidersRequest req) {
613+
return new ListProvidersResponse(List.of(new ProviderInfo("openai", List.of("openai"), false, null)));
614+
}
615+
616+
}
617+
618+
agentSupport = AcpAgentSupport.create(new ListProvidersAgent())
619+
.transport(transportPair.agentTransport())
620+
.requestTimeout(TIMEOUT)
621+
.build();
622+
623+
agentSupport.start();
624+
Thread.sleep(100);
625+
626+
client = AcpClient.async(transportPair.clientTransport())
627+
.requestTimeout(TIMEOUT)
628+
.build();
629+
630+
client.initialize(new InitializeRequest(1, null)).block(TIMEOUT);
631+
ListProvidersResponse resp = client.listProviders(new ListProvidersRequest()).block(TIMEOUT);
632+
633+
assertThat(resp).isNotNull();
634+
assertThat(resp.providers()).hasSize(1);
635+
assertThat(resp.providers().get(0).id()).isEqualTo("openai");
636+
}
637+
638+
@Test
639+
void setProviderHandlerInvoked() throws Exception {
640+
AtomicReference<String> configuredId = new AtomicReference<>();
641+
AtomicReference<String> configuredBaseUrl = new AtomicReference<>();
642+
643+
@AcpAgent
644+
class SetProviderAgent {
645+
646+
@Initialize
647+
InitializeResponse init() {
648+
return InitializeResponse.ok();
649+
}
650+
651+
@SetProvider
652+
SetProviderResponse setProvider(SetProviderRequest req) {
653+
configuredId.set(req.id());
654+
configuredBaseUrl.set(req.baseUrl());
655+
return new SetProviderResponse();
656+
}
657+
658+
}
659+
660+
agentSupport = AcpAgentSupport.create(new SetProviderAgent())
661+
.transport(transportPair.agentTransport())
662+
.requestTimeout(TIMEOUT)
663+
.build();
664+
665+
agentSupport.start();
666+
Thread.sleep(100);
667+
668+
client = AcpClient.async(transportPair.clientTransport())
669+
.requestTimeout(TIMEOUT)
670+
.build();
671+
672+
client.initialize(new InitializeRequest(1, null)).block(TIMEOUT);
673+
SetProviderResponse resp = client
674+
.setProvider(new SetProviderRequest("main", "anthropic", "https://api.anthropic.com"))
675+
.block(TIMEOUT);
676+
677+
assertThat(configuredId.get()).isEqualTo("main");
678+
assertThat(configuredBaseUrl.get()).isEqualTo("https://api.anthropic.com");
679+
assertThat(resp).isNotNull();
680+
}
681+
594682
@Test
595683
void resumeSessionHandlerInvoked() throws Exception {
596684
AtomicReference<String> resumedSessionId = new AtomicReference<>();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.annotation;
6+
7+
import java.lang.annotation.Documented;
8+
import java.lang.annotation.ElementType;
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.RetentionPolicy;
11+
import java.lang.annotation.Target;
12+
13+
/**
14+
* Marks a method as the handler for the {@code providers/disable} JSON-RPC method (UNSTABLE).
15+
*
16+
* <p>The annotated method disables a provider by id. It may take a {@code DisableProviderRequest}
17+
* parameter and should return a {@code DisableProviderResponse} (or
18+
* {@code Mono<DisableProviderResponse>}, or {@code void} for an empty response).
19+
*
20+
* @author Mark Pollack
21+
* @since 0.13.0
22+
* @see AcpAgent
23+
*/
24+
@UnstableAcpApi
25+
@Target(ElementType.METHOD)
26+
@Retention(RetentionPolicy.RUNTIME)
27+
@Documented
28+
public @interface DisableProvider {
29+
30+
}

0 commit comments

Comments
 (0)