@adcp/sdk 15.0.0 → 15.2.0
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/bin/adcp-grade.js +46 -2
- package/bin/adcp.js +7 -2
- package/dist/lib/canonical-references/index.d.mts +19 -1
- package/dist/lib/canonical-references/index.d.ts +19 -1
- package/dist/lib/canonical-references/index.js +81 -9
- package/dist/lib/canonical-references/index.mjs +80 -9
- package/dist/lib/client/core.d.mts +1 -0
- package/dist/lib/client/core.d.ts +1 -0
- package/dist/lib/core/AgentClient.d.mts +9 -1
- package/dist/lib/core/AgentClient.d.ts +9 -1
- package/dist/lib/core/AgentClient.js +10 -0
- package/dist/lib/core/AgentClient.mjs +10 -0
- package/dist/lib/core/SingleAgentClient.d.mts +13 -2
- package/dist/lib/core/SingleAgentClient.d.ts +13 -2
- package/dist/lib/core/SingleAgentClient.js +23 -5
- package/dist/lib/core/SingleAgentClient.mjs +23 -5
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/reporting/gcs/adapter.d.mts +31 -0
- package/dist/lib/reporting/gcs/adapter.d.ts +31 -0
- package/dist/lib/reporting/gcs/adapter.js +342 -0
- package/dist/lib/reporting/gcs/adapter.mjs +325 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.mts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.ts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.js +31 -0
- package/dist/lib/reporting/gcs/bucket-policy.mjs +7 -0
- package/dist/lib/reporting/gcs/errors.d.mts +5 -0
- package/dist/lib/reporting/gcs/errors.d.ts +5 -0
- package/dist/lib/reporting/gcs/errors.js +41 -0
- package/dist/lib/reporting/gcs/errors.mjs +16 -0
- package/dist/lib/reporting/gcs/fence.d.mts +36 -0
- package/dist/lib/reporting/gcs/fence.d.ts +36 -0
- package/dist/lib/reporting/gcs/fence.js +250 -0
- package/dist/lib/reporting/gcs/fence.mjs +230 -0
- package/dist/lib/reporting/gcs/index.d.mts +7 -0
- package/dist/lib/reporting/gcs/index.d.ts +7 -0
- package/dist/lib/reporting/gcs/index.js +41 -0
- package/dist/lib/reporting/gcs/index.mjs +12 -0
- package/dist/lib/reporting/gcs/io.d.mts +7 -0
- package/dist/lib/reporting/gcs/io.d.ts +7 -0
- package/dist/lib/reporting/gcs/io.js +95 -0
- package/dist/lib/reporting/gcs/io.mjs +69 -0
- package/dist/lib/reporting/gcs/reader.d.mts +43 -0
- package/dist/lib/reporting/gcs/reader.d.ts +43 -0
- package/dist/lib/reporting/gcs/reader.js +193 -0
- package/dist/lib/reporting/gcs/reader.mjs +169 -0
- package/dist/lib/reporting/gcs/validation.d.mts +5 -0
- package/dist/lib/reporting/gcs/validation.d.ts +5 -0
- package/dist/lib/reporting/gcs/validation.js +49 -0
- package/dist/lib/reporting/gcs/validation.mjs +25 -0
- package/dist/lib/reporting/ledger/handler.d.mts +1 -0
- package/dist/lib/reporting/ledger/handler.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.d.mts +1 -0
- package/dist/lib/reporting/ledger/index.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.js +2 -0
- package/dist/lib/reporting/ledger/index.mjs +1 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.js +243 -2
- package/dist/lib/reporting/ledger/managed-postgres.mjs +248 -2
- package/dist/lib/reporting/ledger/object-writes.d.mts +69 -0
- package/dist/lib/reporting/ledger/object-writes.d.ts +69 -0
- package/dist/lib/reporting/ledger/object-writes.js +147 -0
- package/dist/lib/reporting/ledger/object-writes.mjs +114 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.d.mts +50 -1
- package/dist/lib/server/a2a-adapter.d.ts +50 -1
- package/dist/lib/server/a2a-adapter.js +230 -7
- package/dist/lib/server/a2a-adapter.mjs +237 -7
- package/dist/lib/server/auth-signature.d.mts +84 -46
- package/dist/lib/server/auth-signature.d.ts +84 -46
- package/dist/lib/server/auth-signature.js +48 -14
- package/dist/lib/server/auth-signature.mjs +58 -15
- package/dist/lib/server/create-adcp-server.js +5 -16
- package/dist/lib/server/create-adcp-server.mjs +5 -16
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-fetch.js +3 -33
- package/dist/lib/signing/agent-fetch.mjs +3 -33
- package/dist/lib/signing/middleware.d.mts +9 -1
- package/dist/lib/signing/middleware.d.ts +9 -1
- package/dist/lib/signing/operation-resolution.d.mts +37 -0
- package/dist/lib/signing/operation-resolution.d.ts +37 -0
- package/dist/lib/signing/operation-resolution.js +154 -0
- package/dist/lib/signing/operation-resolution.mjs +125 -0
- package/dist/lib/signing/postgres-replay-store.d.mts +1 -1
- package/dist/lib/signing/postgres-replay-store.d.ts +1 -1
- package/dist/lib/signing/redis-replay-store.d.mts +1 -1
- package/dist/lib/signing/redis-replay-store.d.ts +1 -1
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/server.js +7 -0
- package/dist/lib/signing/server.mjs +8 -0
- package/dist/lib/signing/verifier.d.mts +36 -1
- package/dist/lib/signing/verifier.d.ts +36 -1
- package/dist/lib/signing/verifier.js +66 -34
- package/dist/lib/signing/verifier.mjs +61 -34
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +29 -2
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +27 -1
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.mts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.ts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.js +298 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.mjs +276 -0
- package/dist/lib/testing/storyboard/request-signing/grader.d.mts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +11 -0
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +11 -0
- package/dist/lib/testing/storyboard/request-signing/index.d.mts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.d.ts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.js +13 -0
- package/dist/lib/testing/storyboard/request-signing/index.mjs +18 -2
- package/dist/lib/testing/storyboard/request-signing/types.d.mts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.d.ts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.js +3 -0
- package/dist/lib/testing/storyboard/request-signing/types.mjs +2 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +100 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +104 -3
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUILD-AN-AGENT.md +9 -4
- package/docs/guides/CREATIVE-DELIVERY.md +34 -0
- package/docs/guides/REPORTING-GCS-FENCE.md +52 -0
- package/docs/guides/REPORTING-GCS-MANAGED.md +56 -0
- package/docs/guides/REPORTING-OPERATIONS.md +4 -0
- package/docs/guides/SIGNING-GUIDE.md +20 -6
- package/docs/guides/VALIDATE-YOUR-AGENT.md +4 -0
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +5 -4
- package/docs/migration-buyer-agent-registry.md +1 -1
- package/package.json +19 -1
- package/skills/build-seller-agent/specialisms/signed-requests.md +2 -0
|
@@ -28,7 +28,10 @@ var import_sdk = require("@a2a-js/sdk");
|
|
|
28
28
|
var import_v0_3 = require("@a2a-js/sdk/compat/v0_3");
|
|
29
29
|
var import_node_crypto = require("node:crypto");
|
|
30
30
|
var import_redact_secrets = require('../utils/redact-secrets.js');
|
|
31
|
+
var import_operation_resolution = require('../signing/operation-resolution.js');
|
|
31
32
|
var import_adcp_server = require('./adcp-server.js');
|
|
33
|
+
var import_create_adcp_server = require('./create-adcp-server.js');
|
|
34
|
+
var import_auth = require('./auth.js');
|
|
32
35
|
class RedactingA2ATaskStore {
|
|
33
36
|
constructor(delegate) {
|
|
34
37
|
this.delegate = delegate;
|
|
@@ -44,7 +47,7 @@ class RedactingA2ATaskStore {
|
|
|
44
47
|
return this.delegate.list(params, context);
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
|
-
function buildAuthenticatedUser(authInfo) {
|
|
50
|
+
function buildAuthenticatedUser(authInfo, gate) {
|
|
48
51
|
const clientId = authInfo.clientId;
|
|
49
52
|
return {
|
|
50
53
|
get isAuthenticated() {
|
|
@@ -53,23 +56,29 @@ function buildAuthenticatedUser(authInfo) {
|
|
|
53
56
|
get userName() {
|
|
54
57
|
return clientId;
|
|
55
58
|
},
|
|
56
|
-
adcpAuthInfo: authInfo
|
|
59
|
+
adcpAuthInfo: authInfo,
|
|
60
|
+
...gate && { adcpGate: gate }
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
|
-
function buildAnonymousUser() {
|
|
63
|
+
function buildAnonymousUser(gate) {
|
|
60
64
|
return {
|
|
61
65
|
get isAuthenticated() {
|
|
62
66
|
return false;
|
|
63
67
|
},
|
|
64
68
|
get userName() {
|
|
65
69
|
return "anonymous";
|
|
66
|
-
}
|
|
70
|
+
},
|
|
71
|
+
...gate && { adcpGate: gate }
|
|
67
72
|
};
|
|
68
73
|
}
|
|
69
74
|
function getAdcpAuthInfo(context) {
|
|
70
75
|
const user = context?.user;
|
|
71
76
|
return user?.adcpAuthInfo;
|
|
72
77
|
}
|
|
78
|
+
function getGateState(context) {
|
|
79
|
+
const user = context?.user;
|
|
80
|
+
return user?.adcpGate;
|
|
81
|
+
}
|
|
73
82
|
function extractInvocation(message, allowLegacyParameters = false) {
|
|
74
83
|
if (!Array.isArray(message.parts) || message.parts.length === 0) {
|
|
75
84
|
throw new A2AInvocationError("Message must carry at least one part with a `data` kind.");
|
|
@@ -106,6 +115,15 @@ function extractInvocation(message, allowLegacyParameters = false) {
|
|
|
106
115
|
}
|
|
107
116
|
return { skill, input };
|
|
108
117
|
}
|
|
118
|
+
function operationTaskWasCreatedFor(task) {
|
|
119
|
+
const first = task.history?.[0];
|
|
120
|
+
if (!first) return void 0;
|
|
121
|
+
try {
|
|
122
|
+
return extractInvocation(first, true).skill;
|
|
123
|
+
} catch {
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
109
127
|
class A2AInvocationError extends Error {
|
|
110
128
|
constructor(message) {
|
|
111
129
|
super(message);
|
|
@@ -177,6 +195,28 @@ class AdcpA2AAgentExecutor {
|
|
|
177
195
|
});
|
|
178
196
|
return;
|
|
179
197
|
}
|
|
198
|
+
const gate = getGateState(requestContext.context);
|
|
199
|
+
if (gate && gate.operation !== invocation.skill) {
|
|
200
|
+
this.logger.error("A2A adapter: dispatcher operation differs from the signature gate", {
|
|
201
|
+
gateOperation: gate.operation === void 0 ? void 0 : JSON.stringify(gate.operation.slice(0, 64)),
|
|
202
|
+
requestedToolName: JSON.stringify(invocation.skill.slice(0, 64))
|
|
203
|
+
});
|
|
204
|
+
this.emitFailure(eventBus, taskId, contextId, {
|
|
205
|
+
reason: "INVALID_REQUEST",
|
|
206
|
+
message: "The request does not resolve to a single AdCP operation."
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (requestContext.task) {
|
|
211
|
+
const createdFor = operationTaskWasCreatedFor(requestContext.task);
|
|
212
|
+
if (createdFor !== invocation.skill) {
|
|
213
|
+
this.emitFailure(eventBus, taskId, contextId, {
|
|
214
|
+
reason: "INVALID_REQUEST",
|
|
215
|
+
message: "A task continuation must use the operation the task was created for."
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
180
220
|
let response;
|
|
181
221
|
const toolNames = a2aSkillToServerToolNames(invocation.skill);
|
|
182
222
|
let invokedToolName;
|
|
@@ -577,6 +617,157 @@ function validateAgentCard(card) {
|
|
|
577
617
|
);
|
|
578
618
|
}
|
|
579
619
|
}
|
|
620
|
+
const MAX_GATED_BODY_BYTES = 100 * 1024;
|
|
621
|
+
class BodyTooLargeError extends Error {
|
|
622
|
+
}
|
|
623
|
+
function captureRawBody(req, _res, buf) {
|
|
624
|
+
req.rawBody = buf.toString("utf8");
|
|
625
|
+
}
|
|
626
|
+
function resolveSignatureGate(options) {
|
|
627
|
+
const symbols = options.server;
|
|
628
|
+
const attached = symbols[import_create_adcp_server.ADCP_PRE_TRANSPORT];
|
|
629
|
+
const gate = options.preTransport ?? (typeof attached === "function" ? attached : void 0);
|
|
630
|
+
if (gate) return gate;
|
|
631
|
+
const state = symbols[import_create_adcp_server.ADCP_SIGNED_REQUESTS_STATE];
|
|
632
|
+
if ((state?.specialismClaimed || state?.capabilitySupported) && !options.allowUnenforcedSignedRequests) {
|
|
633
|
+
throw new Error(
|
|
634
|
+
"createA2AAdapter: the server advertises request signing (`signed-requests` / `request_signing.supported`) but no signature verifier is wired for A2A, so `required_for` would not be enforced over A2A. Configure `createAdcpServer({ signedRequests })`, pass the same verifier as `preTransport`, or set `allowUnenforcedSignedRequests: true` if a gateway in front of this adapter enforces it."
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
class InvalidRequestTargetError extends Error {
|
|
640
|
+
}
|
|
641
|
+
function signedRequestTargetUrl(req, agentUrl, override) {
|
|
642
|
+
if (override) return new URL(override(req));
|
|
643
|
+
const raw = req.originalUrl || req.url;
|
|
644
|
+
if (!raw.startsWith("/") || raw.startsWith("//")) throw new InvalidRequestTargetError();
|
|
645
|
+
const requested = new URL(raw, "http://placeholder.invalid");
|
|
646
|
+
return new URL(requested.pathname + requested.search, new URL(agentUrl).origin);
|
|
647
|
+
}
|
|
648
|
+
function createSignatureGate(opts) {
|
|
649
|
+
const { gate, next: sdkHandler, gateStates, targetUrl, logger } = opts;
|
|
650
|
+
const reject = (res, code, message) => {
|
|
651
|
+
res.status(401).set("WWW-Authenticate", `Signature error="${code}"`).json({ error: code, message });
|
|
652
|
+
};
|
|
653
|
+
const run = async (req, res) => {
|
|
654
|
+
const gated = req;
|
|
655
|
+
if (gated.rawBody === void 0) {
|
|
656
|
+
const encoding = req.headers["content-encoding"];
|
|
657
|
+
if (encoding !== void 0 && encoding.trim().toLowerCase() !== "identity") {
|
|
658
|
+
res.status(415).json({
|
|
659
|
+
jsonrpc: "2.0",
|
|
660
|
+
id: null,
|
|
661
|
+
error: { code: -32600, message: "Content-Encoding is not supported on a signed-requests endpoint." }
|
|
662
|
+
});
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
const body = await readBody(req);
|
|
667
|
+
if (body !== void 0) {
|
|
668
|
+
gated.rawBody = body;
|
|
669
|
+
try {
|
|
670
|
+
gated.body = JSON.parse(body);
|
|
671
|
+
gated._body = true;
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
} catch (err) {
|
|
676
|
+
if (!(err instanceof BodyTooLargeError)) throw err;
|
|
677
|
+
res.status(413).json({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "Request body too large." } });
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const captured = gated.rawBody;
|
|
682
|
+
const rawBody = typeof captured === "string" ? captured : captured?.toString("utf8");
|
|
683
|
+
if (rawBody === void 0) {
|
|
684
|
+
logger.error(
|
|
685
|
+
"A2A adapter: the raw request body is unavailable, so the signature gate cannot verify it. Mount the adapter ahead of any body parser, or capture the bytes with `express.json({ verify: adapter.rawBodyVerify })`."
|
|
686
|
+
);
|
|
687
|
+
res.status(500).json({ error: "raw_body_unavailable" });
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
gated.rawBody = rawBody;
|
|
691
|
+
let target;
|
|
692
|
+
try {
|
|
693
|
+
target = targetUrl(req);
|
|
694
|
+
} catch (err) {
|
|
695
|
+
if (!(err instanceof InvalidRequestTargetError)) throw err;
|
|
696
|
+
res.status(400).json({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid request target." } });
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
const targetPath = target.pathname + target.search;
|
|
700
|
+
const input = { rawBody, method: req.method, url: targetPath };
|
|
701
|
+
const operation = (0, import_operation_resolution.resolveRequestOperation)(input);
|
|
702
|
+
if ((0, import_operation_resolution.isUnresolvableOperation)(operation)) {
|
|
703
|
+
logger.error("A2A adapter: request body does not resolve to one operation", {
|
|
704
|
+
reason: (0, import_operation_resolution.describeUnresolvable)(input) ?? "unknown"
|
|
705
|
+
});
|
|
706
|
+
reject(res, "request_body_malformed", "Request body does not resolve to exactly one AdCP operation");
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
const originalUrl = req.url;
|
|
710
|
+
const stamped = req;
|
|
711
|
+
const previousContext = stamped[import_auth.ADCP_SERVE_REQUEST_CONTEXT];
|
|
712
|
+
stamped[import_auth.ADCP_SERVE_REQUEST_CONTEXT] = { host: target.host.toLowerCase(), publicUrl: target.origin };
|
|
713
|
+
req.url = targetPath;
|
|
714
|
+
const slots = req;
|
|
715
|
+
const previousAuth = slots.auth;
|
|
716
|
+
const previousSigner = slots.verifiedSigner;
|
|
717
|
+
slots.auth = void 0;
|
|
718
|
+
slots.verifiedSigner = void 0;
|
|
719
|
+
let handled;
|
|
720
|
+
let signedAuth;
|
|
721
|
+
try {
|
|
722
|
+
handled = await gate(req, res);
|
|
723
|
+
if (slots.verifiedSigner !== void 0) signedAuth = slots.auth;
|
|
724
|
+
} finally {
|
|
725
|
+
req.url = originalUrl;
|
|
726
|
+
stamped[import_auth.ADCP_SERVE_REQUEST_CONTEXT] = previousContext;
|
|
727
|
+
slots.auth = previousAuth;
|
|
728
|
+
slots.verifiedSigner = previousSigner;
|
|
729
|
+
}
|
|
730
|
+
if (handled || res.headersSent) return false;
|
|
731
|
+
gated.body = JSON.parse(rawBody);
|
|
732
|
+
gated._body = true;
|
|
733
|
+
gateStates.set(req, { operation, ...signedAuth && { signedAuth } });
|
|
734
|
+
return true;
|
|
735
|
+
};
|
|
736
|
+
return (req, res, next) => {
|
|
737
|
+
if (req.method !== "POST") {
|
|
738
|
+
sdkHandler(req, res, next);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
run(req, res).then(
|
|
742
|
+
(proceed) => {
|
|
743
|
+
if (proceed) sdkHandler(req, res, next);
|
|
744
|
+
},
|
|
745
|
+
(err) => {
|
|
746
|
+
logger.error("A2A adapter: signature gate failed", { error: err?.name || "Error" });
|
|
747
|
+
if (!res.headersSent) res.status(500).json({ error: "verifier_error" });
|
|
748
|
+
}
|
|
749
|
+
);
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function readBody(req) {
|
|
753
|
+
if (req.readableEnded || req.complete && !req.readable) return Promise.resolve(void 0);
|
|
754
|
+
return new Promise((resolve, reject) => {
|
|
755
|
+
const chunks = [];
|
|
756
|
+
let size = 0;
|
|
757
|
+
req.on("data", (chunk) => {
|
|
758
|
+
size += chunk.length;
|
|
759
|
+
if (size > MAX_GATED_BODY_BYTES) {
|
|
760
|
+
req.removeAllListeners("data");
|
|
761
|
+
req.resume();
|
|
762
|
+
reject(new BodyTooLargeError());
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
chunks.push(chunk);
|
|
766
|
+
});
|
|
767
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
768
|
+
req.on("error", reject);
|
|
769
|
+
});
|
|
770
|
+
}
|
|
580
771
|
const DEFAULT_LOGGER = {
|
|
581
772
|
debug: (m, d) => console.debug(m, d ?? ""),
|
|
582
773
|
info: (m, d) => console.info(m, d ?? ""),
|
|
@@ -587,6 +778,16 @@ function createA2AAdapter(options) {
|
|
|
587
778
|
const logger = options.logger ?? DEFAULT_LOGGER;
|
|
588
779
|
const legacyCompat = { enabled: options.legacyCompat?.enabled !== false };
|
|
589
780
|
const card = buildAgentCard(options.server, options.agentCard, legacyCompat);
|
|
781
|
+
const signatureGate = resolveSignatureGate(options);
|
|
782
|
+
if (signatureGate && !options.signedRequestsUrl) {
|
|
783
|
+
try {
|
|
784
|
+
new URL(card.supportedInterfaces[0].url);
|
|
785
|
+
} catch {
|
|
786
|
+
throw new Error(
|
|
787
|
+
"createA2AAdapter: request signing needs an absolute `agentCard.url` (the URL buyers sign), or a `signedRequestsUrl` callback that returns it."
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
590
791
|
const handlerCard = structuredClone(card);
|
|
591
792
|
if (handlerCard.capabilities?.extensions) {
|
|
592
793
|
handlerCard.capabilities.extensions = handlerCard.capabilities.extensions.map(
|
|
@@ -603,15 +804,36 @@ function createA2AAdapter(options) {
|
|
|
603
804
|
const executor = new AdcpA2AAgentExecutor(options.server, logger);
|
|
604
805
|
const eventBusManager = new import_server.DefaultExecutionEventBusManager();
|
|
605
806
|
const requestHandler = new import_server.DefaultRequestHandler(handlerCard, taskStore, executor, eventBusManager);
|
|
807
|
+
const gateStates = /* @__PURE__ */ new WeakMap();
|
|
606
808
|
const userBuilder = async (req) => {
|
|
607
|
-
|
|
809
|
+
const gate = gateStates.get(req);
|
|
810
|
+
const signed = gate?.signedAuth;
|
|
811
|
+
if (!options.authenticate) {
|
|
812
|
+
return signed ? buildAuthenticatedUser(signed, gate) : buildAnonymousUser(gate);
|
|
813
|
+
}
|
|
608
814
|
const authInfo = await options.authenticate(req);
|
|
609
815
|
if (authInfo == null) {
|
|
610
816
|
throw new Error("A2A authentication failed");
|
|
611
817
|
}
|
|
612
|
-
|
|
818
|
+
if (signed) {
|
|
819
|
+
if (authInfo.clientId !== signed.clientId) {
|
|
820
|
+
logger.error(
|
|
821
|
+
`A2A adapter: \`authenticate\` returned a principal that differs from the verified signer (${signed.clientId}). Map the signer in \`authenticate\` (read \`req.verifiedSigner\`) or use makePrincipal.`
|
|
822
|
+
);
|
|
823
|
+
throw new Error("A2A authentication failed");
|
|
824
|
+
}
|
|
825
|
+
return buildAuthenticatedUser({ ...authInfo, extra: { ...authInfo.extra, ...signed.extra } }, gate);
|
|
826
|
+
}
|
|
827
|
+
return buildAuthenticatedUser(authInfo, gate);
|
|
613
828
|
};
|
|
614
|
-
const
|
|
829
|
+
const sdkJsonRpc = (0, import_express.jsonRpcHandler)({ requestHandler, userBuilder, legacyCompat });
|
|
830
|
+
const jsonRpc = signatureGate ? createSignatureGate({
|
|
831
|
+
gate: signatureGate,
|
|
832
|
+
next: sdkJsonRpc,
|
|
833
|
+
gateStates,
|
|
834
|
+
targetUrl: (req) => signedRequestTargetUrl(req, card.supportedInterfaces[0].url, options.signedRequestsUrl),
|
|
835
|
+
logger
|
|
836
|
+
}) : sdkJsonRpc;
|
|
615
837
|
const nativeAgentCardMiddleware = (0, import_express.agentCardHandler)({ agentCardProvider: async () => card });
|
|
616
838
|
const legacyAgentCardMiddleware = (0, import_express.agentCardHandler)({ agentCardProvider: async () => handlerCard, legacyCompat });
|
|
617
839
|
const agentCardMiddleware = (req, res, next) => {
|
|
@@ -643,6 +865,7 @@ function createA2AAdapter(options) {
|
|
|
643
865
|
async getAgentCard() {
|
|
644
866
|
return card;
|
|
645
867
|
},
|
|
868
|
+
rawBodyVerify: captureRawBody,
|
|
646
869
|
mount
|
|
647
870
|
};
|
|
648
871
|
}
|
|
@@ -9,12 +9,22 @@ import { AgentCard, Role, TaskState } from "@a2a-js/sdk";
|
|
|
9
9
|
import { duplicateInterfacesForLegacy } from "@a2a-js/sdk/compat/v0_3";
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { redactSecrets } from "../utils/redact-secrets.mjs";
|
|
12
|
+
import {
|
|
13
|
+
describeUnresolvable,
|
|
14
|
+
resolveRequestOperation,
|
|
15
|
+
isUnresolvableOperation
|
|
16
|
+
} from "../signing/operation-resolution.mjs";
|
|
12
17
|
import {
|
|
13
18
|
getSdkServer,
|
|
14
19
|
isToolAvailableForVersion,
|
|
15
20
|
listRegisteredToolNames,
|
|
16
21
|
resolveDiscoveryVersion
|
|
17
22
|
} from "./adcp-server.mjs";
|
|
23
|
+
import {
|
|
24
|
+
ADCP_PRE_TRANSPORT,
|
|
25
|
+
ADCP_SIGNED_REQUESTS_STATE
|
|
26
|
+
} from "./create-adcp-server.mjs";
|
|
27
|
+
import { ADCP_SERVE_REQUEST_CONTEXT } from "./auth.mjs";
|
|
18
28
|
class RedactingA2ATaskStore {
|
|
19
29
|
constructor(delegate) {
|
|
20
30
|
this.delegate = delegate;
|
|
@@ -30,7 +40,7 @@ class RedactingA2ATaskStore {
|
|
|
30
40
|
return this.delegate.list(params, context);
|
|
31
41
|
}
|
|
32
42
|
}
|
|
33
|
-
function buildAuthenticatedUser(authInfo) {
|
|
43
|
+
function buildAuthenticatedUser(authInfo, gate) {
|
|
34
44
|
const clientId = authInfo.clientId;
|
|
35
45
|
return {
|
|
36
46
|
get isAuthenticated() {
|
|
@@ -39,23 +49,29 @@ function buildAuthenticatedUser(authInfo) {
|
|
|
39
49
|
get userName() {
|
|
40
50
|
return clientId;
|
|
41
51
|
},
|
|
42
|
-
adcpAuthInfo: authInfo
|
|
52
|
+
adcpAuthInfo: authInfo,
|
|
53
|
+
...gate && { adcpGate: gate }
|
|
43
54
|
};
|
|
44
55
|
}
|
|
45
|
-
function buildAnonymousUser() {
|
|
56
|
+
function buildAnonymousUser(gate) {
|
|
46
57
|
return {
|
|
47
58
|
get isAuthenticated() {
|
|
48
59
|
return false;
|
|
49
60
|
},
|
|
50
61
|
get userName() {
|
|
51
62
|
return "anonymous";
|
|
52
|
-
}
|
|
63
|
+
},
|
|
64
|
+
...gate && { adcpGate: gate }
|
|
53
65
|
};
|
|
54
66
|
}
|
|
55
67
|
function getAdcpAuthInfo(context) {
|
|
56
68
|
const user = context?.user;
|
|
57
69
|
return user?.adcpAuthInfo;
|
|
58
70
|
}
|
|
71
|
+
function getGateState(context) {
|
|
72
|
+
const user = context?.user;
|
|
73
|
+
return user?.adcpGate;
|
|
74
|
+
}
|
|
59
75
|
function extractInvocation(message, allowLegacyParameters = false) {
|
|
60
76
|
if (!Array.isArray(message.parts) || message.parts.length === 0) {
|
|
61
77
|
throw new A2AInvocationError("Message must carry at least one part with a `data` kind.");
|
|
@@ -92,6 +108,15 @@ function extractInvocation(message, allowLegacyParameters = false) {
|
|
|
92
108
|
}
|
|
93
109
|
return { skill, input };
|
|
94
110
|
}
|
|
111
|
+
function operationTaskWasCreatedFor(task) {
|
|
112
|
+
const first = task.history?.[0];
|
|
113
|
+
if (!first) return void 0;
|
|
114
|
+
try {
|
|
115
|
+
return extractInvocation(first, true).skill;
|
|
116
|
+
} catch {
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
95
120
|
class A2AInvocationError extends Error {
|
|
96
121
|
constructor(message) {
|
|
97
122
|
super(message);
|
|
@@ -163,6 +188,28 @@ class AdcpA2AAgentExecutor {
|
|
|
163
188
|
});
|
|
164
189
|
return;
|
|
165
190
|
}
|
|
191
|
+
const gate = getGateState(requestContext.context);
|
|
192
|
+
if (gate && gate.operation !== invocation.skill) {
|
|
193
|
+
this.logger.error("A2A adapter: dispatcher operation differs from the signature gate", {
|
|
194
|
+
gateOperation: gate.operation === void 0 ? void 0 : JSON.stringify(gate.operation.slice(0, 64)),
|
|
195
|
+
requestedToolName: JSON.stringify(invocation.skill.slice(0, 64))
|
|
196
|
+
});
|
|
197
|
+
this.emitFailure(eventBus, taskId, contextId, {
|
|
198
|
+
reason: "INVALID_REQUEST",
|
|
199
|
+
message: "The request does not resolve to a single AdCP operation."
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (requestContext.task) {
|
|
204
|
+
const createdFor = operationTaskWasCreatedFor(requestContext.task);
|
|
205
|
+
if (createdFor !== invocation.skill) {
|
|
206
|
+
this.emitFailure(eventBus, taskId, contextId, {
|
|
207
|
+
reason: "INVALID_REQUEST",
|
|
208
|
+
message: "A task continuation must use the operation the task was created for."
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
166
213
|
let response;
|
|
167
214
|
const toolNames = a2aSkillToServerToolNames(invocation.skill);
|
|
168
215
|
let invokedToolName;
|
|
@@ -563,6 +610,157 @@ function validateAgentCard(card) {
|
|
|
563
610
|
);
|
|
564
611
|
}
|
|
565
612
|
}
|
|
613
|
+
const MAX_GATED_BODY_BYTES = 100 * 1024;
|
|
614
|
+
class BodyTooLargeError extends Error {
|
|
615
|
+
}
|
|
616
|
+
function captureRawBody(req, _res, buf) {
|
|
617
|
+
req.rawBody = buf.toString("utf8");
|
|
618
|
+
}
|
|
619
|
+
function resolveSignatureGate(options) {
|
|
620
|
+
const symbols = options.server;
|
|
621
|
+
const attached = symbols[ADCP_PRE_TRANSPORT];
|
|
622
|
+
const gate = options.preTransport ?? (typeof attached === "function" ? attached : void 0);
|
|
623
|
+
if (gate) return gate;
|
|
624
|
+
const state = symbols[ADCP_SIGNED_REQUESTS_STATE];
|
|
625
|
+
if ((state?.specialismClaimed || state?.capabilitySupported) && !options.allowUnenforcedSignedRequests) {
|
|
626
|
+
throw new Error(
|
|
627
|
+
"createA2AAdapter: the server advertises request signing (`signed-requests` / `request_signing.supported`) but no signature verifier is wired for A2A, so `required_for` would not be enforced over A2A. Configure `createAdcpServer({ signedRequests })`, pass the same verifier as `preTransport`, or set `allowUnenforcedSignedRequests: true` if a gateway in front of this adapter enforces it."
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
return void 0;
|
|
631
|
+
}
|
|
632
|
+
class InvalidRequestTargetError extends Error {
|
|
633
|
+
}
|
|
634
|
+
function signedRequestTargetUrl(req, agentUrl, override) {
|
|
635
|
+
if (override) return new URL(override(req));
|
|
636
|
+
const raw = req.originalUrl || req.url;
|
|
637
|
+
if (!raw.startsWith("/") || raw.startsWith("//")) throw new InvalidRequestTargetError();
|
|
638
|
+
const requested = new URL(raw, "http://placeholder.invalid");
|
|
639
|
+
return new URL(requested.pathname + requested.search, new URL(agentUrl).origin);
|
|
640
|
+
}
|
|
641
|
+
function createSignatureGate(opts) {
|
|
642
|
+
const { gate, next: sdkHandler, gateStates, targetUrl, logger } = opts;
|
|
643
|
+
const reject = (res, code, message) => {
|
|
644
|
+
res.status(401).set("WWW-Authenticate", `Signature error="${code}"`).json({ error: code, message });
|
|
645
|
+
};
|
|
646
|
+
const run = async (req, res) => {
|
|
647
|
+
const gated = req;
|
|
648
|
+
if (gated.rawBody === void 0) {
|
|
649
|
+
const encoding = req.headers["content-encoding"];
|
|
650
|
+
if (encoding !== void 0 && encoding.trim().toLowerCase() !== "identity") {
|
|
651
|
+
res.status(415).json({
|
|
652
|
+
jsonrpc: "2.0",
|
|
653
|
+
id: null,
|
|
654
|
+
error: { code: -32600, message: "Content-Encoding is not supported on a signed-requests endpoint." }
|
|
655
|
+
});
|
|
656
|
+
return false;
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
const body = await readBody(req);
|
|
660
|
+
if (body !== void 0) {
|
|
661
|
+
gated.rawBody = body;
|
|
662
|
+
try {
|
|
663
|
+
gated.body = JSON.parse(body);
|
|
664
|
+
gated._body = true;
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
} catch (err) {
|
|
669
|
+
if (!(err instanceof BodyTooLargeError)) throw err;
|
|
670
|
+
res.status(413).json({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "Request body too large." } });
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
const captured = gated.rawBody;
|
|
675
|
+
const rawBody = typeof captured === "string" ? captured : captured?.toString("utf8");
|
|
676
|
+
if (rawBody === void 0) {
|
|
677
|
+
logger.error(
|
|
678
|
+
"A2A adapter: the raw request body is unavailable, so the signature gate cannot verify it. Mount the adapter ahead of any body parser, or capture the bytes with `express.json({ verify: adapter.rawBodyVerify })`."
|
|
679
|
+
);
|
|
680
|
+
res.status(500).json({ error: "raw_body_unavailable" });
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
gated.rawBody = rawBody;
|
|
684
|
+
let target;
|
|
685
|
+
try {
|
|
686
|
+
target = targetUrl(req);
|
|
687
|
+
} catch (err) {
|
|
688
|
+
if (!(err instanceof InvalidRequestTargetError)) throw err;
|
|
689
|
+
res.status(400).json({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid request target." } });
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
const targetPath = target.pathname + target.search;
|
|
693
|
+
const input = { rawBody, method: req.method, url: targetPath };
|
|
694
|
+
const operation = resolveRequestOperation(input);
|
|
695
|
+
if (isUnresolvableOperation(operation)) {
|
|
696
|
+
logger.error("A2A adapter: request body does not resolve to one operation", {
|
|
697
|
+
reason: describeUnresolvable(input) ?? "unknown"
|
|
698
|
+
});
|
|
699
|
+
reject(res, "request_body_malformed", "Request body does not resolve to exactly one AdCP operation");
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
const originalUrl = req.url;
|
|
703
|
+
const stamped = req;
|
|
704
|
+
const previousContext = stamped[ADCP_SERVE_REQUEST_CONTEXT];
|
|
705
|
+
stamped[ADCP_SERVE_REQUEST_CONTEXT] = { host: target.host.toLowerCase(), publicUrl: target.origin };
|
|
706
|
+
req.url = targetPath;
|
|
707
|
+
const slots = req;
|
|
708
|
+
const previousAuth = slots.auth;
|
|
709
|
+
const previousSigner = slots.verifiedSigner;
|
|
710
|
+
slots.auth = void 0;
|
|
711
|
+
slots.verifiedSigner = void 0;
|
|
712
|
+
let handled;
|
|
713
|
+
let signedAuth;
|
|
714
|
+
try {
|
|
715
|
+
handled = await gate(req, res);
|
|
716
|
+
if (slots.verifiedSigner !== void 0) signedAuth = slots.auth;
|
|
717
|
+
} finally {
|
|
718
|
+
req.url = originalUrl;
|
|
719
|
+
stamped[ADCP_SERVE_REQUEST_CONTEXT] = previousContext;
|
|
720
|
+
slots.auth = previousAuth;
|
|
721
|
+
slots.verifiedSigner = previousSigner;
|
|
722
|
+
}
|
|
723
|
+
if (handled || res.headersSent) return false;
|
|
724
|
+
gated.body = JSON.parse(rawBody);
|
|
725
|
+
gated._body = true;
|
|
726
|
+
gateStates.set(req, { operation, ...signedAuth && { signedAuth } });
|
|
727
|
+
return true;
|
|
728
|
+
};
|
|
729
|
+
return (req, res, next) => {
|
|
730
|
+
if (req.method !== "POST") {
|
|
731
|
+
sdkHandler(req, res, next);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
run(req, res).then(
|
|
735
|
+
(proceed) => {
|
|
736
|
+
if (proceed) sdkHandler(req, res, next);
|
|
737
|
+
},
|
|
738
|
+
(err) => {
|
|
739
|
+
logger.error("A2A adapter: signature gate failed", { error: err?.name || "Error" });
|
|
740
|
+
if (!res.headersSent) res.status(500).json({ error: "verifier_error" });
|
|
741
|
+
}
|
|
742
|
+
);
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function readBody(req) {
|
|
746
|
+
if (req.readableEnded || req.complete && !req.readable) return Promise.resolve(void 0);
|
|
747
|
+
return new Promise((resolve, reject) => {
|
|
748
|
+
const chunks = [];
|
|
749
|
+
let size = 0;
|
|
750
|
+
req.on("data", (chunk) => {
|
|
751
|
+
size += chunk.length;
|
|
752
|
+
if (size > MAX_GATED_BODY_BYTES) {
|
|
753
|
+
req.removeAllListeners("data");
|
|
754
|
+
req.resume();
|
|
755
|
+
reject(new BodyTooLargeError());
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
chunks.push(chunk);
|
|
759
|
+
});
|
|
760
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
761
|
+
req.on("error", reject);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
566
764
|
const DEFAULT_LOGGER = {
|
|
567
765
|
debug: (m, d) => console.debug(m, d ?? ""),
|
|
568
766
|
info: (m, d) => console.info(m, d ?? ""),
|
|
@@ -573,6 +771,16 @@ function createA2AAdapter(options) {
|
|
|
573
771
|
const logger = options.logger ?? DEFAULT_LOGGER;
|
|
574
772
|
const legacyCompat = { enabled: options.legacyCompat?.enabled !== false };
|
|
575
773
|
const card = buildAgentCard(options.server, options.agentCard, legacyCompat);
|
|
774
|
+
const signatureGate = resolveSignatureGate(options);
|
|
775
|
+
if (signatureGate && !options.signedRequestsUrl) {
|
|
776
|
+
try {
|
|
777
|
+
new URL(card.supportedInterfaces[0].url);
|
|
778
|
+
} catch {
|
|
779
|
+
throw new Error(
|
|
780
|
+
"createA2AAdapter: request signing needs an absolute `agentCard.url` (the URL buyers sign), or a `signedRequestsUrl` callback that returns it."
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
576
784
|
const handlerCard = structuredClone(card);
|
|
577
785
|
if (handlerCard.capabilities?.extensions) {
|
|
578
786
|
handlerCard.capabilities.extensions = handlerCard.capabilities.extensions.map(
|
|
@@ -589,15 +797,36 @@ function createA2AAdapter(options) {
|
|
|
589
797
|
const executor = new AdcpA2AAgentExecutor(options.server, logger);
|
|
590
798
|
const eventBusManager = new DefaultExecutionEventBusManager();
|
|
591
799
|
const requestHandler = new DefaultRequestHandler(handlerCard, taskStore, executor, eventBusManager);
|
|
800
|
+
const gateStates = /* @__PURE__ */ new WeakMap();
|
|
592
801
|
const userBuilder = async (req) => {
|
|
593
|
-
|
|
802
|
+
const gate = gateStates.get(req);
|
|
803
|
+
const signed = gate?.signedAuth;
|
|
804
|
+
if (!options.authenticate) {
|
|
805
|
+
return signed ? buildAuthenticatedUser(signed, gate) : buildAnonymousUser(gate);
|
|
806
|
+
}
|
|
594
807
|
const authInfo = await options.authenticate(req);
|
|
595
808
|
if (authInfo == null) {
|
|
596
809
|
throw new Error("A2A authentication failed");
|
|
597
810
|
}
|
|
598
|
-
|
|
811
|
+
if (signed) {
|
|
812
|
+
if (authInfo.clientId !== signed.clientId) {
|
|
813
|
+
logger.error(
|
|
814
|
+
`A2A adapter: \`authenticate\` returned a principal that differs from the verified signer (${signed.clientId}). Map the signer in \`authenticate\` (read \`req.verifiedSigner\`) or use makePrincipal.`
|
|
815
|
+
);
|
|
816
|
+
throw new Error("A2A authentication failed");
|
|
817
|
+
}
|
|
818
|
+
return buildAuthenticatedUser({ ...authInfo, extra: { ...authInfo.extra, ...signed.extra } }, gate);
|
|
819
|
+
}
|
|
820
|
+
return buildAuthenticatedUser(authInfo, gate);
|
|
599
821
|
};
|
|
600
|
-
const
|
|
822
|
+
const sdkJsonRpc = jsonRpcHandler({ requestHandler, userBuilder, legacyCompat });
|
|
823
|
+
const jsonRpc = signatureGate ? createSignatureGate({
|
|
824
|
+
gate: signatureGate,
|
|
825
|
+
next: sdkJsonRpc,
|
|
826
|
+
gateStates,
|
|
827
|
+
targetUrl: (req) => signedRequestTargetUrl(req, card.supportedInterfaces[0].url, options.signedRequestsUrl),
|
|
828
|
+
logger
|
|
829
|
+
}) : sdkJsonRpc;
|
|
601
830
|
const nativeAgentCardMiddleware = agentCardHandler({ agentCardProvider: async () => card });
|
|
602
831
|
const legacyAgentCardMiddleware = agentCardHandler({ agentCardProvider: async () => handlerCard, legacyCompat });
|
|
603
832
|
const agentCardMiddleware = (req, res, next) => {
|
|
@@ -629,6 +858,7 @@ function createA2AAdapter(options) {
|
|
|
629
858
|
async getAgentCard() {
|
|
630
859
|
return card;
|
|
631
860
|
},
|
|
861
|
+
rawBodyVerify: captureRawBody,
|
|
632
862
|
mount
|
|
633
863
|
};
|
|
634
864
|
}
|