From 8e9f257b3fee8c1f98baf12721e0df75194c6fc3 Mon Sep 17 00:00:00 2001 From: Kabir Khan Date: Thu, 25 Jun 2026 17:07:00 +0200 Subject: [PATCH 1/2] docs: add Roq page layout override to preserve versioned URL paths Overrides the default Roq page layout to use `link: /:raw-path`, which prevents URL slugification of directory names (e.g., keeps `1.0.0.Final` instead of `1-0-0-final`). Required for the versioned documentation site restructure described in the plan at docs/superpowers/plans/2026-06-25-versioned-doc-site-plan.md. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/src/main/resources/templates/layouts/page.html | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/src/main/resources/templates/layouts/page.html 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 /} From b6d18b923c87a179c78b85c0bfb61f9d279ee480 Mon Sep 17 00:00:00 2001 From: Kabir Khan Date: Thu, 9 Jul 2026 15:35:58 +0100 Subject: [PATCH 2/2] docs(site): extract standalone pages and complete doc structure Extract configuration, authorization, and backward compatibility into standalone pages. Add REST API reference, BOMs, examples, and getting started pages. Update navigation menu and add compatibility matrix to landing page. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/content/about.md | 19 -- docs/content/authorization.md | 67 ++++++ docs/content/boms.md | 120 +++++++++++ docs/content/client.md | 38 +--- docs/content/compatibility.md | 124 +++++++++++ docs/content/configuration.md | 147 +++++++++++++ docs/content/examples.md | 149 +++++++++++++ docs/content/getting-started.md | 47 +++++ docs/content/index.html | 32 ++- docs/content/rest-api-reference.md | 327 +++++++++++++++++++++++++++++ docs/content/server.md | 181 +--------------- docs/data/menu.yml | 29 ++- docs/web/_custom.css | 31 ++- 13 files changed, 1075 insertions(+), 236 deletions(-) delete mode 100644 docs/content/about.md create mode 100644 docs/content/authorization.md create mode 100644 docs/content/boms.md create mode 100644 docs/content/compatibility.md create mode 100644 docs/content/configuration.md create mode 100644 docs/content/examples.md create mode 100644 docs/content/getting-started.md create mode 100644 docs/content/rest-api-reference.md 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..c39bb24f9 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](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](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..304598fcb --- /dev/null +++ b/docs/content/configuration.md @@ -0,0 +1,147 @@ +--- +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 { + + @Inject + Environment env; // Your framework's config source (e.g. Spring Environment) + + @Inject + DefaultValuesConfigProvider defaultValues; + + @Override + public String getValue(String name) { + String value = env.getProperty(name); + if (value != null) { + return value; + } + return defaultValues.getValue(name); + } + + @Override + public Optional getOptionalValue(String name) { + String value = env.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..90d3150e2 --- /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/google-a2a/a2a-samples) project: + +```bash +git clone https://github.com/google-a2a/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/google-a2a/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](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..230a609dc --- /dev/null +++ b/docs/content/getting-started.md @@ -0,0 +1,47 @@ +--- +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](server) for the full walkthrough. + +## Guides + +| Guide | Description | +|-------|-------------| +| [Server Guide](server) | Run your Java application as an A2A server | +| [Client Guide](client) | Communicate with A2A-compliant agents | +| [Configuration](configuration) | Config properties, MicroProfile Config, custom providers | +| [Task Authorization](authorization) | Per-user access control for multi-user deployments | +| [Backward Compatibility](compatibility) | Serve v1.0 and v0.3 clients simultaneously | +| [REST API Reference](rest-api-reference) | HTTP+JSON endpoint reference | +| [Extras](extras) | Optional add-ons: database storage, OpenTelemetry, HTTP clients | +| [BOMs](boms) | Dependency management with Bill of Materials | +| [Examples](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/rest-api-reference.md b/docs/content/rest-api-reference.md new file mode 100644 index 000000000..6c48fc309 --- /dev/null +++ b/docs/content/rest-api-reference.md @@ -0,0 +1,327 @@ +--- +title: REST API Reference +description: HTTP+JSON endpoint reference for the A2A REST transport — request/response formats, SSE streaming, error handling. +layout: page +--- + +# REST API Reference + +REST transport implementation for the A2A Protocol, providing HTTP-based (`HTTP+JSON` protocol binding) communication between agents and clients. All request and response bodies use [Protobuf JSON](https://protobuf.dev/programming-guides/proto3/#json) serialization (camelCase field names). + +The `\{tenant}` path prefix is optional on all endpoints. When omitted, the extra 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`. + +## Push Notification Configs + +### 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. | +| `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: + +```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) diff --git a/docs/content/server.md b/docs/content/server.md index 315e8bd71..249200645 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](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](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](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..61fe0d225 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,34 @@ 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: "REST API" + path: "/rest-api-reference" + icon: "fa-solid fa-globe" + 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 +65,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/web/_custom.css b/docs/web/_custom.css index b662a727e..c801967ec 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: 2000px; 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 {