Skip to content

Commit b9ee390

Browse files
author
Mark Pollack
committed
Javadoc: acp-integration (pass 1, batch I01)
1 parent 696b578 commit b9ee390

16 files changed

Lines changed: 588 additions & 270 deletions

‎acp-integration/src/main/java/com/agentclientprotocol/sdk/integration/AcpAgentDiscovery.java‎

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,44 @@
1313
import org.jspecify.annotations.Nullable;
1414

1515
/**
16-
* The rule every framework applies to the application's {@code @AcpAgent} classes: none means a
17-
* client-only application, one is served, more than one is an error naming them. The container
18-
* finds the candidates its own way (bean metadata, a build-time index); this class decides.
16+
* Decides which {@code @AcpAgent} an application serves, by the rule every framework applies:
17+
* none means a client-only application (serve no agent), one is served, and more than one is an
18+
* error that names them all. The framework finds the candidates its own way, from bean metadata
19+
* at run time ({@link AgentCandidate}) or from a build-time index by class name, and passes them
20+
* to {@code requireSingle}; this class only decides. The chosen candidate goes to
21+
* {@link AcpAgents#builder}.
22+
*
23+
* <p>Pass the framework's property that turns the agent off, such as
24+
* {@code spring.acp.agent.enabled}, so the error tells the user how to serve none. A framework
25+
* that requires an agent (because it found one by annotation already) turns the empty result
26+
* into its own error.
1927
*/
2028
public final class AcpAgentDiscovery {
2129

2230
private AcpAgentDiscovery() {
2331
}
2432

2533
/**
26-
* An {@code @AcpAgent} bean the container found.
27-
* @param name the container's name for it, used in messages
28-
* @param userClass the class the application wrote, from the container's metadata: never a
29-
* proxy or generated subclass (CGLIB, ArC, Micronaut AOP), whose methods carry no handler
34+
* One {@code @AcpAgent} bean the container found: its name, the class the application wrote,
35+
* and a way to get the bean. The framework builds one per bean from its own metadata and
36+
* passes them to {@link AcpAgentDiscovery#requireSingle(Collection, String)}.
37+
*
38+
* <p>The user class must be the class the application declared, taken from the container's
39+
* metadata (Spring's {@code ClassUtils.getUserClass}, Micronaut's bean definition type), never
40+
* the class of a proxy or generated subclass (CGLIB, ArC, Micronaut AOP): the handler
41+
* annotations are on the user class, and a proxy's overriding methods carry none. The instance
42+
* may be such a proxy, which keeps its advice (transactions, security) on every call.
43+
* {@link AcpAgents#builder} calls the supplier once, while it assembles the agent.
44+
* @param name the container's name for the bean, used in messages
45+
* @param userClass the class the application wrote, whose methods carry the handler
3046
* annotations
31-
* @param instance supplies the bean, possibly a proxy, that handles every request
47+
* @param instance supplies the bean that handles every request, possibly a proxy
3248
* @param <T> the agent type
3349
*/
3450
public record AgentCandidate<T>(String name, Class<T> userClass, Supplier<? extends T> instance) {
3551

3652
/**
37-
* A candidate, each part checked.
53+
* Creates a candidate, checking each part.
3854
* @throws NullPointerException if a part is null
3955
*/
4056
public AgentCandidate {
@@ -46,22 +62,26 @@ public record AgentCandidate<T>(String name, Class<T> userClass, Supplier<? exte
4662
}
4763

4864
/**
49-
* The one candidate.
65+
* Returns the one candidate, as {@link #requireSingle(Collection, String)} does, with an
66+
* error that names no property.
5067
* @param candidates the {@code @AcpAgent} beans the container found
5168
* @return the candidate, or empty when there is none
52-
* @throws IllegalStateException listing them when there is more than one
69+
* @throws IllegalStateException if there is more than one; the message lists their user
70+
* classes
5371
*/
5472
public static Optional<AgentCandidate<?>> requireSingle(Collection<? extends AgentCandidate<?>> candidates) {
5573
return requireSingle(candidates, null);
5674
}
5775

5876
/**
59-
* The one candidate.
77+
* Returns the one candidate the application serves, or empty for a client-only application.
6078
* @param candidates the {@code @AcpAgent} beans the container found
61-
* @param enabledProperty the framework's property that turns the agent off, named in the error
62-
* as the way to serve none; null to name none
79+
* @param enabledProperty the framework's property that turns the agent off, such as
80+
* {@code spring.acp.agent.enabled}, which the error names as the way to serve none; null to
81+
* name none
6382
* @return the candidate, or empty when there is none
64-
* @throws IllegalStateException listing them when there is more than one
83+
* @throws IllegalStateException if there is more than one; the message lists their user
84+
* classes, sorted, and says to remove {@code @AcpAgent} from all but one
6585
*/
6686
public static Optional<AgentCandidate<?>> requireSingle(Collection<? extends AgentCandidate<?>> candidates,
6787
@Nullable String enabledProperty) {
@@ -73,13 +93,13 @@ public static Optional<AgentCandidate<?>> requireSingle(Collection<? extends Age
7393
}
7494

7595
/**
76-
* The same rule over class names, for build-time discovery (a Jandex index), with no class
77-
* loaded.
96+
* Applies the same rule to class names, for discovery at build time (Quarkus reads a Jandex
97+
* index), so no class is loaded.
7898
* @param classNames the names of the {@code @AcpAgent} classes found
79-
* @param enabledProperty the framework's property that turns the agent off, named in the error;
80-
* null to name none
99+
* @param enabledProperty the framework's property that turns the agent off, which the error
100+
* names; null to name none
81101
* @return the class name, or empty when there is none
82-
* @throws IllegalStateException listing them when there is more than one
102+
* @throws IllegalStateException if there is more than one; the message lists them, sorted
83103
*/
84104
public static Optional<String> requireSingle(List<String> classNames, @Nullable String enabledProperty) {
85105
if (classNames.size() > 1) {

‎acp-integration/src/main/java/com/agentclientprotocol/sdk/integration/AcpAgentHost.java‎

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,31 @@
1717
import reactor.core.publisher.Mono;
1818

1919
/**
20-
* One agent on one transport: stdio, or an application's own transport. Starting, stopping and
21-
* the end of the transport are the SDK's own lifecycle: {@link AcpAgentSupport#start()}, its
22-
* {@link AcpAgentSupport#close() AutoCloseable close} (graceful, bounded, then at once) and its
23-
* agent's {@code awaitTermination()}.
20+
* The {@link AcpHost} for one agent on one transport: stdio, or a transport bean the application
21+
* provides (an in-memory transport in tests, for one). Build the agent with
22+
* {@link AcpAgents#builder}, set its transport ({@link AcpAgentTransports#stdio()}), build it,
23+
* and give it to this host with the action to run when the client ends the transport. For the
24+
* SDK's HTTP and WebSocket listener, which serves many clients, use {@link AcpListenerHost}.
2425
*
25-
* <p>
26-
* When the transport ends by itself (for stdio: the client closed the agent's input and every
27-
* answer has been written), the agent has no one left to serve, and the host runs the
28-
* {@code onTransportEnd} action the framework gave it, typically "close my container". That
29-
* action is latched: it runs at most once, on a thread of the host's own (never the transport's,
30-
* since closing the container stops this host, which closes the transport), also when the
31-
* transport ended before anyone looked, and never when the host itself stopped the agent.
32-
* </p>
26+
* <p>The host drives the SDK's own lifecycle: {@link #start()} calls
27+
* {@link AcpAgentSupport#start()}; a stop runs {@link AcpAgentSupport#close()} (graceful, at most
28+
* about 10 seconds, then at once) on a daemon thread named {@code acp-agent-stop}, and
29+
* {@link #stop(Duration)} closes the agent at once when that does not finish within its timeout.
30+
* {@link #termination()} follows the agent's {@code awaitTermination()}, and completes normally
31+
* also when the transport ended with an error. {@link #port()} is always empty.
32+
*
33+
* <p>When the transport ends by itself (for stdio: the client closed the agent's input and
34+
* every answer has been written), the agent has no one left to serve, and the host runs
35+
* {@code onTransportEnd}, typically "close my container" or "exit the application". The host
36+
* runs it at most once, on a new non-daemon thread named {@code acp-agent-transport-end}, never
37+
* on a transport thread: closing the container stops this host, which closes the transport, and
38+
* the JVM waits for the action to finish. It also runs when the transport ended before the host
39+
* started or saw it, and it does not run when the host's own stop ended the transport. The
40+
* host itself never reads {@link AcpAgentSettings#shutdownOnTransportEnd()}: when that setting
41+
* is false, the framework passes an action that does nothing.
42+
*
43+
* <p>The host is safe for use from several threads; {@link #start()} and the stop methods
44+
* share one lock.
3345
*/
3446
public final class AcpAgentHost implements AcpHost {
3547

@@ -55,10 +67,12 @@ public final class AcpAgentHost implements AcpHost {
5567
private @Nullable CompletableFuture<@Nullable Void> stopped;
5668

5769
/**
58-
* A host for an agent built on its transport.
59-
* @param agent the agent
60-
* @param onTransportEnd what to do when the transport ends by itself, such as closing the
61-
* container; fixed here, so no end is missed
70+
* Creates a host for an agent built on its transport, not yet started. The host watches the
71+
* agent's termination from here on, so the action is fixed now and no end of the transport is
72+
* missed, even one before {@link #start()}.
73+
* @param agent the agent, built with its transport and not started
74+
* @param onTransportEnd what to run once when the transport ends by itself, such as closing
75+
* the container; an action that does nothing when the application should keep running
6276
*/
6377
public AcpAgentHost(AcpAgentSupport agent, Runnable onTransportEnd) {
6478
this.agent = agent;

0 commit comments

Comments
 (0)