Skip to content

Commit 23858f1

Browse files
committed
feat: add streamable HTTP agent transport
1 parent 08ef739 commit 23858f1

28 files changed

Lines changed: 3421 additions & 1 deletion

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### Maven/Gradle Builds ###
22
target/
3+
.m2repo/
34
!.mvn/wrapper/maven-wrapper.jar
45
!**/src/main/**/target/
56
!**/src/test/**/target/
@@ -71,4 +72,5 @@ replay_pid*
7172
### Planning and Internal Documentation ###
7273
plans/*
7374
!plans/STREAMABLE-HTTP-TRANSPORT.md
75+
!plans/STREAMABLE-HTTP-AGENT-TRANSPORT.md
7476
learnings/

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ For WebSocket server support (agents accepting WebSocket connections):
5252
</dependency>
5353
```
5454

55+
For Streamable HTTP server support (agents accepting remote HTTP/SSE connections):
56+
```xml
57+
<dependency>
58+
<groupId>com.agentclientprotocol</groupId>
59+
<artifactId>acp-streamable-http-jetty</artifactId>
60+
<version>0.11.0</version>
61+
</dependency>
62+
```
63+
5564
---
5665

5766
## Getting Started
@@ -369,6 +378,7 @@ agent.start().block(); // Starts WebSocket server on port 8080
369378
| Artifact | Description |
370379
|----------|-------------|
371380
| [`acp-core`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-core) | Client and Agent SDKs, stdio, WebSocket, and Streamable HTTP client transports |
381+
| `acp-streamable-http-jetty` | Jetty-backed Streamable HTTP agent transport for listener-backed remote agents |
372382
| [`acp-annotations`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-annotations) | `@AcpAgent`, `@Prompt`, and other annotations |
373383
| [`acp-agent-support`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-agent-support) | Annotation-based agent runtime |
374384
| [`acp-test`](https://central.sonatype.com/artifact/com.agentclientprotocol/acp-test) | In-memory transport and mock utilities for testing |
@@ -380,7 +390,7 @@ agent.start().block(); // Starts WebSocket server on port 8080
380390
|-----------|--------|-------|--------|
381391
| Stdio | `StdioAcpClientTransport` | `StdioAcpAgentTransport` | acp-core |
382392
| WebSocket | `WebSocketAcpClientTransport` | `WebSocketAcpAgentTransport` | acp-core / acp-websocket-jetty |
383-
| Streamable HTTP | `StreamableHttpAcpClientTransport` | | acp-core |
393+
| Streamable HTTP | `StreamableHttpAcpClientTransport` | `StreamableHttpAcpAgentTransport` | acp-core / acp-streamable-http-jetty |
384394

385395
---
386396

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.agent;
6+
7+
import java.util.function.Function;
8+
9+
import com.agentclientprotocol.sdk.spec.AcpAgentTransport;
10+
import com.agentclientprotocol.sdk.util.Assert;
11+
12+
/**
13+
* Factory for creating one ACP agent runtime for one agent-side transport.
14+
*
15+
* <p>
16+
* Listener-backed transports such as remote HTTP transports accept multiple client
17+
* connections over their lifetime. Each accepted connection needs its own
18+
* connection-bound agent runtime while reusing the same agent definition. This factory
19+
* is the explicit public seam for that relationship.
20+
* </p>
21+
*
22+
* @author Kaiser Dandangi
23+
*/
24+
@FunctionalInterface
25+
public interface AcpAgentFactory {
26+
27+
/**
28+
* Creates a new asynchronous agent runtime for the supplied transport.
29+
* @param transport per-connection transport
30+
* @return a fresh asynchronous agent runtime
31+
*/
32+
AcpAsyncAgent create(AcpAgentTransport transport);
33+
34+
/**
35+
* Creates a factory from an asynchronous agent builder function.
36+
* @param factory function that creates a fresh asynchronous agent per transport
37+
* @return an agent factory
38+
*/
39+
static AcpAgentFactory async(Function<AcpAgentTransport, AcpAsyncAgent> factory) {
40+
Assert.notNull(factory, "The async factory can not be null");
41+
return factory::apply;
42+
}
43+
44+
/**
45+
* Creates a factory from a synchronous agent builder function.
46+
*
47+
* <p>
48+
* Synchronous agents are wrappers around asynchronous agents in this SDK, so the
49+
* transport seam remains asynchronous underneath while callers may still author
50+
* agents with the blocking API.
51+
* </p>
52+
* @param factory function that creates a fresh synchronous agent per transport
53+
* @return an agent factory
54+
*/
55+
static AcpAgentFactory sync(Function<AcpAgentTransport, AcpSyncAgent> factory) {
56+
Assert.notNull(factory, "The sync factory can not be null");
57+
return transport -> factory.apply(transport).async();
58+
}
59+
60+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2025-2026 the original author or authors.
3+
*/
4+
5+
package com.agentclientprotocol.sdk.agent;
6+
7+
import com.agentclientprotocol.sdk.spec.AcpSchema;
8+
import com.agentclientprotocol.sdk.test.InMemoryTransportPair;
9+
import org.junit.jupiter.api.Test;
10+
import reactor.core.publisher.Mono;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
class AcpAgentFactoryTest {
15+
16+
@Test
17+
void asyncFactoryReturnsFreshAgentRuntime() {
18+
AcpAgentFactory factory = AcpAgentFactory.async(transport -> AcpAgent.async(transport)
19+
.initializeHandler(request -> Mono.just(AcpSchema.InitializeResponse.ok()))
20+
.newSessionHandler(request -> Mono.just(new AcpSchema.NewSessionResponse("session", null, null)))
21+
.build());
22+
23+
AcpAsyncAgent first = factory.create(InMemoryTransportPair.create().agentTransport());
24+
AcpAsyncAgent second = factory.create(InMemoryTransportPair.create().agentTransport());
25+
26+
assertThat(first).isNotSameAs(second);
27+
}
28+
29+
@Test
30+
void syncFactoryAdaptsToAsyncRuntime() {
31+
AcpAgentFactory factory = AcpAgentFactory.sync(transport -> AcpAgent.sync(transport)
32+
.initializeHandler(request -> AcpSchema.InitializeResponse.ok())
33+
.newSessionHandler(request -> new AcpSchema.NewSessionResponse("session", null, null))
34+
.build());
35+
36+
AcpAsyncAgent agent = factory.create(InMemoryTransportPair.create().agentTransport());
37+
38+
assertThat(agent).isNotNull();
39+
}
40+
41+
}

acp-streamable-http-jetty/pom.xml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>com.agentclientprotocol</groupId>
9+
<artifactId>acp-java-sdk</artifactId>
10+
<version>0.12.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>acp-streamable-http-jetty</artifactId>
14+
<packaging>jar</packaging>
15+
16+
<name>ACP Streamable HTTP Jetty</name>
17+
<description>Streamable HTTP agent transport using Jetty for listener-backed remote agents</description>
18+
19+
<dependencies>
20+
<dependency>
21+
<groupId>com.agentclientprotocol</groupId>
22+
<artifactId>acp-core</artifactId>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>org.eclipse.jetty</groupId>
27+
<artifactId>jetty-server</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>org.eclipse.jetty.ee10</groupId>
31+
<artifactId>jetty-ee10-servlet</artifactId>
32+
</dependency>
33+
<dependency>
34+
<groupId>org.eclipse.jetty.http2</groupId>
35+
<artifactId>jetty-http2-server</artifactId>
36+
</dependency>
37+
38+
<dependency>
39+
<groupId>org.junit.jupiter</groupId>
40+
<artifactId>junit-jupiter</artifactId>
41+
<scope>test</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.assertj</groupId>
45+
<artifactId>assertj-core</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
<dependency>
49+
<groupId>ch.qos.logback</groupId>
50+
<artifactId>logback-classic</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
<dependency>
54+
<groupId>io.projectreactor</groupId>
55+
<artifactId>reactor-test</artifactId>
56+
<scope>test</scope>
57+
</dependency>
58+
</dependencies>
59+
60+
</project>

0 commit comments

Comments
 (0)