@mailmodo/a2a 0.3.3

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 ADDED
@@ -0,0 +1,544 @@
1
+ # A2A JavaScript SDK
2
+
3
+ [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
4
+
5
+ <!-- markdownlint-disable no-inline-html -->
6
+
7
+ <html>
8
+ <h2 align="center">
9
+ <img src="https://raw.githubusercontent.com/google-a2a/A2A/refs/heads/main/docs/assets/a2a-logo-black.svg" width="256" alt="A2A Logo"/>
10
+ </h2>
11
+ <h3 align="center">A JavaScript library that helps run agentic applications as A2AServers following the <a href="https://google-a2a.github.io/A2A">Agent2Agent (A2A) Protocol</a>.</h3>
12
+ </html>
13
+
14
+ <!-- markdownlint-enable no-inline-html -->
15
+
16
+ ## Installation
17
+
18
+ You can install the A2A SDK using `npm`.
19
+
20
+ ```bash
21
+ npm install @a2a-js/sdk
22
+ ```
23
+
24
+ ### For Server Usage
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:
27
+
28
+ ```bash
29
+ npm install express
30
+ ```
31
+
32
+ You can also find JavaScript samples [here](https://github.com/google-a2a/a2a-samples/tree/main/samples/js).
33
+
34
+ -----
35
+
36
+ ## Quickstart
37
+
38
+ This example shows how to create a simple "Hello World" agent server and a client to interact with it.
39
+
40
+ ### Server: Hello World Agent
41
+
42
+ The core of an A2A server is the `AgentExecutor`, which contains your agent's logic.
43
+
44
+ ```typescript
45
+ // server.ts
46
+ import express from "express";
47
+ import { v4 as uuidv4 } from "uuid";
48
+ import type { AgentCard, Message } from "@a2a-js/sdk";
49
+ import {
50
+ AgentExecutor,
51
+ RequestContext,
52
+ ExecutionEventBus,
53
+ DefaultRequestHandler,
54
+ InMemoryTaskStore,
55
+ } from "@a2a-js/sdk/server";
56
+ import { A2AExpressApp } from "@a2a-js/sdk/server/express";
57
+
58
+ // 1. Define your agent's identity card.
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 ---
67
+ };
68
+
69
+ // 2. Implement the agent's logic.
70
+ class HelloExecutor implements AgentExecutor {
71
+ async execute(
72
+ requestContext: RequestContext,
73
+ eventBus: ExecutionEventBus
74
+ ): Promise<void> {
75
+ // Create a direct message response.
76
+ const responseMessage: Message = {
77
+ kind: "message",
78
+ messageId: uuidv4(),
79
+ role: "agent",
80
+ parts: [{ kind: "text", text: "Hello, world!" }],
81
+ // Associate the response with the incoming request's context.
82
+ contextId: requestContext.contextId,
83
+ };
84
+
85
+ // Publish the message and signal that the interaction is finished.
86
+ eventBus.publish(responseMessage);
87
+ eventBus.finished();
88
+ }
89
+
90
+ // cancelTask is not needed for this simple, non-stateful agent.
91
+ cancelTask = async (): Promise<void> => {};
92
+ }
93
+
94
+ // 3. Set up and run the server.
95
+ const agentExecutor = new HelloExecutor();
96
+ const requestHandler = new DefaultRequestHandler(
97
+ helloAgentCard,
98
+ new InMemoryTaskStore(),
99
+ agentExecutor
100
+ );
101
+
102
+ const appBuilder = new A2AExpressApp(requestHandler);
103
+ const expressApp = appBuilder.setupRoutes(express());
104
+
105
+ expressApp.listen(4000, () => {
106
+ console.log(`🚀 Server started on http://localhost:4000`);
107
+ });
108
+ ```
109
+
110
+ ### Client: Sending a Message
111
+
112
+ The `A2AClient` makes it easy to communicate with any A2A-compliant agent.
113
+
114
+ ```typescript
115
+ // 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";
119
+
120
+ 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");
123
+
124
+ const sendParams: MessageSendParams = {
125
+ message: {
126
+ messageId: uuidv4(),
127
+ role: "user",
128
+ parts: [{ kind: "text", text: "Hi there!" }],
129
+ kind: "message",
130
+ },
131
+ };
132
+
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!"
140
+ }
141
+ }
142
+
143
+ await run();
144
+ ```
145
+
146
+ -----
147
+
148
+ ## A2A `Task` Support
149
+
150
+ For operations that are stateful or long-running, agents create a `Task`. A task has a state (e.g., `working`, `completed`) and can produce `Artifacts` (e.g., files, data).
151
+
152
+ ### Server: Creating a Task
153
+
154
+ This agent creates a task, attaches a file artifact to it, and marks it as complete.
155
+
156
+ ```typescript
157
+ // server.ts
158
+ import { Task, TaskArtifactUpdateEvent, TaskStatusUpdateEvent } from "@a2a-js/sdk";
159
+ // ... other imports from the quickstart server ...
160
+
161
+ 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);
179
+
180
+ // 2. Create and publish an artifact.
181
+ const artifactUpdate: TaskArtifactUpdateEvent = {
182
+ kind: "artifact-update",
183
+ taskId: taskId,
184
+ contextId: contextId,
185
+ artifact: {
186
+ artifactId: "report-1",
187
+ name: "analysis_report.txt",
188
+ parts: [{ kind: "text", text: `This is the analysis for task ${taskId}.` }],
189
+ },
190
+ };
191
+ eventBus.publish(artifactUpdate);
192
+
193
+ // 3. Publish the final status and mark the event as 'final'.
194
+ const finalUpdate: TaskStatusUpdateEvent = {
195
+ kind: "status-update",
196
+ taskId: taskId,
197
+ contextId: contextId,
198
+ status: { state: "completed", timestamp: new Date().toISOString() },
199
+ final: true,
200
+ };
201
+ eventBus.publish(finalUpdate);
202
+ eventBus.finished();
203
+ }
204
+
205
+ cancelTask = async (): Promise<void> => {};
206
+ }
207
+ ```
208
+
209
+ ### Client: Receiving a Task
210
+
211
+ The client sends a message and receives a `Task` object as the result.
212
+
213
+ ```typescript
214
+ // client.ts
215
+ import { A2AClient, SendMessageSuccessResponse } from "@a2a-js/sdk/client";
216
+ import { Message, MessageSendParams, Task } from "@a2a-js/sdk";
217
+ // ... other imports ...
218
+
219
+ const client = await A2AClient.fromCardUrl("http://localhost:4000/.well-known/agent-card.json");
220
+
221
+ const response = await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "Do something." }], kind: "message" } });
222
+
223
+ if ("error" in response) {
224
+ console.error("Error:", response.error.message);
225
+ } else {
226
+ const result = (response as SendMessageSuccessResponse).result;
227
+
228
+ // Check if the agent's response is a Task or a direct Message.
229
+ if (result.kind === "task") {
230
+ const task = result as Task;
231
+ console.log(`Task [${task.id}] completed with status: ${task.status.state}`);
232
+
233
+ if (task.artifacts && task.artifacts.length > 0) {
234
+ console.log(`Artifact found: ${task.artifacts[0].name}`);
235
+ console.log(`Content: ${task.artifacts[0].parts[0].text}`);
236
+ }
237
+ } else {
238
+ const message = result as Message;
239
+ console.log("Received direct message:", message.parts[0].text);
240
+ }
241
+ }
242
+ ```
243
+
244
+ -----
245
+
246
+ ## Client Customization
247
+
248
+ You can provide a custom `fetch` implementation to the `A2AClient` to modify its HTTP request behavior. Common use cases include:
249
+
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.
253
+
254
+ ### Example: Injecting a Custom Header
255
+
256
+ This example creates a `fetch` wrapper that adds a unique `X-Request-ID` to every outgoing request.
257
+
258
+ ```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
+ };
273
+
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
+ );
279
+
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" } });
282
+ ```
283
+
284
+ ### Using the Provided `AuthenticationHandler`
285
+
286
+ 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
+
288
+ Here's how to use it to manage a Bearer token:
289
+
290
+ ```typescript
291
+ import {
292
+ A2AClient,
293
+ AuthenticationHandler,
294
+ createAuthenticatingFetchWithRetry,
295
+ } from "@a2a-js/sdk/client";
296
+
297
+ // A simple token provider that simulates fetching a new token.
298
+ const tokenProvider = {
299
+ token: "initial-stale-token",
300
+ getNewToken: async () => {
301
+ console.log("Refreshing auth token...");
302
+ tokenProvider.token = `new-token-${Date.now()}`;
303
+ return tokenProvider.token;
304
+ },
305
+ };
306
+
307
+ // 1. Implement the AuthenticationHandler interface.
308
+ const handler: AuthenticationHandler = {
309
+ // headers() is called on every request to get the current auth headers.
310
+ headers: async () => ({
311
+ Authorization: `Bearer ${tokenProvider.token}`,
312
+ }),
313
+
314
+ // shouldRetryWithHeaders() is called after a request fails.
315
+ // It decides if a retry is needed and provides new headers.
316
+ shouldRetryWithHeaders: async (req: RequestInit, res: Response) => {
317
+ if (res.status === 401) { // Unauthorized
318
+ const newToken = await tokenProvider.getNewToken();
319
+ // Return new headers to trigger a single retry.
320
+ return { Authorization: `Bearer ${newToken}` };
321
+ }
322
+
323
+ // Return undefined to not retry for other errors.
324
+ return undefined;
325
+ },
326
+ };
327
+
328
+ // 2. Create the authenticated fetch function.
329
+ const authFetch = createAuthenticatingFetchWithRetry(fetch, handler);
330
+
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
+ );
336
+ ```
337
+
338
+ -----
339
+
340
+ ## Streaming
341
+
342
+ For real-time updates, A2A supports streaming responses over Server-Sent Events (SSE).
343
+
344
+ ### Server: Streaming Task Updates
345
+
346
+ The agent publishes events as it works on the task. The client receives these events in real-time.
347
+
348
+ ```typescript
349
+ // server.ts
350
+ // ... imports ...
351
+
352
+ 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
+ });
366
+
367
+ // 2. Publish 'working' state.
368
+ eventBus.publish({
369
+ kind: "status-update",
370
+ taskId,
371
+ contextId,
372
+ status: { state: "working", timestamp: new Date().toISOString() },
373
+ final: false
374
+ });
375
+
376
+ // 3. Simulate work and publish an artifact.
377
+ await new Promise(resolve => setTimeout(resolve, 1000));
378
+ eventBus.publish({
379
+ kind: "artifact-update",
380
+ taskId,
381
+ contextId,
382
+ artifact: { artifactId: "result.txt", parts: [{ kind: "text", text: "First result." }] },
383
+ });
384
+
385
+ // 4. Publish final 'completed' state.
386
+ eventBus.publish({
387
+ kind: "status-update",
388
+ taskId,
389
+ contextId,
390
+ status: { state: "completed", timestamp: new Date().toISOString() },
391
+ final: true,
392
+ });
393
+ eventBus.finished();
394
+ }
395
+ cancelTask = async (): Promise<void> => {};
396
+ }
397
+ ```
398
+
399
+ ### Client: Consuming a Stream
400
+
401
+ The `sendMessageStream` method returns an `AsyncGenerator` that yields events as they arrive from the server.
402
+
403
+ ```typescript
404
+ // client.ts
405
+ import { A2AClient } from "@a2a-js/sdk/client";
406
+ import { MessageSendParams } from "@a2a-js/sdk";
407
+ import { v4 as uuidv4 } from "uuid";
408
+ // ... other imports ...
409
+
410
+ const client = await A2AClient.fromCardUrl("http://localhost:4000/.well-known/agent-card.json");
411
+
412
+ async function streamTask() {
413
+ const streamParams: MessageSendParams = {
414
+ message: {
415
+ messageId: uuidv4(),
416
+ role: "user",
417
+ parts: [{ kind: "text", text: "Stream me some updates!" }],
418
+ kind: "message",
419
+ },
420
+ };
421
+
422
+ try {
423
+ const stream = client.sendMessageStream(streamParams);
424
+
425
+ for await (const event of stream) {
426
+ if (event.kind === "task") {
427
+ console.log(`[${event.id}] Task created. Status: ${event.status.state}`);
428
+ } else if (event.kind === "status-update") {
429
+ console.log(`[${event.taskId}] Status Updated: ${event.status.state}`);
430
+ } else if (event.kind === "artifact-update") {
431
+ console.log(`[${event.taskId}] Artifact Received: ${event.artifact.artifactId}`);
432
+ }
433
+ }
434
+ console.log("--- Stream finished ---");
435
+ } catch (error) {
436
+ console.error("Error during streaming:", error);
437
+ }
438
+ }
439
+
440
+ await streamTask();
441
+ ```
442
+
443
+ ## Handling Task Cancellation
444
+
445
+ To support user-initiated cancellations, you must implement the `cancelTask` method in your **`AgentExecutor`**. The executor is responsible for gracefully stopping the ongoing work and publishing a final `canceled` status event.
446
+
447
+ A straightforward way to manage this is by maintaining an in-memory set of canceled task IDs. The `execute` method can then periodically check this set to see if it should terminate its process.
448
+
449
+ ### Server: Implementing a Cancellable Executor
450
+
451
+ This example demonstrates an agent that simulates a multi-step process. In each step of its work, it checks if a cancellation has been requested. If so, it stops the work and updates the task's state accordingly.
452
+
453
+ ```typescript
454
+ // server.ts
455
+ import {
456
+ AgentExecutor,
457
+ RequestContext,
458
+ ExecutionEventBus,
459
+ TaskStatusUpdateEvent,
460
+ } from "@a2a-js/sdk/server";
461
+ // ... other imports ...
462
+
463
+ class CancellableExecutor implements AgentExecutor {
464
+ // Use a Set to track the IDs of tasks that have been requested to be canceled.
465
+ private cancelledTasks = new Set<string>();
466
+
467
+ /**
468
+ * When a cancellation is requested, add the taskId to our tracking set.
469
+ * The `execute` loop will handle the rest.
470
+ */
471
+ public async cancelTask(
472
+ taskId: string,
473
+ eventBus: ExecutionEventBus,
474
+ ): Promise<void> {
475
+ console.log(`[Executor] Received cancellation request for task: ${taskId}`);
476
+ this.cancelledTasks.add(taskId);
477
+ }
478
+
479
+ public async execute(
480
+ requestContext: RequestContext,
481
+ eventBus: ExecutionEventBus,
482
+ ): Promise<void> {
483
+ const { taskId, contextId } = requestContext;
484
+
485
+ // Start the task
486
+ eventBus.publish({
487
+ kind: "status-update",
488
+ taskId,
489
+ contextId,
490
+ status: { state: "working", timestamp: new Date().toISOString() },
491
+ final: false,
492
+ });
493
+
494
+ // Simulate a multi-step, long-running process
495
+ for (let i = 0; i < 5; i++) {
496
+ // **Cancellation Checkpoint**
497
+ // Before each step, check if the task has been canceled.
498
+ if (this.cancelledTasks.has(taskId)) {
499
+ console.log(`[Executor] Aborting task ${taskId} due to cancellation.`);
500
+
501
+ // Publish the final 'canceled' status.
502
+ const cancelledUpdate: TaskStatusUpdateEvent = {
503
+ kind: "status-update",
504
+ taskId: taskId,
505
+ contextId: contextId,
506
+ status: { state: "canceled", timestamp: new Date().toISOString() },
507
+ final: true,
508
+ };
509
+ eventBus.publish(cancelledUpdate);
510
+ eventBus.finished();
511
+
512
+ // Clean up and exit.
513
+ this.cancelledTasks.delete(taskId);
514
+ return;
515
+ }
516
+
517
+ // Simulate one step of work.
518
+ console.log(`[Executor] Working on step ${i + 1} for task ${taskId}...`);
519
+ await new Promise(resolve => setTimeout(resolve, 1000));
520
+ }
521
+
522
+ console.log(`[Executor] Task ${taskId} finished all steps without cancellation.`);
523
+
524
+ // If not canceled, finish the work and publish the completed state.
525
+ const finalUpdate: TaskStatusUpdateEvent = {
526
+ kind: "status-update",
527
+ taskId,
528
+ contextId,
529
+ status: { state: "completed", timestamp: new Date().toISOString() },
530
+ final: true,
531
+ };
532
+ eventBus.publish(finalUpdate);
533
+ eventBus.finished();
534
+ }
535
+ }
536
+ ```
537
+
538
+ ## License
539
+
540
+ 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).
541
+
542
+ ## Contributing
543
+
544
+ See [CONTRIBUTING.md](https://github.com/google-a2a/a2a-js/blob/main/CONTRIBUTING.md) for contribution guidelines.