@dynamicagents/core 0.9.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 (168) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +522 -0
  3. package/dist/a2a/agent-stub.d.ts +60 -0
  4. package/dist/a2a/agent-stub.js +1 -0
  5. package/dist/a2a/caller-token.d.ts +44 -0
  6. package/dist/a2a/caller-token.js +61 -0
  7. package/dist/a2a/caller.d.ts +23 -0
  8. package/dist/a2a/caller.js +33 -0
  9. package/dist/a2a/card.d.ts +158 -0
  10. package/dist/a2a/card.js +163 -0
  11. package/dist/a2a/context.d.ts +19 -0
  12. package/dist/a2a/context.js +61 -0
  13. package/dist/a2a/deliver.d.ts +159 -0
  14. package/dist/a2a/deliver.js +185 -0
  15. package/dist/a2a/executor.d.ts +84 -0
  16. package/dist/a2a/executor.js +106 -0
  17. package/dist/a2a/index.d.ts +35 -0
  18. package/dist/a2a/index.js +34 -0
  19. package/dist/a2a/notify.d.ts +106 -0
  20. package/dist/a2a/notify.js +180 -0
  21. package/dist/a2a/parts.d.ts +45 -0
  22. package/dist/a2a/parts.js +94 -0
  23. package/dist/a2a/push.d.ts +70 -0
  24. package/dist/a2a/push.js +53 -0
  25. package/dist/a2a/self-origin.d.ts +91 -0
  26. package/dist/a2a/self-origin.js +114 -0
  27. package/dist/a2a/task-store.d.ts +44 -0
  28. package/dist/a2a/task-store.js +99 -0
  29. package/dist/a2a/task.d.ts +85 -0
  30. package/dist/a2a/task.js +15 -0
  31. package/dist/a2a/verify.d.ts +80 -0
  32. package/dist/a2a/verify.js +143 -0
  33. package/dist/agent/budget.d.ts +46 -0
  34. package/dist/agent/budget.js +42 -0
  35. package/dist/agent/control.d.ts +109 -0
  36. package/dist/agent/control.js +115 -0
  37. package/dist/agent/errors.d.ts +85 -0
  38. package/dist/agent/errors.js +64 -0
  39. package/dist/agent/final-reply.d.ts +49 -0
  40. package/dist/agent/final-reply.js +68 -0
  41. package/dist/agent/history.d.ts +97 -0
  42. package/dist/agent/history.js +133 -0
  43. package/dist/agent/index.d.ts +29 -0
  44. package/dist/agent/index.js +29 -0
  45. package/dist/agent/inference.d.ts +110 -0
  46. package/dist/agent/inference.js +120 -0
  47. package/dist/agent/model.d.ts +90 -0
  48. package/dist/agent/model.js +1 -0
  49. package/dist/agent/session.d.ts +100 -0
  50. package/dist/agent/session.js +82 -0
  51. package/dist/agent/workers-ai/index.d.ts +23 -0
  52. package/dist/agent/workers-ai/index.js +23 -0
  53. package/dist/agent/workers-ai/runtime.d.ts +42 -0
  54. package/dist/agent/workers-ai/runtime.js +63 -0
  55. package/dist/alarm/index.d.ts +77 -0
  56. package/dist/alarm/index.js +116 -0
  57. package/dist/config.d.ts +202 -0
  58. package/dist/config.js +135 -0
  59. package/dist/contract/index.d.ts +9 -0
  60. package/dist/contract/index.js +8 -0
  61. package/dist/contract/plugin.d.ts +324 -0
  62. package/dist/contract/plugin.js +114 -0
  63. package/dist/contract/recipe.d.ts +180 -0
  64. package/dist/contract/recipe.js +1 -0
  65. package/dist/contract/validation.d.ts +91 -0
  66. package/dist/contract/validation.js +84 -0
  67. package/dist/db/db.d.ts +147 -0
  68. package/dist/db/db.js +90 -0
  69. package/dist/db/index.d.ts +8 -0
  70. package/dist/db/index.js +8 -0
  71. package/dist/db/migrations/index.d.ts +20 -0
  72. package/dist/db/migrations/index.js +60 -0
  73. package/dist/db/models/subtasks.d.ts +100 -0
  74. package/dist/db/models/subtasks.js +241 -0
  75. package/dist/db/models/tasks.d.ts +118 -0
  76. package/dist/db/models/tasks.js +274 -0
  77. package/dist/db/schema.d.ts +468 -0
  78. package/dist/db/schema.js +88 -0
  79. package/dist/env.d.ts +53 -0
  80. package/dist/env.js +47 -0
  81. package/dist/host/agent.d.ts +305 -0
  82. package/dist/host/agent.js +400 -0
  83. package/dist/host/index.d.ts +20 -0
  84. package/dist/host/index.js +19 -0
  85. package/dist/host/plugin-host.d.ts +42 -0
  86. package/dist/host/plugin-host.js +1 -0
  87. package/dist/index.d.ts +19 -0
  88. package/dist/index.js +17 -0
  89. package/dist/job/index.d.ts +21 -0
  90. package/dist/job/index.js +21 -0
  91. package/dist/job/lifecycle.d.ts +176 -0
  92. package/dist/job/lifecycle.js +230 -0
  93. package/dist/job/state.d.ts +92 -0
  94. package/dist/job/state.js +40 -0
  95. package/dist/platform.d.ts +138 -0
  96. package/dist/platform.js +140 -0
  97. package/dist/round/agent.d.ts +271 -0
  98. package/dist/round/agent.js +678 -0
  99. package/dist/round/index.d.ts +25 -0
  100. package/dist/round/index.js +23 -0
  101. package/dist/round/policy.d.ts +98 -0
  102. package/dist/round/policy.js +1 -0
  103. package/dist/round/subagent.d.ts +87 -0
  104. package/dist/round/subagent.js +108 -0
  105. package/dist/round/turn.d.ts +249 -0
  106. package/dist/round/turn.js +564 -0
  107. package/dist/round/workflow.d.ts +147 -0
  108. package/dist/round/workflow.js +387 -0
  109. package/dist/runtime/index.d.ts +127 -0
  110. package/dist/runtime/index.js +186 -0
  111. package/dist/runtime/tool-families.d.ts +32 -0
  112. package/dist/runtime/tool-families.js +61 -0
  113. package/dist/subagent/fingerprint.d.ts +37 -0
  114. package/dist/subagent/fingerprint.js +92 -0
  115. package/dist/subagent/index.d.ts +169 -0
  116. package/dist/subagent/index.js +330 -0
  117. package/dist/subagent/prompt.d.ts +32 -0
  118. package/dist/subagent/prompt.js +37 -0
  119. package/dist/subagent/run.d.ts +157 -0
  120. package/dist/subagent/run.js +540 -0
  121. package/dist/subagent/workspace.d.ts +85 -0
  122. package/dist/subagent/workspace.js +127 -0
  123. package/dist/subtasks/catalog.d.ts +40 -0
  124. package/dist/subtasks/catalog.js +36 -0
  125. package/dist/subtasks/decomposition.d.ts +85 -0
  126. package/dist/subtasks/decomposition.js +156 -0
  127. package/dist/subtasks/delegate.d.ts +120 -0
  128. package/dist/subtasks/delegate.js +131 -0
  129. package/dist/subtasks/index.d.ts +9 -0
  130. package/dist/subtasks/index.js +9 -0
  131. package/dist/subtasks/subtask-types.d.ts +91 -0
  132. package/dist/subtasks/subtask-types.js +103 -0
  133. package/dist/subtasks/types.d.ts +295 -0
  134. package/dist/subtasks/types.js +15 -0
  135. package/dist/testing/auth.d.ts +34 -0
  136. package/dist/testing/auth.js +35 -0
  137. package/dist/testing/do.d.ts +29 -0
  138. package/dist/testing/do.js +25 -0
  139. package/dist/testing/fake-session.d.ts +26 -0
  140. package/dist/testing/fake-session.js +37 -0
  141. package/dist/testing/fixtures.d.ts +64 -0
  142. package/dist/testing/fixtures.js +104 -0
  143. package/dist/testing/harness.d.ts +97 -0
  144. package/dist/testing/harness.js +138 -0
  145. package/dist/testing/index.d.ts +31 -0
  146. package/dist/testing/index.js +35 -0
  147. package/dist/testing/mock-model.d.ts +77 -0
  148. package/dist/testing/mock-model.js +136 -0
  149. package/dist/testing/node.d.ts +56 -0
  150. package/dist/testing/node.js +56 -0
  151. package/dist/testing/vcr-global-setup.d.ts +12 -0
  152. package/dist/testing/vcr-global-setup.js +15 -0
  153. package/dist/testing/vcr-shared.d.ts +38 -0
  154. package/dist/testing/vcr-shared.js +33 -0
  155. package/dist/testing/vcr-spec.d.ts +25 -0
  156. package/dist/testing/vcr-spec.js +124 -0
  157. package/dist/testing/vcr-store.d.ts +86 -0
  158. package/dist/testing/vcr-store.js +191 -0
  159. package/dist/testing/vcr.d.ts +117 -0
  160. package/dist/testing/vcr.js +275 -0
  161. package/dist/worker/define-agent.d.ts +123 -0
  162. package/dist/worker/define-agent.js +20 -0
  163. package/dist/worker/index.d.ts +218 -0
  164. package/dist/worker/index.js +369 -0
  165. package/eslint-rules/index.js +31 -0
  166. package/eslint-rules/no-deprecated-object-properties.js +81 -0
  167. package/package.json +178 -0
  168. package/scripts/generate-keys.mjs +48 -0
