@a2a-js/sdk 0.3.4 → 0.3.6

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
@@ -31,7 +31,7 @@ npm install express
31
31
 
32
32
  You can also find JavaScript samples [here](https://github.com/google-a2a/a2a-samples/tree/main/samples/js).
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();
104
108
 
105
- 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, () => {
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, SendMessageSuccessResponse } from '@a2a-js/sdk/client';
125
+ import { Message, MessageSendParams } 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,47 +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> {
166
- const { taskId, contextId } = requestContext;
167
-
168
- // 1. Create and publish the initial task object.
169
- const initialTask: Task = {
170
- kind: "task",
171
- id: taskId,
172
- contextId: contextId,
173
- status: {
174
- state: "submitted",
175
- timestamp: new Date().toISOString(),
176
- },
177
- };
178
- eventBus.publish(initialTask);
172
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
173
+ const { taskId, contextId, userMessage, task } = requestContext;
174
+
175
+ // 1. Create and publish the initial task object if it doesn't exist.
176
+ if (!task) {
177
+ const initialTask: Task = {
178
+ kind: 'task',
179
+ id: taskId,
180
+ contextId: contextId,
181
+ status: {
182
+ state: 'submitted',
183
+ timestamp: new Date().toISOString(),
184
+ },
185
+ history: [userMessage],
186
+ };
187
+ eventBus.publish(initialTask);
188
+ }
179
189
 
180
190
  // 2. Create and publish an artifact.
181
191
  const artifactUpdate: TaskArtifactUpdateEvent = {
182
- kind: "artifact-update",
192
+ kind: 'artifact-update',
183
193
  taskId: taskId,
184
194
  contextId: contextId,
185
195
  artifact: {
186
- artifactId: "report-1",
187
- name: "analysis_report.txt",
188
- 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}.` }],
189
199
  },
190
200
  };
191
201
  eventBus.publish(artifactUpdate);
192
202
 
193
203
  // 3. Publish the final status and mark the event as 'final'.
194
204
  const finalUpdate: TaskStatusUpdateEvent = {
195
- kind: "status-update",
205
+ kind: 'status-update',
196
206
  taskId: taskId,
197
207
  contextId: contextId,
198
- status: { state: "completed", timestamp: new Date().toISOString() },
208
+ status: { state: 'completed', timestamp: new Date().toISOString() },
199
209
  final: true,
200
210
  };
201
211
  eventBus.publish(finalUpdate);
@@ -212,21 +222,28 @@ The client sends a message and receives a `Task` object as the result.
212
222
 
213
223
  ```typescript
214
224
  // client.ts
215
- import { A2AClient, SendMessageSuccessResponse } from "@a2a-js/sdk/client";
216
- import { Message, MessageSendParams, Task } from "@a2a-js/sdk";
225
+ import { ClientFactory, SendMessageSuccessResponse } from '@a2a-js/sdk/client';
226
+ import { Message, MessageSendParams, Task } from '@a2a-js/sdk';
217
227
  // ... other imports ...
218
228
 
219
- const client = await A2AClient.fromCardUrl("http://localhost:4000/.well-known/agent-card.json");
229
+ const factory = new ClientFactory();
220
230
 
221
- 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');
222
234
 
223
- if ("error" in response) {
224
- console.error("Error:", response.error.message);
225
- } else {
226
- 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
+ });
227
244
 
228
245
  // Check if the agent's response is a Task or a direct Message.
229
- if (result.kind === "task") {
246
+ if (result.kind === 'task') {
230
247
  const task = result as Task;
231
248
  console.log(`Task [${task.id}] completed with status: ${task.status.state}`);
232
249
 
@@ -236,52 +253,99 @@ if ("error" in response) {
236
253
  }
237
254
  } else {
238
255
  const message = result as Message;
239
- console.log("Received direct message:", message.parts[0].text);
256
+ console.log('Received direct message:', message.parts[0].text);
240
257
  }
258
+ } catch (e) {
259
+ console.error('Error:', e);
241
260
  }
242
261
  ```
243
262
 
244
- -----
263
+ ---
245
264
 
246
265
  ## Client Customization
247
266
 
248
- 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:
249
270
 
250
- * **Request Interception**: Log outgoing requests or collect metrics.
251
- * **Header Injection**: Add custom headers for authentication, tracing, or routing.
252
- * **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.
253
274
 
254
275
  ### Example: Injecting a Custom Header
255
276
 
256
- 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.
257
278
 
258
279
  ```typescript
259
- import { A2AClient } from "@a2a-js/sdk/client";
260
- import { v4 as uuidv4 } from "uuid";
261
-
262
- // 1. Create a wrapper around the global fetch function.
263
- const fetchWithCustomHeader: typeof fetch = async (url, init) => {
264
- const headers = new Headers(init?.headers);
265
- headers.set("X-Request-ID", uuidv4());
266
-
267
- const newInit = { ...init, headers };
268
-
269
- console.log(`Sending request to ${url} with X-Request-ID: ${headers.get("X-Request-ID")}`);
270
-
271
- return fetch(url, newInit);
272
- };
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
+ }
273
295
 
274
- // 2. Provide the custom fetch implementation to the client.
275
- const client = await A2AClient.fromCardUrl(
276
- "http://localhost:4000/.well-known/agent-card.json",
277
- { fetchImpl: fetchWithCustomHeader }
278
- );
296
+ after(): Promise<void> {
297
+ return Promise.resolve();
298
+ }
299
+ }
300
+
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');
279
308
 
