@adcp/sdk 14.4.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.
Files changed (188) hide show
  1. package/bin/adcp-grade.js +46 -2
  2. package/bin/adcp.js +13 -3
  3. package/dist/lib/auth/oauth/ClientCredentialsFlow.js +4 -1
  4. package/dist/lib/auth/oauth/ClientCredentialsFlow.mjs +4 -1
  5. package/dist/lib/auth/oauth/MCPOAuthProvider.d.mts +19 -6
  6. package/dist/lib/auth/oauth/MCPOAuthProvider.d.ts +19 -6
  7. package/dist/lib/auth/oauth/MCPOAuthProvider.js +95 -30
  8. package/dist/lib/auth/oauth/MCPOAuthProvider.mjs +96 -30
  9. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.mts +1 -1
  10. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.d.ts +1 -1
  11. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.js +1 -1
  12. package/dist/lib/auth/oauth/NonInteractiveFlowHandler.mjs +1 -1
  13. package/dist/lib/auth/oauth/client-credentials-schema.d.mts +17 -0
  14. package/dist/lib/auth/oauth/client-credentials-schema.d.ts +17 -0
  15. package/dist/lib/auth/oauth/client-credentials-schema.js +37 -0
  16. package/dist/lib/auth/oauth/client-credentials-schema.mjs +13 -0
  17. package/dist/lib/auth/oauth/file-storage.d.mts +3 -3
  18. package/dist/lib/auth/oauth/file-storage.d.ts +3 -3
  19. package/dist/lib/auth/oauth/file-storage.js +12 -5
  20. package/dist/lib/auth/oauth/file-storage.mjs +12 -5
  21. package/dist/lib/auth/oauth/index.d.mts +10 -6
  22. package/dist/lib/auth/oauth/index.d.ts +10 -6
  23. package/dist/lib/auth/oauth/index.js +9 -3
  24. package/dist/lib/auth/oauth/index.mjs +8 -3
  25. package/dist/lib/auth/oauth/types.d.mts +9 -2
  26. package/dist/lib/auth/oauth/types.d.ts +9 -2
  27. package/dist/lib/auth/oauth/types.js +3 -2
  28. package/dist/lib/auth/oauth/types.mjs +3 -2
  29. package/dist/lib/auth/oauth/web-flow.d.mts +2 -0
  30. package/dist/lib/auth/oauth/web-flow.d.ts +2 -0
  31. package/dist/lib/auth/oauth/web-flow.js +42 -9
  32. package/dist/lib/auth/oauth/web-flow.mjs +42 -9
  33. package/dist/lib/canonical-references/index.d.mts +19 -1
  34. package/dist/lib/canonical-references/index.d.ts +19 -1
  35. package/dist/lib/canonical-references/index.js +81 -9
  36. package/dist/lib/canonical-references/index.mjs +80 -9
  37. package/dist/lib/client/core.d.mts +1 -0
  38. package/dist/lib/client/core.d.ts +1 -0
  39. package/dist/lib/core/AgentClient.d.mts +9 -1
  40. package/dist/lib/core/AgentClient.d.ts +9 -1
  41. package/dist/lib/core/AgentClient.js +10 -0
  42. package/dist/lib/core/AgentClient.mjs +10 -0
  43. package/dist/lib/core/SingleAgentClient.d.mts +13 -2
  44. package/dist/lib/core/SingleAgentClient.d.ts +13 -2
  45. package/dist/lib/core/SingleAgentClient.js +23 -5
  46. package/dist/lib/core/SingleAgentClient.mjs +23 -5
  47. package/dist/lib/index.d.mts +2 -2
  48. package/dist/lib/index.d.ts +2 -2
  49. package/dist/lib/index.js +2 -0
  50. package/dist/lib/index.mjs +3 -1
  51. package/dist/lib/reporting/gcs/adapter.d.mts +31 -0
  52. package/dist/lib/reporting/gcs/adapter.d.ts +31 -0
  53. package/dist/lib/reporting/gcs/adapter.js +342 -0
  54. package/dist/lib/reporting/gcs/adapter.mjs +325 -0
  55. package/dist/lib/reporting/gcs/bucket-policy.d.mts +3 -0
  56. package/dist/lib/reporting/gcs/bucket-policy.d.ts +3 -0
  57. package/dist/lib/reporting/gcs/bucket-policy.js +31 -0
  58. package/dist/lib/reporting/gcs/bucket-policy.mjs +7 -0
  59. package/dist/lib/reporting/gcs/errors.d.mts +5 -0
  60. package/dist/lib/reporting/gcs/errors.d.ts +5 -0
  61. package/dist/lib/reporting/gcs/errors.js +41 -0
  62. package/dist/lib/reporting/gcs/errors.mjs +16 -0
  63. package/dist/lib/reporting/gcs/fence.d.mts +36 -0
  64. package/dist/lib/reporting/gcs/fence.d.ts +36 -0
  65. package/dist/lib/reporting/gcs/fence.js +250 -0
  66. package/dist/lib/reporting/gcs/fence.mjs +230 -0
  67. package/dist/lib/reporting/gcs/index.d.mts +7 -0
  68. package/dist/lib/reporting/gcs/index.d.ts +7 -0
  69. package/dist/lib/reporting/gcs/index.js +41 -0
  70. package/dist/lib/reporting/gcs/index.mjs +12 -0
  71. package/dist/lib/reporting/gcs/io.d.mts +7 -0
  72. package/dist/lib/reporting/gcs/io.d.ts +7 -0
  73. package/dist/lib/reporting/gcs/io.js +95 -0
  74. package/dist/lib/reporting/gcs/io.mjs +69 -0
  75. package/dist/lib/reporting/gcs/reader.d.mts +43 -0
  76. package/dist/lib/reporting/gcs/reader.d.ts +43 -0
  77. package/dist/lib/reporting/gcs/reader.js +193 -0
  78. package/dist/lib/reporting/gcs/reader.mjs +169 -0
  79. package/dist/lib/reporting/gcs/validation.d.mts +5 -0
  80. package/dist/lib/reporting/gcs/validation.d.ts +5 -0
  81. package/dist/lib/reporting/gcs/validation.js +49 -0
  82. package/dist/lib/reporting/gcs/validation.mjs +25 -0
  83. package/dist/lib/reporting/ledger/handler.d.mts +1 -0
  84. package/dist/lib/reporting/ledger/handler.d.ts +1 -0
  85. package/dist/lib/reporting/ledger/index.d.mts +1 -0
  86. package/dist/lib/reporting/ledger/index.d.ts +1 -0
  87. package/dist/lib/reporting/ledger/index.js +2 -0
  88. package/dist/lib/reporting/ledger/index.mjs +1 -0
  89. package/dist/lib/reporting/ledger/managed-postgres.d.mts +16 -1
  90. package/dist/lib/reporting/ledger/managed-postgres.d.ts +16 -1
  91. package/dist/lib/reporting/ledger/managed-postgres.js +243 -2
  92. package/dist/lib/reporting/ledger/managed-postgres.mjs +248 -2
  93. package/dist/lib/reporting/ledger/object-writes.d.mts +69 -0
  94. package/dist/lib/reporting/ledger/object-writes.d.ts +69 -0
  95. package/dist/lib/reporting/ledger/object-writes.js +147 -0
  96. package/dist/lib/reporting/ledger/object-writes.mjs +114 -0
  97. package/dist/lib/schemas/index.d.mts +2 -0
  98. package/dist/lib/schemas/index.d.ts +2 -0
  99. package/dist/lib/schemas/index.js +3 -0
  100. package/dist/lib/schemas/index.mjs +2 -0
  101. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  102. package/dist/lib/server/a2a-adapter.d.mts +50 -1
  103. package/dist/lib/server/a2a-adapter.d.ts +50 -1
  104. package/dist/lib/server/a2a-adapter.js +230 -7
  105. package/dist/lib/server/a2a-adapter.mjs +237 -7
  106. package/dist/lib/server/auth-signature.d.mts +84 -46
  107. package/dist/lib/server/auth-signature.d.ts +84 -46
  108. package/dist/lib/server/auth-signature.js +48 -14
  109. package/dist/lib/server/auth-signature.mjs +58 -15
  110. package/dist/lib/server/create-adcp-server.js +5 -16
  111. package/dist/lib/server/create-adcp-server.mjs +5 -16
  112. package/dist/lib/server/index.d.mts +1 -1
  113. package/dist/lib/server/index.d.ts +1 -1
  114. package/dist/lib/server/index.js +2 -0
  115. package/dist/lib/server/index.mjs +3 -1
  116. package/dist/lib/signing/agent-fetch.js +3 -33
  117. package/dist/lib/signing/agent-fetch.mjs +3 -33
  118. package/dist/lib/signing/middleware.d.mts +9 -1
  119. package/dist/lib/signing/middleware.d.ts +9 -1
  120. package/dist/lib/signing/operation-resolution.d.mts +37 -0
  121. package/dist/lib/signing/operation-resolution.d.ts +37 -0
  122. package/dist/lib/signing/operation-resolution.js +154 -0
  123. package/dist/lib/signing/operation-resolution.mjs +125 -0
  124. package/dist/lib/signing/postgres-replay-store.d.mts +1 -1
  125. package/dist/lib/signing/postgres-replay-store.d.ts +1 -1
  126. package/dist/lib/signing/redis-replay-store.d.mts +1 -1
  127. package/dist/lib/signing/redis-replay-store.d.ts +1 -1
  128. package/dist/lib/signing/server.d.mts +1 -0
  129. package/dist/lib/signing/server.d.ts +1 -0
  130. package/dist/lib/signing/server.js +7 -0
  131. package/dist/lib/signing/server.mjs +8 -0
  132. package/dist/lib/signing/verifier.d.mts +36 -1
  133. package/dist/lib/signing/verifier.d.ts +36 -1
  134. package/dist/lib/signing/verifier.js +66 -34
  135. package/dist/lib/signing/verifier.mjs +61 -34
  136. package/dist/lib/testing/client.js +8 -3
  137. package/dist/lib/testing/client.mjs +8 -3
  138. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +12 -0
  139. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +12 -0
  140. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +29 -2
  141. package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +27 -1
  142. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.mts +61 -0
  143. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.ts +61 -0
  144. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.js +298 -0
  145. package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.mjs +276 -0
  146. package/dist/lib/testing/storyboard/request-signing/grader.d.mts +31 -1
  147. package/dist/lib/testing/storyboard/request-signing/grader.d.ts +31 -1
  148. package/dist/lib/testing/storyboard/request-signing/grader.js +11 -0
  149. package/dist/lib/testing/storyboard/request-signing/grader.mjs +11 -0
  150. package/dist/lib/testing/storyboard/request-signing/index.d.mts +5 -4
  151. package/dist/lib/testing/storyboard/request-signing/index.d.ts +5 -4
  152. package/dist/lib/testing/storyboard/request-signing/index.js +13 -0
  153. package/dist/lib/testing/storyboard/request-signing/index.mjs +18 -2
  154. package/dist/lib/testing/storyboard/request-signing/types.d.mts +43 -0
  155. package/dist/lib/testing/storyboard/request-signing/types.d.ts +43 -0
  156. package/dist/lib/testing/storyboard/request-signing/types.js +3 -0
  157. package/dist/lib/testing/storyboard/request-signing/types.mjs +2 -0
  158. package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +42 -1
  159. package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +42 -1
  160. package/dist/lib/testing/storyboard/request-signing/vector-loader.js +100 -1
  161. package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +104 -3
  162. package/dist/lib/testing/storyboard/validations.d.mts +1 -1
  163. package/dist/lib/testing/storyboard/validations.d.ts +1 -1
  164. package/dist/lib/testing/types.d.mts +10 -1
  165. package/dist/lib/testing/types.d.ts +10 -1
  166. package/dist/lib/version.d.mts +3 -3
  167. package/dist/lib/version.d.ts +3 -3
  168. package/dist/lib/version.js +3 -3
  169. package/dist/lib/version.mjs +3 -3
  170. package/docs/TYPE-SUMMARY.md +2 -2
  171. package/docs/ZOD-SCHEMAS.md +19 -0
  172. package/docs/guides/BUILD-AN-AGENT.md +9 -4
  173. package/docs/guides/CREATIVE-DELIVERY.md +34 -0
  174. package/docs/guides/OAUTH-ISSUER-BINDING.md +46 -0
  175. package/docs/guides/REPORTING-GCS-FENCE.md +52 -0
  176. package/docs/guides/REPORTING-GCS-MANAGED.md +56 -0
  177. package/docs/guides/REPORTING-OPERATIONS.md +4 -0
  178. package/docs/guides/SIGNING-GUIDE.md +20 -6
  179. package/docs/guides/VALIDATE-YOUR-AGENT.md +4 -0
  180. package/docs/guides/WEB-OAUTH.md +58 -163
  181. package/docs/guides/account-resolution.md +1 -1
  182. package/docs/llms.txt +2 -2
  183. package/docs/migration-14.0-to-14.1.md +1 -1
  184. package/docs/migration-14.x-rc-worksheet.md +8 -7
  185. package/docs/migration-buyer-agent-registry.md +1 -1
  186. package/docs/migration-oauth-issuer-bindings.md +14 -79
  187. package/package.json +19 -1
  188. 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
- if (!options.authenticate) return buildAnonymousUser();
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
- return buildAuthenticatedUser(authInfo);
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 jsonRpc = (0, import_express.jsonRpcHandler)({ requestHandler, userBuilder, legacyCompat });
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
- if (!options.authenticate) return buildAnonymousUser();
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
- return buildAuthenticatedUser(authInfo);
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 jsonRpc = jsonRpcHandler({ requestHandler, userBuilder, legacyCompat });
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
  }