@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 +272 -138
- package/dist/a2a_request_handler-DPkhsCMt.d.mts +37 -0
- package/dist/a2a_request_handler-DQfg1Q-R.d.ts +37 -0
- package/dist/auth-handler-DVLcl8yj.d.mts +209 -0
- package/dist/auth-handler-Gzpf3xHC.d.ts +209 -0
- package/dist/chunk-HZFUOBJQ.mjs +198 -0
- package/dist/chunk-LIEYEFQG.mjs +879 -0
- package/dist/chunk-LVD4GF26.mjs +262 -0
- package/dist/chunk-PHP7LM4Y.mjs +8 -0
- package/dist/chunk-UBRSFN2J.mjs +776 -0
- package/dist/client/index.d.mts +312 -0
- package/dist/client/index.d.ts +312 -0
- package/dist/client/index.js +1158 -0
- package/dist/client/index.mjs +367 -0
- package/dist/error-DExKs0Q3.d.mts +233 -0
- package/dist/error-j1vYKII2.d.ts +233 -0
- package/dist/index.d.mts +14 -2739
- package/dist/index.d.ts +14 -2739
- package/dist/index.js +1605 -1158
- package/dist/index.mjs +29 -1612
- package/dist/server/express/index.d.mts +25 -0
- package/dist/server/express/index.d.ts +25 -0
- package/dist/server/express/index.js +468 -0
- package/dist/server/express/index.mjs +10 -0
- package/dist/server/index.d.mts +26 -0
- package/dist/server/index.d.ts +26 -0
- package/dist/server/index.js +1173 -0
- package/dist/server/index.mjs +32 -0
- package/dist/types-Due_Cv6t.d.mts +2550 -0
- package/dist/types-Due_Cv6t.d.ts +2550 -0
- package/package.json +18 -11
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
|
|
47
|
-
import { v4 as uuidv4 } from
|
|
48
|
-
import type { AgentCard, Message } from
|
|
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
|
|
56
|
-
import { A2AExpressApp } from
|
|
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:
|
|
61
|
-
description:
|
|
62
|
-
protocolVersion:
|
|
63
|
-
version:
|
|
64
|
-
url:
|
|
65
|
-
skills: [
|
|
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:
|
|
74
|
+
kind: 'message',
|
|
78
75
|
messageId: uuidv4(),
|
|
79
|
-
role:
|
|
80
|
-
parts: [{ kind:
|
|
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
|
|
117
|
-
import { Message, MessageSendParams } from
|
|
118
|
-
import { v4 as uuidv4 } from
|
|
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(
|
|
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:
|
|
128
|
-
parts: [{ kind:
|
|
129
|
-
kind:
|
|
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 (
|
|
136
|
-
console.error(
|
|
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(
|
|
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
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
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:
|
|
179
|
+
kind: 'artifact-update',
|
|
183
180
|
taskId: taskId,
|
|
184
181
|
contextId: contextId,
|
|
185
182
|
artifact: {
|
|
186
|
-
artifactId:
|
|
187
|
-
name:
|
|
188
|
-
parts: [{ kind:
|
|
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:
|
|
192
|
+
kind: 'status-update',
|
|
196
193
|
taskId: taskId,
|
|
197
194
|
contextId: contextId,
|
|
198
|
-
status: { state:
|
|
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
|
|
216
|
-
import { Message, MessageSendParams, Task } from
|
|
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(
|
|
216
|
+
const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json');
|
|
220
217
|
|
|
221
|
-
const response = await client.sendMessage({
|
|
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 (
|
|
224
|
-
console.error(
|
|
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 ===
|
|
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(
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
260
|
-
import { v4 as uuidv4 } from
|
|
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(
|
|
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(
|
|
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
|
-
|
|
277
|
-
|
|
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({
|
|
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
|
|
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:
|
|
337
|
+
token: 'initial-stale-token',
|
|
300
338
|
getNewToken: async () => {
|
|
301
|
-
console.log(
|
|
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) {
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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:
|
|
411
|
+
kind: 'status-update',
|
|
370
412
|
taskId,
|
|
371
413
|
contextId,
|
|
372
|
-
status: { state:
|
|
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:
|
|
421
|
+
kind: 'artifact-update',
|
|
380
422
|
taskId,
|
|
381
423
|
contextId,
|
|
382
|
-
artifact: { artifactId:
|
|
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:
|
|
429
|
+
kind: 'status-update',
|
|
388
430
|
taskId,
|
|
389
431
|
contextId,
|
|
390
|
-
status: { state:
|
|
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
|
|
406
|
-
import { MessageSendParams } from
|
|
407
|
-
import { v4 as uuidv4 } from
|
|
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(
|
|
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:
|
|
417
|
-
parts: [{ kind:
|
|
418
|
-
kind:
|
|
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 ===
|
|
468
|
+
if (event.kind === 'task') {
|
|
427
469
|
console.log(`[${event.id}] Task created. Status: ${event.status.state}`);
|
|
428
|
-
} else if (event.kind ===
|
|
470
|
+
} else if (event.kind === 'status-update') {
|
|
429
471
|
console.log(`[${event.taskId}] Status Updated: ${event.status.state}`);
|
|
430
|
-
} else if (event.kind ===
|
|
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(
|
|
476
|
+
console.log('--- Stream finished ---');
|
|
435
477
|
} catch (error) {
|
|
436
|
-
console.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
|
|
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:
|
|
523
|
+
kind: 'status-update',
|
|
488
524
|
taskId,
|
|
489
525
|
contextId,
|
|
490
|
-
status: { state:
|
|
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:
|
|
539
|
+
kind: 'status-update',
|
|
504
540
|
taskId: taskId,
|
|
505
541
|
contextId: contextId,
|
|
506
|
-
status: { state:
|
|
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:
|
|
562
|
+
kind: 'status-update',
|
|
527
563
|
taskId,
|
|
528
564
|
contextId,
|
|
529
|
-
status: { state:
|
|
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).
|