Skip to content

Commit d7009e5

Browse files
author
Mark Pollack
committed
Add typed terminal handlers to client builders
- Add createTerminalHandler() to AsyncSpec and SyncSpec - Add terminalOutputHandler() to AsyncSpec and SyncSpec - Add releaseTerminalHandler() to AsyncSpec and SyncSpec - Add waitForTerminalExitHandler() to AsyncSpec and SyncSpec - Add killTerminalHandler() to AsyncSpec and SyncSpec - Add 5 unit tests for terminal handler unmarshalling All terminal handlers now match the ergonomic pattern used for file handlers, accepting typed request/response functions with automatic JSON unmarshalling.
1 parent 7c1549f commit d7009e5

File tree

2 files changed

+483
-0
lines changed

2 files changed

+483
-0
lines changed

acp-core/src/main/java/com/agentclientprotocol/sdk/client/AcpClient.java

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,142 @@ public AsyncSpec requestPermissionHandler(
316316
return this;
317317
}
318318

319+
/**
320+
* Adds a typed handler for terminal creation requests from the agent.
321+
*
322+
* <p>Example usage:
323+
* <pre>{@code
324+
* .createTerminalHandler(req -> {
325+
* String terminalId = UUID.randomUUID().toString();
326+
* // Start process with req.command(), req.args(), req.cwd()
327+
* return Mono.just(new CreateTerminalResponse(terminalId));
328+
* })
329+
* }</pre>
330+
*
331+
* @param handler The typed handler function that processes terminal creation requests
332+
* @return This builder instance for method chaining
333+
* @throws IllegalArgumentException if handler is null
334+
*/
335+
public AsyncSpec createTerminalHandler(
336+
Function<AcpSchema.CreateTerminalRequest, Mono<AcpSchema.CreateTerminalResponse>> handler) {
337+
Assert.notNull(handler, "Create terminal handler must not be null");
338+
AcpClientSession.RequestHandler<AcpSchema.CreateTerminalResponse> rawHandler = params -> {
339+
AcpSchema.CreateTerminalRequest request = transport.unmarshalFrom(params,
340+
new TypeRef<AcpSchema.CreateTerminalRequest>() {});
341+
return handler.apply(request);
342+
};
343+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_CREATE, rawHandler);
344+
return this;
345+
}
346+
347+
/**
348+
* Adds a typed handler for terminal output requests from the agent.
349+
*
350+
* <p>Example usage:
351+
* <pre>{@code
352+
* .terminalOutputHandler(req -> {
353+
* String output = getTerminalOutput(req.terminalId());
354+
* return Mono.just(new TerminalOutputResponse(output, false, null));
355+
* })
356+
* }</pre>
357+
*
358+
* @param handler The typed handler function that processes terminal output requests
359+
* @return This builder instance for method chaining
360+
* @throws IllegalArgumentException if handler is null
361+
*/
362+
public AsyncSpec terminalOutputHandler(
363+
Function<AcpSchema.TerminalOutputRequest, Mono<AcpSchema.TerminalOutputResponse>> handler) {
364+
Assert.notNull(handler, "Terminal output handler must not be null");
365+
AcpClientSession.RequestHandler<AcpSchema.TerminalOutputResponse> rawHandler = params -> {
366+
AcpSchema.TerminalOutputRequest request = transport.unmarshalFrom(params,
367+
new TypeRef<AcpSchema.TerminalOutputRequest>() {});
368+
return handler.apply(request);
369+
};
370+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_OUTPUT, rawHandler);
371+
return this;
372+
}
373+
374+
/**
375+
* Adds a typed handler for terminal release requests from the agent.
376+
*
377+
* <p>Example usage:
378+
* <pre>{@code
379+
* .releaseTerminalHandler(req -> {
380+
* releaseTerminal(req.terminalId());
381+
* return Mono.just(new ReleaseTerminalResponse());
382+
* })
383+
* }</pre>
384+
*
385+
* @param handler The typed handler function that processes terminal release requests
386+
* @return This builder instance for method chaining
387+
* @throws IllegalArgumentException if handler is null
388+
*/
389+
public AsyncSpec releaseTerminalHandler(
390+
Function<AcpSchema.ReleaseTerminalRequest, Mono<AcpSchema.ReleaseTerminalResponse>> handler) {
391+
Assert.notNull(handler, "Release terminal handler must not be null");
392+
AcpClientSession.RequestHandler<AcpSchema.ReleaseTerminalResponse> rawHandler = params -> {
393+
AcpSchema.ReleaseTerminalRequest request = transport.unmarshalFrom(params,
394+
new TypeRef<AcpSchema.ReleaseTerminalRequest>() {});
395+
return handler.apply(request);
396+
};
397+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_RELEASE, rawHandler);
398+
return this;
399+
}
400+
401+
/**
402+
* Adds a typed handler for wait-for-terminal-exit requests from the agent.
403+
*
404+
* <p>Example usage:
405+
* <pre>{@code
406+
* .waitForTerminalExitHandler(req -> {
407+
* int exitCode = waitForExit(req.terminalId());
408+
* return Mono.just(new WaitForTerminalExitResponse(exitCode, null));
409+
* })
410+
* }</pre>
411+
*
412+
* @param handler The typed handler function that processes wait-for-exit requests
413+
* @return This builder instance for method chaining
414+
* @throws IllegalArgumentException if handler is null
415+
*/
416+
public AsyncSpec waitForTerminalExitHandler(
417+
Function<AcpSchema.WaitForTerminalExitRequest, Mono<AcpSchema.WaitForTerminalExitResponse>> handler) {
418+
Assert.notNull(handler, "Wait for terminal exit handler must not be null");
419+
AcpClientSession.RequestHandler<AcpSchema.WaitForTerminalExitResponse> rawHandler = params -> {
420+
AcpSchema.WaitForTerminalExitRequest request = transport.unmarshalFrom(params,
421+
new TypeRef<AcpSchema.WaitForTerminalExitRequest>() {});
422+
return handler.apply(request);
423+
};
424+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_WAIT_FOR_EXIT, rawHandler);
425+
return this;
426+
}
427+
428+
/**
429+
* Adds a typed handler for terminal kill requests from the agent.
430+
*
431+
* <p>Example usage:
432+
* <pre>{@code
433+
* .killTerminalHandler(req -> {
434+
* killProcess(req.terminalId());
435+
* return Mono.just(new KillTerminalCommandResponse());
436+
* })
437+
* }</pre>
438+
*
439+
* @param handler The typed handler function that processes terminal kill requests
440+
* @return This builder instance for method chaining
441+
* @throws IllegalArgumentException if handler is null
442+
*/
443+
public AsyncSpec killTerminalHandler(
444+
Function<AcpSchema.KillTerminalCommandRequest, Mono<AcpSchema.KillTerminalCommandResponse>> handler) {
445+
Assert.notNull(handler, "Kill terminal handler must not be null");
446+
AcpClientSession.RequestHandler<AcpSchema.KillTerminalCommandResponse> rawHandler = params -> {
447+
AcpSchema.KillTerminalCommandRequest request = transport.unmarshalFrom(params,
448+
new TypeRef<AcpSchema.KillTerminalCommandRequest>() {});
449+
return handler.apply(request);
450+
};
451+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_KILL, rawHandler);
452+
return this;
453+
}
454+
319455
/**
320456
* Adds a consumer to be notified when session update notifications are received
321457
* from the agent. Session updates include agent thoughts, message chunks, and
@@ -556,6 +692,147 @@ public SyncSpec requestPermissionHandler(
556692
return this;
557693
}
558694

695+
/**
696+
* Adds a typed handler for terminal creation requests from the agent.
697+
*
698+
* <p>Example usage:
699+
* <pre>{@code
700+
* .createTerminalHandler(req -> {
701+
* String terminalId = UUID.randomUUID().toString();
702+
* // Start process with req.command(), req.args(), req.cwd()
703+
* return new CreateTerminalResponse(terminalId);
704+
* })
705+
* }</pre>
706+
*
707+
* @param handler The typed handler function that processes terminal creation requests
708+
* @return This builder instance for method chaining
709+
* @throws IllegalArgumentException if handler is null
710+
*/
711+
public SyncSpec createTerminalHandler(
712+
Function<AcpSchema.CreateTerminalRequest, AcpSchema.CreateTerminalResponse> handler) {
713+
Assert.notNull(handler, "Create terminal handler must not be null");
714+
SyncRequestHandler<AcpSchema.CreateTerminalResponse> rawHandler = params -> {
715+
logger.debug("createTerminal request params: {}", params);
716+
AcpSchema.CreateTerminalRequest request = transport.unmarshalFrom(params,
717+
new TypeRef<AcpSchema.CreateTerminalRequest>() {});
718+
return handler.apply(request);
719+
};
720+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_CREATE, fromSync(rawHandler));
721+
return this;
722+
}
723+
724+
/**
725+
* Adds a typed handler for terminal output requests from the agent.
726+
*
727+
* <p>Example usage:
728+
* <pre>{@code
729+
* .terminalOutputHandler(req -> {
730+
* String output = getTerminalOutput(req.terminalId());
731+
* return new TerminalOutputResponse(output, false, null);
732+
* })
733+
* }</pre>
734+
*
735+
* @param handler The typed handler function that processes terminal output requests
736+
* @return This builder instance for method chaining
737+
* @throws IllegalArgumentException if handler is null
738+
*/
739+
public SyncSpec terminalOutputHandler(
740+
Function<AcpSchema.TerminalOutputRequest, AcpSchema.TerminalOutputResponse> handler) {
741+
Assert.notNull(handler, "Terminal output handler must not be null");
742+
SyncRequestHandler<AcpSchema.TerminalOutputResponse> rawHandler = params -> {
743+
logger.debug("terminalOutput request params: {}", params);
744+
AcpSchema.TerminalOutputRequest request = transport.unmarshalFrom(params,
745+
new TypeRef<AcpSchema.TerminalOutputRequest>() {});
746+
return handler.apply(request);
747+
};
748+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_OUTPUT, fromSync(rawHandler));
749+
return this;
750+
}
751+
752+
/**
753+
* Adds a typed handler for terminal release requests from the agent.
754+
*
755+
* <p>Example usage:
756+
* <pre>{@code
757+
* .releaseTerminalHandler(req -> {
758+
* releaseTerminal(req.terminalId());
759+
* return new ReleaseTerminalResponse();
760+
* })
761+
* }</pre>
762+
*
763+
* @param handler The typed handler function that processes terminal release requests
764+
* @return This builder instance for method chaining
765+
* @throws IllegalArgumentException if handler is null
766+
*/
767+
public SyncSpec releaseTerminalHandler(
768+
Function<AcpSchema.ReleaseTerminalRequest, AcpSchema.ReleaseTerminalResponse> handler) {
769+
Assert.notNull(handler, "Release terminal handler must not be null");
770+
SyncRequestHandler<AcpSchema.ReleaseTerminalResponse> rawHandler = params -> {
771+
logger.debug("releaseTerminal request params: {}", params);
772+
AcpSchema.ReleaseTerminalRequest request = transport.unmarshalFrom(params,
773+
new TypeRef<AcpSchema.ReleaseTerminalRequest>() {});
774+
return handler.apply(request);
775+
};
776+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_RELEASE, fromSync(rawHandler));
777+
return this;
778+
}
779+
780+
/**
781+
* Adds a typed handler for wait-for-terminal-exit requests from the agent.
782+
*
783+
* <p>Example usage:
784+
* <pre>{@code
785+
* .waitForTerminalExitHandler(req -> {
786+
* int exitCode = waitForExit(req.terminalId());
787+
* return new WaitForTerminalExitResponse(exitCode, null);
788+
* })
789+
* }</pre>
790+
*
791+
* @param handler The typed handler function that processes wait-for-exit requests
792+
* @return This builder instance for method chaining
793+
* @throws IllegalArgumentException if handler is null
794+
*/
795+
public SyncSpec waitForTerminalExitHandler(
796+
Function<AcpSchema.WaitForTerminalExitRequest, AcpSchema.WaitForTerminalExitResponse> handler) {
797+
Assert.notNull(handler, "Wait for terminal exit handler must not be null");
798+
SyncRequestHandler<AcpSchema.WaitForTerminalExitResponse> rawHandler = params -> {
799+
logger.debug("waitForTerminalExit request params: {}", params);
800+
AcpSchema.WaitForTerminalExitRequest request = transport.unmarshalFrom(params,
801+
new TypeRef<AcpSchema.WaitForTerminalExitRequest>() {});
802+
return handler.apply(request);
803+
};
804+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_WAIT_FOR_EXIT, fromSync(rawHandler));
805+
return this;
806+
}
807+
808+
/**
809+
* Adds a typed handler for terminal kill requests from the agent.
810+
*
811+
* <p>Example usage:
812+
* <pre>{@code
813+
* .killTerminalHandler(req -> {
814+
* killProcess(req.terminalId());
815+
* return new KillTerminalCommandResponse();
816+
* })
817+
* }</pre>
818+
*
819+
* @param handler The typed handler function that processes terminal kill requests
820+
* @return This builder instance for method chaining
821+
* @throws IllegalArgumentException if handler is null
822+
*/
823+
public SyncSpec killTerminalHandler(
824+
Function<AcpSchema.KillTerminalCommandRequest, AcpSchema.KillTerminalCommandResponse> handler) {
825+
Assert.notNull(handler, "Kill terminal handler must not be null");
826+
SyncRequestHandler<AcpSchema.KillTerminalCommandResponse> rawHandler = params -> {
827+
logger.debug("killTerminal request params: {}", params);
828+
AcpSchema.KillTerminalCommandRequest request = transport.unmarshalFrom(params,
829+
new TypeRef<AcpSchema.KillTerminalCommandRequest>() {});
830+
return handler.apply(request);
831+
};
832+
this.requestHandlers.put(AcpSchema.METHOD_TERMINAL_KILL, fromSync(rawHandler));
833+
return this;
834+
}
835+
559836
/**
560837
* Adds a synchronous consumer to be notified when session update notifications
561838
* are received from the agent. This is the preferred method for sync clients.

0 commit comments

Comments
 (0)