Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
chore: Add a2a directory to copybara
This change includes the "a2a" directory in the files to be processed by copybara. It adds the directory to the piper_includes list and adds a corresponding core.move transformation to relocate the files from the google3 path to the root of the repository.

PiperOrigin-RevId: 814447419
  • Loading branch information
Doris26 authored and copybara-github committed Oct 3, 2025
commit 76e4b5a9aa9b4e27b7d586a2584b1f26c3eb38d6
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ Coming soon...

For remote agent-to-agent communication, ADK integrates with the
[A2A protocol](https://github.com/google/A2A/).
Examples coming soon...
To build the full runtime, Spring transport, and samples, activate the
`a2a` Maven profile:

```bash
./mvnw -Pa2a clean package
```

See `a2a/README.md` for end-to-end setup instructions and sample commands.

## 🤝 Contributing

Expand Down
228 changes: 228 additions & 0 deletions a2a/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# A2A Runtime and Spring Webservice

This directory contains both the transport-agnostic runtime that maps between
ADK and the A2A specification, as well as the Spring Boot webservice and sample
projects that demonstrate how to expose that runtime over HTTP.

### Module Boundaries

- `com.google.adk.a2a.converters` – pure conversion helpers between ADK events and
A2A spec payloads; entirely framework- and transport-agnostic.
- `com.google.adk.a2a.A2ASendMessageExecutor` and `RemoteA2AAgent` – runtime
entry points that orchestrate sendMessage flows using the converters; they
remain service-framework agnostic but expect a transport-specific client to
be supplied.
- `com.google.adk.a2a.A2AClient` – default HTTP implementation backed by the
A2A SDK; swap this out when binding the runtime to another transport.
- `a2a/webservice` – Spring Boot transport module that imports the runtime
library and exposes the JSON-RPC endpoint.

### High‑Level Picture
```mermaid
graph LR
classDef client fill:#E8F0FE,stroke:#1A73E8,color:#202124;
classDef runner fill:#FCE8B2,stroke:#FBBC04,color:#202124;
classDef agent fill:#FFFFFF,stroke:#5F6368,color:#202124;

subgraph ServiceA["Service A: A2A-ADK Agent"]
direction TB
L_Client["A2A-ADK Client"]
subgraph L_ADK["ADK framework"]
direction TB
L_Runner["Runner.runAsync()"]
L_Root["rootAgent"]
L_Roll["rollDieAgent"]
end

L_Client --> L_ADK
end

subgraph ServiceB["Service B: A2A-ADK Agent"]
direction TB
R_Client["A2A-ADK Client"]
subgraph R_ADK["ADK framework"]
direction TB
R_Runner["Runner.runAsync()"]
R_Check["primeCheckerAgent"]
end

R_Client --> R_ADK
end

L_Client -- "stubby request" --> R_Client
R_Client -- "stubby response" --> L_Client

class L_Client,R_Client client;
class L_Runner,R_Runner runner;
class L_Root,L_Roll,R_Check agent;
```

### Core Runtime Components

- `A2ASendMessageExecutor` walks the full sendMessage lifecycle: converts the
inbound spec message into ADK events, ensures session state, invokes the
agent (either via a provided strategy or an owned `Runner`), applies timeout
handling, and converts the resulting events back into a single spec
`Message`. All logging and fallback responses live here to keep transport
shells minimal.
- `RemoteA2AAgent` resolves `AgentCard` metadata (card object, URL, or file),
constructs/owns an `A2AClient` when needed, builds the JSON-RPC request, and
fans the `SendMessageResponse` back into ADK events using the converters.
- `A2AClient` is strictly an HTTP helper: it serialises the request with
Jackson, posts via `A2AHttpClient`, and deserialises the response. Swap it for
a gRPC client without touching the rest of the package.
- `AgentCard` refers to the spec record in `io.a2a.spec`; we reuse that
builder for discovery metadata so clients and services share the same view.
- `converters/*` hold all mapping logic: `RequestConverter` generates ADK
events from spec messages, `ResponseConverter` performs the reverse and wraps
results, `PartConverter` translates individual parts, and
`ConversationPreprocessor` splits prior history versus the user turn. Nothing
in here depends on any transport framework.

None of these classes depend on Spring or JSON-specific wiring (apart from the
sample HTTP client), so they can be imported directly by other transports or
tools.

### Webservice Module Layout

The `a2a/webservice` module packages the Spring Boot transport that exposes the
REST endpoint:

- `A2ARemoteApplication` – Spring Boot entrypoint that boots the Tomcat server
and wires in the remote configuration.
- `A2ARemoteConfiguration` – Spring `@Configuration` that imports the transport
stack and the shared `A2ASendMessageExecutor`. Provide a `BaseAgent` bean to
handle requests locally (as the `a2a_remote` sample does).
- `A2ARemoteController` – JSON-RPC adapter mounted at `/a2a/remote/v1`.
- `A2ARemoteService` – delegates incoming requests to the executor and handles
JSON-RPC error responses.
All application wiring lives in this module; the core A2A logic remains in the
transport-agnostic `a2a/src/...` tree described above.

### Samples

- `contrib/samples/a2a_basic` – minimal HTTP client demo that hits a remote
A2A endpoint and logs the JSON-RPC exchange. Useful for verifying the
transport without standing up the full Spring service.
- `contrib/samples/a2a_remote` – standalone Spring service mirroring the
Stubby demo. It depends on the shared `a2a/webservice` module so the sample
only provides the prime-agent wiring while reusing the production controller
and service stack.

### Quick Start

All commands below assume you are in `/google_adk`.

1. **Start the Spring webservice sample** (run in its own terminal)
```bash
lsof -ti :8081 | xargs -r kill
./mvnw -f contrib/samples/a2a_remote/pom.xml spring-boot:run \
-Dspring-boot.run.arguments=--server.port=8081
```

Background option:
```bash
nohup env GOOGLE_GENAI_USE_VERTEXAI=FALSE \
GOOGLE_API_KEY=your_api_key \
./mvnw -f contrib/samples/a2a_remote/pom.xml spring-boot:run \
-Dspring-boot.run.arguments=--server.port=8081 \
> /tmp/a2a_webservice.log 2>&1 & echo $!
```
The log can be found at /tmp/a2a_webservice.log.

2. **Run the basic client sample (`a2a_basic`)** (from another terminal)
```bash
GOOGLE_GENAI_USE_VERTEXAI=FALSE \
GOOGLE_API_KEY=your_api_key \
./mvnw -f contrib/samples/a2a_basic/pom.xml exec:java \
-Dexec.args="http://localhost:8081/a2a/remote"
```

The client logs the outbound JSON-RPC payload and shows the remote agent’s
reply (for example, `4 is not a prime number.`).

> The first run downloads dependencies from Maven Central. Configure a
> mirror in `~/.m2/settings.xml` if your environment restricts outbound traffic.

Background option:
```bash
nohup env GOOGLE_GENAI_USE_VERTEXAI=FALSE \
GOOGLE_API_KEY=your_api_key \
./mvnw -f contrib/samples/a2a_basic/pom.xml exec:java \
-Dexec.args="http://localhost:8081/a2a/remote" \
> /tmp/a2a_basic.log 2>&1 & echo $!
```
Tail `/tmp/a2a_basic.log` to observe subsequent turns.

To build the runtime, Spring webservice, and both samples together, activate the
opt-in Maven profile:

```bash
./mvnw -Pa2a clean package
```

#### Manual Smoke Test

With the server running locally you can exercise the endpoint with `curl`:

```bash
curl -X POST http://localhost:8081/a2a/remote/v1/message:send \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "cli-check-2",
"method": "message/send",
"params": {
"message": {
"kind": "message",
"contextId": "cli-demo-context",
"messageId": "cli-check-2",
"role": "USER",
"parts": [
{ "kind": "text", "text": "Is 2 prime?" }
]
}
}
}'
```

Use a fresh `id`/`messageId` for each attempt if you want the request/response
pair logged distinctly. Dropping the `contextId` is fine—the service will
generate a UUID and return it in the response.

Sample response:

```json
{
"jsonrpc": "2.0",
"id": "cli-check-6",
"result": {
"role": "agent",
"parts": [
{
"data": {
"args": { "nums": [6] },
"name": "checkPrime"
},
"metadata": { "type": "function_call" },
"kind": "data"
},
{
"data": {
"response": { "result": "No prime numbers found." },
"name": "checkPrime"
},
"metadata": { "type": "function_response" },
"kind": "data"
},
{
"text": "No prime numbers found.",
"kind": "text"
}
],
"messageId": "36b2a2a4-87c7-4800-b7f7-8e7c73d2f25e",
"contextId": "cli-demo-context",
"kind": "message"
}
}
```
115 changes: 115 additions & 0 deletions a2a/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.adk</groupId>
<artifactId>google-adk-parent</artifactId>
<version>0.3.1-SNAPSHOT</version>
</parent>

<artifactId>google-adk-a2a</artifactId>
<packaging>jar</packaging>

<name>Google ADK A2A Integration</name>

<properties>
<java.version>17</java.version>
<maven.compiler.release>${java.version}</maven.compiler.release>
<a2a.sdk.version>0.3.0.Beta2-SNAPSHOT</a2a.sdk.version>
<google.adk.version>${project.version}</google.adk.version>
<guava.version>33.0.0-jre</guava.version>
<jackson.version>2.19.0</jackson.version>
<rxjava.version>3.1.5</rxjava.version>
<jspecify.version>1.0.0</jspecify.version>
<slf4j.version>2.0.17</slf4j.version>
<errorprone.version>2.38.0</errorprone.version>
<truth.version>1.4.4</truth.version>
<junit4.version>4.13.2</junit4.version>
</properties>

<dependencies>
<dependency>
<groupId>com.google.adk</groupId>
<artifactId>google-adk</artifactId>
<version>${google.adk.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
<version>${errorprone.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>${rxjava.version}</version>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>${jspecify.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-spec</artifactId>
<version>${a2a.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-transport-rest</artifactId>
<version>${a2a.sdk.version}</version>
</dependency>
<dependency>
<groupId>io.github.a2asdk</groupId>
<artifactId>a2a-java-sdk-http-client</artifactId>
<version>${a2a.sdk.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit4.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>${truth.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>${java.version}</release>
</configuration>
</plugin>
</plugins>
</build>
</project>
Loading