@mailmodo/a2a 0.3.3 → 0.3.5

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,38 @@ 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 type { AgentCard, Message } 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 { A2AExpressApp } 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"] } ],
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
66
  // --- Other AgentCard fields omitted for brevity ---
67
67
  };
68
68
 
69
69
  // 2. Implement the agent's logic.
70
70
  class HelloExecutor implements AgentExecutor {
71
- async execute(
72
- requestContext: RequestContext,
73
- eventBus: ExecutionEventBus
74
- ): Promise<void> {
71
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
75
72
  // Create a direct message response.
76
73
  const responseMessage: Message = {
77
- kind: "message",
74
+ kind: 'message',
78
75
  messageId: uuidv4(),
79
- role: "agent",
80
- parts: [{ kind: "text", text: "Hello, world!" }],
76
+ role: 'agent',
77
+ parts: [{ kind: 'text', text: 'Hello, world!' }],
81
78
  // Associate the response with the incoming request's context.
82
79
  contextId: requestContext.contextId,
83
80
  };
@@ -86,7 +83,7 @@ class HelloExecutor implements AgentExecutor {
86
83
  eventBus.publish(responseMessage);
87
84
  eventBus.finished();
88
85
  }
89
-
86
+
90
87
  // cancelTask is not needed for this simple, non-stateful agent.
91
88
  cancelTask = async (): Promise<void> => {};
92
89
  }
@@ -113,37 +110,37 @@ The `A2AClient` makes it easy to communicate with any A2A-compliant agent.
113
110
 
114
111
  ```typescript
115
112
  // 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";
113
+ import { A2AClient, SendMessageSuccessResponse } from '@a2a-js/sdk/client';
114
+ import { Message, MessageSendParams } from '@a2a-js/sdk';
115
+ import { v4 as uuidv4 } from 'uuid';
119
116
 
120
117
  async function run() {
121
118
  // 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");
119
+ const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json');
123
120
 
124
121
  const sendParams: MessageSendParams = {
125
122
  message: {
126
123
  messageId: uuidv4(),
127
- role: "user",
128
- parts: [{ kind: "text", text: "Hi there!" }],
129
- kind: "message",
124
+ role: 'user',
125
+ parts: [{ kind: 'text', text: 'Hi there!' }],
126
+ kind: 'message',
130
127
  },
131
128
  };
132
129
 
133
130
  const response = await client.sendMessage(sendParams);
134
131
 
135
- if ("error" in response) {
136
- console.error("Error:", response.error.message);
132
+ if ('error' in response) {
133
+ console.error('Error:', response.error.message);
137
134
  } else {
138
135
  const result = (response as SendMessageSuccessResponse).result as Message;
139
- console.log("Agent response:", result.parts[0].text); // "Hello, world!"
136
+ console.log('Agent response:', result.parts[0].text); // "Hello, world!"
140
137
  }
141
138
  }
142
139
 
143
140
  await run();
144
141
  ```
145
142
 
146
- -----
143
+ ---
147
144
 
148
145
  ## A2A `Task` Support
149
146
 
@@ -155,47 +152,47 @@ This agent creates a task, attaches a file artifact to it, and marks it as compl
155
152
 
156
153
  ```typescript
157
154
  // server.ts
158
- import { Task, TaskArtifactUpdateEvent, TaskStatusUpdateEvent } from "@a2a-js/sdk";
155
+ import { Task, TaskArtifactUpdateEvent, TaskStatusUpdateEvent } from '@a2a-js/sdk';
159
156
  // ... other imports from the quickstart server ...
160
157
 
161
158
  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);
159
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
160
+ const { taskId, contextId, userMessage, task } = requestContext;
161
+
162
+ // 1. Create and publish the initial task object if it doesn't exist.
163
+ if (!task) {
164
+ const initialTask: Task = {
165
+ kind: 'task',
166
+ id: taskId,
167
+ contextId: contextId,
168
+ status: {
169
+ state: 'submitted',
170
+ timestamp: new Date().toISOString(),
171
+ },
172
+ history: [userMessage],
173
+ };
174
+ eventBus.publish(initialTask);
175
+ }
179
176
 
180
177
  // 2. Create and publish an artifact.
181
178
  const artifactUpdate: TaskArtifactUpdateEvent = {
182
- kind: "artifact-update",
179
+ kind: 'artifact-update',
183
180
  taskId: taskId,
184
181
  contextId: contextId,
185
182
  artifact: {
186
- artifactId: "report-1",
187
- name: "analysis_report.txt",
188
- parts: [{ kind: "text", text: `This is the analysis for task ${taskId}.` }],
183
+ artifactId: 'report-1',
184
+ name: 'analysis_report.txt',
185
+ parts: [{ kind: 'text', text: `This is the analysis for task ${taskId}.` }],
189
186
  },
190
187
  };
191
188
  eventBus.publish(artifactUpdate);
192
189
 
193
190
  // 3. Publish the final status and mark the event as 'final'.
194
191
  const finalUpdate: TaskStatusUpdateEvent = {
195
- kind: "status-update",
192
+ kind: 'status-update',
196
193
  taskId: taskId,
197
194
  contextId: contextId,
198
- status: { state: "completed", timestamp: new Date().toISOString() },
195
+ status: { state: 'completed', timestamp: new Date().toISOString() },
199
196
  final: true,
200
197
  };
201
198
  eventBus.publish(finalUpdate);
@@ -212,21 +209,28 @@ The client sends a message and receives a `Task` object as the result.
212
209
 
213
210
  ```typescript
214
211
  // client.ts
215
- import { A2AClient, SendMessageSuccessResponse } from "@a2a-js/sdk/client";
216
- import { Message, MessageSendParams, Task } from "@a2a-js/sdk";
212
+ import { A2AClient, SendMessageSuccessResponse } from '@a2a-js/sdk/client';
213
+ import { Message, MessageSendParams, Task } from '@a2a-js/sdk';
217
214
  // ... other imports ...
218
215
 
219
- const client = await A2AClient.fromCardUrl("http://localhost:4000/.well-known/agent-card.json");
216
+ const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json');
220
217
 
221
- const response = await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "Do something." }], kind: "message" } });
218
+ const response = await client.sendMessage({
219
+ message: {
220
+ messageId: uuidv4(),
221
+ role: 'user',
222
+ parts: [{ kind: 'text', text: 'Do something.' }],
223
+ kind: 'message',
224
+ },
225
+ });
222
226
 
