Skip to content

Commit 816c2af

Browse files
author
Mark Pollack
committed
Add session/fork and session/set_config_option (unstable)
Implement two unstable ACP protocol methods: session/fork: creates a new session branched from an existing one. ForkSessionRequest/Response with sessionId, cwd, modes, models, configOptions. Capability via sessionCapabilities.fork. session/set_config_option: changes a session configuration value. SetSessionConfigOptionRequest/Response with polymorphic value (select string or boolean). Returns full config state. Supporting types (all @UnstableAcpApi): SessionConfigOption (polymorphic: SessionConfigSelect, SessionConfigBoolean), SessionConfigSelectOption, ConfigOptionUpdate (new SessionUpdate variant for agent-pushed config changes). Full stack: schema types, handler interfaces (sync + async), builder methods, DefaultAcpAsyncAgent registration, client methods, @ForkSession and @SetSessionConfigOption annotations, argument resolvers, DirectResponseHandler, NegotiatedCapabilities. Tests: 5 serialization tests (fork request, config polymorphism, config boolean, select request, config update as session update) and 1 end-to-end integration test (fork session flow).
1 parent 0718837 commit 816c2af

14 files changed

Lines changed: 638 additions & 6 deletions

File tree

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.agentclientprotocol.sdk.agent.support.resolver.CancelNotificationResolver;
2929
import com.agentclientprotocol.sdk.agent.support.resolver.CapabilitiesResolver;
3030
import com.agentclientprotocol.sdk.agent.support.resolver.CloseSessionRequestResolver;
31+
import com.agentclientprotocol.sdk.agent.support.resolver.ForkSessionRequestResolver;
3132
import com.agentclientprotocol.sdk.agent.support.resolver.InitializeRequestResolver;
3233
import com.agentclientprotocol.sdk.agent.support.resolver.ListSessionsRequestResolver;
3334
import com.agentclientprotocol.sdk.agent.support.resolver.LoadSessionRequestResolver;
@@ -36,16 +37,19 @@
3637
import com.agentclientprotocol.sdk.agent.support.resolver.PromptRequestResolver;
3738
import com.agentclientprotocol.sdk.agent.support.resolver.ResumeSessionRequestResolver;
3839
import com.agentclientprotocol.sdk.agent.support.resolver.SessionIdResolver;
40+
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionConfigOptionRequestResolver;
3941
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionModeRequestResolver;
4042
import com.agentclientprotocol.sdk.agent.support.resolver.SetSessionModelRequestResolver;
4143
import com.agentclientprotocol.sdk.annotation.Cancel;
4244
import com.agentclientprotocol.sdk.annotation.CloseSession;
45+
import com.agentclientprotocol.sdk.annotation.ForkSession;
4346
import com.agentclientprotocol.sdk.annotation.Initialize;
4447
import com.agentclientprotocol.sdk.annotation.ListSessions;
4548
import com.agentclientprotocol.sdk.annotation.LoadSession;
4649
import com.agentclientprotocol.sdk.annotation.NewSession;
4750
import com.agentclientprotocol.sdk.annotation.Prompt;
4851
import com.agentclientprotocol.sdk.annotation.ResumeSession;
52+
import com.agentclientprotocol.sdk.annotation.SetSessionConfigOption;
4953
import com.agentclientprotocol.sdk.annotation.SetSessionMode;
5054
import com.agentclientprotocol.sdk.annotation.SetSessionModel;
5155
import com.agentclientprotocol.sdk.capabilities.NegotiatedCapabilities;
@@ -244,6 +248,20 @@ private void wireHandlers(AcpAgent.SyncAgentBuilder agentBuilder) {
244248
req -> invokeHandler(resumeSessionHandler, req, req.sessionId(), null, null));
245249
}
246250

