@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 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
- const initialTask: Task = {
170
- kind: "task",
171
- id: taskId,
172
- contextId: contextId,
173
- status: {
174
- state: "submitted",
175
- timestamp: new Date().toISOString(),
176
- },
177
- };
178
- eventBus.publish(initialTask);
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. Publish initial 'submitted' state.
360
- eventBus.publish({
361
- kind: "task",
362
- id: taskId,
363
- contextId,
364
- status: { state: "submitted", timestamp: new Date().toISOString() },
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 (rpcRequest.jsonrpc !== "2.0" || !rpcRequest.method || typeof rpcRequest.method !== "string") {
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(`'params' is required for '${method}'`);
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 (rpcRequest.jsonrpc !== "2.0" || !rpcRequest.method || typeof rpcRequest.method !== "string") {
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(`'params' is required for '${method}'`);
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 /.well-known/agent-card.json).
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 /.well-known/agent-card.json).
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 /.well-known/agent-card.json).
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-JA52GYRU.js";
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 /.well-known/agent-card.json).
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();
@@ -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
- pushConfigs.forEach((pushConfig) => {
428
- this._dispatchNotification(task, pushConfig).catch((error) => {
429
- console.error(`Error sending push notification for task_id=${task.id} to URL: ${pushConfig.url}. Error:`, error);
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
- if (event.kind === "message" || event.kind === "task") {
536
- options.firstResultResolver(event);
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 (rpcRequest.jsonrpc !== "2.0" || !rpcRequest.method || typeof rpcRequest.method !== "string") {
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(`'params' is required for '${method}'`);
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 = {
@@ -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>;
@@ -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>;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  A2AError,
3
3
  JsonRpcTransportHandler
4
- } from "../chunk-JA52GYRU.js";
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
- pushConfigs.forEach((pushConfig) => {
317
- this._dispatchNotification(task, pushConfig).catch((error) => {
318
- console.error(`Error sending push notification for task_id=${task.id} to URL: ${pushConfig.url}. Error:`, error);
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
- if (event.kind === "message" || event.kind === "task") {
425
- options.firstResultResolver(event);
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.4",
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
- "sample:cli": "tsx src/samples/cli.ts",
70
- "sample:movie-agent": "tsx src/samples/agents/movie-agent/index.ts"
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
+ }