@mailmodo/a2a 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.
- package/README.md +272 -138
- package/dist/a2a_request_handler-DPkhsCMt.d.mts +37 -0
- package/dist/a2a_request_handler-DQfg1Q-R.d.ts +37 -0
- package/dist/auth-handler-DVLcl8yj.d.mts +209 -0
- package/dist/auth-handler-Gzpf3xHC.d.ts +209 -0
- package/dist/chunk-HZFUOBJQ.mjs +198 -0
- package/dist/chunk-LIEYEFQG.mjs +879 -0
- package/dist/chunk-LVD4GF26.mjs +262 -0
- package/dist/chunk-PHP7LM4Y.mjs +8 -0
- package/dist/chunk-UBRSFN2J.mjs +776 -0
- package/dist/client/index.d.mts +312 -0
- package/dist/client/index.d.ts +312 -0
- package/dist/client/index.js +1158 -0
- package/dist/client/index.mjs +367 -0
- package/dist/error-DExKs0Q3.d.mts +233 -0
- package/dist/error-j1vYKII2.d.ts +233 -0
- package/dist/index.d.mts +14 -2739
- package/dist/index.d.ts +14 -2739
- package/dist/index.js +1605 -1158
- package/dist/index.mjs +29 -1612
- package/dist/server/express/index.d.mts +25 -0
- package/dist/server/express/index.d.ts +25 -0
- package/dist/server/express/index.js +468 -0
- package/dist/server/express/index.mjs +10 -0
- package/dist/server/index.d.mts +26 -0
- package/dist/server/index.d.ts +26 -0
- package/dist/server/index.js +1173 -0
- package/dist/server/index.mjs +32 -0
- package/dist/types-Due_Cv6t.d.mts +2550 -0
- package/dist/types-Due_Cv6t.d.ts +2550 -0
- package/package.json +18 -11
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
import {
|
|
2
|
+
A2AError,
|
|
3
|
+
ServerCallContext
|
|
4
|
+
} from "./chunk-LVD4GF26.mjs";
|
|
5
|
+
|
|
6
|
+
// src/server/agent_execution/request_context.ts
|
|
7
|
+
var RequestContext = class {
|
|
8
|
+
userMessage;
|
|
9
|
+
taskId;
|
|
10
|
+
contextId;
|
|
11
|
+
task;
|
|
12
|
+
referenceTasks;
|
|
13
|
+
context;
|
|
14
|
+
constructor(userMessage, taskId, contextId, task, referenceTasks, context) {
|
|
15
|
+
this.userMessage = userMessage;
|
|
16
|
+
this.taskId = taskId;
|
|
17
|
+
this.contextId = contextId;
|
|
18
|
+
this.task = task;
|
|
19
|
+
this.referenceTasks = referenceTasks;
|
|
20
|
+
this.context = context;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/server/events/execution_event_bus.ts
|
|
25
|
+
import { EventEmitter } from "events";
|
|
26
|
+
var DefaultExecutionEventBus = class extends EventEmitter {
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
}
|
|
30
|
+
publish(event) {
|
|
31
|
+
this.emit("event", event);
|
|
32
|
+
}
|
|
33
|
+
finished() {
|
|
34
|
+
this.emit("finished");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/server/events/execution_event_bus_manager.ts
|
|
39
|
+
var DefaultExecutionEventBusManager = class {
|
|
40
|
+
taskIdToBus = /* @__PURE__ */ new Map();
|
|
41
|
+
/**
|
|
42
|
+
* Creates or retrieves an existing ExecutionEventBus based on the taskId.
|
|
43
|
+
* @param taskId The ID of the task.
|
|
44
|
+
* @returns An instance of IExecutionEventBus.
|
|
45
|
+
*/
|
|
46
|
+
createOrGetByTaskId(taskId) {
|
|
47
|
+
if (!this.taskIdToBus.has(taskId)) {
|
|
48
|
+
this.taskIdToBus.set(taskId, new DefaultExecutionEventBus());
|
|
49
|
+
}
|
|
50
|
+
return this.taskIdToBus.get(taskId);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Retrieves an existing ExecutionEventBus based on the taskId.
|
|
54
|
+
* @param taskId The ID of the task.
|
|
55
|
+
* @returns An instance of IExecutionEventBus or undefined if not found.
|
|
56
|
+
*/
|
|
57
|
+
getByTaskId(taskId) {
|
|
58
|
+
return this.taskIdToBus.get(taskId);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Removes the event bus for a given taskId.
|
|
62
|
+
* This should be called when an execution flow is complete to free resources.
|
|
63
|
+
* @param taskId The ID of the task.
|
|
64
|
+
*/
|
|
65
|
+
cleanupByTaskId(taskId) {
|
|
66
|
+
const bus = this.taskIdToBus.get(taskId);
|
|
67
|
+
if (bus) {
|
|
68
|
+
bus.removeAllListeners();
|
|
69
|
+
}
|
|
70
|
+
this.taskIdToBus.delete(taskId);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/server/events/execution_event_queue.ts
|
|
75
|
+
var ExecutionEventQueue = class {
|
|
76
|
+
eventBus;
|
|
77
|
+
eventQueue = [];
|
|
78
|
+
resolvePromise;
|
|
79
|
+
stopped = false;
|
|
80
|
+
boundHandleEvent;
|
|
81
|
+
constructor(eventBus) {
|
|
82
|
+
this.eventBus = eventBus;
|
|
83
|
+
this.eventBus.on("event", this.handleEvent);
|
|
84
|
+
this.eventBus.on("finished", this.handleFinished);
|
|
85
|
+
}
|
|
86
|
+
handleEvent = (event) => {
|
|
87
|
+
if (this.stopped) return;
|
|
88
|
+
this.eventQueue.push(event);
|
|
89
|
+
if (this.resolvePromise) {
|
|
90
|
+
this.resolvePromise();
|
|
91
|
+
this.resolvePromise = void 0;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
handleFinished = () => {
|
|
95
|
+
this.stop();
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Provides an async generator that yields events from the event bus.
|
|
99
|
+
* Stops when a Message event is received or a TaskStatusUpdateEvent with final=true is received.
|
|
100
|
+
*/
|
|
101
|
+
async *events() {
|
|
102
|
+
while (!this.stopped || this.eventQueue.length > 0) {
|
|
103
|
+
if (this.eventQueue.length > 0) {
|
|
104
|
+
const event = this.eventQueue.shift();
|
|
105
|
+
yield event;
|
|
106
|
+
if (event.kind === "message" || event.kind === "status-update" && event.final) {
|
|
107
|
+
this.handleFinished();
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
} else if (!this.stopped) {
|
|
111
|
+
await new Promise((resolve) => {
|
|
112
|
+
this.resolvePromise = resolve;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Stops the event queue from processing further events.
|
|
119
|
+
*/
|
|
120
|
+
stop() {
|
|
121
|
+
this.stopped = true;
|
|
122
|
+
if (this.resolvePromise) {
|
|
123
|
+
this.resolvePromise();
|
|
124
|
+
this.resolvePromise = void 0;
|
|
125
|
+
}
|
|
126
|
+
this.eventBus.off("event", this.handleEvent);
|
|
127
|
+
this.eventBus.off("finished", this.handleFinished);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/server/result_manager.ts
|
|
132
|
+
var ResultManager = class {
|
|
133
|
+
taskStore;
|
|
134
|
+
currentTask;
|
|
135
|
+
latestUserMessage;
|
|
136
|
+
// To add to history if a new task is created
|
|
137
|
+
finalMessageResult;
|
|
138
|
+
// Stores the message if it's the final result
|
|
139
|
+
constructor(taskStore) {
|
|
140
|
+
this.taskStore = taskStore;
|
|
141
|
+
}
|
|
142
|
+
setContext(latestUserMessage) {
|
|
143
|
+
this.latestUserMessage = latestUserMessage;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Processes an agent execution event and updates the task store.
|
|
147
|
+
* @param event The agent execution event.
|
|
148
|
+
*/
|
|
149
|
+
async processEvent(event) {
|
|
150
|
+
if (event.kind === "message") {
|
|
151
|
+
this.finalMessageResult = event;
|
|
152
|
+
} else if (event.kind === "task") {
|
|
153
|
+
const taskEvent = event;
|
|
154
|
+
this.currentTask = { ...taskEvent };
|
|
155
|
+
if (this.latestUserMessage) {
|
|
156
|
+
if (!this.currentTask.history?.find(
|
|
157
|
+
(msg) => msg.messageId === this.latestUserMessage.messageId
|
|
158
|
+
)) {
|
|
159
|
+
this.currentTask.history = [this.latestUserMessage, ...this.currentTask.history || []];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
await this.saveCurrentTask();
|
|
163
|
+
} else if (event.kind === "status-update") {
|
|
164
|
+
const updateEvent = event;
|
|
165
|
+
if (this.currentTask && this.currentTask.id === updateEvent.taskId) {
|
|
166
|
+
this.currentTask.status = updateEvent.status;
|
|
167
|
+
if (updateEvent.status.message) {
|
|
168
|
+
if (!this.currentTask.history?.find(
|
|
169
|
+
(msg) => msg.messageId === updateEvent.status.message.messageId
|
|
170
|
+
)) {
|
|
171
|
+
this.currentTask.history = [
|
|
172
|
+
...this.currentTask.history || [],
|
|
173
|
+
updateEvent.status.message
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
await this.saveCurrentTask();
|
|
178
|
+
} else if (!this.currentTask && updateEvent.taskId) {
|
|
179
|
+
const loaded = await this.taskStore.load(updateEvent.taskId);
|
|
180
|
+
if (loaded) {
|
|
181
|
+
this.currentTask = loaded;
|
|
182
|
+
this.currentTask.status = updateEvent.status;
|
|
183
|
+
if (updateEvent.status.message) {
|
|
184
|
+
if (!this.currentTask.history?.find(
|
|
185
|
+
(msg) => msg.messageId === updateEvent.status.message.messageId
|
|
186
|
+
)) {
|
|
187
|
+
this.currentTask.history = [
|
|
188
|
+
...this.currentTask.history || [],
|
|
189
|
+
updateEvent.status.message
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
await this.saveCurrentTask();
|
|
194
|
+
} else {
|
|
195
|
+
console.warn(
|
|
196
|
+
`ResultManager: Received status update for unknown task ${updateEvent.taskId}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} else if (event.kind === "artifact-update") {
|
|
201
|
+
const artifactEvent = event;
|
|
202
|
+
if (this.currentTask && this.currentTask.id === artifactEvent.taskId) {
|
|
203
|
+
if (!this.currentTask.artifacts) {
|
|
204
|
+
this.currentTask.artifacts = [];
|
|
205
|
+
}
|
|
206
|
+
const existingArtifactIndex = this.currentTask.artifacts.findIndex(
|
|
207
|
+
(art) => art.artifactId === artifactEvent.artifact.artifactId
|
|
208
|
+
);
|
|
209
|
+
if (existingArtifactIndex !== -1) {
|
|
210
|
+
if (artifactEvent.append) {
|
|
211
|
+
const existingArtifact = this.currentTask.artifacts[existingArtifactIndex];
|
|
212
|
+
existingArtifact.parts.push(...artifactEvent.artifact.parts);
|
|
213
|
+
if (artifactEvent.artifact.description)
|
|
214
|
+
existingArtifact.description = artifactEvent.artifact.description;
|
|
215
|
+
if (artifactEvent.artifact.name) existingArtifact.name = artifactEvent.artifact.name;
|
|
216
|
+
if (artifactEvent.artifact.metadata)
|
|
217
|
+
existingArtifact.metadata = {
|
|
218
|
+
...existingArtifact.metadata,
|
|
219
|
+
...artifactEvent.artifact.metadata
|
|
220
|
+
};
|
|
221
|
+
} else {
|
|
222
|
+
this.currentTask.artifacts[existingArtifactIndex] = artifactEvent.artifact;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
this.currentTask.artifacts.push(artifactEvent.artifact);
|
|
226
|
+
}
|
|
227
|
+
await this.saveCurrentTask();
|
|
228
|
+
} else if (!this.currentTask && artifactEvent.taskId) {
|
|
229
|
+
const loaded = await this.taskStore.load(artifactEvent.taskId);
|
|
230
|
+
if (loaded) {
|
|
231
|
+
this.currentTask = loaded;
|
|
232
|
+
if (!this.currentTask.artifacts) this.currentTask.artifacts = [];
|
|
233
|
+
const existingArtifactIndex = this.currentTask.artifacts.findIndex(
|
|
234
|
+
(art) => art.artifactId === artifactEvent.artifact.artifactId
|
|
235
|
+
);
|
|
236
|
+
if (existingArtifactIndex !== -1) {
|
|
237
|
+
if (artifactEvent.append) {
|
|
238
|
+
this.currentTask.artifacts[existingArtifactIndex].parts.push(
|
|
239
|
+
...artifactEvent.artifact.parts
|
|
240
|
+
);
|
|
241
|
+
} else {
|
|
242
|
+
this.currentTask.artifacts[existingArtifactIndex] = artifactEvent.artifact;
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
this.currentTask.artifacts.push(artifactEvent.artifact);
|
|
246
|
+
}
|
|
247
|
+
await this.saveCurrentTask();
|
|
248
|
+
} else {
|
|
249
|
+
console.warn(
|
|
250
|
+
`ResultManager: Received artifact update for unknown task ${artifactEvent.taskId}`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async saveCurrentTask() {
|
|
257
|
+
if (this.currentTask) {
|
|
258
|
+
await this.taskStore.save(this.currentTask);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Gets the final result, which could be a Message or a Task.
|
|
263
|
+
* This should be called after the event stream has been fully processed.
|
|
264
|
+
* @returns The final Message or the current Task.
|
|
265
|
+
*/
|
|
266
|
+
getFinalResult() {
|
|
267
|
+
if (this.finalMessageResult) {
|
|
268
|
+
return this.finalMessageResult;
|
|
269
|
+
}
|
|
270
|
+
return this.currentTask;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Gets the task currently being managed by this ResultManager instance.
|
|
274
|
+
* This task could be one that was started with or one created during agent execution.
|
|
275
|
+
* @returns The current Task or undefined if no task is active.
|
|
276
|
+
*/
|
|
277
|
+
getCurrentTask() {
|
|
278
|
+
return this.currentTask;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/server/push_notification/push_notification_store.ts
|
|
283
|
+
var InMemoryPushNotificationStore = class {
|
|
284
|
+
store = /* @__PURE__ */ new Map();
|
|
285
|
+
async save(taskId, pushNotificationConfig) {
|
|
286
|
+
const configs = this.store.get(taskId) || [];
|
|
287
|
+
if (!pushNotificationConfig.id) {
|
|
288
|
+
pushNotificationConfig.id = taskId;
|
|
289
|
+
}
|
|
290
|
+
const existingIndex = configs.findIndex((config) => config.id === pushNotificationConfig.id);
|
|
291
|
+
if (existingIndex !== -1) {
|
|
292
|
+
configs.splice(existingIndex, 1);
|
|
293
|
+
}
|
|
294
|
+
configs.push(pushNotificationConfig);
|
|
295
|
+
this.store.set(taskId, configs);
|
|
296
|
+
}
|
|
297
|
+
async load(taskId) {
|
|
298
|
+
const configs = this.store.get(taskId);
|
|
299
|
+
return configs || [];
|
|
300
|
+
}
|
|
301
|
+
async delete(taskId, configId) {
|
|
302
|
+
if (configId === void 0) {
|
|
303
|
+
configId = taskId;
|
|
304
|
+
}
|
|
305
|
+
const configs = this.store.get(taskId);
|
|
306
|
+
if (!configs) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const configIndex = configs.findIndex((config) => config.id === configId);
|
|
310
|
+
if (configIndex !== -1) {
|
|
311
|
+
configs.splice(configIndex, 1);
|
|
312
|
+
}
|
|
313
|
+
if (configs.length === 0) {
|
|
314
|
+
this.store.delete(taskId);
|
|
315
|
+
} else {
|
|
316
|
+
this.store.set(taskId, configs);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// src/server/push_notification/default_push_notification_sender.ts
|
|
322
|
+
var DefaultPushNotificationSender = class {
|
|
323
|
+
pushNotificationStore;
|
|
324
|
+
notificationChain;
|
|
325
|
+
options;
|
|
326
|
+
constructor(pushNotificationStore, options = {}) {
|
|
327
|
+
this.pushNotificationStore = pushNotificationStore;
|
|
328
|
+
this.notificationChain = /* @__PURE__ */ new Map();
|
|
329
|
+
this.options = {
|
|
330
|
+
timeout: 5e3,
|
|
331
|
+
tokenHeaderName: "X-A2A-Notification-Token",
|
|
332
|
+
...options
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
async send(task) {
|
|
336
|
+
const pushConfigs = await this.pushNotificationStore.load(task.id);
|
|
337
|
+
if (!pushConfigs || pushConfigs.length === 0) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const lastPromise = this.notificationChain.get(task.id) ?? Promise.resolve();
|
|
341
|
+
const newPromise = lastPromise.then(async () => {
|
|
342
|
+
const dispatches = pushConfigs.map(async (pushConfig) => {
|
|
343
|
+
try {
|
|
344
|
+
await this._dispatchNotification(task, pushConfig);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(
|
|
347
|
+
`Error sending push notification for task_id=${task.id} to URL: ${pushConfig.url}. Error:`,
|
|
348
|
+
error
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
await Promise.all(dispatches);
|
|
353
|
+
});
|
|
354
|
+
this.notificationChain.set(task.id, newPromise);
|
|
355
|
+
newPromise.finally(() => {
|
|
356
|
+
if (this.notificationChain.get(task.id) === newPromise) {
|
|
357
|
+
this.notificationChain.delete(task.id);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
async _dispatchNotification(task, pushConfig) {
|
|
362
|
+
const url = pushConfig.url;
|
|
363
|
+
const controller = new AbortController();
|
|
364
|
+
const timeoutId = setTimeout(() => controller.abort(), this.options.timeout);
|
|
365
|
+
try {
|
|
366
|
+
const headers = {
|
|
367
|
+
"Content-Type": "application/json"
|
|
368
|
+
};
|
|
369
|
+
if (pushConfig.token) {
|
|
370
|
+
headers[this.options.tokenHeaderName] = pushConfig.token;
|
|
371
|
+
}
|
|
372
|
+
const response = await fetch(url, {
|
|
373
|
+
method: "POST",
|
|
374
|
+
headers,
|
|
375
|
+
body: JSON.stringify(task),
|
|
376
|
+
signal: controller.signal
|
|
377
|
+
});
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
380
|
+
}
|
|
381
|
+
console.info(`Push notification sent for task_id=${task.id} to URL: ${url}`);
|
|
382
|
+
} finally {
|
|
383
|
+
clearTimeout(timeoutId);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// src/server/request_handler/default_request_handler.ts
|
|
389
|
+
import { v4 as uuidv4 } from "uuid";
|
|
390
|
+
var terminalStates = ["completed", "failed", "canceled", "rejected"];
|
|
391
|
+
var DefaultRequestHandler = class {
|
|
392
|
+
agentCard;
|
|
393
|
+
taskStore;
|
|
394
|
+
agentExecutor;
|
|
395
|
+
eventBusManager;
|
|
396
|
+
pushNotificationStore;
|
|
397
|
+
pushNotificationSender;
|
|
398
|
+
extendedAgentCardProvider;
|
|
399
|
+
constructor(agentCard, taskStore, agentExecutor, eventBusManager = new DefaultExecutionEventBusManager(), pushNotificationStore, pushNotificationSender, extendedAgentCardProvider) {
|
|
400
|
+
this.agentCard = agentCard;
|
|
401
|
+
this.taskStore = taskStore;
|
|
402
|
+
this.agentExecutor = agentExecutor;
|
|
403
|
+
this.eventBusManager = eventBusManager;
|
|
404
|
+
this.extendedAgentCardProvider = extendedAgentCardProvider;
|
|
405
|
+
if (agentCard.capabilities.pushNotifications) {
|
|
406
|
+
this.pushNotificationStore = pushNotificationStore || new InMemoryPushNotificationStore();
|
|
407
|
+
this.pushNotificationSender = pushNotificationSender || new DefaultPushNotificationSender(this.pushNotificationStore);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async getAgentCard() {
|
|
411
|
+
return this.agentCard;
|
|
412
|
+
}
|
|
413
|
+
async getAuthenticatedExtendedAgentCard(context) {
|
|
414
|
+
if (!this.agentCard.supportsAuthenticatedExtendedCard) {
|
|
415
|
+
throw A2AError.unsupportedOperation("Agent does not support authenticated extended card.");
|
|
416
|
+
}
|
|
417
|
+
if (!this.extendedAgentCardProvider) {
|
|
418
|
+
throw A2AError.authenticatedExtendedCardNotConfigured();
|
|
419
|
+
}
|
|
420
|
+
if (typeof this.extendedAgentCardProvider === "function") {
|
|
421
|
+
return this.extendedAgentCardProvider(context);
|
|
422
|
+
}
|
|
423
|
+
if (context?.user?.isAuthenticated) {
|
|
424
|
+
return this.extendedAgentCardProvider;
|
|
425
|
+
}
|
|
426
|
+
return this.agentCard;
|
|
427
|
+
}
|
|
428
|
+
async _createRequestContext(incomingMessage, context) {
|
|
429
|
+
let task;
|
|
430
|
+
let referenceTasks;
|
|
431
|
+
if (incomingMessage.taskId) {
|
|
432
|
+
task = await this.taskStore.load(incomingMessage.taskId);
|
|
433
|
+
if (!task) {
|
|
434
|
+
throw A2AError.taskNotFound(incomingMessage.taskId);
|
|
435
|
+
}
|
|
436
|
+
if (terminalStates.includes(task.status.state)) {
|
|
437
|
+
throw A2AError.invalidRequest(
|
|
438
|
+
`Task ${task.id} is in a terminal state (${task.status.state}) and cannot be modified.`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
task.history = [...task.history || [], incomingMessage];
|
|
442
|
+
await this.taskStore.save(task);
|
|
443
|
+
}
|
|
444
|
+
const taskId = incomingMessage.taskId || uuidv4();
|
|
445
|
+
if (incomingMessage.referenceTaskIds && incomingMessage.referenceTaskIds.length > 0) {
|
|
446
|
+
referenceTasks = [];
|
|
447
|
+
for (const refId of incomingMessage.referenceTaskIds) {
|
|
448
|
+
const refTask = await this.taskStore.load(refId);
|
|
449
|
+
if (refTask) {
|
|
450
|
+
referenceTasks.push(refTask);
|
|
451
|
+
} else {
|
|
452
|
+
console.warn(`Reference task ${refId} not found.`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
const contextId = incomingMessage.contextId || task?.contextId || uuidv4();
|
|
457
|
+
if (context?.requestedExtensions) {
|
|
458
|
+
const agentCard = await this.getAgentCard();
|
|
459
|
+
const exposedExtensions = new Set(
|
|
460
|
+
agentCard.capabilities.extensions?.map((ext) => ext.uri) || []
|
|
461
|
+
);
|
|
462
|
+
const validExtensions = new Set(
|
|
463
|
+
Array.from(context.requestedExtensions).filter(
|
|
464
|
+
(extension) => exposedExtensions.has(extension)
|
|
465
|
+
)
|
|
466
|
+
);
|
|
467
|
+
context = new ServerCallContext(validExtensions, context.user);
|
|
468
|
+
}
|
|
469
|
+
const messageForContext = {
|
|
470
|
+
...incomingMessage,
|
|
471
|
+
contextId,
|
|
472
|
+
taskId
|
|
473
|
+
};
|
|
474
|
+
return new RequestContext(messageForContext, taskId, contextId, task, referenceTasks, context);
|
|
475
|
+
}
|
|
476
|
+
async _processEvents(taskId, resultManager, eventQueue, options) {
|
|
477
|
+
let firstResultSent = false;
|
|
478
|
+
try {
|
|
479
|
+
for await (const event of eventQueue.events()) {
|
|
480
|
+
await resultManager.processEvent(event);
|
|
481
|
+
try {
|
|
482
|
+
await this._sendPushNotificationIfNeeded(event);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
console.error(`Error sending push notification: ${error}`);
|
|
485
|
+
}
|
|
486
|
+
if (options?.firstResultResolver && !firstResultSent) {
|
|
487
|
+
let firstResult;
|
|
488
|
+
if (event.kind === "message") {
|
|
489
|
+
firstResult = event;
|
|
490
|
+
} else {
|
|
491
|
+
firstResult = resultManager.getCurrentTask();
|
|
492
|
+
}
|
|
493
|
+
if (firstResult) {
|
|
494
|
+
options.firstResultResolver(firstResult);
|
|
495
|
+
firstResultSent = true;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (options?.firstResultRejector && !firstResultSent) {
|
|
500
|
+
options.firstResultRejector(
|
|
501
|
+
A2AError.internalError("Execution finished before a message or task was produced.")
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
} catch (error) {
|
|
505
|
+
console.error(`Event processing loop failed for task ${taskId}:`, error);
|
|
506
|
+
this._handleProcessingError(
|
|
507
|
+
error,
|
|
508
|
+
resultManager,
|
|
509
|
+
firstResultSent,
|
|
510
|
+
taskId,
|
|
511
|
+
options?.firstResultRejector
|
|
512
|
+
);
|
|
513
|
+
} finally {
|
|
514
|
+
this.eventBusManager.cleanupByTaskId(taskId);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async sendMessage(params, context) {
|
|
518
|
+
const incomingMessage = params.message;
|
|
519
|
+
if (!incomingMessage.messageId) {
|
|
520
|
+
throw A2AError.invalidParams("message.messageId is required.");
|
|
521
|
+
}
|
|
522
|
+
const isBlocking = params.configuration?.blocking !== false;
|
|
523
|
+
const resultManager = new ResultManager(this.taskStore);
|
|
524
|
+
resultManager.setContext(incomingMessage);
|
|
525
|
+
const requestContext = await this._createRequestContext(incomingMessage, context);
|
|
526
|
+
const taskId = requestContext.taskId;
|
|
527
|
+
const finalMessageForAgent = requestContext.userMessage;
|
|
528
|
+
if (params.configuration?.pushNotificationConfig && this.agentCard.capabilities.pushNotifications) {
|
|
529
|
+
await this.pushNotificationStore?.save(taskId, params.configuration.pushNotificationConfig);
|
|
530
|
+
}
|
|
531
|
+
const eventBus = this.eventBusManager.createOrGetByTaskId(taskId);
|
|
532
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
533
|
+
this.agentExecutor.execute(requestContext, eventBus).catch((err) => {
|
|
534
|
+
console.error(`Agent execution failed for message ${finalMessageForAgent.messageId}:`, err);
|
|
535
|
+
const errorTask = {
|
|
536
|
+
id: requestContext.task?.id || uuidv4(),
|
|
537
|
+
// Use existing task ID or generate new
|
|
538
|
+
contextId: finalMessageForAgent.contextId,
|
|
539
|
+
status: {
|
|
540
|
+
state: "failed",
|
|
541
|
+
message: {
|
|
542
|
+
kind: "message",
|
|
543
|
+
role: "agent",
|
|
544
|
+
messageId: uuidv4(),
|
|
545
|
+
parts: [{ kind: "text", text: `Agent execution error: ${err.message}` }],
|
|
546
|
+
taskId: requestContext.task?.id,
|
|
547
|
+
contextId: finalMessageForAgent.contextId
|
|
548
|
+
},
|
|
549
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
550
|
+
},
|
|
551
|
+
history: requestContext.task?.history ? [...requestContext.task.history] : [],
|
|
552
|
+
kind: "task"
|
|
553
|
+
};
|
|
554
|
+
if (finalMessageForAgent) {
|
|
555
|
+
if (!errorTask.history?.find((m) => m.messageId === finalMessageForAgent.messageId)) {
|
|
556
|
+
errorTask.history?.push(finalMessageForAgent);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
eventBus.publish(errorTask);
|
|
560
|
+
eventBus.publish({
|
|
561
|
+
// And publish a final status update
|
|
562
|
+
kind: "status-update",
|
|
563
|
+
taskId: errorTask.id,
|
|
564
|
+
contextId: errorTask.contextId,
|
|
565
|
+
status: errorTask.status,
|
|
566
|
+
final: true
|
|
567
|
+
});
|
|
568
|
+
eventBus.finished();
|
|
569
|
+
});
|
|
570
|
+
if (isBlocking) {
|
|
571
|
+
await this._processEvents(taskId, resultManager, eventQueue);
|
|
572
|
+
const finalResult = resultManager.getFinalResult();
|
|
573
|
+
if (!finalResult) {
|
|
574
|
+
throw A2AError.internalError(
|
|
575
|
+
"Agent execution finished without a result, and no task context found."
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
return finalResult;
|
|
579
|
+
} else {
|
|
580
|
+
return new Promise((resolve, reject) => {
|
|
581
|
+
this._processEvents(taskId, resultManager, eventQueue, {
|
|
582
|
+
firstResultResolver: resolve,
|
|
583
|
+
firstResultRejector: reject
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async *sendMessageStream(params, context) {
|
|
589
|
+
const incomingMessage = params.message;
|
|
590
|
+
if (!incomingMessage.messageId) {
|
|
591
|
+
throw A2AError.invalidParams("message.messageId is required for streaming.");
|
|
592
|
+
}
|
|
593
|
+
const resultManager = new ResultManager(this.taskStore);
|
|
594
|
+
resultManager.setContext(incomingMessage);
|
|
595
|
+
const requestContext = await this._createRequestContext(incomingMessage, context);
|
|
596
|
+
const taskId = requestContext.taskId;
|
|
597
|
+
const finalMessageForAgent = requestContext.userMessage;
|
|
598
|
+
const eventBus = this.eventBusManager.createOrGetByTaskId(taskId);
|
|
599
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
600
|
+
if (params.configuration?.pushNotificationConfig && this.agentCard.capabilities.pushNotifications) {
|
|
601
|
+
await this.pushNotificationStore?.save(taskId, params.configuration.pushNotificationConfig);
|
|
602
|
+
}
|
|
603
|
+
this.agentExecutor.execute(requestContext, eventBus).catch((err) => {
|
|
604
|
+
console.error(
|
|
605
|
+
`Agent execution failed for stream message ${finalMessageForAgent.messageId}:`,
|
|
606
|
+
err
|
|
607
|
+
);
|
|
608
|
+
const errorTaskStatus = {
|
|
609
|
+
kind: "status-update",
|
|
610
|
+
taskId: requestContext.task?.id || uuidv4(),
|
|
611
|
+
// Use existing or a placeholder
|
|
612
|
+
contextId: finalMessageForAgent.contextId,
|
|
613
|
+
status: {
|
|
614
|
+
state: "failed",
|
|
615
|
+
message: {
|
|
616
|
+
kind: "message",
|
|
617
|
+
role: "agent",
|
|
618
|
+
messageId: uuidv4(),
|
|
619
|
+
parts: [{ kind: "text", text: `Agent execution error: ${err.message}` }],
|
|
620
|
+
taskId: requestContext.task?.id,
|
|
621
|
+
contextId: finalMessageForAgent.contextId
|
|
622
|
+
},
|
|
623
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
624
|
+
},
|
|
625
|
+
final: true
|
|
626
|
+
// This will terminate the stream for the client
|
|
627
|
+
};
|
|
628
|
+
eventBus.publish(errorTaskStatus);
|
|
629
|
+
});
|
|
630
|
+
try {
|
|
631
|
+
for await (const event of eventQueue.events()) {
|
|
632
|
+
await resultManager.processEvent(event);
|
|
633
|
+
await this._sendPushNotificationIfNeeded(event);
|
|
634
|
+
yield event;
|
|
635
|
+
}
|
|
636
|
+
} finally {
|
|
637
|
+
this.eventBusManager.cleanupByTaskId(taskId);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
async getTask(params, _context) {
|
|
641
|
+
const task = await this.taskStore.load(params.id);
|
|
642
|
+
if (!task) {
|
|
643
|
+
throw A2AError.taskNotFound(params.id);
|
|
644
|
+
}
|
|
645
|
+
if (params.historyLength !== void 0 && params.historyLength >= 0) {
|
|
646
|
+
if (task.history) {
|
|
647
|
+
task.history = task.history.slice(-params.historyLength);
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
task.history = [];
|
|
651
|
+
}
|
|
652
|
+
return task;
|
|
653
|
+
}
|
|
654
|
+
async cancelTask(params, _context) {
|
|
655
|
+
const task = await this.taskStore.load(params.id);
|
|
656
|
+
if (!task) {
|
|
657
|
+
throw A2AError.taskNotFound(params.id);
|
|
658
|
+
}
|
|
659
|
+
const nonCancelableStates = ["completed", "failed", "canceled", "rejected"];
|
|
660
|
+
if (nonCancelableStates.includes(task.status.state)) {
|
|
661
|
+
throw A2AError.taskNotCancelable(params.id);
|
|
662
|
+
}
|
|
663
|
+
const eventBus = this.eventBusManager.getByTaskId(params.id);
|
|
664
|
+
if (eventBus) {
|
|
665
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
666
|
+
await this.agentExecutor.cancelTask(params.id, eventBus);
|
|
667
|
+
await this._processEvents(params.id, new ResultManager(this.taskStore), eventQueue);
|
|
668
|
+
} else {
|
|
669
|
+
task.status = {
|
|
670
|
+
state: "canceled",
|
|
671
|
+
message: {
|
|
672
|
+
// Optional: Add a system message indicating cancellation
|
|
673
|
+
kind: "message",
|
|
674
|
+
role: "agent",
|
|
675
|
+
messageId: uuidv4(),
|
|
676
|
+
parts: [{ kind: "text", text: "Task cancellation requested by user." }],
|
|
677
|
+
taskId: task.id,
|
|
678
|
+
contextId: task.contextId
|
|
679
|
+
},
|
|
680
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
681
|
+
};
|
|
682
|
+
task.history = [...task.history || [], task.status.message];
|
|
683
|
+
await this.taskStore.save(task);
|
|
684
|
+
}
|
|
685
|
+
const latestTask = await this.taskStore.load(params.id);
|
|
686
|
+
if (!latestTask) {
|
|
687
|
+
throw A2AError.internalError(`Task ${params.id} not found after cancellation.`);
|
|
688
|
+
}
|
|
689
|
+
if (latestTask.status.state != "canceled") {
|
|
690
|
+
throw A2AError.taskNotCancelable(params.id);
|
|
691
|
+
}
|
|
692
|
+
return latestTask;
|
|
693
|
+
}
|
|
694
|
+
async setTaskPushNotificationConfig(params, _context) {
|
|
695
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
696
|
+
throw A2AError.pushNotificationNotSupported();
|
|
697
|
+
}
|
|
698
|
+
const task = await this.taskStore.load(params.taskId);
|
|
699
|
+
if (!task) {
|
|
700
|
+
throw A2AError.taskNotFound(params.taskId);
|
|
701
|
+
}
|
|
702
|
+
const { taskId, pushNotificationConfig } = params;
|
|
703
|
+
if (!pushNotificationConfig.id) {
|
|
704
|
+
pushNotificationConfig.id = taskId;
|
|
705
|
+
}
|
|
706
|
+
await this.pushNotificationStore?.save(taskId, pushNotificationConfig);
|
|
707
|
+
return params;
|
|
708
|
+
}
|
|
709
|
+
async getTaskPushNotificationConfig(params, _context) {
|
|
710
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
711
|
+
throw A2AError.pushNotificationNotSupported();
|
|
712
|
+
}
|
|
713
|
+
const task = await this.taskStore.load(params.id);
|
|
714
|
+
if (!task) {
|
|
715
|
+
throw A2AError.taskNotFound(params.id);
|
|
716
|
+
}
|
|
717
|
+
const configs = await this.pushNotificationStore?.load(params.id) || [];
|
|
718
|
+
if (configs.length === 0) {
|
|
719
|
+
throw A2AError.internalError(`Push notification config not found for task ${params.id}.`);
|
|
720
|
+
}
|
|
721
|
+
let configId;
|
|
722
|
+
if ("pushNotificationConfigId" in params && params.pushNotificationConfigId) {
|
|
723
|
+
configId = params.pushNotificationConfigId;
|
|
724
|
+
} else {
|
|
725
|
+
configId = params.id;
|
|
726
|
+
}
|
|
727
|
+
const config = configs.find((c) => c.id === configId);
|
|
728
|
+
if (!config) {
|
|
729
|
+
throw A2AError.internalError(
|
|
730
|
+
`Push notification config with id '${configId}' not found for task ${params.id}.`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
return { taskId: params.id, pushNotificationConfig: config };
|
|
734
|
+
}
|
|
735
|
+
async listTaskPushNotificationConfigs(params, _context) {
|
|
736
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
737
|
+
throw A2AError.pushNotificationNotSupported();
|
|
738
|
+
}
|
|
739
|
+
const task = await this.taskStore.load(params.id);
|
|
740
|
+
if (!task) {
|
|
741
|
+
throw A2AError.taskNotFound(params.id);
|
|
742
|
+
}
|
|
743
|
+
const configs = await this.pushNotificationStore?.load(params.id) || [];
|
|
744
|
+
return configs.map((config) => ({
|
|
745
|
+
taskId: params.id,
|
|
746
|
+
pushNotificationConfig: config
|
|
747
|
+
}));
|
|
748
|
+
}
|
|
749
|
+
async deleteTaskPushNotificationConfig(params, _context) {
|
|
750
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
751
|
+
throw A2AError.pushNotificationNotSupported();
|
|
752
|
+
}
|
|
753
|
+
const task = await this.taskStore.load(params.id);
|
|
754
|
+
if (!task) {
|
|
755
|
+
throw A2AError.taskNotFound(params.id);
|
|
756
|
+
}
|
|
757
|
+
const { id: taskId, pushNotificationConfigId } = params;
|
|
758
|
+
await this.pushNotificationStore?.delete(taskId, pushNotificationConfigId);
|
|
759
|
+
}
|
|
760
|
+
async *resubscribe(params, _context) {
|
|
761
|
+
if (!this.agentCard.capabilities.streaming) {
|
|
762
|
+
throw A2AError.unsupportedOperation("Streaming (and thus resubscription) is not supported.");
|
|
763
|
+
}
|
|
764
|
+
const task = await this.taskStore.load(params.id);
|
|
765
|
+
if (!task) {
|
|
766
|
+
throw A2AError.taskNotFound(params.id);
|
|
767
|
+
}
|
|
768
|
+
yield task;
|
|
769
|
+
const finalStates = ["completed", "failed", "canceled", "rejected"];
|
|
770
|
+
if (finalStates.includes(task.status.state)) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const eventBus = this.eventBusManager.getByTaskId(params.id);
|
|
774
|
+
if (!eventBus) {
|
|
775
|
+
console.warn(`Resubscribe: No active event bus for task ${params.id}.`);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const eventQueue = new ExecutionEventQueue(eventBus);
|
|
779
|
+
try {
|
|
780
|
+
for await (const event of eventQueue.events()) {
|
|
781
|
+
if (event.kind === "status-update" && event.taskId === params.id) {
|
|
782
|
+
yield event;
|
|
783
|
+
} else if (event.kind === "artifact-update" && event.taskId === params.id) {
|
|
784
|
+
yield event;
|
|
785
|
+
} else if (event.kind === "task" && event.id === params.id) {
|
|
786
|
+
yield event;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
} finally {
|
|
790
|
+
eventQueue.stop();
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async _sendPushNotificationIfNeeded(event) {
|
|
794
|
+
if (!this.agentCard.capabilities.pushNotifications) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
let taskId = "";
|
|
798
|
+
if (event.kind == "task") {
|
|
799
|
+
const task2 = event;
|
|
800
|
+
taskId = task2.id;
|
|
801
|
+
} else {
|
|
802
|
+
taskId = event.taskId;
|
|
803
|
+
}
|
|
804
|
+
if (!taskId) {
|
|
805
|
+
console.error(`Task ID not found for event ${event.kind}.`);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const task = await this.taskStore.load(taskId);
|
|
809
|
+
if (!task) {
|
|
810
|
+
console.error(`Task ${taskId} not found.`);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
this.pushNotificationSender?.send(task);
|
|
814
|
+
}
|
|
815
|
+
async _handleProcessingError(error, resultManager, firstResultSent, taskId, firstResultRejector) {
|
|
816
|
+
if (firstResultRejector && !firstResultSent) {
|
|
817
|
+
firstResultRejector(error);
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (!firstResultRejector) {
|
|
821
|
+
throw error;
|
|
822
|
+
}
|
|
823
|
+
const currentTask = resultManager.getCurrentTask();
|
|
824
|
+
const errorMessage = error instanceof Error && error.message || "Unknown error";
|
|
825
|
+
if (currentTask) {
|
|
826
|
+
const statusUpdateFailed = {
|
|
827
|
+
taskId: currentTask.id,
|
|
828
|
+
contextId: currentTask.contextId,
|
|
829
|
+
status: {
|
|
830
|
+
state: "failed",
|
|
831
|
+
message: {
|
|
832
|
+
kind: "message",
|
|
833
|
+
role: "agent",
|
|
834
|
+
messageId: uuidv4(),
|
|
835
|
+
parts: [{ kind: "text", text: `Event processing loop failed: ${errorMessage}` }],
|
|
836
|
+
taskId: currentTask.id,
|
|
837
|
+
contextId: currentTask.contextId
|
|
838
|
+
},
|
|
839
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
840
|
+
},
|
|
841
|
+
kind: "status-update",
|
|
842
|
+
final: true
|
|
843
|
+
};
|
|
844
|
+
try {
|
|
845
|
+
await resultManager.processEvent(statusUpdateFailed);
|
|
846
|
+
} catch (error2) {
|
|
847
|
+
console.error(
|
|
848
|
+
`Event processing loop failed for task ${taskId}: ${error2 instanceof Error && error2.message || "Unknown error"}`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
console.error(`Event processing loop failed for task ${taskId}: ${errorMessage}`);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/server/store.ts
|
|
858
|
+
var InMemoryTaskStore = class {
|
|
859
|
+
store = /* @__PURE__ */ new Map();
|
|
860
|
+
async load(taskId) {
|
|
861
|
+
const entry = this.store.get(taskId);
|
|
862
|
+
return entry ? { ...entry } : void 0;
|
|
863
|
+
}
|
|
864
|
+
async save(task) {
|
|
865
|
+
this.store.set(task.id, { ...task });
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
export {
|
|
870
|
+
RequestContext,
|
|
871
|
+
DefaultExecutionEventBus,
|
|
872
|
+
DefaultExecutionEventBusManager,
|
|
873
|
+
ExecutionEventQueue,
|
|
874
|
+
ResultManager,
|
|
875
|
+
InMemoryPushNotificationStore,
|
|
876
|
+
DefaultPushNotificationSender,
|
|
877
|
+
DefaultRequestHandler,
|
|
878
|
+
InMemoryTaskStore
|
|
879
|
+
};
|