diff --git a/docs/content/about.md b/docs/content/about.md deleted file mode 100644 index b7ffc9c1a..000000000 --- a/docs/content/about.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: About -description: A static site generator built with Java and Quarkus. Zero config to get started, full power of the JVM when you need it. -layout: page ---- - -# About this site - -This site is built with [Roq](https://iamroq.dev), a static site generator powered by [Quarkus](https://quarkus.io). It combines the best of tools like Jekyll and Hugo with the Java ecosystem: zero configuration to get started, blazing fast live-reload in dev mode, and full access to Java when you need it. - -## Authors - -
- {#for id in cdi:authors.fields} - {#let author=cdi:authors.get(id)} - {#roq/authorCard name=author.name avatar=author.avatar?? nickname=author.nickname profile=author.profile /} - {/for} -
- diff --git a/docs/content/authorization.md b/docs/content/authorization.md new file mode 100644 index 000000000..10aa2e6d5 --- /dev/null +++ b/docs/content/authorization.md @@ -0,0 +1,67 @@ +--- +title: Task Authorization +description: Per-user access control for A2A tasks — ownership, read/write checks, and user identity across transports. +layout: page +--- + +# Task Authorization + +> **Security note:** For multi-user deployments, a `TaskAuthorizationProvider` **must** be configured. Without one, all operations are permitted regardless of authentication — any authenticated user can read, modify, or cancel any task. Production deployments should use a fail-closed ownership policy (deny access when ownership is unknown). + +## Implementing TaskAuthorizationProvider + +Implement `TaskAuthorizationProvider` to control per-user access: + +```java +@ApplicationScoped +public class MyTaskAuthorizationProvider implements TaskAuthorizationProvider { + + @Override + public boolean checkRead(ServerCallContext context, String taskId, TaskOperation op) { + return isOwner(context.getUser(), taskId); + } + + @Override + public boolean checkWrite(ServerCallContext context, String taskId, TaskOperation op) { + return isOwner(context.getUser(), taskId); + } + + @Override + public boolean checkCreate(ServerCallContext context, TaskOperation op) { + return context.getUser() != null && context.getUser().isAuthenticated(); + } + + @Override + public boolean isTaskRecorded(String taskId) { + return ownershipStore.contains(taskId); + } + + @Override + public void recordOwnership(ServerCallContext context, String taskId, TaskOperation op) { + if (context.getUser() != null) { + ownershipStore.put(taskId, context.getUser().getUsername()); + } + } +} +``` + +The SDK discovers the bean via CDI automatically — no additional wiring needed. + +> **Note:** When task authorization is required, always obtain `RequestHandler` through CDI injection. Manual instantiation via `DefaultRequestHandler.create()` bypasses the `AuthorizationRequestHandlerDecorator` and all authorization checks. + +## User Identity in ServerCallContext + +Authorization decisions rely on `context.getUser()` returning the authenticated user. How the user is populated depends on the transport: + +- **JSON-RPC and REST**: The Quarkus route handler extracts the user from the Vert.x routing context (`rc.userContext()`) and sets it on `ServerCallContext` directly. +- **gRPC**: The reference server includes a `QuarkusCallContextFactory` CDI bean that injects the Quarkus `SecurityIdentity` and maps it to the `ServerCallContext` `User`. This happens automatically when using the reference gRPC module. If you provide your own `CallContextFactory`, you are responsible for populating the user. + +## Authorization Checks + +| Operation | Authorization check | +|-----------|---------------------| +| `getTask`, `subscribeToTask`, `getTaskPushNotificationConfig`, `listTaskPushNotificationConfigs` | `checkRead` | +| `cancelTask`, `createTaskPushNotificationConfig`, `deleteTaskPushNotificationConfig` | `checkWrite` | +| `messageSend` / `messageSendStream` (existing task) | `checkWrite` | +| `messageSend` / `messageSendStream` (new task) | `checkCreate`, then `recordOwnership` | +| `listTasks` | `checkRead` per task | diff --git a/docs/content/boms.md b/docs/content/boms.md new file mode 100644 index 000000000..e0dc6e1b4 --- /dev/null +++ b/docs/content/boms.md @@ -0,0 +1,120 @@ +--- +title: Bill of Materials (BOM) +description: Dependency management BOMs for the A2A Java SDK — SDK, Extras, and Reference BOMs. +layout: page +--- + +# Bill of Materials (BOM) + +The A2A Java SDK provides three BOMs for different use cases, so you can manage dependency versions in one place. + +## BOM Modules + +### SDK BOM + +**Artifact:** `org.a2aproject.sdk:a2a-java-sdk-bom` + +Includes all A2A SDK core modules (spec, server, client, transport), core third-party dependencies, Jakarta APIs, and test utilities. + +**Use this BOM when:** Building A2A agents with any framework (Quarkus, Spring Boot, vanilla Java, etc.) + +### Extras BOM + +**Artifact:** `org.a2aproject.sdk:a2a-java-sdk-extras-bom` + +Includes everything from the SDK BOM plus server-side enhancement modules (database persistence, distributed queue management, etc.). + +**Use this BOM when:** Building production A2A servers needing advanced server-side features beyond the core SDK. + +### Reference BOM + +**Artifact:** `org.a2aproject.sdk:a2a-java-sdk-reference-bom` + +Includes everything from the SDK BOM plus the Quarkus BOM (complete Quarkus platform), A2A reference implementation modules, and the TCK module for testing. + +**Use this BOM when:** Building Quarkus-based A2A agents or reference implementations. + +## Usage + +### SDK BOM (Any Framework) + +```xml + + + + org.a2aproject.sdk + a2a-java-sdk-bom + $\{org.a2aproject.sdk.version} + pom + import + + + + + + + + org.a2aproject.sdk + a2a-java-sdk-server-common + + + org.a2aproject.sdk + a2a-java-sdk-transport-jsonrpc + + +``` + +### Extras BOM (Database Persistence, Distributed Deployments) + +```xml + + + + org.a2aproject.sdk + a2a-java-sdk-extras-bom + $\{org.a2aproject.sdk.version} + pom + import + + + + + + + org.a2aproject.sdk + a2a-java-sdk-server-common + + + org.a2aproject.sdk + a2a-java-extras-task-store-database-jpa + + +``` + +### Reference BOM (Quarkus) + +```xml + + + + org.a2aproject.sdk + a2a-java-sdk-reference-bom + $\{org.a2aproject.sdk.version} + pom + import + + + + + + + + org.a2aproject.sdk + a2a-java-sdk-reference-jsonrpc + + + io.quarkus + quarkus-arc + + +``` diff --git a/docs/content/client.md b/docs/content/client.md index 94ca0492b..ffa5b67f1 100644 --- a/docs/content/client.md +++ b/docs/content/client.md @@ -190,42 +190,8 @@ Client client = Client ## Communicating with v0.3 Agents -Use `Client_v0_3` to communicate with agents that only support protocol v0.3: - -```xml - - org.a2aproject.sdk - a2a-java-sdk-compat-0.3-client - $\{org.a2aproject.sdk.version} - - - org.a2aproject.sdk - a2a-java-sdk-compat-0.3-client-transport-jsonrpc - $\{org.a2aproject.sdk.version} - -``` - -gRPC and REST transports are also available: -- `a2a-java-sdk-compat-0.3-client-transport-grpc` -- `a2a-java-sdk-compat-0.3-client-transport-rest` - -```java -AgentCard card = A2ACardResolver.builder().baseUrl("http://localhost:1234") - .build().getAgentCard(); - -AgentInterface v03Interface = card.supportedInterfaces().stream() - .filter(i -> A2AProtocol_v0_3.PROTOCOL_VERSION.equals(i.protocolVersion())) - .findFirst().orElseThrow(); - -Client_v0_3 client = ClientBuilder_v0_3.forUrl(v03Interface.url()) - .withTransport(JSONRPCTransport_v0_3.class, new JSONRPCTransportConfigBuilder_v0_3()) - .build(); -``` - -**Note:** `Client_v0_3` exposes only operations available in protocol v0.3. For example, `listTasks()` is not available (it was added in v1.0). Return types use v0.3 domain objects from the `org.a2aproject.sdk.compat03.spec` package. +See [Backward Compatibility]({site.url('compatibility')}#client-communicating-with-v03-agents) for using `Client_v0_3` with older protocol agents. ## Examples -- [Hello World Client](https://github.com/a2aproject/a2a-java/blob/main/examples/helloworld/client/README.md) — Java client talking to a Python A2A server -- [Hello World Server](https://github.com/a2aproject/a2a-java/blob/main/examples/helloworld/server/README.md) — Python client talking to a Java A2A server -- [a2a-samples repository](https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents) — More agent examples +See [Examples]({site.url('examples')}) for Hello World walkthroughs and sample applications. diff --git a/docs/content/compatibility.md b/docs/content/compatibility.md new file mode 100644 index 000000000..aae404e3e --- /dev/null +++ b/docs/content/compatibility.md @@ -0,0 +1,124 @@ +--- +title: Backward Compatibility +description: Serve v1.0 and v0.3 A2A protocol versions simultaneously — multi-version modules, version routing, and v0.3 client support. +layout: page +--- + +# Backward Compatibility with v0.3 + +Add compat modules alongside v1.0 modules to serve both protocol versions simultaneously. No changes to your `AgentExecutor` are needed. + +## Server: Multi-Version Module (recommended) + +```xml + + + org.a2aproject.sdk + a2a-java-sdk-reference-multiversion-jsonrpc + $\{org.a2aproject.sdk.version} + + + + + org.a2aproject.sdk + a2a-java-sdk-reference-multiversion-rest + $\{org.a2aproject.sdk.version} + +``` + +## Server: Individual Compat Modules + +```xml + + + org.a2aproject.sdk + a2a-java-sdk-compat-0.3-reference-jsonrpc + $\{org.a2aproject.sdk.version} + + + + + org.a2aproject.sdk + a2a-java-sdk-compat-0.3-reference-rest + $\{org.a2aproject.sdk.version} + + + + + org.a2aproject.sdk + a2a-java-sdk-compat-0.3-reference-grpc + $\{org.a2aproject.sdk.version} + +``` + +## How Version Routing Works + +- **JSON-RPC and REST**: When serving multiple protocol versions, version routing inspects the `A2A-Version` HTTP header on each request. If the header is `"1.0"`, the request is routed to the v1.0 handler. If it is `"0.3"` or absent, the request is routed to the v0.3 handler. +- **gRPC**: Version dispatch is implicit — v0.3 clients use the `a2a.v1` protobuf package and v1.0 clients use `lf.a2a.v1`, so requests are routed to the correct service automatically. +- **Agent card**: When both v1.0 and v0.3 are enabled, the v1.0 `AgentCard` takes precedence and is served at `/.well-known/agent-card.json`. The v0.3 `AgentCard_v0_3` is ignored. If only v0.3 is enabled, the v0.3 agent card is used. If only v1.0 is enabled, the v1.0 agent card is used as-is. + +## Making the v1.0 Agent Card Compatible with v0.3 Clients + +When serving both protocol versions, you need to ensure the v1.0 agent card contains fields that v0.3 clients expect. Existing v0.3 client implementations (in any language) look for `url`, `preferredTransport`, and `additionalInterfaces` with `transport`/`url` entries — fields that don't exist in the v1.0 format by default. + +To make your v1.0 `AgentCard` parsable by v0.3 clients, set these fields on the builder: + +```java +AgentCard card = AgentCard.builder() + .name("My Agent") + // ... other v1.0 fields ... + .supportedInterfaces(List.of( + new AgentInterface("jsonrpc", "http://localhost:9999"))) + // v0.3 backward-compatibility fields: + .url("http://localhost:9999") + .preferredTransport("jsonrpc") + .additionalInterfaces(List.of( + new Legacy_0_3_AgentInterface("jsonrpc", "http://localhost:9999"))) + .build(); +``` + +The two interface lists serve different clients: + +- `supportedInterfaces` — used by **v1.0 clients** to discover endpoints (uses `AgentInterface` with `protocolBinding`/`url`/`tenant` fields) +- `additionalInterfaces` — used by **v0.3 clients** to discover endpoints (uses `Legacy_0_3_AgentInterface` with v0.3 field names: `transport`/`url`) +- `url` and `preferredTransport` — top-level fields that v0.3 clients use to discover the primary endpoint + +## Push Notification Behavior + +Push notification payloads are automatically formatted to match the protocol version used when the push notification configuration was registered. When a v0.3 client registers a push notification configuration (via any transport), the server records the protocol version alongside the configuration. When a notification is later sent to that webhook, the payload is formatted as a v0.3 Task object. Configurations registered by v1.0 clients receive v1.0 `StreamResponse` payloads as usual. This happens transparently — no additional configuration is needed beyond adding the compat reference module. + +## Client: Communicating with v0.3 Agents + +Use `Client_v0_3` to communicate with agents that only support protocol v0.3: + +```xml + + org.a2aproject.sdk + a2a-java-sdk-compat-0.3-client + $\{org.a2aproject.sdk.version} + + + org.a2aproject.sdk + a2a-java-sdk-compat-0.3-client-transport-jsonrpc + $\{org.a2aproject.sdk.version} + +``` + +gRPC and REST transports are also available: +- `a2a-java-sdk-compat-0.3-client-transport-grpc` +- `a2a-java-sdk-compat-0.3-client-transport-rest` + +```java +AgentCard card = A2ACardResolver.builder().baseUrl("http://localhost:1234") + .build().getAgentCard(); + +AgentInterface v03Interface = card.supportedInterfaces().stream() + .filter(i -> A2AProtocol_v0_3.PROTOCOL_VERSION.equals(i.protocolVersion())) + .findFirst().orElseThrow(); + +Client_v0_3 client = ClientBuilder_v0_3.forUrl(v03Interface.url()) + .withTransport(JSONRPCTransport_v0_3.class, new JSONRPCTransportConfigBuilder_v0_3()) + .build(); +``` + +**Note:** `Client_v0_3` exposes only operations available in protocol v0.3. For example, `listTasks()` is not available (it was added in v1.0). Return types use v0.3 domain objects from the `org.a2aproject.sdk.compat03.spec` package. diff --git a/docs/content/configuration.md b/docs/content/configuration.md new file mode 100644 index 000000000..b164bf3cf --- /dev/null +++ b/docs/content/configuration.md @@ -0,0 +1,150 @@ +--- +title: Configuration +description: Configure the A2A Java SDK — properties, MicroProfile Config integration, custom providers. +layout: page +--- + +# Configuration + +The A2A Java SDK uses a flexible configuration system that works across different frameworks. + +**Default behavior:** Configuration values come from `META-INF/a2a-defaults.properties` files on the classpath (provided by core modules and extras). These defaults work out of the box without any additional setup. + +**Customizing configuration:** +- **Quarkus/MicroProfile Config users**: Add the `microprofile-config` integration to override defaults via `application.properties`, environment variables, or system properties +- **Spring/other frameworks**: Implement a custom `A2AConfigProvider` (see [Custom Config Providers](#custom-config-providers) below) +- **Reference implementations**: Already include the MicroProfile Config integration + +## Configuration Properties + +### Executor Settings + +The SDK uses a dedicated executor for async operations like streaming. Default: 5 core threads, 50 max threads. + +```properties +# Core thread pool size for the @Internal executor (default: 5) +a2a.executor.core-pool-size=5 + +# Maximum thread pool size (default: 50) +a2a.executor.max-pool-size=50 + +# Thread keep-alive time in seconds (default: 60) +a2a.executor.keep-alive-seconds=60 +``` + +### Blocking Call Timeouts + +```properties +# Timeout for agent execution in blocking calls (default: 30 seconds) +a2a.blocking.agent.timeout.seconds=30 + +# Timeout for event consumption in blocking calls (default: 5 seconds) +a2a.blocking.consumption.timeout.seconds=5 +``` + +### Tuning Guidelines + +- **Streaming Performance**: The executor handles streaming subscriptions. Too few threads can cause timeouts under concurrent load. +- **Resource Management**: The dedicated executor prevents streaming operations from competing with the ForkJoinPool. +- **Concurrency**: In production with high concurrent streaming, increase pool sizes accordingly. +- **Agent Timeouts**: LLM-based agents may need longer timeouts (60-120s) compared to simple agents. + +## MicroProfile Config Integration + +Add the integration dependency to override configuration via standard MicroProfile Config sources: + +```xml + + org.a2aproject.sdk + a2a-java-sdk-microprofile-config + $\{org.a2aproject.sdk.version} + +``` + +Once added, you can set any A2A property through: + +**application.properties:** +```properties +a2a.executor.core-pool-size=10 +a2a.executor.max-pool-size=100 +a2a.blocking.agent.timeout.seconds=60 +``` + +**Environment variables:** +```bash +export A2A_EXECUTOR_CORE_POOL_SIZE=10 +export A2A_BLOCKING_AGENT_TIMEOUT_SECONDS=60 +``` + +**System properties:** +```bash +java -Da2a.executor.core-pool-size=10 -jar your-app.jar +``` + +### Configuration Fallback Chain + +``` +MicroProfile Config Sources (application.properties, env vars, -D flags) + | (not found?) +DefaultValuesConfigProvider + -> Scans classpath for ALL META-INF/a2a-defaults.properties files + -> Merges all discovered properties together + -> Throws exception if duplicate keys found + | (property exists?) +Return merged default value + | (not found?) +IllegalArgumentException +``` + +All `META-INF/a2a-defaults.properties` files (from server-common, extras modules, etc.) are loaded and merged together by `DefaultValuesConfigProvider` at startup. This is not a sequential fallback chain, but a single merged set of defaults. + +### Framework Compatibility + +The MicroProfile Config integration works with any MicroProfile Config implementation: + +- **Quarkus** -- Built-in MicroProfile Config support +- **Helidon** -- Built-in MicroProfile Config support +- **Open Liberty** -- Built-in MicroProfile Config support +- **WildFly/JBoss EAP** -- Add `smallrye-config` dependency +- **Other Jakarta EE servers** -- Add MicroProfile Config implementation + +## Custom Config Providers + +If you're using a different framework (Spring, Micronaut, etc.), implement your own `A2AConfigProvider`: + +```java +@ApplicationScoped +@Alternative +@Priority(100) // Higher than MicroProfileConfigProvider's priority of 50 +public class MyConfigProvider implements A2AConfigProvider { + + private final Properties customConfig; + + @Inject + DefaultValuesConfigProvider defaultValues; + + MyConfigProvider() { + customConfig = loadFromYourFramework(); + } + + @Override + public String getValue(String name) { + String value = customConfig.getProperty(name); + if (value != null) { + return value; + } + return defaultValues.getValue(name); + } + + @Override + public Optional getOptionalValue(String name) { + String value = customConfig.getProperty(name); + if (value != null) { + return Optional.of(value); + } + return defaultValues.getOptionalValue(name); + } +} +``` + +**Note:** The reference server implementations (Quarkus-based) automatically include the MicroProfile Config integration, so properties work out of the box in `application.properties`. diff --git a/docs/content/examples.md b/docs/content/examples.md new file mode 100644 index 000000000..369d1bd73 --- /dev/null +++ b/docs/content/examples.md @@ -0,0 +1,149 @@ +--- +title: Examples +description: Hello World examples for the A2A Java SDK — server and client walkthroughs with transport selection and OpenTelemetry. +layout: page +--- + +# Examples + +## Prerequisites + +- Java 17 or higher +- Python 3.8 or higher +- [uv](https://github.com/astral-sh/uv) (recommended Python package installer) +- Git + +## Hello World Server + +This example runs a Java A2A server that a Python client can talk to. + +### Start the Java Server + +```bash +cd examples/helloworld/server +mvn quarkus:dev +``` + +### Transport Protocol Selection + +Select the transport protocol via the `quarkus.agentcard.protocol` property: + +```bash +# JSON-RPC (default) +mvn quarkus:dev + +# gRPC +mvn quarkus:dev -Dquarkus.agentcard.protocol=GRPC + +# HTTP+JSON/REST +mvn quarkus:dev -Dquarkus.agentcard.protocol=HTTP+JSON +``` + +You can also set the default in `src/main/resources/application.properties`: +```properties +quarkus.agentcard.protocol=HTTP+JSON +``` + +### Run the Python Client + +The Python client is part of the [a2a-samples](https://github.com/a2aproject/a2a-samples) project: + +```bash +git clone https://github.com/a2aproject/a2a-samples.git +cd a2a-samples/samples/python/agents/helloworld + +uv venv +source .venv/bin/activate +uv pip install -e . +uv run test_client.py +``` + +The client connects to `http://localhost:9999`, fetches the agent card, sends a regular message, then sends the same message as a streaming request. + +## Hello World Client + +This example runs a Java A2A client that talks to a Python server. + +### Start the Python Server + +```bash +git clone https://github.com/a2aproject/a2a-samples.git +cd a2a-samples/samples/python/agents/helloworld + +uv venv +source .venv/bin/activate +uv pip install -e . +uv run . +``` + +The server starts on `http://localhost:9999`. You can also use the [Java server example](#hello-world-server) instead. + +### Run the Java Client + +First build the SDK, then run the client: + +```bash +# From the a2a-java root +mvn clean install + +# Run the client +cd examples/helloworld/client +mvn exec:java +``` + +#### Transport Protocol Selection + +```bash +# JSON-RPC (default) +mvn exec:java + +# gRPC +mvn exec:java -Dquarkus.agentcard.protocol=GRPC + +# HTTP+JSON/REST +mvn exec:java -Dquarkus.agentcard.protocol=HTTP+JSON +``` + +The protocol you select on the client must match the protocol configured on the server. + +#### Using JBang + +Alternatively, run the client with [JBang](https://www.jbang.dev/) (no Maven required): + +```bash +jbang examples/helloworld/client/src/main/java/org/a2aproject/sdk/examples/helloworld/HelloWorldRunner.java +``` + +Pass transport and OpenTelemetry flags the same way: +```bash +jbang examples/helloworld/client/src/main/java/org/a2aproject/sdk/examples/helloworld/HelloWorldRunner.java \ + -Dquarkus.agentcard.protocol=GRPC -Dopentelemetry=true +``` + +## OpenTelemetry (Optional) + +Both the server and client support distributed tracing with OpenTelemetry. + +### Server with OpenTelemetry + +```bash +cd examples/helloworld/server +mvn quarkus:dev -Popentelemetry +``` + +Quarkus Dev Services automatically starts a Grafana observability stack. Open Grafana at `http://localhost:3001` (credentials: admin/admin) and view traces in the "Explore" section using the Tempo data source. + +### Client with OpenTelemetry + +```bash +cd examples/helloworld/client +mvn exec:java -Dopentelemetry=true +``` + +The client expects an OpenTelemetry collector on port 5317. The easiest way is to run the Java server with `-Popentelemetry` (which starts the collector automatically), then run the client with `-Dopentelemetry=true` for end-to-end traces. + +For more information, see the [OpenTelemetry extras module]({site.url('/extras/opentelemetry')}). + +## More Examples + +- [a2a-samples repository](https://github.com/a2aproject/a2a-samples/tree/main/samples/java/agents) — Additional agent examples in Java and other languages diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md new file mode 100644 index 000000000..a996e4d2d --- /dev/null +++ b/docs/content/getting-started.md @@ -0,0 +1,46 @@ +--- +title: Getting Started +description: Get started with the A2A Java SDK — overview, guides, and next steps. +layout: page +--- + +# Getting Started + +The A2A Java SDK is a multi-module Maven library for implementing the [Agent2Agent (A2A) Protocol](https://a2a-protocol.org/) in Java. It provides both client and server support for agent communication over JSON-RPC, gRPC, and REST transports. + +## What You Can Build + +- **A2A Servers** — Expose your Java agent as an A2A-compliant service that other agents and clients can discover and communicate with. +- **A2A Clients** — Connect to any A2A-compliant agent, with streaming, push notifications, and task management. + +## Quick Start + +Add the reference server dependency to your Maven project: + +```xml + + org.a2aproject.sdk + a2a-java-sdk-reference-jsonrpc + $\{org.a2aproject.sdk.version} + +``` + +Then implement an `AgentExecutor` and define an `AgentCard`. See the [Server Guide]({site.url('server')}) for the full walkthrough. + +## Guides + +| Guide | Description | +|-------|-------------| +| [Server Guide]({site.url('server')}) | Run your Java application as an A2A server | +| [Client Guide]({site.url('client')}) | Communicate with A2A-compliant agents | +| [Configuration]({site.url('configuration')}) | Config properties, MicroProfile Config, custom providers | +| [Task Authorization]({site.url('authorization')}) | Per-user access control for multi-user deployments | +| [Backward Compatibility]({site.url('compatibility')}) | Serve v1.0 and v0.3 clients simultaneously | +| [Extras]({site.url('extras')}) | Optional add-ons: database storage, OpenTelemetry, HTTP clients | +| [BOMs]({site.url('boms')}) | Dependency management with Bill of Materials | +| [Examples]({site.url('examples')}) | Hello World walkthroughs and sample applications | + +## Requirements + +- Java 17+ +- Maven 3.8+ diff --git a/docs/content/index.html b/docs/content/index.html index 4bf0db8b3..ebd358091 100644 --- a/docs/content/index.html +++ b/docs/content/index.html @@ -42,6 +42,36 @@

Quick Install

<version>$\{org.a2aproject.sdk.version}</version> </dependency> -

See the Server Guide and Client Guide for full setup instructions, or browse Community Articles for tutorials and examples.

+

See the Getting Started guide, or jump directly to the Server Guide and Client Guide for full setup instructions.

+ + +
+

Compatibility

+ + + + + + + + + + + + + + + + + + + + + + + + + +
SDK VersionA2A ProtocolJava
1.0.0.Final1.0, 0.317+
1.1.0.Final1.0, 0.317+
dev (unreleased)1.0, 0.317+
diff --git a/docs/content/server.md b/docs/content/server.md index 315e8bd71..378cbadfc 100644 --- a/docs/content/server.md +++ b/docs/content/server.md @@ -152,190 +152,15 @@ public class WeatherAgentExecutorProducer { ## 4. Configuration -The A2A Java SDK uses a flexible configuration system that works across different frameworks. - -**Default behavior:** Configuration values come from `META-INF/a2a-defaults.properties` files on the classpath (provided by core modules and extras). These defaults work out of the box without any additional setup. - -**Customizing configuration:** -- **Quarkus/MicroProfile Config users**: Add the [`microprofile-config`](https://github.com/a2aproject/a2a-java/blob/main/integrations/microprofile-config/README.md) integration to override defaults via `application.properties`, environment variables, or system properties -- **Spring/other frameworks**: See the [integration module README](https://github.com/a2aproject/a2a-java/blob/main/integrations/microprofile-config/README.md#custom-config-providers) for how to implement a custom `A2AConfigProvider` -- **Reference implementations**: Already include the MicroProfile Config integration - -### Configuration Properties - -**Executor Settings** (Optional) - -The SDK uses a dedicated executor for async operations like streaming. Default: 5 core threads, 50 max threads. - -```properties -# Core thread pool size for the @Internal executor (default: 5) -a2a.executor.core-pool-size=5 - -# Maximum thread pool size (default: 50) -a2a.executor.max-pool-size=50 - -# Thread keep-alive time in seconds (default: 60) -a2a.executor.keep-alive-seconds=60 -``` - -**Blocking Call Timeouts** (Optional) - -```properties -# Timeout for agent execution in blocking calls (default: 30 seconds) -a2a.blocking.agent.timeout.seconds=30 - -# Timeout for event consumption in blocking calls (default: 5 seconds) -a2a.blocking.consumption.timeout.seconds=5 -``` - -**Why this matters:** -- **Streaming Performance**: The executor handles streaming subscriptions. Too few threads can cause timeouts under concurrent load. -- **Resource Management**: The dedicated executor prevents streaming operations from competing with the ForkJoinPool. -- **Concurrency**: In production with high concurrent streaming, increase pool sizes accordingly. -- **Agent Timeouts**: LLM-based agents may need longer timeouts (60–120s) compared to simple agents. - -**Note:** The reference server implementations (Quarkus-based) automatically include the MicroProfile Config integration, so properties work out of the box in `application.properties`. +See [Configuration]({site.url('configuration')}) for all config properties and tuning. ## 5. Task Authorization (Optional) -> **⚠ Security note:** For multi-user deployments, a `TaskAuthorizationProvider` **must** be configured. Without one, all operations are permitted regardless of authentication — any authenticated user can read, modify, or cancel any task. Production deployments should use a fail-closed ownership policy (deny access when ownership is unknown). - -Implement `TaskAuthorizationProvider` to control per-user access: - -```java -@ApplicationScoped -public class MyTaskAuthorizationProvider implements TaskAuthorizationProvider { - - @Override - public boolean checkRead(ServerCallContext context, String taskId, TaskOperation op) { - return isOwner(context.getUser(), taskId); - } - - @Override - public boolean checkWrite(ServerCallContext context, String taskId, TaskOperation op) { - return isOwner(context.getUser(), taskId); - } - - @Override - public boolean checkCreate(ServerCallContext context, TaskOperation op) { - return context.getUser().isAuthenticated(); - } - - @Override - public boolean isTaskRecorded(String taskId) { - return ownershipStore.contains(taskId); - } - - @Override - public void recordOwnership(ServerCallContext context, String taskId, TaskOperation op) { - ownershipStore.put(taskId, context.getUser().getUsername()); - } -} -``` - -The SDK discovers the bean via CDI automatically — no additional wiring needed. - -> **Note:** When task authorization is required, always obtain `RequestHandler` through CDI injection. Manual instantiation via `DefaultRequestHandler.create()` bypasses the `AuthorizationRequestHandlerDecorator` and all authorization checks. - -### User Identity in ServerCallContext - -Authorization decisions rely on `context.getUser()` returning the authenticated user. How the user is populated depends on the transport: - -- **JSON-RPC and REST**: The Quarkus route handler extracts the user from the Vert.x routing context (`rc.userContext()`) and sets it on `ServerCallContext` directly. -- **gRPC**: The reference server includes a `QuarkusCallContextFactory` CDI bean that injects the Quarkus `SecurityIdentity` and maps it to the `ServerCallContext` `User`. This happens automatically when using the reference gRPC module. If you provide your own `CallContextFactory`, you are responsible for populating the user. - -### Authorization Checks - -| Operation | Authorization check | -|-----------|---------------------| -| `getTask`, `subscribeToTask`, `getTaskPushNotificationConfig`, `listTaskPushNotificationConfigs` | `checkRead` | -| `cancelTask`, `createTaskPushNotificationConfig`, `deleteTaskPushNotificationConfig` | `checkWrite` | -| `messageSend` / `messageSendStream` (existing task) | `checkWrite` | -| `messageSend` / `messageSendStream` (new task) | `checkCreate`, then `recordOwnership` | -| `listTasks` | `checkRead` per task | +See [Task Authorization]({site.url('authorization')}) for per-user access control. ## Backward Compatibility with v0.3 -Add compat modules alongside v1.0 modules to serve both protocol versions simultaneously. No changes to your `AgentExecutor` are needed. - -### Multi-Version Module (recommended) - -```xml - - - org.a2aproject.sdk - a2a-java-sdk-reference-multiversion-jsonrpc - $\{org.a2aproject.sdk.version} - - - - - org.a2aproject.sdk - a2a-java-sdk-reference-multiversion-rest - $\{org.a2aproject.sdk.version} - -``` - -### Individual Compat Modules - -```xml - - - org.a2aproject.sdk - a2a-java-sdk-compat-0.3-reference-jsonrpc - $\{org.a2aproject.sdk.version} - - - - - org.a2aproject.sdk - a2a-java-sdk-compat-0.3-reference-rest - $\{org.a2aproject.sdk.version} - - - - - org.a2aproject.sdk - a2a-java-sdk-compat-0.3-reference-grpc - $\{org.a2aproject.sdk.version} - -``` - -### How Version Routing Works - -- **JSON-RPC and REST**: When serving multiple protocol versions, version routing inspects the `A2A-Version` HTTP header on each request. If the header is `"1.0"`, the request is routed to the v1.0 handler. If it is `"0.3"` or absent, the request is routed to the v0.3 handler. -- **gRPC**: Version dispatch is implicit — v0.3 clients use the `a2a.v1` protobuf package and v1.0 clients use `lf.a2a.v1`, so requests are routed to the correct service automatically. -- **Agent card**: When both v1.0 and v0.3 are enabled, the v1.0 `AgentCard` takes precedence and is served at `/.well-known/agent-card.json`. The v0.3 `AgentCard_v0_3` is ignored. If only v0.3 is enabled, the v0.3 agent card is used. If only v1.0 is enabled, the v1.0 agent card is used as-is. - -### Making the v1.0 Agent Card Compatible with v0.3 Clients - -When serving both protocol versions, you need to ensure the v1.0 agent card contains fields that v0.3 clients expect. Existing v0.3 client implementations (in any language) look for `url`, `preferredTransport`, and `additionalInterfaces` with `transport`/`url` entries — fields that don't exist in the v1.0 format by default. - -To make your v1.0 `AgentCard` parsable by v0.3 clients, set these fields on the builder: - -```java -AgentCard card = AgentCard.builder() - .name("My Agent") - // ... other v1.0 fields ... - .supportedInterfaces(List.of( - new AgentInterface("jsonrpc", "http://localhost:9999"))) - // v0.3 backward-compatibility fields: - .url("http://localhost:9999") - .preferredTransport("jsonrpc") - .additionalInterfaces(List.of( - new Legacy_0_3_AgentInterface("jsonrpc", "http://localhost:9999"))) - .build(); -``` - -The two interface lists serve different clients: - -- `supportedInterfaces` — used by **v1.0 clients** to discover endpoints (uses `AgentInterface` with `protocolBinding`/`url`/`tenant` fields) -- `additionalInterfaces` — used by **v0.3 clients** to discover endpoints (uses `Legacy_0_3_AgentInterface` with v0.3 field names: `transport`/`url`) -- `url` and `preferredTransport` — top-level fields that v0.3 clients use to discover the primary endpoint - -### Push Notification Behavior - -Push notification payloads are automatically formatted to match the protocol version used when the push notification configuration was registered. When a v0.3 client registers a push notification configuration (via any transport), the server records the protocol version alongside the configuration. When a notification is later sent to that webhook, the payload is formatted as a v0.3 Task object. Configurations registered by v1.0 clients receive v1.0 `StreamResponse` payloads as usual. This happens transparently — no additional configuration is needed beyond adding the compat reference module. +See [Backward Compatibility]({site.url('compatibility')}) for multi-version modules, version routing, and v0.3 client support. ## Server Integrations diff --git a/docs/data/menu.yml b/docs/data/menu.yml index 551048d95..eae08498f 100644 --- a/docs/data/menu.yml +++ b/docs/data/menu.yml @@ -5,6 +5,9 @@ items: - title: "Announcements" path: "/announces" icon: "fa-regular fa-newspaper" + - title: "Getting Started" + path: "/getting-started" + icon: "fa-solid fa-rocket" - title: "Documentation" type: "group" icon: "fa-solid fa-book" @@ -16,10 +19,30 @@ items: path: "/client" icon: "fa-solid fa-plug" group: "documentation" + - title: "Configuration" + path: "/configuration" + icon: "fa-solid fa-sliders" + group: "documentation" + - title: "Authorization" + path: "/authorization" + icon: "fa-solid fa-shield-halved" + group: "documentation" + - title: "Compatibility" + path: "/compatibility" + icon: "fa-solid fa-code-branch" + group: "documentation" - title: "Extras" path: "/extras" icon: "fa-solid fa-puzzle-piece" group: "documentation" + - title: "BOMs" + path: "/boms" + icon: "fa-solid fa-cubes" + group: "documentation" + - title: "Examples" + path: "/examples" + icon: "fa-solid fa-flask" + group: "documentation" - title: "Javadoc" path: "https://javadoc.io/doc/org.a2aproject.sdk" icon: "fa-solid fa-file-code" @@ -38,4 +61,4 @@ items: - title: "GitHub" path: "https://github.com/a2aproject/a2a-java" icon: "fa-brands fa-github" - target: "_blank" \ No newline at end of file + target: "_blank" diff --git a/docs/src/main/resources/templates/layouts/page.html b/docs/src/main/resources/templates/layouts/page.html new file mode 100644 index 000000000..2dd083319 --- /dev/null +++ b/docs/src/main/resources/templates/layouts/page.html @@ -0,0 +1,5 @@ +--- +theme-layout: page +link: /:raw-path +--- +{#insert /} diff --git a/docs/web/_custom.css b/docs/web/_custom.css index b662a727e..9633de360 100644 --- a/docs/web/_custom.css +++ b/docs/web/_custom.css @@ -69,7 +69,7 @@ .menu-group-children-wrapper { overflow: hidden; - max-height: 300px; + max-height: 1000px; transition: max-height 0.25s ease; padding: 0; margin: 0; @@ -107,6 +107,35 @@ max-width: var(--container-5xl); } +/* ── Tables ──────────────────────────────────────────────────────────────── */ + +.page-content table, +.roq-section table { + width: 100%; + border-collapse: collapse; + margin: 1.5rem 0; +} + +.page-content th, +.page-content td, +.roq-section th, +.roq-section td { + padding: 0.6rem 1rem; + text-align: left; + border: 1px solid rgba(255, 255, 255, 0.15); +} + +.page-content th, +.roq-section th { + font-weight: 600; + background: rgba(255, 255, 255, 0.06); +} + +.page-content tr:nth-child(even), +.roq-section tr:nth-child(even) { + background: rgba(255, 255, 255, 0.03); +} + /* ── Quick Install section ────────────────────────────────────────────────── */ .roq-section pre { diff --git a/transport/rest/README.md b/transport/rest/README.md deleted file mode 100644 index 5eef349ec..000000000 --- a/transport/rest/README.md +++ /dev/null @@ -1,355 +0,0 @@ -# A2A REST Transport - -REST transport implementation for the A2A Protocol, providing HTTP-based (`HTTP+JSON` protocol binding) communication between agents and clients. - -## Overview - -This module implements the REST transport layer for A2A protocol operations. All request and response bodies use [Protobuf JSON](https://protobuf.dev/programming-guides/proto3/#json) serialization (camelCase field names). - -## API Endpoints - -The `{tenant}` path prefix is optional on all endpoints. When omitted, the leading slash is also omitted (e.g., `/message:send` instead of `/{tenant}/message:send`). - -### Send Message - -Sends a message to the agent and blocks until the agent reaches a terminal or interrupted state, or returns immediately if `returnImmediately` is set. - -``` -POST /{tenant}/message:send -Content-Type: application/json -``` - -**Request body** (`SendMessageRequest`): -```json -{ - "message": { - "messageId": "msg-1", - "role": "ROLE_USER", - "parts": [ - {"text": "Hello, what can you do?"} - ], - "contextId": "ctx-1" - }, - "configuration": { - "historyLength": 10, - "returnImmediately": false, - "acceptedOutputModes": ["text/plain"] - } -} -``` - -**Response** — one of: -- `{"task": { ... }}` — a `Task` object when the agent creates/updates a task -- `{"message": { ... }}` — a `Message` object when the agent replies without a task - ---- - -### Send Streaming Message - -Sends a message and streams task updates as Server-Sent Events (SSE). Requires `capabilities.streaming = true` in the agent card. - -``` -POST /{tenant}/message:stream -Content-Type: application/json -Accept: text/event-stream -``` - -Request body is identical to `message:send`. Response is a stream of SSE events: - -``` -: SSE stream started - -id: 0 -data: {"statusUpdate":{"taskId":"task-1","contextId":"ctx-1","status":{"state":"TASK_STATE_WORKING"}}} - -id: 1 -data: {"artifactUpdate":{"taskId":"task-1","contextId":"ctx-1","artifact":{"artifactId":"a-1","parts":[{"text":"Hello!"}]}}} - -id: 2 -data: {"statusUpdate":{"taskId":"task-1","contextId":"ctx-1","status":{"state":"TASK_STATE_COMPLETED"}}} -``` - -Each `data` field contains a JSON-serialized `StreamResponse` with one of the following fields set: -- `task` — full `Task` snapshot -- `message` — a `Message` from the agent -- `statusUpdate` — a `TaskStatusUpdateEvent` -- `artifactUpdate` — a `TaskArtifactUpdateEvent` - ---- - -### Get Task - -Retrieves the current state of a task by ID. - -``` -GET /{tenant}/tasks/{taskId}?historyLength=10 -``` - -| Query parameter | Type | Description | -|-----------------|---------|-----------------------------------------------------------------------------| -| `historyLength` | integer | Maximum number of history messages to include. Omit for no limit; `0` for none. | - -**Response** — a `Task` object: -```json -{ - "id": "task-1", - "contextId": "ctx-1", - "status": { - "state": "TASK_STATE_COMPLETED", - "timestamp": "2023-10-27T10:00:00Z" - }, - "artifacts": [], - "history": [] -} -``` - ---- - -### List Tasks - -Lists tasks with optional filtering and pagination. - -``` -GET /{tenant}/tasks -``` - -| Query parameter | Type | Description | -|------------------------|---------|--------------------------------------------------------------------------------------------| -| `contextId` | string | Filter by context ID. | -| `status` | string | Filter by task state. One of the `TaskState` enum values (e.g. `TASK_STATE_COMPLETED`). | -| `pageSize` | integer | Maximum number of tasks to return (server default: 50, max: 100). | -| `pageToken` | string | Pagination token from a previous `ListTasks` response. | -| `historyLength` | integer | Maximum history messages to include per task. | -| `statusTimestampAfter` | string | ISO-8601 timestamp. Only return tasks whose status was updated at or after this time. | -| `includeArtifacts` | boolean | Whether to include artifacts in results. Defaults to `false`. | - -**Response** (`ListTasksResponse`): -```json -{ - "tasks": [ ... ], - "nextPageToken": "", - "pageSize": 50, - "totalSize": 3 -} -``` - -**`TaskState` values:** - -| Value | Description | -|-------------------------------|--------------------------------------------------| -| `TASK_STATE_SUBMITTED` | Task acknowledged, not yet processing. | -| `TASK_STATE_WORKING` | Task is actively being processed. | -| `TASK_STATE_COMPLETED` | Task finished successfully (terminal). | -| `TASK_STATE_FAILED` | Task finished with an error (terminal). | -| `TASK_STATE_CANCELED` | Task was canceled (terminal). | -| `TASK_STATE_REJECTED` | Agent declined to perform the task (terminal). | -| `TASK_STATE_INPUT_REQUIRED` | Agent needs additional input (interrupted). | -| `TASK_STATE_AUTH_REQUIRED` | Authentication is required to proceed (interrupted). | - ---- - -### Cancel Task - -Requests cancellation of a running task. The agent should transition the task to `TASK_STATE_CANCELED`. - -``` -POST /{tenant}/tasks/{taskId}:cancel -Content-Type: application/json -``` - -Request body is optional (may be empty or contain a `metadata` field). - -**Response** — the updated `Task` object on success. - ---- - -### Subscribe to Task - -Opens an SSE stream to receive real-time updates for an existing task. Requires `capabilities.streaming = true`. Returns `UnsupportedOperationError` if the task is already in a terminal state. - -``` -POST /{tenant}/tasks/{taskId}:subscribe -Accept: text/event-stream -``` - -Response is an SSE stream with the same event format as `message:stream`. - ---- - -### Create Push Notification Config - -Creates a webhook configuration for push notifications on a task. - -``` -POST /{tenant}/tasks/{taskId}/pushNotificationConfigs -Content-Type: application/json -``` - -**Request body** (`TaskPushNotificationConfig`): -```json -{ - "url": "https://example.com/webhook", - "token": "optional-token", - "authentication": { - "scheme": "Bearer", - "credentials": "my-token" - } -} -``` - -**Response** — the created `TaskPushNotificationConfig` with its generated `id`. HTTP 201. - ---- - -### Get Push Notification Config - -``` -GET /{tenant}/tasks/{taskId}/pushNotificationConfigs/{configId} -``` - -**Response** — the `TaskPushNotificationConfig` object. - ---- - -### List Push Notification Configs - -``` -GET /{tenant}/tasks/{taskId}/pushNotificationConfigs -``` - -| Query parameter | Type | Description | -|-----------------|---------|------------------------------------| -| `pageSize` | integer | Maximum configurations to return. | -| `pageToken` | string | Pagination token. | - -**Response** (`ListTaskPushNotificationConfigsResponse`): -```json -{ - "configs": [ ... ], - "nextPageToken": "" -} -``` - ---- - -### Delete Push Notification Config - -``` -DELETE /{tenant}/tasks/{taskId}/pushNotificationConfigs/{configId} -``` - -**Response** — HTTP 204 No Content on success. - ---- - -### Get Agent Card - -Public discovery endpoint. Returns the agent's self-describing manifest. No authentication required. - -``` -GET /.well-known/agent-card.json -``` - -**Response** — an `AgentCard` object: -```json -{ - "name": "My Agent", - "description": "An example agent", - "version": "1.0.0", - "supportedInterfaces": [ ... ], - "capabilities": { - "streaming": true, - "pushNotifications": false - }, - "skills": [ ... ], - "defaultInputModes": ["text/plain"], - "defaultOutputModes": ["text/plain"] -} -``` - ---- - -### Get Extended Agent Card - -Returns additional agent metadata for authenticated clients. Requires `capabilities.extendedAgentCard = true` in the public agent card. - -``` -GET /{tenant}/extendedAgentCard -``` - -**Response** — an `AgentCard` object (same structure as the public card, potentially with additional fields). - ---- - -## Request Headers - -| Header | Description | -|--------------------|-------------------------------------------------------------------------------------------------| -| `X-A2A-Version` | Requested A2A protocol version (e.g., `1.0`). Validated against the agent's supported versions. | -| `X-A2A-Extensions` | Comma-separated list of extension URIs the client supports. Required when the agent declares required extensions. | - ---- - -## Error Handling - -All error responses use [RFC 7807 Problem Details](https://tools.ietf.org/html/rfc7807) with `Content-Type: application/problem+json`. - -```json -{ - "type": "https://a2a-protocol.org/errors/task-not-found", - "title": "Task not found", - "status": 404, - "details": "" -} -``` - -| Field | Type | Description | -|-----------|---------|---------------------------------------------| -| `type` | string | URI identifying the error type. | -| `title` | string | Human-readable summary of the error. | -| `status` | integer | HTTP status code. | -| `details` | string | Additional error context (may be empty). | - -### Error Types - -| `type` URI | HTTP Status | Description | -|----------------------------------------------------------------|-------------|------------------------------------------------------------| -| `https://a2a-protocol.org/errors/task-not-found` | 404 | The requested task does not exist. | -| `https://a2a-protocol.org/errors/method-not-found` | 404 | The endpoint does not exist. | -| `https://a2a-protocol.org/errors/invalid-request` | 400 | Malformed request, missing required fields, or JSON parse error. | -| `https://a2a-protocol.org/errors/invalid-params` | 422 | Invalid parameter values (e.g., negative `historyLength`). | -| `https://a2a-protocol.org/errors/extension-support-required` | 400 | The agent requires an extension the client did not declare.| -| `https://a2a-protocol.org/errors/task-not-cancelable` | 409 | The task cannot be canceled in its current state. | -| `https://a2a-protocol.org/errors/content-type-not-supported` | 415 | The requested content type is not supported. | -| `https://a2a-protocol.org/errors/push-notification-not-supported` | 501 | Push notifications are not configured for this agent. | -| `https://a2a-protocol.org/errors/unsupported-operation` | 501 | The operation is not implemented or not applicable (e.g., subscribing to a finalized task). | -| `https://a2a-protocol.org/errors/version-not-supported` | 400 | The requested protocol version is not supported. | -| `https://a2a-protocol.org/errors/invalid-agent-response` | 502 | The agent produced an invalid response. | -| `https://a2a-protocol.org/errors/extended-agent-card-not-configured` | 400 | The agent does not have an extended agent card configured. | -| `https://a2a-protocol.org/errors/internal-error` | 500 | An unexpected server-side error occurred. | - ---- - -## Client Integration - -The REST client (`client/transport/rest`) automatically maps error responses to typed A2A exceptions. It supports both the current RFC 7807 format and the legacy `{"error": "...", "message": "..."}` format for backward compatibility. - -```java -try { - Task task = client.getTask(new TaskQueryParams("task-123")); -} catch (A2AClientException e) { - if (e.getCause() instanceof TaskNotFoundError) { - // Handle task not found - } else if (e.getCause() instanceof UnsupportedOperationError) { - // Handle unsupported operation - } -} -``` - ---- - -## See Also - -- [A2A Protocol Specification](https://a2a-protocol.org/) -- [RFC 7807 Problem Details](https://tools.ietf.org/html/rfc7807) -- [Protobuf JSON Encoding](https://protobuf.dev/programming-guides/proto3/#json)