@a2a-js/sdk 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,15 +23,15 @@ npm install @a2a-js/sdk
23
23
 
24
24
  ### For Server Usage
25
25
 
26
- If you plan to use the A2A server functionality (`A2AExpressApp`), you'll also need to install Express as it's a peer dependency:
26
+ If you plan to use the Express integration (imports from `@a2a-js/sdk/server/express`) for A2A server, you'll also need to install Express as it's a peer dependency:
27
27
 
28
28
  ```bash
29
29
  npm install express
30
30
  ```
31
31
 
32
- You can also find JavaScript samples [here](https://github.com/google-a2a/a2a-samples/tree/main/samples/js).
32
+ You can also find some samples [here](https://github.com/a2aproject/a2a-js/tree/main/src/samples).
33
33
 
34
- -----
34
+ ---
35
35
 
36
36
  ## Quickstart
37
37
 
@@ -43,41 +43,46 @@ The core of an A2A server is the `AgentExecutor`, which contains your agent's lo
43
43
 
44
44
  ```typescript
45
45
  // server.ts
46
- import express from "express";
47
- import { v4 as uuidv4 } from "uuid";
48
- import type { AgentCard, Message } from "@a2a-js/sdk";
46
+ import express from 'express';
47
+ import { v4 as uuidv4 } from 'uuid';
48
+ import { AgentCard, Message, AGENT_CARD_PATH } from '@a2a-js/sdk';
49
49
  import {
50
50
  AgentExecutor,
51
51
  RequestContext,
52
52
  ExecutionEventBus,
53
53
  DefaultRequestHandler,
54
54
  InMemoryTaskStore,
55
- } from "@a2a-js/sdk/server";
56
- import { A2AExpressApp } from "@a2a-js/sdk/server/express";
55
+ } from '@a2a-js/sdk/server';
56
+ import { agentCardHandler, jsonRpcHandler, restHandler, UserBuilder } from '@a2a-js/sdk/server/express';
57
57
 
58
58
  // 1. Define your agent's identity card.
59
59
  const helloAgentCard: AgentCard = {
60
- name: "Hello Agent",
61
- description: "A simple agent that says hello.",
62
- protocolVersion: "0.3.0",
63
- version: "0.1.0",
64
- url: "http://localhost:4000/", // The public URL of your agent server
65
- skills: [ { id: "chat", name: "Chat", description: "Say hello", tags: ["chat"] } ],
66
- // --- Other AgentCard fields omitted for brevity ---
60
+ name: 'Hello Agent',
61
+ description: 'A simple agent that says hello.',
62
+ protocolVersion: '0.3.0',
63
+ version: '0.1.0',
64
+ url: 'http://localhost:4000/a2a/jsonrpc', // The public URL of your agent server
65
+ skills: [{ id: 'chat', name: 'Chat', description: 'Say hello', tags: ['chat'] }],
66
+ capabilities: {
67
+ pushNotifications: false,
68
+ },
69
+ defaultInputModes: ['text'],
70
+ 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
+ ],
67
75
  };
68
76
 
69
77
  // 2. Implement the agent's logic.
70
78
  class HelloExecutor implements AgentExecutor {
71
- async execute(
72
- requestContext: RequestContext,
73
- eventBus: ExecutionEventBus
74
- ): Promise<void> {
79
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
75
80
  // Create a direct message response.
76
81
  const responseMessage: Message = {
77
- kind: "message",
82
+ kind: 'message',
78
83
  messageId: uuidv4(),
79
- role: "agent",
80
- parts: [{ kind: "text", text: "Hello, world!" }],
84
+ role: 'agent',
85
+ parts: [{ kind: 'text', text: 'Hello, world!' }],
81
86
  // Associate the response with the incoming request's context.
82
87
  contextId: requestContext.contextId,
83
88
  };
@@ -86,7 +91,7 @@ class HelloExecutor implements AgentExecutor {
86
91
  eventBus.publish(responseMessage);
87
92
  eventBus.finished();
88
93
  }
89
-
94
+
90
95
  // cancelTask is not needed for this simple, non-stateful agent.
91
96
  cancelTask = async (): Promise<void> => {};
92
97
  }
@@ -99,51 +104,56 @@ const requestHandler = new DefaultRequestHandler(
99
104
  agentExecutor
100
105
  );
101
106
 
102
- const appBuilder = new A2AExpressApp(requestHandler);
103
- const expressApp = appBuilder.setupRoutes(express());
107
+ const app = express();
108
+
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 }));
104
112
 
105
- expressApp.listen(4000, () => {
113
+ app.listen(4000, () => {
106
114
  console.log(`🚀 Server started on http://localhost:4000`);
107
115
  });
