@a2a-js/sdk 0.3.4 → 0.3.6
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 +258 -188
- package/dist/a2a_request_handler-B3LxMq3P.d.cts +49 -0
- package/dist/a2a_request_handler-BuP9LgXH.d.ts +49 -0
- package/dist/chunk-3QDLXHKS.js +8 -0
- package/dist/{chunk-JA52GYRU.js → chunk-LTPINR5K.js} +115 -61
- package/dist/chunk-ZX6KNMCP.js +38 -0
- package/dist/client/index.cjs +1057 -256
- package/dist/client/index.d.cts +419 -48
- package/dist/client/index.d.ts +419 -48
- package/dist/client/index.js +1015 -257
- package/dist/{types-DNKcmF0f.d.cts → extensions-DvruCIzw.d.cts} +28 -1
- package/dist/{types-DNKcmF0f.d.ts → extensions-DvruCIzw.d.ts} +28 -1
- package/dist/index.cjs +42 -2
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +9 -3
- package/dist/server/express/index.cjs +790 -141
- package/dist/server/express/index.d.cts +84 -7
- package/dist/server/express/index.d.ts +84 -7
- package/dist/server/express/index.js +651 -82
- package/dist/server/index.cjs +368 -145
- package/dist/server/index.d.cts +36 -26
- package/dist/server/index.d.ts +36 -26
- package/dist/server/index.js +227 -86
- package/package.json +17 -9
- package/dist/a2a_request_handler-B5t-IxgA.d.ts +0 -17
- package/dist/a2a_request_handler-DUvKWfix.d.cts +0 -17
- package/dist/chunk-67JNQ6TZ.js +0 -6
|
@@ -29,11 +29,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
29
29
|
// src/server/express/index.ts
|
|
30
30
|
var express_exports = {};
|
|
31
31
|
__export(express_exports, {
|
|
32
|
-
A2AExpressApp: () => A2AExpressApp
|
|
32
|
+
A2AExpressApp: () => A2AExpressApp,
|
|
33
|
+
UserBuilder: () => UserBuilder,
|
|
34
|
+
agentCardHandler: () => agentCardHandler,
|
|
35
|
+
jsonRpcHandler: () => jsonRpcHandler,
|
|
36
|
+
restHandler: () => restHandler
|
|
33
37
|
});
|
|
34
38
|
module.exports = __toCommonJS(express_exports);
|
|
35
39
|
|
|
36
40
|
// src/server/express/a2a_express_app.ts
|
|
41
|
+
var import_express3 = __toESM(require("express"), 1);
|
|
42
|
+
|
|
43
|
+
// src/constants.ts
|
|
44
|
+
var AGENT_CARD_PATH = ".well-known/agent-card.json";
|
|
45
|
+
var HTTP_EXTENSION_HEADER = "X-A2A-Extensions";
|
|
46
|
+
|
|
47
|
+
// src/server/express/json_rpc_handler.ts
|
|
37
48
|
var import_express = __toESM(require("express"), 1);
|
|
38
49
|
|
|
39
50
|
// src/server/error.ts
|
|
@@ -70,10 +81,7 @@ var A2AError = class _A2AError extends Error {
|
|
|
70
81
|
return new _A2AError(-32600, message, data);
|
|
71
82
|
}
|
|
72
83
|
static methodNotFound(method) {
|
|
73
|
-
return new _A2AError(
|
|
74
|
-
-32601,
|
|
75
|
-
`Method not found: ${method}`
|
|
76
|
-
);
|
|
84
|
+
return new _A2AError(-32601, `Method not found: ${method}`);
|
|
77
85
|
}
|
|
78
86
|
static invalidParams(message, data) {
|
|
79
87
|
return new _A2AError(-32602, message, data);
|
|
@@ -82,42 +90,23 @@ var A2AError = class _A2AError extends Error {
|
|
|
82
90
|
return new _A2AError(-32603, message, data);
|
|
83
91
|
}
|
|
84
92
|
static taskNotFound(taskId) {
|
|
85
|
-
return new _A2AError(
|
|
86
|
-
-32001,
|
|
87
|
-
`Task not found: ${taskId}`,
|
|
88
|
-
void 0,
|
|
89
|
-
taskId
|
|
90
|
-
);
|
|
93
|
+
return new _A2AError(-32001, `Task not found: ${taskId}`, void 0, taskId);
|
|
91
94
|
}
|
|
92
95
|
static taskNotCancelable(taskId) {
|
|
93
|
-
return new _A2AError(
|
|
94
|
-
-32002,
|
|
95
|
-
`Task not cancelable: ${taskId}`,
|
|
96
|
-
void 0,
|
|
97
|
-
taskId
|
|
98
|
-
);
|
|
96
|
+
return new _A2AError(-32002, `Task not cancelable: ${taskId}`, void 0, taskId);
|
|
99
97
|
}
|
|
100
98
|
static pushNotificationNotSupported() {
|
|
101
|
-
return new _A2AError(
|
|
102
|
-
-32003,
|
|
103
|
-
"Push Notification is not supported"
|
|
104
|
-
);
|
|
99
|
+
return new _A2AError(-32003, "Push Notification is not supported");
|
|
105
100
|
}
|
|
106
101
|
static unsupportedOperation(operation) {
|
|
107
|
-
return new _A2AError(
|
|
108
|
-
-32004,
|
|
109
|
-
`Unsupported operation: ${operation}`
|
|
110
|
-
);
|
|
102
|
+
return new _A2AError(-32004, `Unsupported operation: ${operation}`);
|
|
111
103
|
}
|
|
112
104
|
static authenticatedExtendedCardNotConfigured() {
|
|
113
|
-
return new _A2AError(
|
|
114
|
-
-32007,
|
|
115
|
-
`Extended card not configured.`
|
|
116
|
-
);
|
|
105
|
+
return new _A2AError(-32007, `Extended card not configured.`);
|
|
117
106
|
}
|
|
118
107
|
};
|
|
119
108
|
|
|
120
|
-
// src/server/transports/jsonrpc_transport_handler.ts
|
|
109
|
+
// src/server/transports/jsonrpc/jsonrpc_transport_handler.ts
|
|
121
110
|
var JsonRpcTransportHandler = class {
|
|
122
111
|
requestHandler;
|
|
123
112
|
constructor(requestHandler) {
|
|
@@ -128,7 +117,7 @@ var JsonRpcTransportHandler = class {
|
|
|
128
117
|
* For streaming methods, it returns an AsyncGenerator of JSONRPCResult.
|
|
129
118
|
* For non-streaming methods, it returns a Promise of a single JSONRPCMessage (Result or ErrorResponse).
|
|
130
119
|
*/
|
|
131
|
-
async handle(requestBody) {
|
|
120
|
+
async handle(requestBody, context) {
|
|
132
121
|
let rpcRequest;
|
|
133
122
|
try {
|
|
134
123
|
if (typeof requestBody === "string") {
|
|
@@ -138,31 +127,23 @@ var JsonRpcTransportHandler = class {
|
|
|
138
127
|
} else {
|
|
139
128
|
throw A2AError.parseError("Invalid request body type.");
|
|
140
129
|
}
|
|
141
|
-
if (
|
|
142
|
-
throw A2AError.invalidRequest(
|
|
143
|
-
"Invalid JSON-RPC request structure."
|
|
144
|
-
);
|
|
130
|
+
if (!this.isRequestValid(rpcRequest)) {
|
|
131
|
+
throw A2AError.invalidRequest("Invalid JSON-RPC Request.");
|
|
145
132
|
}
|
|
146
133
|
} catch (error) {
|
|
147
|
-
const a2aError = error instanceof A2AError ? error : A2AError.parseError(
|
|
134
|
+
const a2aError = error instanceof A2AError ? error : A2AError.parseError(
|
|
135
|
+
error instanceof SyntaxError && error.message || "Failed to parse JSON request."
|
|
136
|
+
);
|
|
148
137
|
return {
|
|
149
138
|
jsonrpc: "2.0",
|
|
150
|
-
id:
|
|
139
|
+
id: rpcRequest?.id !== void 0 ? rpcRequest.id : null,
|
|
151
140
|
error: a2aError.toJSONRPCError()
|
|
152
141
|
};
|
|
153
142
|
}
|
|
154
143
|
const { method, id: requestId = null } = rpcRequest;
|
|
155
144
|
try {
|
|
156
|
-
if (method
|
|
157
|
-
|
|
158
|
-
return {
|
|
159
|
-
jsonrpc: "2.0",
|
|
160
|
-
id: requestId,
|
|
161
|
-
result
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
if (!rpcRequest.params) {
|
|
165
|
-
throw A2AError.invalidParams(`'params' is required for '${method}'`);
|
|
145
|
+
if (method !== "agent/getAuthenticatedExtendedCard" && !this.paramsAreValid(rpcRequest.params)) {
|
|
146
|
+
throw A2AError.invalidParams(`Invalid method parameters.`);
|
|
166
147
|
}
|
|
167
148
|
if (method === "message/stream" || method === "tasks/resubscribe") {
|
|
168
149
|
const params = rpcRequest.params;
|
|
@@ -170,8 +151,8 @@ var JsonRpcTransportHandler = class {
|
|
|
170
151
|
if (!agentCard.capabilities.streaming) {
|
|
171
152
|
throw A2AError.unsupportedOperation(`Method ${method} requires streaming capability.`);
|
|
172
153
|
}
|
|
173
|
-
const agentEventStream = method === "message/stream" ? this.requestHandler.sendMessageStream(params) : this.requestHandler.resubscribe(params);
|
|
174
|
-
return async function* jsonRpcEventStream() {
|
|
154
|
+
const agentEventStream = method === "message/stream" ? this.requestHandler.sendMessageStream(params, context) : this.requestHandler.resubscribe(params, context);
|
|
155
|
+
return (async function* jsonRpcEventStream() {
|
|
175
156
|
try {
|
|
176
157
|
for await (const event of agentEventStream) {
|
|
177
158
|
yield {
|
|
@@ -182,43 +163,50 @@ var JsonRpcTransportHandler = class {
|
|
|
182
163
|
};
|
|
183
164
|
}
|
|
184
165
|
} catch (streamError) {
|
|
185
|
-
console.error(
|
|
166
|
+
console.error(
|
|
167
|
+
`Error in agent event stream for ${method} (request ${requestId}):`,
|
|
168
|
+
streamError
|
|
169
|
+
);
|
|
186
170
|
throw streamError;
|
|
187
171
|
}
|
|
188
|
-
}();
|
|
172
|
+
})();
|
|
189
173
|
} else {
|
|
190
174
|
let result;
|
|
191
175
|
switch (method) {
|
|
192
176
|
case "message/send":
|
|
193
|
-
result = await this.requestHandler.sendMessage(rpcRequest.params);
|
|
177
|
+
result = await this.requestHandler.sendMessage(rpcRequest.params, context);
|
|
194
178
|
break;
|
|
195
179
|
case "tasks/get":
|
|
196
|
-
result = await this.requestHandler.getTask(rpcRequest.params);
|
|
180
|
+
result = await this.requestHandler.getTask(rpcRequest.params, context);
|
|
197
181
|
break;
|
|
198
182
|
case "tasks/cancel":
|
|
199
|
-
result = await this.requestHandler.cancelTask(rpcRequest.params);
|
|
183
|
+
result = await this.requestHandler.cancelTask(rpcRequest.params, context);
|
|
200
184
|
break;
|
|
201
185
|
case "tasks/pushNotificationConfig/set":
|
|
202
186
|
result = await this.requestHandler.setTaskPushNotificationConfig(
|
|
203
|
-
rpcRequest.params
|
|
187
|
+
rpcRequest.params,
|
|
188
|
+
context
|
|
204
189
|
);
|
|
205
190
|
break;
|
|
206
191
|
case "tasks/pushNotificationConfig/get":
|
|
207
192
|
result = await this.requestHandler.getTaskPushNotificationConfig(
|
|
208
|
-
rpcRequest.params
|
|
193
|
+
rpcRequest.params,
|
|
194
|
+
context
|
|
209
195
|
);
|
|
210
196
|
break;
|
|
211
197
|
case "tasks/pushNotificationConfig/delete":
|
|
212
|
-
await this.requestHandler.deleteTaskPushNotificationConfig(
|
|
213
|
-
rpcRequest.params
|
|
214
|
-
);
|
|
198
|
+
await this.requestHandler.deleteTaskPushNotificationConfig(rpcRequest.params, context);
|
|
215
199
|
result = null;
|
|
216
200
|
break;
|
|
217
201
|
case "tasks/pushNotificationConfig/list":
|
|
218
202
|
result = await this.requestHandler.listTaskPushNotificationConfigs(
|
|
219
|
-
rpcRequest.params
|
|
203
|
+
rpcRequest.params,
|
|
204
|
+
context
|
|
220
205
|
);
|
|
221
206
|
break;
|
|
207
|
+
case "agent/getAuthenticatedExtendedCard":
|
|
208
|
+
result = await this.requestHandler.getAuthenticatedExtendedAgentCard(context);
|
|
209
|
+
break;
|
|
222
210
|
default:
|
|
223
211
|
throw A2AError.methodNotFound(method);
|
|
224
212
|
}
|
|
@@ -229,7 +217,14 @@ var JsonRpcTransportHandler = class {
|
|
|
229
217
|
};
|
|
230
218
|
}
|
|
231
219
|
} catch (error) {
|
|
232
|
-
|
|
220
|
+
let a2aError;
|
|
221
|
+
if (error instanceof A2AError) {
|
|
222
|
+
a2aError = error;
|
|
223
|
+
} else {
|
|
224
|
+
a2aError = A2AError.internalError(
|
|
225
|
+
error instanceof Error && error.message || "An unexpected error occurred."
|
|
226
|
+
);
|
|
227
|
+
}
|
|
233
228
|
return {
|
|
234
229
|
jsonrpc: "2.0",
|
|
235
230
|
id: requestId,
|
|
@@ -237,106 +232,760 @@ var JsonRpcTransportHandler = class {
|
|
|
237
232
|
};
|
|
238
233
|
}
|
|
239
234
|
}
|
|
235
|
+
// Validates the basic structure of a JSON-RPC request
|
|
236
|
+
isRequestValid(rpcRequest) {
|
|
237
|
+
if (rpcRequest.jsonrpc !== "2.0") {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
if ("id" in rpcRequest) {
|
|
241
|
+
const id = rpcRequest.id;
|
|
242
|
+
const isString = typeof id === "string";
|
|
243
|
+
const isInteger = typeof id === "number" && Number.isInteger(id);
|
|
244
|
+
const isNull = id === null;
|
|
245
|
+
if (!isString && !isInteger && !isNull) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!rpcRequest.method || typeof rpcRequest.method !== "string") {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
// Validates that params is an object with non-empty string keys
|
|
255
|
+
paramsAreValid(params) {
|
|
256
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
for (const key of Object.keys(params)) {
|
|
260
|
+
if (key === "") {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
240
266
|
};
|
|
241
267
|
|
|
242
|
-
// src/
|
|
243
|
-
var
|
|
268
|
+
// src/extensions.ts
|
|
269
|
+
var Extensions = {
|
|
270
|
+
/**
|
|
271
|
+
* Creates new {@link Extensions} from `current` and `additional`.
|
|
272
|
+
* If `current` already contains `additional` it is returned unmodified.
|
|
273
|
+
*/
|
|
274
|
+
createFrom: (current, additional) => {
|
|
275
|
+
if (current?.includes(additional)) {
|
|
276
|
+
return current;
|
|
277
|
+
}
|
|
278
|
+
return [...current ?? [], additional];
|
|
279
|
+
},
|
|
280
|
+
/**
|
|
281
|
+
* Creates {@link Extensions} from comma separated extensions identifiers as per
|
|
282
|
+
* https://a2a-protocol.org/latest/specification/#326-service-parameters.
|
|
283
|
+
* Parses the output of `toServiceParameter`.
|
|
284
|
+
*/
|
|
285
|
+
parseServiceParameter: (value) => {
|
|
286
|
+
if (!value) {
|
|
287
|
+
return [];
|
|
288
|
+
}
|
|
289
|
+
const unique = new Set(
|
|
290
|
+
value.split(",").map((ext) => ext.trim()).filter((ext) => ext.length > 0)
|
|
291
|
+
);
|
|
292
|
+
return Array.from(unique);
|
|
293
|
+
},
|
|
294
|
+
/**
|
|
295
|
+
* Converts {@link Extensions} to comma separated extensions identifiers as per
|
|
296
|
+
* https://a2a-protocol.org/latest/specification/#326-service-parameters.
|
|
297
|
+
*/
|
|
298
|
+
toServiceParameter: (value) => {
|
|
299
|
+
return value.join(",");
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/server/context.ts
|
|
304
|
+
var ServerCallContext = class {
|
|
305
|
+
_requestedExtensions;
|
|
306
|
+
_user;
|
|
307
|
+
_activatedExtensions;
|
|
308
|
+
constructor(requestedExtensions, user) {
|
|
309
|
+
this._requestedExtensions = requestedExtensions;
|
|
310
|
+
this._user = user;
|
|
311
|
+
}
|
|
312
|
+
get user() {
|
|
313
|
+
return this._user;
|
|
314
|
+
}
|
|
315
|
+
get activatedExtensions() {
|
|
316
|
+
return this._activatedExtensions;
|
|
317
|
+
}
|
|
318
|
+
get requestedExtensions() {
|
|
319
|
+
return this._requestedExtensions;
|
|
320
|
+
}
|
|
321
|
+
addActivatedExtension(uri) {
|
|
322
|
+
this._activatedExtensions = Extensions.createFrom(this._activatedExtensions, uri);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// src/server/authentication/user.ts
|
|
327
|
+
var UnauthenticatedUser = class {
|
|
328
|
+
get isAuthenticated() {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
get userName() {
|
|
332
|
+
return "";
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/sse_utils.ts
|
|
337
|
+
var SSE_HEADERS = {
|
|
338
|
+
"Content-Type": "text/event-stream",
|
|
339
|
+
"Cache-Control": "no-cache",
|
|
340
|
+
Connection: "keep-alive",
|
|
341
|
+
"X-Accel-Buffering": "no"
|
|
342
|
+
// Disable buffering in nginx
|
|
343
|
+
};
|
|
344
|
+
function formatSSEEvent(event) {
|
|
345
|
+
return `data: ${JSON.stringify(event)}
|
|
346
|
+
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
function formatSSEErrorEvent(error) {
|
|
350
|
+
return `event: error
|
|
351
|
+
data: ${JSON.stringify(error)}
|
|
352
|
+
|
|
353
|
+
`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/server/express/json_rpc_handler.ts
|
|
357
|
+
function jsonRpcHandler(options) {
|
|
358
|
+
const jsonRpcTransportHandler = new JsonRpcTransportHandler(options.requestHandler);
|
|
359
|
+
const router = import_express.default.Router();
|
|
360
|
+
router.use(import_express.default.json(), jsonErrorHandler);
|
|
361
|
+
router.post("/", async (req, res) => {
|
|
362
|
+
try {
|
|
363
|
+
const user = await options.userBuilder(req);
|
|
364
|
+
const context = new ServerCallContext(
|
|
365
|
+
Extensions.parseServiceParameter(req.header(HTTP_EXTENSION_HEADER)),
|
|
366
|
+
user ?? new UnauthenticatedUser()
|
|
367
|
+
);
|
|
368
|
+
const rpcResponseOrStream = await jsonRpcTransportHandler.handle(req.body, context);
|
|
369
|
+
if (context.activatedExtensions) {
|
|
370
|
+
res.setHeader(HTTP_EXTENSION_HEADER, Array.from(context.activatedExtensions));
|
|
371
|
+
}
|
|
372
|
+
if (typeof rpcResponseOrStream?.[Symbol.asyncIterator] === "function") {
|
|
373
|
+
const stream = rpcResponseOrStream;
|
|
374
|
+
Object.entries(SSE_HEADERS).forEach(([key, value]) => {
|
|
375
|
+
res.setHeader(key, value);
|
|
376
|
+
});
|
|
377
|
+
res.flushHeaders();
|
|
378
|
+
try {
|
|
379
|
+
for await (const event of stream) {
|
|
380
|
+
res.write(formatSSEEvent(event));
|
|
381
|
+
}
|
|
382
|
+
} catch (streamError) {
|
|
383
|
+
console.error(`Error during SSE streaming (request ${req.body?.id}):`, streamError);
|
|
384
|
+
let a2aError;
|
|
385
|
+
if (streamError instanceof A2AError) {
|
|
386
|
+
a2aError = streamError;
|
|
387
|
+
} else {
|
|
388
|
+
a2aError = A2AError.internalError(
|
|
389
|
+
streamError instanceof Error && streamError.message || "Streaming error."
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
const errorResponse = {
|
|
393
|
+
jsonrpc: "2.0",
|
|
394
|
+
id: req.body?.id || null,
|
|
395
|
+
// Use original request ID if available
|
|
396
|
+
error: a2aError.toJSONRPCError()
|
|
397
|
+
};
|
|
398
|
+
if (!res.headersSent) {
|
|
399
|
+
res.status(500).json(errorResponse);
|
|
400
|
+
} else {
|
|
401
|
+
res.write(formatSSEErrorEvent(errorResponse));
|
|
402
|
+
}
|
|
403
|
+
} finally {
|
|
404
|
+
if (!res.writableEnded) {
|
|
405
|
+
res.end();
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
const rpcResponse = rpcResponseOrStream;
|
|
410
|
+
res.status(200).json(rpcResponse);
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error("Unhandled error in JSON-RPC POST handler:", error);
|
|
414
|
+
const a2aError = error instanceof A2AError ? error : A2AError.internalError("General processing error.");
|
|
415
|
+
const errorResponse = {
|
|
416
|
+
jsonrpc: "2.0",
|
|
417
|
+
id: req.body?.id || null,
|
|
418
|
+
error: a2aError.toJSONRPCError()
|
|
419
|
+
};
|
|
420
|
+
if (!res.headersSent) {
|
|
421
|
+
res.status(500).json(errorResponse);
|
|
422
|
+
} else if (!res.writableEnded) {
|
|
423
|
+
res.end();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
return router;
|
|
428
|
+
}
|
|
429
|
+
var jsonErrorHandler = (err, _req, res, next) => {
|
|
430
|
+
if (err instanceof SyntaxError && "body" in err) {
|
|
431
|
+
const a2aError = A2AError.parseError("Invalid JSON payload.");
|
|
432
|
+
const errorResponse = {
|
|
433
|
+
jsonrpc: "2.0",
|
|
434
|
+
id: null,
|
|
435
|
+
error: a2aError.toJSONRPCError()
|
|
436
|
+
};
|
|
437
|
+
return res.status(400).json(errorResponse);
|
|
438
|
+
}
|
|
439
|
+
next(err);
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// src/server/express/agent_card_handler.ts
|
|
443
|
+
var import_express2 = __toESM(require("express"), 1);
|
|
444
|
+
function agentCardHandler(options) {
|
|
445
|
+
const router = import_express2.default.Router();
|
|
446
|
+
const provider = typeof options.agentCardProvider === "function" ? options.agentCardProvider : options.agentCardProvider.getAgentCard.bind(options.agentCardProvider);
|
|
447
|
+
router.get("/", async (_req, res) => {
|
|
448
|
+
try {
|
|
449
|
+
const agentCard = await provider();
|
|
450
|
+
res.json(agentCard);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
console.error("Error fetching agent card:", error);
|
|
453
|
+
res.status(500).json({ error: "Failed to retrieve agent card" });
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return router;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/server/express/common.ts
|
|
460
|
+
var UserBuilder = {
|
|
461
|
+
noAuthentication: () => Promise.resolve(new UnauthenticatedUser())
|
|
462
|
+
};
|
|
244
463
|
|
|
245
464
|
// src/server/express/a2a_express_app.ts
|
|
246
465
|
var A2AExpressApp = class {
|
|
247
466
|
requestHandler;
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
constructor(requestHandler) {
|
|
467
|
+
userBuilder;
|
|
468
|
+
constructor(requestHandler, userBuilder = UserBuilder.noAuthentication) {
|
|
251
469
|
this.requestHandler = requestHandler;
|
|
252
|
-
this.
|
|
470
|
+
this.userBuilder = userBuilder;
|
|
253
471
|
}
|
|
254
472
|
/**
|
|
255
473
|
* Adds A2A routes to an existing Express app.
|
|
256
474
|
* @param app Optional existing Express app.
|
|
257
475
|
* @param baseUrl The base URL for A2A endpoints (e.g., "/a2a/api").
|
|
258
476
|
* @param middlewares Optional array of Express middlewares to apply to the A2A routes.
|
|
259
|
-
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to
|
|
477
|
+
* @param agentCardPath Optional custom path for the agent card endpoint (defaults to .well-known/agent-card.json).
|
|
260
478
|
* @returns The Express app with A2A routes.
|
|
261
479
|
*/
|
|
262
480
|
setupRoutes(app, baseUrl = "", middlewares, agentCardPath = AGENT_CARD_PATH) {
|
|
263
|
-
const router =
|
|
264
|
-
router.use(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
router.
|
|
275
|
-
try {
|
|
276
|
-
const rpcResponseOrStream = await this.jsonRpcTransportHandler.handle(req.body);
|
|
277
|
-
if (typeof rpcResponseOrStream?.[Symbol.asyncIterator] === "function") {
|
|
278
|
-
const stream = rpcResponseOrStream;
|
|
279
|
-
res.setHeader("Content-Type", "text/event-stream");
|
|
280
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
281
|
-
res.setHeader("Connection", "keep-alive");
|
|
282
|
-
res.flushHeaders();
|
|
283
|
-
try {
|
|
284
|
-
for await (const event of stream) {
|
|
285
|
-
res.write(`id: ${(/* @__PURE__ */ new Date()).getTime()}
|
|
286
|
-
`);
|
|
287
|
-
res.write(`data: ${JSON.stringify(event)}
|
|
288
|
-
|
|
289
|
-
`);
|
|
290
|
-
}
|
|
291
|
-
} catch (streamError) {
|
|
292
|
-
console.error(`Error during SSE streaming (request ${req.body?.id}):`, streamError);
|
|
293
|
-
const a2aError = streamError instanceof A2AError ? streamError : A2AError.internalError(streamError.message || "Streaming error.");
|
|
294
|
-
const errorResponse = {
|
|
295
|
-
jsonrpc: "2.0",
|
|
296
|
-
id: req.body?.id || null,
|
|
297
|
-
// Use original request ID if available
|
|
298
|
-
error: a2aError.toJSONRPCError()
|
|
299
|
-
};
|
|
300
|
-
if (!res.headersSent) {
|
|
301
|
-
res.status(500).json(errorResponse);
|
|
302
|
-
} else {
|
|
303
|
-
res.write(`id: ${(/* @__PURE__ */ new Date()).getTime()}
|
|
304
|
-
`);
|
|
305
|
-
res.write(`event: error
|
|
306
|
-
`);
|
|
307
|
-
res.write(`data: ${JSON.stringify(errorResponse)}
|
|
308
|
-
|
|
309
|
-
`);
|
|
310
|
-
}
|
|
311
|
-
} finally {
|
|
312
|
-
if (!res.writableEnded) {
|
|
313
|
-
res.end();
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
} else {
|
|
317
|
-
const rpcResponse = rpcResponseOrStream;
|
|
318
|
-
res.status(200).json(rpcResponse);
|
|
319
|
-
}
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error("Unhandled error in A2AExpressApp POST handler:", error);
|
|
322
|
-
const a2aError = error instanceof A2AError ? error : A2AError.internalError("General processing error.");
|
|
323
|
-
const errorResponse = {
|
|
324
|
-
jsonrpc: "2.0",
|
|
325
|
-
id: req.body?.id || null,
|
|
326
|
-
error: a2aError.toJSONRPCError()
|
|
327
|
-
};
|
|
328
|
-
if (!res.headersSent) {
|
|
329
|
-
res.status(500).json(errorResponse);
|
|
330
|
-
} else if (!res.writableEnded) {
|
|
331
|
-
res.end();
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
});
|
|
481
|
+
const router = import_express3.default.Router();
|
|
482
|
+
router.use(import_express3.default.json(), jsonErrorHandler);
|
|
483
|
+
if (middlewares && middlewares.length > 0) {
|
|
484
|
+
router.use(middlewares);
|
|
485
|
+
}
|
|
486
|
+
router.use(
|
|
487
|
+
jsonRpcHandler({
|
|
488
|
+
requestHandler: this.requestHandler,
|
|
489
|
+
userBuilder: this.userBuilder
|
|
490
|
+
})
|
|
491
|
+
);
|
|
492
|
+
router.use(`/${agentCardPath}`, agentCardHandler({ agentCardProvider: this.requestHandler }));
|
|
335
493
|
app.use(baseUrl, router);
|
|
336
494
|
return app;
|
|
337
495
|
}
|
|
338
496
|
};
|
|
497
|
+
|
|
498
|
+
// src/server/express/rest_handler.ts
|
|
499
|
+
var import_express4 = __toESM(require("express"), 1);
|
|
500
|
+
|
|
501
|
+
// src/server/transports/rest/rest_transport_handler.ts
|
|
502
|
+
var HTTP_STATUS = {
|
|
503
|
+
OK: 200,
|
|
504
|
+
CREATED: 201,
|
|
505
|
+
ACCEPTED: 202,
|
|
506
|
+
NO_CONTENT: 204,
|
|
507
|
+
BAD_REQUEST: 400,
|
|
508
|
+
UNAUTHORIZED: 401,
|
|
509
|
+
NOT_FOUND: 404,
|
|
510
|
+
CONFLICT: 409,
|
|
511
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
512
|
+
NOT_IMPLEMENTED: 501
|
|
513
|
+
};
|
|
514
|
+
var A2A_ERROR_CODE = {
|
|
515
|
+
PARSE_ERROR: -32700,
|
|
516
|
+
INVALID_REQUEST: -32600,
|
|
517
|
+
METHOD_NOT_FOUND: -32601,
|
|
518
|
+
INVALID_PARAMS: -32602,
|
|
519
|
+
TASK_NOT_FOUND: -32001,
|
|
520
|
+
TASK_NOT_CANCELABLE: -32002,
|
|
521
|
+
PUSH_NOTIFICATION_NOT_SUPPORTED: -32003,
|
|
522
|
+
UNSUPPORTED_OPERATION: -32004,
|
|
523
|
+
UNAUTHORIZED: -32005
|
|
524
|
+
};
|
|
525
|
+
function mapErrorToStatus(errorCode) {
|
|
526
|
+
switch (errorCode) {
|
|
527
|
+
case A2A_ERROR_CODE.PARSE_ERROR:
|
|
528
|
+
case A2A_ERROR_CODE.INVALID_REQUEST:
|
|
529
|
+
case A2A_ERROR_CODE.INVALID_PARAMS:
|
|
530
|
+
return HTTP_STATUS.BAD_REQUEST;
|
|
531
|
+
case A2A_ERROR_CODE.METHOD_NOT_FOUND:
|
|
532
|
+
case A2A_ERROR_CODE.TASK_NOT_FOUND:
|
|
533
|
+
return HTTP_STATUS.NOT_FOUND;
|
|
534
|
+
case A2A_ERROR_CODE.TASK_NOT_CANCELABLE:
|
|
535
|
+
return HTTP_STATUS.CONFLICT;
|
|
536
|
+
case A2A_ERROR_CODE.PUSH_NOTIFICATION_NOT_SUPPORTED:
|
|
537
|
+
case A2A_ERROR_CODE.UNSUPPORTED_OPERATION:
|
|
538
|
+
return HTTP_STATUS.BAD_REQUEST;
|
|
539
|
+
case A2A_ERROR_CODE.UNAUTHORIZED:
|
|
540
|
+
return HTTP_STATUS.UNAUTHORIZED;
|
|
541
|
+
default:
|
|
542
|
+
return HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function toHTTPError(error) {
|
|
546
|
+
const errorObject = {
|
|
547
|
+
code: error.code,
|
|
548
|
+
message: error.message
|
|
549
|
+
};
|
|
550
|
+
if (error.data !== void 0) {
|
|
551
|
+
errorObject.data = error.data;
|
|
552
|
+
}
|
|
553
|
+
return errorObject;
|
|
554
|
+
}
|
|
555
|
+
var RestTransportHandler = class _RestTransportHandler {
|
|
556
|
+
requestHandler;
|
|
557
|
+
constructor(requestHandler) {
|
|
558
|
+
this.requestHandler = requestHandler;
|
|
559
|
+
}
|
|
560
|
+
// ==========================================================================
|
|
561
|
+
// Public API Methods
|
|
562
|
+
// ==========================================================================
|
|
563
|
+
/**
|
|
564
|
+
* Gets the agent card (for capability checks).
|
|
565
|
+
*/
|
|
566
|
+
async getAgentCard() {
|
|
567
|
+
return this.requestHandler.getAgentCard();
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Gets the authenticated extended agent card.
|
|
571
|
+
*/
|
|
572
|
+
async getAuthenticatedExtendedAgentCard() {
|
|
573
|
+
return this.requestHandler.getAuthenticatedExtendedAgentCard();
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Sends a message to the agent.
|
|
577
|
+
* Accepts both snake_case and camelCase input, returns camelCase.
|
|
578
|
+
*/
|
|
579
|
+
async sendMessage(params, context) {
|
|
580
|
+
const normalized = this.normalizeMessageParams(params);
|
|
581
|
+
return this.requestHandler.sendMessage(normalized, context);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Sends a message with streaming response.
|
|
585
|
+
* Accepts both snake_case and camelCase input, returns camelCase stream.
|
|
586
|
+
* @throws {A2AError} UnsupportedOperation if streaming not supported
|
|
587
|
+
*/
|
|
588
|
+
async sendMessageStream(params, context) {
|
|
589
|
+
await this.requireCapability("streaming");
|
|
590
|
+
const normalized = this.normalizeMessageParams(params);
|
|
591
|
+
return this.requestHandler.sendMessageStream(normalized, context);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Gets a task by ID.
|
|
595
|
+
* Validates historyLength parameter if provided.
|
|
596
|
+
*/
|
|
597
|
+
async getTask(taskId, context, historyLength) {
|
|
598
|
+
const params = { id: taskId };
|
|
599
|
+
if (historyLength !== void 0) {
|
|
600
|
+
params.historyLength = this.parseHistoryLength(historyLength);
|
|
601
|
+
}
|
|
602
|
+
return this.requestHandler.getTask(params, context);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Cancels a task.
|
|
606
|
+
*/
|
|
607
|
+
async cancelTask(taskId, context) {
|
|
608
|
+
const params = { id: taskId };
|
|
609
|
+
return this.requestHandler.cancelTask(params, context);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Resubscribes to task updates.
|
|
613
|
+
* Returns camelCase stream of task updates.
|
|
614
|
+
* @throws {A2AError} UnsupportedOperation if streaming not supported
|
|
615
|
+
*/
|
|
616
|
+
async resubscribe(taskId, context) {
|
|
617
|
+
await this.requireCapability("streaming");
|
|
618
|
+
const params = { id: taskId };
|
|
619
|
+
return this.requestHandler.resubscribe(params, context);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Sets a push notification configuration.
|
|
623
|
+
* Accepts both snake_case and camelCase input, returns camelCase.
|
|
624
|
+
* @throws {A2AError} PushNotificationNotSupported if push notifications not supported
|
|
625
|
+
*/
|
|
626
|
+
async setTaskPushNotificationConfig(config, context) {
|
|
627
|
+
await this.requireCapability("pushNotifications");
|
|
628
|
+
const normalized = this.normalizeTaskPushNotificationConfig(config);
|
|
629
|
+
return this.requestHandler.setTaskPushNotificationConfig(normalized, context);
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Lists all push notification configurations for a task.
|
|
633
|
+
*/
|
|
634
|
+
async listTaskPushNotificationConfigs(taskId, context) {
|
|
635
|
+
return this.requestHandler.listTaskPushNotificationConfigs({ id: taskId }, context);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Gets a specific push notification configuration.
|
|
639
|
+
*/
|
|
640
|
+
async getTaskPushNotificationConfig(taskId, configId, context) {
|
|
641
|
+
return this.requestHandler.getTaskPushNotificationConfig(
|
|
642
|
+
{ id: taskId, pushNotificationConfigId: configId },
|
|
643
|
+
context
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Deletes a push notification configuration.
|
|
648
|
+
*/
|
|
649
|
+
async deleteTaskPushNotificationConfig(taskId, configId, context) {
|
|
650
|
+
await this.requestHandler.deleteTaskPushNotificationConfig(
|
|
651
|
+
{ id: taskId, pushNotificationConfigId: configId },
|
|
652
|
+
context
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
// ==========================================================================
|
|
656
|
+
// Private Transformation Methods
|
|
657
|
+
// ==========================================================================
|
|
658
|
+
// All type conversion between REST (snake_case) and internal (camelCase) formats
|
|
659
|
+
/**
|
|
660
|
+
* Validates and normalizes message parameters.
|
|
661
|
+
* Accepts both snake_case and camelCase input.
|
|
662
|
+
* @throws {A2AError} InvalidParams if message is missing or conversion fails
|
|
663
|
+
*/
|
|
664
|
+
normalizeMessageParams(input) {
|
|
665
|
+
if (!input.message) {
|
|
666
|
+
throw A2AError.invalidParams("message is required");
|
|
667
|
+
}
|
|
668
|
+
try {
|
|
669
|
+
return this.normalizeMessageSendParams(input);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
if (error instanceof A2AError) throw error;
|
|
672
|
+
throw A2AError.invalidParams(
|
|
673
|
+
error instanceof Error ? error.message : "Invalid message parameters"
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Static map of capability to error for missing capabilities.
|
|
679
|
+
*/
|
|
680
|
+
static CAPABILITY_ERRORS = {
|
|
681
|
+
streaming: () => A2AError.unsupportedOperation("Agent does not support streaming"),
|
|
682
|
+
pushNotifications: () => A2AError.pushNotificationNotSupported()
|
|
683
|
+
};
|
|
684
|
+
/**
|
|
685
|
+
* Validates that the agent supports a required capability.
|
|
686
|
+
* @throws {A2AError} UnsupportedOperation for streaming, PushNotificationNotSupported for push notifications
|
|
687
|
+
*/
|
|
688
|
+
async requireCapability(capability) {
|
|
689
|
+
const agentCard = await this.getAgentCard();
|
|
690
|
+
if (!agentCard.capabilities?.[capability]) {
|
|
691
|
+
throw _RestTransportHandler.CAPABILITY_ERRORS[capability]();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Parses and validates historyLength query parameter.
|
|
696
|
+
*/
|
|
697
|
+
parseHistoryLength(value) {
|
|
698
|
+
if (value === void 0 || value === null) {
|
|
699
|
+
throw A2AError.invalidParams("historyLength is required");
|
|
700
|
+
}
|
|
701
|
+
const parsed = parseInt(String(value), 10);
|
|
702
|
+
if (isNaN(parsed)) {
|
|
703
|
+
throw A2AError.invalidParams("historyLength must be a valid integer");
|
|
704
|
+
}
|
|
705
|
+
if (parsed < 0) {
|
|
706
|
+
throw A2AError.invalidParams("historyLength must be non-negative");
|
|
707
|
+
}
|
|
708
|
+
return parsed;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Normalizes Part input - accepts both snake_case and camelCase for file mimeType.
|
|
712
|
+
*/
|
|
713
|
+
normalizePart(part) {
|
|
714
|
+
if (part.kind === "text") return { kind: "text", text: part.text };
|
|
715
|
+
if (part.kind === "file") {
|
|
716
|
+
const file = this.normalizeFile(part.file);
|
|
717
|
+
return { kind: "file", file, metadata: part.metadata };
|
|
718
|
+
}
|
|
719
|
+
return { kind: "data", data: part.data, metadata: part.metadata };
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Normalizes File input - accepts both snake_case (mime_type) and camelCase (mimeType).
|
|
723
|
+
*/
|
|
724
|
+
normalizeFile(f) {
|
|
725
|
+
const file = f;
|
|
726
|
+
const mimeType = file.mimeType ?? file.mime_type;
|
|
727
|
+
if ("bytes" in file) {
|
|
728
|
+
return { bytes: file.bytes, mimeType, name: file.name };
|
|
729
|
+
}
|
|
730
|
+
return { uri: file.uri, mimeType, name: file.name };
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Normalizes Message input - accepts both snake_case and camelCase.
|
|
734
|
+
*/
|
|
735
|
+
normalizeMessage(input) {
|
|
736
|
+
const m = input;
|
|
737
|
+
const messageId = m.messageId ?? m.message_id;
|
|
738
|
+
if (!messageId) {
|
|
739
|
+
throw A2AError.invalidParams("message.messageId is required");
|
|
740
|
+
}
|
|
741
|
+
if (!m.parts || !Array.isArray(m.parts)) {
|
|
742
|
+
throw A2AError.invalidParams("message.parts must be an array");
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
contextId: m.contextId ?? m.context_id,
|
|
746
|
+
extensions: m.extensions,
|
|
747
|
+
kind: "message",
|
|
748
|
+
messageId,
|
|
749
|
+
metadata: m.metadata,
|
|
750
|
+
parts: m.parts.map((p) => this.normalizePart(p)),
|
|
751
|
+
referenceTaskIds: m.referenceTaskIds ?? m.reference_task_ids,
|
|
752
|
+
role: m.role,
|
|
753
|
+
taskId: m.taskId ?? m.task_id
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Normalizes MessageSendParams - accepts both snake_case and camelCase.
|
|
758
|
+
*/
|
|
759
|
+
normalizeMessageSendParams(input) {
|
|
760
|
+
const p = input;
|
|
761
|
+
const config = p.configuration;
|
|
762
|
+
return {
|
|
763
|
+
configuration: config ? {
|
|
764
|
+
acceptedOutputModes: config.acceptedOutputModes ?? config.accepted_output_modes,
|
|
765
|
+
blocking: config.blocking,
|
|
766
|
+
historyLength: config.historyLength ?? config.history_length
|
|
767
|
+
} : void 0,
|
|
768
|
+
message: this.normalizeMessage(p.message),
|
|
769
|
+
metadata: p.metadata
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Normalizes TaskPushNotificationConfig - accepts both snake_case and camelCase.
|
|
774
|
+
*/
|
|
775
|
+
normalizeTaskPushNotificationConfig(input) {
|
|
776
|
+
const c = input;
|
|
777
|
+
const taskId = c.taskId ?? c.task_id;
|
|
778
|
+
if (!taskId) {
|
|
779
|
+
throw A2AError.invalidParams("taskId is required");
|
|
780
|
+
}
|
|
781
|
+
const pnConfig = c.pushNotificationConfig ?? c.push_notification_config;
|
|
782
|
+
if (!pnConfig) {
|
|
783
|
+
throw A2AError.invalidParams("pushNotificationConfig is required");
|
|
784
|
+
}
|
|
785
|
+
return {
|
|
786
|
+
pushNotificationConfig: pnConfig,
|
|
787
|
+
taskId
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
// src/server/express/rest_handler.ts
|
|
793
|
+
var restErrorHandler = (err, _req, res, next) => {
|
|
794
|
+
if (err instanceof SyntaxError && "body" in err) {
|
|
795
|
+
const a2aError = A2AError.parseError("Invalid JSON payload.");
|
|
796
|
+
return res.status(400).json(toHTTPError(a2aError));
|
|
797
|
+
}
|
|
798
|
+
next(err);
|
|
799
|
+
};
|
|
800
|
+
function restHandler(options) {
|
|
801
|
+
const router = import_express4.default.Router();
|
|
802
|
+
const restTransportHandler = new RestTransportHandler(options.requestHandler);
|
|
803
|
+
router.use(import_express4.default.json(), restErrorHandler);
|
|
804
|
+
const buildContext = async (req) => {
|
|
805
|
+
const user = await options.userBuilder(req);
|
|
806
|
+
return new ServerCallContext(
|
|
807
|
+
Extensions.parseServiceParameter(req.header(HTTP_EXTENSION_HEADER)),
|
|
808
|
+
user
|
|
809
|
+
);
|
|
810
|
+
};
|
|
811
|
+
const setExtensionsHeader = (res, context) => {
|
|
812
|
+
if (context.activatedExtensions) {
|
|
813
|
+
res.setHeader(HTTP_EXTENSION_HEADER, Array.from(context.activatedExtensions));
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
const sendResponse = (res, statusCode, context, body) => {
|
|
817
|
+
setExtensionsHeader(res, context);
|
|
818
|
+
res.status(statusCode);
|
|
819
|
+
if (statusCode === HTTP_STATUS.NO_CONTENT) {
|
|
820
|
+
res.end();
|
|
821
|
+
} else {
|
|
822
|
+
res.json(body);
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
const sendStreamResponse = async (res, stream, context) => {
|
|
826
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
827
|
+
let firstResult;
|
|
828
|
+
try {
|
|
829
|
+
firstResult = await iterator.next();
|
|
830
|
+
} catch (error) {
|
|
831
|
+
const a2aError = error instanceof A2AError ? error : A2AError.internalError(error instanceof Error ? error.message : "Streaming error");
|
|
832
|
+
const statusCode = mapErrorToStatus(a2aError.code);
|
|
833
|
+
sendResponse(res, statusCode, context, toHTTPError(a2aError));
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
Object.entries(SSE_HEADERS).forEach(([key, value]) => {
|
|
837
|
+
res.setHeader(key, value);
|
|
838
|
+
});
|
|
839
|
+
setExtensionsHeader(res, context);
|
|
840
|
+
res.flushHeaders();
|
|
841
|
+
try {
|
|
842
|
+
if (!firstResult.done) {
|
|
843
|
+
res.write(formatSSEEvent(firstResult.value));
|
|
844
|
+
}
|
|
845
|
+
for await (const event of { [Symbol.asyncIterator]: () => iterator }) {
|
|
846
|
+
res.write(formatSSEEvent(event));
|
|
847
|
+
}
|
|
848
|
+
} catch (streamError) {
|
|
849
|
+
console.error("SSE streaming error:", streamError);
|
|
850
|
+
const a2aError = streamError instanceof A2AError ? streamError : A2AError.internalError(
|
|
851
|
+
streamError instanceof Error ? streamError.message : "Streaming error"
|
|
852
|
+
);
|
|
853
|
+
if (!res.writableEnded) {
|
|
854
|
+
res.write(formatSSEErrorEvent(toHTTPError(a2aError)));
|
|
855
|
+
}
|
|
856
|
+
} finally {
|
|
857
|
+
if (!res.writableEnded) {
|
|
858
|
+
res.end();
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
const handleError = (res, error) => {
|
|
863
|
+
if (res.headersSent) {
|
|
864
|
+
if (!res.writableEnded) {
|
|
865
|
+
res.end();
|
|
866
|
+
}
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
const a2aError = error instanceof A2AError ? error : A2AError.internalError(error instanceof Error ? error.message : "Internal server error");
|
|
870
|
+
const statusCode = mapErrorToStatus(a2aError.code);
|
|
871
|
+
res.status(statusCode).json(toHTTPError(a2aError));
|
|
872
|
+
};
|
|
873
|
+
const asyncHandler = (handler) => {
|
|
874
|
+
return async (req, res) => {
|
|
875
|
+
try {
|
|
876
|
+
await handler(req, res);
|
|
877
|
+
} catch (error) {
|
|
878
|
+
handleError(res, error);
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
};
|
|
882
|
+
router.get(
|
|
883
|
+
"/v1/card",
|
|
884
|
+
asyncHandler(async (req, res) => {
|
|
885
|
+
const context = await buildContext(req);
|
|
886
|
+
const result = await restTransportHandler.getAuthenticatedExtendedAgentCard();
|
|
887
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
888
|
+
})
|
|
889
|
+
);
|
|
890
|
+
router.post(
|
|
891
|
+
"/v1/message\\:send",
|
|
892
|
+
asyncHandler(async (req, res) => {
|
|
893
|
+
const context = await buildContext(req);
|
|
894
|
+
const result = await restTransportHandler.sendMessage(req.body, context);
|
|
895
|
+
sendResponse(res, HTTP_STATUS.CREATED, context, result);
|
|
896
|
+
})
|
|
897
|
+
);
|
|
898
|
+
router.post(
|
|
899
|
+
"/v1/message\\:stream",
|
|
900
|
+
asyncHandler(async (req, res) => {
|
|
901
|
+
const context = await buildContext(req);
|
|
902
|
+
const stream = await restTransportHandler.sendMessageStream(req.body, context);
|
|
903
|
+
await sendStreamResponse(res, stream, context);
|
|
904
|
+
})
|
|
905
|
+
);
|
|
906
|
+
router.get(
|
|
907
|
+
"/v1/tasks/:taskId",
|
|
908
|
+
asyncHandler(async (req, res) => {
|
|
909
|
+
const context = await buildContext(req);
|
|
910
|
+
const result = await restTransportHandler.getTask(
|
|
911
|
+
req.params.taskId,
|
|
912
|
+
context,
|
|
913
|
+
req.query.historyLength
|
|
914
|
+
);
|
|
915
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
916
|
+
})
|
|
917
|
+
);
|
|
918
|
+
router.post(
|
|
919
|
+
"/v1/tasks/:taskId\\:cancel",
|
|
920
|
+
asyncHandler(async (req, res) => {
|
|
921
|
+
const context = await buildContext(req);
|
|
922
|
+
const result = await restTransportHandler.cancelTask(req.params.taskId, context);
|
|
923
|
+
sendResponse(res, HTTP_STATUS.ACCEPTED, context, result);
|
|
924
|
+
})
|
|
925
|
+
);
|
|
926
|
+
router.post(
|
|
927
|
+
"/v1/tasks/:taskId\\:subscribe",
|
|
928
|
+
asyncHandler(async (req, res) => {
|
|
929
|
+
const context = await buildContext(req);
|
|
930
|
+
const stream = await restTransportHandler.resubscribe(req.params.taskId, context);
|
|
931
|
+
await sendStreamResponse(res, stream, context);
|
|
932
|
+
})
|
|
933
|
+
);
|
|
934
|
+
router.post(
|
|
935
|
+
"/v1/tasks/:taskId/pushNotificationConfigs",
|
|
936
|
+
asyncHandler(async (req, res) => {
|
|
937
|
+
const context = await buildContext(req);
|
|
938
|
+
const config = {
|
|
939
|
+
...req.body,
|
|
940
|
+
taskId: req.params.taskId,
|
|
941
|
+
task_id: req.params.taskId
|
|
942
|
+
};
|
|
943
|
+
const result = await restTransportHandler.setTaskPushNotificationConfig(config, context);
|
|
944
|
+
sendResponse(res, HTTP_STATUS.CREATED, context, result);
|
|
945
|
+
})
|
|
946
|
+
);
|
|
947
|
+
router.get(
|
|
948
|
+
"/v1/tasks/:taskId/pushNotificationConfigs",
|
|
949
|
+
asyncHandler(async (req, res) => {
|
|
950
|
+
const context = await buildContext(req);
|
|
951
|
+
const result = await restTransportHandler.listTaskPushNotificationConfigs(
|
|
952
|
+
req.params.taskId,
|
|
953
|
+
context
|
|
954
|
+
);
|
|
955
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
956
|
+
})
|
|
957
|
+
);
|
|
958
|
+
router.get(
|
|
959
|
+
"/v1/tasks/:taskId/pushNotificationConfigs/:configId",
|
|
960
|
+
asyncHandler(async (req, res) => {
|
|
961
|
+
const context = await buildContext(req);
|
|
962
|
+
const result = await restTransportHandler.getTaskPushNotificationConfig(
|
|
963
|
+
req.params.taskId,
|
|
964
|
+
req.params.configId,
|
|
965
|
+
context
|
|
966
|
+
);
|
|
967
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
968
|
+
})
|
|
969
|
+
);
|
|
970
|
+
router.delete(
|
|
971
|
+
"/v1/tasks/:taskId/pushNotificationConfigs/:configId",
|
|
972
|
+
asyncHandler(async (req, res) => {
|
|
973
|
+
const context = await buildContext(req);
|
|
974
|
+
await restTransportHandler.deleteTaskPushNotificationConfig(
|
|
975
|
+
req.params.taskId,
|
|
976
|
+
req.params.configId,
|
|
977
|
+
context
|
|
978
|
+
);
|
|
979
|
+
sendResponse(res, HTTP_STATUS.NO_CONTENT, context);
|
|
980
|
+
})
|
|
981
|
+
);
|
|
982
|
+
return router;
|
|
983
|
+
}
|
|
339
984
|
// Annotate the CommonJS export names for ESM import in node:
|
|
340
985
|
0 && (module.exports = {
|
|
341
|
-
A2AExpressApp
|
|
986
|
+
A2AExpressApp,
|
|
987
|
+
UserBuilder,
|
|
988
|
+
agentCardHandler,
|
|
989
|
+
jsonRpcHandler,
|
|
990
|
+
restHandler
|
|
342
991
|
});
|