@loopingai/core 0.1.1 → 0.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 (75) hide show
  1. package/README.md +131 -15
  2. package/dist/a2a/card.d.ts +35 -0
  3. package/dist/a2a/card.d.ts.map +1 -1
  4. package/dist/a2a/card.js +41 -3
  5. package/dist/a2a/card.js.map +1 -1
  6. package/dist/a2a/context.d.ts +9 -3
  7. package/dist/a2a/context.d.ts.map +1 -1
  8. package/dist/a2a/context.js +11 -4
  9. package/dist/a2a/context.js.map +1 -1
  10. package/dist/a2a/index.d.ts +1 -1
  11. package/dist/a2a/index.d.ts.map +1 -1
  12. package/dist/a2a/index.js +1 -1
  13. package/dist/a2a/index.js.map +1 -1
  14. package/dist/a2a/notify.d.ts +11 -2
  15. package/dist/a2a/notify.d.ts.map +1 -1
  16. package/dist/a2a/notify.js +12 -3
  17. package/dist/a2a/notify.js.map +1 -1
  18. package/dist/a2a/verify.d.ts +23 -2
  19. package/dist/a2a/verify.d.ts.map +1 -1
  20. package/dist/a2a/verify.js +26 -3
  21. package/dist/a2a/verify.js.map +1 -1
  22. package/dist/agent/history.d.ts +15 -0
  23. package/dist/agent/history.d.ts.map +1 -1
  24. package/dist/agent/history.js +17 -0
  25. package/dist/agent/history.js.map +1 -1
  26. package/dist/agent/index.d.ts +1 -1
  27. package/dist/agent/index.d.ts.map +1 -1
  28. package/dist/agent/index.js +1 -1
  29. package/dist/agent/index.js.map +1 -1
  30. package/dist/contract/index.d.ts +1 -1
  31. package/dist/contract/index.d.ts.map +1 -1
  32. package/dist/contract/index.js.map +1 -1
  33. package/dist/contract/plugin.d.ts +86 -3
  34. package/dist/contract/plugin.d.ts.map +1 -1
  35. package/dist/contract/plugin.js.map +1 -1
  36. package/dist/db/db.d.ts +32 -14
  37. package/dist/db/db.d.ts.map +1 -1
  38. package/dist/db/db.js.map +1 -1
  39. package/dist/env.d.ts +10 -2
  40. package/dist/env.d.ts.map +1 -1
  41. package/dist/env.js +10 -2
  42. package/dist/env.js.map +1 -1
  43. package/dist/index.d.ts +2 -2
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/runtime/index.d.ts +28 -3
  48. package/dist/runtime/index.d.ts.map +1 -1
  49. package/dist/runtime/index.js +39 -3
  50. package/dist/runtime/index.js.map +1 -1
  51. package/dist/subagent/workspace.d.ts +21 -0
  52. package/dist/subagent/workspace.d.ts.map +1 -1
  53. package/dist/subagent/workspace.js +80 -5
  54. package/dist/subagent/workspace.js.map +1 -1
  55. package/dist/testing/auth.d.ts +23 -0
  56. package/dist/testing/auth.d.ts.map +1 -1
  57. package/dist/testing/auth.js +11 -3
  58. package/dist/testing/auth.js.map +1 -1
  59. package/dist/testing/fixtures.d.ts +21 -0
  60. package/dist/testing/fixtures.d.ts.map +1 -1
  61. package/dist/testing/fixtures.js +21 -0
  62. package/dist/testing/fixtures.js.map +1 -1
  63. package/dist/testing/node.d.ts +23 -0
  64. package/dist/testing/node.d.ts.map +1 -1
  65. package/dist/testing/node.js +23 -0
  66. package/dist/testing/node.js.map +1 -1
  67. package/dist/testing/vcr-spec.d.ts +4 -4
  68. package/dist/testing/vcr-spec.d.ts.map +1 -1
  69. package/dist/testing/vcr-spec.js +32 -11
  70. package/dist/testing/vcr-spec.js.map +1 -1
  71. package/dist/worker/index.d.ts +142 -3
  72. package/dist/worker/index.d.ts.map +1 -1
  73. package/dist/worker/index.js +152 -29
  74. package/dist/worker/index.js.map +1 -1
  75. package/package.json +12 -4
