@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 +258 -188
- package/dist/a2a_request_handler-B3LxMq3P.d.cts +49 -0
- package/dist/a2a_request_handler-BuP9LgXH.d.ts +49 -0
- package/dist/chunk-3QDLXHKS.js +8 -0
- package/dist/{chunk-JA52GYRU.js → chunk-LTPINR5K.js} +115 -61
- package/dist/chunk-ZX6KNMCP.js +38 -0
- package/dist/client/index.cjs +1057 -256
- package/dist/client/index.d.cts +419 -48
- package/dist/client/index.d.ts +419 -48
- package/dist/client/index.js +1015 -257
- package/dist/{types-DNKcmF0f.d.cts → extensions-DvruCIzw.d.cts} +28 -1
- package/dist/{types-DNKcmF0f.d.ts → extensions-DvruCIzw.d.ts} +28 -1
- package/dist/index.cjs +42 -2
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +9 -3
- package/dist/server/express/index.cjs +790 -141
- package/dist/server/express/index.d.cts +84 -7
- package/dist/server/express/index.d.ts +84 -7
- package/dist/server/express/index.js +651 -82
- package/dist/server/index.cjs +368 -145
- package/dist/server/index.d.cts +36 -26
- package/dist/server/index.d.ts +36 -26
- package/dist/server/index.js +227 -86
- package/package.json +17 -9
- package/dist/a2a_request_handler-B5t-IxgA.d.ts +0 -17
- package/dist/a2a_request_handler-DUvKWfix.d.cts +0 -17
- package/dist/chunk-67JNQ6TZ.js +0 -6
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
|
|
47
|
-
import { v4 as uuidv4 } from
|
|
48
|
-
import
|
|
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
|
|
56
|
-
import {
|
|
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:
|
|
61
|
-
description:
|
|
62
|
-
protocolVersion:
|
|
63
|
-
version:
|
|
64
|
-
url:
|
|
65
|
-
skills: [
|
|
66
|
-
|
|
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:
|
|
82
|
+
kind: 'message',
|
|
78
83
|
messageId: uuidv4(),
|
|
79
|
-
role:
|
|
80
|
-
parts: [{ kind:
|
|
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
|
|
103
|
-
const expressApp = appBuilder.setupRoutes(express());
|
|
107
|
+
const app = express();
|
|
104
108
|
|
|
105
|
-
|
|
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 `
|
|
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 {
|
|
117
|
-
import { Message, MessageSendParams } from
|
|
118
|
-
import { v4 as uuidv4 } from
|
|
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
|
-
|
|
122
|
-
|
|
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:
|
|
128
|
-
parts: [{ kind:
|
|
129
|
-
kind:
|
|
138
|
+
role: 'user',
|
|
139
|
+
parts: [{ kind: 'text', text: 'Hi there!' }],
|
|
140
|
+
kind: 'message',
|
|
130
141
|
},
|
|
131
142
|
};
|
|
132
143
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
console.
|
|
137
|
-
}
|
|
138
|
-
|
|
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
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
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:
|
|
192
|
+
kind: 'artifact-update',
|
|
183
193
|
taskId: taskId,
|
|
184
194
|
contextId: contextId,
|
|
185
195
|
artifact: {
|
|
186
|
-
artifactId:
|
|
187
|
-
name:
|
|
188
|
-
parts: [{ kind:
|
|
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:
|
|
205
|
+
kind: 'status-update',
|
|
196
206
|
taskId: taskId,
|
|
197
207
|
contextId: contextId,
|
|
198
|
-
status: { state:
|
|
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 {
|
|
216
|
-
import { Message, MessageSendParams, Task } from
|
|
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
|
|
229
|
+
const factory = new ClientFactory();
|
|
220
230
|
|
|
221
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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 ===
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
277
|
+
This example defines a `CallInterceptor` to update `serviceParameters` which are passed as HTTP headers.
|
|
257
278
|
|
|
258
279
|
```typescript
|
|
259
|
-
import {
|
|
260
|
-
import {
|
|
261
|
-
|
|
262
|
-
// 1.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
281
|
-
await client.sendMessage({
|
|
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
|
-
###
|
|
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
|
-
|
|
356
|
+
ClientFactory,
|
|
357
|
+
ClientFactoryOptions
|
|
358
|
+
JsonRpcTransportFactory,
|
|
293
359
|
AuthenticationHandler,
|
|
294
360
|
createAuthenticatingFetchWithRetry,
|
|
295
|
-
} from
|
|
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:
|
|
365
|
+
token: 'initial-stale-token',
|
|
300
366
|
getNewToken: async () => {
|
|
301
|
-
console.log(
|
|
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) {
|
|
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.
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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:
|
|
444
|
+
kind: 'status-update',
|
|
370
445
|
taskId,
|
|
371
446
|
contextId,
|
|
372
|
-
status: { state:
|
|
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:
|
|
454
|
+
kind: 'artifact-update',
|
|
380
455
|
taskId,
|
|
381
456
|
contextId,
|
|
382
|
-
artifact: { artifactId:
|
|
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:
|
|
462
|
+
kind: 'status-update',
|
|
388
463
|
taskId,
|
|
389
464
|
contextId,
|
|
390
|
-
status: { state:
|
|
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 {
|
|
406
|
-
import { MessageSendParams } from
|
|
407
|
-
import { v4 as uuidv4 } from
|
|
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
|
|
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:
|
|
417
|
-
parts: [{ kind:
|
|
418
|
-
kind:
|
|
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 ===
|
|
505
|
+
if (event.kind === 'task') {
|
|
427
506
|
console.log(`[${event.id}] Task created. Status: ${event.status.state}`);
|
|
428
|
-
} else if (event.kind ===
|
|
507
|
+
} else if (event.kind === 'status-update') {
|
|
429
508
|
console.log(`[${event.taskId}] Status Updated: ${event.status.state}`);
|
|
430
|
-
} else if (event.kind ===
|
|
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(
|
|
513
|
+
console.log('--- Stream finished ---');
|
|
435
514
|
} catch (error) {
|
|
436
|
-
console.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
|
|
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:
|
|
560
|
+
kind: 'status-update',
|
|
488
561
|
taskId,
|
|
489
562
|
contextId,
|
|
490
|
-
status: { state:
|
|
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:
|
|
576
|
+
kind: 'status-update',
|
|
504
577
|
taskId: taskId,
|
|
505
578
|
contextId: contextId,
|
|
506
|
-
status: { state:
|
|
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:
|
|
599
|
+
kind: 'status-update',
|
|
527
600
|
taskId,
|
|
528
601
|
contextId,
|
|
529
|
-
status: { state:
|
|
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
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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:
|
|
596
|
-
url:
|
|
597
|
-
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:
|
|
604
|
-
parts: [{ kind:
|
|
605
|
-
kind:
|
|
673
|
+
role: 'user',
|
|
674
|
+
parts: [{ kind: 'text', text: 'Hello, agent!' }],
|
|
675
|
+
kind: 'message',
|
|
606
676
|
},
|
|
607
677
|
configuration: {
|
|
608
678
|
blocking: true,
|
|
609
|
-
acceptedOutputModes: [
|
|
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
|
```
|