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