@a2a-js/sdk 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 {
@@ -257,6 +257,109 @@ var ResultManager = class {
257
257
  }
258
258
  };
259
259
 
260
+ // src/server/push_notification/push_notification_store.ts
261
+ var InMemoryPushNotificationStore = class {
262
+ store = /* @__PURE__ */ new Map();
263
+ async save(taskId, pushNotificationConfig) {
264
+ const configs = this.store.get(taskId) || [];
265
+ if (!pushNotificationConfig.id) {
266
+ pushNotificationConfig.id = taskId;
267
+ }
268
+ const existingIndex = configs.findIndex((config) => config.id === pushNotificationConfig.id);
269
+ if (existingIndex !== -1) {
270
+ configs.splice(existingIndex, 1);
271
+ }
272
+ configs.push(pushNotificationConfig);
273
+ this.store.set(taskId, configs);
274
+ }
275
+ async load(taskId) {
276
+ const configs = this.store.get(taskId);
277
+ return configs || [];
278
+ }
279
+ async delete(taskId, configId) {
280
+ if (configId === void 0) {
281
+ configId = taskId;
282
+ }
283
+ const configs = this.store.get(taskId);
284
+ if (!configs) {
285
+ return;
286
+ }
287
+ const configIndex = configs.findIndex((config) => config.id === configId);
288
+ if (configIndex !== -1) {
289
+ configs.splice(configIndex, 1);
290
+ }
291
+ if (configs.length === 0) {
292
+ this.store.delete(taskId);
293
+ } else {
294
+ this.store.set(taskId, configs);
295
+ }
296
+ }
297
+ };
298
+
299
+ // src/server/push_notification/default_push_notification_sender.ts
300
+ var DefaultPushNotificationSender = class {
301
+ pushNotificationStore;
302
+ notificationChain;
303
+ options;
304
+ constructor(pushNotificationStore, options = {}) {
305
+ this.pushNotificationStore = pushNotificationStore;
306
+ this.notificationChain = /* @__PURE__ */ new Map();
307
+ this.options = {
308
+ timeout: 5e3,
309
+ tokenHeaderName: "X-A2A-Notification-Token",
310
+ ...options
311
+ };
312
+ }
313
+ async send(task) {
314
+ const pushConfigs = await this.pushNotificationStore.load(task.id);
315
+ if (!pushConfigs || pushConfigs.length === 0) {
316
+ return;
317
+ }
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
+ }
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
+ }
334
+ });
335
+ }
336
+ async _dispatchNotification(task, pushConfig) {
337
+ const url = pushConfig.url;
338
+ const controller = new AbortController();
339
+ const timeoutId = setTimeout(() => controller.abort(), this.options.timeout);
340
+ try {
341
+ const headers = {
342
+ "Content-Type": "application/json"
343
+ };
344
+ if (pushConfig.token) {
345
+ headers[this.options.tokenHeaderName] = pushConfig.token;
346
+ }
347
+ const response = await fetch(url, {
348
+ method: "POST",
349
+ headers,
350
+ body: JSON.stringify(task),
351
+ signal: controller.signal
352
+ });
353
+ if (!response.ok) {
354
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
355
+ }
356
+ console.info(`Push notification sent for task_id=${task.id} to URL: ${url}`);
357
+ } finally {
358
+ clearTimeout(timeoutId);
359
+ }
360
+ }
361
+ };
362
+
260
363
  // src/server/request_handler/default_request_handler.ts
261
364
  var terminalStates = ["completed", "failed", "canceled", "rejected"];
