@mailmodo/a2a 0.3.5 → 0.3.7
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 +171 -93
- package/dist/{a2a_request_handler-DQfg1Q-R.d.ts → a2a_request_handler-1Isk3l-0.d.mts} +16 -4
- package/dist/{a2a_request_handler-DPkhsCMt.d.mts → a2a_request_handler-BuP9LgXH.d.ts} +16 -4
- package/dist/chunk-7JFJW6P6.mjs +38 -0
- package/dist/{chunk-LVD4GF26.mjs → chunk-AZGEDUZX.mjs} +6 -7
- package/dist/chunk-BNBEZNW7.mjs +122 -0
- package/dist/client/index.d.mts +401 -47
- package/dist/client/index.d.ts +401 -47
- package/dist/client/index.js +525 -78
- package/dist/client/index.mjs +1110 -29
- package/dist/{types-Due_Cv6t.d.mts → extensions-DvruCIzw.d.mts} +40 -1
- package/dist/{types-Due_Cv6t.d.ts → extensions-DvruCIzw.d.ts} +40 -1
- package/dist/index.d.mts +5 -33
- package/dist/index.d.ts +5 -33
- package/dist/index.js +27 -2083
- package/dist/index.mjs +6 -42
- package/dist/server/express/index.d.mts +76 -4
- package/dist/server/express/index.d.ts +76 -4
- package/dist/server/express/index.js +563 -37
- package/dist/server/express/index.mjs +642 -6
- package/dist/server/index.d.mts +296 -6
- package/dist/server/index.d.ts +296 -6
- package/dist/server/index.js +265 -52
- package/dist/server/index.mjs +1050 -12
- package/package.json +39 -17
- package/dist/auth-handler-DVLcl8yj.d.mts +0 -209
- package/dist/auth-handler-Gzpf3xHC.d.ts +0 -209
- package/dist/chunk-HZFUOBJQ.mjs +0 -198
- package/dist/chunk-LIEYEFQG.mjs +0 -879
- package/dist/chunk-UBRSFN2J.mjs +0 -776
- package/dist/error-DExKs0Q3.d.mts +0 -233
- package/dist/error-j1vYKII2.d.ts +0 -233
|
@@ -30,7 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
var express_exports = {};
|
|
31
31
|
__export(express_exports, {
|
|
32
32
|
A2AExpressApp: () => A2AExpressApp,
|
|
33
|
-
UserBuilder: () => UserBuilder
|
|
33
|
+
UserBuilder: () => UserBuilder,
|
|
34
|
+
agentCardHandler: () => agentCardHandler,
|
|
35
|
+
jsonRpcHandler: () => jsonRpcHandler,
|
|
36
|
+
restHandler: () => restHandler
|
|
34
37
|
});
|
|
35
38
|
module.exports = __toCommonJS(express_exports);
|
|
36
39
|
|
|
@@ -103,7 +106,7 @@ var A2AError = class _A2AError extends Error {
|
|
|
103
106
|
}
|
|
104
107
|
};
|
|
105
108
|
|
|
106
|
-
// src/server/transports/jsonrpc_transport_handler.ts
|
|
109
|
+
// src/server/transports/jsonrpc/jsonrpc_transport_handler.ts
|
|
107
110
|
var JsonRpcTransportHandler = class {
|
|
108
111
|
requestHandler;
|
|
109
112
|
constructor(requestHandler) {
|
|
@@ -262,6 +265,41 @@ var JsonRpcTransportHandler = class {
|
|
|
262
265
|
}
|
|
263
266
|
};
|
|
264
267
|
|
|
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
|
+
|
|
265
303
|
// src/server/context.ts
|
|
266
304
|
var ServerCallContext = class {
|
|
267
305
|
_requestedExtensions;
|
|
@@ -281,25 +319,10 @@ var ServerCallContext = class {
|
|
|
281
319
|
return this._requestedExtensions;
|
|
282
320
|
}
|
|
283
321
|
addActivatedExtension(uri) {
|
|
284
|
-
|
|
285
|
-
if (!this._activatedExtensions) {
|
|
286
|
-
this._activatedExtensions = /* @__PURE__ */ new Set();
|
|
287
|
-
}
|
|
288
|
-
this._activatedExtensions.add(uri);
|
|
289
|
-
}
|
|
322
|
+
this._activatedExtensions = Extensions.createFrom(this._activatedExtensions, uri);
|
|
290
323
|
}
|
|
291
324
|
};
|
|
292
325
|
|
|
293
|
-
// src/server/utils.ts
|
|
294
|
-
function getRequestedExtensions(values) {
|
|
295
|
-
if (!values) {
|
|
296
|
-
return /* @__PURE__ */ new Set();
|
|
297
|
-
}
|
|
298
|
-
return new Set(
|
|
299
|
-
values.split(",").map((ext) => ext.trim()).filter((ext) => ext.length > 0)
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
326
|
// src/server/authentication/user.ts
|
|
304
327
|
var UnauthenticatedUser = class {
|
|
305
328
|
get isAuthenticated() {
|
|
@@ -310,6 +333,26 @@ var UnauthenticatedUser = class {
|
|
|
310
333
|
}
|
|
311
334
|
};
|
|
312
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
|
+
|
|
313
356
|
// src/server/express/json_rpc_handler.ts
|
|
314
357
|
function jsonRpcHandler(options) {
|
|
315
358
|
const jsonRpcTransportHandler = new JsonRpcTransportHandler(options.requestHandler);
|
|
@@ -319,7 +362,7 @@ function jsonRpcHandler(options) {
|
|
|
319
362
|
try {
|
|
320
363
|
const user = await options.userBuilder(req);
|
|
321
364
|
const context = new ServerCallContext(
|
|
322
|
-
|
|
365
|
+
Extensions.parseServiceParameter(req.header(HTTP_EXTENSION_HEADER)),
|
|
323
366
|
user ?? new UnauthenticatedUser()
|
|
324
367
|
);
|
|
325
368
|
const rpcResponseOrStream = await jsonRpcTransportHandler.handle(req.body, context);
|
|
@@ -328,17 +371,13 @@ function jsonRpcHandler(options) {
|
|
|
328
371
|
}
|
|
329
372
|
if (typeof rpcResponseOrStream?.[Symbol.asyncIterator] === "function") {
|
|
330
373
|
const stream = rpcResponseOrStream;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
374
|
+
Object.entries(SSE_HEADERS).forEach(([key, value]) => {
|
|
375
|
+
res.setHeader(key, value);
|
|
376
|
+
});
|
|
334
377
|
res.flushHeaders();
|
|
335
378
|
try {
|
|
336
379
|
for await (const event of stream) {
|
|
337
|
-
res.write(
|
|
338
|
-
`);
|
|
339
|
-
res.write(`data: ${JSON.stringify(event)}
|
|
340
|
-
|
|
341
|
-
`);
|
|
380
|
+
res.write(formatSSEEvent(event));
|
|
342
381
|
}
|
|
343
382
|
} catch (streamError) {
|
|
344
383
|
console.error(`Error during SSE streaming (request ${req.body?.id}):`, streamError);
|
|
@@ -359,13 +398,7 @@ function jsonRpcHandler(options) {
|
|
|
359
398
|
if (!res.headersSent) {
|
|
360
399
|
res.status(500).json(errorResponse);
|
|
361
400
|
} else {
|
|
362
|
-
res.write(
|
|
363
|
-
`);
|
|
364
|
-
res.write(`event: error
|
|
365
|
-
`);
|
|
366
|
-
res.write(`data: ${JSON.stringify(errorResponse)}
|
|
367
|
-
|
|
368
|
-
`);
|
|
401
|
+
res.write(formatSSEErrorEvent(errorResponse));
|
|
369
402
|
}
|
|
370
403
|
} finally {
|
|
371
404
|
if (!res.writableEnded) {
|
|
@@ -425,14 +458,14 @@ function agentCardHandler(options) {
|
|
|
425
458
|
|
|
426
459
|
// src/server/express/common.ts
|
|
427
460
|
var UserBuilder = {
|
|
428
|
-
|
|
461
|
+
noAuthentication: () => Promise.resolve(new UnauthenticatedUser())
|
|
429
462
|
};
|
|
430
463
|
|
|
431
464
|
// src/server/express/a2a_express_app.ts
|
|
432
465
|
var A2AExpressApp = class {
|
|
433
466
|
requestHandler;
|
|
434
467
|
userBuilder;
|
|
435
|
-
constructor(requestHandler, userBuilder = UserBuilder.
|
|
468
|
+
constructor(requestHandler, userBuilder = UserBuilder.noAuthentication) {
|
|
436
469
|
this.requestHandler = requestHandler;
|
|
437
470
|
this.userBuilder = userBuilder;
|
|
438
471
|
}
|
|
@@ -461,8 +494,501 @@ var A2AExpressApp = class {
|
|
|
461
494
|
return app;
|
|
462
495
|
}
|
|
463
496
|
};
|
|
497
|
+
|
|
498
|
+
// src/server/express/rest_handler.ts
|
|
499
|
+
var import_express4 = __toESM(require("express"));
|
|
500
|
+
|
|
501
|
+
// src/errors.ts
|
|
502
|
+
var A2A_ERROR_CODE = {
|
|
503
|
+
PARSE_ERROR: -32700,
|
|
504
|
+
INVALID_REQUEST: -32600,
|
|
505
|
+
METHOD_NOT_FOUND: -32601,
|
|
506
|
+
INVALID_PARAMS: -32602,
|
|
507
|
+
INTERNAL_ERROR: -32603,
|
|
508
|
+
TASK_NOT_FOUND: -32001,
|
|
509
|
+
TASK_NOT_CANCELABLE: -32002,
|
|
510
|
+
PUSH_NOTIFICATION_NOT_SUPPORTED: -32003,
|
|
511
|
+
UNSUPPORTED_OPERATION: -32004,
|
|
512
|
+
CONTENT_TYPE_NOT_SUPPORTED: -32005,
|
|
513
|
+
INVALID_AGENT_RESPONSE: -32006,
|
|
514
|
+
AUTHENTICATED_EXTENDED_CARD_NOT_CONFIGURED: -32007
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// src/server/transports/rest/rest_transport_handler.ts
|
|
518
|
+
var HTTP_STATUS = {
|
|
519
|
+
OK: 200,
|
|
520
|
+
CREATED: 201,
|
|
521
|
+
ACCEPTED: 202,
|
|
522
|
+
NO_CONTENT: 204,
|
|
523
|
+
BAD_REQUEST: 400,
|
|
524
|
+
UNAUTHORIZED: 401,
|
|
525
|
+
NOT_FOUND: 404,
|
|
526
|
+
CONFLICT: 409,
|
|
527
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
528
|
+
NOT_IMPLEMENTED: 501
|
|
529
|
+
};
|
|
530
|
+
function mapErrorToStatus(errorCode) {
|
|
531
|
+
switch (errorCode) {
|
|
532
|
+
case A2A_ERROR_CODE.PARSE_ERROR:
|
|
533
|
+
case A2A_ERROR_CODE.INVALID_REQUEST:
|
|
534
|
+
case A2A_ERROR_CODE.INVALID_PARAMS:
|
|
535
|
+
return HTTP_STATUS.BAD_REQUEST;
|
|
536
|
+
case A2A_ERROR_CODE.METHOD_NOT_FOUND:
|
|
537
|
+
case A2A_ERROR_CODE.TASK_NOT_FOUND:
|
|
538
|
+
return HTTP_STATUS.NOT_FOUND;
|
|
539
|
+
case A2A_ERROR_CODE.TASK_NOT_CANCELABLE:
|
|
540
|
+
return HTTP_STATUS.CONFLICT;
|
|
541
|
+
case A2A_ERROR_CODE.PUSH_NOTIFICATION_NOT_SUPPORTED:
|
|
542
|
+
case A2A_ERROR_CODE.UNSUPPORTED_OPERATION:
|
|
543
|
+
return HTTP_STATUS.BAD_REQUEST;
|
|
544
|
+
default:
|
|
545
|
+
return HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function toHTTPError(error) {
|
|
549
|
+
const errorObject = {
|
|
550
|
+
code: error.code,
|
|
551
|
+
message: error.message
|
|
552
|
+
};
|
|
553
|
+
if (error.data !== void 0) {
|
|
554
|
+
errorObject.data = error.data;
|
|
555
|
+
}
|
|
556
|
+
return errorObject;
|
|
557
|
+
}
|
|
558
|
+
var RestTransportHandler = class _RestTransportHandler {
|
|
559
|
+
requestHandler;
|
|
560
|
+
constructor(requestHandler) {
|
|
561
|
+
this.requestHandler = requestHandler;
|
|
562
|
+
}
|
|
563
|
+
// ==========================================================================
|
|
564
|
+
// Public API Methods
|
|
565
|
+
// ==========================================================================
|
|
566
|
+
/**
|
|
567
|
+
* Gets the agent card (for capability checks).
|
|
568
|
+
*/
|
|
569
|
+
async getAgentCard() {
|
|
570
|
+
return this.requestHandler.getAgentCard();
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Gets the authenticated extended agent card.
|
|
574
|
+
*/
|
|
575
|
+
async getAuthenticatedExtendedAgentCard(context) {
|
|
576
|
+
return this.requestHandler.getAuthenticatedExtendedAgentCard(context);
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Sends a message to the agent.
|
|
580
|
+
* Accepts both snake_case and camelCase input, returns camelCase.
|
|
581
|
+
*/
|
|
582
|
+
async sendMessage(params, context) {
|
|
583
|
+
const normalized = this.normalizeMessageParams(params);
|
|
584
|
+
return this.requestHandler.sendMessage(normalized, context);
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Sends a message with streaming response.
|
|
588
|
+
* Accepts both snake_case and camelCase input, returns camelCase stream.
|
|
589
|
+
* @throws {A2AError} UnsupportedOperation if streaming not supported
|
|
590
|
+
*/
|
|
591
|
+
async sendMessageStream(params, context) {
|
|
592
|
+
await this.requireCapability("streaming");
|
|
593
|
+
const normalized = this.normalizeMessageParams(params);
|
|
594
|
+
return this.requestHandler.sendMessageStream(normalized, context);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Gets a task by ID.
|
|
598
|
+
* Validates historyLength parameter if provided.
|
|
599
|
+
*/
|
|
600
|
+
async getTask(taskId, context, historyLength) {
|
|
601
|
+
const params = { id: taskId };
|
|
602
|
+
if (historyLength !== void 0) {
|
|
603
|
+
params.historyLength = this.parseHistoryLength(historyLength);
|
|
604
|
+
}
|
|
605
|
+
return this.requestHandler.getTask(params, context);
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Cancels a task.
|
|
609
|
+
*/
|
|
610
|
+
async cancelTask(taskId, context) {
|
|
611
|
+
const params = { id: taskId };
|
|
612
|
+
return this.requestHandler.cancelTask(params, context);
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Resubscribes to task updates.
|
|
616
|
+
* Returns camelCase stream of task updates.
|
|
617
|
+
* @throws {A2AError} UnsupportedOperation if streaming not supported
|
|
618
|
+
*/
|
|
619
|
+
async resubscribe(taskId, context) {
|
|
620
|
+
await this.requireCapability("streaming");
|
|
621
|
+
const params = { id: taskId };
|
|
622
|
+
return this.requestHandler.resubscribe(params, context);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Sets a push notification configuration.
|
|
626
|
+
* Accepts both snake_case and camelCase input, returns camelCase.
|
|
627
|
+
* @throws {A2AError} PushNotificationNotSupported if push notifications not supported
|
|
628
|
+
*/
|
|
629
|
+
async setTaskPushNotificationConfig(config, context) {
|
|
630
|
+
await this.requireCapability("pushNotifications");
|
|
631
|
+
const normalized = this.normalizeTaskPushNotificationConfig(config);
|
|
632
|
+
return this.requestHandler.setTaskPushNotificationConfig(normalized, context);
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Lists all push notification configurations for a task.
|
|
636
|
+
*/
|
|
637
|
+
async listTaskPushNotificationConfigs(taskId, context) {
|
|
638
|
+
return this.requestHandler.listTaskPushNotificationConfigs({ id: taskId }, context);
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Gets a specific push notification configuration.
|
|
642
|
+
*/
|
|
643
|
+
async getTaskPushNotificationConfig(taskId, configId, context) {
|
|
644
|
+
return this.requestHandler.getTaskPushNotificationConfig(
|
|
645
|
+
{ id: taskId, pushNotificationConfigId: configId },
|
|
646
|
+
context
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Deletes a push notification configuration.
|
|
651
|
+
*/
|
|
652
|
+
async deleteTaskPushNotificationConfig(taskId, configId, context) {
|
|
653
|
+
await this.requestHandler.deleteTaskPushNotificationConfig(
|
|
654
|
+
{ id: taskId, pushNotificationConfigId: configId },
|
|
655
|
+
context
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
// ==========================================================================
|
|
659
|
+
// Private Transformation Methods
|
|
660
|
+
// ==========================================================================
|
|
661
|
+
// All type conversion between REST (snake_case) and internal (camelCase) formats
|
|
662
|
+
/**
|
|
663
|
+
* Validates and normalizes message parameters.
|
|
664
|
+
* Accepts both snake_case and camelCase input.
|
|
665
|
+
* @throws {A2AError} InvalidParams if message is missing or conversion fails
|
|
666
|
+
*/
|
|
667
|
+
normalizeMessageParams(input) {
|
|
668
|
+
if (!input.message) {
|
|
669
|
+
throw A2AError.invalidParams("message is required");
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
return this.normalizeMessageSendParams(input);
|
|
673
|
+
} catch (error) {
|
|
674
|
+
if (error instanceof A2AError) throw error;
|
|
675
|
+
throw A2AError.invalidParams(
|
|
676
|
+
error instanceof Error ? error.message : "Invalid message parameters"
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Static map of capability to error for missing capabilities.
|
|
682
|
+
*/
|
|
683
|
+
static CAPABILITY_ERRORS = {
|
|
684
|
+
streaming: () => A2AError.unsupportedOperation("Agent does not support streaming"),
|
|
685
|
+
pushNotifications: () => A2AError.pushNotificationNotSupported()
|
|
686
|
+
};
|
|
687
|
+
/**
|
|
688
|
+
* Validates that the agent supports a required capability.
|
|
689
|
+
* @throws {A2AError} UnsupportedOperation for streaming, PushNotificationNotSupported for push notifications
|
|
690
|
+
*/
|
|
691
|
+
async requireCapability(capability) {
|
|
692
|
+
const agentCard = await this.getAgentCard();
|
|
693
|
+
if (!agentCard.capabilities?.[capability]) {
|
|
694
|
+
throw _RestTransportHandler.CAPABILITY_ERRORS[capability]();
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Parses and validates historyLength query parameter.
|
|
699
|
+
*/
|
|
700
|
+
parseHistoryLength(value) {
|
|
701
|
+
if (value === void 0 || value === null) {
|
|
702
|
+
throw A2AError.invalidParams("historyLength is required");
|
|
703
|
+
}
|
|
704
|
+
const parsed = parseInt(String(value), 10);
|
|
705
|
+
if (isNaN(parsed)) {
|
|
706
|
+
throw A2AError.invalidParams("historyLength must be a valid integer");
|
|
707
|
+
}
|
|
708
|
+
if (parsed < 0) {
|
|
709
|
+
throw A2AError.invalidParams("historyLength must be non-negative");
|
|
710
|
+
}
|
|
711
|
+
return parsed;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Normalizes Part input - accepts both snake_case and camelCase for file mimeType.
|
|
715
|
+
*/
|
|
716
|
+
normalizePart(part) {
|
|
717
|
+
if (part.kind === "text") return { kind: "text", text: part.text };
|
|
718
|
+
if (part.kind === "file") {
|
|
719
|
+
const file = this.normalizeFile(part.file);
|
|
720
|
+
return { kind: "file", file, metadata: part.metadata };
|
|
721
|
+
}
|
|
722
|
+
return { kind: "data", data: part.data, metadata: part.metadata };
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Normalizes File input - accepts both snake_case (mime_type) and camelCase (mimeType).
|
|
726
|
+
*/
|
|
727
|
+
normalizeFile(f) {
|
|
728
|
+
const file = f;
|
|
729
|
+
const mimeType = file.mimeType ?? file.mime_type;
|
|
730
|
+
if ("bytes" in file) {
|
|
731
|
+
return { bytes: file.bytes, mimeType, name: file.name };
|
|
732
|
+
}
|
|
733
|
+
return { uri: file.uri, mimeType, name: file.name };
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Normalizes Message input - accepts both snake_case and camelCase.
|
|
737
|
+
*/
|
|
738
|
+
normalizeMessage(input) {
|
|
739
|
+
const m = input;
|
|
740
|
+
const messageId = m.messageId ?? m.message_id;
|
|
741
|
+
if (!messageId) {
|
|
742
|
+
throw A2AError.invalidParams("message.messageId is required");
|
|
743
|
+
}
|
|
744
|
+
if (!m.parts || !Array.isArray(m.parts)) {
|
|
745
|
+
throw A2AError.invalidParams("message.parts must be an array");
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
contextId: m.contextId ?? m.context_id,
|
|
749
|
+
extensions: m.extensions,
|
|
750
|
+
kind: "message",
|
|
751
|
+
messageId,
|
|
752
|
+
metadata: m.metadata,
|
|
753
|
+
parts: m.parts.map((p) => this.normalizePart(p)),
|
|
754
|
+
referenceTaskIds: m.referenceTaskIds ?? m.reference_task_ids,
|
|
755
|
+
role: m.role,
|
|
756
|
+
taskId: m.taskId ?? m.task_id
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Normalizes MessageSendParams - accepts both snake_case and camelCase.
|
|
761
|
+
*/
|
|
762
|
+
normalizeMessageSendParams(input) {
|
|
763
|
+
const p = input;
|
|
764
|
+
const config = p.configuration;
|
|
765
|
+
return {
|
|
766
|
+
configuration: config ? {
|
|
767
|
+
acceptedOutputModes: config.acceptedOutputModes ?? config.accepted_output_modes,
|
|
768
|
+
blocking: config.blocking,
|
|
769
|
+
historyLength: config.historyLength ?? config.history_length
|
|
770
|
+
} : void 0,
|
|
771
|
+
message: this.normalizeMessage(p.message),
|
|
772
|
+
metadata: p.metadata
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Normalizes TaskPushNotificationConfig - accepts both snake_case and camelCase.
|
|
777
|
+
*/
|
|
778
|
+
normalizeTaskPushNotificationConfig(input) {
|
|
779
|
+
const c = input;
|
|
780
|
+
const taskId = c.taskId ?? c.task_id;
|
|
781
|
+
if (!taskId) {
|
|
782
|
+
throw A2AError.invalidParams("taskId is required");
|
|
783
|
+
}
|
|
784
|
+
const pnConfig = c.pushNotificationConfig ?? c.push_notification_config;
|
|
785
|
+
if (!pnConfig) {
|
|
786
|
+
throw A2AError.invalidParams("pushNotificationConfig is required");
|
|
787
|
+
}
|
|
788
|
+
return {
|
|
789
|
+
pushNotificationConfig: pnConfig,
|
|
790
|
+
taskId
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
// src/server/express/rest_handler.ts
|
|
796
|
+
var restErrorHandler = (err, _req, res, next) => {
|
|
797
|
+
if (err instanceof SyntaxError && "body" in err) {
|
|
798
|
+
const a2aError = A2AError.parseError("Invalid JSON payload.");
|
|
799
|
+
return res.status(400).json(toHTTPError(a2aError));
|
|
800
|
+
}
|
|
801
|
+
next(err);
|
|
802
|
+
};
|
|
803
|
+
function restHandler(options) {
|
|
804
|
+
const router = import_express4.default.Router();
|
|
805
|
+
const restTransportHandler = new RestTransportHandler(options.requestHandler);
|
|
806
|
+
router.use(import_express4.default.json(), restErrorHandler);
|
|
807
|
+
const buildContext = async (req) => {
|
|
808
|
+
const user = await options.userBuilder(req);
|
|
809
|
+
return new ServerCallContext(
|
|
810
|
+
Extensions.parseServiceParameter(req.header(HTTP_EXTENSION_HEADER)),
|
|
811
|
+
user
|
|
812
|
+
);
|
|
813
|
+
};
|
|
814
|
+
const setExtensionsHeader = (res, context) => {
|
|
815
|
+
if (context.activatedExtensions) {
|
|
816
|
+
res.setHeader(HTTP_EXTENSION_HEADER, Array.from(context.activatedExtensions));
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
const sendResponse = (res, statusCode, context, body) => {
|
|
820
|
+
setExtensionsHeader(res, context);
|
|
821
|
+
res.status(statusCode);
|
|
822
|
+
if (statusCode === HTTP_STATUS.NO_CONTENT) {
|
|
823
|
+
res.end();
|
|
824
|
+
} else {
|
|
825
|
+
res.json(body);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
const sendStreamResponse = async (res, stream, context) => {
|
|
829
|
+
const iterator = stream[Symbol.asyncIterator]();
|
|
830
|
+
let firstResult;
|
|
831
|
+
try {
|
|
832
|
+
firstResult = await iterator.next();
|
|
833
|
+
} catch (error) {
|
|
834
|
+
const a2aError = error instanceof A2AError ? error : A2AError.internalError(error instanceof Error ? error.message : "Streaming error");
|
|
835
|
+
const statusCode = mapErrorToStatus(a2aError.code);
|
|
836
|
+
sendResponse(res, statusCode, context, toHTTPError(a2aError));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
Object.entries(SSE_HEADERS).forEach(([key, value]) => {
|
|
840
|
+
res.setHeader(key, value);
|
|
841
|
+
});
|
|
842
|
+
setExtensionsHeader(res, context);
|
|
843
|
+
res.flushHeaders();
|
|
844
|
+
try {
|
|
845
|
+
if (!firstResult.done) {
|
|
846
|
+
res.write(formatSSEEvent(firstResult.value));
|
|
847
|
+
}
|
|
848
|
+
for await (const event of { [Symbol.asyncIterator]: () => iterator }) {
|
|
849
|
+
res.write(formatSSEEvent(event));
|
|
850
|
+
}
|
|
851
|
+
} catch (streamError) {
|
|
852
|
+
console.error("SSE streaming error:", streamError);
|
|
853
|
+
const a2aError = streamError instanceof A2AError ? streamError : A2AError.internalError(
|
|
854
|
+
streamError instanceof Error ? streamError.message : "Streaming error"
|
|
855
|
+
);
|
|
856
|
+
if (!res.writableEnded) {
|
|
857
|
+
res.write(formatSSEErrorEvent(toHTTPError(a2aError)));
|
|
858
|
+
}
|
|
859
|
+
} finally {
|
|
860
|
+
if (!res.writableEnded) {
|
|
861
|
+
res.end();
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
const handleError = (res, error) => {
|
|
866
|
+
if (res.headersSent) {
|
|
867
|
+
if (!res.writableEnded) {
|
|
868
|
+
res.end();
|
|
869
|
+
}
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const a2aError = error instanceof A2AError ? error : A2AError.internalError(error instanceof Error ? error.message : "Internal server error");
|
|
873
|
+
const statusCode = mapErrorToStatus(a2aError.code);
|
|
874
|
+
res.status(statusCode).json(toHTTPError(a2aError));
|
|
875
|
+
};
|
|
876
|
+
const asyncHandler = (handler) => {
|
|
877
|
+
return async (req, res) => {
|
|
878
|
+
try {
|
|
879
|
+
await handler(req, res);
|
|
880
|
+
} catch (error) {
|
|
881
|
+
handleError(res, error);
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
};
|
|
885
|
+
router.get(
|
|
886
|
+
"/v1/card",
|
|
887
|
+
asyncHandler(async (req, res) => {
|
|
888
|
+
const context = await buildContext(req);
|
|
889
|
+
const result = await restTransportHandler.getAuthenticatedExtendedAgentCard(context);
|
|
890
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
891
|
+
})
|
|
892
|
+
);
|
|
893
|
+
router.post(
|
|
894
|
+
"/v1/message\\:send",
|
|
895
|
+
asyncHandler(async (req, res) => {
|
|
896
|
+
const context = await buildContext(req);
|
|
897
|
+
const result = await restTransportHandler.sendMessage(req.body, context);
|
|
898
|
+
sendResponse(res, HTTP_STATUS.CREATED, context, result);
|
|
899
|
+
})
|
|
900
|
+
);
|
|
901
|
+
router.post(
|
|
902
|
+
"/v1/message\\:stream",
|
|
903
|
+
asyncHandler(async (req, res) => {
|
|
904
|
+
const context = await buildContext(req);
|
|
905
|
+
const stream = await restTransportHandler.sendMessageStream(req.body, context);
|
|
906
|
+
await sendStreamResponse(res, stream, context);
|
|
907
|
+
})
|
|
908
|
+
);
|
|
909
|
+
router.get(
|
|
910
|
+
"/v1/tasks/:taskId",
|
|
911
|
+
asyncHandler(async (req, res) => {
|
|
912
|
+
const context = await buildContext(req);
|
|
913
|
+
const result = await restTransportHandler.getTask(
|
|
914
|
+
req.params.taskId,
|
|
915
|
+
context,
|
|
916
|
+
req.query.historyLength
|
|
917
|
+
);
|
|
918
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
919
|
+
})
|
|
920
|
+
);
|
|
921
|
+
router.post(
|
|
922
|
+
"/v1/tasks/:taskId\\:cancel",
|
|
923
|
+
asyncHandler(async (req, res) => {
|
|
924
|
+
const context = await buildContext(req);
|
|
925
|
+
const result = await restTransportHandler.cancelTask(req.params.taskId, context);
|
|
926
|
+
sendResponse(res, HTTP_STATUS.ACCEPTED, context, result);
|
|
927
|
+
})
|
|
928
|
+
);
|
|
929
|
+
router.post(
|
|
930
|
+
"/v1/tasks/:taskId\\:subscribe",
|
|
931
|
+
asyncHandler(async (req, res) => {
|
|
932
|
+
const context = await buildContext(req);
|
|
933
|
+
const stream = await restTransportHandler.resubscribe(req.params.taskId, context);
|
|
934
|
+
await sendStreamResponse(res, stream, context);
|
|
935
|
+
})
|
|
936
|
+
);
|
|
937
|
+
router.post(
|
|
938
|
+
"/v1/tasks/:taskId/pushNotificationConfigs",
|
|
939
|
+
asyncHandler(async (req, res) => {
|
|
940
|
+
const context = await buildContext(req);
|
|
941
|
+
const config = {
|
|
942
|
+
...req.body,
|
|
943
|
+
taskId: req.params.taskId,
|
|
944
|
+
task_id: req.params.taskId
|
|
945
|
+
};
|
|
946
|
+
const result = await restTransportHandler.setTaskPushNotificationConfig(config, context);
|
|
947
|
+
sendResponse(res, HTTP_STATUS.CREATED, context, result);
|
|
948
|
+
})
|
|
949
|
+
);
|
|
950
|
+
router.get(
|
|
951
|
+
"/v1/tasks/:taskId/pushNotificationConfigs",
|
|
952
|
+
asyncHandler(async (req, res) => {
|
|
953
|
+
const context = await buildContext(req);
|
|
954
|
+
const result = await restTransportHandler.listTaskPushNotificationConfigs(
|
|
955
|
+
req.params.taskId,
|
|
956
|
+
context
|
|
957
|
+
);
|
|
958
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
959
|
+
})
|
|
960
|
+
);
|
|
961
|
+
router.get(
|
|
962
|
+
"/v1/tasks/:taskId/pushNotificationConfigs/:configId",
|
|
963
|
+
asyncHandler(async (req, res) => {
|
|
964
|
+
const context = await buildContext(req);
|
|
965
|
+
const result = await restTransportHandler.getTaskPushNotificationConfig(
|
|
966
|
+
req.params.taskId,
|
|
967
|
+
req.params.configId,
|
|
968
|
+
context
|
|
969
|
+
);
|
|
970
|
+
sendResponse(res, HTTP_STATUS.OK, context, result);
|
|
971
|
+
})
|
|
972
|
+
);
|
|
973
|
+
router.delete(
|
|
974
|
+
"/v1/tasks/:taskId/pushNotificationConfigs/:configId",
|
|
975
|
+
asyncHandler(async (req, res) => {
|
|
976
|
+
const context = await buildContext(req);
|
|
977
|
+
await restTransportHandler.deleteTaskPushNotificationConfig(
|
|
978
|
+
req.params.taskId,
|
|
979
|
+
req.params.configId,
|
|
980
|
+
context
|
|
981
|
+
);
|
|
982
|
+
sendResponse(res, HTTP_STATUS.NO_CONTENT, context);
|
|
983
|
+
})
|
|
984
|
+
);
|
|
985
|
+
return router;
|
|
986
|
+
}
|
|
464
987
|
// Annotate the CommonJS export names for ESM import in node:
|
|
465
988
|
0 && (module.exports = {
|
|
466
989
|
A2AExpressApp,
|
|
467
|
-
UserBuilder
|
|
990
|
+
UserBuilder,
|
|
991
|
+
agentCardHandler,
|
|
992
|
+
jsonRpcHandler,
|
|
993
|
+
restHandler
|
|
468
994
|
});
|