@@ -45,29 +45,33 @@ The core of an A2A server is the `AgentExecutor`, which contains your agent's lo
4545// server.ts
4646import express from ' express' ;
4747import { v4 as uuidv4 } from ' uuid' ;
48- import type { AgentCard , Message } from ' @a2a-js/sdk' ;
48+ import { AgentCard , Message , AGENT_CARD_PATH } from ' @a2a-js/sdk' ;
4949import {
5050 AgentExecutor ,
5151 RequestContext ,
5252 ExecutionEventBus ,
5353 DefaultRequestHandler ,
5454 InMemoryTaskStore ,
5555} from ' @a2a-js/sdk/server' ;
56- import { A2AExpressApp } from ' @a2a-js/sdk/server/express' ;
56+ import { agentCardHandler , jsonRpcHandler , restHandler , UserBuilder } from ' @a2a-js/sdk/server/express' ;
5757
5858// 1. Define your agent's identity card.
5959const helloAgentCard: AgentCard = {
6060 name: ' Hello Agent' ,
6161 description: ' A simple agent that says hello.' ,
6262 protocolVersion: ' 0.3.0' ,
6363 version: ' 0.1.0' ,
64- url: ' http://localhost:4000/' , // The public URL of your agent server
64+ url: ' http://localhost:4000/a2a/jsonrpc ' , // The public URL of your agent server
6565 skills: [{ id: ' chat' , name: ' Chat' , description: ' Say hello' , tags: [' chat' ] }],
6666 capabilities: {
6767 pushNotifications: false ,
6868 },
6969 defaultInputModes: [' text' ],
7070 defaultOutputModes: [' text' ],
71+ additionalInterfaces: [
72+ { url: ' http://localhost:4000/a2a/jsonrpc' , transport: ' JSONRPC' }, // Default JSON-RPC transport
73+ { url: ' http://localhost:4000/a2a/rest' , transport: ' HTTP+JSON' }, // HTTP+JSON/REST transport
74+ ],
7175};
7276
7377// 2. Implement the agent's logic.
@@ -100,27 +104,33 @@ const requestHandler = new DefaultRequestHandler(
100104 agentExecutor
101105);
102106
103- const appBuilder = new A2AExpressApp (requestHandler );
104- const expressApp = appBuilder .setupRoutes (express ());
107+ const app = express ();
105108
106- expressApp .listen (4000 , () => {
109+ app .use (` /${AGENT_CARD_PATH } ` , agentCardHandler ({ agentCardProvider: requestHandler }));
110+ app .use (' /a2a/jsonrpc' , jsonRpcHandler ({ requestHandler , userBuilder: UserBuilder .noAuthentication }));
111+ app .use (' /a2a/rest' , restHandler ({ requestHandler , userBuilder: UserBuilder .noAuthentication }));
112+
113+ app .listen (4000 , () => {
107114 console .log (` 🚀 Server started on http://localhost:4000 ` );
108115});
109116```
110117
111118### Client: Sending a Message
112119
113- The ` A2AClient ` makes it easy to communicate with any A2A-compliant agent.
120+ The [ ` ClientFactory ` ] ( src/client/factory.ts ) makes it easy to communicate with any A2A-compliant agent.
114121
115122``` typescript
116123// client.ts
117- import { A2AClient , SendMessageSuccessResponse } from ' @a2a-js/sdk/client' ;
124+ import { ClientFactory , SendMessageSuccessResponse } from ' @a2a-js/sdk/client' ;
118125import { Message , MessageSendParams } from ' @a2a-js/sdk' ;
119126import { v4 as uuidv4 } from ' uuid' ;
120127
121128async function run() {
122- // Create a client pointing to the agent's Agent Card URL.
123- const client = await A2AClient .fromCardUrl (' http://localhost:4000/.well-known/agent-card.json' );
129+ const factory = new ClientFactory ();
130+
131+ // createFromUrl accepts baseUrl and optional path,
132+ // (the default path is /.well-known/agent-card.json)
133+ const client = await factory .createFromUrl (' http://localhost:4000' );
124134
125135 const sendParams: MessageSendParams = {
126136 message: {
@@ -131,13 +141,12 @@ async function run() {
131141 },
132142 };
133143
134- const response = await client .sendMessage (sendParams );
135-
136- if (' error' in response ) {
137- console .error (' Error:' , response .error .message );
138- } else {
139- const result = (response as SendMessageSuccessResponse ).result as Message ;
144+ try {
145+ const response = await client .sendMessage (sendParams );
146+ const result = response as Message ;
140147 console .log (' Agent response:' , result .parts [0 ].text ); // "Hello, world!"
148+ } catch (e ) {
149+ console .error (' Error:' , e );
141150 }
142151}
143152
@@ -213,25 +222,25 @@ The client sends a message and receives a `Task` object as the result.
213222
214223``` typescript
215224// client.ts
216- import { A2AClient , SendMessageSuccessResponse } from ' @a2a-js/sdk/client' ;
225+ import { ClientFactory , SendMessageSuccessResponse } from ' @a2a-js/sdk/client' ;
217226import { Message , MessageSendParams , Task } from ' @a2a-js/sdk' ;
218227// ... other imports ...
219228
220- const client = await A2AClient . fromCardUrl ( ' http://localhost:4000/.well-known/agent-card.json ' );
229+ const factory = new ClientFactory ( );
221230
222- const response = await client .sendMessage ({
223- message: {
224- messageId: uuidv4 (),
225- role: ' user' ,
226- parts: [{ kind: ' text' , text: ' Do something.' }],
227- kind: ' message' ,
228- },
229- });
231+ // createFromUrl accepts baseUrl and optional path,
232+ // (the default path is /.well-known/agent-card.json)
233+ const client = await factory .createFromUrl (' http://localhost:4000' );
230234
231- if (' error' in response ) {
232- console .error (' Error:' , response .error .message );
233- } else {
234- const result = (response as SendMessageSuccessResponse ).result ;
235+ try {
236+ const result = await client .sendMessage ({
237+ message: {
238+ messageId: uuidv4 (),
239+ role: ' user' ,
240+ parts: [{ kind: ' text' , text: ' Do something.' }],
241+ kind: ' message' ,
242+ },
243+ });
235244
236245 // Check if the agent's response is a Task or a direct Message.
237246 if (result .kind === ' task' ) {
@@ -246,45 +255,58 @@ if ('error' in response) {
246255 const message = result as Message ;
247256 console .log (' Received direct message:' , message .parts [0 ].text );
248257 }
258+ } catch (e ) {
259+ console .error (' Error:' , e );
249260}
250261```
251262
252263---
253264
254265## Client Customization
255266
256- You can provide a custom ` fetch ` implementation to the ` A2AClient ` to modify its HTTP request behavior. Common use cases include:
267+ Client can be customized via [ ` CallInterceptor ` 's] ( src/client/interceptors.ts ) which is a recommended way as it's transport-agnostic.
268+
269+ Common use cases include:
257270
258271- ** Request Interception** : Log outgoing requests or collect metrics.
259272- ** Header Injection** : Add custom headers for authentication, tracing, or routing.
260- - ** Retry Mechanisms ** : Implement custom logic for retrying failed requests .
273+ - ** A2A Extensions ** : Modifying payloads to include protocol extension data .
261274
262275### Example: Injecting a Custom Header
263276
264- This example creates a ` fetch ` wrapper that adds a unique ` X-Request-ID ` to every outgoing request .
277+ This example defines a ` CallInterceptor ` to update ` serviceParameters ` which are passed as HTTP headers .
265278
266279``` typescript
267- import { A2AClient } from ' @a2a-js/sdk/client' ;
268280import { v4 as uuidv4 } from ' uuid' ;
281+ import { AfterArgs , BeforeArgs , CallInterceptor , ClientFactory , ClientFactoryOptions } from ' @a2a-js/sdk/client' ;
282+
283+ // 1. Define an interceptor
284+ class RequestIdInterceptor implements CallInterceptor {
285+ before(args : BeforeArgs ): Promise <void > {
286+ args .options = {
287+ ... args .options ,
288+ serviceParameters: {
289+ ... args .options .serviceParameters ,
290+ [' X-Request-ID' ]: uuidv4 (),
291+ },
292+ };
293+ return Promise .resolve ();
294+ }
269295
270- // 1. Create a wrapper around the global fetch function.
271- const fetchWithCustomHeader: typeof fetch = async (url , init ) => {
272- const headers = new Headers (init ?.headers );
273- headers .set (' X-Request-ID' , uuidv4 ());
274-
275- const newInit = { ... init , headers };
276-
277- console .log (` Sending request to ${url } with X-Request-ID: ${headers .get (' X-Request-ID' )} ` );
278-
279- return fetch (url , newInit );
280- };
296+ after(): Promise <void > {
297+ return Promise .resolve ();
298+ }
299+ }
281300
282- // 2. Provide the custom fetch implementation to the client.
283- const client = await A2AClient .fromCardUrl (' http://localhost:4000/.well-known/agent-card.json' , {
284- fetchImpl: fetchWithCustomHeader ,
285- });
301+ // 2. Register the interceptor in the client factory
302+ const factory = new ClientFactory (ClientFactoryOptions .createFrom (ClientFactoryOptions .default , {
303+ clientConfig: {
304+ interceptors: [new RequestIdInterceptor ()]
305+ }
306+ }))
307+ const client = await factory .createFromAgentCardUrl (' http://localhost:4000' );
286308
287- // Now, all requests made by this client instance will include the X-Request-ID header.
309+ // Now, all requests made by clients created by this factory will include the X-Request-ID header.
288310await client .sendMessage ({
289311 message: {
290312 messageId: uuidv4 (),
@@ -297,41 +319,43 @@ await client.sendMessage({
297319
298320### Example: Specifying a Timeout
299321
300- This example creates a ` fetch ` wrapper that sets a timeout for every outgoing request .
322+ Each client method can be configured with an optional ` signal ` field .
301323
302324``` typescript
303- import { A2AClient } from ' @a2a-js/sdk/client' ;
325+ import { ClientFactory } from ' @a2a-js/sdk/client' ;
304326
305- // 1. Create a wrapper around the global fetch function.
306- const fetchWithTimeout: typeof fetch = async (url , init ) => {
307- return fetch (url , { ... init , signal: AbortSignal .timeout (5000 ) });
308- };
327+ const factory = new ClientFactory ();
309328
310- // 2. Provide the custom fetch implementation to the client.
311- const client = await A2AClient .fromCardUrl (' http://localhost:4000/.well-known/agent-card.json' , {
312- fetchImpl: fetchWithTimeout ,
313- });
329+ // createFromUrl accepts baseUrl and optional path,
330+ // (the default path is /.well-known/agent-card.json)
331+ const client = await factory .createFromUrl (' http://localhost:4000' );
314332
315- // Now, all requests made by this client instance will have a configured timeout.
316- await client .sendMessage ({
317- message: {
318- messageId: uuidv4 (),
319- role: ' user' ,
320- parts: [{ kind: ' text' , text: ' A message requiring custom headers.' }],
321- kind: ' message' ,
333+ await client .sendMessage (
334+ {
335+ message: {
336+ messageId: uuidv4 (),
337+ role: ' user' ,
338+ parts: [{ kind: ' text' , text: ' A long-running message.' }],
339+ kind: ' message' ,
340+ },
322341 },
323- });
342+ {
343+ signal: AbortSignal .timeout (5000 ), // 5 seconds timeout
344+ }
345+ );
324346```
325347
326- ### Using the Provided ` AuthenticationHandler `
348+ ### Customizing Transports: Using the Provided ` AuthenticationHandler `
327349
328350For advanced authentication scenarios, the SDK includes a higher-order function ` createAuthenticatingFetchWithRetry ` and an ` AuthenticationHandler ` interface. This utility automatically adds authorization headers and can retry requests that fail with authentication errors (e.g., 401 Unauthorized).
329351
330352Here's how to use it to manage a Bearer token:
331353
332354``` typescript
333355import {
334- A2AClient ,
356+ ClientFactory ,
357+ ClientFactoryOptions
358+ JsonRpcTransportFactory ,
335359 AuthenticationHandler ,
336360 createAuthenticatingFetchWithRetry ,
337361} from ' @a2a-js/sdk/client' ;
@@ -371,10 +395,15 @@ const handler: AuthenticationHandler = {
371395// 2. Create the authenticated fetch function.
372396const authFetch = createAuthenticatingFetchWithRetry (fetch , handler );
373397
374- // 3. Initialize the client with the new fetch implementation.
375- const client = await A2AClient .fromCardUrl (' http://localhost:4000/.well-known/agent-card.json' , {
376- fetchImpl: authFetch ,
377- });
398+ // 3. Inject new fetch implementation into a client factory.
399+ const factory = new ClientFactory (ClientFactoryOptions .createFrom (ClientFactoryOptions .default , {
400+ transports: [
401+ new JsonRpcTransportFactory ({ fetchImpl: authFetch })
402+ ]
403+ }))
404+
405+ // 4. Clients created from the factory are going to have custom fetch attached.
406+ const client = await factory .createFromUrl (' http://localhost:4000' );
378407```
379408
380409---
@@ -448,12 +477,16 @@ The `sendMessageStream` method returns an `AsyncGenerator` that yields events as
448477
449478``` typescript
450479// client.ts
451- import { A2AClient } from ' @a2a-js/sdk/client' ;
480+ import { ClientFactory } from ' @a2a-js/sdk/client' ;
452481import { MessageSendParams } from ' @a2a-js/sdk' ;
453482import { v4 as uuidv4 } from ' uuid' ;
454483// ... other imports ...
455484
456- const client = await A2AClient .fromCardUrl (' http://localhost:4000/.well-known/agent-card.json' );
485+ const factory = new ClientFactory ();
486+
487+ // createFromUrl accepts baseUrl and optional path,
488+ // (the default path is /.well-known/agent-card.json)
489+ const client = await factory .createFromUrl (' http://localhost:4000' );
457490
458491async function streamTask() {
459492 const streamParams: MessageSendParams = {
0 commit comments