Skip to content

Commit ad3bcde

Browse files
author
Mark Pollack
committed
Add session/list, session/close, session/resume methods
Implement three stable ACP protocol methods across all SDK layers: - session/list: enumerate sessions with optional cwd filter and pagination - session/close: close a session and cancel in-flight work (request/response) - session/resume: reconnect to session without replaying history Each method follows the established pattern: AcpSchema types, NegotiatedCapabilities, handler interfaces (sync + async), builder methods, client methods, annotations, resolvers, and tests. Also adds SessionCapabilities and SessionInfo types to the protocol schema, and updates AgentCapabilities with sessionCapabilities field.
1 parent 526eeff commit ad3bcde

16 files changed

Lines changed: 931 additions & 9 deletions

File tree

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,25 @@
2727
import com.agentclientprotocol.sdk.agent.support.resolver.ArgumentResolverComposite;
2828
import com.agentclientprotocol.sdk.agent.support.resolver.CancelNotificationResolver;
2929
import com.agentclientprotocol.sdk.agent.support.resolver.CapabilitiesResolver;
30+
import com.agentclientprotocol.sdk.agent.support.resolver.CloseSessionRequestResolver;
3031
import com.agentclientprotocol.sdk.agent.support.resolver.InitializeRequestResolver;
32+
import com.agentclientprotocol.sdk.agent.support.resolver.ListSessionsRequestResolver;
3133
import com.agentclientprotocol.sdk.agent.support.resolver.LoadSessionRequestResolver;
3234
import com.agentclientprotocol.sdk.agent.support.resolver.NewSessionRequestResolver;
3335
import com.agentclientprotocol.sdk.agent.support.resolver.PromptContextResolver;
3436
import com.agentclientprotocol.sdk.agent.support.resolver.PromptRequestResolver;
37+
import com.agentclientprotocol.sdk.agent.support.resolver.ResumeSessionRequestResolver;
3538
import com.agentclientprotocol.sdk.agent.support.resolver.SessionIdResolver;
3639
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionModeRequestResolver;
3740
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionModelRequestResolver;
3841
import com.agentclientprotocol.sdk.annotation.Cancel;
42+
import com.agentclientprotocol.sdk.annotation.CloseSession;
3943
import com.agentclientprotocol.sdk.annotation.Initialize;
44+
import com.agentclientprotocol.sdk.annotation.ListSessions;
4045
import com.agentclientprotocol.sdk.annotation.LoadSession;
4146
import com.agentclientprotocol.sdk.annotation.NewSession;
4247
import com.agentclientprotocol.sdk.annotation.Prompt;
48+
import com.agentclientprotocol.sdk.annotation.ResumeSession;
4349
import com.agentclientprotocol.sdk.annotation.SetSessionMode;
4450
import com.agentclientprotocol.sdk.annotation.SetSessionModel;
4551
import com.agentclientprotocol.sdk.capabilities.NegotiatedCapabilities;
@@ -218,6 +224,26 @@ private void wireHandlers(AcpAgent.SyncAgentBuilder agentBuilder) {
218224
agentBuilder.setSessionModelHandler(req -> invokeHandler(setModelHandler, req, req.sessionId(), null, null));
219225
}
220226