@@ -18,20 +18,134 @@ import { type A2ASecretsEnv } from "../env.js";
18
18
  *
19
19
  * No secret is ever shared in either direction: trust flows entirely on domains
20
20
  * and asymmetric (Ed25519) signatures over public JWKS.
21
+ *
22
+ * **Several agents share one Worker, addressed by `tenant`.** They share one
23
+ * origin, one endpoint, one signing key and one card at the well-known path;
24
+ * what differs is the tenant id on every request. That is the A2A mechanism for
25
+ * exactly this — `AgentInterface.tenant` is "an opaque string used for routing
26
+ * requests to a specific agent or tenant when multiple agents are served behind
27
+ * a single A2A endpoint", and §8.3.2 requires a client to send the value the
28
+ * selected interface declared.
29
+ *
30
+ * A tenant is **required on every request**. There is no default and no
31
+ * implicit routing: an absent tenant is an error, because guessing one would
32
+ * mean picking an agent the caller never named.
33
+ *
34
+ * This replaces mounting a handler per path prefix. That could not work: the
35
+ * card is a **well-known URI**, which RFC 8615 defines per-authority, so only
36
+ * one card per origin is discoverable at the registered path — a gateway
37
+ * resolving `…/.well-known/agent-card.json` against the origin found whichever
38
+ * agent owned the bare path and pinned *its* key for all of them.
39
+ *
40
+ * Three routes:
41
+ *
42
+ * 1. `GET jwksPath` — the card-signing public JWKS, resolving every card's
43
+ * `jku`.
44
+ * 2. `GET …/.well-known/agent-card.json` — the signed **stub** card for the
45
+ * origin. Matched by suffix, since that path is fixed by the spec.
46
+ * 3. `POST rpcPath` — gateway-authenticated JSON-RPC, routed to the named
47
+ * tenant. `GetExtendedAgentCard` returns that tenant's own signed card;
48
+ * everything else runs a turn against its Durable Object.
49
+ *
50
+ * Two independent checks keep one tenant's traffic out of another's:
51
+ * `audience` proves the token was minted for this deployment, and
52
+ * {@link file://../a2a/verify.ts TENANT_CLAIM} proves it was minted for *this
53
+ * agent on it*. The second is what makes `tenant` more than an unauthenticated
54
+ * routing hint in the request body.
21
55
  */
22
56
  /** Default path serving the card-signing public JWKS (the card's `jku`). */
23
57
  export declare const JWKS_PATH = "/.well-known/jwks.json";
24
- export interface A2AWorkerOptions {
58
+ /** The two secrets a mount signs and verifies with, already read off `env`. */
59
+ export interface A2ASecrets {
60
+ /** Ed25519 private JWK, as JSON. See {@link A2ASecretsEnv.A2A_SIGNING_KEY}. */
61
+ signingKey: string;
62
+ /** Origin allowlist. See {@link A2ASecretsEnv.GATEWAY_ORIGINS}. */
63
+ gatewayOrigins: string;
64
+ }
65
+ /** One agent served on this origin — everything that differs between tenants. */
66
+ export interface TenantAgent {
25
67
  /** The transport-independent half of this agent's card. */
26
68
  manifest: AgentManifest;
27
69
  /** Resolve the agent DO stub for a verified caller. */
28
70
  resolveAgent: AgentResolver;
29
71
  /** Start the durable turn. Must be idempotent — see {@link TurnStarter}. */
30
72
  startTurn: TurnStarter;
73
+ }
74
+ export interface A2AWorkerOptions<TEnv = A2ASecretsEnv> {
75
+ /**
76
+ * The **stub** card served at the well-known path.
77
+ *
78
+ * It describes the origin, not an agent. Every agent here is a tenant and its
79
+ * card is reached through `GetExtendedAgentCard`, so this card exists to be
80
+ * the one conformant, signed AgentCard at the URI RFC 8615 and the A2A IANA
81
+ * registration reserve — advertising the endpoint, the protocol binding and
82
+ * `extendedAgentCard`.
83
+ *
84
+ * It cannot enumerate the tenants: a card carries one interface entry and
85
+ * spec §8.3.2 has clients take the first, so listing siblings there would
86
+ * just make every client address the same one. Put their names in
87
+ * `description` for a human, and register them out of band.
88
+ */
89
+ manifest: AgentManifest;
90
+ /**
91
+ * The agents on this origin, keyed by tenant id. At least one.
92
+ *
93
+ * The id is opaque to the protocol — "an opaque string used for routing
94
+ * requests to a specific agent or tenant when multiple agents are served
95
+ * behind a single A2A endpoint". A caller names one on every request and this
96
+ * Worker refuses a request that does not.
97
+ *
98
+ * The tenant chooses *which agent*; the verified `identity.key` still chooses
99
+ * *which instance of it*, so two callers of one tenant stay in separate
100
+ * Durable Objects exactly as before.
101
+ */
102
+ tenants: Record<string, TenantAgent>;
31
103
  /** Path serving the public JWKS. Defaults to {@link JWKS_PATH}. */
32
104
  jwksPath?: string;
33
105
  /** Path this agent answers JSON-RPC on. Defaults to `/a2a`. */
34
106
  rpcPath?: string;
107
+ /**
108
+ * Where to read this deployment's two secrets. Defaults to the documented
109
+ * names, `env.A2A_SIGNING_KEY` and `env.GATEWAY_ORIGINS`.
110
+ *
111
+ * There is **one signing key per origin**, not one per tenant: the card is
112
+ * per-origin now, so the key the gateway pins is too. Nothing was lost — the
113
+ * tenants share a Worker and an `env`, so they could always read each other's
114
+ * secrets, and separate keys never expressed a boundary that existed.
115
+ *
116
+ * ```ts
117
+ * secrets: (env) => ({
118
+ * signingKey: env.AGENT_KEY,
119
+ * gatewayOrigins: env.ALLOWED_GATEWAYS
120
+ * })
121
+ * ```
122
+ *
123
+ * Renaming does not weaken anything: the same key is still Ed25519, still
124
+ * signs the cards and every callback JWT, and its public half is still what
125
+ * the gateway pins. Only where it is read from changes.
126
+ */
127
+ secrets?: (env: TEnv) => A2ASecrets;
128
+ /**
129
+ * The audience a gateway JWT must carry.
130
+ *
131
+ * Defaults to this deployment's **own endpoint** — `${origin}${rpcPath}`, the
132
+ * same URL its cards advertise as their interface — and that default is
133
+ * almost certainly what you want. Setting this is for a gateway that mints
134
+ * something else.
135
+ *
136
+ * The audience is one half of a two-sided contract: whatever is required here
137
+ * has to be exactly what the calling gateway *mints*, and a mismatch is a 401
138
+ * on every request. looping-gateway mints
139
+ * `new URL(agent.a2aEndpoint).origin + pathname`, which is what this default
140
+ * matches.
141
+ *
142
+ * It proves the token was minted for **this deployment**, and deliberately
143
+ * says nothing about which agent on it — every tenant shares one endpoint and
144
+ * therefore one audience. {@link file://../a2a/verify.ts TENANT_CLAIM} is what
145
+ * separates them, and is the check to look at when reasoning about one agent
146
+ * spending another's token.
147
+ */
148
+ audience?: string | ((url: URL) => string);
35
149
  /**
36
150
  * Advertise `securitySchemes` on the card. **Defaults to `false`** — see
37
151
  * `BuildCardOptions.advertiseSecuritySchemes` for why, and read that note
@@ -40,6 +154,17 @@ export interface A2AWorkerOptions {
40
154
  advertiseSecuritySchemes?: boolean;
41
155
  /** Claim carrying the caller identity. Defaults to the Looping namespace. */
42
156
  identityClaim?: string;
157
+ /**
158
+ * Claim carrying the authorized tenant. Defaults to the Looping namespace.
159
+ *
160
+ * Both claim names are one side of the same contract as `audience`: whatever
161
+ * is named here has to be exactly what the calling gateway *mints*. Override
162
+ * them together, or not at all — a deployment fronted by something other than
163
+ * looping-gateway that renames only the identity claim keeps reading the
164
+ * tenant from a key its gateway never sets, and every request 401s on the
165
+ * empty-tenant comparison below.
166
+ */
167
+ tenantClaim?: string;
43
168
  /**
44
169
  * Require a `taskPushNotificationConfig` on every `SendMessage`.
45
170
  *
@@ -54,9 +179,23 @@ export interface A2AWorkerOptions {
54
179
  * Build the Worker `fetch` handler.
55
180
  *
56
181
  * ```ts
57
- * const handler = createA2AWorker({ manifest, resolveAgent: getAgent, startTurn });
182
+ * const handler = createA2AWorker({
183
+ * manifest: hostManifest,
184
+ * tenants: {
185
+ * reactive: { manifest, resolveAgent: getAgent, startTurn }
186
+ * }
187
+ * });
58
188
  * export default { fetch: handler } satisfies ExportedHandler<Env>;
59
189
  * ```
190
+ *
191
+ * Two overloads, because `secrets` is optional for exactly one shape of `env`.
192
+ * An `env` carrying the two documented names needs no reader; anything else has
193
+ * to say where its keys live, and the type system is where that gets enforced —
194
+ * the alternative is a `parsePrivateJwk` failure on the first request, which is
195
+ * both later and much harder to read.
60
196
  */
61
- export declare function createA2AWorker<TEnv extends A2ASecretsEnv>(options: A2AWorkerOptions): (request: Request, env: TEnv) => Promise<Response>;
197
+ export declare function createA2AWorker<TEnv extends A2ASecretsEnv>(options: A2AWorkerOptions<TEnv>): (request: Request, env: TEnv) => Promise<Response>;
198
+ export declare function createA2AWorker<TEnv extends object>(options: A2AWorkerOptions<TEnv> & {
199
+ secrets: (env: TEnv) => A2ASecrets;
200
+ }): (request: Request, env: TEnv) => Promise<Response>;
62
201
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAQxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpE;;;;;;;;;;;;;;;;GAgBG;AAEH,4EAA4E;AAC5E,eAAO,MAAM,SAAS,2BAA2B,CAAC;AAKlD,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,QAAQ,EAAE,aAAa,CAAC;IACxB,uDAAuD;IACvD,YAAY,EAAE,aAAa,CAAC;IAC5B,4EAA4E;IAC5E,SAAS,EAAE,WAAW,CAAC;IACvB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AA2ED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,SAAS,aAAa,EACxD,OAAO,EAAE,gBAAgB,GACxB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAsHpD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAOL,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AAQxB,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAEpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,4EAA4E;AAC5E,eAAO,MAAM,SAAS,2BAA2B,CAAC;AAKlD,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,QAAQ,EAAE,aAAa,CAAC;IACxB,uDAAuD;IACvD,YAAY,EAAE,aAAa,CAAC;IAC5B,4EAA4E;IAC5E,SAAS,EAAE,WAAW,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB,CAAC,IAAI,GAAG,aAAa;IACpD;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,aAAa,CAAC;IACxB;;;;;;;;;;;OAWG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC;IACpC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;IAC3C;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AA2ED;;;;;;;;;;;;;;;;;;GAkBG;AAEH,wBAAgB,eAAe,CAAC,IAAI,SAAS,aAAa,EACxD,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAC9B,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEtD,wBAAgB,eAAe,CAAC,IAAI,SAAS,MAAM,EACjD,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,GAAG;IAChC,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,UAAU,CAAC;CACpC,GACA,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { AGENT_CARD_PATH, SendMessageRequest } from "@a2a-js/sdk";
2
2
  import { DefaultRequestHandler, JsonRpcTransportHandler, validateVersion } from "@a2a-js/sdk/server";
3
3
  import { RequestMalformedError, toJsonRpcError } from "@a2a-js/sdk/errors";
4
- import { buildBaseCard, parsePrivateJwk, publicCardJwks, signCard } from "../a2a/card.js";
4
+ import { A2A_RPC_PATH, buildBaseCard, parsePrivateJwk, publicCardJwks, signCard, signCardInPlace } from "../a2a/card.js";
5
5
  import { buildCallContext, extensionHeaders } from "../a2a/context.js";
6
6
  import { GatewayAuthError, bearerToken, verifyGatewayToken } from "../a2a/verify.js";
7
7
  import { A2AExecutor } from "../a2a/executor.js";
@@ -23,6 +23,40 @@ import { parseGatewayOrigins } from "../env.js";
23
23
  *
24
24
  * No secret is ever shared in either direction: trust flows entirely on domains
25
25
  * and asymmetric (Ed25519) signatures over public JWKS.
26
+ *
27
+ * **Several agents share one Worker, addressed by `tenant`.** They share one
28
+ * origin, one endpoint, one signing key and one card at the well-known path;
29
+ * what differs is the tenant id on every request. That is the A2A mechanism for
30
+ * exactly this — `AgentInterface.tenant` is "an opaque string used for routing
31
+ * requests to a specific agent or tenant when multiple agents are served behind
32
+ * a single A2A endpoint", and §8.3.2 requires a client to send the value the
33
+ * selected interface declared.
34
+ *
35
+ * A tenant is **required on every request**. There is no default and no
36
+ * implicit routing: an absent tenant is an error, because guessing one would
37
+ * mean picking an agent the caller never named.
38
+ *
39
+ * This replaces mounting a handler per path prefix. That could not work: the
40
+ * card is a **well-known URI**, which RFC 8615 defines per-authority, so only
41
+ * one card per origin is discoverable at the registered path — a gateway
42
+ * resolving `…/.well-known/agent-card.json` against the origin found whichever
43
+ * agent owned the bare path and pinned *its* key for all of them.
44
+ *
45
+ * Three routes:
46
+ *
47
+ * 1. `GET jwksPath` — the card-signing public JWKS, resolving every card's
48
+ * `jku`.
49
+ * 2. `GET …/.well-known/agent-card.json` — the signed **stub** card for the
50
+ * origin. Matched by suffix, since that path is fixed by the spec.
51
+ * 3. `POST rpcPath` — gateway-authenticated JSON-RPC, routed to the named
52
+ * tenant. `GetExtendedAgentCard` returns that tenant's own signed card;
53
+ * everything else runs a turn against its Durable Object.
54
+ *
55
+ * Two independent checks keep one tenant's traffic out of another's:
56
+ * `audience` proves the token was minted for this deployment, and
57
+ * {@link file://../a2a/verify.ts TENANT_CLAIM} proves it was minted for *this
58
+ * agent on it*. The second is what makes `tenant` more than an unauthenticated
59
+ * routing hint in the request body.
26
60
  */
27
61
  /** Default path serving the card-signing public JWKS (the card's `jku`). */
28
62
  export const JWKS_PATH = "/.well-known/jwks.json";
@@ -89,49 +123,90 @@ function pushConfigError(rpcBody) {
89
123
  }
90
124
  return undefined;
91
125
  }
92
- /**
93
- * Build the Worker `fetch` handler.
94
- *
95
- * ```ts
96
- * const handler = createA2AWorker({ manifest, resolveAgent: getAgent, startTurn });
97
- * export default { fetch: handler } satisfies ExportedHandler<Env>;
98
- * ```
99
- */
100
126
  export function createA2AWorker(options) {
101
127
  const jwksPath = options.jwksPath ?? JWKS_PATH;
128
+ const rpcPath = options.rpcPath ?? A2A_RPC_PATH;
102
129
  const requirePushConfig = options.requirePushConfig ?? true;
130
+ // Nothing can be served without at least one agent, and a deployment that
131
+ // configured none is a startup mistake rather than a per-request one.
132
+ const tenantIds = Object.keys(options.tenants);
133
+ if (tenantIds.length === 0) {
134
+ throw new Error("createA2AWorker requires at least one tenant");
135
+ }
136
+ // …and one registered under `""` is the same mistake wearing a disguise. A
137
+ // request must name a tenant, and the empty string is how "named none" is
138
+ // spelled, so that agent is registered and permanently unreachable — every
139
+ // call to it is refused as a missing tenant before the lookup ever runs. The
140
+ // check above would otherwise report a deployment with no routable agent as
141
+ // satisfying "at least one tenant".
142
+ if (tenantIds.some((id) => !id)) {
143
+ throw new Error("createA2AWorker requires every tenant id to be non-empty: a request " +
144
+ "names its tenant, so an agent registered under '' can never be reached");
145
+ }
146
+ // The tenant id is caller-controlled, so the lookup is by *own* property.
147
+ // Plain indexing reaches `Object.prototype`, and every inherited name —
148
+ // `toString`, `constructor`, `__proto__` — comes back truthy, so a tenant
149
+ // called one of those slips past an `if (!agent)` guard and only fails later
150
+ // when a function is read off it. That is a 500 where the whole point of the
151
+ // guard is a 400 naming the unknown tenant.
152
+ const tenantAgent = (id) => Object.hasOwn(options.tenants, id) ? options.tenants[id] : undefined;
153
+ // Only reachable through the first overload, which has already established
154
+ // that `TEnv` carries the two documented names.
155
+ const readSecrets = options.secrets ??
156
+ ((env) => ({
157
+ signingKey: env.A2A_SIGNING_KEY,
158
+ gatewayOrigins: env.GATEWAY_ORIGINS
159
+ }));
103
160
  return async function fetch(request, env) {
104
161
  const url = new URL(request.url);
105
162
  const origin = url.origin;
106
- const privateJwk = parsePrivateJwk(env.A2A_SIGNING_KEY);
107
- const cardOptions = {
163
+ const secrets = readSecrets(env);
164
+ // This agent's own endpoint, which is also exactly what its card advertises
165
+ // as its interface — so the value the gateway was registered with and the
166
+ // value checked here are the same string by construction.
167
+ const audience = typeof options.audience === "function"
168
+ ? options.audience(url)
169
+ : (options.audience ?? `${origin}${rpcPath}`);
170
+ const privateJwk = parsePrivateJwk(secrets.signingKey);
171
+ const signing = { privateJwk, jku: `${origin}${jwksPath}` };
172
+ const cardFor = (manifest, tenant) => buildBaseCard(manifest, {
108
173
  origin,
109
174
  rpcPath: options.rpcPath,
110
- advertiseSecuritySchemes: options.advertiseSecuritySchemes
111
- };
112
- // (1) Card-signing public JWKS — resolves the card's `jku` for the gateway.
175
+ advertiseSecuritySchemes: options.advertiseSecuritySchemes,
176
+ tenant
177
+ });
178
+ // (1) Card-signing public JWKS — resolves every card's `jku` for the
179
+ // gateway. One key per origin, so one JWKS for every tenant.
113
180
  if (request.method === "GET" && url.pathname === jwksPath) {
114
181
  return Response.json(publicCardJwks(privateJwk), {
115
182
  headers: { "cache-control": "public, max-age=3600" }
116
183
  });
117
184
  }
118
- // (2) Signed AgentCard discovery. `signCard` returns the protobuf-JSON
119
- // encoding — the exact document the signature is computed over.
185
+ // (2) The stub card, at the well-known path. `signCard` returns the
186
+ // protobuf-JSON encoding — the exact document the signature is computed
187
+ // over — so it is serialized here without re-encoding.
120
188
  if (request.method === "GET" && url.pathname.endsWith(AGENT_CARD_PATH)) {
121
- const card = await signCard(buildBaseCard(options.manifest, cardOptions), { privateJwk, jku: `${origin}${jwksPath}` });
122
- return Response.json(card);
189
+ return Response.json(await signCard(cardFor(options.manifest), signing));
123
190
  }
124
191
  // (3) A2A JSON-RPC — gateway-authenticated, dispatched into the caller's DO.
125
- if (request.method === "POST") {
192
+ //
193
+ // Matched on `rpcPath`, not on the method alone. Accepting every POST made
194
+ // the path this agent advertises purely decorative: a call to any URL on the
195
+ // origin was served as JSON-RPC, so a mounted agent's isolation rested
196
+ // entirely on an outer router matching first, and a typo'd endpoint quietly
197
+ // worked instead of 404ing.
198
+ if (request.method === "POST" && url.pathname === rpcPath) {
126
199
  const token = bearerToken(request);
127
200
  if (!token)
128
201
  return unauthorized("missing gateway bearer token");
129
202
  let identity;
203
+ let authorizedTenant;
130
204
  try {
131
- ({ identity } = await verifyGatewayToken(token, {
132
- allowedOrigins: parseGatewayOrigins(env.GATEWAY_ORIGINS),
133
- audience: origin,
134
- identityClaim: options.identityClaim
205
+ ({ identity, tenant: authorizedTenant } = await verifyGatewayToken(token, {
206
+ allowedOrigins: parseGatewayOrigins(secrets.gatewayOrigins),
207
+ audience,
208
+ identityClaim: options.identityClaim,
209
+ tenantClaim: options.tenantClaim
135
210
  }));
136
211
  }
137
212
  catch (err) {
@@ -152,8 +227,37 @@ export function createA2AWorker(options) {
152
227
  // throwing on the property reads below instead of reaching the SDK
153
228
  // handler, which maps it to the same -32602 that `[]` or a string gets.
154
229
  const rpcBody = (typeof body === "object" && body !== null ? body : {});
155
- const card = buildBaseCard(options.manifest, cardOptions);
156
- const context = buildCallContext(request, identity);
230
+ // Which agent this call is for. Read straight off the body rather than
231
+ // through a codec: every request message carries `tenant` in the same
232
+ // place, so decoding as one specific request type would mean knowing the
233
+ // method first, and the tenant is needed to pick the handler that would
234
+ // decide that.
235
+ const requestedTenant = rpcBody.params?.tenant ?? "";
236
+ if (typeof requestedTenant !== "string" || !requestedTenant) {
237
+ return jsonRpcErrorResponse(body, toJsonRpcError(new RequestMalformedError("params.tenant is required: this endpoint serves several agents " +
238
+ "and none is the default. Use the tenant declared on the " +
239
+ "interface of the agent's card (A2A §8.3.2), or call " +
240
+ "GetExtendedAgentCard with it to fetch that card.")));
241
+ }
242
+ // The token names the tenant the gateway authorized; the body names the
243
+ // one the call addressed. They must agree.
244
+ //
245
+ // This is the check that makes `tenant` more than a routing hint. Every
246
+ // tenant here shares one endpoint and so one `aud`, which means the
247
+ // audience cannot tell them apart — without this, a token legitimately
248
+ // minted for one agent could be replayed against any of its siblings by
249
+ // editing a field in the request body. An absent claim is a rejection
250
+ // rather than a wildcard for the same reason.
251
+ if (authorizedTenant !== requestedTenant) {
252
+ return unauthorized(`gateway token authorizes tenant '${authorizedTenant || "<none>"}', ` +
253
+ `but the request addressed '${requestedTenant}'`);
254
+ }
255
+ const agent = tenantAgent(requestedTenant);
256
+ if (!agent) {
257
+ return jsonRpcErrorResponse(body, toJsonRpcError(new RequestMalformedError(`unknown tenant '${requestedTenant}'`)));
258
+ }
259
+ const card = cardFor(agent.manifest, requestedTenant);
260
+ const context = buildCallContext(request, identity, requestedTenant);
157
261
  // v1.0 negotiates the protocol version per request via the `A2A-Version`
158
262
  // header, and the SDK leaves enforcement to the transport binding (its
159
263
  // Express handlers do it; this Worker is the equivalent seam). An absent
@@ -174,12 +278,31 @@ export function createA2AWorker(options) {
174
278
  return jsonRpcErrorResponse(body, toJsonRpcError(new RequestMalformedError(contractError)));
175
279
  }
176
280
  }
177
- const handler = new DefaultRequestHandler(card, new DurableTaskStore(identity, options.resolveAgent), new A2AExecutor({
281
+ const handler = new DefaultRequestHandler(card, new DurableTaskStore(identity, agent.resolveAgent), new A2AExecutor({
178
282
  identity,
179
283
  jku: `${origin}${jwksPath}`,
180
- resolveAgent: options.resolveAgent,
181
- startTurn: options.startTurn
182
- }));
284
+ resolveAgent: agent.resolveAgent,
285
+ startTurn: agent.startTurn
286
+ }), undefined, undefined, undefined,
287
+ // `GetExtendedAgentCard` — how a caller gets a *tenant's* card, since
288
+ // only the stub is discoverable at the well-known path.
289
+ //
290
+ // Returns the in-memory card and lets the SDK encode it: its JSON-RPC
291
+ // transport runs `AgentCard.toJSON()` over whatever comes back, so
292
+ // handing it `signCard`'s already-encoded wire card would encode twice
293
+ // and silently break the signature. See {@link signCardInPlace}.
294
+ //
295
+ // The tenant is read off the context rather than the request params
296
+ // because that is all the SDK passes; it is the value this Worker
297
+ // already authorized and routed on.
298
+ async (ctx) => {
299
+ const requested = ctx.tenant ?? "";
300
+ const target = tenantAgent(requested);
301
+ if (!target) {
302
+ throw new RequestMalformedError(`unknown tenant '${requested}'`);
303
+ }
304
+ return signCardInPlace(cardFor(target.manifest, requested), signing);
305
+ });
183
306
  const rpc = new JsonRpcTransportHandler(handler);
184
307
  const result = await rpc.handle(body, context);
185
308
  // Streaming is not advertised; reject async generators outright.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loopingai/core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Shared, mandatory foundation for Looping agents on Cloudflare Workers: zero-trust A2A, durable task lifecycle, delegation and subagent runtime, test harness.",
5
5
  "keywords": [
6
6
  "a2a",
@@ -69,6 +69,10 @@
69
69
  "types": "./dist/testing/index.d.ts",
70
70
  "import": "./dist/testing/index.js"
71
71
  },
72
+ "./testing/fixtures": {
73
+ "types": "./dist/testing/fixtures.d.ts",
74
+ "import": "./dist/testing/fixtures.js"
75
+ },
72
76
  "./testing/node": {
73
77
  "types": "./dist/testing/node.d.ts",
74
78
  "import": "./dist/testing/node.js"
@@ -102,13 +106,17 @@
102
106
  "zod": "^4.4.3"
103
107
  },
104
108
  "peerDependencies": {
109
+ "@cloudflare/vitest-pool-workers": "^0.18.8",
105
110
  "agents": "^0.20.0",
106
111
  "ai": "^7.0.40",
107
- "undici": ">=8",
112
+ "undici": "^7.28.0",
108
113
  "vitest": ">=4",
109
114
  "workers-ai-provider": "^4.0.0"
110
115
  },
111
116
  "peerDependenciesMeta": {
117
+ "@cloudflare/vitest-pool-workers": {
118
+ "optional": true
119
+ },
112
120
  "undici": {
113
121
  "optional": true
114
122
  },
@@ -117,7 +125,7 @@
117
125
  }
118
126
  },
119
127
  "devDependencies": {
120
- "@cloudflare/vitest-pool-workers": "^0.20.1",
128
+ "@cloudflare/vitest-pool-workers": "^0.18.8",
121
129
  "@types/node": "^26.1.1",
122
130
  "agents": "^0.20.0",
123
131
  "ai": "^7.0.40",
@@ -126,7 +134,7 @@
126
134
  "prettier": "^3.9.6",
127
135
  "typescript": "^6.0.3",
128
136
  "typescript-eslint": "^8.65.0",
129
- "undici": "8.9.0",
137
+ "undici": "7.28.0",
130
138
  "vitest": "^4.1.10",
131
139
  "workers-ai-provider": "^4.0.0",
132
140
  "wrangler": "^4.114.0"