1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
//! The convenience façade for serving an A2A agent.
//!
//! A consumer who wants one router that serves the
//! well-known card, the JSON-RPC endpoint, and the
//! streaming SSE endpoint on one path prefix calls
//! [`serve_a2a_agent`]. The function is a façade over
//! [`serve_well_known`],
//! [`serve_jsonrpc`],
//! and [`serve_streaming`];
//! the three layers are still public for a consumer
//! who wants to compose them with extra routes or
//! different paths.
//!
//! # Why a façade
//!
//! The three routes an A2A server serves are a
//! shape, not a free composition. A consumer who
//! wires the well-known path manually has to know
//! the constant, the path the spec names for the
//! card (`/.well-known/agent-card.json`); a consumer
//! who wires the streaming path manually has to
//! know it is `"{jsonrpc_path}/stream"`, not a
//! separate configurable path. The two paths are
//! derived from the JSON-RPC path because that is
//! what the example server uses, and it is the
//! shape a peer reading the agent card would
//! expect — the card's `supportedInterfaces[0].url`
//! is the JSON-RPC path, and the streaming path is
//! a peer convention derived from it. Encoding the
//! shape in one function stops a consumer from
//! getting the streaming path wrong.
//!
//! # What the façade does not include
//!
//! A health probe (`/health`) is not part of the
//! A2A spec; the example adds it as a container
//! orchestration convention. A consumer who wants
//! one is expected to `.merge(health_router())` on
//! top of the façade's router. The auth scaffolding
//! (`A2aError::AuthenticationRequired` and friends)
//! is the consumer's choice too; the façade does
//! not mount an auth middleware because the spec
//! leaves the shape of authentication to the
//! deployment.
//!
//! # Example
//!
//! ```no_run
//! use std::sync::Arc;
//!
//! use axum::Router;
//! use protocol_droid::adapters::primary::a2a::serve_agent::serve_a2a_agent;
//! use protocol_droid::adapters::primary::a2a::streaming_serve::A2aStreamingRequestHandler;
//! use protocol_droid::application::a2a::{A2aError, A2aRequestContext, A2aRequestHandler};
//! use protocol_droid::domain::a2a::agent_card::AgentCardBuilder;
//!
//! struct EchoAgent;
//!
//! impl A2aRequestHandler for EchoAgent {
//! fn dispatch(
//! &self,
//! method: protocol_droid::adapters::secondary::a2a::A2aMethod,
//! _params: Option<serde_json::Value>,
//! _context: &A2aRequestContext,
//! ) -> std::pin::Pin<
//! Box<dyn futures_core::Future<Output = Result<serde_json::Value, A2aError>> + Send + '_>,
//! > {
//! Box::pin(async move {
//! Err(A2aError::MethodNotImplemented {
//! method,
//! })
//! })
//! }
//! }
//!
//! impl A2aStreamingRequestHandler for EchoAgent {
//! fn dispatch_stream(
//! &self,
//! method: protocol_droid::adapters::secondary::a2a::A2aMethod,
//! _params: Option<serde_json::Value>,
//! _context: &A2aRequestContext,
//! ) -> std::pin::Pin<
//! Box<dyn futures_core::Stream<Item = Result<protocol_droid::domain::a2a::streaming_events::StreamingEvent, A2aError>> + Send>,
//! > {
//! Box::pin(futures_util::stream::empty())
//! }
//! }
//!
//! # async fn build() -> Router {
//! let agent: Arc<dyn A2aRequestHandler> = Arc::new(EchoAgent);
//! let streaming: Arc<dyn A2aStreamingRequestHandler> = Arc::new(EchoAgent);
//! let card = AgentCardBuilder::new()
//! .with_name("echo")
//! .with_description("An A2A echo agent.")
//! .with_version("0.1.0")
//! .build()
//! .expect("builder inputs are valid");
//! let app: Router = serve_a2a_agent("/a2a", agent, streaming, Arc::new(card));
//! app
//! # }
//! ```
use Arc;
use Router;
use crateserve_jsonrpc;
use crateA2aStreamingRequestHandler;
use crateserve_streaming;
use crateserve_well_known;
use crateA2aRequestHandler;
use crateAgentCard;
use crateA2aVersion;
/// Returns an axum [`Router`] that serves a single A2A
/// agent on three routes:
///
/// - `/.well-known/agent-card.json` — the well-known
/// card discovery endpoint (fixed path per A2A
/// v1.0 §8).
/// - `{jsonrpc_path}` — the JSON-RPC endpoint for
/// the request/response methods (`SendMessage`,
/// `GetTask`, `ListTasks`, `CancelTask`, the four
/// push-notification config methods, and
/// `GetExtendedAgentCard`).
/// - `{jsonrpc_path}/stream` — the streaming SSE
/// endpoint for the two streaming methods
/// (`SendStreamingMessage` and `SubscribeToTask`).
///
/// The two handlers are independent so a consumer
/// who needs different types for the JSON-RPC
/// endpoint and the streaming endpoint can pass
/// different `Arc`s; the common case is one struct
/// that implements both traits, and the example
/// shows that shape. A consumer who wants the
/// layers directly can call the three underlying
/// serving adapters; they are still public and
/// they still compose the same way.
///
/// The `card` is the agent card the well-known
/// endpoint serves. The card is the consumer's
/// value object: the same struct that the
/// `AgentCardBuilder` builds, and the same struct
/// `GetExtendedAgentCard` returns. A card whose
/// `supportedInterfaces[0].url` does not point at
/// `jsonrpc_path` is a card a peer cannot reach;
/// the consumer is responsible for keeping the
/// two in sync. The example's `build_card(addr)`
/// derives the interface URL from the bind
/// address, which is the only way to keep them in
/// sync without manual coordination.