108
116
  ```
109
117
 
110
118
  ### Client: Sending a Message
111
119
 
112
- 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.
113
121
 
114
122
  ```typescript
115
123
  // client.ts
116
- import { A2AClient, SendMessageSuccessResponse } from "@a2a-js/sdk/client";
117
- import { Message, MessageSendParams } from "@a2a-js/sdk";
118
- import { v4 as uuidv4 } from "uuid";
124
+ import { ClientFactory } from '@a2a-js/sdk/client';
125
+ import { Message, MessageSendParams, SendMessageSuccessResponse } from '@a2a-js/sdk';
126
+ import { v4 as uuidv4 } from 'uuid';
119
127
 
120
128
  async function run() {
121
- // Create a client pointing to the agent's Agent Card URL.
122
- 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');
123
134
 
124
135
  const sendParams: MessageSendParams = {
125
136
  message: {
126
137
  messageId: uuidv4(),
127
- role: "user",
128
- parts: [{ kind: "text", text: "Hi there!" }],
129
- kind: "message",
138
+ role: 'user',
139
+ parts: [{ kind: 'text', text: 'Hi there!' }],
140
+ kind: 'message',
130
141
  },
131
142
  };
132
143
 
133
- const response = await client.sendMessage(sendParams);
134
-
135
- if ("error" in response) {
136
- console.error("Error:", response.error.message);
137
- } else {
138
- const result = (response as SendMessageSuccessResponse).result as Message;
139
- console.log("Agent response:", result.parts[0].text); // "Hello, world!"
144
+ try {
145
+ const response = await client.sendMessage(sendParams);
146
+ const result = response as Message;
147
+ console.log('Agent response:', result.parts[0].text); // "Hello, world!"
148
+ } catch(e) {
149
+ console.error('Error:', e);
140
150
  }
141
151
  }
142
152
 
143
153
  await run();
144
154
  ```
145
155
 
146
- -----
156
+ ---
147
157
 
148
158
  ## A2A `Task` Support
149
159
 
@@ -155,50 +165,47 @@ This agent creates a task, attaches a file artifact to it, and marks it as compl
155
165
 
156
166
  ```typescript
157
167
  // server.ts
158
- import { Task, TaskArtifactUpdateEvent, TaskStatusUpdateEvent } from "@a2a-js/sdk";
168
+ import { Task, TaskArtifactUpdateEvent, TaskStatusUpdateEvent } from '@a2a-js/sdk';
159
169
  // ... other imports from the quickstart server ...
160
170
 
161
171
  class TaskExecutor implements AgentExecutor {
162
- async execute(
163
- requestContext: RequestContext,
164
- eventBus: ExecutionEventBus
165
- ): Promise<void> {
172
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
166
173
  const { taskId, contextId, userMessage, task } = requestContext;
167
174
 
168
175
  // 1. Create and publish the initial task object if it doesn't exist.
169
176
  if (!task) {
170
177
  const initialTask: Task = {
171
- kind: "task",
178
+ kind: 'task',
172
179
  id: taskId,
173
180
  contextId: contextId,
174
181
  status: {
175
- state: "submitted",
182
+ state: 'submitted',
176
183
  timestamp: new Date().toISOString(),
177
184
  },
178
- history: [userMessage]
185
+ history: [userMessage],
179
186
  };
180
187
  eventBus.publish(initialTask);
181
188
  }
182
189
 
183
190
  // 2. Create and publish an artifact.
184
191
  const artifactUpdate: TaskArtifactUpdateEvent = {
185
- kind: "artifact-update",
192
+ kind: 'artifact-update',
186
193
  taskId: taskId,
187
194
  contextId: contextId,
188
195
  artifact: {
189
- artifactId: "report-1",
190
- name: "analysis_report.txt",
191
- parts: [{ kind: "text", text: `This is the analysis for task ${taskId}.` }],
196
+ artifactId: 'report-1',
197
+ name: 'analysis_report.txt',
198
+ parts: [{ kind: 'text', text: `This is the analysis for task ${taskId}.` }],
192
199
  },
193
200
  };
194
201
  eventBus.publish(artifactUpdate);
195
202
 
196
203
  // 3. Publish the final status and mark the event as 'final'.
197
204
  const finalUpdate: TaskStatusUpdateEvent = {
198
- kind: "status-update",
205
+ kind: 'status-update',
199
206
  taskId: taskId,
200
207
  contextId: contextId,
201
- status: { state: "completed", timestamp: new Date().toISOString() },
208
+ status: { state: 'completed', timestamp: new Date().toISOString() },
202
209
  final: true,
203
210
  };
204
211
  eventBus.publish(finalUpdate);
@@ -215,21 +222,28 @@ The client sends a message and receives a `Task` object as the result.
215
222
 
216
223
  ```typescript
217
224
  // client.ts
218
- import { A2AClient, SendMessageSuccessResponse } from "@a2a-js/sdk/client";
219
- import { Message, MessageSendParams, Task } from "@a2a-js/sdk";
225
+ import { ClientFactory } from '@a2a-js/sdk/client';
226
+ import { Message, MessageSendParams, SendMessageSuccessResponse, Task } from '@a2a-js/sdk';
220
227
  // ... other imports ...
221
228
 
222
- const client = await A2AClient.fromCardUrl("http://localhost:4000/.well-known/agent-card.json");
229
+ const factory = new ClientFactory();
223
230
 
224
- const response = await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "Do something." }], kind: "message" } });
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');
225
234
 
226
- if ("error" in response) {
227
- console.error("Error:", response.error.message);
228
- } else {
229
- 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
+ });
230
244
 
231
245
  // Check if the agent's response is a Task or a direct Message.
232
- if (result.kind === "task") {
246
+ if (result.kind === 'task') {
233
247
  const task = result as Task;
234
248
  console.log(`Task [${task.id}] completed with status: ${task.status.state}`);
235
249
 
@@ -239,74 +253,99 @@ if ("error" in response) {
239
253
  }
240
254
  } else {
241
255
  const message = result as Message;
242
- console.log("Received direct message:", message.parts[0].text);
256
+ console.log('Received direct message:', message.parts[0].text);
243
257
  }
258
+ } catch (e) {
259
+ console.error('Error:', e);
244
260
  }
245
261
  ```
246
262
 
247
- -----
263
+ ---
248
264
 
249
265
  ## Client Customization
250
266
 
251
- 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:
252
270
 
253
- * **Request Interception**: Log outgoing requests or collect metrics.
254
- * **Header Injection**: Add custom headers for authentication, tracing, or routing.
255
- * **Retry Mechanisms**: Implement custom logic for retrying failed requests.
271
+ - **Request Interception**: Log outgoing requests or collect metrics.
272
+ - **Header Injection**: Add custom headers for authentication, tracing, or routing.
273
+ - **A2A Extensions**: Modifying payloads to include protocol extension data.
256
274
 
257
275
  ### Example: Injecting a Custom Header
258
276
 
259
- 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.
260
278
 
261
279
  ```typescript
262
- import { A2AClient } from "@a2a-js/sdk/client";
263
- import { v4 as uuidv4 } from "uuid";
264
-
265
- // 1. Create a wrapper around the global fetch function.
266
- const fetchWithCustomHeader: typeof fetch = async (url, init) => {
267
- const headers = new Headers(init?.headers);
268
- headers.set("X-Request-ID", uuidv4());
269
-
270
- const newInit = { ...init, headers };
271
-
272
- console.log(`Sending request to ${url} with X-Request-ID: ${headers.get("X-Request-ID")}`);
273
-
274
- return fetch(url, newInit);
275
- };
280
+ import { 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
+ }
276
295
 
277
- // 2. Provide the custom fetch implementation to the client.
278
- const client = await A2AClient.fromCardUrl(
279
- "http://localhost:4000/.well-known/agent-card.json",
280
- { fetchImpl: fetchWithCustomHeader }
281
- );
296
+ after(): Promise<void> {
297
+ return Promise.resolve();
298
+ }
299
+ }
282
300
 
283
- // Now, all requests made by this client instance will include the X-Request-ID header.
284
- await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "A message requiring custom headers." }], kind: "message" } });
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');
308
+
309
+ // Now, all requests made by clients created by this factory will include the X-Request-ID header.
310
+ await client.sendMessage({
311
+ message: {
312
+ messageId: uuidv4(),
313
+ role: 'user',
314
+ parts: [{ kind: 'text', text: 'A message requiring custom headers.' }],
315
+ kind: 'message',
316
+ },
317
+ });
285
318
  ```
286
319
 
287
320
  ### Example: Specifying a Timeout
288
321
 
289
- 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.
290
323
 
291
324
  ```typescript
292
- import { A2AClient } from "@a2a-js/sdk/client";
325
+ import { ClientFactory } from '@a2a-js/sdk/client';
293
326
 
294
- // 1. Create a wrapper around the global fetch function.
295
- const fetchWithTimeout: typeof fetch = async (url, init) => {
296
- return fetch(url, { ...init, signal: AbortSignal.timeout(5000)});
297
- };
327
+ const factory = new ClientFactory();
298
328
 
299
- // 2. Provide the custom fetch implementation to the client.
300
- const client = await A2AClient.fromCardUrl(
301
- "http://localhost:4000/.well-known/agent-card.json",
302
- { fetchImpl: fetchWithTimeout }
303
- );
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');
304
332
 
305
- // Now, all requests made by this client instance will have a configured timeout.
306
- await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "A message requiring custom headers." }], 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
+ },
341
+ },
342
+ {
343
+ signal: AbortSignal.timeout(5000), // 5 seconds timeout
344
+ }
345
+ );
307
346
  ```
308
347
 
309
- ### Using the Provided `AuthenticationHandler`
348
+ ### Customizing Transports: Using the Provided `AuthenticationHandler`
310
349
 
311
350
  For 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).
312
351
 
@@ -314,16 +353,18 @@ Here's how to use it to manage a Bearer token:
314
353
 
315
354
  ```typescript
316
355
  import {
317
- A2AClient,
356
+ ClientFactory,
357
+ ClientFactoryOptions,
358
+ JsonRpcTransportFactory,
318
359
  AuthenticationHandler,
319
360
  createAuthenticatingFetchWithRetry,
320
- } from "@a2a-js/sdk/client";
361
+ } from '@a2a-js/sdk/client';
321
362
 
322
363
  // A simple token provider that simulates fetching a new token.
323
364
  const tokenProvider = {
324
- token: "initial-stale-token",
365
+ token: 'initial-stale-token',
325
366
  getNewToken: async () => {
326
- console.log("Refreshing auth token...");
367
+ console.log('Refreshing auth token...');
327
368
  tokenProvider.token = `new-token-${Date.now()}`;
328
369
  return tokenProvider.token;
329
370
  },
@@ -339,7 +380,8 @@ const handler: AuthenticationHandler = {
339
380
  // shouldRetryWithHeaders() is called after a request fails.
340
381
  // It decides if a retry is needed and provides new headers.
341
382
  shouldRetryWithHeaders: async (req: RequestInit, res: Response) => {
342
- if (res.status === 401) { // Unauthorized
383
+ if (res.status === 401) {
384
+ // Unauthorized
343
385
  const newToken = await tokenProvider.getNewToken();
344
386
  // Return new headers to trigger a single retry.
345
387
  return { Authorization: `Bearer ${newToken}` };
@@ -353,14 +395,18 @@ const handler: AuthenticationHandler = {
353
395
  // 2. Create the authenticated fetch function.
354
396
  const authFetch = createAuthenticatingFetchWithRetry(fetch, handler);
355
397
 
356
- // 3. Initialize the client with the new fetch implementation.
357
- const client = await A2AClient.fromCardUrl(
358
- "http://localhost:4000/.well-known/agent-card.json",
359
- { fetchImpl: authFetch }
360
- );
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');
361
407
  ```
362
408
 
363
- -----
409
+ ---
364
410
 
365
411
  ## Streaming
366
412
 
@@ -375,51 +421,48 @@ The agent publishes events as it works on the task. The client receives these ev
375
421
  // ... imports ...
376
422
 
377
423
  class StreamingExecutor implements AgentExecutor {
378
- async execute(
379
- requestContext: RequestContext,
380
- eventBus: ExecutionEventBus
381
- ): Promise<void> {
424
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
382
425
  const { taskId, contextId, userMessage, task } = requestContext;
383
426
 
384
427
  // 1. Create and publish the initial task object if it doesn't exist.
385
428
  if (!task) {
386
429
  const initialTask: Task = {
387
- kind: "task",
430
+ kind: 'task',
388
431
  id: taskId,
389
432
  contextId: contextId,
390
433
  status: {
391
- state: "submitted",
434
+ state: 'submitted',
392
435
  timestamp: new Date().toISOString(),
393
436
  },
394
- history: [userMessage]
437
+ history: [userMessage],
395
438
  };
396
439
  eventBus.publish(initialTask);
397
440
  }
398
441
 
399
442
  // 2. Publish 'working' state.
400
443
  eventBus.publish({
401
- kind: "status-update",
444
+ kind: 'status-update',
402
445
  taskId,
403
446
  contextId,
404
- status: { state: "working", timestamp: new Date().toISOString() },
405
- final: false
447
+ status: { state: 'working', timestamp: new Date().toISOString() },
448
+ final: false,
406
449
  });
407
450
 
408
451
  // 3. Simulate work and publish an artifact.
409
- await new Promise(resolve => setTimeout(resolve, 1000));
452
+ await new Promise((resolve) => setTimeout(resolve, 1000));
410
453
  eventBus.publish({
411
- kind: "artifact-update",
454
+ kind: 'artifact-update',
412
455
  taskId,
413
456
  contextId,
414
- artifact: { artifactId: "result.txt", parts: [{ kind: "text", text: "First result." }] },
457
+ artifact: { artifactId: 'result.txt', parts: [{ kind: 'text', text: 'First result.' }] },
415
458
  });
416
459
 
417
460
  // 4. Publish final 'completed' state.
418
461
  eventBus.publish({
419
- kind: "status-update",
462
+ kind: 'status-update',
420
463
  taskId,
421
464
  contextId,
422
- status: { state: "completed", timestamp: new Date().toISOString() },
465
+ status: { state: 'completed', timestamp: new Date().toISOString() },
423
466
  final: true,
424
467
  });
425
468
  eventBus.finished();
@@ -434,20 +477,24 @@ The `sendMessageStream` method returns an `AsyncGenerator` that yields events as
434
477
 
435
478
  ```typescript
436
479
  // client.ts
437
- import { A2AClient } from "@a2a-js/sdk/client";
438
- import { MessageSendParams } from "@a2a-js/sdk";
439
- import { v4 as uuidv4 } from "uuid";
480
+ import { ClientFactory } from '@a2a-js/sdk/client';
481
+ import { MessageSendParams } from '@a2a-js/sdk';
482
+ import { v4 as uuidv4 } from 'uuid';
440
483
  // ... other imports ...
441
484
 
442
- 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');
443
490
 
444
491
  async function streamTask() {
445
492
  const streamParams: MessageSendParams = {
446
493
  message: {
447
494
  messageId: uuidv4(),
448
- role: "user",
449
- parts: [{ kind: "text", text: "Stream me some updates!" }],
450
- kind: "message",
495
+ role: 'user',
496
+ parts: [{ kind: 'text', text: 'Stream me some updates!' }],
497
+ kind: 'message',
451
498
  },
452
499
  };
453
500
 
@@ -455,17 +502,17 @@ async function streamTask() {
455
502
  const stream = client.sendMessageStream(streamParams);
456
503
 
457
504
  for await (const event of stream) {
458
- if (event.kind === "task") {
505
+ if (event.kind === 'task') {
459
506
  console.log(`[${event.id}] Task created. Status: ${event.status.state}`);
460
- } else if (event.kind === "status-update") {
507
+ } else if (event.kind === 'status-update') {
461
508
  console.log(`[${event.taskId}] Status Updated: ${event.status.state}`);
462
- } else if (event.kind === "artifact-update") {
509
+ } else if (event.kind === 'artifact-update') {
463
510
  console.log(`[${event.taskId}] Artifact Received: ${event.artifact.artifactId}`);
464
511
  }
465
512
  }
466
- console.log("--- Stream finished ---");
513
+ console.log('--- Stream finished ---');
467
514
  } catch (error) {
468
- console.error("Error during streaming:", error);
515
+ console.error('Error during streaming:', error);
469
516
  }
470
517
  }
471
518
 
@@ -489,7 +536,7 @@ import {
489
536
  RequestContext,
490
537
  ExecutionEventBus,
491
538
  TaskStatusUpdateEvent,
492
- } from "@a2a-js/sdk/server";
539
+ } from '@a2a-js/sdk/server';
493
540
  // ... other imports ...
494
541
 
495
542
  class CancellableExecutor implements AgentExecutor {
@@ -500,26 +547,20 @@ class CancellableExecutor implements AgentExecutor {
500
547
  * When a cancellation is requested, add the taskId to our tracking set.
501
548
  * The `execute` loop will handle the rest.
502
549
  */
503
- public async cancelTask(
504
- taskId: string,
505
- eventBus: ExecutionEventBus,
506
- ): Promise<void> {
550
+ public async cancelTask(taskId: string, eventBus: ExecutionEventBus): Promise<void> {
507
551
  console.log(`[Executor] Received cancellation request for task: ${taskId}`);
508
552
  this.cancelledTasks.add(taskId);
509
553
  }
510
554
 
511
- public async execute(
512
- requestContext: RequestContext,
513
- eventBus: ExecutionEventBus,
514
- ): Promise<void> {
555
+ public async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
515
556
  const { taskId, contextId } = requestContext;
516
557
 
517
558
  // Start the task
518
559
  eventBus.publish({
519
- kind: "status-update",
560
+ kind: 'status-update',
520
561
  taskId,
521
562
  contextId,
522
- status: { state: "working", timestamp: new Date().toISOString() },
563
+ status: { state: 'working', timestamp: new Date().toISOString() },
523
564
  final: false,
524
565
  });
525
566
 
@@ -529,18 +570,18 @@ class CancellableExecutor implements AgentExecutor {
529
570
  // Before each step, check if the task has been canceled.
530
571
  if (this.cancelledTasks.has(taskId)) {
531
572
  console.log(`[Executor] Aborting task ${taskId} due to cancellation.`);
532
-
573
+
533
574
  // Publish the final 'canceled' status.
534
575
  const cancelledUpdate: TaskStatusUpdateEvent = {
535
- kind: "status-update",
576
+ kind: 'status-update',
536
577
  taskId: taskId,
537
578
  contextId: contextId,
538
- status: { state: "canceled", timestamp: new Date().toISOString() },
579
+ status: { state: 'canceled', timestamp: new Date().toISOString() },
539
580
  final: true,
540
581
  };
541
582
  eventBus.publish(cancelledUpdate);
542
583
  eventBus.finished();
543
-
584
+
544
585
  // Clean up and exit.
545
586
  this.cancelledTasks.delete(taskId);
546
587
  return;
@@ -548,17 +589,17 @@ class CancellableExecutor implements AgentExecutor {
548
589
 
549
590
  // Simulate one step of work.
550
591
  console.log(`[Executor] Working on step ${i + 1} for task ${taskId}...`);
551
- await new Promise(resolve => setTimeout(resolve, 1000));
592
+ await new Promise((resolve) => setTimeout(resolve, 1000));
552
593
  }
553
594
 
554
595
  console.log(`[Executor] Task ${taskId} finished all steps without cancellation.`);
555
596
 
556
597
  // If not canceled, finish the work and publish the completed state.
557
598
  const finalUpdate: TaskStatusUpdateEvent = {
558
- kind: "status-update",
599
+ kind: 'status-update',
559
600
  taskId,
560
601
  contextId,
561
- status: { state: "completed", timestamp: new Date().toISOString() },
602
+ status: { state: 'completed', timestamp: new Date().toISOString() },
562
603
  final: true,
563
604
  };
564
605
  eventBus.publish(finalUpdate);
@@ -590,21 +631,18 @@ const movieAgentCard: AgentCard = {
590
631
  When creating the `DefaultRequestHandler`, you can optionally provide custom push notification components:
591
632
 
592
633
  ```typescript
593
- import {
594
- DefaultRequestHandler,
634
+ import {
635
+ DefaultRequestHandler,
595
636
  InMemoryPushNotificationStore,
596
- DefaultPushNotificationSender
597
- } from "@a2a-js/sdk/server";
637
+ DefaultPushNotificationSender,
638
+ } from '@a2a-js/sdk/server';
598
639
 
599
640
  // Optional: Custom push notification store and sender
600
641
  const pushNotificationStore = new InMemoryPushNotificationStore();
601
- const pushNotificationSender = new DefaultPushNotificationSender(
602
- pushNotificationStore,
603
- {
604
- timeout: 5000, // 5 second timeout
605
- tokenHeaderName: 'X-A2A-Notification-Token' // Custom header name
606
- }
607
- );
642
+ const pushNotificationSender = new DefaultPushNotificationSender(pushNotificationStore, {
643
+ timeout: 5000, // 5 second timeout
644
+ tokenHeaderName: 'X-A2A-Notification-Token', // Custom header name
645
+ });
608
646
 
609
647
  const requestHandler = new DefaultRequestHandler(
610
648
  movieAgentCard,
@@ -624,22 +662,22 @@ Configure push notifications when sending messages:
624
662
  ```typescript
625
663
  // Configure push notification for a message
626
664
  const pushConfig: PushNotificationConfig = {
627
- id: "my-notification-config", // Optional, defaults to task ID
628
- url: "https://my-app.com/webhook/task-updates",
629
- token: "your-auth-token" // Optional authentication token
665
+ id: 'my-notification-config', // Optional, defaults to task ID
666
+ url: 'https://my-app.com/webhook/task-updates',
667
+ token: 'your-auth-token', // Optional authentication token
630
668
  };
631
669
 
632
670
  const sendParams: MessageSendParams = {
633
671
  message: {
634
672
  messageId: uuidv4(),
635
- role: "user",
636
- parts: [{ kind: "text", text: "Hello, agent!" }],
637
- kind: "message",
673
+ role: 'user',
674
+ parts: [{ kind: 'text', text: 'Hello, agent!' }],
675
+ kind: 'message',
638
676
  },
639
677
  configuration: {
640
678
  blocking: true,
641
- acceptedOutputModes: ["text/plain"],
642
- pushNotificationConfig: pushConfig // Add push notification config
679
+ acceptedOutputModes: ['text/plain'],
680
+ pushNotificationConfig: pushConfig, // Add push notification config
643
681
  },
644
682
  };
645
683
  ```
@@ -652,18 +690,18 @@ Your webhook endpoint should expect POST requests with the task data:
652
690
  // Example Express.js webhook endpoint
653
691
  app.post('/webhook/task-updates', (req, res) => {
654
692
  const task = req.body; // The complete task object
655
-
693
+
656
694
  // Verify the token if provided
657
695
  const token = req.headers['x-a2a-notification-token'];
658
696
  if (token !== 'your-auth-token') {
659
697
  return res.status(401).json({ error: 'Unauthorized' });
660
698
  }
661
-
699
+
662
700
  console.log(`Task ${task.id} status: ${task.status.state}`);
663
-
701
+
664
702
  // Process the task update
665
703
  // ...
666
-
704
+
667
705
  res.status(200).json({ received: true });
668
706
  });
669
707
  ```