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