223
- if ("error" in response) {
224
- console.error("Error:", response.error.message);
227
+ if ('error' in response) {
228
+ console.error('Error:', response.error.message);
225
229
  } else {
226
230
  const result = (response as SendMessageSuccessResponse).result;
227
231
 
228
232
  // Check if the agent's response is a Task or a direct Message.
229
- if (result.kind === "task") {
233
+ if (result.kind === 'task') {
230
234
  const task = result as Task;
231
235
  console.log(`Task [${task.id}] completed with status: ${task.status.state}`);
232
236
 
@@ -236,49 +240,83 @@ if ("error" in response) {
236
240
  }
237
241
  } else {
238
242
  const message = result as Message;
239
- console.log("Received direct message:", message.parts[0].text);
243
+ console.log('Received direct message:', message.parts[0].text);
240
244
  }
241
245
  }
242
246
  ```
243
247
 
244
- -----
248
+ ---
245
249
 
246
250
  ## Client Customization
247
251
 
248
252
  You can provide a custom `fetch` implementation to the `A2AClient` to modify its HTTP request behavior. Common use cases include:
249
253
 
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.
254
+ - **Request Interception**: Log outgoing requests or collect metrics.
255
+ - **Header Injection**: Add custom headers for authentication, tracing, or routing.
256
+ - **Retry Mechanisms**: Implement custom logic for retrying failed requests.
253
257
 
254
258
  ### Example: Injecting a Custom Header
255
259
 
256
260
  This example creates a `fetch` wrapper that adds a unique `X-Request-ID` to every outgoing request.
257
261
 
258
262
  ```typescript
259
- import { A2AClient } from "@a2a-js/sdk/client";
260
- import { v4 as uuidv4 } from "uuid";
263
+ import { A2AClient } from '@a2a-js/sdk/client';
264
+ import { v4 as uuidv4 } from 'uuid';
261
265
 
262
266
  // 1. Create a wrapper around the global fetch function.
263
267
  const fetchWithCustomHeader: typeof fetch = async (url, init) => {
264
268
  const headers = new Headers(init?.headers);
265
- headers.set("X-Request-ID", uuidv4());
269
+ headers.set('X-Request-ID', uuidv4());
266
270
 
267
271
  const newInit = { ...init, headers };
268
-
269
- console.log(`Sending request to ${url} with X-Request-ID: ${headers.get("X-Request-ID")}`);
270
-
272
+
273
+ console.log(`Sending request to ${url} with X-Request-ID: ${headers.get('X-Request-ID')}`);
274
+
271
275
  return fetch(url, newInit);
272
276
  };
273
277
 
274
278
  // 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
- );
279
+ const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json', {
280
+ fetchImpl: fetchWithCustomHeader,
281
+ });
279
282
 