262
365
  var DefaultRequestHandler = class {
@@ -265,14 +368,18 @@ var DefaultRequestHandler = class {
265
368
  taskStore;
266
369
  agentExecutor;
267
370
  eventBusManager;
268
- // Store for push notification configurations (could be part of TaskStore or separate)
269
- pushNotificationConfigs = /* @__PURE__ */ new Map();
270
- constructor(agentCard, taskStore, agentExecutor, eventBusManager = new DefaultExecutionEventBusManager(), extendedAgentCard) {
371
+ pushNotificationStore;
372
+ pushNotificationSender;
373
+ constructor(agentCard, taskStore, agentExecutor, eventBusManager = new DefaultExecutionEventBusManager(), pushNotificationStore, pushNotificationSender, extendedAgentCard) {
271
374
  this.agentCard = agentCard;
272
375
  this.taskStore = taskStore;
273
376
  this.agentExecutor = agentExecutor;
274
377
  this.eventBusManager = eventBusManager;
275
378
  this.extendedAgentCard = extendedAgentCard;
379
+ if (agentCard.capabilities.pushNotifications) {
380
+ this.pushNotificationStore = pushNotificationStore || new InMemoryPushNotificationStore();
381
+ this.pushNotificationSender = pushNotificationSender || new DefaultPushNotificationSender(this.pushNotificationStore);
382
+ }
276
383
  }
277
384
  async getAgentCard() {
278
385
  return this.agentCard;
@@ -294,6 +401,8 @@ var DefaultRequestHandler = class {
294
401
  if (terminalStates.includes(task.status.state)) {
295
402
  throw A2AError.invalidRequest(`Task ${task.id} is in a terminal state (${task.status.state}) and cannot be modified.`);
296
403
  }
404
+ task.history = [...task.history || [], incomingMessage];
405
+ await this.taskStore.save(task);
297
406
  }
298
407
  if (incomingMessage.referenceTaskIds && incomingMessage.referenceTaskIds.length > 0) {
299
408
  referenceTasks = [];
@@ -324,9 +433,16 @@ var DefaultRequestHandler = class {
324
433
  try {
325
434
  for await (const event of eventQueue.events()) {
326
435
  await resultManager.processEvent(event);
436
+ await this._sendPushNotificationIfNeeded(event);
327
437
  if (options?.firstResultResolver && !firstResultSent) {
328
- if (event.kind === "message" || event.kind === "task") {
329
- 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);
330
446
  firstResultSent = true;
331
447
  }
332
448
  }
@@ -355,6 +471,9 @@ var DefaultRequestHandler = class {
355
471
  resultManager.setContext(incomingMessage);
356
472
  const requestContext = await this._createRequestContext(incomingMessage, taskId, false);
357
473
  const finalMessageForAgent = requestContext.userMessage;
474
+ if (params.configuration?.pushNotificationConfig && this.agentCard.capabilities.pushNotifications) {
475
+ await this.pushNotificationStore?.save(taskId, params.configuration.pushNotificationConfig);
476
+ }
358
477
  const eventBus = this.eventBusManager.createOrGetByTaskId(taskId);
359
478
  const eventQueue = new ExecutionEventQueue(eventBus);
360
479
  this.agentExecutor.execute(requestContext, eventBus).catch((err) => {
@@ -422,6 +541,9 @@ var DefaultRequestHandler = class {
422
541
  const finalMessageForAgent = requestContext.userMessage;
423
542
  const eventBus = this.eventBusManager.createOrGetByTaskId(taskId);
424
543
  const eventQueue = new ExecutionEventQueue(eventBus);
544
+ if (params.configuration?.pushNotificationConfig && this.agentCard.capabilities.pushNotifications) {
545
+ await this.pushNotificationStore?.save(taskId, params.configuration.pushNotificationConfig);
546
+ }
425
547
  this.agentExecutor.execute(requestContext, eventBus).catch((err) => {
426
548
  console.error(`Agent execution failed for stream message ${finalMessageForAgent.messageId}:`, err);
427
549
  const errorTaskStatus = {
@@ -449,6 +571,7 @@ var DefaultRequestHandler = class {
449
571
  try {
450
572
  for await (const event of eventQueue.events()) {
451
573
  await resultManager.processEvent(event);
574
+ await this._sendPushNotificationIfNeeded(event);
452
575
  yield event;
453
576
  }
454
577
  } finally {
@@ -480,7 +603,9 @@ var DefaultRequestHandler = class {
480
603
  }
481
604
  const eventBus = this.eventBusManager.getByTaskId(params.id);
482
605
  if (eventBus) {
606
+ const eventQueue = new ExecutionEventQueue(eventBus);
483
607
  await this.agentExecutor.cancelTask(params.id, eventBus);
608
+ await this._processEvents(params.id, new ResultManager(this.taskStore), eventQueue);
484
609
  } else {
485
610
  task.status = {
486
611
  state: "canceled",
@@ -499,6 +624,12 @@ var DefaultRequestHandler = class {
499
624
  await this.taskStore.save(task);
500
625
  }
501
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
+ }
502
633
  return latestTask;
503
634
  }
504
635
  async setTaskPushNotificationConfig(params) {
@@ -513,10 +644,7 @@ var DefaultRequestHandler = class {
513
644
  if (!pushNotificationConfig.id) {
514
645
  pushNotificationConfig.id = taskId;
515
646
  }
516
- const configs = this.pushNotificationConfigs.get(taskId) || [];
517
- const updatedConfigs = configs.filter((c) => c.id !== pushNotificationConfig.id);
518
- updatedConfigs.push(pushNotificationConfig);
519
- this.pushNotificationConfigs.set(taskId, updatedConfigs);
647
+ await this.pushNotificationStore?.save(taskId, pushNotificationConfig);
520
648
  return params;
521
649
  }
522
650
  async getTaskPushNotificationConfig(params) {
@@ -527,7 +655,7 @@ var DefaultRequestHandler = class {
527
655
  if (!task) {
528
656
  throw A2AError.taskNotFound(params.id);
529
657
  }
530
- const configs = this.pushNotificationConfigs.get(params.id) || [];
658
+ const configs = await this.pushNotificationStore?.load(params.id) || [];
531
659
  if (configs.length === 0) {
532
660
  throw A2AError.internalError(`Push notification config not found for task ${params.id}.`);
533
661
  }
@@ -551,7 +679,7 @@ var DefaultRequestHandler = class {
551
679
  if (!task) {
552
680
  throw A2AError.taskNotFound(params.id);
553
681
  }
554
- const configs = this.pushNotificationConfigs.get(params.id) || [];
682
+ const configs = await this.pushNotificationStore?.load(params.id) || [];
555
683
  return configs.map((config) => ({
556
684
  taskId: params.id,
557
685
  pushNotificationConfig: config
@@ -566,16 +694,7 @@ var DefaultRequestHandler = class {
566
694
  throw A2AError.taskNotFound(params.id);
567
695
  }
568
696
  const { id: taskId, pushNotificationConfigId } = params;
569
- const configs = this.pushNotificationConfigs.get(taskId);
570
- if (!configs) {
571
- return;
572
- }
573
- const updatedConfigs = configs.filter((c) => c.id !== pushNotificationConfigId);
574
- if (updatedConfigs.length === 0) {
575
- this.pushNotificationConfigs.delete(taskId);
576
- } else if (updatedConfigs.length < configs.length) {
577
- this.pushNotificationConfigs.set(taskId, updatedConfigs);
578
- }
697
+ await this.pushNotificationStore?.delete(taskId, pushNotificationConfigId);
579
698
  }
580
699
  async *resubscribe(params) {
581
700
  if (!this.agentCard.capabilities.streaming) {
@@ -610,6 +729,28 @@ var DefaultRequestHandler = class {
610
729
  eventQueue.stop();
611
730
  }
612
731
  }
732
+ async _sendPushNotificationIfNeeded(event) {
733
+ if (!this.agentCard.capabilities.pushNotifications) {
734
+ return;
735
+ }
736
+ let taskId = "";
737
+ if (event.kind == "task") {
738
+ const task2 = event;
739
+ taskId = task2.id;
740
+ } else {
741
+ taskId = event.taskId;
742
+ }
743
+ if (!taskId) {
744
+ console.error(`Task ID not found for event ${event.kind}.`);
745
+ return;
746
+ }
747
+ const task = await this.taskStore.load(taskId);
748
+ if (!task) {
749
+ console.error(`Task ${taskId} not found.`);
750
+ return;
751
+ }
752
+ this.pushNotificationSender?.send(task);
753
+ }
613
754
  };
614
755
 
615
756
  // src/server/store.ts
@@ -627,8 +768,10 @@ export {
627
768
  A2AError,
628
769
  DefaultExecutionEventBus,
629
770
  DefaultExecutionEventBusManager,
771
+ DefaultPushNotificationSender,
630
772
  DefaultRequestHandler,
631
773
  ExecutionEventQueue,
774
+ InMemoryPushNotificationStore,
632
775
  InMemoryTaskStore,
633
776
  JsonRpcTransportHandler,
634
777
  RequestContext,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2a-js/sdk",
3
- "version": "0.3.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
- "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
+ }