251+
// ForkSession handler
252+
AcpHandlerMethod forkSessionHandler = handlers.get("session/fork");
253+
if (forkSessionHandler != null) {
254+
agentBuilder.forkSessionHandler(
255+
req -> invokeHandler(forkSessionHandler, req, req.sessionId(), null, null));
256+
}
257+
258+
// SetSessionConfigOption handler
259+
AcpHandlerMethod setConfigOptionHandler = handlers.get("session/set_config_option");
260+
if (setConfigOptionHandler != null) {
261+
agentBuilder.setSessionConfigOptionHandler(
262+
req -> invokeHandler(setConfigOptionHandler, req, req.sessionId(), null, null));
263+
}
264+
247265
// Cancel handler
248266
AcpHandlerMethod cancelHandler = handlers.get("session/cancel");
249267
if (cancelHandler != null) {
@@ -479,6 +497,15 @@ private void discoverHandlers(Class<?> agentClass, Supplier<Object> beanSupplier
479497
handlers.put("session/resume", new AcpHandlerMethod(beanSupplier, method, "session/resume"));
480498
log.debug("Discovered @ResumeSession handler: {}", method.getName());
481499
}
500+
if (method.isAnnotationPresent(ForkSession.class)) {
501+
handlers.put("session/fork", new AcpHandlerMethod(beanSupplier, method, "session/fork"));
502+
log.debug("Discovered @ForkSession handler: {}", method.getName());
503+
}
504+
if (method.isAnnotationPresent(SetSessionConfigOption.class)) {
505+
handlers.put("session/set_config_option",
506+
new AcpHandlerMethod(beanSupplier, method, "session/set_config_option"));
507+
log.debug("Discovered @SetSessionConfigOption handler: {}", method.getName());
508+
}
482509
if (method.isAnnotationPresent(Cancel.class)) {
483510
handlers.put("session/cancel", new AcpHandlerMethod(beanSupplier, method, "session/cancel"));
484511
log.debug("Discovered @Cancel handler: {}", method.getName());
@@ -498,6 +525,8 @@ private void addDefaultResolvers() {
498525
argumentResolvers.addResolver(new ListSessionsRequestResolver());
499526
argumentResolvers.addResolver(new CloseSessionRequestResolver());
500527
argumentResolvers.addResolver(new ResumeSessionRequestResolver());
528+
argumentResolvers.addResolver(new ForkSessionRequestResolver());
529+
argumentResolvers.addResolver(new SetSessionConfigOptionRequestResolver());
501530
argumentResolvers.addResolver(new CancelNotificationResolver());
502531
argumentResolvers.addResolver(new PromptContextResolver());
503532
argumentResolvers.addResolver(new SessionIdResolver());

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
import com.agentclientprotocol.sdk.agent.support.AcpInvocationContext;
88
import com.agentclientprotocol.sdk.agent.support.AcpMethodParameter;
99
import com.agentclientprotocol.sdk.spec.AcpSchema.CloseSessionResponse;
10+
import com.agentclientprotocol.sdk.spec.AcpSchema.ForkSessionResponse;
1011
import com.agentclientprotocol.sdk.spec.AcpSchema.InitializeResponse;
1112
import com.agentclientprotocol.sdk.spec.AcpSchema.ListSessionsResponse;
1213
import com.agentclientprotocol.sdk.spec.AcpSchema.LoadSessionResponse;
1314
import com.agentclientprotocol.sdk.spec.AcpSchema.NewSessionResponse;
1415
import com.agentclientprotocol.sdk.spec.AcpSchema.PromptResponse;
1516
import com.agentclientprotocol.sdk.spec.AcpSchema.ResumeSessionResponse;
17+
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionConfigOptionResponse;
1618
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModeResponse;
1719
import com.agentclientprotocol.sdk.spec.AcpSchema.SetSessionModelResponse;
1820

@@ -39,7 +41,9 @@ public boolean supportsReturnType(AcpMethodParameter returnType) {
3941
|| SetSessionModelResponse.class.isAssignableFrom(type)
4042
|| ListSessionsResponse.class.isAssignableFrom(type)
4143
|| CloseSessionResponse.class.isAssignableFrom(type)
42-
|| ResumeSessionResponse.class.isAssignableFrom(type);
44+
|| ResumeSessionResponse.class.isAssignableFrom(type)
45+
|| ForkSessionResponse.class.isAssignableFrom(type)
46+
|| SetSessionConfigOptionResponse.class.isAssignableFrom(type);
4347
}
4448

4549
@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.ForkSessionRequest;
10+
11+
/**
12+
* Resolves {@link ForkSessionRequest} parameters in fork session handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 0.12.0
16+
*/
17+
public class ForkSessionRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return ForkSessionRequest.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 ForkSessionRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException(
31+
"Expected ForkSessionRequest but got: " + (request != null ? request.getClass().getName() : "null"));
32+
}
33+
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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.SetSessionConfigOptionRequest;
10+
11+
/**
12+
* Resolves {@link SetSessionConfigOptionRequest} parameters in config option handlers.
13+
*
14+
* @author Mark Pollack
15+
* @since 0.12.0
16+
*/
17+
public class SetSessionConfigOptionRequestResolver implements ArgumentResolver {
18+
19+
@Override
20+
public boolean supportsParameter(AcpMethodParameter parameter) {
21+
return SetSessionConfigOptionRequest.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 SetSessionConfigOptionRequest) {
28+
return request;
29+
}
30+
throw new ArgumentResolutionException(
31+
"Expected SetSessionConfigOptionRequest but got: "
32+
+ (request != null ? request.getClass().getName() : "null"));
33+
}
34+
35+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 forking ACP sessions.
15+
*
16+
* <p>The annotated method handles the {@code session/fork} JSON-RPC method,
17+
* which creates a new session branched from an existing one.
18+
*
19+
* @author Mark Pollack
20+
* @since 0.12.0
21+
* @see AcpAgent
22+
*/
23+
@UnstableAcpApi
24+
@Target(ElementType.METHOD)
25+
@Retention(RetentionPolicy.RUNTIME)
26+
@Documented
27+
public @interface ForkSession {
28+
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 setting session configuration options.
15+
*
16+
* <p>The annotated method handles the {@code session/set_config_option}
17+
* JSON-RPC method, which changes a configuration value for the session.
18+
*
19+
* @author Mark Pollack
20+
* @since 0.12.0
21+
* @see AcpAgent
22+
*/
23+
@UnstableAcpApi
24+
@Target(ElementType.METHOD)
25+
@Retention(RetentionPolicy.RUNTIME)
26+
@Documented
27+
public @interface SetSessionConfigOption {
28+
29+
}

acp-core/src/main/java/com/agentclientprotocol/sdk/agent/AcpAgent.java

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,26 @@ interface ResumeSessionHandler {
248248

249249
}
250250

251+
/**
252+
* Functional interface for handling fork session requests.
253+
*/
254+
@FunctionalInterface
255+
interface ForkSessionHandler {
256+
257+
Mono<AcpSchema.ForkSessionResponse> handle(AcpSchema.ForkSessionRequest request);
258+
259+
}
260+
261+
/**
262+
* Functional interface for handling set config option requests.
263+
*/
264+
@FunctionalInterface
265+
interface SetSessionConfigOptionHandler {
266+
267+
Mono<AcpSchema.SetSessionConfigOptionResponse> handle(AcpSchema.SetSessionConfigOptionRequest request);
268+
269+
}
270+
251271
/**
252272
* Functional interface for handling cancel notifications.
253273
*/
@@ -397,6 +417,26 @@ interface SyncResumeSessionHandler {
397417

398418
}
399419

420+
/**
421+
* Synchronous functional interface for handling fork session requests.
422+
*/
423+
@FunctionalInterface
424+
interface SyncForkSessionHandler {
425+
426+
AcpSchema.ForkSessionResponse handle(AcpSchema.ForkSessionRequest request);
427+
428+
}
429+
430+
/**
431+
* Synchronous functional interface for handling set config option requests.
432+
*/
433+
@FunctionalInterface
434+
interface SyncSetSessionConfigOptionHandler {
435+
436+
AcpSchema.SetSessionConfigOptionResponse handle(AcpSchema.SetSessionConfigOptionRequest request);
437+
438+
}
439+
400440
/**
401441
* Synchronous functional interface for handling cancel notifications.
402442
* Returns void instead of Mono for use with sync agents.
@@ -437,6 +477,10 @@ class AsyncAgentBuilder {
437477

438478
private ResumeSessionHandler resumeSessionHandler;
439479

480+
private ForkSessionHandler forkSessionHandler;
481+
482+
private SetSessionConfigOptionHandler setSessionConfigOptionHandler;
483+
440484
private CancelHandler cancelHandler;
441485

442486
AsyncAgentBuilder(AcpAgentTransport transport) {
@@ -555,6 +599,26 @@ public AsyncAgentBuilder resumeSessionHandler(ResumeSessionHandler handler) {
555599
return this;
556600
}
557601

602+
/**
603+
* Sets the handler for fork session requests.
604+
* @param handler The fork session handler
605+
* @return This builder for chaining
606+
*/
607+
public AsyncAgentBuilder forkSessionHandler(ForkSessionHandler handler) {
608+
this.forkSessionHandler = handler;
609+
return this;
610+
}
611+
612+
/**
613+
* Sets the handler for set config option requests.
614+
* @param handler The set config option handler
615+
* @return This builder for chaining
616+
*/
617+
public AsyncAgentBuilder setSessionConfigOptionHandler(SetSessionConfigOptionHandler handler) {
618+
this.setSessionConfigOptionHandler = handler;
619+
return this;
620+
}
621+
558622
/**
559623
* Sets the handler for cancel notifications.
560624
* @param handler The cancel handler
@@ -572,7 +636,8 @@ public AsyncAgentBuilder cancelHandler(CancelHandler handler) {
572636
public AcpAsyncAgent build() {
573637
return new DefaultAcpAsyncAgent(transport, requestTimeout, initializeHandler, authenticateHandler,
574638
newSessionHandler, loadSessionHandler, promptHandler, setSessionModeHandler, setSessionModelHandler,
575-
listSessionsHandler, closeSessionHandler, resumeSessionHandler, cancelHandler);
639+
listSessionsHandler, closeSessionHandler, resumeSessionHandler, forkSessionHandler,
640+
setSessionConfigOptionHandler, cancelHandler);
576641
}
577642

578643
}
@@ -716,6 +781,16 @@ public SyncAgentBuilder resumeSessionHandler(SyncResumeSessionHandler handler) {
716781
return this;
717782
}
718783

784+
public SyncAgentBuilder forkSessionHandler(SyncForkSessionHandler handler) {
785+
asyncBuilder.forkSessionHandler(fromSync(handler));
786+
return this;
787+
}
788+
789+
public SyncAgentBuilder setSessionConfigOptionHandler(SyncSetSessionConfigOptionHandler handler) {
790+
asyncBuilder.setSessionConfigOptionHandler(fromSync(handler));
791+
return this;
792+
}
793+
719794
/**
720795
* Sets the synchronous handler for cancel notifications.
721796
* @param handler The sync cancel handler (returns void)
@@ -822,6 +897,22 @@ private static ResumeSessionHandler fromSync(SyncResumeSessionHandler syncHandle
822897
.subscribeOn(SYNC_HANDLER_SCHEDULER);
823898
}
824899

900+
private static ForkSessionHandler fromSync(SyncForkSessionHandler syncHandler) {
901+
if (syncHandler == null) {
902+
return null;
903+
}
904+
return request -> Mono.fromCallable(() -> syncHandler.handle(request))
905+
.subscribeOn(SYNC_HANDLER_SCHEDULER);
906+
}
907+
908+
private static SetSessionConfigOptionHandler fromSync(SyncSetSessionConfigOptionHandler syncHandler) {
909+
if (syncHandler == null) {
910+
return null;
911+
}
912+
return request -> Mono.fromCallable(() -> syncHandler.handle(request))
913+
.subscribeOn(SYNC_HANDLER_SCHEDULER);
914+
}
915+
825916
private static CancelHandler fromSync(SyncCancelHandler syncHandler) {
826917
if (syncHandler == null) {
827918
return null;

0 commit comments

Comments
 (0)