280
283
  // 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" } });
284
+ await client.sendMessage({
285
+ message: {
286
+ messageId: uuidv4(),
287
+ role: 'user',
288
+ parts: [{ kind: 'text', text: 'A message requiring custom headers.' }],
289
+ kind: 'message',
290
+ },
291
+ });
292
+ ```
293
+
294
+ ### Example: Specifying a Timeout
295
+
296
+ This example creates a `fetch` wrapper that sets a timeout for every outgoing request.
297
+
298
+ ```typescript
299
+ import { A2AClient } from '@a2a-js/sdk/client';
300
+
301
+ // 1. Create a wrapper around the global fetch function.
302
+ const fetchWithTimeout: typeof fetch = async (url, init) => {
303
+ return fetch(url, { ...init, signal: AbortSignal.timeout(5000) });
304
+ };
305
+
306
+ // 2. Provide the custom fetch implementation to the client.
307
+ const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json', {
308
+ fetchImpl: fetchWithTimeout,
309
+ });
310
+
311
+ // Now, all requests made by this client instance will have a configured timeout.
312
+ await client.sendMessage({
313
+ message: {
314
+ messageId: uuidv4(),
315
+ role: 'user',
316
+ parts: [{ kind: 'text', text: 'A message requiring custom headers.' }],
317
+ kind: 'message',
318
+ },
319
+ });
282
320
  ```
283
321
 
284
322
  ### Using the Provided `AuthenticationHandler`
@@ -292,13 +330,13 @@ import {
292
330
  A2AClient,
293
331
  AuthenticationHandler,
294
332
  createAuthenticatingFetchWithRetry,
295
- } from "@a2a-js/sdk/client";
333
+ } from '@a2a-js/sdk/client';
296
334
 
297
335
  // A simple token provider that simulates fetching a new token.
298
336
  const tokenProvider = {
299
- token: "initial-stale-token",
337
+ token: 'initial-stale-token',
300
338
  getNewToken: async () => {
301
- console.log("Refreshing auth token...");
339
+ console.log('Refreshing auth token...');
302
340
  tokenProvider.token = `new-token-${Date.now()}`;
303
341
  return tokenProvider.token;
304
342
  },
@@ -314,7 +352,8 @@ const handler: AuthenticationHandler = {
314
352
  // shouldRetryWithHeaders() is called after a request fails.
315
353
  // It decides if a retry is needed and provides new headers.
316
354
  shouldRetryWithHeaders: async (req: RequestInit, res: Response) => {
317
- if (res.status === 401) { // Unauthorized
355
+ if (res.status === 401) {
356
+ // Unauthorized
318
357
  const newToken = await tokenProvider.getNewToken();
319
358
  // Return new headers to trigger a single retry.
320
359
  return { Authorization: `Bearer ${newToken}` };
@@ -329,13 +368,12 @@ const handler: AuthenticationHandler = {
329
368
  const authFetch = createAuthenticatingFetchWithRetry(fetch, handler);
330
369
 
331
370
  // 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
- );
371
+ const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json', {
372
+ fetchImpl: authFetch,
373
+ });
336
374
  ```
