@keycardai/a2a 0.1.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 (60) hide show
  1. package/README.md +110 -0
  2. package/dist/cjs/auth.d.ts +48 -0
  3. package/dist/cjs/auth.d.ts.map +1 -0
  4. package/dist/cjs/auth.js +90 -0
  5. package/dist/cjs/auth.js.map +1 -0
  6. package/dist/cjs/config.d.ts +35 -0
  7. package/dist/cjs/config.d.ts.map +1 -0
  8. package/dist/cjs/config.js +19 -0
  9. package/dist/cjs/config.js.map +1 -0
  10. package/dist/cjs/delegation.d.ts +56 -0
  11. package/dist/cjs/delegation.d.ts.map +1 -0
  12. package/dist/cjs/delegation.js +124 -0
  13. package/dist/cjs/delegation.js.map +1 -0
  14. package/dist/cjs/discovery.d.ts +30 -0
  15. package/dist/cjs/discovery.d.ts.map +1 -0
  16. package/dist/cjs/discovery.js +90 -0
  17. package/dist/cjs/discovery.js.map +1 -0
  18. package/dist/cjs/index.d.ts +14 -0
  19. package/dist/cjs/index.d.ts.map +1 -0
  20. package/dist/cjs/index.js +34 -0
  21. package/dist/cjs/index.js.map +1 -0
  22. package/dist/cjs/package.json +1 -0
  23. package/dist/cjs/server.d.ts +32 -0
  24. package/dist/cjs/server.d.ts.map +1 -0
  25. package/dist/cjs/server.js +53 -0
  26. package/dist/cjs/server.js.map +1 -0
  27. package/dist/cjs/types.d.ts +84 -0
  28. package/dist/cjs/types.d.ts.map +1 -0
  29. package/dist/cjs/types.js +21 -0
  30. package/dist/cjs/types.js.map +1 -0
  31. package/dist/esm/auth.d.ts +48 -0
  32. package/dist/esm/auth.d.ts.map +1 -0
  33. package/dist/esm/auth.js +84 -0
  34. package/dist/esm/auth.js.map +1 -0
  35. package/dist/esm/config.d.ts +35 -0
  36. package/dist/esm/config.d.ts.map +1 -0
  37. package/dist/esm/config.js +14 -0
  38. package/dist/esm/config.js.map +1 -0
  39. package/dist/esm/delegation.d.ts +56 -0
  40. package/dist/esm/delegation.d.ts.map +1 -0
  41. package/dist/esm/delegation.js +120 -0
  42. package/dist/esm/delegation.js.map +1 -0
  43. package/dist/esm/discovery.d.ts +30 -0
  44. package/dist/esm/discovery.d.ts.map +1 -0
  45. package/dist/esm/discovery.js +86 -0
  46. package/dist/esm/discovery.js.map +1 -0
  47. package/dist/esm/index.d.ts +14 -0
  48. package/dist/esm/index.d.ts.map +1 -0
  49. package/dist/esm/index.js +15 -0
  50. package/dist/esm/index.js.map +1 -0
  51. package/dist/esm/package.json +1 -0
  52. package/dist/esm/server.d.ts +32 -0
  53. package/dist/esm/server.d.ts.map +1 -0
  54. package/dist/esm/server.js +47 -0
  55. package/dist/esm/server.js.map +1 -0
  56. package/dist/esm/types.d.ts +84 -0
  57. package/dist/esm/types.d.ts.map +1 -0
  58. package/dist/esm/types.js +18 -0
  59. package/dist/esm/types.js.map +1 -0
  60. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # @keycardai/a2a
