You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 479eaf8
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/AbstractA2ARequestHandlerTest_v0_3.java
Copy file name to clipboardExpand all lines: docs/content/dev/extra/http-clients.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -266,3 +266,9 @@ public class MyHttpClientProducer {
266
266
| 0 | JDK | Always (fallback) |
267
267
268
268
If the CDI container is active but no `A2AHttpClient` bean is registered, this provider is skipped and the next available provider is used.
269
+
270
+
### Compatibility with OpenTelemetry
271
+
272
+
When the [OpenTelemetry server module](opentelemetry.md) is on the classpath alongside a custom `A2AHttpClient` CDI bean, the OpenTelemetry module wraps your bean with trace-context propagation rather than replacing it. Your custom configuration (mTLS, proxy, authentication, timeouts, etc.) is preserved for push notification delivery while `traceparent` and `tracestate` headers are injected automatically.
273
+
274
+
This works through the `@Instrumented` CDI qualifier (defined in `server-common`): the OpenTelemetry module produces an `@Instrumented A2AHttpClient` that delegates to your unqualified bean. `BasePushNotificationSender` prefers the `@Instrumented` variant when available, so both your custom configuration and the trace-context headers are present on outbound requests. No changes to your producer code are required.
Copy file name to clipboardExpand all lines: docs/content/dev/extra/opentelemetry.md
+76-11Lines changed: 76 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -177,6 +177,70 @@ The following attributes are automatically added to spans (defined in `A2AObserv
177
177
|`gen_ai.agent.a2a.response`| Full response data (only if extraction enabled) |
178
178
|`error.type`| Error message (on failures) |
179
179
180
+
### Push Notification Delivery Tracing
181
+
182
+
When the server delivers a push notification to a client webhook, `OpenTelemetryPushNotificationSenderDecorator` creates a `CLIENT` span named `SendPushNotification`. This span is independent of the original request span but carries the `gen_ai.agent.a2a.task_id` attribute, allowing correlation by task ID in your observability tool. The span status reflects the actual delivery outcome: `OK` when all notifications succeed, `ERROR` when one or more deliveries fail (with `error.type` set to `delivery_failed`) or when an exception is thrown.
183
+
184
+
The module also provides **W3C trace-context propagation** for outbound push notification HTTP requests via the `@Instrumented` CDI qualifier and `OpenTelemetryHttpClientProducer`.
185
+
186
+
#### How it works
187
+
188
+
The `OpenTelemetryHttpClientProducer` CDI bean produces an `@Instrumented A2AHttpClient` — a wrapper that injects `traceparent` and `tracestate` headers into every outbound HTTP request. `BasePushNotificationSender` resolves the HTTP client in this order:
189
+
190
+
1. An `@Instrumented A2AHttpClient` bean (if available — e.g. the one produced by this module)
191
+
2. An unqualified `A2AHttpClient` bean (if the application provides one)
192
+
3.`A2AHttpClientFactory.create()` as last resort
193
+
194
+
The `@Instrumented` qualifier (defined in `server-common`) marks HTTP client beans that have been wrapped with observability instrumentation. It ensures the instrumented client does not compete with the application's own `A2AHttpClient` bean in default CDI resolution — the two coexist, and the instrumented variant wraps the application's bean as its delegate.
195
+
196
+
#### Custom HTTP client compatibility
197
+
198
+
If your application provides a custom `A2AHttpClient` CDI bean (e.g. for mTLS, proxy, or custom timeouts), the OpenTelemetry wrapper **preserves that configuration** by delegating to your bean rather than replacing it. No additional annotations or configuration are needed — just produce your bean as usual:
199
+
200
+
```java
201
+
@Produces
202
+
@ApplicationScoped
203
+
publicA2AHttpClient myHttpClient() {
204
+
// your custom client (mTLS, proxy, auth, etc.)
205
+
}
206
+
```
207
+
208
+
The OpenTelemetry module automatically wraps it with trace-context injection.
209
+
210
+
#### Context-propagating executor
211
+
212
+
The producer also creates an `@Instrumented Executor` (via `Context.taskWrapping()`) that ensures the OTel trace context survives the async boundary when push notifications are dispatched to multiple recipients in parallel. This allows the receiving webhook to join the distributed trace.
213
+
214
+
`BasePushNotificationSender` resolves the dispatch executor in this order:
215
+
216
+
1. An `@Instrumented Executor` bean (if available — e.g. the one produced by this module)
217
+
2. A `@Named("a2a.pushNotificationDispatch") Executor` bean (if the application provides one)
218
+
3.`ForkJoinPool.commonPool()` as last resort
219
+
220
+
#### Custom dispatch executor compatibility
221
+
222
+
If your application provides a custom `@Named("a2a.pushNotificationDispatch") Executor` CDI bean (e.g. for custom thread pool sizing), the OpenTelemetry module **preserves that configuration** by wrapping it with `Context.taskWrapping()` rather than replacing it. Just produce your bean as usual:
223
+
224
+
```java
225
+
@Produces
226
+
@ApplicationScoped
227
+
@Named("a2a.pushNotificationDispatch")
228
+
publicExecutor myDispatchExecutor() {
229
+
// your custom thread pool
230
+
}
231
+
```
232
+
233
+
The OpenTelemetry module automatically wraps it with context propagation.
The client transport records the following histogram:
273
+
The client transport wrapper is auto-discovered via ServiceLoader — just adding the dependency enables tracing. To also enable `gen_ai.client.operation.duration` metrics, pass a `Meter` via the transport config:
Copy file name to clipboardExpand all lines: extras/opentelemetry/common/src/main/java/org/a2aproject/sdk/extras/opentelemetry/A2AObservabilityNames.java
+4Lines changed: 4 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -42,4 +42,8 @@ public interface A2AObservabilityNames {
Copy file name to clipboardExpand all lines: extras/opentelemetry/integration-tests/src/main/java/org/a2aproject/sdk/extras/opentelemetry/it/A2ATestRoutes.java
0 commit comments