337
375
 
338
- -----
376
+ ---
339
377
 
340
378
  ## Streaming
341
379
 
@@ -350,44 +388,48 @@ The agent publishes events as it works on the task. The client receives these ev
350
388
  // ... imports ...
351
389
 
352
390
  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
- });
391
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
392
+ const { taskId, contextId, userMessage, task } = requestContext;
393
+
394
+ // 1. Create and publish the initial task object if it doesn't exist.
395
+ if (!task) {
396
+ const initialTask: Task = {
397
+ kind: 'task',
398
+ id: taskId,
399
+ contextId: contextId,
400
+ status: {
401
+ state: 'submitted',
402
+ timestamp: new Date().toISOString(),
403
+ },
404
+ history: [userMessage],
405
+ };
406
+ eventBus.publish(initialTask);
407
+ }
366
408
 
367
409
  // 2. Publish 'working' state.
368
410
  eventBus.publish({
369
- kind: "status-update",
411
+ kind: 'status-update',
370
412
  taskId,
371
413
  contextId,
372
- status: { state: "working", timestamp: new Date().toISOString() },
373
- final: false
414
+ status: { state: 'working', timestamp: new Date().toISOString() },
415
+ final: false,
374
416
  });
375
417
 
376
418
  // 3. Simulate work and publish an artifact.
377
- await new Promise(resolve => setTimeout(resolve, 1000));
419
+ await new Promise((resolve) => setTimeout(resolve, 1000));
378
420
  eventBus.publish({
379
- kind: "artifact-update",
421
+ kind: 'artifact-update',
380
422
  taskId,
381
423
  contextId,
382
- artifact: { artifactId: "result.txt", parts: [{ kind: "text", text: "First result." }] },
424
+ artifact: { artifactId: 'result.txt', parts: [{ kind: 'text', text: 'First result.' }] },
383
425
  });
384
426
 
385
427
  // 4. Publish final 'completed' state.
386
428
  eventBus.publish({
387
- kind: "status-update",
429
+ kind: 'status-update',
388
430
  taskId,
389
431
  contextId,
390
- status: { state: "completed", timestamp: new Date().toISOString() },
432
+ status: { state: 'completed', timestamp: new Date().toISOString() },
391
433
  final: true,
392
434
  });
393
435
  eventBus.finished();
@@ -402,20 +444,20 @@ The `sendMessageStream` method returns an `AsyncGenerator` that yields events as
402
444
 
