@a2a-js/sdk 0.3.4 → 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 +54 -22
- package/dist/{chunk-JA52GYRU.js → chunk-SY3G7ITG.js} +35 -6
- package/dist/server/express/index.cjs +48 -7
- package/dist/server/express/index.d.cts +1 -1
- package/dist/server/express/index.d.ts +1 -1
- package/dist/server/express/index.js +14 -2
- package/dist/server/index.cjs +70 -13
- package/dist/server/index.d.cts +3 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +36 -8
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -163,19 +163,22 @@ class TaskExecutor implements AgentExecutor {
|
|
|
163
163
|
requestContext: RequestContext,
|
|
164
164
|
eventBus: ExecutionEventBus
|
|
165
165
|
): Promise<void> {
|
|
166
|
-
const { taskId, contextId } = requestContext;
|
|
167
|
-
|
|
168
|
-
// 1. Create and publish the initial task object.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
166
|
+
const { taskId, contextId, userMessage, task } = requestContext;
|
|
167
|
+
|
|
168
|
+
// 1. Create and publish the initial task object if it doesn't exist.
|
|
169
|
+
if (!task) {
|
|
170
|
+
const initialTask: Task = {
|
|
171
|
+
kind: "task",
|
|
172
|
+
id: taskId,
|
|
173
|
+
contextId: contextId,
|
|
174
|
+
status: {
|
|
175
|
+
state: "submitted",
|
|
176
|
+
timestamp: new Date().toISOString(),
|
|
177
|
+
},
|
|
178
|
+
history: [userMessage]
|
|
179
|
+
};
|
|
180
|
+
eventBus.publish(initialTask);
|
|
181
|
+
}
|
|
179
182
|
|
|
180
183
|
// 2. Create and publish an artifact.
|
|
181
184
|
const artifactUpdate: TaskArtifactUpdateEvent = {
|
|
@@ -281,6 +284,28 @@ const client = await A2AClient.fromCardUrl(
|
|
|
281
284
|
await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "A message requiring custom headers." }], kind: "message" } });
|
|
282
285
|
```
|
|
283
286
|
|
|
287
|
+
### Example: Specifying a Timeout
|
|
288
|
+
|
|
289
|
+
This example creates a `fetch` wrapper that sets a timeout for every outgoing request.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { A2AClient } from "@a2a-js/sdk/client";
|
|
293
|
+
|
|
294
|
+
// 1. Create a wrapper around the global fetch function.
|
|
295
|
+
const fetchWithTimeout: typeof fetch = async (url, init) => {
|
|
296
|
+
return fetch(url, { ...init, signal: AbortSignal.timeout(5000)});
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// 2. Provide the custom fetch implementation to the client.
|
|
300
|
+
const client = await A2AClient.fromCardUrl(
|
|
301
|
+
"http://localhost:4000/.well-known/agent-card.json",
|
|
302
|
+
{ fetchImpl: fetchWithTimeout }
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Now, all requests made by this client instance will have a configured timeout.
|
|
306
|
+
await client.sendMessage({ message: { messageId: uuidv4(), role: "user", parts: [{ kind: "text", text: "A message requiring custom headers." }], kind: "message" } });
|
|
307
|
+
```
|
|
308
|
+
|
|
284
309
|
### Using the Provided `AuthenticationHandler`
|
|
285
310
|
|
|
286
311
|
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).
|
|
@@ -354,15 +379,22 @@ class StreamingExecutor implements AgentExecutor {
|
|
|
354
379
|
requestContext: RequestContext,
|
|
355
380
|
eventBus: ExecutionEventBus
|
|
356
381
|
): Promise<void> {
|
|
357
|
-
const { taskId, contextId } = requestContext;
|
|
358
|
-
|
|
359
|
-
// 1.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
382
|
+
const { taskId, contextId, userMessage, task } = requestContext;
|
|
383
|
+
|
|
384
|
+
// 1. Create and publish the initial task object if it doesn't exist.
|
|
385
|
+
if (!task) {
|
|
386
|
+
const initialTask: Task = {
|
|
387
|
+
kind: "task",
|
|
388
|
+
id: taskId,
|
|
389
|
+
contextId: contextId,
|
|
390
|
+
status: {
|
|
391
|
+
state: "submitted",
|
|
392
|
+
timestamp: new Date().toISOString(),
|
|
393
|
+
},
|
|
394
|
+
history: [userMessage]
|
|
395
|
+
};
|
|
396
|
+
eventBus.publish(initialTask);
|
|
397
|
+
}
|
|
366
398
|
|
|
367
399
|
// 2. Publish 'working' state.
|
|
368
400
|
eventBus.publish({
|
|
@@ -100,10 +100,8 @@ var JsonRpcTransportHandler = class {
|
|
|
100
100
|
} else {
|
|
101
101
|
throw A2AError.parseError("Invalid request body type.");
|
|
102
102
|
}
|
|
103
|
-
if (
|
|
104
|
-
throw A2AError.invalidRequest(
|
|
105
|
-
"Invalid JSON-RPC request structure."
|
|
106
|
-
);
|
|
103
|
+
if (!this.isRequestValid(rpcRequest)) {
|
|
104
|
+
throw A2AError.invalidRequest("Invalid JSON-RPC Request.");
|
|
107
105
|
}
|
|
108
106
|
} catch (error) {
|
|
109
107
|
const a2aError = error instanceof A2AError ? error : A2AError.parseError(error.message || "Failed to parse JSON request.");
|
|
@@ -123,8 +121,8 @@ var JsonRpcTransportHandler = class {
|
|
|
123
121
|
result
|
|
124
122
|
};
|
|
125
123
|
}
|
|
126
|
-
if (!rpcRequest.params) {
|
|
127
|
-
throw A2AError.invalidParams(`
|
|
124
|
+
if (!this.paramsAreValid(rpcRequest.params)) {
|
|
125
|
+
throw A2AError.invalidParams(`Invalid method parameters.`);
|
|
128
126
|
}
|
|
129
127
|
if (method === "message/stream" || method === "tasks/resubscribe") {
|
|
130
128
|
const params = rpcRequest.params;
|
|
@@ -199,6 +197,37 @@ var JsonRpcTransportHandler = class {
|
|
|
199
197
|
};
|
|
200
198
|
}
|
|
201
199
|
}
|
|
200
|
+
// Validates the basic structure of a JSON-RPC request
|
|
201
|
+
isRequestValid(rpcRequest) {
|
|
202
|
+
if (rpcRequest.jsonrpc !== "2.0") {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
if ("id" in rpcRequest) {
|
|
206
|
+
const id = rpcRequest.id;
|
|
207
|
+
const isString = typeof id === "string";
|
|
208
|
+
const isInteger = typeof id === "number" && Number.isInteger(id);
|
|
209
|
+
const isNull = id === null;
|
|
210
|
+
if (!isString && !isInteger && !isNull) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!rpcRequest.method || typeof rpcRequest.method !== "string") {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
// Validates that params is an object with non-empty string keys
|
|
220
|
+
paramsAreValid(params) {
|
|
221
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
for (const key of Object.keys(params)) {
|
|
225
|
+
if (key === "") {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
202
231
|
};
|
|
203
232
|
|
|
204
233
|
export {
|
|
@@ -138,10 +138,8 @@ var JsonRpcTransportHandler = class {
|
|
|
138
138
|
} else {
|
|
139
139
|
throw A2AError.parseError("Invalid request body type.");
|
|
140
140
|
}
|
|
141
|
-
if (
|
|
142
|
-
throw A2AError.invalidRequest(
|
|
143
|
-
"Invalid JSON-RPC request structure."
|
|
144
|
-
);
|
|
141
|
+
if (!this.isRequestValid(rpcRequest)) {
|
|
142
|
+
throw A2AError.invalidRequest("Invalid JSON-RPC Request.");
|
|
145
143
|
}
|
|
146
144
|
} catch (error) {
|
|
147
145
|
const a2aError = error instanceof A2AError ? error : A2AError.parseError(error.message || "Failed to parse JSON request.");
|
|
@@ -161,8 +159,8 @@ var JsonRpcTransportHandler = class {
|
|
|
161
159
|
result
|
|
162
160
|
};
|
|
163
161
|
}
|
|
164
|
-
if (!rpcRequest.params) {
|
|
165
|
-
throw A2AError.invalidParams(`
|
|
162
|
+
if (!this.paramsAreValid(rpcRequest.params)) {
|
|
163
|
+
throw A2AError.invalidParams(`Invalid method parameters.`);
|
|
166
164
|
}
|
|
167
165
|
if (method === "message/stream" || method === "tasks/resubscribe") {
|
|
168
166
|
const params = rpcRequest.params;
|
|
@@ -237,6 +235,37 @@ var JsonRpcTransportHandler = class {
|
|
|
237
235
|
};
|
|
238
236
|
}
|
|
239
237
|
}
|
|
238
|
+
// Validates the basic structure of a JSON-RPC request
|
|
239
|
+
isRequestValid(rpcRequest) {
|
|
240
|
+
if (rpcRequest.jsonrpc !== "2.0") {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if ("id" in rpcRequest) {
|
|
244
|
+
const id = rpcRequest.id;
|
|
245
|
+
const isString = typeof id === "string";
|
|
246
|
+
const isInteger = typeof id === "number" && Number.isInteger(id);
|
|
247
|
+
const isNull = id === null;
|
|
248
|
+
if (!isString && !isInteger && !isNull) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (!rpcRequest.method || typeof rpcRequest.method !== "string") {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
// Validates that params is an object with non-empty string keys
|
|
258
|
+
paramsAreValid(params) {
|
|
259
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
for (const key of Object.keys(params)) {
|
|
263
|
+
if (key === "") {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
240
269
|
};
|
|
241
270
|
|
|
242
271
|
// src/constants.ts
|
|
@@ -256,12 +285,24 @@ var A2AExpressApp = class {
|
|
|
256
285
|
* @param app Optional existing Express app.
|
|
257
286
|
* @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
|
|
258
287
|
* @param middlewares Optional array of Express middlewares to apply to the A2A routes.
|
|
259
|
-
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to
|
|
288
|
+
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
|
|
260
289
|
* @returns The Express app with A2A routes.
|
|
261
290
|
*/
|
|
262
291
|
setupRoutes(app, baseUrl = "", middlewares, agentCardPath = AGENT_CARD_PATH) {
|
|
263
292
|
const router = import_express.default.Router();
|
|
264
293
|
router.use(import_express.default.json(), ...middlewares ?? []);
|
|
294
|
+
router.use((err, req, res, next) => {
|
|
295
|
+
if (err instanceof SyntaxError && "body" in err) {
|
|
296
|
+
const a2aError = A2AError.parseError("Invalid JSON payload.");
|
|
297
|
+
const errorResponse = {
|
|
298
|
+
jsonrpc: "2.0",
|
|
299
|
+
id: null,
|
|
300
|
+
error: a2aError.toJSONRPCError()
|
|
301
|
+
};
|
|
302
|
+
return res.status(400).json(errorResponse);
|
|
303
|
+
}
|
|
304
|
+
next(err);
|
|
305
|
+
});
|
|
265
306
|
router.get(`/${agentCardPath}`, async (req, res) => {
|
|
266
307
|
try {
|
|
267
308
|
const agentCard = await this.requestHandler.getAgentCard();
|
|
@@ -11,7 +11,7 @@ declare class A2AExpressApp {
|
|
|
11
11
|
* @param app Optional existing Express app.
|
|
12
12
|
* @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
|
|
13
13
|
* @param middlewares Optional array of Express middlewares to apply to the A2A routes.
|
|
14
|
-
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to
|
|
14
|
+
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
|
|
15
15
|
* @returns The Express app with A2A routes.
|
|
16
16
|
*/
|
|
17
17
|
setupRoutes(app: Express, baseUrl?: string, middlewares?: Array<RequestHandler | ErrorRequestHandler>, agentCardPath?: string): Express;
|
|
@@ -11,7 +11,7 @@ declare class A2AExpressApp {
|
|
|
11
11
|
* @param app Optional existing Express app.
|
|
12
12
|
* @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
|
|
13
13
|
* @param middlewares Optional array of Express middlewares to apply to the A2A routes.
|
|
14
|
-
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to
|
|
14
|
+
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
|
|
15
15
|
* @returns The Express app with A2A routes.
|
|
16
16
|
*/
|
|
17
17
|
setupRoutes(app: Express, baseUrl?: string, middlewares?: Array<RequestHandler | ErrorRequestHandler>, agentCardPath?: string): Express;
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
A2AError,
|
|
6
6
|
JsonRpcTransportHandler
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-SY3G7ITG.js";
|
|
8
8
|
|
|
9
9
|
// src/server/express/a2a_express_app.ts
|
|
10
10
|
import express from "express";
|
|
@@ -21,12 +21,24 @@ var A2AExpressApp = class {
|
|
|
21
21
|
* @param app Optional existing Express app.
|
|
22
22
|
* @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
|
|
23
23
|
* @param middlewares Optional array of Express middlewares to apply to the A2A routes.
|
|
24
|
-
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to
|
|
24
|
+
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
|
|
25
25
|
* @returns The Express app with A2A routes.
|
|
26
26
|
*/
|
|
27
27
|
setupRoutes(app, baseUrl = "", middlewares, agentCardPath = AGENT_CARD_PATH) {
|
|
28
28
|
const router = express.Router();
|
|
29
29
|
router.use(express.json(), ...middlewares ?? []);
|
|
30
|
+
router.use((err, req, res, next) => {
|
|
31
|
+
if (err instanceof SyntaxError && "body" in err) {
|
|
32
|
+
const a2aError = A2AError.parseError("Invalid JSON payload.");
|
|
33
|
+
const errorResponse = {
|
|
34
|
+
jsonrpc: "2.0",
|
|
35
|
+
id: null,
|
|
36
|
+
error: a2aError.toJSONRPCError()
|
|
37
|
+
};
|
|
38
|
+
return res.status(400).json(errorResponse);
|
|
39
|
+
}
|
|
40
|
+
next(err);
|
|
41
|
+
});
|
|
30
42
|
router.get(`/${agentCardPath}`, async (req, res) => {
|
|
31
43
|
try {
|
|
32
44
|
const agentCard = await this.requestHandler.getAgentCard();
|
package/dist/server/index.cjs
CHANGED
|
@@ -410,9 +410,11 @@ var InMemoryPushNotificationStore = class {
|
|
|
410
410
|
// src/server/push_notification/default_push_notification_sender.ts
|
|
411
411
|
var DefaultPushNotificationSender = class {
|
|
412
412
|
pushNotificationStore;
|
|
413
|
+
notificationChain;
|
|
413
414
|
options;
|
|
414
415
|
constructor(pushNotificationStore, options = {}) {
|
|
415
416
|
this.pushNotificationStore = pushNotificationStore;
|
|
417
|
+
this.notificationChain = /* @__PURE__ */ new Map();
|
|
416
418
|
this.options = {
|
|
417
419
|
timeout: 5e3,
|
|
418
420
|
tokenHeaderName: "X-A2A-Notification-Token",
|
|
@@ -424,10 +426,22 @@ var DefaultPushNotificationSender = class {
|
|
|
424
426
|
if (!pushConfigs || pushConfigs.length === 0) {
|
|
425
427
|
return;
|
|
426
428
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
429
|
+
const lastPromise = this.notificationChain.get(task.id) ?? Promise.resolve();
|
|
430
|
+
const newPromise = lastPromise.then(async () => {
|
|
431
|
+
const dispatches = pushConfigs.map(async (pushConfig) => {
|
|
432
|
+
try {
|
|
433
|
+
await this._dispatchNotification(task, pushConfig);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error(`Error sending push notification for task_id=${task.id} to URL: ${pushConfig.url}. Error:`, error);
|
|
436
|
+
}
|
|
430
437
|
});
|
|
438
|
+
await Promise.all(dispatches);
|
|
439
|
+
});
|
|
440
|
+
this.notificationChain.set(task.id, newPromise);
|
|
441
|
+
newPromise.finally(() => {
|
|
442
|
+
if (this.notificationChain.get(task.id) === newPromise) {
|
|
443
|
+
this.notificationChain.delete(task.id);
|
|
444
|
+
}
|
|
431
445
|
});
|
|
432
446
|
}
|
|
433
447
|
async _dispatchNotification(task, pushConfig) {
|
|
@@ -451,8 +465,6 @@ var DefaultPushNotificationSender = class {
|
|
|
451
465
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
452
466
|
}
|
|
453
467
|
console.info(`Push notification sent for task_id=${task.id} to URL: ${url}`);
|
|
454
|
-
} catch (error) {
|
|
455
|
-
console.error(`Error sending push notification for task_id=${task.id} to URL: ${url}. Error:`, error);
|
|
456
468
|
} finally {
|
|
457
469
|
clearTimeout(timeoutId);
|
|
458
470
|
}
|
|
@@ -500,6 +512,8 @@ var DefaultRequestHandler = class {
|
|
|
500
512
|
if (terminalStates.includes(task.status.state)) {
|
|
501
513
|
throw A2AError.invalidRequest(`Task ${task.id} is in a terminal state (${task.status.state}) and cannot be modified.`);
|
|
502
514
|
}
|
|
515
|
+
task.history = [...task.history || [], incomingMessage];
|
|
516
|
+
await this.taskStore.save(task);
|
|
503
517
|
}
|
|
504
518
|
if (incomingMessage.referenceTaskIds && incomingMessage.referenceTaskIds.length > 0) {
|
|
505
519
|
referenceTasks = [];
|
|
@@ -532,8 +546,14 @@ var DefaultRequestHandler = class {
|
|
|
532
546
|
await resultManager.processEvent(event);
|
|
533
547
|
await this._sendPushNotificationIfNeeded(event);
|
|
534
548
|
if (options?.firstResultResolver && !firstResultSent) {
|
|
535
|
-
|
|
536
|
-
|
|
549
|
+
let firstResult;
|
|
550
|
+
if (event.kind === "message") {
|
|
551
|
+
firstResult = event;
|
|
552
|
+
} else {
|
|
553
|
+
firstResult = resultManager.getCurrentTask();
|
|
554
|
+
}
|
|
555
|
+
if (firstResult) {
|
|
556
|
+
options.firstResultResolver(firstResult);
|
|
537
557
|
firstResultSent = true;
|
|
538
558
|
}
|
|
539
559
|
}
|
|
@@ -694,7 +714,9 @@ var DefaultRequestHandler = class {
|
|
|
694
714
|
}
|
|
695
715
|
const eventBus = this.eventBusManager.getByTaskId(params.id);
|
|
696
716
|
if (eventBus) {
|
|
717
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
697
718
|
await this.agentExecutor.cancelTask(params.id, eventBus);
|
|
719
|
+
await this._processEvents(params.id, new ResultManager(this.taskStore), eventQueue);
|
|
698
720
|
} else {
|
|
699
721
|
task.status = {
|
|
700
722
|
state: "canceled",
|
|
@@ -713,6 +735,12 @@ var DefaultRequestHandler = class {
|
|
|
713
735
|
await this.taskStore.save(task);
|
|
714
736
|
}
|
|
715
737
|
const latestTask = await this.taskStore.load(params.id);
|
|
738
|
+
if (!latestTask) {
|
|
739
|
+
throw A2AError.internalError(`Task ${params.id} not found after cancellation.`);
|
|
740
|
+
}
|
|
741
|
+
if (latestTask.status.state != "canceled") {
|
|
742
|
+
throw A2AError.taskNotCancelable(params.id);
|
|
743
|
+
}
|
|
716
744
|
return latestTask;
|
|
717
745
|
}
|
|
718
746
|
async setTaskPushNotificationConfig(params) {
|
|
@@ -869,10 +897,8 @@ var JsonRpcTransportHandler = class {
|
|
|
869
897
|
} else {
|
|
870
898
|
throw A2AError.parseError("Invalid request body type.");
|
|
871
899
|
}
|
|
872
|
-
if (
|
|
873
|
-
throw A2AError.invalidRequest(
|
|
874
|
-
"Invalid JSON-RPC request structure."
|
|
875
|
-
);
|
|
900
|
+
if (!this.isRequestValid(rpcRequest)) {
|
|
901
|
+
throw A2AError.invalidRequest("Invalid JSON-RPC Request.");
|
|
876
902
|
}
|
|
877
903
|
} catch (error) {
|
|
878
904
|
const a2aError = error instanceof A2AError ? error : A2AError.parseError(error.message || "Failed to parse JSON request.");
|
|
@@ -892,8 +918,8 @@ var JsonRpcTransportHandler = class {
|
|
|
892
918
|
result
|
|
893
919
|
};
|
|
894
920
|
}
|
|
895
|
-
if (!rpcRequest.params) {
|
|
896
|
-
throw A2AError.invalidParams(`
|
|
921
|
+
if (!this.paramsAreValid(rpcRequest.params)) {
|
|
922
|
+
throw A2AError.invalidParams(`Invalid method parameters.`);
|
|
897
923
|
}
|
|
898
924
|
if (method === "message/stream" || method === "tasks/resubscribe") {
|
|
899
925
|
const params = rpcRequest.params;
|
|
@@ -968,6 +994,37 @@ var JsonRpcTransportHandler = class {
|
|
|
968
994
|
};
|
|
969
995
|
}
|
|
970
996
|
}
|
|
997
|
+
// Validates the basic structure of a JSON-RPC request
|
|
998
|
+
isRequestValid(rpcRequest) {
|
|
999
|
+
if (rpcRequest.jsonrpc !== "2.0") {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
if ("id" in rpcRequest) {
|
|
1003
|
+
const id = rpcRequest.id;
|
|
1004
|
+
const isString = typeof id === "string";
|
|
1005
|
+
const isInteger = typeof id === "number" && Number.isInteger(id);
|
|
1006
|
+
const isNull = id === null;
|
|
1007
|
+
if (!isString && !isInteger && !isNull) {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (!rpcRequest.method || typeof rpcRequest.method !== "string") {
|
|
1012
|
+
return false;
|
|
1013
|
+
}
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
// Validates that params is an object with non-empty string keys
|
|
1017
|
+
paramsAreValid(params) {
|
|
1018
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
for (const key of Object.keys(params)) {
|
|
1022
|
+
if (key === "") {
|
|
1023
|
+
return false;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
return true;
|
|
1027
|
+
}
|
|
971
1028
|
};
|
|
972
1029
|
// Annotate the CommonJS export names for ESM import in node:
|
|
973
1030
|
0 && (module.exports = {
|
package/dist/server/index.d.cts
CHANGED
|
@@ -199,6 +199,8 @@ declare class JsonRpcTransportHandler {
|
|
|
199
199
|
* For non-streaming methods, it returns a Promise of a single JSONRPCMessage (Result or ErrorResponse).
|
|
200
200
|
*/
|
|
201
201
|
handle(requestBody: any): Promise<JSONRPCResponse | AsyncGenerator<JSONRPCResponse, void, undefined>>;
|
|
202
|
+
private isRequestValid;
|
|
203
|
+
private paramsAreValid;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
/**
|
|
@@ -237,6 +239,7 @@ interface DefaultPushNotificationSenderOptions {
|
|
|
237
239
|
}
|
|
238
240
|
declare class DefaultPushNotificationSender implements PushNotificationSender {
|
|
239
241
|
private readonly pushNotificationStore;
|
|
242
|
+
private notificationChain;
|
|
240
243
|
private readonly options;
|
|
241
244
|
constructor(pushNotificationStore: PushNotificationStore, options?: DefaultPushNotificationSenderOptions);
|
|
242
245
|
send(task: Task): Promise<void>;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -199,6 +199,8 @@ declare class JsonRpcTransportHandler {
|
|
|
199
199
|
* For non-streaming methods, it returns a Promise of a single JSONRPCMessage (Result or ErrorResponse).
|
|
200
200
|
*/
|
|
201
201
|
handle(requestBody: any): Promise<JSONRPCResponse | AsyncGenerator<JSONRPCResponse, void, undefined>>;
|
|
202
|
+
private isRequestValid;
|
|
203
|
+
private paramsAreValid;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
/**
|
|
@@ -237,6 +239,7 @@ interface DefaultPushNotificationSenderOptions {
|
|
|
237
239
|
}
|
|
238
240
|
declare class DefaultPushNotificationSender implements PushNotificationSender {
|
|
239
241
|
private readonly pushNotificationStore;
|
|
242
|
+
private notificationChain;
|
|
240
243
|
private readonly options;
|
|
241
244
|
constructor(pushNotificationStore: PushNotificationStore, options?: DefaultPushNotificationSenderOptions);
|
|
242
245
|
send(task: Task): Promise<void>;
|
package/dist/server/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
A2AError,
|
|
3
3
|
JsonRpcTransportHandler
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-SY3G7ITG.js";
|
|
5
5
|
|
|
6
6
|
// src/server/agent_execution/request_context.ts
|
|
7
7
|
var RequestContext = class {
|
|
@@ -299,9 +299,11 @@ var InMemoryPushNotificationStore = class {
|
|
|
299
299
|
// src/server/push_notification/default_push_notification_sender.ts
|
|
300
300
|
var DefaultPushNotificationSender = class {
|
|
301
301
|
pushNotificationStore;
|
|
302
|
+
notificationChain;
|
|
302
303
|
options;
|
|
303
304
|
constructor(pushNotificationStore, options = {}) {
|
|
304
305
|
this.pushNotificationStore = pushNotificationStore;
|
|
306
|
+
this.notificationChain = /* @__PURE__ */ new Map();
|
|
305
307
|
this.options = {
|
|
306
308
|
timeout: 5e3,
|
|
307
309
|
tokenHeaderName: "X-A2A-Notification-Token",
|
|
@@ -313,10 +315,22 @@ var DefaultPushNotificationSender = class {
|
|
|
313
315
|
if (!pushConfigs || pushConfigs.length === 0) {
|
|
314
316
|
return;
|
|
315
317
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
318
|
+
const lastPromise = this.notificationChain.get(task.id) ?? Promise.resolve();
|
|
319
|
+
const newPromise = lastPromise.then(async () => {
|
|
320
|
+
const dispatches = pushConfigs.map(async (pushConfig) => {
|
|
321
|
+
try {
|
|
322
|
+
await this._dispatchNotification(task, pushConfig);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error(`Error sending push notification for task_id=${task.id} to URL: ${pushConfig.url}. Error:`, error);
|
|
325
|
+
}
|
|
319
326
|
});
|
|
327
|
+
await Promise.all(dispatches);
|
|
328
|
+
});
|
|
329
|
+
this.notificationChain.set(task.id, newPromise);
|
|
330
|
+
newPromise.finally(() => {
|
|
331
|
+
if (this.notificationChain.get(task.id) === newPromise) {
|
|
332
|
+
this.notificationChain.delete(task.id);
|
|
333
|
+
}
|
|
320
334
|
});
|
|
321
335
|
}
|
|
322
336
|
async _dispatchNotification(task, pushConfig) {
|
|
@@ -340,8 +354,6 @@ var DefaultPushNotificationSender = class {
|
|
|
340
354
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
341
355
|
}
|
|
342
356
|
console.info(`Push notification sent for task_id=${task.id} to URL: ${url}`);
|
|
343
|
-
} catch (error) {
|
|
344
|
-
console.error(`Error sending push notification for task_id=${task.id} to URL: ${url}. Error:`, error);
|
|
345
357
|
} finally {
|
|
346
358
|
clearTimeout(timeoutId);
|
|
347
359
|
}
|
|
@@ -389,6 +401,8 @@ var DefaultRequestHandler = class {
|
|
|
389
401
|
if (terminalStates.includes(task.status.state)) {
|
|
390
402
|
throw A2AError.invalidRequest(`Task ${task.id} is in a terminal state (${task.status.state}) and cannot be modified.`);
|
|
391
403
|
}
|
|
404
|
+
task.history = [...task.history || [], incomingMessage];
|
|
405
|
+
await this.taskStore.save(task);
|
|
392
406
|
}
|
|
393
407
|
if (incomingMessage.referenceTaskIds && incomingMessage.referenceTaskIds.length > 0) {
|
|
394
408
|
referenceTasks = [];
|
|
@@ -421,8 +435,14 @@ var DefaultRequestHandler = class {
|
|
|
421
435
|
await resultManager.processEvent(event);
|
|
422
436
|
await this._sendPushNotificationIfNeeded(event);
|
|
423
437
|
if (options?.firstResultResolver && !firstResultSent) {
|
|
424
|
-
|
|
425
|
-
|
|
438
|
+
let firstResult;
|
|
439
|
+
if (event.kind === "message") {
|
|
440
|
+
firstResult = event;
|
|
441
|
+
} else {
|
|
442
|
+
firstResult = resultManager.getCurrentTask();
|
|
443
|
+
}
|
|
444
|
+
if (firstResult) {
|
|
445
|
+
options.firstResultResolver(firstResult);
|
|
426
446
|
firstResultSent = true;
|
|
427
447
|
}
|
|
428
448
|
}
|
|
@@ -583,7 +603,9 @@ var DefaultRequestHandler = class {
|
|
|
583
603
|
}
|
|
584
604
|
const eventBus = this.eventBusManager.getByTaskId(params.id);
|
|
585
605
|
if (eventBus) {
|
|
606
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
586
607
|
await this.agentExecutor.cancelTask(params.id, eventBus);
|
|
608
|
+
await this._processEvents(params.id, new ResultManager(this.taskStore), eventQueue);
|
|
587
609
|
} else {
|
|
588
610
|
task.status = {
|
|
589
611
|
state: "canceled",
|
|
@@ -602,6 +624,12 @@ var DefaultRequestHandler = class {
|
|
|
602
624
|
await this.taskStore.save(task);
|
|
603
625
|
}
|
|
604
626
|
const latestTask = await this.taskStore.load(params.id);
|
|
627
|
+
if (!latestTask) {
|
|
628
|
+
throw A2AError.internalError(`Task ${params.id} not found after cancellation.`);
|
|
629
|
+
}
|
|
630
|
+
if (latestTask.status.state != "canceled") {
|
|
631
|
+
throw A2AError.taskNotCancelable(params.id);
|
|
632
|
+
}
|
|
605
633
|
return latestTask;
|
|
606
634
|
}
|
|
607
635
|
async setTaskPushNotificationConfig(params) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a2a-js/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Server & Client SDK for Agent2Agent protocol",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@types/mocha": "^10.0.10",
|
|
48
48
|
"@types/node": "^22.13.14",
|
|
49
49
|
"@types/sinon": "^17.0.4",
|
|
50
|
+
"@types/supertest": "^6.0.3",
|
|
50
51
|
"c8": "^10.1.3",
|
|
51
52
|
"chai": "^5.2.0",
|
|
52
53
|
"express": "^5.1.0",
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"json-schema-to-typescript": "^15.0.4",
|
|
56
57
|
"mocha": "^11.6.0",
|
|
57
58
|
"sinon": "^20.0.0",
|
|
59
|
+
"supertest": "^7.1.4",
|
|
58
60
|
"tsup": "^8.5.0",
|
|
59
61
|
"tsx": "^4.19.3",
|
|
60
62
|
"typescript": "^5.8.2"
|
|
@@ -66,8 +68,9 @@
|
|
|
66
68
|
"test": "mocha test/**/*.spec.ts",
|
|
67
69
|
"coverage": "c8 npm run test",
|
|
68
70
|
"generate": "curl https://raw.githubusercontent.com/google-a2a/A2A/refs/heads/main/specification/json/a2a.json > spec.json && node scripts/generateTypes.js && rm spec.json",
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
+
"a2a:cli": "tsx src/samples/cli.ts",
|
|
72
|
+
"agents:movie-agent": "tsx src/samples/agents/movie-agent/index.ts",
|
|
73
|
+
"agents:sample-agent": "tsx src/samples/agents/sample-agent/index.ts"
|
|
71
74
|
},
|
|
72
75
|
"dependencies": {
|
|
73
76
|
"uuid": "^11.1.0"
|
|
@@ -83,4 +86,4 @@
|
|
|
83
86
|
"mocha": {
|
|
84
87
|
"require": "tsx"
|
|
85
88
|
}
|
|
86
|
-
}
|
|
89
|
+
}
|