@a2a-js/sdk 0.2.1 → 0.2.2

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.
@@ -12,6 +12,7 @@ export declare class DefaultRequestHandler implements A2ARequestHandler {
12
12
  constructor(agentCard: AgentCard, taskStore: TaskStore, agentExecutor: AgentExecutor, eventBusManager?: ExecutionEventBusManager);
13
13
  getAgentCard(): Promise<AgentCard>;
14
14
  private _createRequestContext;
15
+ private _processEvents;
15
16
  sendMessage(params: MessageSendParams): Promise<Message | Task>;
16
17
  sendMessageStream(params: MessageSendParams): AsyncGenerator<Message | Task | TaskStatusUpdateEvent | TaskArtifactUpdateEvent, void, undefined>;
17
18
  getTask(params: TaskQueryParams): Promise<Task>;
@@ -4,6 +4,7 @@ import { A2AError } from "../error.js";
4
4
  import { DefaultExecutionEventBusManager } from "../events/execution_event_bus_manager.js";
5
5
  import { ExecutionEventQueue } from "../events/execution_event_queue.js";
6
6
  import { ResultManager } from "../result_manager.js";
7
+ const terminalStates = ["completed", "failed", "canceled", "rejected"];
7
8
  export class DefaultRequestHandler {
8
9
  agentCard;
9
10
  taskStore;
@@ -29,6 +30,10 @@ export class DefaultRequestHandler {
29
30
  if (!task) {
30
31
  throw A2AError.taskNotFound(incomingMessage.taskId);
31
32
  }
33
+ if (terminalStates.includes(task.status.state)) {
34
+ // Throw an error that conforms to the JSON-RPC Invalid Request error specification.
35
+ throw A2AError.invalidRequest(`Task ${task.id} is in a terminal state (${task.status.state}) and cannot be modified.`);
36
+ }
32
37
  }
33
38
  if (incomingMessage.referenceTaskIds && incomingMessage.referenceTaskIds.length > 0) {
34
39
  referenceTasks = [];
@@ -51,11 +56,41 @@ export class DefaultRequestHandler {
51
56
  const contextId = incomingMessage.contextId || uuidv4();
52
57
  return new RequestContext(messageForContext, taskId, contextId, task, referenceTasks);
53
58
  }
59
+ async _processEvents(taskId, resultManager, eventQueue, options) {
60
+ let firstResultSent = false;
61
+ try {
62
+ for await (const event of eventQueue.events()) {
63
+ await resultManager.processEvent(event);
64
+ if (options?.firstResultResolver && !firstResultSent) {
65
+ if (event.kind === 'message' || event.kind === 'task') {
66
+ options.firstResultResolver(event);
67
+ firstResultSent = true;
68
+ }
69
+ }
70
+ }
71
+ if (options?.firstResultRejector && !firstResultSent) {
72
+ options.firstResultRejector(A2AError.internalError('Execution finished before a message or task was produced.'));
73
+ }
74
+ }
75
+ catch (error) {
76
+ console.error(`Event processing loop failed for task ${taskId}:`, error);
77
+ if (options?.firstResultRejector && !firstResultSent) {
78
+ options.firstResultRejector(error);
79
+ }
80
+ // re-throw error for blocking case to catch
81
+ throw error;
82
+ }
83
+ finally {
84
+ this.eventBusManager.cleanupByTaskId(taskId);
85
+ }
86
+ }
54
87
  async sendMessage(params) {
55
88
  const incomingMessage = params.message;
56
89
  if (!incomingMessage.messageId) {
57
90
  throw A2AError.invalidParams('message.messageId is required.');
58
91
  }
92
+ // Default to blocking behavior if 'blocking' is not explicitly false.
93
+ const isBlocking = params.configuration?.blocking !== false;
59
94
  const taskId = incomingMessage.taskId || uuidv4();
60
95
  // Instantiate ResultManager before creating RequestContext
61
96
  const resultManager = new ResultManager(this.taskStore);
@@ -64,12 +99,14 @@ export class DefaultRequestHandler {
64
99
  // Use the (potentially updated) contextId from requestContext
65
100
  const finalMessageForAgent = requestContext.userMessage;
66
101
  const eventBus = this.eventBusManager.createOrGetByTaskId(taskId);
102
+ // EventQueue should be attached to the bus, before the agent execution begins.
67
103
  const eventQueue = new ExecutionEventQueue(eventBus);
68
- // Start agent execution (non-blocking)
104
+ // Start agent execution (non-blocking).
105
+ // It runs in the background and publishes events to the eventBus.
69
106
  this.agentExecutor.execute(requestContext, eventBus).catch(err => {
70
107
  console.error(`Agent execution failed for message ${finalMessageForAgent.messageId}:`, err);
71
- // Publish a synthetic error event if needed, or handle error reporting
72
- // For example, create a Task with a failed status
108
+ // Publish a synthetic error event, which will be handled by the ResultManager
109
+ // and will also settle the firstResultPromise for non-blocking calls.
73
110
  const errorTask = {
74
111
  id: requestContext.task?.id || uuidv4(), // Use existing task ID or generate new
75
112
  contextId: finalMessageForAgent.contextId,
@@ -93,7 +130,7 @@ export class DefaultRequestHandler {
93
130
  errorTask.history?.push(finalMessageForAgent);
94
131
  }
95
132
  }
96
- eventBus.publish(errorTask); // This will update the task store via ResultManager
133
+ eventBus.publish(errorTask);
97
134
  eventBus.publish({
98
135
  kind: "status-update",
99
136
  taskId: errorTask.id,
@@ -103,17 +140,24 @@ export class DefaultRequestHandler {
103
140
  });
104
141
  eventBus.finished();
105
142
  });
106
- for await (const event of eventQueue.events()) {
107
- // lastEvent is no longer needed here as ResultManager tracks the final result type
108
- await resultManager.processEvent(event);
143
+ if (isBlocking) {
144
+ // In blocking mode, wait for the full processing to complete.
145
+ await this._processEvents(taskId, resultManager, eventQueue);
146
+ const finalResult = resultManager.getFinalResult();
147
+ if (!finalResult) {
148
+ throw A2AError.internalError('Agent execution finished without a result, and no task context found.');
149
+ }
150
+ return finalResult;
109
151
  }
110
- const finalResult = resultManager.getFinalResult();
111
- if (!finalResult) {
112
- throw A2AError.internalError('Agent execution finished without a result, and no task context found.');
152
+ else {
153
+ // In non-blocking mode, return a promise that will be settled by fullProcessing.
154
+ return new Promise((resolve, reject) => {
155
+ this._processEvents(taskId, resultManager, eventQueue, {
156
+ firstResultResolver: resolve,
157
+ firstResultRejector: reject,
158
+ });
159
+ });
113
160
  }
114
- // Cleanup after processing is complete for taskId
115
- this.eventBusManager.cleanupByTaskId(taskId);
116
- return finalResult;
117
161
  }
118
162
  async *sendMessageStream(params) {
119
163
  const incomingMessage = params.message;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@a2a-js/sdk",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Server & Client SDK for Agent2Agent protocol",
5
- "repository":"google-a2a/a2a-js.git",
5
+ "repository": "google-a2a/a2a-js.git",
6
6
  "engines": {
7
7
  "node": ">=18"
8
8
  },