403
445
  ```typescript
404
446
  // client.ts
405
- import { A2AClient } from "@a2a-js/sdk/client";
406
- import { MessageSendParams } from "@a2a-js/sdk";
407
- import { v4 as uuidv4 } from "uuid";
447
+ import { A2AClient } from '@a2a-js/sdk/client';
448
+ import { MessageSendParams } from '@a2a-js/sdk';
449
+ import { v4 as uuidv4 } from 'uuid';
408
450
  // ... other imports ...
409
451
 
410
- const client = await A2AClient.fromCardUrl("http://localhost:4000/.well-known/agent-card.json");
452
+ const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json');
411
453
 
412
454
  async function streamTask() {
413
455
  const streamParams: MessageSendParams = {
414
456
  message: {
415
457
  messageId: uuidv4(),
416
- role: "user",
417
- parts: [{ kind: "text", text: "Stream me some updates!" }],
418
- kind: "message",
458
+ role: 'user',
459
+ parts: [{ kind: 'text', text: 'Stream me some updates!' }],
460
+ kind: 'message',
419
461
  },
420
462
  };
421
463
 
@@ -423,17 +465,17 @@ async function streamTask() {
423
465
  const stream = client.sendMessageStream(streamParams);
424
466
 
425
467
  for await (const event of stream) {
426
- if (event.kind === "task") {
468
+ if (event.kind === 'task') {
427
469
  console.log(`[${event.id}] Task created. Status: ${event.status.state}`);
428
- } else if (event.kind === "status-update") {
470
+ } else if (event.kind === 'status-update') {
429
471
  console.log(`[${event.taskId}] Status Updated: ${event.status.state}`);
430
- } else if (event.kind === "artifact-update") {
472
+ } else if (event.kind === 'artifact-update') {
431
473
  console.log(`[${event.taskId}] Artifact Received: ${event.artifact.artifactId}`);
432
474
  }
433
475
  }
434
- console.log("--- Stream finished ---");
476
+ console.log('--- Stream finished ---');
435
477
  } catch (error) {
436
- console.error("Error during streaming:", error);
478
+ console.error('Error during streaming:', error);
437
479
  }
438
480
  }
439
481
 
@@ -457,7 +499,7 @@ import {
457
499
  RequestContext,
458
500
  ExecutionEventBus,
459
501
  TaskStatusUpdateEvent,
460
- } from "@a2a-js/sdk/server";
502
+ } from '@a2a-js/sdk/server';
461
503
  // ... other imports ...
462
504
 
463
505
  class CancellableExecutor implements AgentExecutor {
@@ -468,26 +510,20 @@ class CancellableExecutor implements AgentExecutor {
468
510
  * When a cancellation is requested, add the taskId to our tracking set.
469
511
  * The `execute` loop will handle the rest.
470
512
  */
471
- public async cancelTask(
472
- taskId: string,
473
- eventBus: ExecutionEventBus,
474
- ): Promise<void> {
513
+ public async cancelTask(taskId: string, eventBus: ExecutionEventBus): Promise<void> {
475
514
  console.log(`[Executor] Received cancellation request for task: ${taskId}`);
476
515
  this.cancelledTasks.add(taskId);
477
516
  }
478
517
 
479
- public async execute(
480
- requestContext: RequestContext,
481
- eventBus: ExecutionEventBus,
482
- ): Promise<void> {
518
+ public async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
483
519
  const { taskId, contextId } = requestContext;
484
520
 
485
521
  // Start the task
486
522
  eventBus.publish({
487
- kind: "status-update",
523
+ kind: 'status-update',
488
524
  taskId,
489
525
  contextId,
490
- status: { state: "working", timestamp: new Date().toISOString() },
526
+ status: { state: 'working', timestamp: new Date().toISOString() },
491
527
  final: false,
492
528
  });
493
529
 
@@ -497,18 +533,18 @@ class CancellableExecutor implements AgentExecutor {
497
533
  // Before each step, check if the task has been canceled.
498
534
  if (this.cancelledTasks.has(taskId)) {
499
535
  console.log(`[Executor] Aborting task ${taskId} due to cancellation.`);
500
-
536
+
501
537
  // Publish the final 'canceled' status.
502
538
  const cancelledUpdate: TaskStatusUpdateEvent = {
503
- kind: "status-update",
539
+ kind: 'status-update',
504
540
  taskId: taskId,
505
541
  contextId: contextId,
506
- status: { state: "canceled", timestamp: new Date().toISOString() },
542
+ status: { state: 'canceled', timestamp: new Date().toISOString() },
507
543
  final: true,
508
544
  };
509
545
  eventBus.publish(cancelledUpdate);
510
546
  eventBus.finished();
511
-
547
+
512
548
  // Clean up and exit.
513
549
  this.cancelledTasks.delete(taskId);
514
550
  return;
@@ -516,17 +552,17 @@ class CancellableExecutor implements AgentExecutor {
516
552
 
517
553
  // Simulate one step of work.
518
554
  console.log(`[Executor] Working on step ${i + 1} for task ${taskId}...`);
519
- await new Promise(resolve => setTimeout(resolve, 1000));
555
+ await new Promise((resolve) => setTimeout(resolve, 1000));
520
556
  }
521
557
 
522
558
  console.log(`[Executor] Task ${taskId} finished all steps without cancellation.`);
523
559
 
524
560
  // If not canceled, finish the work and publish the completed state.
525
561
  const finalUpdate: TaskStatusUpdateEvent = {
526
- kind: "status-update",
562
+ kind: 'status-update',
527
563
  taskId,
528
564
  contextId,
529
- status: { state: "completed", timestamp: new Date().toISOString() },
565
+ status: { state: 'completed', timestamp: new Date().toISOString() },
530
566
  final: true,
531
567
  };
532
568
  eventBus.publish(finalUpdate);
@@ -535,6 +571,104 @@ class CancellableExecutor implements AgentExecutor {
535
571
  }
536
572
  ```