@@ -0,0 +1,61 @@
1
+ import { importJWK, SignJWT } from "jose";
2
+ import { A2A_JWS_ALG, IDENTITY_CLAIM, TENANT_CLAIM, jwksUrl } from "@dynamicagents/g2a-protocol";
3
+ import { parsePrivateJwk } from "./card.js";
4
+ /** Default lifetime: long enough for clock skew, short enough to be worthless from a log. */
5
+ const DEFAULT_TTL_SECONDS = 120;
6
+ /**
7
+ * `importJWK` does real work and this is on the per-request path.
8
+ *
9
+ * Keyed by the raw secret so a rotated key invalidates the entry rather than
10
+ * being ignored for the life of the isolate.
11
+ */
12
+ let cached;
13
+ async function signingKeyFor(raw) {
14
+ if (cached?.raw === raw)
15
+ return cached;
16
+ const jwk = parsePrivateJwk(raw);
17
+ // Not cast to `CryptoKey`: `importJWK` returns a union, and asserting the
18
+ // branch would be a lie the day this key is anything but Ed25519.
19
+ const key = await importJWK(jwk, A2A_JWS_ALG);
20
+ cached = { raw, key, kid: jwk.kid };
21
+ return cached;
22
+ }
23
+ /**
24
+ * Sign a short-lived token identifying **this agent as a caller** to another
25
+ * service that trusts its card key.
26
+ *
27
+ * The production sibling of `makeGatekeeperToken`, which core previously shipped
28
+ * only from `/testing` — so an agent that had to call out mint-signed had to
29
+ * hand-write this shape, and every deployment that did so wrote its own subtly
30
+ * different version of the `iss`/`jku` agreement above.
31
+ *
32
+ * Distinct from {@link signCallbackJwt}, which carries **no** claims: that one
33
+ * proves "the agent you called is calling you back about this task", where this
34
+ * proves "this is who I am and which tenant I speak for".
35
+ */
36
+ export async function signCallerToken(options) {
37
+ const { key, kid } = await signingKeyFor(options.signingKey);
38
+ // Normalized for the same reason `audience` is, and it has to happen before
39
+ // both uses: `jku` names only the origin, so an `issuer` carrying a trailing
40
+ // slash or a path would sign an `iss` that disagrees with it — and a verifier
41
+ // comparing `iss` byte-for-byte against a normalized origin allowlist rejects
42
+ // the token even though the two URLs share an origin.
43
+ const issuer = new URL(options.issuer).origin;
44
+ return new SignJWT({
45
+ [IDENTITY_CLAIM]: options.identity,
46
+ [TENANT_CLAIM]: options.tenant
47
+ })
48
+ .setProtectedHeader({
49
+ alg: A2A_JWS_ALG,
50
+ kid,
51
+ // Where the far side fetches the public half. A verifier must check this
52
+ // origin against its own allowlist *before* fetching, which is what stops
53
+ // a forged token nominating an attacker-controlled JWKS.
54
+ jku: jwksUrl(issuer)
55
+ })
56
+ .setIssuer(issuer)
57
+ .setAudience(new URL(options.audience).origin)
58
+ .setIssuedAt()
59
+ .setExpirationTime(`${options.ttlSeconds ?? DEFAULT_TTL_SECONDS}s`)
60
+ .sign(key);
61
+ }
@@ -0,0 +1,23 @@
1
+ import type { GatekeeperIdentity } from "./verify.js";
2
+ /**
3
+ * Per-request system-prompt suffix describing the verified calling gatekeeper-agent
4
+ * instance, from the gatekeeper identity JWT.
5
+ *
6
+ * Core ships this and not a soul, and the line between them is the point: a soul
7
+ * is prompt copy expressing who an agent *is*, which nobody else can write for
8
+ * you. This is a **rendering of a protocol fact** — who the gatekeeper proved it was
9
+ * — and that fact has one correct rendering. Two agents disagreeing about how to
10
+ * describe their caller would be a bug, not a personality. It is the same
11
+ * category as `renderSubagentPrompt`'s budget section, which core also owns.
12
+ *
13
+ * **Advisory context only.** This is the calling *agent instance*, not the human
14
+ * on the other end of it, so an agent's soul must never let the model read it as
15
+ * "who you're talking to" — point the model at whatever speaker wrapper the
16
+ * gatekeeper applies instead.
17
+ *
18
+ * Deliberately neutral about what a workspace is: `GatekeeperIdentity.workspaceId`
19
+ * is whatever the calling gatekeeper partitions by, and core does not know that it
20
+ * is a Slack team. Override `DynamicAgent.callerContext` for a deployment that
21
+ * wants to name it.
22
+ */
23
+ export declare function callerContext(identity: GatekeeperIdentity): string;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Per-request system-prompt suffix describing the verified calling gatekeeper-agent
3
+ * instance, from the gatekeeper identity JWT.
4
+ *
5
+ * Core ships this and not a soul, and the line between them is the point: a soul
6
+ * is prompt copy expressing who an agent *is*, which nobody else can write for
7
+ * you. This is a **rendering of a protocol fact** — who the gatekeeper proved it was
8
+ * — and that fact has one correct rendering. Two agents disagreeing about how to
9
+ * describe their caller would be a bug, not a personality. It is the same
10
+ * category as `renderSubagentPrompt`'s budget section, which core also owns.
11
+ *
12
+ * **Advisory context only.** This is the calling *agent instance*, not the human
13
+ * on the other end of it, so an agent's soul must never let the model read it as
14
+ * "who you're talking to" — point the model at whatever speaker wrapper the
15
+ * gatekeeper applies instead.
16
+ *
17
+ * Deliberately neutral about what a workspace is: `GatekeeperIdentity.workspaceId`
18
+ * is whatever the calling gatekeeper partitions by, and core does not know that it
19
+ * is a Slack team. Override `DynamicAgent.callerContext` for a deployment that
20
+ * wants to name it.
21
+ */
22
+ export function callerContext(identity) {
23
+ const label = identity.name ?? identity.key;
24
+ if (!label) {
25
+ return "\n\nCalling agent instance: unknown (the gatekeeper did not include an agent identity).";
26
+ }
27
+ const withKind = identity.kind ? `${label} (${identity.kind})` : label;
28
+ const lines = ["", "", `Calling agent instance: ${withKind}.`];
29
+ if (identity.workspaceId != null) {
30
+ lines.push(`Calling workspace: ${identity.workspaceId}.`);
31
+ }
32
+ return lines.join("\n");
33
+ }
@@ -0,0 +1,158 @@
1
+ import type { JWK } from "jose";
2
+ import { AgentCard } from "@a2a-js/sdk";
3
+ /**
4
+ * The card signature uses the one algorithm every Dynamic Agents A2A signature uses,
5
+ * and the default RPC path is the one the gatekeeper also knows. Both come from
6
+ * `@dynamicagents/g2a-protocol` — see {@link file://./verify.ts} for why that is a
7
+ * separate package rather than a constant declared on each side.
8
+ */
9
+ export { A2A_RPC_PATH } from "@dynamicagents/g2a-protocol";
10
+ /**
11
+ * The transport-independent half of an {@link AgentCard} — everything that does
12
+ * not depend on the request origin. {@link buildBaseCard} adds
13
+ * `supportedInterfaces` and the security fields.
14
+ *
15
+ * Derived from the SDK `AgentCard` rather than hand-declared so a protocol field
16
+ * that gains a requirement fails the build here instead of silently going
17
+ * unadvertised.
18
+ */
19
+ export type AgentManifest = Pick<AgentCard, "name" | "description" | "version" | "capabilities" | "defaultInputModes" | "defaultOutputModes" | "skills">;
20
+ /**
21
+ * An AgentCard in its **protobuf-JSON** encoding — the form served at the
22
+ * well-known path and the exact document a signature is computed over. Distinct
23
+ * from the in-memory `AgentCard`: the wire form omits proto defaults and renders
24
+ * oneofs (`securitySchemes`, `Part.content`) as a single key rather than a
25
+ * `$case` tag.
26
+ */
27
+ export type WireAgentCard = Record<string, unknown>;
28
+ export interface CardSigningConfig {
29
+ /** Ed25519 private JWK (with `kid`) that signs the card. */
30
+ privateJwk: JWK & {
31
+ kid: string;
32
+ };
33
+ /** Public URL serving this agent's JWKS — embedded as the JWS `jku`. */
34
+ jku: string;
35
+ }
36
+ export interface BuildCardOptions {
37
+ /** Origin the card is served from; the interface `url` is built under it. */
38
+ origin: string;
39
+ /** Path this agent answers JSON-RPC on. Defaults to {@link A2A_RPC_PATH}. */
40
+ rpcPath?: string;
41
+ /**
42
+ * The tenant this card describes, advertised on its interface entry.
43
+ *
44
+ * Several agents share one endpoint here, so `tenant` is what says *which*
45
+ * one — A2A spec §8.3.2 requires a client to echo the declared value on every
46
+ * request, and this Worker refuses a request that does not.
47
+ *
48
+ * Omitted for the **root stub card**, which describes the origin rather than
49
+ * any agent: it advertises the endpoint and `extendedAgentCard`, and a caller
50
+ * reaches a real agent by asking for its tenant.
51
+ */
52
+ tenant?: string;
53
+ /**
54
+ * Advertise the gatekeeper's Bearer-JWT scheme in `securitySchemes`.
55
+ *
56
+ * **Defaults to `false` for historical reasons that no longer hold — see
57
+ * below before relying on the default.**
58
+ *
59
+ * `SecurityScheme` is the card's only protobuf *oneof*, and in earlier SDK
60
+ * releases `SecurityScheme.fromJSON` read only the wire spelling
61
+ * (`{ httpAuthSecurityScheme: … }`) and not the decoded `$case` form. A
62
+ * verifier that decodes the fetched card and then canonicalizes it through
63
+ * `toJSON(fromJSON(card))` decodes twice, and the second pass collapsed
64
+ * `{ gatekeeperJwt: { httpAuthSecurityScheme: … } }` to `{ gatekeeperJwt: {} }` — a
65
+ * different document from the one that was signed, so the signature failed.
66
+ * slack-gatekeeper's `canonicalCardPayload` double-decodes exactly this way.
67
+ *
68
+ * **As of the pinned `@a2a-js/sdk` (1.0.1) that is fixed.** `card.spec.ts`
69
+ * asserts it directly: an advertised-schemes card is a fixed point under
70
+ * repeated decoding, and its signature verifies both as served and after a
71
+ * double decode. So the safety argument for the `false` default is gone, and
72
+ * the remaining reason to keep it is deployment ordering — a gatekeeper pinned to
73
+ * an older SDK copy would still collapse the oneof. Flip the default once the
74
+ * gatekeepers in play are known to be on ≥1.0.1; the specs will hold the line if
75
+ * a later SDK regresses.
76
+ *
77
+ * `securityRequirements` is unaffected either way — it is a plain map, not a
78
+ * oneof — so the card always declares that auth is *required*, it just may not
79
+ * describe the scheme.
80
+ */
81
+ advertiseSecuritySchemes?: boolean;
82
+ }
83
+ /**
84
+ * Build the (unsigned) AgentCard.
85
+ *
86
+ * v1.0 replaced the card's flat `url` / `preferredTransport` / `protocolVersion`
87
+ * fields with an ordered `supportedInterfaces` list, where each entry pins its
88
+ * own protocol binding *and* protocol version. That per-interface
89
+ * `protocolVersion` is what a client's transport factory matches on and what the
90
+ * Worker validates the `A2A-Version` request header against, so it must be the
91
+ * real protocol version rather than the agent's own version string.
92
+ */
93
+ export declare function buildBaseCard(manifest: AgentManifest, opts: BuildCardOptions): AgentCard;
94
+ /**
95
+ * Sign the card with a detached-payload EdDSA flattened JWS, returning the
96
+ * signed **wire** card to serve. v1.0 standardized this (A2A spec §8.4), so the
97
+ * canonicalization and JWS construction are the SDK's rather than a local
98
+ * scheme: a JCS (RFC 8785) canonicalization of the card with `signatures`
99
+ * removed, under a protected header carrying `alg`, `kid` and `typ` (all three
100
+ * are required — the SDK verifier rejects a signature missing any of them).
101
+ *
102
+ * The card is normalized to its wire form *before* signing because that is what
103
+ * the verifier canonicalizes: `verifyAgentCardSignature` re-encodes whatever
104
+ * document it fetched through `AgentCard.toJSON(AgentCard.fromJSON(card))`.
105
+ * Signing the in-memory proto object instead would canonicalize `$case` tags and
106
+ * proto defaults that never appear on the wire, and every signature would fail.
107
+ *
108
+ * A verifying gatekeeper strips the `signatures` array, recomputes the canonical
109
+ * payload, and verifies — pinning this key's `kid`+`jku` on first registration
110
+ * (Trust-On-First-Use).
111
+ */
112
+ export declare function signCard(card: AgentCard, cfg: CardSigningConfig): Promise<WireAgentCard>;
113
+ /**
114
+ * The same signature, attached to the **in-memory** card instead of the wire
115
+ * one — for a caller that hands the card to something which will encode it
116
+ * itself.
117
+ *
118
+ * That caller is `GetExtendedAgentCard`. The SDK's JSON-RPC transport runs
119
+ * `AgentCard.toJSON()` over whatever the extended-card provider returns, so
120
+ * returning {@link signCard}'s wire card means `toJSON` runs over a document
121
+ * that has already been encoded. For most cards that is a no-op and looks fine
122
+ * — which is the trap. `securitySchemes` is a protobuf *oneof*: encoding it
123
+ * twice collapses `{ gatekeeperJwt: { httpAuthSecurityScheme: … } }` to
124
+ * `{ gatekeeperJwt: {} }`, the served document stops matching the one that was
125
+ * signed, and every signature verification fails.
126
+ *
127
+ * So the double encode is latent today only because
128
+ * {@link BuildCardOptions.advertiseSecuritySchemes} defaults to `false`; it
129
+ * would surface the day that flag is flipped, as a signature failure with
130
+ * nothing pointing back here. Returning the in-memory card lets the SDK do the
131
+ * single encode the signature was computed over, and is correct either way.
132
+ *
133
+ * `card.spec.ts` pins both halves.
134
+ */
135
+ export declare function signCardInPlace(card: AgentCard, cfg: CardSigningConfig): Promise<AgentCard>;
136
+ /**
137
+ * The card's protobuf-JSON encoding. Typed back as `AgentCard` for the SDK
138
+ * signer, which is generic over "the document to canonicalize" rather than over
139
+ * the in-memory shape specifically.
140
+ *
141
+ * Encode-only — never `toJSON(fromJSON(card))`. `fromJSON` reads the *wire*
142
+ * spelling of a oneof, so running it over an in-memory card silently drops every
143
+ * `$case`-tagged field. The verifier's round trip is the mirror image and stable:
144
+ * it decodes the document it fetched and re-encodes it to exactly this.
145
+ */
146
+ export declare function wireCard(card: AgentCard): AgentCard;
147
+ /**
148
+ * Parse and validate the `A2A_SIGNING_KEY` secret into the private JWK used to
149
+ * sign the card. Throws if the JWK is missing its `kid` (required for the JWS
150
+ * protected header and gatekeeper key-pinning).
151
+ */
152
+ export declare function parsePrivateJwk(raw: string): CardSigningConfig["privateJwk"];
153
+ /** Public card-signing JWKS (served at the `jku`): the private JWK minus `d`. */
154
+ export declare function publicCardJwks(privateJwk: JWK & {
155
+ kid: string;
156
+ }): {
157
+ keys: JWK[];
158
+ };
@@ -0,0 +1,163 @@
1
+ import { A2A_PROTOCOL_VERSION, AgentCard, generateAgentCardSignature } from "@a2a-js/sdk";
2
+ import { A2A_JWS_ALG, A2A_RPC_PATH, endpointUrl } from "@dynamicagents/g2a-protocol";
3
+ /**
4
+ * The card signature uses the one algorithm every Dynamic Agents A2A signature uses,
5
+ * and the default RPC path is the one the gatekeeper also knows. Both come from
6
+ * `@dynamicagents/g2a-protocol` — see {@link file://./verify.ts} for why that is a
7
+ * separate package rather than a constant declared on each side.
8
+ */
9
+ export { A2A_RPC_PATH } from "@dynamicagents/g2a-protocol";
10
+ /**
11
+ * The `httpAuthSecurityScheme` describing the gatekeeper's Bearer JWT, and the
12
+ * requirement that references it. Split out so the requirement can be advertised
13
+ * even when {@link BuildCardOptions.advertiseSecuritySchemes} keeps the scheme
14
+ * itself off the wire.
15
+ */
16
+ const GATEKEEPER_SCHEME_ID = "gatekeeperJwt";
17
+ /**
18
+ * Build the (unsigned) AgentCard.
19
+ *
20
+ * v1.0 replaced the card's flat `url` / `preferredTransport` / `protocolVersion`
21
+ * fields with an ordered `supportedInterfaces` list, where each entry pins its
22
+ * own protocol binding *and* protocol version. That per-interface
23
+ * `protocolVersion` is what a client's transport factory matches on and what the
24
+ * Worker validates the `A2A-Version` request header against, so it must be the
25
+ * real protocol version rather than the agent's own version string.
26
+ */
27
+ export function buildBaseCard(manifest, opts) {
28
+ const rpcPath = opts.rpcPath ?? A2A_RPC_PATH;
29
+ return {
30
+ ...manifest,
31
+ capabilities: {
32
+ // `extensions` is required on the proto type but absent from most
33
+ // hand-written manifests, so it is defaulted before the spread rather
34
+ // than after — a manifest that declares extensions keeps them.
35
+ extensions: [],
36
+ ...manifest.capabilities,
37
+ // Forced on, not inherited from the manifest: an agent here is *only*
38
+ // reachable as a tenant, and its card *only* through
39
+ // `GetExtendedAgentCard`. The SDK gates that method on this flag, reading
40
+ // it off whichever card the request handler was built with (spec §3.3.4 —
41
+ // it MUST return `UnsupportedOperationError` when unset), so every card
42
+ // this Worker serves has to carry it, the stub and the tenants alike.
43
+ extendedAgentCard: true
44
+ },
45
+ supportedInterfaces: [
46
+ {
47
+ // The gatekeeper reads this URL off the card and mints `aud` from it with
48
+ // `audienceFor`; this Worker derives its expected audience the same
49
+ // way. `endpointUrl` is what makes those provably equal — the protocol
50
+ // package pins `audienceFor(endpointUrl(o, p)) === endpointUrl(o, p)`
51
+ // for every path, rather than leaving both sides to concatenate.
52
+ url: endpointUrl(opts.origin, rpcPath),
53
+ protocolBinding: "JSONRPC",
54
+ protocolVersion: A2A_PROTOCOL_VERSION,
55
+ tenant: opts.tenant ?? ""
56
+ }
57
+ ],
58
+ provider: undefined,
59
+ documentationUrl: undefined,
60
+ iconUrl: undefined,
61
+ securitySchemes: opts.advertiseSecuritySchemes
62
+ ? {
63
+ [GATEKEEPER_SCHEME_ID]: {
64
+ scheme: {
65
+ $case: "httpAuthSecurityScheme",
66
+ value: { description: "", scheme: "bearer", bearerFormat: "JWT" }
67
+ }
68
+ }
69
+ }
70
+ : {},
71
+ // v0.3's `security: [{ gatekeeperJwt: [] }]`. The empty `list` means the scheme
72
+ // is required but carries no scopes. Safe to advertise unconditionally: this
73
+ // is a plain map, so it survives the round trip that eats `securitySchemes`.
74
+ securityRequirements: [
75
+ { schemes: { [GATEKEEPER_SCHEME_ID]: { list: [] } } }
76
+ ],
77
+ signatures: []
78
+ };
79
+ }
80
+ /**
81
+ * Sign the card with a detached-payload EdDSA flattened JWS, returning the
82
+ * signed **wire** card to serve. v1.0 standardized this (A2A spec §8.4), so the
83
+ * canonicalization and JWS construction are the SDK's rather than a local
84
+ * scheme: a JCS (RFC 8785) canonicalization of the card with `signatures`
85
+ * removed, under a protected header carrying `alg`, `kid` and `typ` (all three
86
+ * are required — the SDK verifier rejects a signature missing any of them).
87
+ *
88
+ * The card is normalized to its wire form *before* signing because that is what
89
+ * the verifier canonicalizes: `verifyAgentCardSignature` re-encodes whatever
90
+ * document it fetched through `AgentCard.toJSON(AgentCard.fromJSON(card))`.
91
+ * Signing the in-memory proto object instead would canonicalize `$case` tags and
92
+ * proto defaults that never appear on the wire, and every signature would fail.
93
+ *
94
+ * A verifying gatekeeper strips the `signatures` array, recomputes the canonical
95
+ * payload, and verifies — pinning this key's `kid`+`jku` on first registration
96
+ * (Trust-On-First-Use).
97
+ */
98
+ export async function signCard(card, cfg) {
99
+ const sign = generateAgentCardSignature(cfg.privateJwk, {
100
+ alg: A2A_JWS_ALG,
101
+ kid: cfg.privateJwk.kid,
102
+ typ: "JOSE",
103
+ jku: cfg.jku
104
+ });
105
+ const signed = await sign(wireCard(card));
106
+ return signed;
107
+ }
108
+ /**
109
+ * The same signature, attached to the **in-memory** card instead of the wire
110
+ * one — for a caller that hands the card to something which will encode it
111
+ * itself.
112
+ *
113
+ * That caller is `GetExtendedAgentCard`. The SDK's JSON-RPC transport runs
114
+ * `AgentCard.toJSON()` over whatever the extended-card provider returns, so
115
+ * returning {@link signCard}'s wire card means `toJSON` runs over a document
116
+ * that has already been encoded. For most cards that is a no-op and looks fine
117
+ * — which is the trap. `securitySchemes` is a protobuf *oneof*: encoding it
118
+ * twice collapses `{ gatekeeperJwt: { httpAuthSecurityScheme: … } }` to
119
+ * `{ gatekeeperJwt: {} }`, the served document stops matching the one that was
120
+ * signed, and every signature verification fails.
121
+ *
122
+ * So the double encode is latent today only because
123
+ * {@link BuildCardOptions.advertiseSecuritySchemes} defaults to `false`; it
124
+ * would surface the day that flag is flipped, as a signature failure with
125
+ * nothing pointing back here. Returning the in-memory card lets the SDK do the
126
+ * single encode the signature was computed over, and is correct either way.
127
+ *
128
+ * `card.spec.ts` pins both halves.
129
+ */
130
+ export async function signCardInPlace(card, cfg) {
131
+ const signed = await signCard(card, cfg);
132
+ return { ...card, signatures: signed.signatures };
133
+ }
134
+ /**
135
+ * The card's protobuf-JSON encoding. Typed back as `AgentCard` for the SDK
136
+ * signer, which is generic over "the document to canonicalize" rather than over
137
+ * the in-memory shape specifically.
138
+ *
139
+ * Encode-only — never `toJSON(fromJSON(card))`. `fromJSON` reads the *wire*
140
+ * spelling of a oneof, so running it over an in-memory card silently drops every
141
+ * `$case`-tagged field. The verifier's round trip is the mirror image and stable:
142
+ * it decodes the document it fetched and re-encodes it to exactly this.
143
+ */
144
+ export function wireCard(card) {
145
+ return AgentCard.toJSON(card);
146
+ }
147
+ /**
148
+ * Parse and validate the `A2A_SIGNING_KEY` secret into the private JWK used to
149
+ * sign the card. Throws if the JWK is missing its `kid` (required for the JWS
150
+ * protected header and gatekeeper key-pinning).
151
+ */
152
+ export function parsePrivateJwk(raw) {
153
+ const jwk = JSON.parse(raw);
154
+ if (!jwk.kid)
155
+ throw new Error("A2A_SIGNING_KEY must include a `kid`");
156
+ return jwk;
157
+ }
158
+ /** Public card-signing JWKS (served at the `jku`): the private JWK minus `d`. */
159
+ export function publicCardJwks(privateJwk) {
160
+ const { d: _d, ...pub } = privateJwk;
161
+ void _d;
162
+ return { keys: [{ ...pub, use: "sig", alg: A2A_JWS_ALG }] };
163
+ }
@@ -0,0 +1,19 @@
1
+ import { type ServerCallContext } from "@a2a-js/sdk/server";
2
+ import type { GatekeeperIdentity } from "./verify.js";
3
+ /**
4
+ * Build the call context for one verified JSON-RPC request. The default builder
5
+ * stashes the raw headers in the context's state bag, so an executor can reach
6
+ * them the same way it would under the Express binding.
7
+ *
8
+ * `tenant` names which agent on this origin the call is for. The SDK's
9
+ * `JsonRpcTransportHandler` would also lift it off `params.tenant` on its own,
10
+ * but only when the context arrives without one — so passing it here is not
11
+ * redundant: this Worker has already parsed, authorized and routed on the
12
+ * tenant before a handler exists to receive the context, and setting it keeps
13
+ * the value the executor and task store see identical to the one that chose
14
+ * them. It is also what reaches the extended-card provider, which the SDK calls
15
+ * with the context alone.
16
+ */
17
+ export declare function buildCallContext(request: Request, identity: GatekeeperIdentity, tenant: string): ServerCallContext;
18
+ /** Echo back the extensions the handler actually activated (spec §14.2.2). */
19
+ export declare function extensionHeaders(context: ServerCallContext): HeadersInit;
@@ -0,0 +1,61 @@
1
+ import { A2A_VERSION_HEADER, Extensions, HTTP_EXTENSION_HEADER } from "@a2a-js/sdk";
2
+ import { defaultServerCallContextBuilder } from "@a2a-js/sdk/server";
3
+ /**
4
+ * The per-call {@link ServerCallContext} bridge for a Workers `fetch` handler.
5
+ *
6
+ * v1.0 made the call context mandatory on every request-handler and task-store
7
+ * method, and the SDK only ships Express and gRPC bindings that build one; this
8
+ * is the equivalent seam for `fetch`.
9
+ */
10
+ /**
11
+ * The verified calling gatekeeper-agent, as the SDK's {@link User}. `userName` is
12
+ * the canonical instance key (e.g. `custom:7:analytics`) — the same value the
13
+ * agent Durable Object is keyed by, so the SDK's owner-scoped bookkeeping lines
14
+ * up with the isolation this Worker already enforces by routing.
15
+ */
16
+ class GatekeeperUser {
17
+ identity;
18
+ constructor(identity) {
19
+ this.identity = identity;
20
+ }
21
+ get isAuthenticated() {
22
+ return true;
23
+ }
24
+ get userName() {
25
+ // The Worker rejects a keyless identity (400) before a context is built.
26
+ return this.identity.key ?? "";
27
+ }
28
+ }
29
+ /**
30
+ * Build the call context for one verified JSON-RPC request. The default builder
31
+ * stashes the raw headers in the context's state bag, so an executor can reach
32
+ * them the same way it would under the Express binding.
33
+ *
34
+ * `tenant` names which agent on this origin the call is for. The SDK's
35
+ * `JsonRpcTransportHandler` would also lift it off `params.tenant` on its own,
36
+ * but only when the context arrives without one — so passing it here is not
37
+ * redundant: this Worker has already parsed, authorized and routed on the
38
+ * tenant before a handler exists to receive the context, and setting it keeps
39
+ * the value the executor and task store see identical to the one that chose
40
+ * them. It is also what reaches the extended-card provider, which the SDK calls
41
+ * with the context alone.
42
+ */
43
+ export function buildCallContext(request, identity, tenant) {
44
+ const headers = {};
45
+ for (const [name, value] of request.headers)
46
+ headers[name] = value;
47
+ return defaultServerCallContextBuilder({
48
+ extensions: Extensions.parseServiceParameter(request.headers.get(HTTP_EXTENSION_HEADER) ?? undefined),
49
+ user: new GatekeeperUser(identity),
50
+ headers,
51
+ requestedVersion: request.headers.get(A2A_VERSION_HEADER) ?? undefined,
52
+ tenant
53
+ });
54
+ }
55
+ /** Echo back the extensions the handler actually activated (spec §14.2.2). */
56
+ export function extensionHeaders(context) {
57
+ const activated = context.activatedExtensions;
58
+ if (!activated?.length)
59
+ return {};
60
+ return { [HTTP_EXTENSION_HEADER]: Extensions.toServiceParameter(activated) };
61
+ }