2
+
3
+ > **Preview.** This SDK has not reached parity with the Keycard Python SDK. APIs may change between minor versions.
4
+
5
+ Keycard auth integration for the [Agent-to-Agent (A2A) protocol](https://google.github.io/A2A). Wraps [`@a2a-js/sdk`](https://github.com/a2aproject/a2a-js) the same way Python's `keycardai-a2a` wraps `a2a-sdk 1.x` — adds Keycard auth on top of the existing SDK's routing, executor, and task store infrastructure.
6
+
7
+ Python equivalent: [`keycardai-a2a`](https://github.com/keycardai/python-sdk/tree/main/packages/a2a).
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @keycardai/a2a @a2a-js/sdk express
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### Build an A2A agent server
18
+
19
+ ```typescript
20
+ import express from "express";
21
+ import { agentCardHandler, jsonRpcHandler } from "@a2a-js/sdk/server/express";
22
+ import { InMemoryTaskStore, type AgentExecutor, type RequestContext, type ExecutionEventBus } from "@a2a-js/sdk/server";
23
+ import {
24
+ keycardUserBuilder,
25
+ getKeycardAuth,
26
+ createKeycardRequestHandler,
27
+ buildAgentCard,
28
+ } from "@keycardai/a2a";
29
+
30
+ const executor: AgentExecutor = {
31
+ async execute(requestContext: RequestContext, eventBus: ExecutionEventBus) {
32
+ const auth = getKeycardAuth(requestContext);
33
+ if (!auth) throw new Error("unauthenticated"); // guard: keycardUserBuilder normally prevents this
34
+ // auth.token is the raw bearer string for downstream delegation
35
+ const text = (requestContext.userMessage.parts[0] as any).text;
36
+ eventBus.publish({ messageId: crypto.randomUUID(), role: "agent",
37
+ parts: [{ kind: "text", text: `Hello: ${text}` }] } as any);
38
+ eventBus.finished();
39
+ },
40
+ async cancelTask() {},
41
+ };
42
+
43
+ const config = {
44
+ serviceName: "My Agent",
45
+ clientId: process.env.KEYCARD_CLIENT_ID!,
46
+ clientSecret: process.env.KEYCARD_CLIENT_SECRET!,
47
+ identityUrl: "https://my-agent.example.com",
48
+ zoneId: process.env.KEYCARD_ZONE_ID,
49
+ };
50
+
51
+ const agentCard = buildAgentCard(config);
52
+ const requestHandler = createKeycardRequestHandler(executor, agentCard);
53
+ const userBuilder = keycardUserBuilder({
54
+ issuer: `https://${config.zoneId}.keycard.cloud`,
55
+ });
56
+
57
+ const app = express();
58
+ app.use(express.json());
59
+ app.use("/.well-known/agent-card.json", agentCardHandler({ agentCardProvider: requestHandler }));
60
+ app.use("/a2a/jsonrpc", jsonRpcHandler({ requestHandler, userBuilder }));
61
+
62
+ app.listen(3000);
63
+ ```
64
+
65
+ ### Call a remote A2A agent
66
+
67
+ ```typescript
68
+ import { DelegationClient, getKeycardAuth } from "@keycardai/a2a";
69
+
70
+ const client = new DelegationClient(config);
71
+
72
+ // Inside your executor — pass the caller's token for delegation chain
73
+ async execute(requestContext, eventBus) {
74
+ const auth = getKeycardAuth(requestContext)!;
75
+ const result = await client.invokeService(
76
+ "https://remote-agent.example.com",
77
+ "Summarize this document",
78
+ { subjectToken: auth.token },
79
+ );
80
+ eventBus.publish(result.message);
81
+ eventBus.finished();
82
+ }
83
+ ```
84
+
85
+ ## How it works
86
+
87
+ `keycardUserBuilder` implements [`@a2a-js/sdk`'s `UserBuilder`](https://github.com/a2aproject/a2a-js) interface — the auth extension point where Keycard JWT validation is wired in. This is the same pattern as Python's `KeycardServerCallContextBuilder`. The builder validates the bearer token with `TokenVerifier`, creates a `KeycardUser` carrying the `AccessToken`, and injects it into each `RequestContext` via `ServerCallContext`.
88
+
89
+ `getKeycardAuth(requestContext)` extracts that `AccessToken` in the executor, giving you the caller's identity and a ready-to-use `token` string for downstream RFC 8693 delegation.
90
+
91
+ ## API
92
+
93
+ | Export | Description |
94
+ |---|---|
95
+ | `keycardUserBuilder(options)` | Returns a `UserBuilder` for `@a2a-js/sdk`'s Express handlers; validates Keycard JWTs |
96
+ | `KeycardUser` | Implements `User`, carries `AccessToken` |
97
+ | `getKeycardAuth(requestContext)` | Extracts `AccessToken` from executor context; returns `null` if unauthenticated |
98
+ | `createKeycardRequestHandler(executor, agentCard, options?)` | Convenience wrapper creating `DefaultRequestHandler` with `InMemoryTaskStore` |
99
+ | `buildAgentCard(config)` | Builds an `AgentCard` from `AgentServiceConfig` |
100
+ | `DelegationClient` | Discovers, exchanges tokens, and invokes remote A2A agents |
101
+ | `ServiceDiscovery` | Fetches and caches agent cards from `/.well-known/agent-card.json` |
102
+ | `AgentServiceConfig` | Config: service name, credentials, identity URL, zone |
103
+
104
+ Re-exports from `@a2a-js/sdk`: `agentCardHandler`, `jsonRpcHandler`, `restHandler`, `UserBuilder`, `AgentExecutor`, `RequestContext`, `ExecutionEventBus`, `InMemoryTaskStore`, `DefaultRequestHandler`, `AgentCard`, `Message`, `Task`.
105
+
106
+ ## Related Packages
107
+
108
+ - [`@keycardai/oauth`](../oauth/) — Token exchange primitives used by `DelegationClient`
109
+ - [`@keycardai/express`](../express/) — Bearer auth middleware for plain HTTP APIs
110
+ - [Keycard TypeScript SDK](../../README.md) — Root documentation
@@ -0,0 +1,48 @@
1
+ import type { User } from "@a2a-js/sdk/server";
2
+ import type { UserBuilder } from "@a2a-js/sdk/server/express";
3
+ import type { TokenVerifierOptions } from "@keycardai/oauth/server/tokenVerifier";
4
+ import type { AccessToken } from "@keycardai/oauth/server/accessToken";
5
+ import type { RequestContext } from "@a2a-js/sdk/server";
6
+ /**
7
+ * A Keycard-verified user. Implements `@a2a-js/sdk`'s `User` interface
8
+ * and carries the full `AccessToken` for downstream delegation.
9
+ *
10
+ * Python equivalent: the `KeycardUser` injected into `ServerCallContext.state`
11
+ * by `KeycardServerCallContextBuilder`.
12
+ */
13
+ export declare class KeycardUser implements User {
14
+ readonly accessToken: AccessToken;
15
+ constructor(accessToken: AccessToken);
16
+ get isAuthenticated(): boolean;
17
+ get userName(): string;
18
+ }
19
+ export type KeycardUserBuilderOptions = Pick<TokenVerifierOptions, "issuer" | "audience" | "enableMultiZone" | "keyring" | "requiredScopes">;
20
+ /**
21
+ * Returns a `UserBuilder` for `@a2a-js/sdk`'s Express handlers that validates
22
+ * Keycard-issued JWTs and injects a `KeycardUser` into the request context.
23
+ *
24
+ * Python equivalent: `KeycardServerCallContextBuilder`, the auth extension
25
+ * point of `a2a-sdk` where Keycard auth is wired in.
26
+ *
27
+ * ```ts
28
+ * const userBuilder = keycardUserBuilder({ issuer: "https://zone.keycard.cloud" });
29
+ * app.post("/a2a/jsonrpc", jsonRpcHandler({ requestHandler, userBuilder }));
30
+ * ```
31
+ *
32
+ * On a valid token the SDK receives a `KeycardUser`; on an invalid or missing
33
+ * token it receives an `UnauthenticatedUser`, and the SDK rejects the request
34
+ * with a 401.
35
+ */
36
+ export declare function keycardUserBuilder(options: KeycardUserBuilderOptions): UserBuilder;
37
+ /**
38
+ * Extract the `AccessToken` from an A2A `RequestContext`.
39
+ * Returns `null` if the request was not authenticated with a Keycard token.
40
+ *
41
+ * ```ts
42
+ * const auth = getKeycardAuth(requestContext);
43
+ * if (!auth) throw new Error("unauthenticated");
44
+ * // use auth.token for downstream delegation
45
+ * ```
46
+ */
47
+ export declare function getKeycardAuth(requestContext: RequestContext): AccessToken | null;
48
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qCAAqC,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAGzD;;;;;;GAMG;AACH,qBAAa,WAAY,YAAW,IAAI;IACtC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;gBAEtB,WAAW,EAAE,WAAW;IAIpC,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;CACF;AAED,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAC1C,oBAAoB,EACpB,QAAQ,GAAG,UAAU,GAAG,iBAAiB,GAAG,SAAS,GAAG,gBAAgB,CACzE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,WAAW,CAsBlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,cAAc,GAAG,WAAW,GAAG,IAAI,CAMjF"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeycardUser = void 0;
4
+ exports.keycardUserBuilder = keycardUserBuilder;
5
+ exports.getKeycardAuth = getKeycardAuth;
6
+ const tokenVerifier_1 = require("@keycardai/oauth/server/tokenVerifier");
7
+ const server_1 = require("@a2a-js/sdk/server");
8
+ /**
9
+ * A Keycard-verified user. Implements `@a2a-js/sdk`'s `User` interface
10
+ * and carries the full `AccessToken` for downstream delegation.
11
+ *
12
+ * Python equivalent: the `KeycardUser` injected into `ServerCallContext.state`
13
+ * by `KeycardServerCallContextBuilder`.
14
+ */
15
+ class KeycardUser {
16
+ constructor(accessToken) {
17
+ this.accessToken = accessToken;
18
+ }
19
+ get isAuthenticated() {
20
+ return true;
21
+ }
22
+ get userName() {
23
+ return this.accessToken.clientId;
24
+ }
25
+ }
26
+ exports.KeycardUser = KeycardUser;
27
+ /**
28
+ * Returns a `UserBuilder` for `@a2a-js/sdk`'s Express handlers that validates
29
+ * Keycard-issued JWTs and injects a `KeycardUser` into the request context.
30
+ *
31
+ * Python equivalent: `KeycardServerCallContextBuilder`, the auth extension
32
+ * point of `a2a-sdk` where Keycard auth is wired in.
33
+ *
34
+ * ```ts
35
+ * const userBuilder = keycardUserBuilder({ issuer: "https://zone.keycard.cloud" });
36
+ * app.post("/a2a/jsonrpc", jsonRpcHandler({ requestHandler, userBuilder }));
37
+ * ```
38
+ *
39
+ * On a valid token the SDK receives a `KeycardUser`; on an invalid or missing
40
+ * token it receives an `UnauthenticatedUser`, and the SDK rejects the request
41
+ * with a 401.
42
+ */
43
+ function keycardUserBuilder(options) {
44
+ const verifier = new tokenVerifier_1.TokenVerifier({
45
+ issuer: options.issuer,
46
+ audience: options.audience,
47
+ enableMultiZone: options.enableMultiZone,
48
+ keyring: options.keyring,
49
+ requiredScopes: options.requiredScopes,
50
+ });
51
+ return async (req) => {
52
+ const authorization = req.headers.authorization;
53
+ if (!authorization?.startsWith("Bearer ")) {
54
+ // -32001 is the A2A unauthorized error code
55
+ throw new server_1.A2AError(-32001, "Missing or invalid Authorization header");
56
+ }
57
+ const token = authorization.slice(7);
58
+ const accessToken = await verifier.verifyToken(token);
59
+ if (!accessToken) {
60
+ throw new server_1.A2AError(-32001, "Invalid or expired token");
61
+ }
62
+ return new KeycardUser(accessToken);
63
+ };
64
+ }
65
+ /**
66
+ * Extract the `AccessToken` from an A2A `RequestContext`.
67
+ * Returns `null` if the request was not authenticated with a Keycard token.
68
+ *
69
+ * ```ts
70
+ * const auth = getKeycardAuth(requestContext);
71
+ * if (!auth) throw new Error("unauthenticated");
72
+ * // use auth.token for downstream delegation
73
+ * ```
74
+ */
75
+ function getKeycardAuth(requestContext) {
76
+ const user = requestContext.context?.user;
77
+ if (user instanceof KeycardUser) {
78
+ return user.accessToken;
79
+ }
80
+ return null;
81
+ }
82
+ class UnauthenticatedUser {
83
+ get isAuthenticated() {
84
+ return false;
85
+ }
86
+ get userName() {
87
+ return "anonymous";
88
+ }
89
+ }
90
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":";;;AAqDA,gDAsBC;AAYD,wCAMC;AA1FD,yEAAsE;AAItE,+CAA8C;AAE9C;;;;;;GAMG;AACH,MAAa,WAAW;IAGtB,YAAY,WAAwB;QAClC,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;IACnC,CAAC;CACF;AAdD,kCAcC;AAOD;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,kBAAkB,CAAC,OAAkC;IACnE,MAAM,QAAQ,GAAG,IAAI,6BAAa,CAAC;QACjC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,eAAe,EAAE,OAAO,CAAC,eAAe;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC,CAAC;IAEH,OAAO,KAAK,EAAE,GAAY,EAAiB,EAAE;QAC3C,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QAChD,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1C,4CAA4C;YAC5C,MAAM,IAAI,iBAAQ,CAAC,CAAC,KAAK,EAAE,yCAAyC,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACtD,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,iBAAQ,CAAC,CAAC,KAAK,EAAE,0BAA0B,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,IAAI,WAAW,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,cAAc,CAAC,cAA8B;IAC3D,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC;IAC1C,IAAI,IAAI,YAAY,WAAW,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,mBAAmB;IACvB,IAAI,eAAe;QACjB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,QAAQ;QACV,OAAO,WAAW,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,35 @@
1
+ import type { AgentSkill } from "@a2a-js/sdk";
2
+ export type { AgentSkill } from "@a2a-js/sdk";
3
+ /**
4
+ * Configuration for a Keycard-protected A2A agent service.
5
+ *
6
+ * Python equivalent: `keycardai.a2a.AgentServiceConfig`
7
+ */
8
+ export interface AgentServiceConfig {
9
+ /** Human-readable service name, used as the agent card `name`. */
10
+ serviceName: string;
11
+ /** Keycard client ID for service-to-service token exchange. */
12
+ clientId: string;
13
+ /** Keycard client secret. */
14
+ clientSecret: string;
15
+ /**
16
+ * Public URL of this agent service, e.g. "https://my-agent.example.com".
17
+ * Used to construct the JSONRPC endpoint URL.
18
+ */
19
+ identityUrl: string;
20
+ /** Keycard zone ID, e.g. "abc1234". Constructs the auth server URL. */
21
+ zoneId?: string;
22
+ /**
23
+ * Explicit authorization server URL. Defaults to
24
+ * `https://{zoneId}.keycard.cloud` when `zoneId` is provided.
25
+ */
26
+ authorizationServerUrl?: string;
27
+ /** Description for the agent card. */
28
+ description?: string;
29
+ /** Skills advertised in the agent card. */
30
+ skills?: readonly AgentSkill[];
31
+ }
32
+ export declare function getAgentCardUrl(config: AgentServiceConfig): string;
33
+ export declare function getJsonrpcUrl(config: AgentServiceConfig): string;
34
+ export declare function getAuthServerUrl(config: AgentServiceConfig): string;
35
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CAChC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAElE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAEhE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAMnE"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAgentCardUrl = getAgentCardUrl;
4
+ exports.getJsonrpcUrl = getJsonrpcUrl;
5
+ exports.getAuthServerUrl = getAuthServerUrl;
6
+ function getAgentCardUrl(config) {
7
+ return `${config.identityUrl}/.well-known/agent-card.json`;
8
+ }
9
+ function getJsonrpcUrl(config) {
10
+ return `${config.identityUrl}/a2a/jsonrpc`;
11
+ }
12
+ function getAuthServerUrl(config) {
13
+ if (config.authorizationServerUrl)
14
+ return config.authorizationServerUrl;
15
+ if (config.zoneId)
16
+ return `https://${config.zoneId}.keycard.cloud`;
17
+ throw new Error("AgentServiceConfig: either `authorizationServerUrl` or `zoneId` is required");
18
+ }
19
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":";;AAkCA,0CAEC;AAED,sCAEC;AAED,4CAMC;AAdD,SAAgB,eAAe,CAAC,MAA0B;IACxD,OAAO,GAAG,MAAM,CAAC,WAAW,8BAA8B,CAAC;AAC7D,CAAC;AAED,SAAgB,aAAa,CAAC,MAA0B;IACtD,OAAO,GAAG,MAAM,CAAC,WAAW,cAAc,CAAC;AAC7C,CAAC;AAED,SAAgB,gBAAgB,CAAC,MAA0B;IACzD,IAAI,MAAM,CAAC,sBAAsB;QAAE,OAAO,MAAM,CAAC,sBAAsB,CAAC;IACxE,IAAI,MAAM,CAAC,MAAM;QAAE,OAAO,WAAW,MAAM,CAAC,MAAM,gBAAgB,CAAC;IACnE,MAAM,IAAI,KAAK,CACb,6EAA6E,CAC9E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,56 @@
1
+ import type { AgentCard } from "@a2a-js/sdk";
2
+ import type { Message } from "@a2a-js/sdk";
3
+ import { ServiceDiscovery } from "./discovery.js";
4
+ import type { AgentServiceConfig } from "./config.js";
5
+ export interface DelegationResult {
6
+ /** The agent's response message. */
7
+ message: Message;
8
+ /** Resolved agent card for the target service. */
9
+ agentCard: AgentCard;
10
+ }
11
+ export interface InvokeOptions {
12
+ /**
13
+ * Keycard bearer token from the current request context. Pass
14
+ * `getKeycardAuth(requestContext)?.token` from the executor.
15
+ * Required for all delegation flows.
16
+ *
17
+ * For service-to-service delegation without a user token (equivalent to
18
+ * Python's client-credentials fallback in `DelegationClient`), first
19
+ * acquire a service access token from Keycard, then pass it here.
20
+ * A convenience method for this path is a planned follow-up.
21
+ */
22
+ subjectToken: string;
23
+ /** Timeout in ms for the JSONRPC call. Default: 30 000. */
24
+ timeoutMs?: number;
25
+ /** Arbitrary metadata to attach to the A2A message. */
26
+ metadata?: Record<string, unknown>;
27
+ }
28
+ /**
29
+ * Client for delegating tasks to remote A2A agent services with
30
+ * Keycard token exchange.
31
+ *
32
+ * ```ts
33
+ * const client = new DelegationClient(config);
34
+ *
35
+ * // Inside your AgentExecutor.execute():
36
+ * const auth = getKeycardAuth(requestContext);
37
+ * const result = await client.invokeService(targetUrl, "summarize this", {
38
+ * subjectToken: auth!.token,
39
+ * });
40
+ * eventBus.publish(result.message);
41
+ * eventBus.finished();
42
+ * ```
43
+ *
44
+ * Python equivalent: `keycardai.a2a.DelegationClient`
45
+ */
46
+ export declare class DelegationClient {
47
+ #private;
48
+ constructor(config: AgentServiceConfig, options?: {
49
+ discovery?: ServiceDiscovery;
50
+ });
51
+ /**
52
+ * Discover, authenticate, and invoke a remote A2A agent in one call.
53
+ */
54
+ invokeService(serviceUrl: string, task: string, options: InvokeOptions): Promise<DelegationResult>;
55
+ }
56
+ //# sourceMappingURL=delegation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../../src/delegation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGtD,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;OASG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,gBAAgB;;gBAKf,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,gBAAgB,CAAA;KAAE;IAQlF;;OAEG;IACG,aAAa,CACjB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,gBAAgB,CAAC;CAsE7B"}
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _DelegationClient_instances, _DelegationClient_config, _DelegationClient_tokenClient, _DelegationClient_discovery, _DelegationClient_getDelegationToken, _DelegationClient_sendMessage;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DelegationClient = void 0;
16
+ const tokenExchange_1 = require("@keycardai/oauth/tokenExchange");
17
+ const clientSecret_1 = require("@keycardai/oauth/server/clientSecret");
18
+ const discovery_js_1 = require("./discovery.js");
19
+ const config_js_1 = require("./config.js");
20
+ const A2A_JSONRPC_PATH = "/a2a/jsonrpc";
21
+ const A2A_PROTOCOL_VERSION = "0.3";
22
+ const A2A_VERSION_HEADER = "x-a2a-protocol-version";
23
+ /**
24
+ * Client for delegating tasks to remote A2A agent services with
25
+ * Keycard token exchange.
26
+ *
27
+ * ```ts
28
+ * const client = new DelegationClient(config);
29
+ *
30
+ * // Inside your AgentExecutor.execute():
31
+ * const auth = getKeycardAuth(requestContext);
32
+ * const result = await client.invokeService(targetUrl, "summarize this", {
33
+ * subjectToken: auth!.token,
34
+ * });
35
+ * eventBus.publish(result.message);
36
+ * eventBus.finished();
37
+ * ```
38
+ *
39
+ * Python equivalent: `keycardai.a2a.DelegationClient`
40
+ */
41
+ class DelegationClient {
42
+ constructor(config, options) {
43
+ _DelegationClient_instances.add(this);
44
+ _DelegationClient_config.set(this, void 0);
45
+ _DelegationClient_tokenClient.set(this, void 0);
46
+ _DelegationClient_discovery.set(this, void 0);
47
+ __classPrivateFieldSet(this, _DelegationClient_config, config, "f");
48
+ __classPrivateFieldSet(this, _DelegationClient_discovery, options?.discovery ?? new discovery_js_1.ServiceDiscovery(), "f");
49
+ __classPrivateFieldSet(this, _DelegationClient_tokenClient, new tokenExchange_1.TokenExchangeClient((0, config_js_1.getAuthServerUrl)(config), {
50
+ credential: new clientSecret_1.ClientSecret(config.clientId, config.clientSecret),
51
+ }), "f");
52
+ }
53
+ /**
54
+ * Discover, authenticate, and invoke a remote A2A agent in one call.
55
+ */
56
+ async invokeService(serviceUrl, task, options) {
57
+ const agentCard = await __classPrivateFieldGet(this, _DelegationClient_discovery, "f").getServiceCard(serviceUrl);
58
+ const delegationToken = await __classPrivateFieldGet(this, _DelegationClient_instances, "m", _DelegationClient_getDelegationToken).call(this, serviceUrl, options.subjectToken);
59
+ const message = buildUserMessage(task, options.metadata);
60
+ const jsonrpcUrl = buildJsonrpcUrl(serviceUrl, agentCard);
61
+ const responseMessage = await __classPrivateFieldGet(this, _DelegationClient_instances, "m", _DelegationClient_sendMessage).call(this, jsonrpcUrl, message, delegationToken, options.timeoutMs);
62
+ return { message: responseMessage, agentCard };
63
+ }
64
+ }
65
+ exports.DelegationClient = DelegationClient;
66
+ _DelegationClient_config = new WeakMap(), _DelegationClient_tokenClient = new WeakMap(), _DelegationClient_discovery = new WeakMap(), _DelegationClient_instances = new WeakSet(), _DelegationClient_getDelegationToken = async function _DelegationClient_getDelegationToken(targetUrl, subjectToken) {
67
+ const response = await __classPrivateFieldGet(this, _DelegationClient_tokenClient, "f").exchangeToken({
68
+ subjectToken,
69
+ subjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
70
+ resource: targetUrl,
71
+ });
72
+ return response.accessToken;
73
+ }, _DelegationClient_sendMessage = async function _DelegationClient_sendMessage(jsonrpcUrl, message, bearerToken, timeoutMs = 30_000) {
74
+ const requestBody = {
75
+ jsonrpc: "2.0",
76
+ id: crypto.randomUUID(),
77
+ method: "message/send",
78
+ params: { message },
79
+ };
80
+ const response = await fetch(jsonrpcUrl, {
81
+ method: "POST",
82
+ headers: {
83
+ "Content-Type": "application/json",
84
+ Accept: "application/json",
85
+ Authorization: `Bearer ${bearerToken}`,
86
+ [A2A_VERSION_HEADER]: A2A_PROTOCOL_VERSION,
87
+ },
88
+ body: JSON.stringify(requestBody),
89
+ signal: AbortSignal.timeout(timeoutMs),
90
+ });
91
+ if (!response.ok) {
92
+ throw new Error(`DelegationClient: A2A request to "${jsonrpcUrl}" failed (HTTP ${response.status})`);
93
+ }
94
+ let body;
95
+ try {
96
+ body = await response.json();
97
+ }
98
+ catch {
99
+ throw new Error(`DelegationClient: response from "${jsonrpcUrl}" is not valid JSON`);
100
+ }
101
+ const envelope = body;
102
+ if (!envelope.result?.message) {
103
+ throw new Error(`DelegationClient: A2A error from "${jsonrpcUrl}": ${envelope.error?.message ?? "unknown error"}`);
104
+ }
105
+ return envelope.result.message;
106
+ };
107
+ function buildUserMessage(text, metadata) {
108
+ return {
109
+ messageId: crypto.randomUUID(),
110
+ role: "user",
111
+ parts: [{ kind: "text", text }],
112
+ ...(metadata ? { metadata } : {}),
113
+ };
114
+ }
115
+ function buildJsonrpcUrl(serviceUrl, agentCard) {
116
+ // agentCard.url IS the JSONRPC endpoint per the A2A spec — use it directly.
117
+ // Do not append A2A_JSONRPC_PATH: the agent card is built with getJsonrpcUrl()
118
+ // which already includes /a2a/jsonrpc, so appending would double the path.
119
+ if (agentCard.url) {
120
+ return agentCard.url;
121
+ }
122
+ return `${serviceUrl.replace(/\/$/, "")}${A2A_JSONRPC_PATH}`;
123
+ }
124
+ //# sourceMappingURL=delegation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation.js","sourceRoot":"","sources":["../../src/delegation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { AgentCard } from "@a2a-js/sdk";
2
+ export type { AgentCard } from "@a2a-js/sdk";
3
+ /**
4
+ * Fetches and caches agent cards from remote A2A services.
5
+ *
6
+ * Python equivalent: `keycardai.a2a.ServiceDiscovery`
7
+ */
8
+ export declare class ServiceDiscovery {
9
+ #private;
10
+ constructor(options?: {
11
+ cacheTtlMs?: number;
12
+ });
13
+ /**
14
+ * Fetch the agent card from the given service URL.
15
+ * Validates that the card has a `name` field.
16
+ */
17
+ discoverService(serviceUrl: string): Promise<AgentCard>;
18
+ /**
19
+ * Return a cached agent card, fetching if absent or expired.
20
+ */
21
+ getServiceCard(serviceUrl: string, options?: {
22
+ forceRefresh?: boolean;
23
+ }): Promise<AgentCard>;
24
+ clearCache(): void;
25
+ getCacheStats(): {
26
+ size: number;
27
+ keys: string[];
28
+ };
29
+ }
30
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../src/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,YAAY,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAU7C;;;;GAIG;AACH,qBAAa,gBAAgB;;gBAIf,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;IAI7C;;;OAGG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC;IAqB7D;;OAEG;IACG,cAAc,CAClB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,OAAO,CAAA;KAAE,GACnC,OAAO,CAAC,SAAS,CAAC;IAWrB,UAAU,IAAI,IAAI;IAIlB,aAAa,IAAI;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE;CAGlD"}
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _ServiceDiscovery_cacheTtlMs, _ServiceDiscovery_cache;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ServiceDiscovery = void 0;
16
+ const DEFAULT_CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
17
+ const AGENT_CARD_PATH = "/.well-known/agent-card.json";
18
+ /**
19
+ * Fetches and caches agent cards from remote A2A services.
20
+ *
21
+ * Python equivalent: `keycardai.a2a.ServiceDiscovery`
22
+ */
23
+ class ServiceDiscovery {
24
+ constructor(options) {
25
+ _ServiceDiscovery_cacheTtlMs.set(this, void 0);
26
+ _ServiceDiscovery_cache.set(this, new Map());
27
+ __classPrivateFieldSet(this, _ServiceDiscovery_cacheTtlMs, options?.cacheTtlMs ?? DEFAULT_CACHE_TTL_MS, "f");
28
+ }
29
+ /**
30
+ * Fetch the agent card from the given service URL.
31
+ * Validates that the card has a `name` field.
32
+ */
33
+ async discoverService(serviceUrl) {
34
+ const cardUrl = buildAgentCardUrl(serviceUrl);
35
+ const response = await fetch(cardUrl, {
36
+ headers: { Accept: "application/json" },
37
+ signal: AbortSignal.timeout(10_000),
38
+ });
39
+ if (!response.ok) {
40
+ throw new Error(`ServiceDiscovery: failed to fetch agent card from "${cardUrl}" (HTTP ${response.status})`);
41
+ }
42
+ let card;
43
+ try {
44
+ card = await response.json();
45
+ }
46
+ catch {
47
+ throw new Error(`ServiceDiscovery: agent card at "${cardUrl}" is not valid JSON`);
48
+ }
49
+ validateAgentCard(card, cardUrl);
50
+ return card;
51
+ }
52
+ /**
53
+ * Return a cached agent card, fetching if absent or expired.
54
+ */
55
+ async getServiceCard(serviceUrl, options) {
56
+ const key = normalizeUrl(serviceUrl);
57
+ const cached = __classPrivateFieldGet(this, _ServiceDiscovery_cache, "f").get(key);
58
+ if (!options?.forceRefresh && cached && Date.now() < cached.expiresAt) {
59
+ return cached.card;
60
+ }
61
+ const card = await this.discoverService(serviceUrl);
62
+ __classPrivateFieldGet(this, _ServiceDiscovery_cache, "f").set(key, { card, expiresAt: Date.now() + __classPrivateFieldGet(this, _ServiceDiscovery_cacheTtlMs, "f") });
63
+ return card;
64
+ }
65
+ clearCache() {
66
+ __classPrivateFieldGet(this, _ServiceDiscovery_cache, "f").clear();
67
+ }
68
+ getCacheStats() {
69
+ return { size: __classPrivateFieldGet(this, _ServiceDiscovery_cache, "f").size, keys: Array.from(__classPrivateFieldGet(this, _ServiceDiscovery_cache, "f").keys()) };
70
+ }
71
+ }
72
+ exports.ServiceDiscovery = ServiceDiscovery;
73
+ _ServiceDiscovery_cacheTtlMs = new WeakMap(), _ServiceDiscovery_cache = new WeakMap();
74
+ function buildAgentCardUrl(serviceUrl) {
75
+ const base = serviceUrl.endsWith("/") ? serviceUrl.slice(0, -1) : serviceUrl;
76
+ return `${base}${AGENT_CARD_PATH}`;
77
+ }
78
+ function normalizeUrl(url) {
79
+ return url.endsWith("/") ? url.slice(0, -1) : url;
80
+ }
81
+ function validateAgentCard(card, url) {
82
+ if (!card || typeof card !== "object" || Array.isArray(card)) {
83
+ throw new Error(`ServiceDiscovery: agent card at "${url}" is not a JSON object`);
84
+ }
85
+ const obj = card;
86
+ if (typeof obj.name !== "string" || !obj.name) {
87
+ throw new Error(`ServiceDiscovery: agent card at "${url}" is missing required field "name"`);
88
+ }
89
+ }
90
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/discovery.ts"],"names":[],"mappings":"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"}