280
- // Now, all requests made by this client instance will include the X-Request-ID header.
281
- await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "A message requiring custom headers." }], kind: "message" } });
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
+ });
282
318
  ```
283
319
 
284
- ### Using the Provided `AuthenticationHandler`
320
+ ### Example: Specifying a Timeout
321
+
322
+ Each client method can be configured with an optional `signal` field.
323
+
324
+ ```typescript
325
+ import { ClientFactory } from '@a2a-js/sdk/client';
326
+
327
+ const factory = new ClientFactory();
328
+
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');
332
+
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
+ );
346
+ ```
347
+
348
+ ### Customizing Transports: Using the Provided `AuthenticationHandler`
285
349
 
286
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).
287
351
 
@@ -289,16 +353,18 @@ Here's how to use it to manage a Bearer token:
289
353
 
290
354
  ```typescript
291
355
  import {
292
- A2AClient,
356
+ ClientFactory,
357
+ ClientFactoryOptions
358
+ JsonRpcTransportFactory,
293
359
  AuthenticationHandler,
294
360
  createAuthenticatingFetchWithRetry,
295
- } from "@a2a-js/sdk/client";
361
+ } from '@a2a-js/sdk/client';
296
362
 
297
363
  // A simple token provider that simulates fetching a new token.
298
364
  const tokenProvider = {
299
- token: "initial-stale-token",
365
+ token: 'initial-stale-token',
300
366
  getNewToken: async () => {
301
- console.log("Refreshing auth token...");
367
+ console.log('Refreshing auth token...');
302
368
  tokenProvider.token = `new-token-${Date.now()}`;
303
369
  return tokenProvider.token;
304
370
  },
@@ -314,7 +380,8 @@ const handler: AuthenticationHandler = {
314
380
  // shouldRetryWithHeaders() is called after a request fails.
315
381
  // It decides if a retry is needed and provides new headers.
316
382
  shouldRetryWithHeaders: async (req: RequestInit, res: Response) => {
317
- if (res.status === 401) { // Unauthorized
383
+ if (res.status === 401) {
384
+ // Unauthorized
318
385
  const newToken = await tokenProvider.getNewToken();
319
386
  // Return new headers to trigger a single retry.
320
387
  return { Authorization: `Bearer ${newToken}` };
@@ -328,14 +395,18 @@ const handler: AuthenticationHandler = {
328
395
  // 2. Create the authenticated fetch function.
329
396
  const authFetch = createAuthenticatingFetchWithRetry(fetch, handler);
330
397
 
331
- // 3. Initialize the client with the new fetch implementation.
332
- const client = await A2AClient.fromCardUrl(
333
- "http://localhost:4000/.well-known/agent-card.json",
334
- { fetchImpl: authFetch }
335
- );
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');
336
407
  ```
337
408
 
338
- -----
409
+ ---
339
410
 
340
411
  ## Streaming
341
412
 
@@ -350,44 +421,48 @@ The agent publishes events as it works on the task. The client receives these ev
350
421
  // ... imports ...
351
422
 
352
423
  class StreamingExecutor implements AgentExecutor {
353
- async execute(
354
- requestContext: RequestContext,
355
- eventBus: ExecutionEventBus
356
- ): Promise<void> {
357
- const { taskId, contextId } = requestContext;
358
-
359
- // 1. Publish initial 'submitted' state.
360
- eventBus.publish({
361
- kind: "task",
362
- id: taskId,
363
- contextId,
364
- status: { state: "submitted", timestamp: new Date().toISOString() },
365
- });
424
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
425
+ const { taskId, contextId, userMessage, task } = requestContext;
426
+
427
+ // 1. Create and publish the initial task object if it doesn't exist.
428
+ if (!task) {
429
+ const initialTask: Task = {
430
+ kind: 'task',
431
+ id: taskId,
432
+ contextId: contextId,
433
+ status: {
434
+ state: 'submitted',
435
+ timestamp: new Date().toISOString(),
436
+ },
437
+ history: [userMessage],
438
+ };
439
+ eventBus.publish(initialTask);
440
+ }
366
441
 
367
442
  // 2. Publish 'working' state.
368
443
  eventBus.publish({
369
- kind: "status-update",
444
+ kind: 'status-update',
370
445
  taskId,
371
446
  contextId,
372
- status: { state: "working", timestamp: new Date().toISOString() },
373
- final: false
447
+ status: { state: 'working', timestamp: new Date().toISOString() },
448
+ final: false,
374
449
  });
375
450
 
376
451
  // 3. Simulate work and publish an artifact.
377
- await new Promise(resolve => setTimeout(resolve, 1000));
452
+ await new Promise((resolve) => setTimeout(resolve, 1000));
378
453
  eventBus.publish({
379
- kind: "artifact-update",
454
+ kind: 'artifact-update',
380
455
  taskId,
381
456
  contextId,
382
- artifact: { artifactId: "result.txt", parts: [{ kind: "text", text: "First result." }] },
457
+ artifact: { artifactId: 'result.txt', parts: [{ kind: 'text', text: 'First result.' }] },
383
458
  });
384
459
 
385
460
  // 4. Publish final 'completed' state.
386
461
  eventBus.publish({
387
- kind: "status-update",
462
+ kind: 'status-update',
388
463
  taskId,
389
464
  contextId,
390
- status: { state: "completed", timestamp: new Date().toISOString() },
465
+ status: { state: 'completed', timestamp: new Date().toISOString() },
391
466
  final: true,
392
467
  });
393
468
  eventBus.finished();
@@ -402,20 +477,24 @@ The `sendMessageStream` method returns an `AsyncGenerator` that yields events as
402
477
 
403
478
  ```typescript
404
479
  // client.ts
405
- import { A2AClient } from "@a2a-js/sdk/client";
406
- import { MessageSendParams } from "@a2a-js/sdk";
407
- 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';
408
483
  // ... other imports ...
409
484
 
410
- 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');
411
490
 
412
491
  async function streamTask() {
413
492
  const streamParams: MessageSendParams = {
414
493
  message: {
415
494
  messageId: uuidv4(),
416
- role: "user",
417
- parts: [{ kind: "text", text: "Stream me some updates!" }],
418
- kind: "message",
495
+ role: 'user',
496
+ parts: [{ kind: 'text', text: 'Stream me some updates!' }],
497
+ kind: 'message',
419
498
  },
420
499
  };
421
500
 
@@ -423,17 +502,17 @@ async function streamTask() {
423
502
  const stream = client.sendMessageStream(streamParams);
424
503
 
425
504
  for await (const event of stream) {
426
- if (event.kind === "task") {
505
+ if (event.kind === 'task') {
427
506
  console.log(`[${event.id}] Task created. Status: ${event.status.state}`);
428
- } else if (event.kind === "status-update") {
507
+ } else if (event.kind === 'status-update') {
429
508
  console.log(`[${event.taskId}] Status Updated: ${event.status.state}`);
430
- } else if (event.kind === "artifact-update") {
509
+ } else if (event.kind === 'artifact-update') {
431
510
  console.log(`[${event.taskId}] Artifact Received: ${event.artifact.artifactId}`);
432
511
  }
433
512
  }
434
- console.log("--- Stream finished ---");
513
+ console.log('--- Stream finished ---');
435
514
  } catch (error) {
436
- console.error("Error during streaming:", error);
515
+ console.error('Error during streaming:', error);
437
516
  }
438
517
  }
439
518
 
@@ -457,7 +536,7 @@ import {
457
536
  RequestContext,
458
537
  ExecutionEventBus,
459
538
  TaskStatusUpdateEvent,
460
- } from "@a2a-js/sdk/server";
539
+ } from '@a2a-js/sdk/server';
461
540
  // ... other imports ...
462
541
 
463
542
  class CancellableExecutor implements AgentExecutor {
@@ -468,26 +547,20 @@ class CancellableExecutor implements AgentExecutor {
468
547
  * When a cancellation is requested, add the taskId to our tracking set.
469
548
  * The `execute` loop will handle the rest.
470
549
  */
471
- public async cancelTask(
472
- taskId: string,
473
- eventBus: ExecutionEventBus,
474
- ): Promise<void> {
550
+ public async cancelTask(taskId: string, eventBus: ExecutionEventBus): Promise<void> {
475
551
  console.log(`[Executor] Received cancellation request for task: ${taskId}`);
476
552
  this.cancelledTasks.add(taskId);
477
553
  }
478
554
 
479
- public async execute(
480
- requestContext: RequestContext,
481
- eventBus: ExecutionEventBus,
482
- ): Promise<void> {
555
+ public async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
483
556
  const { taskId, contextId } = requestContext;
484
557
 
485
558
  // Start the task
486
559
  eventBus.publish({
487
- kind: "status-update",
560
+ kind: 'status-update',
488
561
  taskId,
489
562
  contextId,
490
- status: { state: "working", timestamp: new Date().toISOString() },
563
+ status: { state: 'working', timestamp: new Date().toISOString() },
491
564
  final: false,
492
565
  });
493
566
 
@@ -497,18 +570,18 @@ class CancellableExecutor implements AgentExecutor {
497
570
  // Before each step, check if the task has been canceled.
498
571
  if (this.cancelledTasks.has(taskId)) {
499
572
  console.log(`[Executor] Aborting task ${taskId} due to cancellation.`);
500
-
573
+
501
574
  // Publish the final 'canceled' status.
502
575
  const cancelledUpdate: TaskStatusUpdateEvent = {
503
- kind: "status-update",
576
+ kind: 'status-update',
504
577
  taskId: taskId,
505
578
  contextId: contextId,
506
- status: { state: "canceled", timestamp: new Date().toISOString() },
579
+ status: { state: 'canceled', timestamp: new Date().toISOString() },
507
580
  final: true,
508
581
  };
509
582
  eventBus.publish(cancelledUpdate);
510
583
  eventBus.finished();
511
-
584
+
512
585
  // Clean up and exit.
513
586
  this.cancelledTasks.delete(taskId);
514
587
  return;
@@ -516,17 +589,17 @@ class CancellableExecutor implements AgentExecutor {
516
589
 
517
590
  // Simulate one step of work.
518
591
  console.log(`[Executor] Working on step ${i + 1} for task ${taskId}...`);
519
- await new Promise(resolve => setTimeout(resolve, 1000));
592
+ await new Promise((resolve) => setTimeout(resolve, 1000));
520
593
  }
521
594
 
522
595
  console.log(`[Executor] Task ${taskId} finished all steps without cancellation.`);
523
596
 
524
597
  // If not canceled, finish the work and publish the completed state.
525
598
  const finalUpdate: TaskStatusUpdateEvent = {
526
- kind: "status-update",
599
+ kind: 'status-update',
527
600
  taskId,
528
601
  contextId,
529
- status: { state: "completed", timestamp: new Date().toISOString() },
602
+ status: { state: 'completed', timestamp: new Date().toISOString() },
530
603
  final: true,
531
604
  };
532
605
  eventBus.publish(finalUpdate);
@@ -558,21 +631,18 @@ const movieAgentCard: AgentCard = {
558
631
  When creating the `DefaultRequestHandler`, you can optionally provide custom push notification components:
559
632
 
560
633
  ```typescript
561
- import {
562
- DefaultRequestHandler,
634
+ import {
635
+ DefaultRequestHandler,
563
636
  InMemoryPushNotificationStore,
564
- DefaultPushNotificationSender
565
- } from "@a2a-js/sdk/server";
637
+ DefaultPushNotificationSender,
638
+ } from '@a2a-js/sdk/server';
566
639
 
567
640
  // Optional: Custom push notification store and sender
568
641
  const pushNotificationStore = new InMemoryPushNotificationStore();
569
- const pushNotificationSender = new DefaultPushNotificationSender(
570
- pushNotificationStore,
571
- {
572
- timeout: 5000, // 5 second timeout
573
- tokenHeaderName: 'X-A2A-Notification-Token' // Custom header name
574
- }
575
- );
642
+ const pushNotificationSender = new DefaultPushNotificationSender(pushNotificationStore, {
643
+ timeout: 5000, // 5 second timeout
644
+ tokenHeaderName: 'X-A2A-Notification-Token', // Custom header name
645
+ });
576
646
 
577
647
  const requestHandler = new DefaultRequestHandler(
578
648
  movieAgentCard,
@@ -592,22 +662,22 @@ Configure push notifications when sending messages:
592
662
  ```typescript
593
663
  // Configure push notification for a message
594
664
  const pushConfig: PushNotificationConfig = {
595
- id: "my-notification-config", // Optional, defaults to task ID
596
- url: "https://my-app.com/webhook/task-updates",
597
- 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
598
668
  };
599
669
 
600
670
  const sendParams: MessageSendParams = {
601
671
  message: {
602
672
  messageId: uuidv4(),
603
- role: "user",
604
- parts: [{ kind: "text", text: "Hello, agent!" }],
605
- kind: "message",
673
+ role: 'user',
674
+ parts: [{ kind: 'text', text: 'Hello, agent!' }],
675
+ kind: 'message',
606
676
  },
607
677
  configuration: {
608
678
  blocking: true,
609
- acceptedOutputModes: ["text/plain"],
610
- pushNotificationConfig: pushConfig // Add push notification config
679
+ acceptedOutputModes: ['text/plain'],
680
+ pushNotificationConfig: pushConfig, // Add push notification config
611
681
  },
612
682
  };
613
683
  ```
@@ -620,18 +690,18 @@ Your webhook endpoint should expect POST requests with the task data:
620
690
  // Example Express.js webhook endpoint
621
691
  app.post('/webhook/task-updates', (req, res) => {
622
692
  const task = req.body; // The complete task object
623
-
693
+
624
694
  // Verify the token if provided
625
695
  const token = req.headers['x-a2a-notification-token'];
626
696
  if (token !== 'your-auth-token') {
627
697
  return res.status(401).json({ error: 'Unauthorized' });
628
698
  }
629
-
699
+
630
700
  console.log(`Task ${task.id} status: ${task.status.state}`);
631
-
701
+
632
702
  // Process the task update
633
703
  // ...
634
-
704
+
635
705
  res.status(200).json({ received: true });
636
706
  });
637
707
  ```