227+
// ListSessions handler
228+
AcpHandlerMethod listSessionsHandler = handlers.get("session/list");
229+
if (listSessionsHandler != null) {
230+
agentBuilder.listSessionsHandler(req -> invokeHandler(listSessionsHandler, req, null, null, null));
231+
}
232+
233+
// CloseSession handler
234+
AcpHandlerMethod closeSessionHandler = handlers.get("session/close");
235+
if (closeSessionHandler != null) {
236+
agentBuilder.closeSessionHandler(
237+
req -> invokeHandler(closeSessionHandler, req, req.sessionId(), null, null));
238+
}
239+
240+
// ResumeSession handler
241+
AcpHandlerMethod resumeSessionHandler = handlers.get("session/resume");
242+
if (resumeSessionHandler != null) {
243+
agentBuilder.resumeSessionHandler(
244+
req -> invokeHandler(resumeSessionHandler, req, req.sessionId(), null, null));
245+
}
246+
221247
// Cancel handler
222248
AcpHandlerMethod cancelHandler = handlers.get("session/cancel");
223249
if (cancelHandler != null) {
@@ -441,6 +467,18 @@ private void discoverHandlers(Class<?> agentClass, Supplier<Object> beanSupplier
441467
handlers.put("session/set_model", new AcpHandlerMethod(beanSupplier, method, "session/set_model"));
442468
log.debug("Discovered @SetSessionModel handler: {}", method.getName());
443469
}
470+
if (method.isAnnotationPresent(ListSessions.class)) {
471+
handlers.put("session/list", new AcpHandlerMethod(beanSupplier, method, "session/list"));
472+
log.debug("Discovered @ListSessions handler: {}", method.getName());
473+
}
474+
if (method.isAnnotationPresent(CloseSession.class)) {
475+
handlers.put("session/close", new AcpHandlerMethod(beanSupplier, method, "session/close"));
476+
log.debug("Discovered @CloseSession handler: {}", method.getName());
477+
}
478+
if (method.isAnnotationPresent(ResumeSession.class)) {
479+
handlers.put("session/resume", new AcpHandlerMethod(beanSupplier, method, "session/resume"));
480+
log.debug("Discovered @ResumeSession handler: {}", method.getName());
481+
}
444482
if (method.isAnnotationPresent(Cancel.class)) {
445483
handlers.put("session/cancel", new AcpHandlerMethod(beanSupplier, method, "session/cancel"));
446484
log.debug("Discovered @Cancel handler: {}", method.getName());
@@ -457,6 +495,9 @@ private void addDefaultResolvers() {
457495
argumentResolvers.addResolver(new PromptRequestResolver());
458496
argumentResolvers.addResolver(new SetSessionModeRequestResolver());
459497
argumentResolvers.addResolver(new SetSessionModelRequestResolver());
498+
argumentResolvers.addResolver(new ListSessionsRequestResolver());
499+
argumentResolvers.addResolver(new CloseSessionRequestResolver());
500+
argumentResolvers.addResolver(new ResumeSessionRequestResolver());
460501
argumentResolvers.addResolver(new CancelNotificationResolver());
461502
argumentResolvers.addResolver(new PromptContextResolver());
462503
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
@@ -6,10 +6,13 @@
66

77
import com.agentclientprotocol.sdk.agent.support.AcpInvocationContext;
88
import com.agentclientprotocol.sdk.agent.support.AcpMethodParameter;
9+
import com.agentclientprotocol.sdk.spec.AcpSchema.CloseSessionResponse;
910
import com.agentclientprotocol.sdk.spec.AcpSchema.InitializeResponse;
11+
import com.agentclientprotocol.sdk.spec.AcpSchema.ListSessionsResponse;
1012
import com.agentclientprotocol.sdk.spec.AcpSchema.LoadSessionResponse;
1113
import com.agentclientprotocol.sdk.spec.AcpSchema.NewSessionResponse;
1214
import com.agentclientprotocol.sdk.spec.AcpSchema.PromptResponse;
15+
import com.agentclientprotocol.sdk.spec.AcpSchema.ResumeSessionResponse;
1316
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModeResponse;
1417
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModelResponse;
1518

@@ -33,7 +36,10 @@ public boolean supportsReturnType(AcpMethodParameter returnType) {
3336
|| LoadSessionResponse.class.isAssignableFrom(type)
3437
|| PromptResponse.class.isAssignableFrom(type)
3538
|| SetSessionModeResponse.class.isAssignableFrom(type)
36-
|| SetSessionModelResponse.class.isAssignableFrom(type);
39+
|| SetSessionModelResponse.class.isAssignableFrom(type)
40+
|| ListSessionsResponse.class.isAssignableFrom(type)
41+
|| CloseSessionResponse.class.isAssignableFrom(type)
42+
|| ResumeSessionResponse.class.isAssignableFrom(type);
3743
}
3844

3945
@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.CloseSessionRequest;
10+
11+
/**
12+
* Resolves {@link CloseSessionRequest} parameters in close session handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 1.0.0
16+
*/
17+
public class CloseSessionRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return CloseSessionRequest.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 CloseSessionRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException(
31+
"Expected CloseSessionRequest 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.ListSessionsRequest;
10+
11+
/**
12+
* Resolves {@link ListSessionsRequest} parameters in list sessions handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 1.0.0
16+
*/
17+
public class ListSessionsRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return ListSessionsRequest.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 ListSessionsRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException(
31+
"Expected ListSessionsRequest 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.ResumeSessionRequest;
10+
11+
/**
12+
* Resolves {@link ResumeSessionRequest} parameters in resume session handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 1.0.0
16+
*/
17+
public class ResumeSessionRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return ResumeSessionRequest.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 ResumeSessionRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException(
31+
"Expected ResumeSessionRequest 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: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,33 @@
1111
import com.agentclientprotocol.sdk.agent.SyncPromptContext;
1212
import com.agentclientprotocol.sdk.annotation.AcpAgent;
1313
import com.agentclientprotocol.sdk.annotation.Cancel;
14+
import com.agentclientprotocol.sdk.annotation.CloseSession;
1415
import com.agentclientprotocol.sdk.annotation.Initialize;
16+
import com.agentclientprotocol.sdk.annotation.ListSessions;
1517
import com.agentclientprotocol.sdk.annotation.LoadSession;
1618
import com.agentclientprotocol.sdk.annotation.NewSession;
1719
import com.agentclientprotocol.sdk.annotation.Prompt;
20+
import com.agentclientprotocol.sdk.annotation.ResumeSession;
1821
import com.agentclientprotocol.sdk.annotation.SetSessionMode;
1922
import com.agentclientprotocol.sdk.annotation.SetSessionModel;
2023
import com.agentclientprotocol.sdk.client.AcpAsyncClient;
2124
import com.agentclientprotocol.sdk.client.AcpClient;
2225
import com.agentclientprotocol.sdk.spec.AcpSchema.CancelNotification;
26+
import com.agentclientprotocol.sdk.spec.AcpSchema.CloseSessionRequest;
27+
import com.agentclientprotocol.sdk.spec.AcpSchema.CloseSessionResponse;
2328
import com.agentclientprotocol.sdk.spec.AcpSchema.InitializeRequest;
2429
import com.agentclientprotocol.sdk.spec.AcpSchema.InitializeResponse;
30+
import com.agentclientprotocol.sdk.spec.AcpSchema.ListSessionsRequest;
31+
import com.agentclientprotocol.sdk.spec.AcpSchema.ListSessionsResponse;
2532
import com.agentclientprotocol.sdk.spec.AcpSchema.LoadSessionRequest;
2633
import com.agentclientprotocol.sdk.spec.AcpSchema.LoadSessionResponse;
2734
import com.agentclientprotocol.sdk.spec.AcpSchema.NewSessionRequest;
2835
import com.agentclientprotocol.sdk.spec.AcpSchema.NewSessionResponse;
2936
import com.agentclientprotocol.sdk.spec.AcpSchema.PromptRequest;
3037
import com.agentclientprotocol.sdk.spec.AcpSchema.PromptResponse;
38+
import com.agentclientprotocol.sdk.spec.AcpSchema.ResumeSessionRequest;
39+
import com.agentclientprotocol.sdk.spec.AcpSchema.ResumeSessionResponse;
40+
import com.agentclientprotocol.sdk.spec.AcpSchema.SessionInfo;
3141
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModeRequest;
3242
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModeResponse;
3343
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModelRequest;
@@ -412,6 +422,130 @@ void onCancel(CancelNotification notification) {
412422
assertThat(cancelledSessionId.get()).isEqualTo("cancel-session");
413423
}
414424

425+
@Test
426+
void listSessionsHandlerInvoked() throws Exception {
427+
AtomicReference<String> requestedCwd = new AtomicReference<>();
428+
429+
@AcpAgent
430+
class ListSessionsAgent {
431+
432+
@Initialize
433+
InitializeResponse init() {
434+
return InitializeResponse.ok();
435+
}
436+
437+
@ListSessions
438+
ListSessionsResponse listSessions(ListSessionsRequest req) {
439+
requestedCwd.set(req.cwd());
440+
return new ListSessionsResponse(
441+
List.of(new SessionInfo("session-1", "/workspace")));
442+
}
443+
444+
}
445+
446+
agentSupport = AcpAgentSupport.create(new ListSessionsAgent())
447+
.transport(transportPair.agentTransport())
448+
.requestTimeout(TIMEOUT)
449+
.build();
450+
451+
agentSupport.start();
452+
Thread.sleep(100);
453+
454+
client = AcpClient.async(transportPair.clientTransport())
455+
.requestTimeout(TIMEOUT)
456+
.build();
457+
458+
client.initialize(new InitializeRequest(1, null)).block(TIMEOUT);
459+
ListSessionsResponse resp = client.listSessions(new ListSessionsRequest("/workspace"))
460+
.block(TIMEOUT);
461+
462+
assertThat(requestedCwd.get()).isEqualTo("/workspace");
463+
assertThat(resp).isNotNull();
464+
assertThat(resp.sessions()).hasSize(1);
465+
assertThat(resp.sessions().get(0).sessionId()).isEqualTo("session-1");
466+
}
467+
468+
@Test
469+
void closeSessionHandlerInvoked() throws Exception {
470+
AtomicReference<String> closedSessionId = new AtomicReference<>();
471+
472+
@AcpAgent
473+
class CloseSessionAgent {
474+
475+
@Initialize
476+
InitializeResponse init() {
477+
return InitializeResponse.ok();
478+
}
479+
480+
@CloseSession
481+
CloseSessionResponse closeSession(CloseSessionRequest req) {
482+
closedSessionId.set(req.sessionId());
483+
return new CloseSessionResponse();
484+
}
485+
486+
}
487+
488+
agentSupport = AcpAgentSupport.create(new CloseSessionAgent())
489+
.transport(transportPair.agentTransport())
490+
.requestTimeout(TIMEOUT)
491+
.build();
492+
493+
agentSupport.start();
494+
Thread.sleep(100);
495+
496+
client = AcpClient.async(transportPair.clientTransport())
497+
.requestTimeout(TIMEOUT)
498+
.build();
499+
500+
client.initialize(new InitializeRequest(1, null)).block(TIMEOUT);
501+
CloseSessionResponse resp = client.closeSession(new CloseSessionRequest("session-to-close"))
502+
.block(TIMEOUT);
503+
504+
assertThat(closedSessionId.get()).isEqualTo("session-to-close");
505+
assertThat(resp).isNotNull();
506+
}
507+
508+
@Test
509+
void resumeSessionHandlerInvoked() throws Exception {
510+
AtomicReference<String> resumedSessionId = new AtomicReference<>();
511+
512+
@AcpAgent
513+
class ResumeSessionAgent {
514+
515+
@Initialize
516+
InitializeResponse init() {
517+
return InitializeResponse.ok();
518+
}
519+
520+
@ResumeSession
521+
ResumeSessionResponse resumeSession(ResumeSessionRequest req) {
522+
resumedSessionId.set(req.sessionId());
523+
return new ResumeSessionResponse(null, null);
524+
}
525+
526+
}
527+
528+
agentSupport = AcpAgentSupport.create(new ResumeSessionAgent())
529+
.transport(transportPair.agentTransport())
530+
.requestTimeout(TIMEOUT)
531+
.build();
532+
533+
agentSupport.start();
534+
Thread.sleep(100);
535+
536+
client = AcpClient.async(transportPair.clientTransport())
537+
.requestTimeout(TIMEOUT)
538+
.build();
539+
540+
client.initialize(new InitializeRequest(1, null)).block(TIMEOUT);
541+
ResumeSessionResponse resp = client
542+
.resumeSession(new ResumeSessionRequest("existing-session", "/workspace", List.of()))
543+
.block(TIMEOUT);
544+
545+
assertThat(resumedSessionId.get()).isEqualTo("existing-session");
546+
assertThat(resp).isNotNull();
547+
}
548+
415549
// Simple test agent
416550
@AcpAgent(name = "simple-agent", version = "1.0")
417551
static class SimpleAgent {

0 commit comments

Comments
 (0)