537
573
 
574
+ ## A2A Push Notifications
575
+
576
+ For very long-running tasks (e.g., lasting minutes, hours, or even days) or when clients cannot or prefer not to maintain persistent connections (like mobile clients or serverless functions), A2A supports asynchronous updates via push notifications. This mechanism allows the A2A Server to actively notify a client-provided webhook when a significant task update occurs.
577
+
578
+ ### Server-Side Configuration
579
+
580
+ To enable push notifications, your agent card must declare support:
581
+
582
+ ```typescript
583
+ const movieAgentCard: AgentCard = {
584
+ // ... other properties
585
+ capabilities: {
586
+ streaming: true,
587
+ pushNotifications: true, // Enable push notifications
588
+ stateTransitionHistory: true,
589
+ },
590
+ // ... rest of agent card
591
+ };
592
+ ```
593
+
594
+ When creating the `DefaultRequestHandler`, you can optionally provide custom push notification components:
595
+
596
+ ```typescript
597
+ import {
598
+ DefaultRequestHandler,
599
+ InMemoryPushNotificationStore,
600
+ DefaultPushNotificationSender,
601
+ } from '@a2a-js/sdk/server';
602
+
603
+ // Optional: Custom push notification store and sender
604
+ const pushNotificationStore = new InMemoryPushNotificationStore();
605
+ const pushNotificationSender = new DefaultPushNotificationSender(pushNotificationStore, {
606
+ timeout: 5000, // 5 second timeout
607
+ tokenHeaderName: 'X-A2A-Notification-Token', // Custom header name
608
+ });
609
+
610
+ const requestHandler = new DefaultRequestHandler(
611
+ movieAgentCard,
612
+ taskStore,
613
+ agentExecutor,
614
+ undefined, // eventBusManager (optional)
615
+ pushNotificationStore, // custom store
616
+ pushNotificationSender, // custom sender
617
+ undefined // extendedAgentCard (optional)
618
+ );
619
+ ```
620
+
621
+ ### Client-Side Usage
622
+
623
+ Configure push notifications when sending messages:
624
+
625
+ ```typescript
626
+ // Configure push notification for a message
627
+ const pushConfig: PushNotificationConfig = {
628
+ id: 'my-notification-config', // Optional, defaults to task ID
629
+ url: 'https://my-app.com/webhook/task-updates',
630
+ token: 'your-auth-token', // Optional authentication token
631
+ };
632
+
633
+ const sendParams: MessageSendParams = {
634
+ message: {
635
+ messageId: uuidv4(),
636
+ role: 'user',
637
+ parts: [{ kind: 'text', text: 'Hello, agent!' }],
638
+ kind: 'message',
639
+ },
640
+ configuration: {
641
+ blocking: true,
642
+ acceptedOutputModes: ['text/plain'],
643
+ pushNotificationConfig: pushConfig, // Add push notification config
644
+ },
645
+ };
646
+ ```
647
+
648
+ ### Webhook Endpoint Implementation
649
+
650
+ Your webhook endpoint should expect POST requests with the task data:
651
+
652
+ ```typescript
653
+ // Example Express.js webhook endpoint
654
+ app.post('/webhook/task-updates', (req, res) => {
655
+ const task = req.body; // The complete task object
656
+
657
+ // Verify the token if provided
658
+ const token = req.headers['x-a2a-notification-token'];
659
+ if (token !== 'your-auth-token') {
660
+ return res.status(401).json({ error: 'Unauthorized' });
661
+ }
662
+
663
+ console.log(`Task ${task.id} status: ${task.status.state}`);
664
+
665
+ // Process the task update
666
+ // ...
667
+
668
+ res.status(200).json({ received: true });
669
+ });
670
+ ```
671
+
538
672
  ## License
539
673
 
540
674
  This project is licensed under the terms of the [Apache 2.0 License](https://raw.githubusercontent.com/google-a2a/a2a-python/refs/heads/main/LICENSE).