@a2a-js/sdk 0.2.1
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/LICENSE +202 -0
- package/README.md +422 -0
- package/build/src/a2a_response.d.ts +5 -0
- package/build/src/a2a_response.js +2 -0
- package/build/src/client/client.d.ts +118 -0
- package/build/src/client/client.js +409 -0
- package/build/src/index.d.ts +21 -0
- package/build/src/index.js +18 -0
- package/build/src/samples/agents/movie-agent/genkit.d.ts +2 -0
- package/build/src/samples/agents/movie-agent/genkit.js +11 -0
- package/build/src/samples/agents/movie-agent/index.d.ts +1 -0
- package/build/src/samples/agents/movie-agent/index.js +252 -0
- package/build/src/samples/agents/movie-agent/tmdb.d.ts +7 -0
- package/build/src/samples/agents/movie-agent/tmdb.js +32 -0
- package/build/src/samples/agents/movie-agent/tools.d.ts +15 -0
- package/build/src/samples/agents/movie-agent/tools.js +74 -0
- package/build/src/samples/cli.d.ts +2 -0
- package/build/src/samples/cli.js +289 -0
- package/build/src/server/a2a_express_app.d.ts +14 -0
- package/build/src/server/a2a_express_app.js +98 -0
- package/build/src/server/agent_execution/agent_executor.d.ts +18 -0
- package/build/src/server/agent_execution/agent_executor.js +2 -0
- package/build/src/server/agent_execution/request_context.d.ts +9 -0
- package/build/src/server/agent_execution/request_context.js +15 -0
- package/build/src/server/error.d.ts +23 -0
- package/build/src/server/error.js +57 -0
- package/build/src/server/events/execution_event_bus.d.ts +16 -0
- package/build/src/server/events/execution_event_bus.js +13 -0
- package/build/src/server/events/execution_event_bus_manager.d.ts +27 -0
- package/build/src/server/events/execution_event_bus_manager.js +36 -0
- package/build/src/server/events/execution_event_queue.d.ts +24 -0
- package/build/src/server/events/execution_event_queue.js +63 -0
- package/build/src/server/request_handler/a2a_request_handler.d.ts +11 -0
- package/build/src/server/request_handler/a2a_request_handler.js +2 -0
- package/build/src/server/request_handler/default_request_handler.d.ts +22 -0
- package/build/src/server/request_handler/default_request_handler.js +296 -0
- package/build/src/server/result_manager.d.ts +29 -0
- package/build/src/server/result_manager.js +149 -0
- package/build/src/server/store.d.ts +25 -0
- package/build/src/server/store.js +17 -0
- package/build/src/server/transports/jsonrpc_transport_handler.d.ts +15 -0
- package/build/src/server/transports/jsonrpc_transport_handler.js +114 -0
- package/build/src/server/utils.d.ts +22 -0
- package/build/src/server/utils.js +34 -0
- package/build/src/types-old.d.ts +832 -0
- package/build/src/types-old.js +20 -0
- package/build/src/types.d.ts +2046 -0
- package/build/src/types.js +8 -0
- package/package.json +52 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid'; // For generating unique IDs
|
|
2
|
+
import { RequestContext } from "../agent_execution/request_context.js";
|
|
3
|
+
import { A2AError } from "../error.js";
|
|
4
|
+
import { DefaultExecutionEventBusManager } from "../events/execution_event_bus_manager.js";
|
|
5
|
+
import { ExecutionEventQueue } from "../events/execution_event_queue.js";
|
|
6
|
+
import { ResultManager } from "../result_manager.js";
|
|
7
|
+
export class DefaultRequestHandler {
|
|
8
|
+
agentCard;
|
|
9
|
+
taskStore;
|
|
10
|
+
agentExecutor;
|
|
11
|
+
eventBusManager;
|
|
12
|
+
// Store for push notification configurations (could be part of TaskStore or separate)
|
|
13
|
+
pushNotificationConfigs = new Map();
|
|
14
|
+
constructor(agentCard, taskStore, agentExecutor, eventBusManager = new DefaultExecutionEventBusManager()) {
|
|
15
|
+
this.agentCard = agentCard;
|
|
16
|
+
this.taskStore = taskStore;
|
|
17
|
+
this.agentExecutor = agentExecutor;
|
|
18
|
+
this.eventBusManager = eventBusManager;
|
|
19
|
+
}
|
|
20
|
+
async getAgentCard() {
|
|
21
|
+
return this.agentCard;
|
|
22
|
+
}
|
|
23
|
+
async _createRequestContext(incomingMessage, taskId, isStream) {
|
|
24
|
+
let task;
|
|
25
|
+
let referenceTasks;
|
|
26
|
+
// incomingMessage would contain taskId, if a task already exists.
|
|
27
|
+
if (incomingMessage.taskId) {
|
|
28
|
+
task = await this.taskStore.load(incomingMessage.taskId);
|
|
29
|
+
if (!task) {
|
|
30
|
+
throw A2AError.taskNotFound(incomingMessage.taskId);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (incomingMessage.referenceTaskIds && incomingMessage.referenceTaskIds.length > 0) {
|
|
34
|
+
referenceTasks = [];
|
|
35
|
+
for (const refId of incomingMessage.referenceTaskIds) {
|
|
36
|
+
const refTask = await this.taskStore.load(refId);
|
|
37
|
+
if (refTask) {
|
|
38
|
+
referenceTasks.push(refTask);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.warn(`Reference task ${refId} not found.`);
|
|
42
|
+
// Optionally, throw an error or handle as per specific requirements
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Ensure contextId is present
|
|
47
|
+
const messageForContext = { ...incomingMessage };
|
|
48
|
+
if (!messageForContext.contextId) {
|
|
49
|
+
messageForContext.contextId = task?.contextId || uuidv4();
|
|
50
|
+
}
|
|
51
|
+
const contextId = incomingMessage.contextId || uuidv4();
|
|
52
|
+
return new RequestContext(messageForContext, taskId, contextId, task, referenceTasks);
|
|
53
|
+
}
|
|
54
|
+
async sendMessage(params) {
|
|
55
|
+
const incomingMessage = params.message;
|
|
56
|
+
if (!incomingMessage.messageId) {
|
|
57
|
+
throw A2AError.invalidParams('message.messageId is required.');
|
|
58
|
+
}
|
|
59
|
+
const taskId = incomingMessage.taskId || uuidv4();
|
|
60
|
+
// Instantiate ResultManager before creating RequestContext
|
|
61
|
+
const resultManager = new ResultManager(this.taskStore);
|
|
62
|
+
resultManager.setContext(incomingMessage); // Set context for ResultManager
|
|
63
|
+
const requestContext = await this._createRequestContext(incomingMessage, taskId, false);
|
|
64
|
+
// Use the (potentially updated) contextId from requestContext
|
|
65
|
+
const finalMessageForAgent = requestContext.userMessage;
|
|
66
|
+
const eventBus = this.eventBusManager.createOrGetByTaskId(taskId);
|
|
67
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
68
|
+
// Start agent execution (non-blocking)
|
|
69
|
+
this.agentExecutor.execute(requestContext, eventBus).catch(err => {
|
|
70
|
+
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
|
|
73
|
+
const errorTask = {
|
|
74
|
+
id: requestContext.task?.id || uuidv4(), // Use existing task ID or generate new
|
|
75
|
+
contextId: finalMessageForAgent.contextId,
|
|
76
|
+
status: {
|
|
77
|
+
state: "failed",
|
|
78
|
+
message: {
|
|
79
|
+
kind: "message",
|
|
80
|
+
role: "agent",
|
|
81
|
+
messageId: uuidv4(),
|
|
82
|
+
parts: [{ kind: "text", text: `Agent execution error: ${err.message}` }],
|
|
83
|
+
taskId: requestContext.task?.id,
|
|
84
|
+
contextId: finalMessageForAgent.contextId,
|
|
85
|
+
},
|
|
86
|
+
timestamp: new Date().toISOString(),
|
|
87
|
+
},
|
|
88
|
+
history: requestContext.task?.history ? [...requestContext.task.history] : [],
|
|
89
|
+
kind: "task",
|
|
90
|
+
};
|
|
91
|
+
if (finalMessageForAgent) { // Add incoming message to history
|
|
92
|
+
if (!errorTask.history?.find(m => m.messageId === finalMessageForAgent.messageId)) {
|
|
93
|
+
errorTask.history?.push(finalMessageForAgent);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
eventBus.publish(errorTask); // This will update the task store via ResultManager
|
|
97
|
+
eventBus.publish({
|
|
98
|
+
kind: "status-update",
|
|
99
|
+
taskId: errorTask.id,
|
|
100
|
+
contextId: errorTask.contextId,
|
|
101
|
+
status: errorTask.status,
|
|
102
|
+
final: true,
|
|
103
|
+
});
|
|
104
|
+
eventBus.finished();
|
|
105
|
+
});
|
|
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);
|
|
109
|
+
}
|
|
110
|
+
const finalResult = resultManager.getFinalResult();
|
|
111
|
+
if (!finalResult) {
|
|
112
|
+
throw A2AError.internalError('Agent execution finished without a result, and no task context found.');
|
|
113
|
+
}
|
|
114
|
+
// Cleanup after processing is complete for taskId
|
|
115
|
+
this.eventBusManager.cleanupByTaskId(taskId);
|
|
116
|
+
return finalResult;
|
|
117
|
+
}
|
|
118
|
+
async *sendMessageStream(params) {
|
|
119
|
+
const incomingMessage = params.message;
|
|
120
|
+
if (!incomingMessage.messageId) {
|
|
121
|
+
// For streams, messageId might be set by client, or server can generate if not present.
|
|
122
|
+
// Let's assume client provides it or throw for now.
|
|
123
|
+
throw A2AError.invalidParams('message.messageId is required for streaming.');
|
|
124
|
+
}
|
|
125
|
+
const taskId = incomingMessage.taskId || uuidv4();
|
|
126
|
+
// Instantiate ResultManager before creating RequestContext
|
|
127
|
+
const resultManager = new ResultManager(this.taskStore);
|
|
128
|
+
resultManager.setContext(incomingMessage); // Set context for ResultManager
|
|
129
|
+
const requestContext = await this._createRequestContext(incomingMessage, taskId, true);
|
|
130
|
+
const finalMessageForAgent = requestContext.userMessage;
|
|
131
|
+
const eventBus = this.eventBusManager.createOrGetByTaskId(taskId);
|
|
132
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
133
|
+
// Start agent execution (non-blocking)
|
|
134
|
+
this.agentExecutor.execute(requestContext, eventBus).catch(err => {
|
|
135
|
+
console.error(`Agent execution failed for stream message ${finalMessageForAgent.messageId}:`, err);
|
|
136
|
+
// Publish a synthetic error event if needed
|
|
137
|
+
const errorTaskStatus = {
|
|
138
|
+
kind: "status-update",
|
|
139
|
+
taskId: requestContext.task?.id || uuidv4(), // Use existing or a placeholder
|
|
140
|
+
contextId: finalMessageForAgent.contextId,
|
|
141
|
+
status: {
|
|
142
|
+
state: "failed",
|
|
143
|
+
message: {
|
|
144
|
+
kind: "message",
|
|
145
|
+
role: "agent",
|
|
146
|
+
messageId: uuidv4(),
|
|
147
|
+
parts: [{ kind: "text", text: `Agent execution error: ${err.message}` }],
|
|
148
|
+
taskId: requestContext.task?.id,
|
|
149
|
+
contextId: finalMessageForAgent.contextId,
|
|
150
|
+
},
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
},
|
|
153
|
+
final: true, // This will terminate the stream for the client
|
|
154
|
+
};
|
|
155
|
+
eventBus.publish(errorTaskStatus);
|
|
156
|
+
});
|
|
157
|
+
try {
|
|
158
|
+
for await (const event of eventQueue.events()) {
|
|
159
|
+
await resultManager.processEvent(event); // Update store in background
|
|
160
|
+
yield event; // Stream the event to the client
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
// Cleanup when the stream is fully consumed or breaks
|
|
165
|
+
this.eventBusManager.cleanupByTaskId(taskId);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async getTask(params) {
|
|
169
|
+
const task = await this.taskStore.load(params.id);
|
|
170
|
+
if (!task) {
|
|
171
|
+
throw A2AError.taskNotFound(params.id);
|
|
172
|
+
}
|
|
173
|
+
if (params.historyLength !== undefined && params.historyLength >= 0) {
|
|
174
|
+
if (task.history) {
|
|
175
|
+
task.history = task.history.slice(-params.historyLength);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
// Negative or invalid historyLength means no history
|
|
180
|
+
task.history = [];
|
|
181
|
+
}
|
|
182
|
+
return task;
|
|
183
|
+
}
|
|
184
|
+
async cancelTask(params) {
|
|
185
|
+
const task = await this.taskStore.load(params.id);
|
|
186
|
+
if (!task) {
|
|
187
|
+
throw A2AError.taskNotFound(params.id);
|
|
188
|
+
}
|
|
189
|
+
// Check if task is in a cancelable state
|
|
190
|
+
const nonCancelableStates = ["completed", "failed", "canceled", "rejected"];
|
|
191
|
+
if (nonCancelableStates.includes(task.status.state)) {
|
|
192
|
+
throw A2AError.taskNotCancelable(params.id);
|
|
193
|
+
}
|
|
194
|
+
const eventBus = this.eventBusManager.getByTaskId(params.id);
|
|
195
|
+
if (eventBus) {
|
|
196
|
+
await this.agentExecutor.cancelTask(params.id, eventBus);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Here we are marking task as cancelled. We are not waiting for the executor to actually cancel processing.
|
|
200
|
+
task.status = {
|
|
201
|
+
state: "canceled",
|
|
202
|
+
message: {
|
|
203
|
+
kind: "message",
|
|
204
|
+
role: "agent",
|
|
205
|
+
messageId: uuidv4(),
|
|
206
|
+
parts: [{ kind: "text", text: "Task cancellation requested by user." }],
|
|
207
|
+
taskId: task.id,
|
|
208
|
+
contextId: task.contextId,
|
|
209
|
+
},
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
};
|
|
212
|
+
// Add cancellation message to history
|
|
213
|
+
task.history = [...(task.history || []), task.status.message];
|
|
214
|
+
await this.taskStore.save(task);
|
|
215
|
+
}
|
|
216
|
+
const latestTask = await this.taskStore.load(params.id);
|
|
217
|
+
return latestTask;
|
|
218
|
+
}
|
|
219
|
+
async setTaskPushNotificationConfig(params) {
|
|
220
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
221
|
+
throw A2AError.pushNotificationNotSupported();
|
|
222
|
+
}
|
|
223
|
+
const taskAndHistory = await this.taskStore.load(params.taskId);
|
|
224
|
+
if (!taskAndHistory) {
|
|
225
|
+
throw A2AError.taskNotFound(params.taskId);
|
|
226
|
+
}
|
|
227
|
+
// Store the config. In a real app, this might be stored in the TaskStore
|
|
228
|
+
// or a dedicated push notification service.
|
|
229
|
+
this.pushNotificationConfigs.set(params.taskId, params.pushNotificationConfig);
|
|
230
|
+
return params;
|
|
231
|
+
}
|
|
232
|
+
async getTaskPushNotificationConfig(params) {
|
|
233
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
234
|
+
throw A2AError.pushNotificationNotSupported();
|
|
235
|
+
}
|
|
236
|
+
const taskAndHistory = await this.taskStore.load(params.id); // Ensure task exists
|
|
237
|
+
if (!taskAndHistory) {
|
|
238
|
+
throw A2AError.taskNotFound(params.id);
|
|
239
|
+
}
|
|
240
|
+
const config = this.pushNotificationConfigs.get(params.id);
|
|
241
|
+
if (!config) {
|
|
242
|
+
throw A2AError.internalError(`Push notification config not found for task ${params.id}.`);
|
|
243
|
+
}
|
|
244
|
+
return { taskId: params.id, pushNotificationConfig: config };
|
|
245
|
+
}
|
|
246
|
+
async *resubscribe(params) {
|
|
247
|
+
if (!this.agentCard.capabilities.streaming) {
|
|
248
|
+
throw A2AError.unsupportedOperation("Streaming (and thus resubscription) is not supported.");
|
|
249
|
+
}
|
|
250
|
+
const task = await this.taskStore.load(params.id);
|
|
251
|
+
if (!task) {
|
|
252
|
+
throw A2AError.taskNotFound(params.id);
|
|
253
|
+
}
|
|
254
|
+
// Yield the current task state first
|
|
255
|
+
yield task;
|
|
256
|
+
// If task is already in a final state, no more events will come.
|
|
257
|
+
const finalStates = ["completed", "failed", "canceled", "rejected"];
|
|
258
|
+
if (finalStates.includes(task.status.state)) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const eventBus = this.eventBusManager.getByTaskId(params.id);
|
|
262
|
+
if (!eventBus) {
|
|
263
|
+
// No active execution for this task, so no live events.
|
|
264
|
+
console.warn(`Resubscribe: No active event bus for task ${params.id}.`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
// Attach a new queue to the existing bus for this resubscription
|
|
268
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
269
|
+
// Note: The ResultManager part is already handled by the original execution flow.
|
|
270
|
+
// Resubscribe just listens for new events.
|
|
271
|
+
try {
|
|
272
|
+
for await (const event of eventQueue.events()) {
|
|
273
|
+
// We only care about updates related to *this* task.
|
|
274
|
+
// The event bus might be shared if messageId was reused, though
|
|
275
|
+
// ExecutionEventBusManager tries to give one bus per original message.
|
|
276
|
+
if (event.kind === 'status-update' && event.taskId === params.id) {
|
|
277
|
+
yield event;
|
|
278
|
+
}
|
|
279
|
+
else if (event.kind === 'artifact-update' && event.taskId === params.id) {
|
|
280
|
+
yield event;
|
|
281
|
+
}
|
|
282
|
+
else if (event.kind === 'task' && event.id === params.id) {
|
|
283
|
+
// This implies the task was re-emitted, yield it.
|
|
284
|
+
yield event;
|
|
285
|
+
}
|
|
286
|
+
// We don't yield 'message' events on resubscribe typically,
|
|
287
|
+
// as those signal the end of an interaction for the *original* request.
|
|
288
|
+
// If a 'message' event for the original request terminates the bus, this loop will also end.
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
eventQueue.stop();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=default_request_handler.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Message, Task } from "../types.js";
|
|
2
|
+
import { AgentExecutionEvent } from "./events/execution_event_bus.js";
|
|
3
|
+
import { TaskStore } from "./store.js";
|
|
4
|
+
export declare class ResultManager {
|
|
5
|
+
private taskStore;
|
|
6
|
+
private currentTask?;
|
|
7
|
+
private latestUserMessage?;
|
|
8
|
+
private finalMessageResult?;
|
|
9
|
+
constructor(taskStore: TaskStore);
|
|
10
|
+
setContext(latestUserMessage: Message): void;
|
|
11
|
+
/**
|
|
12
|
+
* Processes an agent execution event and updates the task store.
|
|
13
|
+
* @param event The agent execution event.
|
|
14
|
+
*/
|
|
15
|
+
processEvent(event: AgentExecutionEvent): Promise<void>;
|
|
16
|
+
private saveCurrentTask;
|
|
17
|
+
/**
|
|
18
|
+
* Gets the final result, which could be a Message or a Task.
|
|
19
|
+
* This should be called after the event stream has been fully processed.
|
|
20
|
+
* @returns The final Message or the current Task.
|
|
21
|
+
*/
|
|
22
|
+
getFinalResult(): Message | Task | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Gets the task currently being managed by this ResultManager instance.
|
|
25
|
+
* This task could be one that was started with or one created during agent execution.
|
|
26
|
+
* @returns The current Task or undefined if no task is active.
|
|
27
|
+
*/
|
|
28
|
+
getCurrentTask(): Task | undefined;
|
|
29
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export class ResultManager {
|
|
2
|
+
taskStore;
|
|
3
|
+
currentTask;
|
|
4
|
+
latestUserMessage; // To add to history if a new task is created
|
|
5
|
+
finalMessageResult; // Stores the message if it's the final result
|
|
6
|
+
constructor(taskStore) {
|
|
7
|
+
this.taskStore = taskStore;
|
|
8
|
+
}
|
|
9
|
+
setContext(latestUserMessage) {
|
|
10
|
+
this.latestUserMessage = latestUserMessage;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Processes an agent execution event and updates the task store.
|
|
14
|
+
* @param event The agent execution event.
|
|
15
|
+
*/
|
|
16
|
+
async processEvent(event) {
|
|
17
|
+
if (event.kind === 'message') {
|
|
18
|
+
this.finalMessageResult = event;
|
|
19
|
+
// If a message is received, it's usually the final result,
|
|
20
|
+
// but we continue processing to ensure task state (if any) is also saved.
|
|
21
|
+
// The ExecutionEventQueue will stop after a message event.
|
|
22
|
+
}
|
|
23
|
+
else if (event.kind === 'task') {
|
|
24
|
+
const taskEvent = event;
|
|
25
|
+
this.currentTask = { ...taskEvent }; // Make a copy
|
|
26
|
+
// Ensure the latest user message is in history if not already present
|
|
27
|
+
if (this.latestUserMessage) {
|
|
28
|
+
if (!this.currentTask.history?.find(msg => msg.messageId === this.latestUserMessage.messageId)) {
|
|
29
|
+
this.currentTask.history = [this.latestUserMessage, ...(this.currentTask.history || [])];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await this.saveCurrentTask();
|
|
33
|
+
}
|
|
34
|
+
else if (event.kind === 'status-update') {
|
|
35
|
+
const updateEvent = event;
|
|
36
|
+
if (this.currentTask && this.currentTask.id === updateEvent.taskId) {
|
|
37
|
+
this.currentTask.status = updateEvent.status;
|
|
38
|
+
if (updateEvent.status.message) {
|
|
39
|
+
// Add message to history if not already present
|
|
40
|
+
if (!this.currentTask.history?.find(msg => msg.messageId === updateEvent.status.message.messageId)) {
|
|
41
|
+
this.currentTask.history = [...(this.currentTask.history || []), updateEvent.status.message];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
await this.saveCurrentTask();
|
|
45
|
+
}
|
|
46
|
+
else if (!this.currentTask && updateEvent.taskId) {
|
|
47
|
+
// Potentially an update for a task we haven't seen the 'task' event for yet,
|
|
48
|
+
// or we are rehydrating. Attempt to load.
|
|
49
|
+
const loaded = await this.taskStore.load(updateEvent.taskId);
|
|
50
|
+
if (loaded) {
|
|
51
|
+
this.currentTask = loaded;
|
|
52
|
+
this.currentTask.status = updateEvent.status;
|
|
53
|
+
if (updateEvent.status.message) {
|
|
54
|
+
if (!this.currentTask.history?.find(msg => msg.messageId === updateEvent.status.message.messageId)) {
|
|
55
|
+
this.currentTask.history = [...(this.currentTask.history || []), updateEvent.status.message];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
await this.saveCurrentTask();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
console.warn(`ResultManager: Received status update for unknown task ${updateEvent.taskId}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// If it's a final status update, the ExecutionEventQueue will stop.
|
|
65
|
+
// The final result will be the currentTask.
|
|
66
|
+
}
|
|
67
|
+
else if (event.kind === 'artifact-update') {
|
|
68
|
+
const artifactEvent = event;
|
|
69
|
+
if (this.currentTask && this.currentTask.id === artifactEvent.taskId) {
|
|
70
|
+
if (!this.currentTask.artifacts) {
|
|
71
|
+
this.currentTask.artifacts = [];
|
|
72
|
+
}
|
|
73
|
+
const existingArtifactIndex = this.currentTask.artifacts.findIndex((art) => art.artifactId === artifactEvent.artifact.artifactId);
|
|
74
|
+
if (existingArtifactIndex !== -1) {
|
|
75
|
+
if (artifactEvent.append) {
|
|
76
|
+
// Basic append logic, assuming parts are compatible
|
|
77
|
+
// More sophisticated merging might be needed for specific part types
|
|
78
|
+
const existingArtifact = this.currentTask.artifacts[existingArtifactIndex];
|
|
79
|
+
existingArtifact.parts.push(...artifactEvent.artifact.parts);
|
|
80
|
+
if (artifactEvent.artifact.description)
|
|
81
|
+
existingArtifact.description = artifactEvent.artifact.description;
|
|
82
|
+
if (artifactEvent.artifact.name)
|
|
83
|
+
existingArtifact.name = artifactEvent.artifact.name;
|
|
84
|
+
if (artifactEvent.artifact.metadata)
|
|
85
|
+
existingArtifact.metadata = { ...existingArtifact.metadata, ...artifactEvent.artifact.metadata };
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.currentTask.artifacts[existingArtifactIndex] = artifactEvent.artifact;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.currentTask.artifacts.push(artifactEvent.artifact);
|
|
93
|
+
}
|
|
94
|
+
await this.saveCurrentTask();
|
|
95
|
+
}
|
|
96
|
+
else if (!this.currentTask && artifactEvent.taskId) {
|
|
97
|
+
// Similar to status update, try to load if task not in memory
|
|
98
|
+
const loaded = await this.taskStore.load(artifactEvent.taskId);
|
|
99
|
+
if (loaded) {
|
|
100
|
+
this.currentTask = loaded;
|
|
101
|
+
if (!this.currentTask.artifacts)
|
|
102
|
+
this.currentTask.artifacts = [];
|
|
103
|
+
// Apply artifact update logic (as above)
|
|
104
|
+
const existingArtifactIndex = this.currentTask.artifacts.findIndex((art) => art.artifactId === artifactEvent.artifact.artifactId);
|
|
105
|
+
if (existingArtifactIndex !== -1) {
|
|
106
|
+
if (artifactEvent.append) {
|
|
107
|
+
this.currentTask.artifacts[existingArtifactIndex].parts.push(...artifactEvent.artifact.parts);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.currentTask.artifacts[existingArtifactIndex] = artifactEvent.artifact;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.currentTask.artifacts.push(artifactEvent.artifact);
|
|
115
|
+
}
|
|
116
|
+
await this.saveCurrentTask();
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.warn(`ResultManager: Received artifact update for unknown task ${artifactEvent.taskId}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async saveCurrentTask() {
|
|
125
|
+
if (this.currentTask) {
|
|
126
|
+
await this.taskStore.save(this.currentTask);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Gets the final result, which could be a Message or a Task.
|
|
131
|
+
* This should be called after the event stream has been fully processed.
|
|
132
|
+
* @returns The final Message or the current Task.
|
|
133
|
+
*/
|
|
134
|
+
getFinalResult() {
|
|
135
|
+
if (this.finalMessageResult) {
|
|
136
|
+
return this.finalMessageResult;
|
|
137
|
+
}
|
|
138
|
+
return this.currentTask;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Gets the task currently being managed by this ResultManager instance.
|
|
142
|
+
* This task could be one that was started with or one created during agent execution.
|
|
143
|
+
* @returns The current Task or undefined if no task is active.
|
|
144
|
+
*/
|
|
145
|
+
getCurrentTask() {
|
|
146
|
+
return this.currentTask;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=result_manager.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Task } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Simplified interface for task storage providers.
|
|
4
|
+
* Stores and retrieves the task.
|
|
5
|
+
*/
|
|
6
|
+
export interface TaskStore {
|
|
7
|
+
/**
|
|
8
|
+
* Saves a task.
|
|
9
|
+
* Overwrites existing data if the task ID exists.
|
|
10
|
+
* @param data An object containing the task.
|
|
11
|
+
* @returns A promise resolving when the save operation is complete.
|
|
12
|
+
*/
|
|
13
|
+
save(task: Task): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Loads a task by task ID.
|
|
16
|
+
* @param taskId The ID of the task to load.
|
|
17
|
+
* @returns A promise resolving to an object containing the Task, or undefined if not found.
|
|
18
|
+
*/
|
|
19
|
+
load(taskId: string): Promise<Task | undefined>;
|
|
20
|
+
}
|
|
21
|
+
export declare class InMemoryTaskStore implements TaskStore {
|
|
22
|
+
private store;
|
|
23
|
+
load(taskId: string): Promise<Task | undefined>;
|
|
24
|
+
save(task: Task): Promise<void>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// ========================
|
|
2
|
+
// InMemoryTaskStore
|
|
3
|
+
// ========================
|
|
4
|
+
// Use Task directly for storage
|
|
5
|
+
export class InMemoryTaskStore {
|
|
6
|
+
store = new Map();
|
|
7
|
+
async load(taskId) {
|
|
8
|
+
const entry = this.store.get(taskId);
|
|
9
|
+
// Return copies to prevent external mutation
|
|
10
|
+
return entry ? { ...entry } : undefined;
|
|
11
|
+
}
|
|
12
|
+
async save(task) {
|
|
13
|
+
// Store copies to prevent internal mutation if caller reuses objects
|
|
14
|
+
this.store.set(task.id, { ...task });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { A2AResponse } from "../../a2a_response.js";
|
|
2
|
+
import { A2ARequestHandler } from "../request_handler/a2a_request_handler.js";
|
|
3
|
+
/**
|
|
4
|
+
* Handles JSON-RPC transport layer, routing requests to A2ARequestHandler.
|
|
5
|
+
*/
|
|
6
|
+
export declare class JsonRpcTransportHandler {
|
|
7
|
+
private requestHandler;
|
|
8
|
+
constructor(requestHandler: A2ARequestHandler);
|
|
9
|
+
/**
|
|
10
|
+
* Handles an incoming JSON-RPC request.
|
|
11
|
+
* For streaming methods, it returns an AsyncGenerator of JSONRPCResult.
|
|
12
|
+
* For non-streaming methods, it returns a Promise of a single JSONRPCMessage (Result or ErrorResponse).
|
|
13
|
+
*/
|
|
14
|
+
handle(requestBody: any): Promise<A2AResponse | AsyncGenerator<A2AResponse, void, undefined>>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { A2AError } from "../error.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handles JSON-RPC transport layer, routing requests to A2ARequestHandler.
|
|
4
|
+
*/
|
|
5
|
+
export class JsonRpcTransportHandler {
|
|
6
|
+
requestHandler;
|
|
7
|
+
constructor(requestHandler) {
|
|
8
|
+
this.requestHandler = requestHandler;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Handles an incoming JSON-RPC request.
|
|
12
|
+
* For streaming methods, it returns an AsyncGenerator of JSONRPCResult.
|
|
13
|
+
* For non-streaming methods, it returns a Promise of a single JSONRPCMessage (Result or ErrorResponse).
|
|
14
|
+
*/
|
|
15
|
+
async handle(requestBody) {
|
|
16
|
+
let rpcRequest;
|
|
17
|
+
try {
|
|
18
|
+
if (typeof requestBody === 'string') {
|
|
19
|
+
rpcRequest = JSON.parse(requestBody);
|
|
20
|
+
}
|
|
21
|
+
else if (typeof requestBody === 'object' && requestBody !== null) {
|
|
22
|
+
rpcRequest = requestBody;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
throw A2AError.parseError('Invalid request body type.');
|
|
26
|
+
}
|
|
27
|
+
if (rpcRequest.jsonrpc !== '2.0' ||
|
|
28
|
+
!rpcRequest.method ||
|
|
29
|
+
typeof rpcRequest.method !== 'string') {
|
|
30
|
+
throw A2AError.invalidRequest('Invalid JSON-RPC request structure.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const a2aError = error instanceof A2AError ? error : A2AError.parseError(error.message || 'Failed to parse JSON request.');
|
|
35
|
+
return {
|
|
36
|
+
jsonrpc: '2.0',
|
|
37
|
+
id: (typeof rpcRequest?.id !== 'undefined' ? rpcRequest.id : null),
|
|
38
|
+
error: a2aError.toJSONRPCError(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const { method, params = {}, id: requestId = null } = rpcRequest;
|
|
42
|
+
try {
|
|
43
|
+
if (method === 'message/stream' || method === 'tasks/resubscribe') {
|
|
44
|
+
const agentCard = await this.requestHandler.getAgentCard();
|
|
45
|
+
if (!agentCard.capabilities.streaming) {
|
|
46
|
+
throw A2AError.unsupportedOperation(`Method ${method} requires streaming capability.`);
|
|
47
|
+
}
|
|
48
|
+
const agentEventStream = method === 'message/stream'
|
|
49
|
+
? this.requestHandler.sendMessageStream(params)
|
|
50
|
+
: this.requestHandler.resubscribe(params);
|
|
51
|
+
// Wrap the agent event stream into a JSON-RPC result stream
|
|
52
|
+
return (async function* jsonRpcEventStream() {
|
|
53
|
+
try {
|
|
54
|
+
for await (const event of agentEventStream) {
|
|
55
|
+
yield {
|
|
56
|
+
jsonrpc: '2.0',
|
|
57
|
+
id: requestId, // Use the original request ID for all streamed responses
|
|
58
|
+
result: event,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (streamError) {
|
|
63
|
+
// If the underlying agent stream throws an error, we need to yield a JSONRPCErrorResponse.
|
|
64
|
+
// However, an AsyncGenerator is expected to yield JSONRPCResult.
|
|
65
|
+
// This indicates an issue with how errors from the agent's stream are propagated.
|
|
66
|
+
// For now, log it. The Express layer will handle the generator ending.
|
|
67
|
+
console.error(`Error in agent event stream for ${method} (request ${requestId}):`, streamError);
|
|
68
|
+
// Ideally, the Express layer should catch this and send a final error to the client if the stream breaks.
|
|
69
|
+
// Or, the agentEventStream itself should yield a final error event that gets wrapped.
|
|
70
|
+
// For now, we re-throw so it can be caught by A2AExpressApp's stream handling.
|
|
71
|
+
throw streamError;
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Handle non-streaming methods
|
|
77
|
+
let result;
|
|
78
|
+
switch (method) {
|
|
79
|
+
case 'message/send':
|
|
80
|
+
result = await this.requestHandler.sendMessage(params);
|
|
81
|
+
break;
|
|
82
|
+
case 'tasks/get':
|
|
83
|
+
result = await this.requestHandler.getTask(params);
|
|
84
|
+
break;
|
|
85
|
+
case 'tasks/cancel':
|
|
86
|
+
result = await this.requestHandler.cancelTask(params);
|
|
87
|
+
break;
|
|
88
|
+
case 'tasks/pushNotificationConfig/set':
|
|
89
|
+
result = await this.requestHandler.setTaskPushNotificationConfig(params);
|
|
90
|
+
break;
|
|
91
|
+
case 'tasks/pushNotificationConfig/get':
|
|
92
|
+
result = await this.requestHandler.getTaskPushNotificationConfig(params);
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
throw A2AError.methodNotFound(method);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
jsonrpc: '2.0',
|
|
99
|
+
id: requestId,
|
|
100
|
+
result: result,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
const a2aError = error instanceof A2AError ? error : A2AError.internalError(error.message || 'An unexpected error occurred.');
|
|
106
|
+
return {
|
|
107
|
+
jsonrpc: '2.0',
|
|
108
|
+
id: requestId,
|
|
109
|
+
error: a2aError.toJSONRPCError(),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=jsonrpc_transport_handler.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { TaskStatus, Artifact } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generates a timestamp in ISO 8601 format.
|
|
4
|
+
* @returns The current timestamp as a string.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getCurrentTimestamp(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Checks if a value is a plain object (excluding arrays and null).
|
|
9
|
+
* @param value The value to check.
|
|
10
|
+
* @returns True if the value is a plain object, false otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isObject(value: unknown): value is Record<string, any>;
|
|
13
|
+
/**
|
|
14
|
+
* Type guard to check if an object is a TaskStatus update (lacks 'parts').
|
|
15
|
+
* Used to differentiate yielded updates from the handler.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isTaskStatusUpdate(update: any): update is Omit<TaskStatus, "timestamp">;
|
|
18
|
+
/**
|
|
19
|
+
* Type guard to check if an object is an Artifact update (has 'parts').
|
|
20
|
+
* Used to differentiate yielded updates from the handler.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isArtifactUpdate(update: any): update is Artifact;
|